Compare commits

..

7 Commits

Author SHA1 Message Date
Michele Caini
965c440d86 update single include file 2022-04-28 09:16:13 +02:00
Michele Caini
6b2aff821c ready to cut version 3.10.1 2022-04-28 09:15:38 +02:00
Michele Caini
84d9125ea1 meta: re-added meta_type::remove_pointer (close #878) 2022-04-28 09:14:03 +02:00
Michele Caini
9b969e762c meta (close #872):
* container traits don't really support plain arrays anymore (if they ever did)
* fix an issue with insert/erase of meta sequence containers
* add non-regression tests
2022-04-28 09:13:51 +02:00
Corentin Schreiber
2695d48ba7 registry: overflow of version and max number of entities (#864) 2022-04-28 09:12:25 +02:00
Michele Caini
aba2a6b17d config: decouple ENTT_NOEXCEPT and exceptions handling (close #867) 2022-04-28 09:12:13 +02:00
Michele Caini
7b87d17d22 resource: added more comparison operators for resource handles 2022-04-28 09:11:58 +02:00
204 changed files with 30833 additions and 62381 deletions

View File

@@ -1,61 +0,0 @@
name: analyzer
on:
push:
branches:
- master
- wip
jobs:
iwyu:
timeout-minutes: 30
env:
IWYU: 0.19
LLVM: 15
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v3
- name: Install llvm/clang
# see: https://apt.llvm.org/
run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-$LLVM main"
sudo apt update
sudo apt remove -y "llvm*"
sudo apt remove -y "libclang-dev*"
sudo apt remove -y "clang*"
sudo apt install -y llvm-$LLVM-dev
sudo apt install -y libclang-$LLVM-dev
sudo apt install -y clang-$LLVM
- name: Compile iwyu
# see: https://github.com/include-what-you-use/include-what-you-use
working-directory: build
run: |
git clone https://github.com/include-what-you-use/include-what-you-use.git --branch $IWYU --depth 1
mkdir include-what-you-use/build
cd include-what-you-use/build
cmake -DCMAKE_C_COMPILER=clang-$LLVM \
-DCMAKE_CXX_COMPILER=clang++-$LLVM \
-DCMAKE_INSTALL_PREFIX=./ \
..
make -j4
bin/include-what-you-use --version
- name: Compile tests
working-directory: build
run: |
export PATH=$PATH:${GITHUB_WORKSPACE}/build/include-what-you-use/build/bin
cmake -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" \
..
make -j4

View File

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

View File

@@ -9,11 +9,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Compile tests
working-directory: build
env:
CXXFLAGS: "--coverage -fno-elide-constructors -fno-inline -fno-default-inline"
CXXFLAGS: "--coverage -fno-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@v3
uses: codecov/codecov-action@v2
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: build/coverage.info

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
# EnTT
#
cmake_minimum_required(VERSION 3.15.7)
cmake_minimum_required(VERSION 3.12.4)
#
# Read project version
@@ -31,15 +31,9 @@ endif()
message(VERBOSE "*")
message(VERBOSE "* ${PROJECT_NAME} v${PROJECT_VERSION} (${CMAKE_BUILD_TYPE})")
message(VERBOSE "* Copyright (c) 2017-2023 Michele Caini <michele.caini@gmail.com>")
message(VERBOSE "* Copyright (c) 2017-2022 Michele Caini <michele.caini@gmail.com>")
message(VERBOSE "*")
#
# CMake stuff
#
list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
#
# Compiler stuff
#
@@ -143,24 +137,21 @@ 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>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/registry.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/runtime_view.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/sigh_storage_mixin.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/snapshot.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/sparse_set.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/storage.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/utility.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/view.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/graph/adjacency_matrix.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/graph/dot.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/graph/flow.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/graph/fwd.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/locator/locator.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/meta/adl_pointer.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/meta/container.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/meta/context.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/meta/ctx.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/meta/factory.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/meta/fwd.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/meta/meta.hpp>
@@ -175,7 +166,6 @@ if(ENTT_INCLUDE_HEADERS)
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/platform/android-ndk-r17.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/poly/fwd.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/poly/poly.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/process/fwd.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/process/process.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/process/scheduler.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/resource/cache.hpp>
@@ -200,7 +190,6 @@ if(ENTT_HAS_NATVIS)
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/natvis/entt/container.natvis>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/natvis/entt/core.natvis>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/natvis/entt/entity.natvis>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/natvis/entt/graph.natvis>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/natvis/entt/locator.natvis>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/natvis/entt/meta.natvis>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/natvis/entt/platform.natvis>
@@ -224,12 +213,8 @@ endif()
# Install pkg-config file
#
include(JoinPaths)
set(EnTT_PKGCONFIG ${CMAKE_CURRENT_BINARY_DIR}/entt.pc)
join_paths(EnTT_PKGCONFIG_INCLUDEDIR "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}")
configure_file(
${EnTT_SOURCE_DIR}/cmake/in/entt.pc.in
${EnTT_PKGCONFIG}
@@ -323,3 +308,22 @@ if(ENTT_BUILD_DOCS)
add_subdirectory(docs)
endif()
endif()
#
# AOB
#
add_custom_target(
aob
SOURCES
.github/workflows/build.yml
.github/workflows/coverage.yml
.github/workflows/deploy.yml
.github/workflows/sanitizer.yml
.github/FUNDING.yml
AUTHORS
CONTRIBUTING.md
LICENSE
README.md
TODO
)

View File

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

View File

@@ -6,16 +6,13 @@
[![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)
[![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/)
[![Documentation](https://img.shields.io/badge/docs-docsforge-blue)](http://entt.docsforge.com/)
[![Gitter chat](https://badges.gitter.im/skypjack/entt.png)](https://gitter.im/skypjack/entt)
[![Discord channel](https://img.shields.io/discord/707607951396962417?logo=discord)](https://discord.gg/5BjPWBd)
[![Donate](https://img.shields.io/badge/donate-paypal-blue.svg)](https://www.paypal.me/skypjack)
> `EnTT` has been a dream so far, we haven't found a single bug to date and it's
> super easy to work with
>
> -- Every EnTT User Ever
`EnTT` is a header-only, tiny and easy to use library for game programming and
much more written in **modern C++**.<br/>
@@ -81,16 +78,14 @@ codebase has grown as more and more classes and functionalities were added.<br/>
Here is a brief, yet incomplete list of what it offers today:
* Built-in **RTTI system** mostly similar to the standard one.
* A `constexpr` utility for human-readable **resource names**.
* A `constexpr` utility for human readable **resource names**.
* Minimal **configuration system** built using the monostate pattern.
* Incredibly fast **entity-component system** with its own _pay for what you
use_ policy, unconstrained component types with optional pointer stability and
hooks for storage customization.
use_ policy.
* Views and groups to iterate entities and components and allow different access
patterns, from **perfect SoA** to fully random.
* A lot of **facilities** built on top of the entity-component system to help
the users and avoid reinventing the wheel.
* General purpose **execution graph builder** for optimal scheduling.
* The smallest and most basic implementation of a **service locator** ever seen.
* A built-in, non-intrusive and macro-free runtime **reflection system**.
* **Static polymorphism** made simple and within everyone's reach.
@@ -102,7 +97,7 @@ Here is a brief, yet incomplete list of what it offers today:
* And **much more**! Check out the
[**wiki**](https://github.com/skypjack/entt/wiki).
Consider this list a work in progress as well as the project. The whole API is
Consider these lists a work in progress as well as the project. The whole API is
fully documented in-code for those who are brave enough to read it.<br/>
Please, do note that all tools are also DLL-friendly now and run smoothly across
boundaries.
@@ -344,8 +339,8 @@ The documentation is based on [doxygen](http://www.doxygen.nl/). To build it:
$ cmake .. -DENTT_BUILD_DOCS=ON
$ make
The API reference is created in HTML format in the `build/docs/html` directory.
To navigate it with your favorite browser:
The API reference will be created in HTML format within the directory
`build/docs/html`. To navigate it with your favorite browser:
$ cd build
$ your_favorite_browser docs/html/index.html
@@ -354,7 +349,10 @@ To navigate it with your favorite browser:
@cond TURN_OFF_DOXYGEN
-->
The same version is also available [online](https://skypjack.github.io/entt/)
for the latest release, that is the last stable tag.<br/>
for the latest release, that is the last stable tag. If you are looking for
something more pleasing to the eye, consider reading the nice-looking version
available on [docsforge](https://entt.docsforge.com/): same documentation, much
more pleasant to read.<br/>
Moreover, there exists a [wiki](https://github.com/skypjack/entt/wiki) dedicated
to the project where users can find all related documentation pages.
<!--
@@ -364,8 +362,9 @@ to the project where users can find all related documentation pages.
# Tests
To compile and run the tests, `EnTT` requires *googletest*.<br/>
`cmake` downloads and compiles the library before compiling anything else. In
order to build the tests, set the `CMake` option `ENTT_BUILD_TESTING` to `ON`.
`cmake` will download and compile the library before compiling anything else.
In order to build the tests, set the `CMake` option `ENTT_BUILD_TESTING` to
`ON`.
To build the most basic set of tests:
@@ -417,7 +416,7 @@ know who has participated so far.
# License
Code and documentation Copyright (c) 2017-2023 Michele Caini.<br/>
Code and documentation Copyright (c) 2017-2022 Michele Caini.<br/>
Colorful logo Copyright (c) 2018-2021 Richard Caseres.
Code released under

38
TODO
View File

@@ -1,26 +1,26 @@
* debugging tools (#60): the issue online already contains interesting tips on this, look at it
* work stealing job system (see #100) + mt scheduler based on const awareness for types
* add examples (and credits) from @alanjfs :)
EXAMPLES
* filter on runtime values/variables (not only types)
* support to polymorphic types (see #859)
DOC:
* 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):
* 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:
* view/group: no storage_traits dependency -> use storage instead of components for the definition
* basic_storage::bind for cross-registry setups
* uses-allocator construction: any (with allocator support), poly, ...
* process scheduler: reviews, use free lists internally
* iterator based try_emplace vs try_insert for perf reasons
* dedicated entity storage, in-place O(1) release/destroy for non-orphaned entities, out-of-sync model
* entity-only and exclude-only views
* custom allocators all over (sigh storage mixin, registry, ...)
* consider removing ENTT_NOEXCEPT, use ENTT_NOEXCEPT_IF (or noexcept(...)) as appropriate in any case (ie make compressed_pair conditionally noexcept)
* add test for maximum number of entities reached
WIP:
* get rid of observers, storage based views made them pointless - document alternatives
* exploit the tombstone mechanism to allow enabling/disabling entities (see bump, compact and clear for further details)
* process scheduler: reviews, use free lists internally
* add user data to type_info
* write documentation for custom storages and views!!
* make runtime views use opaque storage and therefore return also elements.
* entity-aware observer, add observer functions aside observer class
* deprecate non-owning groups in favor of owning views and view packs, introduce lazy owning views
* bring nested groups back in place (see bd34e7f)
* work stealing job system (see #100) + mt scheduler based on const awareness for types

View File

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

View File

@@ -1,23 +0,0 @@
# This module provides function for joining paths
# known from most languages
#
# SPDX-License-Identifier: (MIT OR CC0-1.0)
# Copyright 2020 Jan Tojnar
# https://github.com/jtojnar/cmake-snips
#
# Modelled after Pythons os.path.join
# https://docs.python.org/3.7/library/os.path.html#os.path.join
# Windows not supported
function(join_paths joined_path first_path_segment)
set(temp_path "${first_path_segment}")
foreach(current_segment IN LISTS ARGN)
if(NOT ("${current_segment}" STREQUAL ""))
if(IS_ABSOLUTE "${current_segment}")
set(temp_path "${current_segment}")
else()
set(temp_path "${temp_path}/${current_segment}")
endif()
endif()
endforeach()
set(${joined_path} "${temp_path}" PARENT_SCOPE)
endfunction()

View File

@@ -2,22 +2,8 @@
# Doxygen configuration (documentation)
#
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_DEPS_DIRECTORY ${EnTT_SOURCE_DIR}/deps)
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.6
# Doxyfile 1.9.1
# This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project.
@@ -12,16 +12,6 @@
# For lists, items can also be appended using:
# TAG += value [value, ...]
# Values that contain spaces should be placed between quotes (\" \").
#
# Note:
#
# Use doxygen to compare the used configuration file with the template
# configuration file:
# doxygen -x [configFile]
# Use doxygen to compare the used configuration file with the template
# configuration file without replacing the environment variables or CMake type
# replacement variables:
# doxygen -x_noenv [configFile]
#---------------------------------------------------------------------------
# Project related configuration options
@@ -70,28 +60,16 @@ PROJECT_LOGO =
OUTPUT_DIRECTORY = @DOXY_OUTPUT_DIRECTORY@
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096
# sub-directories (in 2 levels) under the output directory of each output format
# and will distribute the generated files over these directories. Enabling this
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
# directories (in 2 levels) under the output directory of each output format and
# will distribute the generated files over these directories. Enabling this
# option can be useful when feeding doxygen a huge amount of source files, where
# putting all generated files in the same directory would otherwise causes
# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to
# control the number of sub-directories.
# performance problems for the file system.
# The default value is: NO.
CREATE_SUBDIRS = NO
# Controls the number of sub-directories that will be created when
# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every
# 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
# 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.
CREATE_SUBDIRS_LEVEL = 8
# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
# characters to appear in the names of generated files. If set to NO, non-ASCII
# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
@@ -103,18 +81,26 @@ ALLOW_UNICODE_NAMES = NO
# The OUTPUT_LANGUAGE tag is used to specify the language in which all
# documentation generated by doxygen is written. Doxygen will use this
# information to generate all constant output in the proper language.
# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian,
# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English
# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek,
# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with
# English messages), Korean, Korean-en (Korean with English messages), Latvian,
# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese,
# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish,
# Swedish, Turkish, Ukrainian and Vietnamese.
# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
# Ukrainian and Vietnamese.
# The default value is: English.
OUTPUT_LANGUAGE = English
# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all
# documentation generated by doxygen is written. Doxygen will use this
# information to generate all generated output in the proper direction.
# Possible values are: None, LTR, RTL and Context.
# The default value is: None.
OUTPUT_TEXT_DIRECTION = None
# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
# descriptions after the members that are listed in the file and class
# documentation (similar to Javadoc). Set to NO to disable this.
@@ -262,16 +248,16 @@ TAB_SIZE = 4
# the documentation. An alias has the form:
# name=value
# For example adding
# "sideeffect=@par Side Effects:^^"
# "sideeffect=@par Side Effects:\n"
# will allow you to put the command \sideeffect (or @sideeffect) in the
# documentation, which will result in a user-defined paragraph with heading
# "Side Effects:". Note that you cannot put \n's in the value part of an alias
# to insert newlines (in the resulting output). You can put ^^ in the value part
# of an alias to insert a newline as if a physical newline was in the original
# file. When you need a literal { or } or , in the value part of an alias you
# have to escape them by means of a backslash (\), this can lead to conflicts
# with the commands \{ and \} for these it is advised to use the version @{ and
# @} or use a double escape (\\{ and \\})
# "Side Effects:". You can put \n's in the value part of an alias to insert
# newlines (in the resulting output). You can put ^^ in the value part of an
# alias to insert a newline as if a physical newline was in the original file.
# When you need a literal { or } or , in the value part of an alias you have to
# escape them by means of a backslash (\), this can lead to conflicts with the
# commands \{ and \} for these it is advised to use the version @{ and @} or use
# a double escape (\\{ and \\})
ALIASES =
@@ -316,8 +302,8 @@ OPTIMIZE_OUTPUT_SLICE = NO
# extension. Doxygen has a built-in mapping, but you can override or extend it
# using this tag. The format is ext=language, where ext is a file extension, and
# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice,
# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL,
# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
# tries to guess whether the code is fixed or free formatted code, this is the
# default for Fortran type files). For instance to make doxygen treat .inc files
@@ -464,13 +450,13 @@ TYPEDEF_HIDES_STRUCT = NO
LOOKUP_CACHE_SIZE = 0
# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use
# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use
# during processing. When set to 0 doxygen will based this on the number of
# cores available in the system. You can set it explicitly to a value larger
# than 0 to get more control over the balance between CPU load and processing
# speed. At this moment only the input processing can be done using multiple
# threads. Since this is still an experimental feature the default is set to 1,
# which effectively disables parallel processing. Please report any issues you
# which efficively disables parallel processing. Please report any issues you
# encounter. Generating dot graphs in parallel is controlled by the
# DOT_NUM_THREADS setting.
# Minimum value: 0, maximum value: 32, default value: 1.
@@ -558,8 +544,7 @@ 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
# will also hide undocumented C++ concepts if enabled. This option has no effect
# if EXTRACT_ALL is enabled.
# has no effect if EXTRACT_ALL is enabled.
# The default value is: NO.
HIDE_UNDOC_CLASSES = NO
@@ -590,15 +575,14 @@ INTERNAL_DOCS = NO
# filesystem is case sensitive (i.e. it supports files in the same directory
# whose names only differ in casing), the option must be set to YES to properly
# deal with such files in case they appear in the input. For filesystems that
# are not case sensitive the option should be set to NO to properly deal with
# are not case sensitive the option should be be set to NO to properly deal with
# output files written for symbols that only differ in casing, such as for two
# classes, one named CLASS and the other named Class, and to also support
# references to files without having to specify the exact matching casing. On
# 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.
# Possible values are: SYSTEM, NO and YES.
# The default value is: SYSTEM.
# The default value is: system dependent.
CASE_SENSE_NAMES = YES
@@ -616,12 +600,6 @@ HIDE_SCOPE_NAMES = NO
HIDE_COMPOUND_REFERENCE= NO
# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class
# will show which file needs to be included to use the class.
# The default value is: YES.
SHOW_HEADERFILE = YES
# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
# the files that are included by a file in the documentation of that file.
# The default value is: YES.
@@ -779,8 +757,7 @@ FILE_VERSION_FILTER =
# output files in an output format independent way. To create the layout file
# that represents doxygen's defaults, run doxygen with the -l option. You can
# optionally specify a file name after the option, if omitted DoxygenLayout.xml
# will be used as the name of the layout file. See also section "Changing the
# layout of pages" for information.
# will be used as the name of the layout file.
#
# Note that if you run doxygen from a directory containing a file called
# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
@@ -826,38 +803,22 @@ WARNINGS = YES
WARN_IF_UNDOCUMENTED = YES
# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
# potential errors in the documentation, such as documenting some parameters in
# a documented function twice, or documenting parameters that don't exist or
# using markup commands wrongly.
# potential errors in the documentation, such as not documenting some parameters
# in a documented function, or documenting parameters that don't exist or using
# markup commands wrongly.
# The default value is: YES.
WARN_IF_DOC_ERROR = YES
# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete
# function parameter documentation. If set to NO, doxygen will accept that some
# parameters have no documentation without warning.
# The default value is: YES.
WARN_IF_INCOMPLETE_DOC = YES
# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
# are documented, but have no documentation for their parameters or return
# value. If set to NO, doxygen will only warn about wrong parameter
# documentation, but not about the absence of documentation. If EXTRACT_ALL is
# set to YES then this flag will automatically be disabled. See also
# WARN_IF_INCOMPLETE_DOC
# value. If set to NO, doxygen will only warn about wrong or incomplete
# parameter documentation, but not about the absence of documentation. If
# EXTRACT_ALL is set to YES then this flag will automatically be disabled.
# The default value is: NO.
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
@@ -873,27 +834,13 @@ WARN_AS_ERROR = NO
# and the warning text. Optionally the format may contain $version, which will
# be replaced by the version of the file (if it could be obtained via
# FILE_VERSION_FILTER)
# See also: WARN_LINE_FORMAT
# The default value is: $file:$line: $text.
WARN_FORMAT = "$file:$line: $text"
# In the $text part of the WARN_FORMAT command it is possible that a reference
# to a more specific place is given. To make it easier to jump to this place
# (outside of doxygen) the user can define a custom "cut" / "paste" string.
# Example:
# WARN_LINE_FORMAT = "'vi $file +$line'"
# See also: WARN_FORMAT
# The default value is: at line $line of file $file.
WARN_LINE_FORMAT = "at line $line of file $file"
# The WARN_LOGFILE tag can be used to specify a file to which warning and error
# messages should be written. If left blank the output is written to standard
# error (stderr). In case the file specified cannot be opened for writing the
# warning and error messages are written to standard error. When as file - is
# specified the warning and error messages are written to standard output
# (stdout).
# error (stderr).
WARN_LOGFILE =
@@ -916,21 +863,10 @@ 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.
@@ -944,10 +880,10 @@ INPUT_FILE_ENCODING =
#
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml,
# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C
# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
# *.vhdl, *.ucf, *.qsf and *.ice.
# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),
# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl,
# *.ucf, *.qsf and *.ice.
FILE_PATTERNS = *.h \
*.hpp \
@@ -967,7 +903,7 @@ RECURSIVE = YES
# Note that relative paths are relative to the directory from which doxygen is
# run.
EXCLUDE =
EXCLUDE = @DOXY_DEPS_DIRECTORY@
# 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
@@ -989,7 +925,7 @@ EXCLUDE_PATTERNS =
# (namespaces, classes, functions, etc.) that should be excluded from the
# output. The symbol name can be a fully qualified name, a word, or if the
# wildcard * is used, a substring. Examples: ANamespace, AClass,
# ANamespace::AClass, ANamespace::*Test
# AClass::ANamespace, ANamespace::*Test
#
# Note that the wildcards are matched against the file with absolute path, so to
# exclude all test directories use the pattern */test/*
@@ -1037,11 +973,6 @@ 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.
@@ -1083,15 +1014,6 @@ 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
#---------------------------------------------------------------------------
@@ -1189,11 +1111,9 @@ VERBATIM_HEADERS = YES
CLANG_ASSISTED_PARSING = NO
# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS
# tag is set to YES then doxygen will add the directory of each input to the
# include path.
# If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to
# YES then doxygen will add the directory of each input to the include path.
# The default value is: YES.
# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
CLANG_ADD_INC_PATHS = YES
@@ -1229,11 +1149,10 @@ CLANG_DATABASE_PATH =
ALPHABETICAL_INDEX = YES
# 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.
# 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.
# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
IGNORE_PREFIX =
@@ -1302,7 +1221,7 @@ HTML_FOOTER =
# obsolete.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_STYLESHEET = @DOXY_CSS_DIRECTORY@/doxygen-awesome.css
HTML_STYLESHEET =
# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
# cascading style sheets that are included after the standard style sheets
@@ -1312,12 +1231,7 @@ HTML_STYLESHEET = @DOXY_CSS_DIRECTORY@/doxygen-awesome.css
# 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).
# 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.
# list). For an example see the documentation.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_EXTRA_STYLESHEET =
@@ -1332,22 +1246,9 @@ 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
# this color. Hue is specified as an angle on a colorwheel, see
# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
# purple, and 360 is red again.
@@ -1357,7 +1258,7 @@ HTML_COLORSTYLE = LIGHT
HTML_COLORSTYLE_HUE = 220
# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
# in the HTML output. For a value of 0 the output will use gray-scales only. A
# in the HTML output. For a value of 0 the output will use grayscales only. A
# value of 255 will produce the most vivid colors.
# Minimum value: 0, maximum value: 255, default value: 100.
# This tag requires that the tag GENERATE_HTML is set to YES.
@@ -1439,13 +1340,6 @@ GENERATE_DOCSET = NO
DOCSET_FEEDNAME = "Doxygen generated docs"
# This tag determines the URL of the docset feed. A documentation feed provides
# an umbrella under which multiple documentation sets from a single provider
# (such as a company or product suite) can be grouped.
# This tag requires that the tag GENERATE_DOCSET is set to YES.
DOCSET_FEEDURL =
# This tag specifies a string that should uniquely identify the documentation
# set bundle. This should be a reverse domain-name style string, e.g.
# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
@@ -1471,12 +1365,8 @@ DOCSET_PUBLISHER_NAME = Publisher
# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
# on Windows. In the beginning of 2021 Microsoft took the original page, with
# a.o. the download links, offline the HTML help workshop was already many years
# in maintenance mode). You can download the HTML help workshop from the web
# archives at Installation executable (see:
# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo
# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe).
# (see:
# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows.
#
# The HTML Help Workshop contains a compiler that can convert all HTML output
# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
@@ -1635,28 +1525,16 @@ DISABLE_INDEX = NO
# to work a browser that supports JavaScript, DHTML, CSS and frames is required
# (i.e. any modern browser). Windows users are probably better off using the
# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
# further fine tune the look of the index (see "Fine-tuning the output"). As an
# example, the default style sheet generated by doxygen has an example that
# shows how to put an image at the root of the tree instead of the PROJECT_NAME.
# Since the tree basically has the same information as the tab index, you could
# consider setting DISABLE_INDEX to YES when enabling this option.
# further fine-tune the look of the index. As an example, the default style
# sheet generated by doxygen has an example that shows how to put an image at
# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
# the same information as the tab index, you could consider setting
# DISABLE_INDEX to YES when enabling this option.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
GENERATE_TREEVIEW = NO
# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the
# FULL_SIDEBAR option determines if the side bar is limited to only the treeview
# area (value NO) or if it should extend to the full height of the window (value
# YES). Setting this to YES gives a layout similar to
# https://docs.readthedocs.io with more room for contents, but less room for the
# project logo, title, and description. If either GENERATE_TREEVIEW or
# DISABLE_INDEX is set to NO, this option has no effect.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
FULL_SIDEBAR = NO
# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
# doxygen will group on one line in the generated HTML documentation.
#
@@ -1681,13 +1559,6 @@ TREEVIEW_WIDTH = 250
EXT_LINKS_IN_WINDOW = NO
# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email
# addresses.
# The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to YES.
OBFUSCATE_EMAILS = YES
# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
@@ -1708,6 +1579,17 @@ 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.
@@ -1725,29 +1607,11 @@ FORMULA_MACROFILE =
USE_MATHJAX = NO
# With MATHJAX_VERSION it is possible to specify the MathJax version to be used.
# Note that the different versions of MathJax have different requirements with
# regards to the different settings, so it is possible that also other MathJax
# settings have to be changed when switching between the different MathJax
# versions.
# Possible values are: MathJax_2 and MathJax_3.
# The default value is: MathJax_2.
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_VERSION = MathJax_2
# When MathJax is enabled you can set the default output format to be used for
# the MathJax output. For more details about the output format see MathJax
# version 2 (see:
# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3
# (see:
# http://docs.mathjax.org/en/latest/web/components/output.html).
# the MathJax output. See the MathJax site (see:
# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details.
# Possible values are: HTML-CSS (which is slower, but has the best
# compatibility. This is the name for Mathjax version 2, for MathJax version 3
# this will be translated into chtml), NativeMML (i.e. MathML. Only supported
# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This
# is the name for Mathjax version 3, for MathJax version 2 this will be
# translated into HTML-CSS) and SVG.
# compatibility), NativeMML (i.e. MathML) and SVG.
# The default value is: HTML-CSS.
# This tag requires that the tag USE_MATHJAX is set to YES.
@@ -1760,21 +1624,15 @@ MATHJAX_FORMAT = HTML-CSS
# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
# Content Delivery Network so you can quickly see the result without installing
# MathJax. However, it is strongly recommended to install a local copy of
# MathJax from https://www.mathjax.org before deployment. The default value is:
# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2
# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3
# MathJax from https://www.mathjax.org before deployment.
# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2.
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/
# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
# extension names that should be enabled during MathJax rendering. For example
# for MathJax version 2 (see
# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):
# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
# For example for MathJax version 3 (see
# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
# MATHJAX_EXTENSIONS = ams
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_EXTENSIONS =
@@ -1954,31 +1812,29 @@ PAPER_TYPE = a4
EXTRA_PACKAGES =
# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for
# the generated LaTeX document. The header should contain everything until the
# first chapter. If it is left blank doxygen will generate a standard header. It
# is highly recommended to start with a default header using
# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty
# and then modify the file new_header.tex. See also section "Doxygen usage" for
# information on how to generate the default header that doxygen normally uses.
# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
# generated LaTeX document. The header should contain everything until the first
# chapter. If it is left blank doxygen will generate a standard header. See
# section "Doxygen usage" for information on how to let doxygen write the
# default header to a separate file.
#
# Note: Only use a user-defined header if you know what you are doing!
# Note: The header is subject to change so you typically have to regenerate the
# default header when upgrading to a newer version of doxygen. The following
# commands have a special meaning inside the header (and footer): For a
# description of the possible markers and block names see the documentation.
# Note: Only use a user-defined header if you know what you are doing! The
# following commands have a special meaning inside the header: $title,
# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
# string, for the replacement values of the other commands the user is referred
# to HTML_HEADER.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_HEADER =
# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for
# the generated LaTeX document. The footer should contain everything after the
# last chapter. If it is left blank doxygen will generate a standard footer. See
# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
# generated LaTeX document. The footer should contain everything after the last
# chapter. If it is left blank doxygen will generate a standard footer. See
# LATEX_HEADER for more information on how to generate a default footer and what
# special commands can be used inside the footer. See also section "Doxygen
# usage" for information on how to generate the default footer that doxygen
# normally uses. Note: Only use a user-defined footer if you know what you are
# doing!
# special commands can be used inside the footer.
#
# Note: Only use a user-defined footer if you know what you are doing!
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_FOOTER =
@@ -2023,7 +1879,8 @@ USE_PDFLATEX = YES
# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
# command to the generated LaTeX files. This will instruct LaTeX to keep running
# if errors occur, instead of asking the user for help.
# if errors occur, instead of asking the user for help. This option is also used
# when generating formulas in HTML.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.
@@ -2036,6 +1893,16 @@ LATEX_BATCHMODE = NO
LATEX_HIDE_INDICES = NO
# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
# code with syntax highlighting in the LaTeX output.
#
# Note that which sources are shown also depends on other settings such as
# SOURCE_BROWSER.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_SOURCE_CODE = NO
# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
# bibliography, e.g. plainnat, or ieeetr. See
# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
@@ -2116,6 +1983,16 @@ RTF_STYLESHEET_FILE =
RTF_EXTENSIONS_FILE =
# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
# with syntax highlighting in the RTF output.
#
# Note that which sources are shown also depends on other settings such as
# SOURCE_BROWSER.
# The default value is: NO.
# This tag requires that the tag GENERATE_RTF is set to YES.
RTF_SOURCE_CODE = NO
#---------------------------------------------------------------------------
# Configuration options related to the man page output
#---------------------------------------------------------------------------
@@ -2212,6 +2089,15 @@ GENERATE_DOCBOOK = NO
DOCBOOK_OUTPUT = docbook
# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
# program listings (including syntax highlighting and cross-referencing
# information) to the DOCBOOK output. Note that enabling this will significantly
# increase the size of the DOCBOOK output.
# The default value is: NO.
# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
DOCBOOK_PROGRAMLISTING = NO
#---------------------------------------------------------------------------
# Configuration options for the AutoGen Definitions output
#---------------------------------------------------------------------------
@@ -2298,8 +2184,7 @@ SEARCH_INCLUDES = YES
# The INCLUDE_PATH tag can be used to specify one or more directories that
# contain include files that are not input files but should be processed by the
# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of
# RECURSIVE has no effect here.
# preprocessor.
# This tag requires that the tag SEARCH_INCLUDES is set to YES.
INCLUDE_PATH =
@@ -2391,6 +2276,15 @@ EXTERNAL_PAGES = YES
# Configuration options related to the dot tool
#---------------------------------------------------------------------------
# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
# NO turns the diagrams off. Note that this option also works with HAVE_DOT
# disabled, but it is recommended to install and use dot, since it yields more
# powerful graphs.
# The default value is: YES.
CLASS_DIAGRAMS = YES
# You can include diagrams made with dia in doxygen documentation. Doxygen will
# then run dia to produce the diagram and insert it in the documentation. The
# DIA_PATH tag allows you to specify the directory where the dia binary resides.
@@ -2409,7 +2303,7 @@ HIDE_UNDOC_RELATIONS = YES
# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
# Bell Labs. The other options in this section have no effect if this option is
# set to NO
# The default value is: NO.
# The default value is: YES.
HAVE_DOT = YES
@@ -2423,50 +2317,35 @@ HAVE_DOT = YES
DOT_NUM_THREADS = 0
# 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.
# 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.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10"
DOT_FONTNAME = Helvetica
# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can
# 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.
# 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.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10"
DOT_FONTSIZE = 10
# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes
# 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.
# 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.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTPATH =
# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a
# graph for each documented class showing the direct and indirect inheritance
# relations. In case HAVE_DOT is set as well dot will be used to draw the graph,
# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set
# to TEXT the direct and indirect inheritance relations will be shown as texts /
# links.
# Possible values are: NO, YES, TEXT and GRAPH.
# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
# each documented class showing the direct and indirect inheritance relations.
# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.
CLASS_GRAPH = YES
@@ -2480,8 +2359,7 @@ CLASS_GRAPH = YES
COLLABORATION_GRAPH = YES
# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
# groups, showing the direct groups dependencies. See also the chapter Grouping
# in the manual.
# groups, showing the direct groups dependencies.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.
@@ -2596,13 +2474,6 @@ GRAPHICAL_HIERARCHY = YES
DIRECTORY_GRAPH = YES
# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels
# of child directories generated in directory dependency graphs by dot.
# Minimum value: 1, maximum value: 25, default value: 1.
# This tag requires that the tag DIRECTORY_GRAPH is set to YES.
DIR_GRAPH_MAX_DEPTH = 1
# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
# generated by dot. For an explanation of the image formats see the section
# output formats in the documentation of the dot tool (Graphviz (see:
@@ -2610,7 +2481,9 @@ DIR_GRAPH_MAX_DEPTH = 1
# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
# to make the SVG files visible in IE 9+ (other browsers do not have this
# requirement).
# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd,
# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo,
# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo,
# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
# png:gdiplus:gdiplus.
# The default value is: png.
@@ -2656,10 +2529,10 @@ MSCFILE_DIRS =
DIAFILE_DIRS =
# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
# path where java can find the plantuml.jar file or to the filename of jar file
# to be used. If left blank, it is assumed PlantUML is not used or called during
# a preprocessing step. Doxygen will generate a warning when it encounters a
# \startuml command in this case and will not generate output for the diagram.
# path where java can find the plantuml.jar file. If left blank, it is assumed
# PlantUML is not used or called during a preprocessing step. Doxygen will
# generate a warning when it encounters a \startuml command in this case and
# will not generate output for the diagram.
PLANTUML_JAR_PATH =
@@ -2697,6 +2570,18 @@ 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
@@ -2709,8 +2594,6 @@ DOT_MULTI_TARGETS = NO
# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
# explaining the meaning of the various boxes and arrows in the dot generated
# graphs.
# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal
# graphical representation for inheritance and collaboration diagrams is used.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.
@@ -2719,8 +2602,8 @@ GENERATE_LEGEND = YES
# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate
# files that are used to generate the various graphs.
#
# Note: This setting is not only used for dot files but also for msc temporary
# files.
# Note: This setting is not only used for dot files but also for msc and
# plantuml temporary files.
# The default value is: YES.
DOT_CLEANUP = YES

View File

@@ -7,25 +7,24 @@
* [Introduction](#introduction)
* [Definitions](#definitions)
* [ENTT_NOEXCEPTION](#entt_noexception)
* [ENTT_NOEXCEPTION](#entt_noexcept)
* [ENTT_USE_ATOMIC](#entt_use_atomic)
* [ENTT_ID_TYPE](#entt_id_type)
* [ENTT_SPARSE_PAGE](#entt_sparse_page)
* [ENTT_PACKED_PAGE](#entt_packed_page)
* [ENTT_ASSERT](#entt_assert)
* [ENTT_ASSERT_CONSTEXPR](#entt_assert_constexpr)
* [ENTT_DISABLE_ASSERT](#entt_disable_assert)
* [ENTT_NO_ETO](#entt_no_eto)
* [ENTT_STANDARD_CPP](#entt_standard_cpp)
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Introduction
`EnTT` has become almost completely customizable over time, in many
respects. These variables are just one of the many ways to customize how it
works.<br/>
`EnTT` doesn't offer many hooks for customization but it certainly offers
some.<br/>
In the vast majority of cases, users will have no interest in changing the
default parameters. For all other cases, the list of possible configurations
with which it's possible to adjust the behavior of the library at runtime can be
@@ -41,17 +40,17 @@ will remain stable over time unlike the options below.
## ENTT_NOEXCEPTION
Define this variable without assigning any value to it to turn off exception
handling in `EnTT`.<br/>
This is roughly equivalent to setting the compiler flag `-fno-exceptions` but is
also limited to this library only.
This parameter can be used to switch off exception handling in `EnTT`.<br/>
To do this, simply define the variable without assigning any value to it. This
is roughly equivalent to setting the compiler flag `-ff-noexceptions`.
## ENTT_USE_ATOMIC
In general, `EnTT` doesn't offer primitives to support multi-threading. Many of
the features can be split over multiple threads without any explicit control and
the user is the one who knows if a synchronization point is required.<br/>
However, some features aren't easily accessible to users and are made
the user is the only one who knows if and when a synchronization point is
required.<br/>
However, some features aren't easily accessible to users and can be made
thread-safe by means of this definition.
## ENTT_ID_TYPE
@@ -71,9 +70,9 @@ power of 2.
## ENTT_PACKED_PAGE
As it happens with sparse arrays, packed arrays are also paginated. However, in
this case the aim isn't to reduce memory usage but to have pointer stability
upon component creation.<br/>
Similar to sparse arrays, packed arrays of components are paginated as well. In
However, int this case the aim isn't to reduce memory usage but to have pointer
stability upon component creation.<br/>
Default size of pages (that is, the number of elements they contain) is 1024 but
users can adjust it if appropriate. In all case, the chosen value **must** be a
power of 2.
@@ -84,29 +83,21 @@ For performance reasons, `EnTT` doesn't use exceptions or any other control
structures. In fact, it offers many features that result in undefined behavior
if not used correctly.<br/>
To get around this, the library relies on a lot of asserts for the purpose of
detecting errors in debug builds. By default, it uses `assert` internally. Users
are allowed to overwrite its behavior by setting this variable.
### ENTT_ASSERT_CONSTEXPR
Usually, an assert within a `constexpr` function isn't a big deal. However, in
case of extreme customizations, it might be useful to differentiate.<br/>
For this purpose, `EnTT` introduces an admittedly badly named variable to make
the job easier in this regard. By default, this variable forwards its arguments
to `ENTT_ASSERT`.
detecting errors in debug builds. By default, it uses `assert` internally, but
users are allowed to overwrite its behavior by setting this variable.
### ENTT_DISABLE_ASSERT
Assertions may in turn affect performance to an extent when enabled. Whether
`ENTT_ASSERT` and `ENTT_ASSERT_CONSTEXPR` are redefined or not, all asserts can
be disabled at once by means of this definition.<br/>
Note that `ENTT_DISABLE_ASSERT` takes precedence over the redefinition of the
other variables and is therefore meant to disable all controls no matter what.
`ENTT_ASSERT` is redefined or not, all asserts can be disabled at once by means
of this definition.<br/>
Note that `ENTT_DISABLE_ASSERT` takes precedence over the redefinition of
`ENTT_ASSERT` and is therefore meant to disable all controls no matter what.
## ENTT_NO_ETO
In order to reduce memory consumption and increase performance, empty types are
never instantiated nor stored by the ECS module of `EnTT`.<br/>
never stored by the ECS module of `EnTT`.<br/>
Use this variable to treat these types like all others and therefore to create a
dedicated storage for them.
@@ -114,7 +105,7 @@ dedicated storage for them.
`EnTT` mixes non-standard language features with others that are perfectly
compliant to offer some of its functionalities.<br/>
This definition prevents the library from using non-standard techniques, that
is, functionalities that aren't fully compliant with the standard C++.<br/>
This definition will prevent the library from using non-standard techniques,
that is, functionalities that aren't fully compliant with the standard C++.<br/>
While there are no known portability issues at the time of this writing, this
should make the library fully portable anyway if needed.

View File

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

View File

@@ -14,10 +14,6 @@
* [Hashed strings](#hashed-strings)
* [Wide characters](wide-characters)
* [Conflicts](#conflicts)
* [Iterators](#iterators)
* [Input iterator pointer](#input-iterator-pointer)
* [Iota iterator](#iota-iterator)
* [Iterable adaptor](#iterable-adaptor)
* [Memory](#memory)
* [Power of two and fast modulus](#power-of-two-and-fast-modulus)
* [Allocator aware unique pointers](#allocator-aware-unique-pointers)
@@ -31,7 +27,6 @@
* [Is applicable](#is-applicable)
* [Constness as](#constness-as)
* [Member class type](#member-class-type)
* [N-th argument](#n-th-argument)
* [Integral constant](#integral-constant)
* [Tag](#tag)
* [Type list and value list](#type-list-and-value-list)
@@ -46,24 +41,25 @@
# Introduction
`EnTT` comes with a bunch of core functionalities mostly used by the other parts
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.
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.
# Any as in any type
`EnTT` offers its own `any` type. It may seem redundant considering that C++17
introduced `std::any`, but it is not (hopefully).<br/>
`EnTT` comes with 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 bind it to the type
system of the library and therefore with its integrated RTTI support.
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.
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.
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.
Creating an object of the `any` type, whether empty or not, is trivial:
@@ -92,8 +88,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 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
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
the object is used in _aliasing mode_, as described below:
```cpp
@@ -107,15 +103,15 @@ any.assign(value);
any.assign(std::move(value));
```
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.
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.
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:
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:
```cpp
if(any == empty) { /* ... */ }
@@ -124,7 +120,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 the possible risks of a comparison.
`type_info` works and on possible risks of a comparison.
A particularly interesting feature of this class is that it can also be used as
an opaque container for const and non-const references:
@@ -152,19 +148,22 @@ entt::any ref = other.as_ref();
```
In this case, it doesn't matter if the original container actually holds an
object or is as a reference for unmanaged elements already. The new instance
thus created doesn't create copies and only serves as a reference for the
original item.
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.
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/>
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/>
In particular, the `data` member function invoked on a non-const instance of
`any` that wraps a const reference returns a null pointer in all cases.
`any` that wraps a const reference will return a null pointer in all cases.
To cast an instance of `any` to a type, the library offers a set of `any_cast`
functions in all respects similar to their most famous counterparts.<br/>
The only difference is that, in the case of `EnTT`, they won't raise exceptions
The only difference is that, in the case of `EnTT`, these won't raise exceptions
but will only trigger an assert in debug mode, otherwise resulting in undefined
behavior in case of misuse in release mode.
@@ -184,23 +183,31 @@ using my_any = entt::basic_any<sizeof(double[4])>;
This feature, in addition to allowing the choice of a size that best suits the
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` suppresses the small buffer optimization
and always dynamically allocates objects (except for aliasing cases).
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.
## 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/>
It's provided as an optional second parameter following the desired size for the
internal storage:
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:
```cpp
using my_any = entt::basic_any<sizeof(double[4]), alignof(double[4])>;
```
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.
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.
# Compressed pair
@@ -213,8 +220,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 requirements:
no` entt::make_compressed_pair`), the major difference is that `first` and
`second` are functions for implementation needs:
```cpp
entt::compressed_pair pair{0, 3.};
@@ -227,15 +234,16 @@ 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. 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.
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.
Fortunately, there is also a third way: adding enough operators in the global
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):
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):
```cpp
enum class my_flag {
@@ -248,11 +256,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 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 will
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 the enum classes for which bitmask support
library requires users to register all enum classes for which bitmask support
should be enabled:
```cpp
@@ -263,7 +271,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 user has no control. However, it's also verbose and can be
and over which the users have no control. However, it's also verbose and can be
avoided by adding a specific value to the enum class itself:
```cpp
@@ -276,21 +284,23 @@ enum class my_flag {
```
In this case, there is no need to specialize the `enum_as_bitmask` traits, since
`EnTT` automatically detects the flag and enables the bitmask support.<br/>
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.
`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 `|=`.
Refer to the official documentation for the full list of operators.
# Hashed strings
Hashed strings are human-readable identifiers in the codebase that turn into
numeric values at runtime, thus without affecting performance.<br/>
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/>
The class has an implicit `constexpr` constructor that chews a bunch of
characters. Once created, one can get the original string by means of the `data`
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.
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.
Example of use:
@@ -303,18 +313,19 @@ auto resource = load(entt::hashed_string{"gui/background"});
```
There is also a _user defined literal_ dedicated to hashed strings to make them
more _user-friendly_:
more user-friendly:
```cpp
using namespace entt::literals;
constexpr auto str = "text"_hs;
```
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:
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:
```cpp
std::string orig{"text"};
@@ -327,14 +338,16 @@ 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. It could therefore affect
takes place at runtime and no longer at compile-time and could therefore impact
performance to some degrees.
## Wide characters
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/>
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/>
In this case, the user defined literal to use to create hashed strings on the
fly is `_hws`:
@@ -342,99 +355,28 @@ fly is `_hws`:
constexpr auto str = L"text"_hws;
```
The hash type of `hashed_wstring` is the same as its counterpart.
Note that the hash type of the `hashed_wstring` is the same of its counterpart.
## Conflicts
The hashed string class uses FNV-1a internally to hash strings. Because of the
_pigeonhole principle_, conflicts are possible. This is a fact.<br/>
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/>
There is no silver bullet to solve the problem of conflicts when dealing with
hashing functions. In this case, the best solution is likely to give up. That's
all.<br/>
hashing functions. In this case, the best solution seemed to be 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
case.
# Iterators
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.
## Input iterator pointer
When writing an input iterator that returns in-place constructed values if
dereferenced, it's not always straightforward to figure out what `value_type` is
and how to make it behave like a full-fledged pointer.<br/>
Conversely, it would be very useful to have an `operator->` available on the
iterator itself that always works without too much complexity.
The input iterator pointer is meant for this. It's a small class that wraps the
in-place constructed value and adds some functions on top of it to make it
suitable for use with input iterators:
```cpp
struct iterator_type {
using value_type = std::pair<first_type, second_type>;
using pointer = input_iterator_pointer<value_type>;
using reference = value_type;
using difference_type = std::ptrdiff_t;
using iterator_category = std::input_iterator_tag;
// ...
}
```
The library makes extensive use of this class internally. In many cases, the
`value_type` of the returned iterators is just an input iterator pointer.
## Iota iterator
Waiting for C++20, this iterator accepts an integral value and returns all
elements in a certain range:
```cpp
entt::iota_iterator first{0};
entt::iota_iterator last{100};
for(; first != last; ++first) {
int value = *first;
// ...
}
```
In the future, views will replace this class. Meanwhile, the library makes some
interesting uses of it when a range of integral values is to be returned to the
user.
## Iterable adaptor
Typically, a container class provides `begin` and `end` member functions (with
their const counterparts) for iteration.<br/>
However, it can happen that a class offers multiple iteration methods or allows
users to iterate different sets of _elements_.
The iterable adaptor is a utility class that makes it easier to use and access
data in this case.<br/>
It accepts a couple of iterators (or an iterator and a sentinel) and offers an
_iterable_ object with all the expected methods like `begin`, `end` and whatnot.
The library uses this class extensively.<br/>
Think for example of views, which can be iterated to access entities but also
offer a method of obtaining an iterable object that returns tuples of entities
and components at once.<br/>
Another example is the registry class which allows users to iterate its storage
by returning an iterable object for the purpose.
# Memory
There are a handful of tools within `EnTT` to interact with memory in one way or
There are a handful of tools within EnTT to interact with memory in one way or
another.<br/>
Some are geared towards simplifying the implementation of (internal or external)
allocator aware containers. Others are designed to help the developer with
everyday problems.
allocator aware containers. Others, on the other hand, 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
@@ -462,7 +404,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
@@ -479,16 +421,13 @@ the same feature.
# Monostate
The monostate pattern is often presented as an alternative to a singleton based
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.
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.
Example of use:
@@ -526,10 +465,17 @@ 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.<br/>
The returned value isn't guaranteed to be stable across different runs.
However, it can be very useful as index in associative and unordered
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:
@@ -537,13 +483,13 @@ Basically, the whole system relies on a handful of classes. In particular:
```cpp
template<typename Type>
struct entt::type_index<Type, std::void_d<decltype(Type::index())>> {
static entt::id_type value() noexcept {
static entt::id_type value() ENTT_NOEXCEPT {
return Type::index();
}
};
```
Indexes **must** be sequentially generated in this case.<br/>
Note that indexes **must** still be generated sequentially in this case.<br/>
The tool is widely used within `EnTT`. Generating indices not sequentially
would break an assumption and would likely lead to undesired behaviors.
@@ -560,14 +506,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/>
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
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
at runtime and are no longer a compile-time thing.
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.
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.
* The name associated with a given type:
@@ -575,9 +521,10 @@ Basically, the whole system relies on a handful of classes. In particular:
auto name = entt::type_name<a_type>::value();
```
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/>
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/>
For example, given the following class:
```cpp
@@ -588,20 +535,21 @@ 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 normalize
the result. `EnTT` doesn't do this for obvious reasons, since it would be
creating a new string at runtime otherwise.
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.
This function **can** use non-standard features of the language for its own
purposes. Users can prevent the library from using these features by means of
the `ENTT_STANDARD_CPP` definition. In this case, the name is just empty.
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.
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.
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.
These are then combined into utilities that aim to offer an API that is somewhat
similar to that made available by the standard library.
similar to that offered by the language.
### Type info
@@ -683,8 +631,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, `type_name` returns the same
value for the two types.<br/>
have the same fully qualified name. In this case, also `type_name` will return
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
@@ -714,9 +662,9 @@ offered by this module.
### Size of
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/>
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/>
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:
@@ -745,15 +693,15 @@ tuple-like type and simplify the code at the call site.
### Constness as
A utility to easily transfer the constness of a type to another type:
An utility to easily transfer the constness of a type to another type:
```cpp
// type is const dst_type because of the constness of src_type
using type = entt::constness_as_t<dst_type, const src_type>;
```
The trait is subject to the rules of the language. For example, _transferring_
constness between references won't give the desired effect.
The trait is subject to the rules of the language. Therefore, for example,
transferring constness between references won't give the desired effect.
### Member class type
@@ -767,18 +715,6 @@ template<typename Member>
using clazz = entt::member_class_t<Member>;
```
### N-th argument
A utility to quickly find the n-th argument of a function, member function or
data member (for blind operations on opaque types):
```cpp
using type = entt::nth_argument_t<1u, &clazz::member>;
```
Disambiguation of overloaded functions is the responsibility of the user, should
it be needed.
### Integral constant
Since `std::integral_constant` may be annoying because of its form that requires
@@ -800,8 +736,8 @@ registry.emplace<enemy_tag>(entity);
### Tag
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/>
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/>
This shortcut is the alias template `entt::tag`.
If used in combination with hashed strings, it helps to use human-readable names
@@ -826,12 +762,10 @@ Here is a (possibly incomplete) list of the functionalities that come with a
type list:
* `type_list_element[_t]` to get the N-th element of a type list.
* `type_list_index[_v]` to get the index of a given element of a type list.
* `type_list_cat[_t]` and a handy `operator+` to concatenate type lists.
* `type_list_unique[_t]` to remove duplicate types from a type list.
* `type_list_contains[_v]` to know if a type list contains a given type.
* `type_list_diff[_t]` to remove types from type lists.
* `type_list_transform[_t]` to _transform_ a range and create another type list.
I'm also pretty sure that more and more utilities will be added over time as
needs become apparent.<br/>
@@ -850,19 +784,19 @@ that fully embraces what the modern C++ has to offer.
## Compile-time generator
To generate sequential numeric identifiers at compile-time, `EnTT` offers the
`ident` class template:
`identifier` class template:
```cpp
// defines the identifiers for the given types
using id = entt::ident<a_type, another_type>;
using id = entt::identifier<a_type, another_type>;
// ...
switch(a_type_identifier) {
case id::value<a_type>:
case id::type<a_type>:
// ...
break;
case id::value<another_type>:
case id::type<another_type>:
// ...
break;
default:
@@ -870,21 +804,21 @@ default:
}
```
This is what this class template has to offer: a `value` inline variable that
This is all what this class template has to offer: a `type` inline variable that
contains a numeric identifier for the given type. It can be used in any context
where constant expressions are required.
As long as the list remains unchanged, identifiers are also guaranteed to be
stable across different runs. In case they have been used in a production
environment and a type has to be removed, one can just use a placeholder to
leave the other identifiers unchanged:
environment and a type has to be removed, one can just use a placeholder to left
the other identifiers unchanged:
```cpp
template<typename> struct ignore_type {};
using id = entt::ident<
using id = entt::identifier<
a_type_still_valid,
ignore_type<no_longer_valid_type>,
ignore_type<a_type_no_longer_valid>,
another_type_still_valid
>;
```
@@ -893,8 +827,8 @@ Perhaps a bit ugly to see in a codebase but it gets the job done at least.
## Runtime generator
The `family` class template helps to generate sequential numeric identifiers for
types at runtime:
To generate sequential numeric identifiers at runtime, `EnTT` offers the
`family` class template:
```cpp
// defines a custom generator
@@ -902,17 +836,17 @@ using id = entt::family<struct my_tag>;
// ...
const auto a_type_id = id::value<a_type>;
const auto another_type_id = id::value<another_type>;
const auto a_type_id = id::type<a_type>;
const auto another_type_id = id::type<another_type>;
```
This is what a _family_ has to offer: a `value` inline variable that contains a
numeric identifier for the given type.<br/>
This is all what a _family_ has to offer: a `type` inline variable that contains
a numeric identifier for the given type.<br/>
The generator is customizable, so as to get different _sequences_ for different
purposes if needed.
Identifiers aren't guaranteed to be stable across different runs. Indeed it
mostly depends on the flow of execution.
Please, note that 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

@@ -20,13 +20,12 @@
# Introduction
This is a constantly updated section where I'm trying to put the answers to the
This is a constantly updated section where I'll try to put the answers to the
most frequently asked questions.<br/>
If you don't find your answer here, there are two cases: nobody has done it yet
or this section needs updating. In both cases, you can
[open a new issue](https://github.com/skypjack/entt/issues/new) or enter either
the [gitter channel](https://gitter.im/skypjack/entt) or the
[discord server](https://discord.gg/5BjPWBd) to ask for help.<br/>
or this section needs updating. In both cases, try to
[open a new issue](https://github.com/skypjack/entt/issues/new) or enter the
[gitter channel](https://gitter.im/skypjack/entt) and ask your question.
Probably someone already has an answer for you and we can then integrate this
part of the documentation.
@@ -46,14 +45,14 @@ lot, achieving good results in many cases.
First of all, there are two things to do in a Windows project:
* Disable the [`/JMC`](https://docs.microsoft.com/cpp/build/reference/jmc)
option (_Just My Code_ debugging), available starting with Visual Studio 2017
option (_Just My Code_ debugging), available starting in Visual Studio 2017
version 15.8.
* Set the [`_ITERATOR_DEBUG_LEVEL`](https://docs.microsoft.com/cpp/standard-library/iterator-debug-level)
macro to 0. This will disable checked iterators and iterator debugging.
Moreover, set the `ENTT_DISABLE_ASSERT` variable or redefine the `ENTT_ASSERT`
macro to disable internal debug checks in `EnTT`:
Moreover, the macro `ENTT_ASSERT` should be redefined to disable internal checks
made by `EnTT` in debug:
```cpp
#define ENTT_ASSERT(...) ((void)0)
@@ -62,22 +61,22 @@ macro to disable internal debug checks in `EnTT`:
These asserts are introduced to help the users but they require to access to the
underlying containers and therefore risk ruining the performance in some cases.
With these changes, debug performance should increase enough in most cases. If
you want something more, you can also switch to an optimization level `O0` or
preferably `O1`.
With these changes, debug performance should increase enough for most cases. If
you want something more, you can can also switch to an optimization level `O0`
or preferably `O1`.
## How can I represent hierarchies with my components?
This is one of the first questions that anyone makes when starting to work with
the entity-component-system architectural pattern.<br/>
There are several approaches to the problem and the best one depends mainly on
the real problem one is facing. In all cases, how to do it doesn't strictly
depend on the library in use, but the latter certainly allows or not different
techniques depending on how the data are laid out.
There are several approaches to the problem and whats the best one depends
mainly on the real problem one is facing. In all cases, how to do it doesn't
strictly depend on the library in use, but the latter can certainly allow or
not different techniques depending on how the data are laid out.
I tried to describe some of the approaches that fit well with the model of
`EnTT`. [This](https://skypjack.github.io/2019-06-25-ecs-baf-part-4/) is the
first post of a series that tries to _explore_ the problem. More will probably
I tried to describe some of the techniques that fit well with the model of
`EnTT`. [Here](https://skypjack.github.io/2019-06-25-ecs-baf-part-4/) is the
first post of a series that tries to explore the problem. More will probably
come in future.<br/>
In addition, `EnTT` also offers the possibility to create stable storage types
and therefore have pointer stability for one, all or some components. This is by
@@ -91,7 +90,6 @@ Custom entity identifiers are definitely a good idea in two cases at least:
* If `std::uint32_t` isn't large enough for your purposes, since this is the
underlying type of `entt::entity`.
* If you want to avoid conflicts when using multiple registries.
Identifiers can be defined through enum classes and class types that define an
@@ -107,7 +105,7 @@ There is no limit to the number of identifiers that can be defined.
## Warning C4307: integral constant overflow
According to [this](https://github.com/skypjack/entt/issues/121) issue, using a
hashed string under VS (toolset v141) could generate a warning.<br/>
hashed string under VS could generate a warning.<br/>
First of all, I want to reassure you: it's expected and harmless. However, it
can be annoying.
@@ -138,7 +136,7 @@ errors during compilation.
It's a pretty big problem but fortunately it's not a problem of `EnTT` and there
is a fairly simple solution to it.<br/>
It consists in defining the `NOMINMAX` macro before including any other header
It consists in defining the `NOMINMAX` macro before to include any other header
so as to get rid of the extra definitions:
```cpp
@@ -193,15 +191,13 @@ to mitigate the problem makes it manageable.
## Which functions trigger which signals
Storage classes offer three _signals_ that are emitted following specific
The `registry` class offers three signals that are emitted following specific
operations. Maybe not everyone knows what these operations are, though.<br/>
If this isn't clear, below you can find a _vademecum_ for this purpose:
* `on_created` is invoked when a component is first added (neither modified nor
replaced) to an entity.
* `on_update` is called whenever an existing component is modified or replaced.
* `on_destroyed` is called when a component is explicitly or implicitly removed
from an entity.

View File

@@ -1,372 +0,0 @@
# Crash Course: graph
<!--
@cond TURN_OFF_DOXYGEN
-->
# Table of Contents
* [Introduction](#introduction)
* [Data structures](#data-structures)
* [Adjacency matrix](#adjacency-matrix)
* [Graphviz dot language](#graphviz-dot-language)
* [Flow builder](#flow-builder)
* [Tasks and resources](#tasks-and-resources)
* [Fake resources and order of execution](#fake-resources-and-order-of-execution)
* [Sync points](#sync-points)
* [Execution graph](#execution-graph)
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Introduction
`EnTT` doesn't aim to offer everything one needs to work with graphs. Therefore,
anyone looking for this in the _graph_ submodule will be disappointed.<br/>
Quite the opposite is true 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
tight scheduling on the subject.<br/>
The data structures presented in this section are mainly useful for the
development and support of some tools which are also part of the same submodule.
## Adjacency matrix
The adjacency matrix is designed to represent either a directed or an undirected
graph:
```cpp
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{};
```
The `directed_tag` type _creates_ the graph as directed. There is also an
`undirected_tag` counterpart which creates it as undirected.<br/>
The interface deviates slightly from the typical double indexing of C and offers
an API that is perhaps more familiar to a C++ programmer. Therefore, the access
and modification of an element takes place via the `contains`, `insert` and
`erase` functions rather than a double call to an `operator[]`:
```cpp
if(adjacency_matrix.contains(0u, 1u)) {
adjacency_matrix.erase(0u, 1u);
} else {
adjacency_matrix.insert(0u, 1u);
}
```
Both `insert` and` erase` are _idempotent_ functions which have no effect if the
element already exists or has already been deleted.<br/>
The first one returns an `std::pair` containing the iterator to the element and
a boolean value indicating whether the element was newly inserted or not. The
second one returns the number of deleted elements (0 or 1).
An adjacency matrix is initialized with the number of elements (vertices) when
constructing it but can also be resized later using the `resize` function:
```cpp
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{3u};
```
To visit all vertices, the class offers a function named `vertices` that returns
an iterable object suitable for the purpose:
```cpp
for(auto &&vertex: adjacency_matrix.vertices()) {
// ...
}
```
The same result is obtained with the following snippet, since the vertices are
plain unsigned integral values:
```cpp
for(auto last = adjacency_matrix.size(), pos = {}; pos < last; ++pos) {
// ...
}
```
As for visiting the edges, a few functions are available.<br/>
When the purpose is to visit all the edges of a given adjacency matrix, the
`edges` function returns an iterable object that is used to get them as pairs of
vertices:
```cpp
for(auto [lhs, rhs]: adjacency_matrix.edges()) {
// ...
}
```
If the goal is to visit all the in- or out-edges of a given vertex instead, the
`in_edges` and `out_edges` functions are meant for that:
```cpp
for(auto [lhs, rhs]: adjacency_matrix.out_edges(3u)) {
// ...
}
```
Both the functions expect the vertex to visit (that is, to return the in- or
out-edges for) as an argument.<br/>
Finally, the adjacency matrix is an allocator-aware container and offers most of
the functionalities one would expect from this type of containers, such as
`clear` or 'get_allocator` and so on.
## Graphviz dot language
As it's one of the most popular formats, the library offers minimal support for
converting a graph to a Graphviz dot snippet.<br/>
The simplest way is to pass both an output stream and a graph to the `dot`
function:
```cpp
std::ostringstream output{};
entt::dot(output, adjacency_matrix);
```
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
externally managed data to the graph being converted.
# Flow builder
A flow builder is used to create execution graphs from tasks and resources.<br/>
The implementation is as generic as possible and doesn't bind to any other part
of the library.
This class is designed as a sort of _state machine_ to which a specific task is
attached for which the resources accessed in read-only or read-write mode are
specified.<br/>
Most of the functions in the API also return the flow builder itself, according
to what is the common sense API when it comes to builder classes.
Once all tasks are registered and resources assigned to them, an execution graph
in the form of an adjacency matrix is returned to the user.<br/>
This graph contains all the tasks assigned to the flow builder in the form of
_vertices_. The _vertex_ itself is used as an index to get the identifier passed
during registration.
## Tasks and resources
Although these terms are used extensively in the documentation, the flow builder
has no real concept of tasks and resources.<br/>
This class works mainly with _identifiers_, that is, values of type `id_type`.
In other terms, both tasks and resources are identified by integral values.<br/>
This allows not to couple the class itself to the rest of the library or to any
particular data structure. On the other hand, it requires the user to keep track
of the association between identifiers and operations or actual data.
Once a flow builder is created (which requires no constructor arguments), the
first thing to do is to bind a task. This tells to the builder _who_ intends to
consume the resources that are specified immediately after:
```cpp
entt::flow builder{};
builder.bind("task_1"_hs);
```
The example uses the `EnTT` hashed string to generate an identifier for the
task.<br/>
Indeed, the use of `id_type` as an identifier type 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 necessary.
Once a task is associated with the flow builder, it's also assigned read-only or
read-write resources as appropriate:
```cpp
builder
.bind("task_1"_hs)
.ro("resource_1"_hs)
.ro("resource_2"_hs)
.bind("task_2"_hs)
.rw("resource_2"_hs)
```
As mentioned, many functions return the builder itself and it's therefore easy
to concatenate the different calls.<br/>
Also in the case of resources, they are identified by numeric values of type
`id_type`. As above, the choice is not entirely random. This goes well with the
tools offered by the library while leaving room for maximum flexibility.
Finally, both the `ro` and` rw` functions also offer an overload that accepts a
pair of iterators, so that one can pass a range of resources in one go.
### Rebinding
The `flow` class is resource based rather than task based. This means that graph
generation is driven by resources and not by the order of _appearance_ of tasks
during flow definition.<br/>
Although this concept is particularly important, it'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
before or after another task.<br/>
In fact, the order of _registration_ on the resources also determines the order
in which the tasks are processed during the generation of the execution graph.
However, there is a way to _force_ the execution order of two processes.<br/>
Briefly, since accessing a resource in opposite modes requires sequential rather
than parallel scheduling, it's possible to make use of fake resources to rule on
the execution order:
```cpp
builder
.bind("task_1"_hs)
.ro("resource_1"_hs)
.rw("fake"_hs)
.bind("task_2"_hs)
.ro("resource_2"_hs)
.ro("fake"_hs)
.bind("task_3"_hs)
.ro("resource_2"_hs)
.ro("fake"_hs)
```
This snippet forces the execution of `task_1` **before** `task_2` and `task_3`.
This is due to the fact that the former sets a read-write requirement on a fake
resource that the other tasks also want to access in read-only mode.<br/>
Similarly, it's possible to force a task to run **after** a certain group:
```cpp
builder
.bind("task_1"_hs)
.ro("resource_1"_hs)
.ro("fake"_hs)
.bind("task_2"_hs)
.ro("resource_1"_hs)
.ro("fake"_hs)
.bind("task_3"_hs)
.ro("resource_2"_hs)
.rw("fake"_hs)
```
In this case, since there are a number of processes that want to read a specific
resource, they will do so in parallel by forcing `task_3` to run after all the
others tasks.
## Sync points
Sometimes it's useful to assign the role of _sync point_ to a node.<br/>
Whether it accesses new resources or is simply a watershed, the procedure for
assigning this role to a vertex is always the same. First it's tied to the flow
builder, then the `sync` function is invoked:
```cpp
builder.bind("sync_point"_hs).sync();
```
The choice to assign an _identity_ to this type of nodes lies in the fact that,
more often than not, they also perform operations on resources.<br/>
If this isn't the case, it will still be possible to create no-op vertices to
which empty tasks are assigned.
## Execution graph
Once both the resources and their consumers have been properly registered, the
purpose of this tool is to generate an execution graph that takes into account
all specified constraints to return the best scheduling for the vertices:
```cpp
entt::adjacency_matrix<entt::directed_tag> graph = builder.graph();
```
Searching for the main vertices (that is, those without in-edges) is usually the
first thing required:
```cpp
for(auto &&vertex: graph) {
if(auto in_edges = graph.in_edges(vertex); in_edges.begin() == in_edges.end()) {
// ...
}
}
```
Then it'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,12 +19,14 @@
general and on GNU/Linux when default visibility was set to hidden. The
limitation was mainly due to a custom utility used to assign unique, sequential
identifiers with different types.<br/>
Fortunately, nowadays `EnTT` works smoothly across boundaries.
Fortunately, nowadays using `EnTT` across boundaries is much easier.
## Smooth until proven otherwise
Many classes in `EnTT` make extensive use of type erasure for their purposes.
This raises the need to identify objects whose type has been erased.<br/>
This isn't a problem on itself (in fact, it's the basis of an API so convenient
to use). However, a way is needed to recognize the objects whose type has been
erased on the other side of a boundary.<br/>
The `type_hash` class template is how identifiers are generated and thus made
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
@@ -34,13 +36,13 @@ The section dedicated to `type_info` contains all the details to get around the
issue in a concise and elegant way. Please refer to the specific documentation.
When working with linked libraries, compile definitions `ENTT_API_EXPORT` and
`ENTT_API_IMPORT` are to import or export symbols, so as to make everything work
nicely across boundaries.<br/>
`ENTT_API_IMPORT` can be used where there is a need to import or export symbols,
so as to make everything work nicely across boundaries.<br/>
On the other hand, everything should run smoothly when working with plugins or
shared libraries that don't export any symbols.
For those who need more details, the test suite contains many examples covering
the most common cases (see the `lib` directory for all details).<br/>
For anyone who needs more details, the test suite contains multiple examples
covering the most common cases (see the `lib` directory for all details).<br/>
It goes without saying that it's impossible to cover **all** possible cases.
However, what is offered should hopefully serve as a basis for all of them.
@@ -48,29 +50,40 @@ However, what is offered should hopefully serve as a basis for all of them.
The runtime reflection system deserves a special mention when it comes to using
it across boundaries.<br/>
Since it's linked already to a static context to which the elements are attached
and different contexts don't relate to each other, they must be _shared_ to
allow the use of meta types across boundaries.
Since it's linked already to a static context to which the visible components
are attached and different contexts don't relate to each other, they must be
_shared_ to allow the use of meta types across boundaries.
Fortunately, sharing a context is also trivial to do. First of all, the local
one is acquired in the main space:
Sharing a context is trivial though. First of all, the local one must be
acquired in the main space:
```cpp
auto handle = entt::locator<entt::meta_ctx>::handle();
entt::meta_ctx ctx{};
```
Then, it's passed to the receiving space that sets it as its default context,
thus discarding or storing aside the local one:
Then, it must passed to the receiving space that will set it as its global
context, thus releasing the local one that remains available but is no longer
referred to by the runtime reflection system:
```cpp
entt::locator<entt::meta_ctx>::reset(handle);
entt::meta_ctx::bind(ctx);
```
From now on, both spaces refer to the same context and on it are attached all
new meta types, no matter where they are created.<br/>
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.
From now on, both spaces will refer to the same context and on it will be
attached the new visible meta types, no matter where they are created.<br/>
A context can also be reset and then associated again locally as:
```cpp
entt::meta_ctx::bind(entt::meta_ctx{});
```
This is allowed because local and global contexts are separated. Therefore, it's
always possible to make the local context the current one again.
Before to release a context, all locally registered types should be reset to
avoid dangling references. Otherwise, if a type is accessed from another space
by name, there could be an attempt to address its parts that are no longer
available.
## Memory Management

View File

@@ -1,22 +1,5 @@
# 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
@@ -24,18 +7,13 @@ source projects based on `EnTT` and didn't hold back when it came to documenting
them.
Below an incomplete list of games, applications and articles that can be used as
a reference.<br/>
Where I put the word _apparently_ means that the use of `EnTT` is documented but
the authors didn't make explicit announcements or contacted me directly.
a reference. Where I put the word _apparently_ means that the use of `EnTT` is
documented but the authors didn't make explicit announcements or contacted me
directly.
If you know of other resources out there that are about `EnTT`, feel free to
open an issue or a PR 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
I hope this list can grow much more in the future:
* Games:
* [Minecraft](https://minecraft.net/en-us/attribution/) by
[Mojang](https://mojang.com/): of course, **that** Minecraft, see the
open source attributions page for more details.
@@ -76,7 +54,7 @@ I hope the following lists can grow much more in the future.
puzzler with logic gates and other cool stuff.
[Check it out](https://indi-kernick.itch.io/the-machine-web-version).
* [EnTTPong](https://github.com/DomRe/EnttPong): a basic game made to showcase
different parts of `EnTT` and C++17.
different parts of EnTT and C++17.
* [Randballs](https://github.com/gale93/randballs): simple `SFML` and `EnTT`
playground.
* [EnTT Tower Defense](https://github.com/Daivuk/tddod): a data oriented tower
@@ -116,17 +94,8 @@ I hope the following lists can grow much more in the future.
town, before it's too late.
* [City Builder Game](https://github.com/PhiGei2000/CityBuilderGame): a simple
city-building game using C++ and OpenGL.
* [BattleSub](https://github.com/bfeldpw/battlesub): two player 2D submarine
game with some fluid dynamics.
* [Crimson Rush](https://github.com/WilKam01/LuaCGame): a dungeon-crawler and
rougelike inspired game about exploring and surviving as long as possible.
* [Space Fight](https://github.com/cholushkin/SpaceFight): one screen
multi-player arcade shooter game prototype.
* [Confetti Party](https://github.com/hexerei/entt-confetti): C++ sample
application as a starting point using `EnTT` and `SDL2`.
## Engines and the like:
* 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
@@ -187,23 +156,8 @@ I hope the following lists can grow much more in the future.
of the Alan Wake Engine.
* [Nazara Engine](https://github.com/DigitalPulseSoftware/NazaraEngine): fast,
cross-platform, object-oriented API to help in daily developer life.
* [Billy Engine](https://github.com/billy4479/BillyEngine): some kind of a 2D
engine based on `SDL2` and `EnTT`.
* [Ducktape](https://github.com/DucktapeEngine/Ducktape): an open source C++
2D & 3D game engine that focuses on being fast and powerful.
* [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.
@@ -227,12 +181,6 @@ I hope the following lists can grow much more in the future.
- ... And so on.
[Check out](https://www.youtube.com/channel/UCQ-W1KE9EYfdxhL6S4twUNw) the
_Game Engine Series_ by The Cherno for more videos.
* [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
@@ -255,12 +203,8 @@ I hope the following lists can grow much more in the future.
project retrospect by [Eric Hildebrand](https://www.erichildebrand.net/).
* [EnTT Entity Component System Gaming Library](https://gamefromscratch.com/entt-entity-component-system-gaming-library/):
`EnTT` on GameFromScratch.com.
* [Custom C++ server for UE5](https://youtu.be/fbXZVNCOvjM) optimized for
MMO(RPG)s and its [follow-up](https://youtu.be/yGlZeopx2hU) episode about
player bots and full external ECS: a series definitely worth looking at.
## Any Other Business:
* Any Other Business:
* [ArcGIS Runtime SDKs](https://developers.arcgis.com/arcgis-runtime/) by
[Esri](https://www.esri.com/): they use `EnTT` for the internal ECS and the
cross platform C++ rendering engine. The SDKs are utilized by a lot of
@@ -302,3 +246,6 @@ I hope the following lists can grow much more in the future.
* GitHub contains also
[many other examples](https://github.com/search?o=desc&q=%22skypjack%2Fentt%22&s=indexed&type=Code)
of use of `EnTT` from which to take inspiration if interested.
If you know of other resources out there that are about `EnTT`, feel free to
open an issue or a PR and I'll be glad to add them to this page.

View File

@@ -7,16 +7,15 @@
* [Introduction](#introduction)
* [Service locator](#service-locator)
* [Opaque handles](#opaque-handles)
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Introduction
Usually, service locators are tightly bound to the services they expose and it's
Usually service locators are tightly bound to the services they expose and it's
hard to define a general purpose solution.<br/>
This tiny class tries to fill the gap and gets rid of the burden of defining a
This tiny class tries to fill the gap and to get rid of the burden of defining a
different specific locator for each application.
# Service locator
@@ -33,7 +32,7 @@ entt::locator<interface>::allocate_emplace<service>(allocator, argument);
The difference is that the latter expects an allocator as the first argument and
uses it to allocate the service itself.<br/>
Once a service is set up, it's retrieved using the `value` function:
Once a service has been set up, it's retrieved using the value function:
```cpp
interface &service = entt::locator<interface>::value();
@@ -55,34 +54,3 @@ All arguments are used only if necessary, that is, if a service doesn't already
exist and therefore the fallback service is constructed and returned. In all
other cases, they are discarded.<br/>
Finally, to reset a service, use the `reset` function.
## Opaque handles
Sometimes it's useful to _transfer_ a copy of a service to another locator. For
example, when working across boundaries it's common to _share_ a service with a
dynamically loaded module.<br/>
Options aren't much in this case. Among these is the possibility of _exporting_
services and assigning them to a different locator.
This is what the `handle` and `reset` functions are meant for.<br/>
The former returns an opaque object useful for _exporting_ (or rather, obtaining
a reference to) a service. The latter also accepts an optional argument to a
handle which then allows users to reset a service by initializing it with an
opaque handle:
```cpp
auto handle = entt::locator<interface>::handle();
entt::locator<interface>::reset(handle);
```
It's worth noting that it's possible to get handles for uninitialized services
and use them with other locators. Of course, all a user will get is to have an
uninitialized service elsewhere as well.
Note that exporting a service allows users to _share_ the object currently set
in a locator. Replacing it won't replace the element even where a service has
been configured with a handle to the previous item.<br/>
In other words, if an audio service is replaced with a null object to silence an
application and the original service was shared, this operation won't propagate
to the other locators. Therefore, a module that share the ownership of the
original audio service is still able to emit sounds.

View File

@@ -15,12 +15,10 @@
* [Template information](#template-information)
* [Automatic conversions](#automatic-conversions)
* [Implicitly generated default constructor](#implicitly-generated-default-constructor)
* [From void to any](#from-void-to-any)
* [Policies: the more, the less](#policies-the-more-the-less)
* [Named constants and enums](#named-constants-and-enums)
* [Properties and meta objects](#properties-and-meta-objects)
* [Unregister types](#unregister-types)
* [Meta context](#meta-context)
<!--
@endcond TURN_OFF_DOXYGEN
-->
@@ -28,7 +26,7 @@
# Introduction
Reflection (or rather, its lack) is a trending topic in the C++ world and a tool
that can unlock a lot of interesting features in the specific case of `EnTT`. I
that can unlock a lot of interesting feature in the specific case of `EnTT`. I
looked for a third-party library that met my needs on the subject, but I always
came across some details that I didn't like: macros, being intrusive, too many
allocations, and so on.<br/>
@@ -44,22 +42,22 @@ when it comes to working with names and identifiers. It does this by offering an
API that works with opaque identifiers that may or may not be generated by means
of a hashed string.<br/>
This means that users can assign any type of identifier to the meta objects, as
long as they're numeric. It doesn't matter if they're generated at runtime, at
long as they are numeric. It doesn't matter if they are generated at runtime, at
compile-time or with custom functions.
That being said, the examples in the following sections are all based on the
`hashed_string` class as provided by this library. Therefore, where an
identifier is required, it's likely that a user defined literal is used as
identifier is required, it's likely that an user defined literal is used as
follows:
```cpp
auto factory = entt::meta<my_type>().type("reflected_type"_hs);
```
For what it's worth, this is completely equivalent to:
For what it's worth, this is likely completely equivalent to:
```cpp
auto factory = entt::meta<my_type>().type(42u);
auto factory = entt::meta<my_type>().type(42);
```
Obviously, human-readable identifiers are more convenient to use and highly
@@ -67,61 +65,69 @@ recommended.
# Reflection in a nutshell
Reflection always starts from actual C++ types. Users cannot reflect _imaginary_
types.<br/>
The `meta` function is where it all starts:
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:
```cpp
auto factory = entt::meta<my_type>();
```
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
runtime type identification system built-in in `EnTT`.<br/>
However, it's also possible to assign custom identifiers to meta types:
This isn't enough to _export_ the given type and make it visible though.<br/>
The returned value is a factory object to use to continue building the meta
type. In order to make the type _visible_, users can assign it an identifier:
```cpp
auto factory = entt::meta<my_type>().type("reflected_type"_hs);
```
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`.
Or use the default one, that is, the built-in identifier for the given type:
A factory is such that all its member functions return the factory itself. It's
generally used to create the following:
```cpp
auto factory = entt::meta<my_type>().type();
```
* _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:
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`.
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:
* _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:
```cpp
entt::meta<my_type>().ctor<int, char>().ctor<&factory>();
```
Meta default constructors are implicitly generated, if possible.
* _Destructors_. Both free functions and member functions are valid destructors:
* _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:
```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_. 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:
* _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:
```cpp
entt::meta<my_type>()
@@ -130,11 +136,13 @@ generally used to create the following:
.data<&global_variable>("global"_hs);
```
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:
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:
```cpp
entt::meta<my_type>().data<nullptr, &my_type::data_member>("member"_hs);
@@ -146,10 +154,13 @@ generally used to create the following:
entt::meta<my_type>().data<entt::value_list<&from_int, &from_string>, &my_type::data_member>("member"_hs);
```
* _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:
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:
```cpp
entt::meta<my_type>()
@@ -158,31 +169,40 @@ generally used to create the following:
.func<&free_function>("free"_hs);
```
The `func` function requires the identifier to use for the meta data function.
Users can then access it by _name_ at runtime.<br/>
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/>
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:
derived from it. In this case, the reflection system tracks the relationship
and allows for implicit casts at runtime when required.<br/>
Use the `base` member function for this purpose:
```cpp
entt::meta<derived_type>().base<base_type>();
```
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.
From now on, wherever a `base_type` is required, an instance of `derived_type`
will also be accepted.
* _Conversion functions_. Conversion functions allow users to define conversions
that are implicitly performed by the reflection system when required:
* _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:
```cpp
entt::meta<double>().conv<int>();
```
This is everything users need to create meta types. Refer to the inline
documentation for further details.
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.
## Any to the rescue
@@ -195,13 +215,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, while `any` doesn't.<br/>
Similar to `any`, this class is also used to create _aliases_ for unmanaged
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
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.<br/>
Unlike `any` instead, `meta_any` treats an empty instance and one initialized
with `void` differently:
member function. However, unlike `any`, `meta_any` treats an empty instance and
one initialized with `void` differently:
```cpp
entt::meta_any empty{};
@@ -210,19 +230,21 @@ 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.
function calls and function calls that are successful but return nothing.<br/>
Finally, the member functions `try_cast`, `cast` and `allow_cast` are used to
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.<br/>
There is in fact no `any_cast` equivalent for `meta_any`.
resulting object. There is in fact no `any_cast` equivalent for `meta_any`.
## Enjoy the runtime
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:
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:
```cpp
// direct access to a reflected type
@@ -235,23 +257,25 @@ auto by_id = entt::resolve("reflected_type"_hs);
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
reflected types at once. It returns an iterable object to be used in a range-for
loop:
There exits 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:
```cpp
for(auto &&[id, type]: entt::resolve()) {
for(auto type: entt::resolve()) {
// ...
}
```
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/>
Meta data members and functions are accessed by name:
In all cases, the returned value is an instance of `meta_type`. 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:
The meta objects that compose a meta type are accessed in the following ways:
* _Meta data_. They are accessed by _name_:
```cpp
auto data = entt::resolve<my_type>().data("member"_hs);
@@ -263,7 +287,7 @@ Meta data members and functions are accessed by name:
know if it's a const or a static one), to get the meta type of the variable
and to set or get the contained value.
* Meta function members:
* _Meta functions_. They are accessed by _name_:
```cpp
auto func = entt::resolve<my_type>().func("member"_hs);
@@ -274,11 +298,20 @@ Meta data members and functions are accessed by name:
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 is 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 can be 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 explicitly convert
to a boolean value to check for validity:
* _Meta bases_. They are accessed through the _name_ of the base types:
```cpp
auto base = entt::resolve<derived_type>().base("base"_hs);
```
The returned type is `meta_type` and may be invalid if there is no meta base
object associated with the given identifier.
All the objects thus obtained as well as the meta types can be explicitly
converted to a boolean value to check if they are valid:
```cpp
if(auto func = entt::resolve<my_type>().func("member"_hs); func) {
@@ -286,33 +319,35 @@ if(auto func = entt::resolve<my_type>().func("member"_hs); func) {
}
```
Furthermore, all them (and a few more, like meta basis) are returned by a bunch
of overloads that provide the caller with iterable ranges of top-level elements.
As an example:
Furthermore, all them are also returned by specific overloads that provide the
caller with iterable ranges of top-level elements. As an example:
```cpp
for(auto &&[id, type]: entt::resolve<my_type>().base()) {
for(auto data: entt::resolve<my_type>().data()) {
// ...
}
```
Meta type are also used to `construct` actual instances of the underlying
A meta type can be 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 was found
or not.
or may not be initialized, depending on whether a suitable constructor has been
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'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
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
internally by `meta_any` and the meta objects when needed.
Meta types and meta objects in general contain much more than what was said.
Refer to the inline documentation for further details.
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.
## Container support
@@ -323,15 +358,13 @@ 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 with the actual type
of the container.<br/>
`EnTT` already exports the specializations for some common classes. In
particular:
* `std::vector`, `std::array`, `std::deque` and `std::list` (but not
`std::forward_list`) are supported as _sequence containers_.
* `std::map`, `std::set` and their unordered counterparts are supported as
* `std::vector` and `std::array` are exported as _sequence containers_.
* `std::map`, `std::set` and their unordered counterparts are exported as
_associative containers_.
It's important to include the header file `container.hpp` to make these
@@ -360,10 +393,11 @@ 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'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/>
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/>
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.
@@ -375,18 +409,32 @@ 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.
an unsigned integer value:
```cpp
const auto size = view.size();
```
* The `resize` member function allows to resize the wrapped container and
returns true in case of success.<br/>
returns true in case of success:
```cpp
const bool ok = view.resize(3u);
```
For example, it's not possible to resize fixed size containers.
* The `clear` member function allows to clear the wrapped container and returns
true in case of success.<br/>
true in case of success:
```cpp
const bool ok = view.clear();
```
For example, it's not possible to clear fixed size containers.
* The `begin` and `end` member functions return opaque iterators that is used to
iterate the container directly:
* The `begin` and `end` member functions return opaque iterators that can be
used to iterate the container directly:
```cpp
for(entt::meta_any element: view) {
@@ -400,7 +448,7 @@ to case. In particular:
All meta iterators are input iterators and don't offer an indirection operator
on purpose.
* The `insert` member function is used to add elements to the container. It
* The `insert` member function can be used to add elements to the container. It
accepts a meta iterator and the element to insert:
```cpp
@@ -410,15 +458,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. 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.
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.
* The `erase` member function is used to remove elements from the container. It
accepts a meta iterator to the element to remove:
* The `erase` member function can be used to remove elements from the container.
It accepts a meta iterator to the element to remove:
```cpp
auto first = view.begin();
@@ -427,11 +475,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. A call
to `erase` may silently fail in case of fixed size containers.
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.
* The `operator[]` is used to access container elements. It accepts a single
argument, the position of the element to return:
* 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:
```cpp
for(std::size_t pos{}, last = view.size(); pos < last; ++pos) {
@@ -441,12 +489,8 @@ to case. In particular:
```
The function returns instances of `meta_any` that directly refer to the actual
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
expects).
elements. Modifying the returned object will then directly modify the element
inside the container.
Similarly, also the interface of the `meta_associative_container` proxy object
is the same for all types of associative containers. However, there are some
@@ -467,13 +511,21 @@ 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.
an unsigned integer value:
```cpp
const auto size = view.size();
```
* The `clear` member function allows to clear the wrapped container and returns
true in case of success.
true in case of success:
* The `begin` and `end` member functions return opaque iterators that are used
to iterate the container directly:
```cpp
const bool ok = view.clear();
```
* The `begin` and `end` member functions return opaque iterators that can be
used to iterate the container directly:
```cpp
for(std::pair<entt::meta_any, entt::meta_any> element: view) {
@@ -490,11 +542,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 directly
modifies the element inside the container.
`meta_any` that directly refers to the actual element. Modifying it will then
directly modify the element inside the container.
* The `insert` member function is used to add elements to a container. It gets
two arguments, respectively the key and the value to insert:
* 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:
```cpp
auto last = view.end();
@@ -503,39 +555,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. A call to `insert` may fail when the arguments aren't at
least convertible to the required types.
successful or not. Note that a call to `insert` may fail when the arguments
aren't at least convertible to the required types.
* The `erase` member function is used to remove elements from a container. It
gets a single argument, the key to remove:
* 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:
```cpp
view.erase(42);
```
This function returns a boolean value to indicate whether the operation was
successful or not. A call to `erase` may fail when the argument isn't at least
convertible to the required type.
successful or not. Note that a call to `erase` may fail when the argument
isn't at least convertible to the required type.
* The `operator[]` is used to access elements in a container. It gets a single
argument, the key of the element to return:
* 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:
```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 directly modifies the element inside
the container.
elements. Modifying the returned object will then directly modify 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 _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/>
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/>
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:
@@ -565,12 +617,13 @@ if(any.type().is_pointer_like()) {
}
```
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).
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).
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:
@@ -598,15 +651,15 @@ In general, _dereferencing_ a pointer-like type boils down to a `*ptr`. However,
};
```
In all other cases and when dereferencing a pointer works as expected regardless
of the pointed type, no user intervention is required.
In all other cases, that is, when dereferencing a pointer works as expected and
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
important to include the header file `template.hpp` to make these information
available to the compiler when needed.
Meta template information are easily found:
@@ -638,9 +691,9 @@ template<typename Ret, typename... Args>
struct function_type<Ret(Args...)> {};
```
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/>
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/>
To achieve this, users must enter the library internals and provide their own
specialization for the class template `entt::meta_template_traits`, such as:
@@ -654,8 +707,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 is used as is and the information it contains is
associated with the appropriate type when required.
Therefore, the specialization will be used as is and the information it contains
will be associated with the appropriate type when required.
## Automatic conversions
@@ -673,7 +726,7 @@ entt::meta<int>()
```
Repeated for each type eligible to undergo this type of conversions. This is
both error-prone and repetitive.<br/>
both error prone and repetitive.<br/>
Similarly, the language allows users to silently convert unscoped enums to their
underlying types and offers what it takes to do the same for scoped enums. It
would result in the following if it were to be done explicitly:
@@ -702,51 +755,34 @@ any.allow_cast(type);
int value = any.cast<int>();
```
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.
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.
## Implicitly generated default constructor
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/>
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/>
For example, in the case of primitive types like `int` or `char`, but not just
them.
For default constructible types only, default constructors are automatically
defined and associated with their meta types, whether they are explicitly or
implicitly generated.<br/>
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/>
Therefore, this is all is needed to construct an integer from its meta type:
```cpp
entt::resolve<int>().construct();
```
Where the meta type is for example the one returned from a meta container,
Where the meta type can be for example the one returned from a meta container,
useful for building keys without knowing or having to register the actual types.
In all cases, when users register default constructors, they are preferred both
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` 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`:
```cpp
entt::meta_any any = entt::resolve(id).from_void(element);
```
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.
## Policies: the more, the less
Policies are a kind of compile-time directives that can be used when registering
@@ -775,17 +811,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, 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.
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.
* 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 makes it possible to directly access the instance used to initialize
the wrapper itself:
requested will make 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);
@@ -803,16 +839,21 @@ obvious corner cases that can in turn be solved with the use of policies.
## Named constants and enums
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/>
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.
A special mention should be made for constant values and enums. It wouldn't be
necessary, but it will help distracted readers.
Exposing constant values or elements from an enum is quite simple:
As mentioned, the `data` member function can be used to reflect constants of any
type among the other things.<br/>
This allows users to create meta types for enums that will work exactly like any
other meta type built from a class. Similarly, arithmetic types can be enriched
with constants of special meaning where required.<br/>
Personally, I find it very useful not to export what is the difference between
enums and classes in C++ directly in the space of the reflected types.
All the values thus exported will appear to users as if they were constant data
members of the reflected types.
Exporting constant values or elements from an enum is as simple as ever:
```cpp
entt::meta<my_enum>()
@@ -822,40 +863,68 @@ entt::meta<my_enum>()
entt::meta<int>().data<2048>("max_int"_hs);
```
Accessing them is trivial as well. It's a matter of doing the following, as with
any other data member of a meta type:
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:
```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>();
```
All this happens behind the scenes without any allocation because of the small
object optimization performed by the `meta_any` class.
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.
## 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:
for most of them.<br/>
For the meta objects that support properties, the member functions of the
factory used for registering them will return a decorated version of the factory
itself. The latter can be used to attach properties to the last created meta
object.<br/>
Apparently, it's more difficult to say than to do:
```cpp
entt::meta<my_type>().type("reflected_type"_hs).prop("tooltip"_hs, "message");
```
Properties are always in the key/value form. The key is a numeric identifier,
mostly similar to the identifier used to register meta objects. There are no
restrictions on the type of the value instead, as long as it's movable.<br/>
Key only properties are also supported out of the box:
Properties are always in the key/value form. There are no restrictions on the
type of the key or value, as long as they are copy constructible objects.<br/>
Multiple formats are supported when it comes to defining a property:
```cpp
entt::meta<my_type>().type("reflected_type"_hs).prop(my_enum::key_only);
```
* Properties as key/value pairs:
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.
```cpp
entt::meta<my_type>().type("reflected_type"_hs).prop("tooltip"_hs, "message");
```
* Properties as `std::pair`s:
```cpp
entt::meta<my_type>().type("reflected_type"_hs).prop(std::make_pair("tooltip"_hs, "message"));
```
* Key only properties:
```cpp
entt::meta<my_type>().type("reflected_type"_hs).prop(my_enum::key_only);
```
* Properties as `std::tuple`s:
```cpp
entt::meta<my_type>().type("reflected_type"_hs).prop(std::make_tuple(std::make_pair("tooltip"_hs, "message"), my_enum::key_only));
```
A tuple contains one or more properties. All of them are treated individually.
Note that it's not possible to invoke `prop` multiple times for the same meta
object and trying to do that will result in a compilation error.<br/>
However, the `props` function is available to associate several properties at
once. In this case, properties in the key/value form aren't allowed, since they
would be interpreted as two different properties rather than a single one.
The meta objects for which properties are supported are currently meta types,
meta data and meta functions.<br/>
@@ -864,7 +933,7 @@ properties at once or to search a specific property by key:
```cpp
// iterate all properties of a meta type
for(auto &&[id, prop]: entt::resolve<my_type>().prop()) {
for(auto prop: entt::resolve<my_type>().prop()) {
// ...
}
@@ -873,89 +942,37 @@ auto prop = entt::resolve<my_type>().prop("tooltip"_hs);
```
Meta properties are objects having a fairly poor interface, all in all. They
only provide the `value` member function to retrieve the contained value in the
form of a `meta_any` object.
only provide the `key` and the `value` member functions to be used to retrieve
the key and the value contained in the form of `meta_any` objects, respectively.
## 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/>
necessarily depend on it. Similarly, implicitly generated types (as an example,
the meta types implicitly generated for function parameters when needed) aren't
unregistered.<br/>
Roughly speaking, unregistering a type means disconnecting all associated meta
objects from it and making its identifier no longer available:
objects from it and making its identifier no longer visible. The underlying node
will remain available though, as if it were implicitly generated:
```cpp
entt::meta_reset<my_type>();
```
It's also possible to reset types by their unique identifiers:
It's also possible to reset types by their unique identifiers if required:
```cpp
entt::meta_reset("my_type"_hs);
```
Finally, there exists a non-template overload of the `meta_reset` function that
doesn't accept arguments and resets all meta types at once:
doesn't accept argument and resets all searchable types (that is, all types that
were assigned an unique identifier):
```cpp
entt::meta_reset();
```
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 is obtained via a service locator as:
```cpp
auto &&context = entt::locator<entt::meta_context>::value_or();
```
By itself, a context is an opaque object that the user cannot do much with.
However, users can replace an existing context with another at any time:
```cpp
entt::meta_context other{};
auto &&context = entt::locator<entt::meta_context>::value_or();
std::swap(context, other);
```
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
system.<br/>
For example, to create new meta types within a context other than the default
one, simply pass it as an argument to the `meta` call:
```cpp
entt::meta_ctx context{};
auto factory = entt::meta<my_type>(context).type("reflected_type"_hs);
```
By doing so, the new meta type isn't available in the default context but is
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's necessary to pass it to the `resolve` function:
```cpp
entt::meta_type type = entt::resolve(context, "reflected_type"_hs)
```
More generally, when using externally managed contexts, it's always required to
provide the system with the context to use, at least at the _entry point_.<br/>
For example, once the `meta_type` instant is obtained, it's no longer necessary
to pass the context around as the meta type takes the information with it and
eventually propagates it to all its parts.<br/>
On the other hand, it's necessary to instruct the library on where meta types
are to be fetched when `meta_any`s and `meta_handle`s are constructed, a factory
created or a meta type resolved.
All types can be re-registered later with a completely different name and form.

View File

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

View File

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

View File

@@ -1,35 +1,18 @@
# 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.
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/>
Below an incomplete list of those that I've come across so far.<br/>
If some terms or designs aren't clear, I recommend referring to the
[_ECS Back and Forth_](https://skypjack.github.io/tags/#ecs) series for all the
details.
I hope this list can grow much more in the future:
* C:
* [destral_ecs](https://github.com/roig/destral_ecs): a single-file ECS based
on sparse sets.
@@ -51,8 +34,6 @@ details.
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
@@ -89,3 +70,6 @@ details.
* 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

@@ -16,17 +16,17 @@
# Introduction
Resource management is usually one of the most critical parts of a game.
Solutions are often tuned to the particular application. There exist several
approaches and all of them are perfectly fine as long as they fit the
Resource management is usually one of the most critical part of a software like
a game. Solutions are often tuned to the particular application. There exist
several approaches and all of them are perfectly fine as long as they fit the
requirements of the piece of software in which they are used.<br/>
Examples are loading everything on start, loading on request, predictive
loading, and so on.
`EnTT` doesn't pretend to offer a _one-fits-all_ solution for the different
cases.<br/>
Instead, the library comes with a minimal, general purpose resource cache that
might be useful in many cases.
Instead, the library offers a minimal, general purpose resource cache that might
be useful in many cases.
# The resource, the loader and the cache
@@ -66,8 +66,8 @@ using my_cache = entt::resource_cache<my_resource, my_loader>;
my_cache cache{};
```
The class is designed to create different caches for different resource types
and to manage each one independently in the most appropriate way.<br/>
The cache is meant to be used to create different caches for different types of
resources and to manage each one independently in the most appropriate way.<br/>
As a (very) trivial example, audio tracks can survive in most of the scenes of
an application while meshes can be associated with a single scene only, then
discarded when a player leaves it.
@@ -75,22 +75,22 @@ discarded when a player leaves it.
## Resource handle
Resources aren't returned directly to the caller. Instead, they are wrapped in a
_resource handle_, an instance of the `entt::resource` class template.<br/>
_resource handle_ identified by the `entt::resource` class template.<br/>
For those who know the _flyweight design pattern_ already, that's exactly what
it is. To all others, this is the time to brush up on some notions instead.
A shared pointer could have been used as a resource handle. In fact, the default
implementation mostly maps the interface of its standard counterpart and only
adds a few things on top of it.<br/>
However, the handle in `EnTT` is designed as a standalone class template. This
is due to the fact that specializing a class in the standard library is often
undefined behavior while having the ability to specialize the handle for one,
more or all resource types could help over time.
handle mostly maps the interface of its standard counterpart and only adds a few
things to it.<br/>
However, the handle in `EnTT` is designed as a standalone class template named
`resource`. It boils down to the fact that specializing a class in the standard
is often undefined behavior while having the ability to specialize the handle
for one, more or all resource types could help over time.
## Loaders
A loader is responsible for _loading_ resources (quite obviously).<br/>
By default, it's just a callable object that forwards its arguments to the
A loader is a class that is responsible for _loading_ the resources.<br/>
By default, it's just a callable object which forwards its arguments to the
resource itself. That is, a _passthrough type_. All the work is demanded to the
constructor(s) of the resource itself.<br/>
Loaders also are fully customizable as expected.
@@ -104,7 +104,7 @@ When using the default handle, it expects a resource type which is convertible
to or suitable for constructing an `std::shared_ptr<Type>` (where `Type` is the
actual resource type).<br/>
In other terms, the loader should return shared pointers to the given resource
type. However, this isn't mandatory. Users can easily get around this constraint
type. However, it isn't mandatory. Users can easily get around this constraint
by specializing both the handle and the loader.
A cache forwards all its arguments to the loader if required. This means that
@@ -136,7 +136,7 @@ This makes the whole loading logic quite flexible and easy to extend over time.
## The cache class
The cache is the class that is asked to _connect the dots_.<br/>
It loads the resources, stores them aside and returns handles as needed:
It loads the resources, store them aside and returns handles as needed:
```cpp
entt::resource_cache<my_resource, my_loader> cache{};
@@ -144,12 +144,12 @@ entt::resource_cache<my_resource, my_loader> cache{};
Under the hood, a cache is nothing more than a map where the key value has type
`entt::id_type` while the mapped value is whatever type its loader returns.<br/>
For this reason, it offers most of the functionalities a user would expect from
a map, such as `empty` or `size` and so on. Similarly, it's an iterable type
that also supports indexing by resource id:
For this reason, it offers most of the functionality a user would expect from a
map, such as `empty` or `size` and so on. Similarly, it's an iterable type that
also supports indexing by resource id:
```cpp
for(auto [id, res]: cache) {
for(entt::resource<my_resource> curr: cache) {
// ...
}
@@ -159,9 +159,9 @@ if(entt::resource<my_resource> res = cache["resource/id"_hs]; res) {
```
Please, refer to the inline documentation for all the details about the other
functions (such as `contains` or `erase`).
functions (for example `contains` or `erase`).
Set aside the part of the API that this class _shares_ with a map, it also adds
Set aside the part of the API that this class shares with a map, it also adds
something on top of it in order to address the most common requirements of a
resource cache.<br/>
In particular, it doesn't have an `emplace` member function which is replaced by
@@ -175,15 +175,16 @@ auto ret = cache.load("resource/id"_hs);
const bool loaded = ret.second;
// takes the resource handle pointed to by the returned iterator
entt::resource<my_resource> res = ret.first->second;
entt::resource<my_resource> res = *ret.first;
```
Note that the hashed string is used for convenience in the example above.<br/>
Resource identifiers are nothing more than integral values. Therefore, plain
numbers as well as non-class enum value are accepted.
It's worth mentioning that the iterators of a cache as well as its indexing
operators return resource handles rather than instances of the mapped type.<br/>
Moreover, it's worth mentioning that both the iterators of a cache and its
indexing operators return resource handles rather than instances of the mapped
type.<br/>
Since the cache has no control over the loader and a resource isn't required to
also be convertible to bool, these handles can be invalid. This usually means an
error in the user logic but it may also be an _expected_ event.<br/>

View File

@@ -9,7 +9,6 @@
* [Delegate](#delegate)
* [Runtime arguments](#runtime-arguments)
* [Lambda support](#lambda-support)
* [Raw access](#raw-access)
* [Signals](#signals)
* [Event dispatcher](#event-dispatcher)
* [Named queues](#named-queues)
@@ -20,27 +19,27 @@
# Introduction
Signals are more often than not a core part of games and software architectures
in general.<br/>
They help to decouple the various parts of a system while allowing them to
communicate with each other somehow.
Signals are usually a core part of games and software architectures in
general.<br/>
Roughly speaking, they help to decouple the various parts of a system while
allowing them to communicate with each other somehow.
The so called _modern C++_ comes with a tool that can be useful in this regard,
The so called _modern C++_ comes with a tool that can be useful in these terms,
the `std::function`. As an example, it can be used to create delegates.<br/>
However, there is no guarantee that an `std::function` doesn't perform
However, there is no guarantee that an `std::function` does not perform
allocations under the hood and this could be problematic sometimes. Furthermore,
it solves a problem but may not adapt well to other requirements that may arise
from time to time.
In case that the flexibility and power of an `std::function` isn't required or
if the price to pay for them is too high, `EnTT` offers a complete set of
if the price to pay for them is too high,` EnTT` offers a complete set of
lightweight classes to solve the same and many other problems.
# Delegate
A delegate can be used as a general purpose invoker with no memory overhead for
free functions, lambdas and members provided along with an instance on which to
invoke them.<br/>
free functions and members provided along with an instance on which to invoke
them.<br/>
It doesn't claim to be a drop-in replacement for an `std::function`, so don't
expect to use it whenever an `std::function` fits well. That said, it's most
likely even a better fit than an `std::function` in a lot of cases, so expect to
@@ -53,13 +52,15 @@ delegates:
entt::delegate<int(int)> delegate{};
```
What is needed to create an instance is to specify the type of the function the
delegate _accepts_, that is the signature of the functions it models.<br/>
However, attempting to use an empty delegate by invoking its function call
operator results in undefined behavior or most likely a crash.
All what is needed to create an instance is to specify the type of the function
the delegate will _contain_, that is the signature of the free function or the
member one wants to assign to it.
There exist a few overloads of the `connect` member function to initialize a
delegate:
Attempting to use an empty delegate by invoking its function call operator
results in undefined behavior or most likely a crash. Before to use a delegate,
it must be initialized.<br/>
There exists a bunch of overloads of the `connect` member function to do that.
As an example of use:
```cpp
int f(int i) { return i; }
@@ -76,7 +77,7 @@ my_struct instance;
delegate.connect<&my_struct::f>(instance);
```
The delegate class also accepts data members, if needed. In this case, the
The delegate class accepts also data members, if needed. In this case, the
function type of the delegate is such that the parameter list is empty and the
value of the data member is at least convertible to the return type.
@@ -93,11 +94,14 @@ delegate.connect<&g>(c);
delegate(42);
```
Function `g` is invoked with a reference to `c` and `42`. However, the function
type of the delegate is still `void(int)`. This is also the signature of its
function call operator.<br/>
Another interesting aspect of the delegate class is that it accepts functions
with a list of parameters that is shorter than that of its function type:
The function `g` will be invoked with a reference to `c` and `42`. However, the
function type of the delegate is still `void(int)`. This is also the signature
of its function call operator.
Another interesting aspect of the delegate class is that it accepts also
functions with a list of parameters that is shorter than that of the function
type used to specialize the delegate itself.<br/>
The following code is therefore perfectly valid:
```cpp
void g() { /* ... */ }
@@ -106,15 +110,9 @@ delegate(42);
```
Where the function type of the delegate is `void(int)` as above. It goes without
saying that the extra arguments are silently discarded internally. This is a
nice-to-have feature in a lot of cases, as an example when the `delegate` class
is used as a building block of a signal-slot system.<br/>
In fact, this filtering works both ways. The class tries to pass its first
_count_ arguments **first**, then the last _count_. Watch out for conversion
rules if in doubt when connecting a listener!<br/>
Arbitrary functions that pull random arguments from the delegate list 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.
saying that the extra arguments are silently discarded internally.<br/>
This is a nice-to-have feature in a lot of cases, as an example when the
`delegate` class is used as a building block of a signal-slot system.
To create and initialize a delegate at once, there are a few specialized
constructors. Because of the rules of the language, the listener is provided by
@@ -142,7 +140,7 @@ already shown in the examples above:
auto ret = delegate(42);
```
In all cases, listeners don't have to strictly follow the signature of the
In all cases, the listeners don't have to strictly follow the signature of the
delegate. As long as a listener can be invoked with the given arguments to yield
a result that is convertible to the given result type, everything works just
fine.
@@ -160,7 +158,7 @@ my_struct instance;
delegate(instance, 42);
```
In this case, it's not possible to _deduce_ the function type since the first
In this case, it's not possible to deduce the function type since the first
argument doesn't necessarily have to be a reference (for example, it can be a
pointer, as well as a const reference).<br/>
Therefore, the function type must be declared explicitly for unbound members.
@@ -168,9 +166,9 @@ Therefore, the function type must be declared explicitly for unbound members.
## Runtime arguments
The `delegate` class is meant to be used primarily with template arguments.
However, as a consequence of its design, it also offers minimal support for
However, as a consequence of its design, it can also offer minimal support for
runtime arguments.<br/>
When used like this, some features aren't supported though. In particular:
When used in this modality, some feature aren't supported though. In particular:
* Curried functions aren't accepted.
* Functions with an argument list that differs from that of the delegate aren't
@@ -211,7 +209,7 @@ their nuances. The reason is pretty simple: a `delegate` isn't a drop-in
replacement for an `std::function`. Instead, it tries to overcome the problems
with the latter.<br/>
That being said, non-capturing lambda functions are supported, even though some
features aren't available in this case.
feature aren't available in this case.
This is a logical consequence of the support for connecting functions at
runtime. Therefore, lambda functions undergo the same rules and
@@ -238,24 +236,6 @@ As above, the first parameter (`const void *`) isn't part of the function type
of the delegate and is used to dispatch arbitrary user data back and forth. In
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
@@ -267,17 +247,16 @@ Signals make use of delegates internally and therefore they undergo the same
rules and offer similar functionalities. It may be a good idea to consult the
documentation of the `delegate` class for further information.
A signal handler is can be used as a private data member without exposing any
_publish_ functionality to the clients of a class.<br/>
The basic idea is to impose a clear separation between the signal itself and the
`sink` class, that is a tool to be used to connect and disconnect listeners on
the fly.
A signal handler can be used as a private data member without exposing any
_publish_ functionality to the clients of a class. The basic idea is to impose a
clear separation between the signal itself and the `sink` class, that is a tool
to be used to connect and disconnect listeners on the fly.
The API of a signal handler is straightforward. If a collector is supplied to
the signal when something is published, all the values returned by its listeners
are literally _collected_ and used later by the caller. Otherwise, the class
the signal when something is published, all the values returned by the listeners
can be literally _collected_ and used later by the caller. Otherwise, the class
works just like a plain signal that emits events from time to time.<br/>
To create instances of signal handlers it's sufficient to provide the type of
To create instances of signal handlers it is sufficient to provide the type of
function to which they refer:
```cpp
@@ -315,31 +294,41 @@ sink.disconnect<&foo>();
sink.disconnect<&listener::bar>(instance);
// disconnect all member functions of an instance, if any
sink.disconnect(&instance);
sink.disconnect(instance);
// discards all listeners at once
sink.disconnect();
```
As shown above, listeners don't have to strictly follow the signature of the
As shown above, the listeners don't have to strictly follow the signature of the
signal. As long as a listener can be invoked with the given arguments to yield a
result that is convertible to the given return type, everything works just
fine.<br/>
It's also possible to connect a listener before other listeners already
contained by the signal. The `before` function returns a `sink` object correctly
initialized for the purpose that can be used to connect one or more listeners in
order and in the desired position:
```cpp
sink.before<&foo>().connect<&listener::bar>(instance);
```
In all cases, the `connect` member function returns by default a `connection`
object to be used as an alternative to break a connection by means of its
`release` member function.<br/>
A `scoped_connection` can also be created from a connection. In this case, the
link is broken automatically as soon as the object goes out of scope.
`release` member function. A `scoped_connection` can also be created from a
connection. In this case, the link is broken automatically as soon as the object
goes out of scope.
Once listeners are attached (or even if there are no listeners at all), events
and data in general are published through a signal by means of the `publish`
and data in general can be published through a signal by means of the `publish`
member function:
```cpp
signal.publish(42, 'c');
```
To collect data, the `collect` member function is used instead:
To collect data, the `collect` member function should be used instead. Below is
a minimal example to show how to use it:
```cpp
int f() { return 0; }
@@ -418,14 +407,12 @@ dispatcher.sink<an_event>().connect<&listener::receive>(listener);
dispatcher.sink<another_event>().connect<&listener::method>(listener);
```
Note that connecting listeners within event handlers can result in undefined
behavior.<br/>
The `disconnect` member function is used to remove one listener at a time or all
of them at once:
```cpp
dispatcher.sink<an_event>().disconnect<&listener::receive>(listener);
dispatcher.sink<another_event>().disconnect(&listener);
dispatcher.sink<another_event>().disconnect(listener);
```
The `trigger` member function serves the purpose of sending an immediate event
@@ -493,7 +480,8 @@ Originally designed to fit the requirements of
[`uvw`](https://github.com/skypjack/uvw) (a wrapper for `libuv` written in
modern C++), it was adapted later to be included in this library.
To create an emitter type, derived classes must inherit from the base as:
To create a custom emitter type, derived classes must inherit directly from the
base class as:
```cpp
struct my_emitter: emitter<my_emitter> {
@@ -501,10 +489,18 @@ struct my_emitter: emitter<my_emitter> {
}
```
Handlers for the different events are created internally on the fly. It's not
required to specify in advance the full list of accepted events.<br/>
Moreover, whenever an event is published, an emitter also passes a reference
to itself to its listeners.
The full list of accepted types of events isn't required. Handlers are created
internally on the fly and thus each type of event is accepted by default.
Whenever an event is published, an emitter provides the listeners with a
reference to itself along with a reference to the event. Therefore listeners
have an handy way to work with it without incurring in the need of capturing a
reference to the emitter itself.<br/>
In addition, an opaque object is returned each time a connection is established
between an emitter and a listener, allowing the caller to disconnect them at a
later time.<br/>
The opaque object used to handle connections is both movable and copyable. On
the other side, an event emitter is movable but not copyable by default.
To create new instances of an emitter, no arguments are required:
@@ -512,54 +508,90 @@ To create new instances of an emitter, no arguments are required:
my_emitter emitter{};
```
Listeners are movable and callable objects (free functions, lambdas, functors,
`std::function`s, whatever) whose function type is compatible with:
Listeners must be movable and callable objects (free functions, lambdas,
functors, `std::function`s, whatever) whose function type is compatible with:
```cpp
void(Type &, my_emitter &)
void(Event &, my_emitter &)
```
Where `Type` is the type of event they want to receive.<br/>
To attach a listener to an emitter, there exists the `on` member function:
Where `Event` is the type of event they want to listen.<br/>
There are two ways to attach a listener to an event emitter that differ
slightly from each other:
* To register a long-lived listener, use the `on` member function. It is meant
to register a listener designed to be invoked more than once for the given
event type.<br/>
As an example:
```cpp
auto conn = emitter.on<my_event>([](const my_event &event, my_emitter &emitter) {
// ...
});
```
The connection object can be freely discarded. Otherwise, it can be used later
to disconnect the listener if required.
* To register a short-lived listener, use the `once` member function. It is
meant to register a listener designed to be invoked only once for the given
event type. The listener is automatically disconnected after the first
invocation.<br/>
As an example:
```cpp
auto conn = emitter.once<my_event>([](const my_event &event, my_emitter &emitter) {
// ...
});
```
The connection object can be freely discarded. Otherwise, it can be used later
to disconnect the listener if required.
In both cases, the connection object can be used with the `erase` member
function:
```cpp
emitter.on<my_event>([](const my_event &event, my_emitter &emitter) {
// ...
});
emitter.erase(conn);
```
Similarly, the `reset` member function is used to disconnect listeners given a
type while `clear` is used to disconnect all listeners at once:
There are also two member functions to use either to disconnect all the
listeners for a given type of event or to clear the emitter:
```cpp
// resets the listener for my_event
emitter.erase<my_event>();
// removes all the listener for the specific event
emitter.clear<my_event>();
// resets all listeners
emitter.clear()
// removes all the listeners registered so far
emitter.clear();
```
To send an event to the listener registered on a given type, the `publish`
function is the way to go:
To send an event to all the listeners that are interested in it, the `publish`
member function offers a convenient approach that relieves users from having to
create the event:
```cpp
struct my_event { int i; };
// ...
emitter.publish(my_event{42});
emitter.publish<my_event>(42);
```
Finally, the `empty` member function tests if there exists at least a listener
registered with the event emitter while `contains` is used to check if a given
event type is associated with a valid listener:
Finally, the `empty` member function tests if there exists at least either a
listener registered with the event emitter or to a given type of event:
```cpp
if(emitter.contains<my_event>()) {
// ...
}
bool empty;
// checks if there is any listener registered for the specific event
empty = emitter.empty<my_event>();
// checks it there are listeners registered with the event emitter
empty = emitter.empty();
```
This class introduces a _nice-to-have_ model based on events and listeners.<br/>
More in general, it's a handy tool when the derived classes _wrap_ asynchronous
operations but it's not limited to such uses.
In general, the event emitter is a handy tool when the derived classes _wrap_
asynchronous operations, because it introduces a _nice-to-have_ model based on
events and listeners that kindly hides the complexity behind the scenes. However
it is not limited to such uses.

View File

@@ -1,34 +0,0 @@
[
{ "include": [ "@<gtest/internal/.*>", "private", "<gtest/gtest.h>", "public" ] },
{ "include": [ "@<gtest/gtest-.*>", "private", "<gtest/gtest.h>", "public" ] },
{ "include": [ "@[\"<].*/container/fwd.hpp[\">]", "private", "<entt/container/dense_map.hpp>", "public" ] },
{ "include": [ "@[\"<].*/container/fwd.hpp[\">]", "private", "<entt/container/dense_set.hpp>", "public" ] },
{ "include": [ "@[\"<].*/core/fwd.hpp[\">]", "private", "<entt/core/any.hpp>", "public" ] },
{ "include": [ "@[\"<].*/core/fwd.hpp[\">]", "private", "<entt/core/family.hpp>", "public" ] },
{ "include": [ "@[\"<].*/core/fwd.hpp[\">]", "private", "<entt/core/hashed_string.hpp>", "public" ] },
{ "include": [ "@[\"<].*/core/fwd.hpp[\">]", "private", "<entt/core/ident.hpp>", "public" ] },
{ "include": [ "@[\"<].*/core/fwd.hpp[\">]", "private", "<entt/core/monostate.hpp>", "public" ] },
{ "include": [ "@[\"<].*/core/fwd.hpp[\">]", "private", "<entt/core/type_info.hpp>", "public" ] },
{ "include": [ "@[\"<].*/core/fwd.hpp[\">]", "private", "<entt/core/type_traits.hpp>", "public" ] },
{ "include": [ "@[\"<].*/entity/fwd.hpp[\">]", "private", "<entt/entity/entity.hpp>", "public" ] },
{ "include": [ "@[\"<].*/entity/fwd.hpp[\">]", "private", "<entt/entity/group.hpp>", "public" ] },
{ "include": [ "@[\"<].*/entity/fwd.hpp[\">]", "private", "<entt/entity/handle.hpp>", "public" ] },
{ "include": [ "@[\"<].*/entity/fwd.hpp[\">]", "private", "<entt/entity/helper.hpp>", "public" ] },
{ "include": [ "@[\"<].*/entity/fwd.hpp[\">]", "private", "<entt/entity/observer.hpp>", "public" ] },
{ "include": [ "@[\"<].*/entity/fwd.hpp[\">]", "private", "<entt/entity/organizer.hpp>", "public" ] },
{ "include": [ "@[\"<].*/entity/fwd.hpp[\">]", "private", "<entt/entity/registry.hpp>", "public" ] },
{ "include": [ "@[\"<].*/entity/fwd.hpp[\">]", "private", "<entt/entity/runtime_view.hpp>", "public" ] },
{ "include": [ "@[\"<].*/entity/fwd.hpp[\">]", "private", "<entt/entity/snapshot.hpp>", "public" ] },
{ "include": [ "@[\"<].*/entity/fwd.hpp[\">]", "private", "<entt/entity/sparse_set.hpp>", "public" ] },
{ "include": [ "@[\"<].*/entity/fwd.hpp[\">]", "private", "<entt/entity/storage.hpp>", "public" ] },
{ "include": [ "@[\"<].*/entity/fwd.hpp[\">]", "private", "<entt/entity/view.hpp>", "public" ] },
{ "include": [ "@[\"<].*/meta/fwd.hpp[\">]", "private", "<entt/meta/meta.hpp>", "public" ] },
{ "include": [ "@[\"<].*/poly/fwd.hpp[\">]", "private", "<entt/poly/poly.hpp>", "public" ] },
{ "include": [ "@[\"<].*/resource/fwd.hpp[\">]", "private", "<entt/resource/cache.hpp>", "public" ] },
{ "include": [ "@[\"<].*/resource/fwd.hpp[\">]", "private", "<entt/resource/loader.hpp>", "public" ] },
{ "include": [ "@[\"<].*/resource/fwd.hpp[\">]", "private", "<entt/resource/resource.hpp>", "public" ] },
{ "include": [ "@[\"<].*/signal/fwd.hpp[\">]", "private", "<entt/signal/delegate.hpp>", "public" ] },
{ "include": [ "@[\"<].*/signal/fwd.hpp[\">]", "private", "<entt/signal/dispatcher.hpp>", "public" ] },
{ "include": [ "@[\"<].*/signal/fwd.hpp[\">]", "private", "<entt/signal/emitter.hpp>", "public" ] },
{ "include": [ "@[\"<].*/signal/fwd.hpp[\">]", "private", "<entt/signal/sigh.hpp>", "public" ] }
]

View File

@@ -23,7 +23,8 @@
</Expand>
</Type>
<Type Name="entt::type_info">
<DisplayString>{{ name={ alias,na } }}</DisplayString>
<DisplayString Condition="seq != 0u">{{ name={ alias,na } }}</DisplayString>
<DisplayString>{{}}</DisplayString>
<Expand>
<Item Name="[hash]">identifier</Item>
<Item Name="[index]">seq</Item>

View File

@@ -1,32 +1,63 @@
<?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="to_entity" Expression="*((traits_type::entity_type *)&amp;entity) &amp; traits_type::entity_mask">
<Parameter Name="entity" Type="traits_type::value_type &amp;"/>
<Intrinsic Name="pools_size" Expression="pools.packed.first_base::value.size()"/>
<Intrinsic Name="vars_size" Expression="vars.data.packed.first_base::value.size()"/>
<Intrinsic Name="to_entity" Expression="*((entity_traits::entity_type *)&amp;entity) &amp; entity_traits::entity_mask">
<Parameter Name="entity" Type="entity_traits::value_type &amp;"/>
</Intrinsic>
<DisplayString>{{ pools={ pools.size() } }}</DisplayString>
<DisplayString>{{ size={ entities.size() } }}</DisplayString>
<Expand>
<Item Name="[entities]">entities</Item>
<Item IncludeView="simple" Name="[entities]">entities,view(simple)nr</Item>
<Synthetic Name="[entities]" ExcludeView="simple">
<DisplayString>{ entities.size() }</DisplayString>
<Expand>
<CustomListItems>
<Variable Name="pos" InitialValue="0" />
<Variable Name="last" InitialValue="entities.size()"/>
<Loop>
<Break Condition="pos == last"/>
<If Condition="to_entity(entities[pos]) == pos">
<Item Name="[{ pos }]">entities[pos]</Item>
</If>
<Exec>++pos</Exec>
</Loop>
</CustomListItems>
</Expand>
</Synthetic>
<Synthetic Name="[destroyed]" ExcludeView="simple">
<DisplayString>{ to_entity(free_list) != entity_traits::entity_mask }</DisplayString>
<Expand>
<CustomListItems>
<Variable Name="it" InitialValue="to_entity(free_list)" />
<Loop>
<Break Condition="it == entity_traits::entity_mask"/>
<Item Name="[{ it }]">entities[it]</Item>
<Exec>it = to_entity(entities[it])</Exec>
</Loop>
</CustomListItems>
</Expand>
</Synthetic>
<Synthetic Name="[pools]">
<DisplayString>{ pools.size() }</DisplayString>
<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.ctx.size() }</DisplayString>
<DisplayString>{ vars_size() }</DisplayString>
<Expand>
<IndexListItems>
<Size>vars.ctx.size()</Size>
<ValueNode>vars.ctx.packed.first_base::value[$i].element.second</ValueNode>
<Size>vars_size()</Size>
<ValueNode>vars.data.packed.first_base::value[$i].element.second</ValueNode>
</IndexListItems>
</Expand>
</Synthetic>
@@ -38,20 +69,20 @@
<Item Name="[capacity]" ExcludeView="simple">packed.capacity()</Item>
<Item Name="[policy]">mode,en</Item>
<Synthetic Name="[sparse]">
<DisplayString>{ sparse.size() * traits_type::page_size }</DisplayString>
<DisplayString>{ sparse.size() * entity_traits::page_size }</DisplayString>
<Expand>
<ExpandedItem IncludeView="simple">sparse,view(simple)</ExpandedItem>
<CustomListItems ExcludeView="simple">
<Variable Name="pos" InitialValue="0"/>
<Variable Name="page" InitialValue="0"/>
<Variable Name="offset" InitialValue="0"/>
<Variable Name="last" InitialValue="sparse.size() * traits_type::page_size"/>
<Variable Name="last" InitialValue="sparse.size() * entity_traits::page_size"/>
<Loop>
<Break Condition="pos == last"/>
<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>
<Exec>page = pos / entity_traits::page_size</Exec>
<Exec>offset = pos &amp; (entity_traits::page_size - 1)</Exec>
<If Condition="sparse[page] &amp;&amp; (*((entity_traits::entity_type *)&amp;sparse[page][offset]) &lt; ~entity_traits::entity_mask)">
<Item Name="[{ pos }]">*((entity_traits::entity_type *)&amp;sparse[page][offset]) &amp; entity_traits::entity_mask</Item>
</If>
<Exec>++pos</Exec>
</Loop>
@@ -67,7 +98,7 @@
<Variable Name="last" InitialValue="packed.size()"/>
<Loop>
<Break Condition="pos == last"/>
<If Condition="*((traits_type::entity_type *)&amp;packed[pos]) &lt; ~traits_type::entity_mask">
<If Condition="*((entity_traits::entity_type *)&amp;packed[pos]) &lt; ~entity_traits::entity_mask">
<Item Name="[{ pos }]">packed[pos]</Item>
</If>
<Exec>++pos</Exec>
@@ -80,19 +111,18 @@
<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">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="[capacity]" Optional="true" ExcludeView="simple">packed.first_base::value.capacity() * comp_traits::page_size</Item>
<Item Name="[page size]" Optional="true" ExcludeView="simple">comp_traits::page_size</Item>
<Item Name="[base]" ExcludeView="simple">(base_type*)this,nand</Item>
<Item Name="[base]" IncludeView="simple">(base_type*)this,view(simple)nand</Item>
<!-- having SFINAE-like techniques in natvis is priceless :) -->
<CustomListItems Condition="payload.size() != 0" Optional="true">
<CustomListItems Condition="packed.first_base::value.size() != 0" Optional="true">
<Variable Name="pos" InitialValue="0" />
<Variable Name="last" InitialValue="base_type::packed.size()"/>
<Loop>
<Break Condition="pos == last"/>
<If Condition="*((base_type::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 Condition="*((base_type::entity_traits::entity_type *)&amp;base_type::packed[pos]) &lt; ~base_type::entity_traits::entity_mask">
<Item Name="[{ pos }]">packed.first_base::value[pos / comp_traits::page_size][pos &amp; (comp_traits::page_size - 1)]</Item>
</If>
<Exec>++pos</Exec>
</Loop>
@@ -106,14 +136,6 @@
<Item Name="[filter]">filter,na</Item>
</Expand>
</Type>
<Type Name="entt::basic_runtime_view&lt;*&gt;">
<DisplayString Condition="pools.size() != 0u">{{ size_hint={ pools[0]->packed.size() } }}</DisplayString>
<DisplayString>{{ size_hint=0 }}</DisplayString>
<Expand>
<Item Name="[pools]">pools,na</Item>
<Item Name="[filter]">filter,na</Item>
</Expand>
</Type>
<Type Name="entt::null_t">
<DisplayString>&lt;null&gt;</DisplayString>
</Type>

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="entt::adjacency_matrix&lt;*&gt;">
<DisplayString>{{ size={ vert } }}</DisplayString>
<Expand>
<CustomListItems>
<Variable Name="pos" InitialValue="0" />
<Variable Name="last" InitialValue="vert * vert"/>
<Loop>
<Break Condition="pos == last"/>
<If Condition="matrix[pos] != 0u">
<Item Name="{pos / vert}">pos % vert</Item>
</If>
<Exec>++pos</Exec>
</Loop>
</CustomListItems>
</Expand>
</Type>
</AutoVisualizer>

View File

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

View File

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

@@ -4,17 +4,22 @@
#include "version.h"
#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION)
# define ENTT_CONSTEXPR
# define ENTT_THROW throw
# define ENTT_TRY try
# define ENTT_CATCH catch(...)
#else
# define ENTT_CONSTEXPR constexpr // use only with throwing functions (waiting for C++20)
# define ENTT_THROW
# define ENTT_TRY if(true)
# define ENTT_CATCH if(false)
#endif
#ifndef ENTT_NOEXCEPT
# define ENTT_NOEXCEPT noexcept
# define ENTT_NOEXCEPT_IF(expr) noexcept(expr)
# else
# define ENTT_NOEXCEPT_IF(...)
#endif
#ifdef ENTT_USE_ATOMIC
# include <atomic>
# define ENTT_MAYBE_ATOMIC(Type) std::atomic<Type>
@@ -37,25 +42,16 @@
#ifdef ENTT_DISABLE_ASSERT
# undef ENTT_ASSERT
# define ENTT_ASSERT(condition, msg) (void(0))
# define ENTT_ASSERT(...) (void(0))
#elif !defined ENTT_ASSERT
# include <cassert>
# define ENTT_ASSERT(condition, msg) assert(condition)
# define ENTT_ASSERT(condition, ...) assert(condition)
#endif
#ifdef ENTT_DISABLE_ASSERT
# undef ENTT_ASSERT_CONSTEXPR
# define ENTT_ASSERT_CONSTEXPR(condition, msg) (void(0))
#elif !defined ENTT_ASSERT_CONSTEXPR
# define ENTT_ASSERT_CONSTEXPR(condition, msg) ENTT_ASSERT(condition, msg)
#endif
#define ENTT_FAIL(msg) ENTT_ASSERT(false, msg);
#ifdef ENTT_NO_ETO
# define ENTT_ETO_TYPE(Type) void
# define ENTT_IGNORE_IF_EMPTY false
#else
# define ENTT_ETO_TYPE(Type) Type
# define ENTT_IGNORE_IF_EMPTY true
#endif
#ifdef ENTT_STANDARD_CPP

View File

@@ -4,7 +4,7 @@
#include "macro.h"
#define ENTT_VERSION_MAJOR 3
#define ENTT_VERSION_MINOR 12
#define ENTT_VERSION_MINOR 10
#define ENTT_VERSION_PATCH 1
#define ENTT_VERSION \

View File

@@ -1,6 +1,7 @@
#ifndef ENTT_CONTAINER_DENSE_MAP_HPP
#define ENTT_CONTAINER_DENSE_MAP_HPP
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <functional>
@@ -70,109 +71,109 @@ public:
using difference_type = std::ptrdiff_t;
using iterator_category = std::input_iterator_tag;
constexpr dense_map_iterator() noexcept
dense_map_iterator() ENTT_NOEXCEPT
: it{} {}
constexpr dense_map_iterator(const It iter) noexcept
dense_map_iterator(const It iter) ENTT_NOEXCEPT
: it{iter} {}
template<typename Other, typename = std::enable_if_t<!std::is_same_v<It, Other> && std::is_constructible_v<It, Other>>>
constexpr dense_map_iterator(const dense_map_iterator<Other> &other) noexcept
dense_map_iterator(const dense_map_iterator<Other> &other) ENTT_NOEXCEPT
: it{other.it} {}
constexpr dense_map_iterator &operator++() noexcept {
dense_map_iterator &operator++() ENTT_NOEXCEPT {
return ++it, *this;
}
constexpr dense_map_iterator operator++(int) noexcept {
dense_map_iterator operator++(int) ENTT_NOEXCEPT {
dense_map_iterator orig = *this;
return ++(*this), orig;
}
constexpr dense_map_iterator &operator--() noexcept {
dense_map_iterator &operator--() ENTT_NOEXCEPT {
return --it, *this;
}
constexpr dense_map_iterator operator--(int) noexcept {
dense_map_iterator operator--(int) ENTT_NOEXCEPT {
dense_map_iterator orig = *this;
return operator--(), orig;
}
constexpr dense_map_iterator &operator+=(const difference_type value) noexcept {
dense_map_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT {
it += value;
return *this;
}
constexpr dense_map_iterator operator+(const difference_type value) const noexcept {
dense_map_iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
dense_map_iterator copy = *this;
return (copy += value);
}
constexpr dense_map_iterator &operator-=(const difference_type value) noexcept {
dense_map_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT {
return (*this += -value);
}
constexpr dense_map_iterator operator-(const difference_type value) const noexcept {
dense_map_iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
return (*this + -value);
}
[[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept {
[[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT {
return {it[value].element.first, it[value].element.second};
}
[[nodiscard]] constexpr pointer operator->() const noexcept {
[[nodiscard]] pointer operator->() const ENTT_NOEXCEPT {
return operator*();
}
[[nodiscard]] constexpr reference operator*() const noexcept {
[[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
return {it->element.first, it->element.second};
}
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 std::ptrdiff_t operator-(const dense_map_iterator<ILhs> &, const dense_map_iterator<IRhs> &) ENTT_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 bool operator==(const dense_map_iterator<ILhs> &, const dense_map_iterator<IRhs> &) ENTT_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 bool operator<(const dense_map_iterator<ILhs> &, const dense_map_iterator<IRhs> &) ENTT_NOEXCEPT;
private:
It it;
};
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept {
template<typename ILhs, typename IRhs>
[[nodiscard]] std::ptrdiff_t operator-(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return lhs.it - rhs.it;
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator==(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept {
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator==(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return lhs.it == rhs.it;
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator!=(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept {
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator!=(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator<(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept {
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator<(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return lhs.it < rhs.it;
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator>(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept {
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator>(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return rhs < lhs;
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator<=(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept {
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator<=(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return !(lhs > rhs);
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator>=(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept {
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator>=(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return !(lhs < rhs);
}
@@ -191,37 +192,37 @@ public:
using difference_type = std::ptrdiff_t;
using iterator_category = std::input_iterator_tag;
constexpr dense_map_local_iterator() noexcept
dense_map_local_iterator() ENTT_NOEXCEPT
: it{},
offset{} {}
constexpr dense_map_local_iterator(It iter, const std::size_t pos) noexcept
dense_map_local_iterator(It iter, const std::size_t pos) ENTT_NOEXCEPT
: it{iter},
offset{pos} {}
template<typename Other, typename = std::enable_if_t<!std::is_same_v<It, Other> && std::is_constructible_v<It, Other>>>
constexpr dense_map_local_iterator(const dense_map_local_iterator<Other> &other) noexcept
dense_map_local_iterator(const dense_map_local_iterator<Other> &other) ENTT_NOEXCEPT
: it{other.it},
offset{other.offset} {}
constexpr dense_map_local_iterator &operator++() noexcept {
dense_map_local_iterator &operator++() ENTT_NOEXCEPT {
return offset = it[offset].next, *this;
}
constexpr dense_map_local_iterator operator++(int) noexcept {
dense_map_local_iterator operator++(int) ENTT_NOEXCEPT {
dense_map_local_iterator orig = *this;
return ++(*this), orig;
}
[[nodiscard]] constexpr pointer operator->() const noexcept {
[[nodiscard]] pointer operator->() const ENTT_NOEXCEPT {
return operator*();
}
[[nodiscard]] constexpr reference operator*() const noexcept {
[[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
return {it[offset].element.first, it[offset].element.second};
}
[[nodiscard]] constexpr std::size_t index() const noexcept {
[[nodiscard]] std::size_t index() const ENTT_NOEXCEPT {
return offset;
}
@@ -230,13 +231,13 @@ private:
std::size_t offset;
};
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator==(const dense_map_local_iterator<Lhs> &lhs, const dense_map_local_iterator<Rhs> &rhs) noexcept {
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator==(const dense_map_local_iterator<ILhs> &lhs, const dense_map_local_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return lhs.index() == rhs.index();
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator!=(const dense_map_local_iterator<Lhs> &lhs, const dense_map_local_iterator<Rhs> &rhs) noexcept {
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator!=(const dense_map_local_iterator<ILhs> &lhs, const dense_map_local_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}
@@ -266,14 +267,14 @@ class dense_map {
static constexpr std::size_t minimum_capacity = 8u;
using node_type = internal::dense_map_node<Key, Type>;
using alloc_traits = std::allocator_traits<Allocator>;
using alloc_traits = typename std::allocator_traits<Allocator>;
static_assert(std::is_same_v<typename alloc_traits::value_type, std::pair<const Key, Type>>, "Invalid value type");
using sparse_container_type = std::vector<std::size_t, typename alloc_traits::template rebind_alloc<std::size_t>>;
using packed_container_type = std::vector<node_type, typename alloc_traits::template rebind_alloc<node_type>>;
template<typename Other>
[[nodiscard]] std::size_t key_to_bucket(const Other &key) const noexcept {
return fast_mod(static_cast<size_type>(sparse.second()(key)), bucket_count());
[[nodiscard]] std::size_t key_to_bucket(const Other &key) const ENTT_NOEXCEPT {
return fast_mod(sparse.second()(key), bucket_count());
}
template<typename Other>
@@ -331,8 +332,8 @@ class dense_map {
void move_and_pop(const std::size_t pos) {
if(const auto last = size() - 1u; pos != last) {
size_type *curr = sparse.first().data() + key_to_bucket(packed.first().back().element.first);
packed.first()[pos] = std::move(packed.first().back());
size_type *curr = sparse.first().data() + key_to_bucket(packed.first().back().element.first);
for(; *curr != last; curr = &packed.first()[*curr].next) {}
*curr = pos;
}
@@ -372,7 +373,7 @@ public:
/*! @brief Default constructor. */
dense_map()
: dense_map{minimum_capacity} {}
: dense_map(minimum_capacity) {}
/**
* @brief Constructs an empty container with a given allocator.
@@ -384,35 +385,35 @@ public:
/**
* @brief Constructs an empty container with a given allocator and user
* supplied minimal number of buckets.
* @param cnt Minimal number of buckets.
* @param bucket_count Minimal number of buckets.
* @param allocator The allocator to use.
*/
dense_map(const size_type cnt, const allocator_type &allocator)
: dense_map{cnt, hasher{}, key_equal{}, allocator} {}
dense_map(const size_type bucket_count, const allocator_type &allocator)
: dense_map{bucket_count, hasher{}, key_equal{}, allocator} {}
/**
* @brief Constructs an empty container with a given allocator, hash
* function and user supplied minimal number of buckets.
* @param cnt Minimal number of buckets.
* @param bucket_count Minimal number of buckets.
* @param hash Hash function to use.
* @param allocator The allocator to use.
*/
dense_map(const size_type cnt, const hasher &hash, const allocator_type &allocator)
: dense_map{cnt, hash, key_equal{}, allocator} {}
dense_map(const size_type bucket_count, const hasher &hash, const allocator_type &allocator)
: dense_map{bucket_count, hash, key_equal{}, allocator} {}
/**
* @brief Constructs an empty container with a given allocator, hash
* function, compare function and user supplied minimal number of buckets.
* @param cnt Minimal number of buckets.
* @param bucket_count Minimal number of buckets.
* @param hash Hash function to use.
* @param equal Compare function to use.
* @param allocator The allocator to use.
*/
explicit dense_map(const size_type cnt, const hasher &hash = hasher{}, const key_equal &equal = key_equal{}, const allocator_type &allocator = allocator_type{})
explicit dense_map(const size_type bucket_count, const hasher &hash = hasher{}, const key_equal &equal = key_equal{}, const allocator_type &allocator = allocator_type{})
: sparse{allocator, hash},
packed{allocator, equal},
threshold{default_threshold} {
rehash(cnt);
rehash(bucket_count);
}
/*! @brief Default copy constructor. */
@@ -429,7 +430,7 @@ public:
threshold{other.threshold} {}
/*! @brief Default move constructor. */
dense_map(dense_map &&) noexcept(std::is_nothrow_move_constructible_v<compressed_pair<sparse_container_type, hasher>> &&std::is_nothrow_move_constructible_v<compressed_pair<packed_container_type, key_equal>>) = default;
dense_map(dense_map &&) = default;
/**
* @brief Allocator-extended move constructor.
@@ -451,53 +452,59 @@ public:
* @brief Default move assignment operator.
* @return This container.
*/
dense_map &operator=(dense_map &&) noexcept(std::is_nothrow_move_assignable_v<compressed_pair<sparse_container_type, hasher>> &&std::is_nothrow_move_assignable_v<compressed_pair<packed_container_type, key_equal>>) = default;
dense_map &operator=(dense_map &&) = default;
/**
* @brief Returns the associated allocator.
* @return The associated allocator.
*/
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
[[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT {
return sparse.first().get_allocator();
}
/**
* @brief Returns an iterator to the beginning.
*
* The returned iterator points to the first instance of the internal array.
* If the array is empty, the returned iterator will be equal to `end()`.
*
* @return An iterator to the first instance of the internal array.
*/
[[nodiscard]] const_iterator cbegin() const noexcept {
[[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT {
return packed.first().begin();
}
/*! @copydoc cbegin */
[[nodiscard]] const_iterator begin() const noexcept {
[[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT {
return cbegin();
}
/*! @copydoc begin */
[[nodiscard]] iterator begin() noexcept {
[[nodiscard]] iterator begin() ENTT_NOEXCEPT {
return packed.first().begin();
}
/**
* @brief Returns an iterator to the end.
*
* The returned iterator points to the element following the last instance
* of the internal array. Attempting to dereference the returned iterator
* results in undefined behavior.
*
* @return An iterator to the element following the last instance of the
* internal array.
*/
[[nodiscard]] const_iterator cend() const noexcept {
[[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT {
return packed.first().end();
}
/*! @copydoc cend */
[[nodiscard]] const_iterator end() const noexcept {
[[nodiscard]] const_iterator end() const ENTT_NOEXCEPT {
return cend();
}
/*! @copydoc end */
[[nodiscard]] iterator end() noexcept {
[[nodiscard]] iterator end() ENTT_NOEXCEPT {
return packed.first().end();
}
@@ -505,7 +512,7 @@ public:
* @brief Checks whether a container is empty.
* @return True if the container is empty, false otherwise.
*/
[[nodiscard]] bool empty() const noexcept {
[[nodiscard]] bool empty() const ENTT_NOEXCEPT {
return packed.first().empty();
}
@@ -513,20 +520,12 @@ public:
* @brief Returns the number of elements in a container.
* @return Number of elements in a container.
*/
[[nodiscard]] size_type size() const noexcept {
[[nodiscard]] size_type size() const ENTT_NOEXCEPT {
return packed.first().size();
}
/**
* @brief Returns the maximum possible number of elements.
* @return Maximum possible number of elements.
*/
[[nodiscard]] size_type max_size() const noexcept {
return packed.first().max_size();
}
/*! @brief Clears the container. */
void clear() noexcept {
void clear() ENTT_NOEXCEPT {
sparse.first().clear();
packed.first().clear();
rehash(0u);
@@ -742,27 +741,6 @@ public:
return insert_or_do_nothing(std::move(key)).first->second;
}
/**
* @brief Returns the number of elements matching a key (either 1 or 0).
* @param key Key value of an element to search for.
* @return Number of elements matching the key (either 1 or 0).
*/
[[nodiscard]] size_type count(const key_type &key) const {
return find(key) != end();
}
/**
* @brief Returns the number of elements matching a key (either 1 or 0).
* @tparam Other Type of the key value of an element to search for.
* @param key Key value of an element to search for.
* @return Number of elements matching the key (either 1 or 0).
*/
template<typename Other>
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, size_type>>
count(const Other &key) const {
return find(key) != end();
}
/**
* @brief Finds an element with a given key.
* @param key Key value of an element to search for.
@@ -780,7 +758,7 @@ public:
/**
* @brief Finds an element with a key that compares _equivalent_ to a given
* key.
* value.
* @tparam Other Type of the key value of an element to search for.
* @param key Key value of an element to search for.
* @return An iterator to an element with the given key. If no such element
@@ -799,46 +777,6 @@ public:
return constrained_find(key, key_to_bucket(key));
}
/**
* @brief Returns a range containing all elements with a given key.
* @param key Key value of an element to search for.
* @return A pair of iterators pointing to the first element and past the
* last element of the range.
*/
[[nodiscard]] std::pair<iterator, iterator> equal_range(const key_type &key) {
const auto it = find(key);
return {it, it + !(it == end())};
}
/*! @copydoc equal_range */
[[nodiscard]] std::pair<const_iterator, const_iterator> equal_range(const key_type &key) const {
const auto it = find(key);
return {it, it + !(it == cend())};
}
/**
* @brief Returns a range containing all elements that compare _equivalent_
* to a given key.
* @tparam Other Type of an element to search for.
* @param key Key value of an element to search for.
* @return A pair of iterators pointing to the first element and past the
* last element of the range.
*/
template<typename Other>
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, std::pair<iterator, iterator>>>
equal_range(const Other &key) {
const auto it = find(key);
return {it, it + !(it == end())};
}
/*! @copydoc equal_range */
template<typename Other>
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, std::pair<const_iterator, const_iterator>>>
equal_range(const Other &key) const {
const auto it = find(key);
return {it, it + !(it == cend())};
}
/**
* @brief Checks if the container contains an element with a given key.
* @param key Key value of an element to search for.
@@ -902,7 +840,7 @@ public:
* @param index An index of a bucket to access.
* @return An iterator to the end of the given bucket.
*/
[[nodiscard]] const_local_iterator end(const size_type index) const {
[[nodiscard]] const_local_iterator end([[maybe_unused]] const size_type index) const {
return cend(index);
}
@@ -978,19 +916,15 @@ public:
/**
* @brief Reserves at least the specified number of buckets and regenerates
* the hash table.
* @param cnt New number of buckets.
* @param count New number of buckets.
*/
void rehash(const size_type cnt) {
auto value = cnt > minimum_capacity ? cnt : minimum_capacity;
const auto cap = static_cast<size_type>(size() / max_load_factor());
value = value > cap ? value : cap;
void rehash(const size_type count) {
auto value = (std::max)(count, minimum_capacity);
value = (std::max)(value, static_cast<size_type>(size() / max_load_factor()));
if(const auto sz = next_power_of_two(value); sz != bucket_count()) {
sparse.first().resize(sz);
for(auto &&elem: sparse.first()) {
elem = std::numeric_limits<size_type>::max();
}
std::fill(sparse.first().begin(), sparse.first().end(), (std::numeric_limits<size_type>::max)());
for(size_type pos{}, last = size(); pos < last; ++pos) {
const auto index = key_to_bucket(packed.first()[pos].element.first);
@@ -1002,11 +936,11 @@ public:
/**
* @brief Reserves space for at least the specified number of elements and
* regenerates the hash table.
* @param cnt New number of elements.
* @param count New number of elements.
*/
void reserve(const size_type cnt) {
packed.first().reserve(cnt);
rehash(static_cast<size_type>(std::ceil(cnt / max_load_factor())));
void reserve(const size_type count) {
packed.first().reserve(count);
rehash(static_cast<size_type>(std::ceil(count / max_load_factor())));
}
/**

View File

@@ -1,6 +1,7 @@
#ifndef ENTT_CONTAINER_DENSE_SET_HPP
#define ENTT_CONTAINER_DENSE_SET_HPP
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <functional>
@@ -38,109 +39,109 @@ public:
using difference_type = std::ptrdiff_t;
using iterator_category = std::random_access_iterator_tag;
constexpr dense_set_iterator() noexcept
dense_set_iterator() ENTT_NOEXCEPT
: it{} {}
constexpr dense_set_iterator(const It iter) noexcept
dense_set_iterator(const It iter) ENTT_NOEXCEPT
: it{iter} {}
template<typename Other, typename = std::enable_if_t<!std::is_same_v<It, Other> && std::is_constructible_v<It, Other>>>
constexpr dense_set_iterator(const dense_set_iterator<Other> &other) noexcept
dense_set_iterator(const dense_set_iterator<Other> &other) ENTT_NOEXCEPT
: it{other.it} {}
constexpr dense_set_iterator &operator++() noexcept {
dense_set_iterator &operator++() ENTT_NOEXCEPT {
return ++it, *this;
}
constexpr dense_set_iterator operator++(int) noexcept {
dense_set_iterator operator++(int) ENTT_NOEXCEPT {
dense_set_iterator orig = *this;
return ++(*this), orig;
}
constexpr dense_set_iterator &operator--() noexcept {
dense_set_iterator &operator--() ENTT_NOEXCEPT {
return --it, *this;
}
constexpr dense_set_iterator operator--(int) noexcept {
dense_set_iterator operator--(int) ENTT_NOEXCEPT {
dense_set_iterator orig = *this;
return operator--(), orig;
}
constexpr dense_set_iterator &operator+=(const difference_type value) noexcept {
dense_set_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT {
it += value;
return *this;
}
constexpr dense_set_iterator operator+(const difference_type value) const noexcept {
dense_set_iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
dense_set_iterator copy = *this;
return (copy += value);
}
constexpr dense_set_iterator &operator-=(const difference_type value) noexcept {
dense_set_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT {
return (*this += -value);
}
constexpr dense_set_iterator operator-(const difference_type value) const noexcept {
dense_set_iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
return (*this + -value);
}
[[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept {
[[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT {
return it[value].second;
}
[[nodiscard]] constexpr pointer operator->() const noexcept {
[[nodiscard]] pointer operator->() const ENTT_NOEXCEPT {
return std::addressof(it->second);
}
[[nodiscard]] constexpr reference operator*() const noexcept {
[[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
return *operator->();
}
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 std::ptrdiff_t operator-(const dense_set_iterator<ILhs> &, const dense_set_iterator<IRhs> &) ENTT_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 bool operator==(const dense_set_iterator<ILhs> &, const dense_set_iterator<IRhs> &) ENTT_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 bool operator<(const dense_set_iterator<ILhs> &, const dense_set_iterator<IRhs> &) ENTT_NOEXCEPT;
private:
It it;
};
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
template<typename ILhs, typename IRhs>
[[nodiscard]] std::ptrdiff_t operator-(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return lhs.it - rhs.it;
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator==(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator==(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return lhs.it == rhs.it;
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator!=(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator!=(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator<(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator<(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return lhs.it < rhs.it;
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator>(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator>(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return rhs < lhs;
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator<=(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator<=(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return !(lhs > rhs);
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator>=(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator>=(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return !(lhs < rhs);
}
@@ -156,37 +157,37 @@ public:
using difference_type = std::ptrdiff_t;
using iterator_category = std::forward_iterator_tag;
constexpr dense_set_local_iterator() noexcept
dense_set_local_iterator() ENTT_NOEXCEPT
: it{},
offset{} {}
constexpr dense_set_local_iterator(It iter, const std::size_t pos) noexcept
dense_set_local_iterator(It iter, const std::size_t pos) ENTT_NOEXCEPT
: it{iter},
offset{pos} {}
template<typename Other, typename = std::enable_if_t<!std::is_same_v<It, Other> && std::is_constructible_v<It, Other>>>
constexpr dense_set_local_iterator(const dense_set_local_iterator<Other> &other) noexcept
dense_set_local_iterator(const dense_set_local_iterator<Other> &other) ENTT_NOEXCEPT
: it{other.it},
offset{other.offset} {}
constexpr dense_set_local_iterator &operator++() noexcept {
dense_set_local_iterator &operator++() ENTT_NOEXCEPT {
return offset = it[offset].first, *this;
}
constexpr dense_set_local_iterator operator++(int) noexcept {
dense_set_local_iterator operator++(int) ENTT_NOEXCEPT {
dense_set_local_iterator orig = *this;
return ++(*this), orig;
}
[[nodiscard]] constexpr pointer operator->() const noexcept {
[[nodiscard]] pointer operator->() const ENTT_NOEXCEPT {
return std::addressof(it[offset].second);
}
[[nodiscard]] constexpr reference operator*() const noexcept {
[[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
return *operator->();
}
[[nodiscard]] constexpr std::size_t index() const noexcept {
[[nodiscard]] std::size_t index() const ENTT_NOEXCEPT {
return offset;
}
@@ -195,13 +196,13 @@ private:
std::size_t offset;
};
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator==(const dense_set_local_iterator<Lhs> &lhs, const dense_set_local_iterator<Rhs> &rhs) noexcept {
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator==(const dense_set_local_iterator<ILhs> &lhs, const dense_set_local_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return lhs.index() == rhs.index();
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator!=(const dense_set_local_iterator<Lhs> &lhs, const dense_set_local_iterator<Rhs> &rhs) noexcept {
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator!=(const dense_set_local_iterator<ILhs> &lhs, const dense_set_local_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}
@@ -236,8 +237,8 @@ class dense_set {
using packed_container_type = std::vector<node_type, typename alloc_traits::template rebind_alloc<node_type>>;
template<typename Other>
[[nodiscard]] std::size_t value_to_bucket(const Other &value) const noexcept {
return fast_mod(static_cast<size_type>(sparse.second()(value)), bucket_count());
[[nodiscard]] std::size_t value_to_bucket(const Other &value) const ENTT_NOEXCEPT {
return fast_mod(sparse.second()(value), bucket_count());
}
template<typename Other>
@@ -279,8 +280,8 @@ class dense_set {
void move_and_pop(const std::size_t pos) {
if(const auto last = size() - 1u; pos != last) {
size_type *curr = sparse.first().data() + value_to_bucket(packed.first().back().second);
packed.first()[pos] = std::move(packed.first().back());
size_type *curr = sparse.first().data() + value_to_bucket(packed.first().back().second);
for(; *curr != last; curr = &packed.first()[*curr].first) {}
*curr = pos;
}
@@ -318,7 +319,7 @@ public:
/*! @brief Default constructor. */
dense_set()
: dense_set{minimum_capacity} {}
: dense_set(minimum_capacity) {}
/**
* @brief Constructs an empty container with a given allocator.
@@ -330,35 +331,35 @@ public:
/**
* @brief Constructs an empty container with a given allocator and user
* supplied minimal number of buckets.
* @param cnt Minimal number of buckets.
* @param bucket_count Minimal number of buckets.
* @param allocator The allocator to use.
*/
dense_set(const size_type cnt, const allocator_type &allocator)
: dense_set{cnt, hasher{}, key_equal{}, allocator} {}
dense_set(const size_type bucket_count, const allocator_type &allocator)
: dense_set{bucket_count, hasher{}, key_equal{}, allocator} {}
/**
* @brief Constructs an empty container with a given allocator, hash
* function and user supplied minimal number of buckets.
* @param cnt Minimal number of buckets.
* @param bucket_count Minimal number of buckets.
* @param hash Hash function to use.
* @param allocator The allocator to use.
*/
dense_set(const size_type cnt, const hasher &hash, const allocator_type &allocator)
: dense_set{cnt, hash, key_equal{}, allocator} {}
dense_set(const size_type bucket_count, const hasher &hash, const allocator_type &allocator)
: dense_set{bucket_count, hash, key_equal{}, allocator} {}
/**
* @brief Constructs an empty container with a given allocator, hash
* function, compare function and user supplied minimal number of buckets.
* @param cnt Minimal number of buckets.
* @param bucket_count Minimal number of buckets.
* @param hash Hash function to use.
* @param equal Compare function to use.
* @param allocator The allocator to use.
*/
explicit dense_set(const size_type cnt, const hasher &hash = hasher{}, const key_equal &equal = key_equal{}, const allocator_type &allocator = allocator_type{})
explicit dense_set(const size_type bucket_count, const hasher &hash = hasher{}, const key_equal &equal = key_equal{}, const allocator_type &allocator = allocator_type{})
: sparse{allocator, hash},
packed{allocator, equal},
threshold{default_threshold} {
rehash(cnt);
rehash(bucket_count);
}
/*! @brief Default copy constructor. */
@@ -375,7 +376,7 @@ public:
threshold{other.threshold} {}
/*! @brief Default move constructor. */
dense_set(dense_set &&) noexcept(std::is_nothrow_move_constructible_v<compressed_pair<sparse_container_type, hasher>> &&std::is_nothrow_move_constructible_v<compressed_pair<packed_container_type, key_equal>>) = default;
dense_set(dense_set &&) = default;
/**
* @brief Allocator-extended move constructor.
@@ -397,53 +398,59 @@ public:
* @brief Default move assignment operator.
* @return This container.
*/
dense_set &operator=(dense_set &&) noexcept(std::is_nothrow_move_assignable_v<compressed_pair<sparse_container_type, hasher>> &&std::is_nothrow_move_assignable_v<compressed_pair<packed_container_type, key_equal>>) = default;
dense_set &operator=(dense_set &&) = default;
/**
* @brief Returns the associated allocator.
* @return The associated allocator.
*/
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
[[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT {
return sparse.first().get_allocator();
}
/**
* @brief Returns an iterator to the beginning.
*
* The returned iterator points to the first instance of the internal array.
* If the array is empty, the returned iterator will be equal to `end()`.
*
* @return An iterator to the first instance of the internal array.
*/
[[nodiscard]] const_iterator cbegin() const noexcept {
[[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT {
return packed.first().begin();
}
/*! @copydoc cbegin */
[[nodiscard]] const_iterator begin() const noexcept {
[[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT {
return cbegin();
}
/*! @copydoc begin */
[[nodiscard]] iterator begin() noexcept {
[[nodiscard]] iterator begin() ENTT_NOEXCEPT {
return packed.first().begin();
}
/**
* @brief Returns an iterator to the end.
*
* The returned iterator points to the element following the last instance
* of the internal array. Attempting to dereference the returned iterator
* results in undefined behavior.
*
* @return An iterator to the element following the last instance of the
* internal array.
*/
[[nodiscard]] const_iterator cend() const noexcept {
[[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT {
return packed.first().end();
}
/*! @copydoc cend */
[[nodiscard]] const_iterator end() const noexcept {
[[nodiscard]] const_iterator end() const ENTT_NOEXCEPT {
return cend();
}
/*! @copydoc end */
[[nodiscard]] iterator end() noexcept {
[[nodiscard]] iterator end() ENTT_NOEXCEPT {
return packed.first().end();
}
@@ -451,7 +458,7 @@ public:
* @brief Checks whether a container is empty.
* @return True if the container is empty, false otherwise.
*/
[[nodiscard]] bool empty() const noexcept {
[[nodiscard]] bool empty() const ENTT_NOEXCEPT {
return packed.first().empty();
}
@@ -459,20 +466,12 @@ public:
* @brief Returns the number of elements in a container.
* @return Number of elements in a container.
*/
[[nodiscard]] size_type size() const noexcept {
[[nodiscard]] size_type size() const ENTT_NOEXCEPT {
return packed.first().size();
}
/**
* @brief Returns the maximum possible number of elements.
* @return Maximum possible number of elements.
*/
[[nodiscard]] size_type max_size() const noexcept {
return packed.first().max_size();
}
/*! @brief Clears the container. */
void clear() noexcept {
void clear() ENTT_NOEXCEPT {
sparse.first().clear();
packed.first().clear();
rehash(0u);
@@ -522,7 +521,7 @@ public:
*/
template<typename... Args>
std::pair<iterator, bool> emplace(Args &&...args) {
if constexpr(((sizeof...(Args) == 1u) && ... && std::is_same_v<std::decay_t<Args>, value_type>)) {
if constexpr(((sizeof...(Args) == 1u) && ... && std::is_same_v<std::remove_cv_t<std::remove_reference_t<Args>>, value_type>)) {
return insert_or_do_nothing(std::forward<Args>(args)...);
} else {
auto &node = packed.first().emplace_back(std::piecewise_construct, std::make_tuple(packed.first().size()), std::forward_as_tuple(std::forward<Args>(args)...));
@@ -596,27 +595,6 @@ public:
swap(threshold, other.threshold);
}
/**
* @brief Returns the number of elements matching a value (either 1 or 0).
* @param key Key value of an element to search for.
* @return Number of elements matching the key (either 1 or 0).
*/
[[nodiscard]] size_type count(const value_type &key) const {
return find(key) != end();
}
/**
* @brief Returns the number of elements matching a key (either 1 or 0).
* @tparam Other Type of the key value of an element to search for.
* @param key Key value of an element to search for.
* @return Number of elements matching the key (either 1 or 0).
*/
template<typename Other>
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, size_type>>
count(const Other &key) const {
return find(key) != end();
}
/**
* @brief Finds an element with a given value.
* @param value Value of an element to search for.
@@ -652,46 +630,6 @@ public:
return constrained_find(value, value_to_bucket(value));
}
/**
* @brief Returns a range containing all elements with a given value.
* @param value Value of an element to search for.
* @return A pair of iterators pointing to the first element and past the
* last element of the range.
*/
[[nodiscard]] std::pair<iterator, iterator> equal_range(const value_type &value) {
const auto it = find(value);
return {it, it + !(it == end())};
}
/*! @copydoc equal_range */
[[nodiscard]] std::pair<const_iterator, const_iterator> equal_range(const value_type &value) const {
const auto it = find(value);
return {it, it + !(it == cend())};
}
/**
* @brief Returns a range containing all elements that compare _equivalent_
* to a given value.
* @tparam Other Type of an element to search for.
* @param value Value of an element to search for.
* @return A pair of iterators pointing to the first element and past the
* last element of the range.
*/
template<typename Other>
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, std::pair<iterator, iterator>>>
equal_range(const Other &value) {
const auto it = find(value);
return {it, it + !(it == end())};
}
/*! @copydoc equal_range */
template<typename Other>
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, std::pair<const_iterator, const_iterator>>>
equal_range(const Other &value) const {
const auto it = find(value);
return {it, it + !(it == cend())};
}
/**
* @brief Checks if the container contains an element with a given value.
* @param value Value of an element to search for.
@@ -755,7 +693,7 @@ public:
* @param index An index of a bucket to access.
* @return An iterator to the end of the given bucket.
*/
[[nodiscard]] const_local_iterator end(const size_type index) const {
[[nodiscard]] const_local_iterator end([[maybe_unused]] const size_type index) const {
return cend(index);
}
@@ -831,19 +769,15 @@ public:
/**
* @brief Reserves at least the specified number of buckets and regenerates
* the hash table.
* @param cnt New number of buckets.
* @param count New number of buckets.
*/
void rehash(const size_type cnt) {
auto value = cnt > minimum_capacity ? cnt : minimum_capacity;
const auto cap = static_cast<size_type>(size() / max_load_factor());
value = value > cap ? value : cap;
void rehash(const size_type count) {
auto value = (std::max)(count, minimum_capacity);
value = (std::max)(value, static_cast<size_type>(size() / max_load_factor()));
if(const auto sz = next_power_of_two(value); sz != bucket_count()) {
sparse.first().resize(sz);
for(auto &&elem: sparse.first()) {
elem = std::numeric_limits<size_type>::max();
}
std::fill(sparse.first().begin(), sparse.first().end(), (std::numeric_limits<size_type>::max)());
for(size_type pos{}, last = size(); pos < last; ++pos) {
const auto index = value_to_bucket(packed.first()[pos].second);
@@ -855,11 +789,11 @@ public:
/**
* @brief Reserves space for at least the specified number of elements and
* regenerates the hash table.
* @param cnt New number of elements.
* @param count New number of elements.
*/
void reserve(const size_type cnt) {
packed.first().reserve(cnt);
rehash(static_cast<size_type>(std::ceil(cnt / max_load_factor())));
void reserve(const size_type count) {
packed.first().reserve(count);
rehash(static_cast<size_type>(std::ceil(count / max_load_factor())));
}
/**

View File

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

@@ -13,36 +13,6 @@
namespace entt {
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal {
enum class any_operation : std::uint8_t {
copy,
move,
transfer,
assign,
destroy,
compare,
get
};
enum class any_policy : std::uint8_t {
owner,
ref,
cref
};
} // namespace internal
/**
* Internal details not to be documented.
* @endcond
*/
/**
* @brief A SBO friendly, type-safe container for single values of any type.
* @tparam Len Size of the storage reserved for the small buffer optimization.
@@ -50,19 +20,30 @@ enum class any_policy : std::uint8_t {
*/
template<std::size_t Len, std::size_t Align>
class basic_any {
using operation = internal::any_operation;
using policy = internal::any_policy;
using vtable_type = const void *(const operation, const basic_any &, const void *);
struct storage_type {
alignas(Align) std::byte data[Len + !Len];
enum class operation : std::uint8_t {
copy,
move,
transfer,
assign,
destroy,
compare,
get
};
template<typename Type>
static constexpr bool in_situ = Len && alignof(Type) <= Align && sizeof(Type) <= Len && std::is_nothrow_move_constructible_v<Type>;
enum class policy : std::uint8_t {
owner,
ref,
cref
};
using storage_type = std::aligned_storage_t<Len + !Len, Align>;
using vtable_type = const void *(const operation, const basic_any &, const void *);
template<typename Type>
static const void *basic_vtable(const operation op, const basic_any &value, const void *other) {
static constexpr bool in_situ = Len && alignof(Type) <= alignof(storage_type) && sizeof(Type) <= sizeof(storage_type) && std::is_nothrow_move_constructible_v<Type>;
template<typename Type>
static const void *basic_vtable([[maybe_unused]] const operation op, [[maybe_unused]] const basic_any &value, [[maybe_unused]] const void *other) {
static_assert(!std::is_same_v<Type, void> && std::is_same_v<std::remove_cv_t<std::remove_reference_t<Type>>, Type>, "Invalid type");
const Type *element = nullptr;
@@ -109,7 +90,7 @@ class basic_any {
break;
case operation::compare:
if constexpr(!std::is_function_v<Type> && !std::is_array_v<Type> && is_equality_comparable_v<Type>) {
return *element == *static_cast<const Type *>(other) ? other : nullptr;
return *static_cast<const Type *>(element) == *static_cast<const Type *>(other) ? other : nullptr;
} else {
return (element == other) ? other : nullptr;
}
@@ -122,32 +103,31 @@ class basic_any {
template<typename Type, typename... Args>
void initialize([[maybe_unused]] Args &&...args) {
info = &type_id<std::remove_cv_t<std::remove_reference_t<Type>>>();
if constexpr(!std::is_void_v<Type>) {
info = &type_id<std::remove_cv_t<std::remove_reference_t<Type>>>();
vtable = basic_vtable<std::remove_cv_t<std::remove_reference_t<Type>>>;
if constexpr(std::is_lvalue_reference_v<Type>) {
static_assert((std::is_lvalue_reference_v<Args> && ...) && (sizeof...(Args) == 1u), "Invalid arguments");
static_assert(sizeof...(Args) == 1u && (std::is_lvalue_reference_v<Args> && ...), "Invalid arguments");
mode = std::is_const_v<std::remove_reference_t<Type>> ? policy::cref : policy::ref;
instance = (std::addressof(args), ...);
} else if constexpr(in_situ<std::remove_cv_t<std::remove_reference_t<Type>>>) {
if constexpr(std::is_aggregate_v<std::remove_cv_t<std::remove_reference_t<Type>>> && (sizeof...(Args) != 0u || !std::is_default_constructible_v<std::remove_cv_t<std::remove_reference_t<Type>>>)) {
new(&storage) std::remove_cv_t<std::remove_reference_t<Type>>{std::forward<Args>(args)...};
} else if constexpr(in_situ<Type>) {
if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v<Type>) {
new(&storage) Type{std::forward<Args>(args)...};
} else {
new(&storage) std::remove_cv_t<std::remove_reference_t<Type>>(std::forward<Args>(args)...);
new(&storage) Type(std::forward<Args>(args)...);
}
} else {
if constexpr(std::is_aggregate_v<std::remove_cv_t<std::remove_reference_t<Type>>> && (sizeof...(Args) != 0u || !std::is_default_constructible_v<std::remove_cv_t<std::remove_reference_t<Type>>>)) {
instance = new std::remove_cv_t<std::remove_reference_t<Type>>{std::forward<Args>(args)...};
if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v<Type>) {
instance = new Type{std::forward<Args>(args)...};
} else {
instance = new std::remove_cv_t<std::remove_reference_t<Type>>(std::forward<Args>(args)...);
instance = new Type(std::forward<Args>(args)...);
}
}
}
}
basic_any(const basic_any &other, const policy pol) noexcept
basic_any(const basic_any &other, const policy pol) ENTT_NOEXCEPT
: instance{other.data()},
info{other.info},
vtable{other.vtable},
@@ -160,8 +140,11 @@ public:
static constexpr auto alignment = Align;
/*! @brief Default constructor. */
constexpr basic_any() noexcept
: basic_any{std::in_place_type<void>} {}
constexpr basic_any() ENTT_NOEXCEPT
: instance{},
info{&type_id<void>()},
vtable{},
mode{policy::owner} {}
/**
* @brief Constructs a wrapper by directly initializing the new object.
@@ -171,10 +154,7 @@ public:
*/
template<typename Type, typename... Args>
explicit basic_any(std::in_place_type_t<Type>, Args &&...args)
: instance{},
info{},
vtable{},
mode{policy::owner} {
: basic_any{} {
initialize<Type>(std::forward<Args>(args)...);
}
@@ -185,7 +165,9 @@ public:
*/
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::decay_t<Type>, basic_any>>>
basic_any(Type &&value)
: basic_any{std::in_place_type<std::decay_t<Type>>, std::forward<Type>(value)} {}
: basic_any{} {
initialize<std::decay_t<Type>>(std::forward<Type>(value));
}
/**
* @brief Copy constructor.
@@ -202,7 +184,7 @@ public:
* @brief Move constructor.
* @param other The instance to move from.
*/
basic_any(basic_any &&other) noexcept
basic_any(basic_any &&other) ENTT_NOEXCEPT
: instance{},
info{other.info},
vtable{other.vtable},
@@ -239,7 +221,7 @@ public:
* @param other The instance to move from.
* @return This any object.
*/
basic_any &operator=(basic_any &&other) noexcept {
basic_any &operator=(basic_any &&other) ENTT_NOEXCEPT {
reset();
if(other.vtable) {
@@ -269,7 +251,7 @@ public:
* @brief Returns the object type if any, `type_id<void>()` otherwise.
* @return The object type if any, `type_id<void>()` otherwise.
*/
[[nodiscard]] const type_info &type() const noexcept {
[[nodiscard]] const type_info &type() const ENTT_NOEXCEPT {
return *info;
}
@@ -277,7 +259,7 @@ public:
* @brief Returns an opaque pointer to the contained instance.
* @return An opaque pointer the contained instance, if any.
*/
[[nodiscard]] const void *data() const noexcept {
[[nodiscard]] const void *data() const ENTT_NOEXCEPT {
return vtable ? vtable(operation::get, *this, nullptr) : nullptr;
}
@@ -286,7 +268,7 @@ public:
* @param req Expected type.
* @return An opaque pointer the contained instance, if any.
*/
[[nodiscard]] const void *data(const type_info &req) const noexcept {
[[nodiscard]] const void *data(const type_info &req) const ENTT_NOEXCEPT {
return *info == req ? data() : nullptr;
}
@@ -294,8 +276,8 @@ public:
* @brief Returns an opaque pointer to the contained instance.
* @return An opaque pointer the contained instance, if any.
*/
[[nodiscard]] void *data() noexcept {
return mode == policy::cref ? nullptr : const_cast<void *>(std::as_const(*this).data());
[[nodiscard]] void *data() ENTT_NOEXCEPT {
return (!vtable || mode == policy::cref) ? nullptr : const_cast<void *>(vtable(operation::get, *this, nullptr));
}
/**
@@ -303,8 +285,8 @@ public:
* @param req Expected type.
* @return An opaque pointer the contained instance, if any.
*/
[[nodiscard]] void *data(const type_info &req) noexcept {
return mode == policy::cref ? nullptr : const_cast<void *>(std::as_const(*this).data(req));
[[nodiscard]] void *data(const type_info &req) ENTT_NOEXCEPT {
return *info == req ? data() : nullptr;
}
/**
@@ -324,7 +306,7 @@ public:
* @param other The value to assign to the contained object.
* @return True in case of success, false otherwise.
*/
bool assign(const basic_any &other) {
bool assign(const any &other) {
if(vtable && mode != policy::cref && *info == *other.info) {
return (vtable(operation::assign, *this, other.data()) != nullptr);
}
@@ -333,7 +315,7 @@ public:
}
/*! @copydoc assign */
bool assign(basic_any &&other) {
bool assign(any &&other) {
if(vtable && mode != policy::cref && *info == *other.info) {
if(auto *val = other.data(); val) {
return (vtable(operation::transfer, *this, val) != nullptr);
@@ -351,8 +333,6 @@ public:
vtable(operation::destroy, *this, nullptr);
}
// unnecessary but it helps to detect nasty bugs
ENTT_ASSERT((instance = nullptr) == nullptr, "");
info = &type_id<void>();
vtable = nullptr;
mode = policy::owner;
@@ -362,7 +342,7 @@ public:
* @brief Returns false if a wrapper is empty, true otherwise.
* @return False if the wrapper is empty, true otherwise.
*/
[[nodiscard]] explicit operator bool() const noexcept {
[[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT {
return vtable != nullptr;
}
@@ -371,7 +351,7 @@ public:
* @param other Wrapper with which to compare.
* @return False if the two objects differ in their content, true otherwise.
*/
[[nodiscard]] bool operator==(const basic_any &other) const noexcept {
bool operator==(const basic_any &other) const ENTT_NOEXCEPT {
if(vtable && *info == *other.info) {
return (vtable(operation::compare, *this, other.data()) != nullptr);
}
@@ -379,25 +359,16 @@ public:
return (!vtable && !other.vtable);
}
/**
* @brief Checks if two wrappers differ in their content.
* @param other Wrapper with which to compare.
* @return True if the two objects differ in their content, false otherwise.
*/
[[nodiscard]] bool operator!=(const basic_any &other) const noexcept {
return !(*this == other);
}
/**
* @brief Aliasing constructor.
* @return A wrapper that shares a reference to an unmanaged object.
*/
[[nodiscard]] basic_any as_ref() noexcept {
[[nodiscard]] basic_any as_ref() ENTT_NOEXCEPT {
return basic_any{*this, (mode == policy::cref ? policy::cref : policy::ref)};
}
/*! @copydoc as_ref */
[[nodiscard]] basic_any as_ref() const noexcept {
[[nodiscard]] basic_any as_ref() const ENTT_NOEXCEPT {
return basic_any{*this, policy::cref};
}
@@ -405,7 +376,7 @@ public:
* @brief Returns true if a wrapper owns its object, false otherwise.
* @return True if the wrapper owns its object, false otherwise.
*/
[[nodiscard]] bool owner() const noexcept {
[[nodiscard]] bool owner() const ENTT_NOEXCEPT {
return (mode == policy::owner);
}
@@ -419,6 +390,19 @@ private:
policy mode;
};
/**
* @brief Checks if two wrappers differ in their content.
* @tparam Len Size of the storage reserved for the small buffer optimization.
* @tparam Align Alignment requirement.
* @param lhs A wrapper, either empty or not.
* @param rhs A wrapper, either empty or not.
* @return True if the two wrappers differ in their content, false otherwise.
*/
template<std::size_t Len, std::size_t Align>
[[nodiscard]] inline bool operator!=(const basic_any<Len, Align> &lhs, const basic_any<Len, Align> &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}
/**
* @brief Performs type-safe access to the contained object.
* @tparam Type Type to which conversion is required.
@@ -428,7 +412,7 @@ private:
* @return The element converted to the requested type.
*/
template<typename Type, std::size_t Len, std::size_t Align>
[[nodiscard]] Type any_cast(const basic_any<Len, Align> &data) noexcept {
Type any_cast(const basic_any<Len, Align> &data) ENTT_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 +420,7 @@ template<typename Type, std::size_t Len, std::size_t Align>
/*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align>
[[nodiscard]] Type any_cast(basic_any<Len, Align> &data) noexcept {
Type any_cast(basic_any<Len, Align> &data) ENTT_NOEXCEPT {
// 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 +429,7 @@ template<typename Type, std::size_t Len, std::size_t Align>
/*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align>
[[nodiscard]] Type any_cast(basic_any<Len, Align> &&data) noexcept {
Type any_cast(basic_any<Len, Align> &&data) ENTT_NOEXCEPT {
if constexpr(std::is_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,21 +445,17 @@ template<typename Type, std::size_t Len, std::size_t Align>
/*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align>
[[nodiscard]] const Type *any_cast(const basic_any<Len, Align> *data) noexcept {
const auto &info = type_id<std::remove_cv_t<Type>>();
const Type *any_cast(const basic_any<Len, Align> *data) ENTT_NOEXCEPT {
const auto &info = type_id<std::remove_cv_t<std::remove_reference_t<Type>>>();
return static_cast<const Type *>(data->data(info));
}
/*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align>
[[nodiscard]] Type *any_cast(basic_any<Len, Align> *data) noexcept {
if constexpr(std::is_const_v<Type>) {
// last attempt to make wrappers for const references return their values
return any_cast<Type>(&std::as_const(*data));
} else {
const auto &info = type_id<std::remove_cv_t<Type>>();
return static_cast<Type *>(data->data(info));
}
Type *any_cast(basic_any<Len, Align> *data) ENTT_NOEXCEPT {
const auto &info = type_id<std::remove_cv_t<std::remove_reference_t<Type>>>();
// last attempt to make wrappers for const references return their values
return static_cast<Type *>(static_cast<constness_as_t<basic_any<Len, Align>, Type> *>(data)->data(info));
}
/**
@@ -488,7 +468,7 @@ template<typename Type, std::size_t Len, std::size_t Align>
* @return A properly initialized wrapper for an object of the given type.
*/
template<typename Type, std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename... Args>
[[nodiscard]] basic_any<Len, Align> make_any(Args &&...args) {
basic_any<Len, Align> make_any(Args &&...args) {
return basic_any<Len, Align>{std::in_place_type<Type>, std::forward<Args>(args)...};
}
@@ -501,8 +481,8 @@ template<typename Type, std::size_t Len = basic_any<>::length, std::size_t Align
* @return A properly initialized and not necessarily owning wrapper.
*/
template<std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename Type>
[[nodiscard]] basic_any<Len, Align> forward_as_any(Type &&value) {
return basic_any<Len, Align>{std::in_place_type<Type &&>, std::forward<Type>(value)};
basic_any<Len, Align> forward_as_any(Type &&value) {
return basic_any<Len, Align>{std::in_place_type<std::conditional_t<std::is_rvalue_reference_v<Type>, std::decay_t<Type>, Type>>, std::forward<Type>(value)};
}
} // namespace entt

View File

@@ -5,6 +5,7 @@
#include <tuple>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "type_traits.hpp"
namespace entt {
@@ -22,22 +23,22 @@ struct compressed_pair_element {
using const_reference = const Type &;
template<bool Dummy = true, typename = std::enable_if_t<Dummy && std::is_default_constructible_v<Type>>>
constexpr compressed_pair_element() noexcept(std::is_nothrow_default_constructible_v<Type>)
compressed_pair_element()
: value{} {}
template<typename Arg, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Arg>>, compressed_pair_element>>>
constexpr compressed_pair_element(Arg &&arg) noexcept(std::is_nothrow_constructible_v<Type, Arg>)
: value{std::forward<Arg>(arg)} {}
template<typename Args, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Args>>, compressed_pair_element>>>
compressed_pair_element(Args &&args)
: value{std::forward<Args>(args)} {}
template<typename... Args, std::size_t... Index>
constexpr compressed_pair_element(std::tuple<Args...> args, std::index_sequence<Index...>) noexcept(std::is_nothrow_constructible_v<Type, Args...>)
compressed_pair_element(std::tuple<Args...> args, std::index_sequence<Index...>)
: value{std::forward<Args>(std::get<Index>(args))...} {}
[[nodiscard]] constexpr reference get() noexcept {
[[nodiscard]] reference get() ENTT_NOEXCEPT {
return value;
}
[[nodiscard]] constexpr const_reference get() const noexcept {
[[nodiscard]] const_reference get() const ENTT_NOEXCEPT {
return value;
}
@@ -52,22 +53,22 @@ struct compressed_pair_element<Type, Tag, std::enable_if_t<is_ebco_eligible_v<Ty
using base_type = Type;
template<bool Dummy = true, typename = std::enable_if_t<Dummy && std::is_default_constructible_v<base_type>>>
constexpr compressed_pair_element() noexcept(std::is_nothrow_default_constructible_v<base_type>)
compressed_pair_element()
: base_type{} {}
template<typename Arg, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Arg>>, compressed_pair_element>>>
constexpr compressed_pair_element(Arg &&arg) noexcept(std::is_nothrow_constructible_v<base_type, Arg>)
: base_type{std::forward<Arg>(arg)} {}
template<typename Args, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Args>>, compressed_pair_element>>>
compressed_pair_element(Args &&args)
: base_type{std::forward<Args>(args)} {}
template<typename... Args, std::size_t... Index>
constexpr compressed_pair_element(std::tuple<Args...> args, std::index_sequence<Index...>) noexcept(std::is_nothrow_constructible_v<base_type, Args...>)
compressed_pair_element(std::tuple<Args...> args, std::index_sequence<Index...>)
: base_type{std::forward<Args>(std::get<Index>(args))...} {}
[[nodiscard]] constexpr reference get() noexcept {
[[nodiscard]] reference get() ENTT_NOEXCEPT {
return *this;
}
[[nodiscard]] constexpr const_reference get() const noexcept {
[[nodiscard]] const_reference get() const ENTT_NOEXCEPT {
return *this;
}
};
@@ -110,7 +111,7 @@ public:
* @tparam Dummy Dummy template parameter used for internal purposes.
*/
template<bool Dummy = true, typename = std::enable_if_t<Dummy && std::is_default_constructible_v<first_type> && std::is_default_constructible_v<second_type>>>
constexpr compressed_pair() noexcept(std::is_nothrow_default_constructible_v<first_base> &&std::is_nothrow_default_constructible_v<second_base>)
constexpr compressed_pair()
: first_base{},
second_base{} {}
@@ -118,13 +119,13 @@ public:
* @brief Copy constructor.
* @param other The instance to copy from.
*/
constexpr compressed_pair(const compressed_pair &other) noexcept(std::is_nothrow_copy_constructible_v<first_base> &&std::is_nothrow_copy_constructible_v<second_base>) = default;
constexpr compressed_pair(const compressed_pair &other) = default;
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
constexpr compressed_pair(compressed_pair &&other) noexcept(std::is_nothrow_move_constructible_v<first_base> &&std::is_nothrow_move_constructible_v<second_base>) = default;
constexpr compressed_pair(compressed_pair &&other) = default;
/**
* @brief Constructs a pair from its values.
@@ -134,7 +135,7 @@ public:
* @param other Value to use to initialize the second element.
*/
template<typename Arg, typename Other>
constexpr compressed_pair(Arg &&arg, Other &&other) noexcept(std::is_nothrow_constructible_v<first_base, Arg> &&std::is_nothrow_constructible_v<second_base, Other>)
constexpr compressed_pair(Arg &&arg, Other &&other)
: first_base{std::forward<Arg>(arg)},
second_base{std::forward<Other>(other)} {}
@@ -146,7 +147,7 @@ public:
* @param other Arguments to use to initialize the second element.
*/
template<typename... Args, typename... Other>
constexpr compressed_pair(std::piecewise_construct_t, std::tuple<Args...> args, std::tuple<Other...> other) noexcept(std::is_nothrow_constructible_v<first_base, Args...> &&std::is_nothrow_constructible_v<second_base, Other...>)
constexpr compressed_pair(std::piecewise_construct_t, std::tuple<Args...> args, std::tuple<Other...> other)
: first_base{std::move(args), std::index_sequence_for<Args...>{}},
second_base{std::move(other), std::index_sequence_for<Other...>{}} {}
@@ -155,25 +156,25 @@ public:
* @param other The instance to copy from.
* @return This compressed pair object.
*/
constexpr compressed_pair &operator=(const compressed_pair &other) noexcept(std::is_nothrow_copy_assignable_v<first_base> &&std::is_nothrow_copy_assignable_v<second_base>) = default;
constexpr compressed_pair &operator=(const compressed_pair &other) = default;
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This compressed pair object.
*/
constexpr compressed_pair &operator=(compressed_pair &&other) noexcept(std::is_nothrow_move_assignable_v<first_base> &&std::is_nothrow_move_assignable_v<second_base>) = default;
constexpr compressed_pair &operator=(compressed_pair &&other) = default;
/**
* @brief Returns the first element that a pair stores.
* @return The first element that a pair stores.
*/
[[nodiscard]] constexpr first_type &first() noexcept {
[[nodiscard]] first_type &first() ENTT_NOEXCEPT {
return static_cast<first_base &>(*this).get();
}
/*! @copydoc first */
[[nodiscard]] constexpr const first_type &first() const noexcept {
[[nodiscard]] const first_type &first() const ENTT_NOEXCEPT {
return static_cast<const first_base &>(*this).get();
}
@@ -181,12 +182,12 @@ public:
* @brief Returns the second element that a pair stores.
* @return The second element that a pair stores.
*/
[[nodiscard]] constexpr second_type &second() noexcept {
[[nodiscard]] second_type &second() ENTT_NOEXCEPT {
return static_cast<second_base &>(*this).get();
}
/*! @copydoc second */
[[nodiscard]] constexpr const second_type &second() const noexcept {
[[nodiscard]] const second_type &second() const ENTT_NOEXCEPT {
return static_cast<const second_base &>(*this).get();
}
@@ -194,7 +195,7 @@ public:
* @brief Swaps two compressed pair objects.
* @param other The compressed pair to swap with.
*/
constexpr void swap(compressed_pair &other) noexcept(std::is_nothrow_swappable_v<first_type> &&std::is_nothrow_swappable_v<second_type>) {
void swap(compressed_pair &other) {
using std::swap;
swap(first(), other.first());
swap(second(), other.second());
@@ -207,7 +208,7 @@ public:
* reference to the second element if `Index` is 1.
*/
template<std::size_t Index>
constexpr decltype(auto) get() noexcept {
decltype(auto) get() ENTT_NOEXCEPT {
if constexpr(Index == 0u) {
return first();
} else {
@@ -218,7 +219,7 @@ public:
/*! @copydoc get */
template<std::size_t Index>
constexpr decltype(auto) get() const noexcept {
decltype(auto) get() const ENTT_NOEXCEPT {
if constexpr(Index == 0u) {
return first();
} else {
@@ -244,7 +245,7 @@ compressed_pair(Type &&, Other &&) -> compressed_pair<std::decay_t<Type>, std::d
* @param rhs A valid compressed pair object.
*/
template<typename First, typename Second>
inline constexpr void swap(compressed_pair<First, Second> &lhs, compressed_pair<First, Second> &rhs) {
inline void swap(compressed_pair<First, Second> &lhs, compressed_pair<First, Second> &rhs) {
lhs.swap(rhs);
}

View File

@@ -2,6 +2,7 @@
#define ENTT_CORE_ENUM_HPP
#include <type_traits>
#include "../config/config.h"
namespace entt {
@@ -35,21 +36,21 @@ inline constexpr bool enum_as_bitmask_v = enum_as_bitmask<Type>::value;
*/
template<typename Type>
[[nodiscard]] constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type>
operator|(const Type lhs, const Type rhs) noexcept {
operator|(const Type lhs, const Type rhs) ENTT_NOEXCEPT {
return static_cast<Type>(static_cast<std::underlying_type_t<Type>>(lhs) | static_cast<std::underlying_type_t<Type>>(rhs));
}
/*! @copydoc operator| */
template<typename Type>
[[nodiscard]] constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type>
operator&(const Type lhs, const Type rhs) noexcept {
operator&(const Type lhs, const Type rhs) ENTT_NOEXCEPT {
return static_cast<Type>(static_cast<std::underlying_type_t<Type>>(lhs) & static_cast<std::underlying_type_t<Type>>(rhs));
}
/*! @copydoc operator| */
template<typename Type>
[[nodiscard]] constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type>
operator^(const Type lhs, const Type rhs) noexcept {
operator^(const Type lhs, const Type rhs) ENTT_NOEXCEPT {
return static_cast<Type>(static_cast<std::underlying_type_t<Type>>(lhs) ^ static_cast<std::underlying_type_t<Type>>(rhs));
}
@@ -62,35 +63,35 @@ operator^(const Type lhs, const Type rhs) noexcept {
*/
template<typename Type>
[[nodiscard]] constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type>
operator~(const Type value) noexcept {
operator~(const Type value) ENTT_NOEXCEPT {
return static_cast<Type>(~static_cast<std::underlying_type_t<Type>>(value));
}
/*! @copydoc operator~ */
template<typename Type>
[[nodiscard]] constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, bool>
operator!(const Type value) noexcept {
operator!(const Type value) ENTT_NOEXCEPT {
return !static_cast<std::underlying_type_t<Type>>(value);
}
/*! @copydoc operator| */
template<typename Type>
constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type &>
operator|=(Type &lhs, const Type rhs) noexcept {
operator|=(Type &lhs, const Type rhs) ENTT_NOEXCEPT {
return (lhs = (lhs | rhs));
}
/*! @copydoc operator| */
template<typename Type>
constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type &>
operator&=(Type &lhs, const Type rhs) noexcept {
operator&=(Type &lhs, const Type rhs) ENTT_NOEXCEPT {
return (lhs = (lhs & rhs));
}
/*! @copydoc operator| */
template<typename Type>
constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type &>
operator^=(Type &lhs, const Type rhs) noexcept {
operator^=(Type &lhs, const Type rhs) ENTT_NOEXCEPT {
return (lhs = (lhs ^ rhs));
}

View File

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

View File

@@ -1,12 +1,13 @@
#ifndef ENTT_CORE_FWD_HPP
#define ENTT_CORE_FWD_HPP
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include "../config/config.h"
namespace entt {
template<std::size_t Len = sizeof(double[2]), std::size_t = alignof(double[2])>
template<std::size_t Len = sizeof(double[2]), std::size_t = alignof(typename std::aligned_storage_t<Len + !Len>)>
class basic_any;
/*! @brief Alias declaration for type identifiers. */

View File

@@ -3,6 +3,7 @@
#include <cstddef>
#include <cstdint>
#include "../config/config.h"
#include "fwd.hpp"
namespace entt {
@@ -67,33 +68,31 @@ 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 traits_type = internal::fnv1a_traits<id_type>;
using hs_traits = internal::fnv1a_traits<id_type>;
struct const_wrapper {
// non-explicit constructor on purpose
constexpr const_wrapper(const Char *str) noexcept
: repr{str} {}
constexpr const_wrapper(const Char *str) ENTT_NOEXCEPT: repr{str} {}
const Char *repr;
};
// FowlerNollVo hash function v. 1a - the good
[[nodiscard]] static constexpr auto helper(const Char *str) noexcept {
base_type base{str, 0u, traits_type::offset};
[[nodiscard]] static constexpr auto helper(const Char *str) ENTT_NOEXCEPT {
base_type base{str, 0u, hs_traits::offset};
for(; str[base.length]; ++base.length) {
base.hash = (base.hash ^ static_cast<traits_type::type>(str[base.length])) * traits_type::prime;
base.hash = (base.hash ^ static_cast<hs_traits::type>(str[base.length])) * hs_traits::prime;
}
return base;
}
// 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, traits_type::offset};
[[nodiscard]] static constexpr auto helper(const Char *str, const std::size_t len) ENTT_NOEXCEPT {
base_type base{str, len, hs_traits::offset};
for(size_type pos{}; pos < len; ++pos) {
base.hash = (base.hash ^ static_cast<traits_type::type>(str[pos])) * traits_type::prime;
base.hash = (base.hash ^ static_cast<hs_traits::type>(str[pos])) * hs_traits::prime;
}
return base;
@@ -113,7 +112,7 @@ public:
* @param len Length of the string to hash.
* @return The numeric representation of the string.
*/
[[nodiscard]] static constexpr hash_type value(const value_type *str, const size_type len) noexcept {
[[nodiscard]] static constexpr hash_type value(const value_type *str, const size_type len) ENTT_NOEXCEPT {
return basic_hashed_string{str, len};
}
@@ -124,7 +123,7 @@ public:
* @return The numeric representation of the string.
*/
template<std::size_t N>
[[nodiscard]] static constexpr hash_type value(const value_type (&str)[N]) noexcept {
[[nodiscard]] static constexpr hash_type value(const value_type (&str)[N]) ENTT_NOEXCEPT {
return basic_hashed_string{str};
}
@@ -133,12 +132,12 @@ public:
* @param wrapper Helps achieving the purpose by relying on overloading.
* @return The numeric representation of the string.
*/
[[nodiscard]] static constexpr hash_type value(const_wrapper wrapper) noexcept {
[[nodiscard]] static constexpr hash_type value(const_wrapper wrapper) ENTT_NOEXCEPT {
return basic_hashed_string{wrapper};
}
/*! @brief Constructs an empty hashed string. */
constexpr basic_hashed_string() noexcept
constexpr basic_hashed_string() ENTT_NOEXCEPT
: base_type{} {}
/**
@@ -146,7 +145,7 @@ public:
* @param str Human-readable identifier.
* @param len Length of the string to hash.
*/
constexpr basic_hashed_string(const value_type *str, const size_type len) noexcept
constexpr basic_hashed_string(const value_type *str, const size_type len) ENTT_NOEXCEPT
: base_type{helper(str, len)} {}
/**
@@ -155,7 +154,7 @@ public:
* @param str Human-readable identifier.
*/
template<std::size_t N>
constexpr basic_hashed_string(const value_type (&str)[N]) noexcept
constexpr basic_hashed_string(const value_type (&str)[N]) ENTT_NOEXCEPT
: base_type{helper(str)} {}
/**
@@ -167,14 +166,14 @@ public:
*
* @param wrapper Helps achieving the purpose by relying on overloading.
*/
explicit constexpr basic_hashed_string(const_wrapper wrapper) noexcept
explicit constexpr basic_hashed_string(const_wrapper wrapper) ENTT_NOEXCEPT
: base_type{helper(wrapper.repr)} {}
/**
* @brief Returns the size a hashed string.
* @return The size of the hashed string.
*/
[[nodiscard]] constexpr size_type size() const noexcept {
[[nodiscard]] constexpr size_type size() const ENTT_NOEXCEPT {
return base_type::length;
}
@@ -182,7 +181,7 @@ public:
* @brief Returns the human-readable representation of a hashed string.
* @return The string used to initialize the hashed string.
*/
[[nodiscard]] constexpr const value_type *data() const noexcept {
[[nodiscard]] constexpr const value_type *data() const ENTT_NOEXCEPT {
return base_type::repr;
}
@@ -190,12 +189,12 @@ public:
* @brief Returns the numeric representation of a hashed string.
* @return The numeric representation of the hashed string.
*/
[[nodiscard]] constexpr hash_type value() const noexcept {
[[nodiscard]] constexpr hash_type value() const ENTT_NOEXCEPT {
return base_type::hash;
}
/*! @copydoc data */
[[nodiscard]] constexpr operator const value_type *() const noexcept {
[[nodiscard]] constexpr operator const value_type *() const ENTT_NOEXCEPT {
return data();
}
@@ -203,7 +202,7 @@ public:
* @brief Returns the numeric representation of a hashed string.
* @return The numeric representation of the hashed string.
*/
[[nodiscard]] constexpr operator hash_type() const noexcept {
[[nodiscard]] constexpr operator hash_type() const ENTT_NOEXCEPT {
return value();
}
};
@@ -234,7 +233,7 @@ basic_hashed_string(const Char (&str)[N]) -> basic_hashed_string<Char>;
* @return True if the two hashed strings are identical, false otherwise.
*/
template<typename Char>
[[nodiscard]] constexpr bool operator==(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) noexcept {
[[nodiscard]] constexpr bool operator==(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) ENTT_NOEXCEPT {
return lhs.value() == rhs.value();
}
@@ -246,7 +245,7 @@ template<typename Char>
* @return True if the two hashed strings differ, false otherwise.
*/
template<typename Char>
[[nodiscard]] constexpr bool operator!=(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) noexcept {
[[nodiscard]] constexpr bool operator!=(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}
@@ -258,7 +257,7 @@ template<typename Char>
* @return True if the first element is less than the second, false otherwise.
*/
template<typename Char>
[[nodiscard]] constexpr bool operator<(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) noexcept {
[[nodiscard]] constexpr bool operator<(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) ENTT_NOEXCEPT {
return lhs.value() < rhs.value();
}
@@ -271,7 +270,7 @@ template<typename Char>
* otherwise.
*/
template<typename Char>
[[nodiscard]] constexpr bool operator<=(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) noexcept {
[[nodiscard]] constexpr bool operator<=(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) ENTT_NOEXCEPT {
return !(rhs < lhs);
}
@@ -284,7 +283,7 @@ template<typename Char>
* otherwise.
*/
template<typename Char>
[[nodiscard]] constexpr bool operator>(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) noexcept {
[[nodiscard]] constexpr bool operator>(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) ENTT_NOEXCEPT {
return rhs < lhs;
}
@@ -297,7 +296,7 @@ template<typename Char>
* false otherwise.
*/
template<typename Char>
[[nodiscard]] constexpr bool operator>=(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) noexcept {
[[nodiscard]] constexpr bool operator>=(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) ENTT_NOEXCEPT {
return !(lhs < rhs);
}
@@ -314,7 +313,7 @@ inline namespace literals {
* @param str The literal without its suffix.
* @return A properly initialized hashed string.
*/
[[nodiscard]] constexpr hashed_string operator"" _hs(const char *str, std::size_t) noexcept {
[[nodiscard]] constexpr hashed_string operator"" _hs(const char *str, std::size_t) ENTT_NOEXCEPT {
return hashed_string{str};
}
@@ -323,7 +322,7 @@ inline namespace literals {
* @param str The literal without its suffix.
* @return A properly initialized hashed wstring.
*/
[[nodiscard]] constexpr hashed_wstring operator"" _hws(const wchar_t *str, std::size_t) noexcept {
[[nodiscard]] constexpr hashed_wstring operator"" _hws(const wchar_t *str, std::size_t) ENTT_NOEXCEPT {
return hashed_wstring{str};
}

View File

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

View File

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

View File

@@ -11,12 +11,69 @@
namespace entt {
/**
* @brief Unwraps fancy pointers, does nothing otherwise (waiting for C++20).
* @tparam Type Pointer type.
* @param ptr Fancy or raw pointer.
* @return A raw pointer that represents the address of the original pointer.
*/
template<typename Type>
[[nodiscard]] constexpr auto to_address(Type &&ptr) ENTT_NOEXCEPT {
if constexpr(std::is_pointer_v<std::remove_cv_t<std::remove_reference_t<Type>>>) {
return ptr;
} else {
return to_address(std::forward<Type>(ptr).operator->());
}
}
/**
* @brief Utility function to design allocation-aware containers.
* @tparam Allocator Type of allocator.
* @param lhs A valid allocator.
* @param rhs Another valid allocator.
*/
template<typename Allocator>
constexpr void propagate_on_container_copy_assignment([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) ENTT_NOEXCEPT {
if constexpr(std::allocator_traits<Allocator>::propagate_on_container_copy_assignment::value) {
lhs = rhs;
}
}
/**
* @brief Utility function to design allocation-aware containers.
* @tparam Allocator Type of allocator.
* @param lhs A valid allocator.
* @param rhs Another valid allocator.
*/
template<typename Allocator>
constexpr void propagate_on_container_move_assignment([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) ENTT_NOEXCEPT {
if constexpr(std::allocator_traits<Allocator>::propagate_on_container_move_assignment::value) {
lhs = std::move(rhs);
}
}
/**
* @brief Utility function to design allocation-aware containers.
* @tparam Allocator Type of allocator.
* @param lhs A valid allocator.
* @param rhs Another valid allocator.
*/
template<typename Allocator>
constexpr void propagate_on_container_swap([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) ENTT_NOEXCEPT {
ENTT_ASSERT(std::allocator_traits<Allocator>::propagate_on_container_swap::value || lhs == rhs, "Cannot swap the containers");
if constexpr(std::allocator_traits<Allocator>::propagate_on_container_swap::value) {
using std::swap;
swap(lhs, rhs);
}
}
/**
* @brief Checks whether a value is a power of two or not.
* @param value A value that may or may not be a power of two.
* @return True if the value is a power of two, false otherwise.
*/
[[nodiscard]] inline constexpr bool is_power_of_two(const std::size_t value) noexcept {
[[nodiscard]] inline constexpr bool is_power_of_two(const std::size_t value) ENTT_NOEXCEPT {
return value && ((value & (value - 1)) == 0);
}
@@ -25,8 +82,8 @@ namespace entt {
* @param value The value to use.
* @return The smallest power of two greater than or equal to the given value.
*/
[[nodiscard]] inline constexpr std::size_t next_power_of_two(const std::size_t value) noexcept {
ENTT_ASSERT_CONSTEXPR(value < (std::size_t{1u} << (std::numeric_limits<std::size_t>::digits - 1)), "Numeric limits exceeded");
[[nodiscard]] inline constexpr std::size_t next_power_of_two(const std::size_t value) ENTT_NOEXCEPT {
ENTT_ASSERT(value < (std::size_t{1u} << (std::numeric_limits<std::size_t>::digits - 1)), "Numeric limits exceeded");
std::size_t curr = value - (value != 0u);
for(int next = 1; next < std::numeric_limits<std::size_t>::digits; next = next * 2) {
@@ -42,71 +99,14 @@ namespace entt {
* @param mod _Modulus_, it must be a power of two.
* @return The common remainder.
*/
[[nodiscard]] inline constexpr std::size_t fast_mod(const std::size_t value, const std::size_t mod) noexcept {
ENTT_ASSERT_CONSTEXPR(is_power_of_two(mod), "Value must be a power of two");
[[nodiscard]] inline constexpr std::size_t fast_mod(const std::size_t value, const std::size_t mod) ENTT_NOEXCEPT {
ENTT_ASSERT(is_power_of_two(mod), "Value must be a power of two");
return value & (mod - 1u);
}
/**
* @brief Unwraps fancy pointers, does nothing otherwise (waiting for C++20).
* @tparam Type Pointer type.
* @param ptr Fancy or raw pointer.
* @return A raw pointer that represents the address of the original pointer.
*/
template<typename Type>
[[nodiscard]] constexpr auto to_address(Type &&ptr) noexcept {
if constexpr(std::is_pointer_v<std::decay_t<Type>>) {
return ptr;
} else {
return to_address(std::forward<Type>(ptr).operator->());
}
}
/**
* @brief Utility function to design allocation-aware containers.
* @tparam Allocator Type of allocator.
* @param lhs A valid allocator.
* @param rhs Another valid allocator.
*/
template<typename Allocator>
constexpr void propagate_on_container_copy_assignment([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) noexcept {
if constexpr(std::allocator_traits<Allocator>::propagate_on_container_copy_assignment::value) {
lhs = rhs;
}
}
/**
* @brief Utility function to design allocation-aware containers.
* @tparam Allocator Type of allocator.
* @param lhs A valid allocator.
* @param rhs Another valid allocator.
*/
template<typename Allocator>
constexpr void propagate_on_container_move_assignment([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) noexcept {
if constexpr(std::allocator_traits<Allocator>::propagate_on_container_move_assignment::value) {
lhs = std::move(rhs);
}
}
/**
* @brief Utility function to design allocation-aware containers.
* @tparam Allocator Type of allocator.
* @param lhs A valid allocator.
* @param rhs Another valid allocator.
*/
template<typename Allocator>
constexpr void propagate_on_container_swap([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) noexcept {
if constexpr(std::allocator_traits<Allocator>::propagate_on_container_swap::value) {
using std::swap;
swap(lhs, rhs);
} else {
ENTT_ASSERT_CONSTEXPR(lhs == rhs, "Cannot swap the containers");
}
}
/**
* @brief Deleter for allocator-aware unique pointers (waiting for C++20).
* @tparam Allocator Type of allocator used to manage memory and elements.
* @tparam Args Types of arguments to use to construct the object.
*/
template<typename Allocator>
struct allocation_deleter: private Allocator {
@@ -119,15 +119,15 @@ struct allocation_deleter: private Allocator {
* @brief Inherited constructors.
* @param alloc The allocator to use.
*/
constexpr allocation_deleter(const allocator_type &alloc) noexcept(std::is_nothrow_copy_constructible_v<allocator_type>)
allocation_deleter(const allocator_type &alloc)
: Allocator{alloc} {}
/**
* @brief Destroys the pointed object and deallocates its memory.
* @param ptr A valid pointer to an object of the given type.
*/
constexpr void operator()(pointer ptr) noexcept(std::is_nothrow_destructible_v<typename allocator_type::value_type>) {
using alloc_traits = std::allocator_traits<Allocator>;
void operator()(pointer ptr) {
using alloc_traits = typename std::allocator_traits<Allocator>;
alloc_traits::destroy(*this, to_address(ptr));
alloc_traits::deallocate(*this, ptr, 1u);
}
@@ -143,7 +143,7 @@ struct allocation_deleter: private Allocator {
* @return A properly initialized unique pointer with a custom deleter.
*/
template<typename Type, typename Allocator, typename... Args>
ENTT_CONSTEXPR auto allocate_unique(Allocator &allocator, Args &&...args) {
auto allocate_unique(Allocator &allocator, Args &&...args) {
static_assert(!std::is_array_v<Type>, "Array types are not supported");
using alloc_traits = typename std::allocator_traits<Allocator>::template rebind_traits<Type>;
@@ -173,14 +173,14 @@ namespace internal {
template<typename Type>
struct uses_allocator_construction {
template<typename Allocator, typename... Params>
static constexpr auto args([[maybe_unused]] const Allocator &allocator, Params &&...params) noexcept {
static constexpr auto args([[maybe_unused]] const Allocator &allocator, Params &&...params) ENTT_NOEXCEPT {
if constexpr(!std::uses_allocator_v<Type, Allocator> && std::is_constructible_v<Type, Params...>) {
return std::forward_as_tuple(std::forward<Params>(params)...);
} else {
static_assert(std::uses_allocator_v<Type, Allocator>, "Ill-formed request");
if constexpr(std::is_constructible_v<Type, std::allocator_arg_t, const Allocator &, Params...>) {
return std::tuple<std::allocator_arg_t, const Allocator &, Params &&...>{std::allocator_arg, allocator, std::forward<Params>(params)...};
return std::tuple<std::allocator_arg_t, const Allocator &, Params &&...>(std::allocator_arg, allocator, std::forward<Params>(params)...);
} else {
static_assert(std::is_constructible_v<Type, Params..., const Allocator &>, "Ill-formed request");
return std::forward_as_tuple(std::forward<Params>(params)..., allocator);
@@ -194,7 +194,7 @@ struct uses_allocator_construction<std::pair<Type, Other>> {
using type = std::pair<Type, Other>;
template<typename Allocator, typename First, typename Second>
static constexpr auto args(const Allocator &allocator, std::piecewise_construct_t, First &&first, Second &&second) noexcept {
static constexpr auto args(const Allocator &allocator, std::piecewise_construct_t, First &&first, Second &&second) ENTT_NOEXCEPT {
return std::make_tuple(
std::piecewise_construct,
std::apply([&allocator](auto &&...curr) { return uses_allocator_construction<Type>::args(allocator, std::forward<decltype(curr)>(curr)...); }, std::forward<First>(first)),
@@ -202,22 +202,22 @@ struct uses_allocator_construction<std::pair<Type, Other>> {
}
template<typename Allocator>
static constexpr auto args(const Allocator &allocator) noexcept {
static constexpr auto args(const Allocator &allocator) ENTT_NOEXCEPT {
return uses_allocator_construction<type>::args(allocator, std::piecewise_construct, std::tuple<>{}, std::tuple<>{});
}
template<typename Allocator, typename First, typename Second>
static constexpr auto args(const Allocator &allocator, First &&first, Second &&second) noexcept {
static constexpr auto args(const Allocator &allocator, First &&first, Second &&second) ENTT_NOEXCEPT {
return uses_allocator_construction<type>::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::forward<First>(first)), std::forward_as_tuple(std::forward<Second>(second)));
}
template<typename Allocator, typename First, typename Second>
static constexpr auto args(const Allocator &allocator, const std::pair<First, Second> &value) noexcept {
static constexpr auto args(const Allocator &allocator, const std::pair<First, Second> &value) ENTT_NOEXCEPT {
return uses_allocator_construction<type>::args(allocator, std::piecewise_construct, std::forward_as_tuple(value.first), std::forward_as_tuple(value.second));
}
template<typename Allocator, typename First, typename Second>
static constexpr auto args(const Allocator &allocator, std::pair<First, Second> &&value) noexcept {
static constexpr auto args(const Allocator &allocator, std::pair<First, Second> &&value) ENTT_NOEXCEPT {
return uses_allocator_construction<type>::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::move(value.first)), std::forward_as_tuple(std::move(value.second)));
}
};
@@ -243,7 +243,7 @@ struct uses_allocator_construction<std::pair<Type, Other>> {
* @return The arguments needed to create an object of the given type.
*/
template<typename Type, typename Allocator, typename... Args>
constexpr auto uses_allocator_construction_args(const Allocator &allocator, Args &&...args) noexcept {
constexpr auto uses_allocator_construction_args(const Allocator &allocator, Args &&...args) ENTT_NOEXCEPT {
return internal::uses_allocator_construction<Type>::args(allocator, std::forward<Args>(args)...);
}
@@ -281,7 +281,7 @@ constexpr Type make_obj_using_allocator(const Allocator &allocator, Args &&...ar
*/
template<typename Type, typename Allocator, typename... Args>
constexpr Type *uninitialized_construct_using_allocator(Type *value, const Allocator &allocator, Args &&...args) {
return std::apply([value](auto &&...curr) { return new(value) Type(std::forward<decltype(curr)>(curr)...); }, internal::uses_allocator_construction<Type>::args(allocator, std::forward<Args>(args)...));
return std::apply([&](auto &&...curr) { return new(value) Type(std::forward<decltype(curr)>(curr)...); }, internal::uses_allocator_construction<Type>::args(allocator, std::forward<Args>(args)...));
}
} // namespace entt

View File

@@ -25,7 +25,7 @@ struct monostate {
* @param val User data to assign to the given key.
*/
template<typename Type>
void operator=(Type val) const noexcept {
void operator=(Type val) const ENTT_NOEXCEPT {
value<Type> = val;
}
@@ -35,7 +35,7 @@ struct monostate {
* @return Stored value, if any.
*/
template<typename Type>
operator Type() const noexcept {
operator Type() const ENTT_NOEXCEPT {
return value<Type>;
}

View File

@@ -4,44 +4,10 @@
#include <tuple>
#include <type_traits>
#include <utility>
#include "../config/config.h"
namespace entt {
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal {
template<typename>
struct is_tuple_impl: std::false_type {};
template<typename... Args>
struct is_tuple_impl<std::tuple<Args...>>: std::true_type {};
} // namespace internal
/**
* Internal details not to be documented.
* @endcond
*/
/**
* @brief Provides the member constant `value` to true if a given type is a
* tuple, false otherwise.
* @tparam Type The type to test.
*/
template<typename Type>
struct is_tuple: internal::is_tuple_impl<std::remove_cv_t<Type>> {};
/**
* @brief Helper variable template.
* @tparam Type The type to test.
*/
template<typename Type>
inline constexpr bool is_tuple_v = is_tuple<Type>::value;
/**
* @brief Utility function to unwrap tuples of a single element.
* @tparam Type Tuple type of any sizes.
@@ -50,7 +16,7 @@ inline constexpr bool is_tuple_v = is_tuple<Type>::value;
* element otherwise.
*/
template<typename Type>
constexpr decltype(auto) unwrap_tuple(Type &&value) noexcept {
constexpr decltype(auto) unwrap_tuple(Type &&value) ENTT_NOEXCEPT {
if constexpr(std::tuple_size_v<std::remove_reference_t<Type>> == 1u) {
return std::get<0>(std::forward<Type>(value));
} else {
@@ -58,46 +24,6 @@ constexpr decltype(auto) unwrap_tuple(Type &&value) noexcept {
}
}
/**
* @brief Utility class to forward-and-apply tuple objects.
* @tparam Func Type of underlying invocable object.
*/
template<typename Func>
struct forward_apply: private Func {
/**
* @brief Constructs a forward-and-apply object.
* @tparam Args Types of arguments to use to construct the new instance.
* @param args Parameters to use to construct the instance.
*/
template<typename... Args>
constexpr forward_apply(Args &&...args) noexcept(std::is_nothrow_constructible_v<Func, Args...>)
: Func{std::forward<Args>(args)...} {}
/**
* @brief Forwards and applies the arguments with the underlying function.
* @tparam Type Tuple-like type to forward to the underlying function.
* @param args Parameters to forward to the underlying function.
* @return Return value of the underlying function, if any.
*/
template<typename Type>
constexpr decltype(auto) operator()(Type &&args) noexcept(noexcept(std::apply(std::declval<Func &>(), args))) {
return std::apply(static_cast<Func &>(*this), std::forward<Type>(args));
}
/*! @copydoc operator()() */
template<typename Type>
constexpr decltype(auto) operator()(Type &&args) const noexcept(noexcept(std::apply(std::declval<const Func &>(), args))) {
return std::apply(static_cast<const Func &>(*this), std::forward<Type>(args));
}
};
/**
* @brief Deduction guide.
* @tparam Func Type of underlying invocable object.
*/
template<typename Func>
forward_apply(Func) -> forward_apply<std::remove_reference_t<std::remove_cv_t<Func>>>;
} // namespace entt
#endif

View File

@@ -19,14 +19,14 @@ namespace entt {
namespace internal {
struct ENTT_API type_index final {
[[nodiscard]] static id_type next() noexcept {
[[nodiscard]] static id_type next() ENTT_NOEXCEPT {
static ENTT_MAYBE_ATOMIC(id_type) value{};
return value++;
}
};
template<typename Type>
[[nodiscard]] constexpr auto stripped_type_name() noexcept {
[[nodiscard]] constexpr auto stripped_type_name() ENTT_NOEXCEPT {
#if defined ENTT_PRETTY_FUNCTION
std::string_view pretty_function{ENTT_PRETTY_FUNCTION};
auto first = pretty_function.find_first_not_of(' ', pretty_function.find_first_of(ENTT_PRETTY_FUNCTION_PREFIX) + 1);
@@ -38,26 +38,26 @@ template<typename Type>
}
template<typename Type, auto = stripped_type_name<Type>().find_first_of('.')>
[[nodiscard]] static constexpr std::string_view type_name(int) noexcept {
[[nodiscard]] static constexpr std::string_view type_name(int) ENTT_NOEXCEPT {
constexpr auto value = stripped_type_name<Type>();
return value;
}
template<typename Type>
[[nodiscard]] static std::string_view type_name(char) noexcept {
[[nodiscard]] static std::string_view type_name(char) ENTT_NOEXCEPT {
static const auto value = stripped_type_name<Type>();
return value;
}
template<typename Type, auto = stripped_type_name<Type>().find_first_of('.')>
[[nodiscard]] static constexpr id_type type_hash(int) noexcept {
[[nodiscard]] static constexpr id_type type_hash(int) ENTT_NOEXCEPT {
constexpr auto stripped = stripped_type_name<Type>();
constexpr auto value = hashed_string::value(stripped.data(), stripped.size());
return value;
}
template<typename Type>
[[nodiscard]] static id_type type_hash(char) noexcept {
[[nodiscard]] static id_type type_hash(char) ENTT_NOEXCEPT {
static const auto value = [](const auto stripped) {
return hashed_string::value(stripped.data(), stripped.size());
}(stripped_type_name<Type>());
@@ -81,13 +81,13 @@ struct ENTT_API type_index final {
* @brief Returns the sequential identifier of a given type.
* @return The sequential identifier of a given type.
*/
[[nodiscard]] static id_type value() noexcept {
[[nodiscard]] static id_type value() ENTT_NOEXCEPT {
static const id_type value = internal::type_index::next();
return value;
}
/*! @copydoc value */
[[nodiscard]] constexpr operator id_type() const noexcept {
[[nodiscard]] constexpr operator id_type() const ENTT_NOEXCEPT {
return value();
}
};
@@ -103,16 +103,16 @@ struct type_hash final {
* @return The numeric representation of the given type.
*/
#if defined ENTT_PRETTY_FUNCTION
[[nodiscard]] static constexpr id_type value() noexcept {
[[nodiscard]] static constexpr id_type value() ENTT_NOEXCEPT {
return internal::type_hash<Type>(0);
#else
[[nodiscard]] static constexpr id_type value() noexcept {
[[nodiscard]] static constexpr id_type value() ENTT_NOEXCEPT {
return type_index<Type>::value();
#endif
}
/*! @copydoc value */
[[nodiscard]] constexpr operator id_type() const noexcept {
[[nodiscard]] constexpr operator id_type() const ENTT_NOEXCEPT {
return value();
}
};
@@ -127,12 +127,12 @@ struct type_name final {
* @brief Returns the name of a given type.
* @return The name of the given type.
*/
[[nodiscard]] static constexpr std::string_view value() noexcept {
[[nodiscard]] static constexpr std::string_view value() ENTT_NOEXCEPT {
return internal::type_name<Type>(0);
}
/*! @copydoc value */
[[nodiscard]] constexpr operator std::string_view() const noexcept {
[[nodiscard]] constexpr operator std::string_view() const ENTT_NOEXCEPT {
return value();
}
};
@@ -144,7 +144,7 @@ struct type_info final {
* @tparam Type Type for which to construct a type info object.
*/
template<typename Type>
constexpr type_info(std::in_place_type_t<Type>) noexcept
constexpr type_info(std::in_place_type_t<Type>) ENTT_NOEXCEPT
: seq{type_index<std::remove_cv_t<std::remove_reference_t<Type>>>::value()},
identifier{type_hash<std::remove_cv_t<std::remove_reference_t<Type>>>::value()},
alias{type_name<std::remove_cv_t<std::remove_reference_t<Type>>>::value()} {}
@@ -153,7 +153,7 @@ struct type_info final {
* @brief Type index.
* @return Type index.
*/
[[nodiscard]] constexpr id_type index() const noexcept {
[[nodiscard]] constexpr id_type index() const ENTT_NOEXCEPT {
return seq;
}
@@ -161,7 +161,7 @@ struct type_info final {
* @brief Type hash.
* @return Type hash.
*/
[[nodiscard]] constexpr id_type hash() const noexcept {
[[nodiscard]] constexpr id_type hash() const ENTT_NOEXCEPT {
return identifier;
}
@@ -169,7 +169,7 @@ struct type_info final {
* @brief Type name.
* @return Type name.
*/
[[nodiscard]] constexpr std::string_view name() const noexcept {
[[nodiscard]] constexpr std::string_view name() const ENTT_NOEXCEPT {
return alias;
}
@@ -185,7 +185,7 @@ private:
* @param rhs A type info object.
* @return True if the two type info objects are identical, false otherwise.
*/
[[nodiscard]] inline constexpr bool operator==(const type_info &lhs, const type_info &rhs) noexcept {
[[nodiscard]] inline constexpr bool operator==(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT {
return lhs.hash() == rhs.hash();
}
@@ -195,7 +195,7 @@ private:
* @param rhs A type info object.
* @return True if the two type info objects differ, false otherwise.
*/
[[nodiscard]] inline constexpr bool operator!=(const type_info &lhs, const type_info &rhs) noexcept {
[[nodiscard]] inline constexpr bool operator!=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}
@@ -205,7 +205,7 @@ private:
* @param rhs A valid type info object.
* @return True if the first element is less than the second, false otherwise.
*/
[[nodiscard]] constexpr bool operator<(const type_info &lhs, const type_info &rhs) noexcept {
[[nodiscard]] constexpr bool operator<(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT {
return lhs.index() < rhs.index();
}
@@ -216,7 +216,7 @@ private:
* @return True if the first element is less than or equal to the second, false
* otherwise.
*/
[[nodiscard]] constexpr bool operator<=(const type_info &lhs, const type_info &rhs) noexcept {
[[nodiscard]] constexpr bool operator<=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT {
return !(rhs < lhs);
}
@@ -227,7 +227,7 @@ private:
* @return True if the first element is greater than the second, false
* otherwise.
*/
[[nodiscard]] constexpr bool operator>(const type_info &lhs, const type_info &rhs) noexcept {
[[nodiscard]] constexpr bool operator>(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT {
return rhs < lhs;
}
@@ -238,7 +238,7 @@ private:
* @return True if the first element is greater than or equal to the second,
* false otherwise.
*/
[[nodiscard]] constexpr bool operator>=(const type_info &lhs, const type_info &rhs) noexcept {
[[nodiscard]] constexpr bool operator>=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT {
return !(lhs < rhs);
}
@@ -254,7 +254,7 @@ private:
* @return A reference to a properly initialized type info object.
*/
template<typename Type>
[[nodiscard]] const type_info &type_id() noexcept {
[[nodiscard]] const type_info &type_id() ENTT_NOEXCEPT {
if constexpr(std::is_same_v<Type, std::remove_cv_t<std::remove_reference_t<Type>>>) {
static type_info instance{std::in_place_type<Type>};
return instance;
@@ -265,7 +265,7 @@ template<typename Type>
/*! @copydoc type_id */
template<typename Type>
[[nodiscard]] const type_info &type_id(Type &&) noexcept {
[[nodiscard]] const type_info &type_id(Type &&) ENTT_NOEXCEPT {
return type_id<std::remove_cv_t<std::remove_reference_t<Type>>>();
}

View File

@@ -3,7 +3,6 @@
#include <cstddef>
#include <iterator>
#include <tuple>
#include <type_traits>
#include <utility>
#include "../config/config.h"
@@ -56,6 +55,7 @@ using type_identity_t = typename type_identity<Type>::type;
/**
* @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains.
* @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> {};
@@ -121,22 +121,22 @@ struct type_list_element;
/**
* @brief Provides compile-time indexed access to the types of a type list.
* @tparam Index Index of the type to return.
* @tparam First First type provided by the type list.
* @tparam Type First type provided by the type list.
* @tparam Other Other types provided by the type list.
*/
template<std::size_t Index, typename First, typename... Other>
struct type_list_element<Index, type_list<First, Other...>>
template<std::size_t Index, typename Type, typename... Other>
struct type_list_element<Index, type_list<Type, Other...>>
: type_list_element<Index - 1u, type_list<Other...>> {};
/**
* @brief Provides compile-time indexed access to the types of a type list.
* @tparam First First type provided by the type list.
* @tparam Type First type provided by the type list.
* @tparam Other Other types provided by the type list.
*/
template<typename First, typename... Other>
struct type_list_element<0u, type_list<First, Other...>> {
template<typename Type, typename... Other>
struct type_list_element<0u, type_list<Type, Other...>> {
/*! @brief Searched type. */
using type = First;
using type = Type;
};
/**
@@ -147,58 +147,6 @@ struct type_list_element<0u, type_list<First, Other...>> {
template<std::size_t Index, typename List>
using type_list_element_t = typename type_list_element<Index, List>::type;
/*! @brief Primary template isn't defined on purpose. */
template<typename, typename>
struct type_list_index;
/**
* @brief Provides compile-time type access to the types of a type list.
* @tparam Type Type to look for and for which to return the index.
* @tparam First First type provided by the type list.
* @tparam Other Other types provided by the type list.
*/
template<typename Type, typename First, typename... Other>
struct type_list_index<Type, type_list<First, Other...>> {
/*! @brief Unsigned integer type. */
using value_type = std::size_t;
/*! @brief Compile-time position of the given type in the sublist. */
static constexpr value_type value = 1u + type_list_index<Type, type_list<Other...>>::value;
};
/**
* @brief Provides compile-time type access to the types of a type list.
* @tparam Type Type to look for and for which to return the index.
* @tparam Other Other types provided by the type list.
*/
template<typename Type, typename... Other>
struct type_list_index<Type, type_list<Type, Other...>> {
static_assert(type_list_index<Type, type_list<Other...>>::value == sizeof...(Other), "Non-unique type");
/*! @brief Unsigned integer type. */
using value_type = std::size_t;
/*! @brief Compile-time position of the given type in the sublist. */
static constexpr value_type value = 0u;
};
/**
* @brief Provides compile-time type access to the types of a type list.
* @tparam Type Type to look for and for which to return the index.
*/
template<typename Type>
struct type_list_index<Type, type_list<>> {
/*! @brief Unsigned integer type. */
using value_type = std::size_t;
/*! @brief Compile-time position of the given type in the sublist. */
static constexpr value_type value = 0u;
};
/**
* @brief Helper variable template.
* @tparam List Type list.
* @tparam Type Type to look for and for which to return the index.
*/
template<typename Type, typename List>
inline constexpr std::size_t type_list_index_v = type_list_index<Type, List>::value;
/**
* @brief Concatenates multiple type lists.
* @tparam Type Types provided by the first type list.
@@ -297,8 +245,7 @@ struct type_list_contains;
* @tparam Other Type to look for.
*/
template<typename... Type, typename Other>
struct type_list_contains<type_list<Type...>, Other>
: std::bool_constant<(std::is_same_v<Type, Other> || ...)> {};
struct type_list_contains<type_list<Type...>, Other>: std::disjunction<std::is_same<Type, Other>...> {};
/**
* @brief Helper variable template.
@@ -330,29 +277,6 @@ struct type_list_diff<type_list<Type...>, type_list<Other...>> {
template<typename... List>
using type_list_diff_t = typename type_list_diff<List...>::type;
/*! @brief Primary template isn't defined on purpose. */
template<typename, template<typename...> class>
struct type_list_transform;
/**
* @brief Applies a given _function_ to a type list and generate a new list.
* @tparam Type Types provided by the type list.
* @tparam Op Unary operation as template class with a type member named `type`.
*/
template<typename... Type, template<typename...> class Op>
struct type_list_transform<type_list<Type...>, Op> {
/*! @brief Resulting type list after applying the transform function. */
using type = type_list<typename Op<Type>::type...>;
};
/**
* @brief Helper type.
* @tparam List Type list.
* @tparam Op Unary operation as template class with a type member named `type`.
*/
template<typename List, template<typename...> class Op>
using type_list_transform_t = typename type_list_transform<List, Op>::type;
/**
* @brief A class to use to push around lists of constant values, nothing more.
* @tparam Value Values provided by the value list.
@@ -386,20 +310,10 @@ struct value_list_element<Index, value_list<Value, Other...>>
*/
template<auto Value, auto... Other>
struct value_list_element<0u, value_list<Value, Other...>> {
/*! @brief Searched type. */
using type = decltype(Value);
/*! @brief Searched value. */
static constexpr auto value = Value;
};
/**
* @brief Helper type.
* @tparam Index Index of the type to return.
* @tparam List Value list to search into.
*/
template<std::size_t Index, typename List>
using value_list_element_t = typename value_list_element<Index, List>::type;
/**
* @brief Helper type.
* @tparam Index Index of the value to return.
@@ -408,58 +322,6 @@ using value_list_element_t = typename value_list_element<Index, List>::type;
template<std::size_t Index, typename List>
inline constexpr auto value_list_element_v = value_list_element<Index, List>::value;
/*! @brief Primary template isn't defined on purpose. */
template<auto, typename>
struct value_list_index;
/**
* @brief Provides compile-time type access to the values of a value list.
* @tparam Value Value to look for and for which to return the index.
* @tparam First First value provided by the value list.
* @tparam Other Other values provided by the value list.
*/
template<auto Value, auto First, auto... Other>
struct value_list_index<Value, value_list<First, Other...>> {
/*! @brief Unsigned integer type. */
using value_type = std::size_t;
/*! @brief Compile-time position of the given value in the sublist. */
static constexpr value_type value = 1u + value_list_index<Value, value_list<Other...>>::value;
};
/**
* @brief Provides compile-time type access to the values of a value list.
* @tparam Value Value to look for and for which to return the index.
* @tparam Other Other values provided by the value list.
*/
template<auto Value, auto... Other>
struct value_list_index<Value, value_list<Value, Other...>> {
static_assert(value_list_index<Value, value_list<Other...>>::value == sizeof...(Other), "Non-unique type");
/*! @brief Unsigned integer type. */
using value_type = std::size_t;
/*! @brief Compile-time position of the given value in the sublist. */
static constexpr value_type value = 0u;
};
/**
* @brief Provides compile-time type access to the values of a value list.
* @tparam Value Value to look for and for which to return the index.
*/
template<auto Value>
struct value_list_index<Value, value_list<>> {
/*! @brief Unsigned integer type. */
using value_type = std::size_t;
/*! @brief Compile-time position of the given type in the sublist. */
static constexpr value_type value = 0u;
};
/**
* @brief Helper variable template.
* @tparam List Value list.
* @tparam Value Value to look for and for which to return the index.
*/
template<auto Value, typename List>
inline constexpr std::size_t value_list_index_v = value_list_index<Value, List>::value;
/**
* @brief Concatenates multiple value lists.
* @tparam Value Values provided by the first value list.
@@ -511,89 +373,6 @@ 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 {};
@@ -697,7 +476,7 @@ struct has_iterator_category<Type, std::void_t<typename std::iterator_traits<Typ
/*! @copydoc is_iterator */
template<typename Type>
struct is_iterator<Type, std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_pointer_t<Type>>, void>>>
struct is_iterator<Type, std::enable_if_t<!std::is_same_v<std::remove_const_t<std::remove_pointer_t<Type>>, void>>>
: internal::has_iterator_category<Type> {};
/**
@@ -714,7 +493,7 @@ inline constexpr bool is_iterator_v = is_iterator<Type>::value;
*/
template<typename Type>
struct is_ebco_eligible
: std::bool_constant<std::is_empty_v<Type> && !std::is_final_v<Type>> {};
: std::conjunction<std::is_empty<Type>, std::negation<std::is_final<Type>>> {};
/**
* @brief Helper variable template.
@@ -785,7 +564,7 @@ template<typename Type>
}
template<typename Type>
[[nodiscard]] constexpr std::enable_if_t<is_complete_v<std::tuple_size<std::remove_cv_t<Type>>>, bool> maybe_equality_comparable(choice_t<2>) {
[[nodiscard]] constexpr std::enable_if_t<is_complete_v<std::tuple_size<std::remove_const_t<Type>>>, bool> maybe_equality_comparable(choice_t<2>) {
if constexpr(has_tuple_size_value<Type>::value) {
return unpack_maybe_equality_comparable<Type>(std::make_index_sequence<std::tuple_size<Type>::value>{});
} else {
@@ -805,10 +584,6 @@ 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.
@@ -831,7 +606,7 @@ struct constness_as {
template<typename To, typename From>
struct constness_as<To, const From> {
/*! @brief The type resulting from the transcription of the constness. */
using type = const To;
using type = std::add_const_t<To>;
};
/**
@@ -871,50 +646,6 @@ public:
template<typename Member>
using member_class_t = typename member_class<Member>::type;
/**
* @brief Extracts the n-th argument of a given function or member function.
* @tparam Index The index of the argument to extract.
* @tparam Candidate A valid function, member function or data member.
*/
template<std::size_t Index, auto Candidate>
class nth_argument {
template<typename Ret, typename... Args>
static constexpr type_list<Args...> pick_up(Ret (*)(Args...));
template<typename Ret, typename Class, typename... Args>
static constexpr type_list<Args...> pick_up(Ret (Class ::*)(Args...));
template<typename Ret, typename Class, typename... Args>
static constexpr type_list<Args...> pick_up(Ret (Class ::*)(Args...) const);
template<typename Type, typename Class>
static constexpr type_list<Type> pick_up(Type Class ::*);
public:
/*! @brief N-th argument of the given function or member function. */
using type = type_list_element_t<Index, decltype(pick_up(Candidate))>;
};
/**
* @brief Helper type.
* @tparam Index The index of the argument to extract.
* @tparam Candidate A valid function, member function or data member.
*/
template<std::size_t Index, auto Candidate>
using nth_argument_t = typename nth_argument<Index, Candidate>::type;
} // namespace entt
template<typename... Type>
struct std::tuple_size<entt::type_list<Type...>>: std::integral_constant<std::size_t, entt::type_list<Type...>::size> {};
template<std::size_t Index, typename... Type>
struct std::tuple_element<Index, entt::type_list<Type...>>: entt::type_list_element<Index, entt::type_list<Type...>> {};
template<auto... Value>
struct std::tuple_size<entt::value_list<Value...>>: std::integral_constant<std::size_t, entt::value_list<Value...>::size> {};
template<std::size_t Index, auto... Value>
struct std::tuple_element<Index, entt::value_list<Value...>>: entt::value_list_element<Index, entt::value_list<Value...>> {};
#endif

View File

@@ -1,8 +1,8 @@
#ifndef ENTT_CORE_UTILITY_HPP
#define ENTT_CORE_UTILITY_HPP
#include <type_traits>
#include <utility>
#include "../config/config.h"
namespace entt {
@@ -17,8 +17,8 @@ struct identity {
* @param value The actual argument.
* @return The submitted value as-is.
*/
template<typename Type>
[[nodiscard]] constexpr Type &&operator()(Type &&value) const noexcept {
template<class Type>
[[nodiscard]] constexpr Type &&operator()(Type &&value) const ENTT_NOEXCEPT {
return std::forward<Type>(value);
}
};
@@ -31,7 +31,7 @@ struct identity {
* @return Pointer to the member.
*/
template<typename Type, typename Class>
[[nodiscard]] constexpr auto overload(Type Class::*member) noexcept {
[[nodiscard]] constexpr auto overload(Type Class::*member) ENTT_NOEXCEPT {
return member;
}
@@ -42,7 +42,7 @@ template<typename Type, typename Class>
* @return Pointer to the function.
*/
template<typename Func>
[[nodiscard]] constexpr auto overload(Func *func) noexcept {
[[nodiscard]] constexpr auto overload(Func *func) ENTT_NOEXCEPT {
return func;
}
@@ -50,7 +50,7 @@ template<typename Func>
* @brief Helper type for visitors.
* @tparam Func Types of function objects.
*/
template<typename... Func>
template<class... Func>
struct overloaded: Func... {
using Func::operator()...;
};
@@ -59,20 +59,20 @@ struct overloaded: Func... {
* @brief Deduction guide.
* @tparam Func Types of function objects.
*/
template<typename... Func>
template<class... Func>
overloaded(Func...) -> overloaded<Func...>;
/**
* @brief Basic implementation of a y-combinator.
* @tparam Func Type of a potentially recursive function.
*/
template<typename Func>
template<class Func>
struct y_combinator {
/**
* @brief Constructs a y-combinator from a given function.
* @param recursive A potentially recursive function.
*/
constexpr y_combinator(Func recursive) noexcept(std::is_nothrow_move_constructible_v<Func>)
y_combinator(Func recursive)
: func{std::move(recursive)} {}
/**
@@ -81,14 +81,14 @@ struct y_combinator {
* @param args Parameters to use to invoke the underlying function.
* @return Return value of the underlying function, if any.
*/
template<typename... Args>
constexpr decltype(auto) operator()(Args &&...args) const noexcept(std::is_nothrow_invocable_v<Func, const y_combinator &, Args...>) {
template<class... Args>
decltype(auto) operator()(Args &&...args) const {
return func(*this, std::forward<Args>(args)...);
}
/*! @copydoc operator()() */
template<typename... Args>
constexpr decltype(auto) operator()(Args &&...args) noexcept(std::is_nothrow_invocable_v<Func, y_combinator &, Args...>) {
template<class... Args>
decltype(auto) operator()(Args &&...args) {
return func(*this, std::forward<Args>(args)...);
}

View File

@@ -4,7 +4,6 @@
#include <cstddef>
#include <type_traits>
#include "../config/config.h"
#include "fwd.hpp"
namespace entt {
@@ -15,21 +14,15 @@ namespace entt {
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, typename = void>
struct in_place_delete: std::false_type {};
template<typename Type>
struct in_place_delete<Type, std::enable_if_t<Type::in_place_delete>>
: std::true_type {};
template<typename Type, typename = void>
struct page_size: std::integral_constant<std::size_t, !std::is_empty_v<ENTT_ETO_TYPE(Type)> * ENTT_PACKED_PAGE> {};
template<>
struct page_size<void>: std::integral_constant<std::size_t, 0u> {};
struct page_size: std::integral_constant<std::size_t, (ENTT_IGNORE_IF_EMPTY && std::is_empty_v<Type>) ? 0u : ENTT_PACKED_PAGE> {};
template<typename Type>
struct page_size<Type, std::enable_if_t<std::is_convertible_v<decltype(Type::page_size), std::size_t>>>
@@ -50,15 +43,19 @@ template<typename Type, typename = void>
struct component_traits {
static_assert(std::is_same_v<std::decay_t<Type>, Type>, "Unsupported type");
/*! @brief Component type. */
using type = Type;
/*! @brief Pointer stability, default is `false`. */
static constexpr bool in_place_delete = internal::in_place_delete<Type>::value;
/*! @brief Page size, default is `ENTT_PACKED_PAGE` for non-empty types. */
static constexpr std::size_t page_size = internal::page_size<Type>::value;
};
/**
* @brief Helper variable template.
* @tparam Type Type of component.
*/
template<class Type>
inline constexpr bool ignore_as_empty_v = (component_traits<Type>::page_size == 0u);
} // namespace entt
#endif

View File

@@ -16,47 +16,35 @@ 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>> {
using value_type = Type;
};
: entt_traits<std::underlying_type_t<Type>> {};
template<typename Type>
struct entt_traits<Type, std::enable_if_t<std::is_class_v<Type>>>
: entt_traits<typename Type::entity_type> {
using value_type = Type;
};
: entt_traits<typename Type::entity_type> {};
template<>
struct entt_traits<std::uint32_t> {
using value_type = std::uint32_t;
using entity_type = std::uint32_t;
using version_type = std::uint16_t;
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
@@ -67,35 +55,31 @@ struct entt_traits<std::uint64_t> {
*/
/**
* @brief Common basic entity traits implementation.
* @tparam Traits Actual entity traits to use.
* @brief Entity traits.
* @tparam Type Type of identifier.
*/
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");
template<typename Type>
class entt_traits: internal::entt_traits<Type> {
using base_type = internal::entt_traits<Type>;
public:
/*! @brief Value type. */
using value_type = typename Traits::value_type;
using value_type = Type;
/*! @brief Underlying entity type. */
using entity_type = typename Traits::entity_type;
using entity_type = typename base_type::entity_type;
/*! @brief Underlying version type. */
using version_type = typename Traits::version_type;
/*! @brief Entity mask size. */
static constexpr entity_type entity_mask = Traits::entity_mask;
/*! @brief Version mask size */
static constexpr entity_type version_mask = Traits::version_mask;
using version_type = typename base_type::version_type;
/*! @brief Reserved identifier. */
static constexpr entity_type reserved = base_type::entity_mask | (base_type::version_mask << base_type::entity_shift);
/*! @brief Page size, default is `ENTT_SPARSE_PAGE`. */
static constexpr auto page_size = ENTT_SPARSE_PAGE;
/**
* @brief Converts an entity to its underlying type.
* @param value The value to convert.
* @return The integral representation of the given value.
*/
[[nodiscard]] static constexpr entity_type to_integral(const value_type value) noexcept {
[[nodiscard]] static constexpr entity_type to_integral(const value_type value) ENTT_NOEXCEPT {
return static_cast<entity_type>(value);
}
@@ -104,8 +88,8 @@ public:
* @param value The value to convert.
* @return The integral representation of the entity part.
*/
[[nodiscard]] static constexpr entity_type to_entity(const value_type value) noexcept {
return (to_integral(value) & entity_mask);
[[nodiscard]] static constexpr entity_type to_entity(const value_type value) ENTT_NOEXCEPT {
return (to_integral(value) & base_type::entity_mask);
}
/**
@@ -113,18 +97,8 @@ public:
* @param value The value to convert.
* @return The integral representation of the version part.
*/
[[nodiscard]] static constexpr version_type to_version(const value_type value) noexcept {
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)));
[[nodiscard]] static constexpr version_type to_version(const value_type value) ENTT_NOEXCEPT {
return (to_integral(value) >> base_type::entity_shift);
}
/**
@@ -137,8 +111,8 @@ public:
* @param version The version part of the identifier.
* @return A properly constructed identifier.
*/
[[nodiscard]] static constexpr value_type construct(const entity_type entity, const version_type version) noexcept {
return value_type{(entity & entity_mask) | (static_cast<entity_type>(version) << length)};
[[nodiscard]] static constexpr value_type construct(const entity_type entity, const version_type version) ENTT_NOEXCEPT {
return value_type{(entity & base_type::entity_mask) | (static_cast<entity_type>(version) << base_type::entity_shift)};
}
/**
@@ -151,30 +125,18 @@ public:
* @param rhs The identifier from which to take the version part.
* @return A properly constructed identifier.
*/
[[nodiscard]] static constexpr value_type combine(const entity_type lhs, const entity_type rhs) noexcept {
constexpr auto mask = (version_mask << length);
return value_type{(lhs & entity_mask) | (rhs & mask)};
[[nodiscard]] static constexpr value_type combine(const entity_type lhs, const entity_type rhs) ENTT_NOEXCEPT {
constexpr auto mask = (base_type::version_mask << base_type::entity_shift);
return value_type{(lhs & base_type::entity_mask) | (rhs & mask)};
}
};
/**
* @brief Entity traits.
* @tparam Type Type of identifier.
*/
template<typename Type>
struct entt_traits: basic_entt_traits<internal::entt_traits<Type>> {
/*! @brief Base type. */
using base_type = basic_entt_traits<internal::entt_traits<Type>>;
/*! @brief Page size, default is `ENTT_SPARSE_PAGE`. */
static constexpr std::size_t page_size = ENTT_SPARSE_PAGE;
};
/**
* @copydoc entt_traits<Entity>::to_integral
* @tparam Entity The value type.
*/
template<typename Entity>
[[nodiscard]] constexpr typename entt_traits<Entity>::entity_type to_integral(const Entity value) noexcept {
[[nodiscard]] constexpr typename entt_traits<Entity>::entity_type to_integral(const Entity value) ENTT_NOEXCEPT {
return entt_traits<Entity>::to_integral(value);
}
@@ -183,7 +145,7 @@ template<typename Entity>
* @tparam Entity The value type.
*/
template<typename Entity>
[[nodiscard]] constexpr typename entt_traits<Entity>::entity_type to_entity(const Entity value) noexcept {
[[nodiscard]] constexpr typename entt_traits<Entity>::entity_type to_entity(const Entity value) ENTT_NOEXCEPT {
return entt_traits<Entity>::to_entity(value);
}
@@ -192,7 +154,7 @@ template<typename Entity>
* @tparam Entity The value type.
*/
template<typename Entity>
[[nodiscard]] constexpr typename entt_traits<Entity>::version_type to_version(const Entity value) noexcept {
[[nodiscard]] constexpr typename entt_traits<Entity>::version_type to_version(const Entity value) ENTT_NOEXCEPT {
return entt_traits<Entity>::to_version(value);
}
@@ -204,10 +166,9 @@ struct null_t {
* @return The null representation for the given type.
*/
template<typename Entity>
[[nodiscard]] constexpr operator Entity() const noexcept {
using traits_type = entt_traits<Entity>;
constexpr auto value = traits_type::construct(traits_type::entity_mask, traits_type::version_mask);
return value;
[[nodiscard]] constexpr operator Entity() const ENTT_NOEXCEPT {
using entity_traits = entt_traits<Entity>;
return entity_traits::combine(entity_traits::reserved, entity_traits::reserved);
}
/**
@@ -215,7 +176,7 @@ struct null_t {
* @param other A null object.
* @return True in all cases.
*/
[[nodiscard]] constexpr bool operator==([[maybe_unused]] const null_t other) const noexcept {
[[nodiscard]] constexpr bool operator==([[maybe_unused]] const null_t other) const ENTT_NOEXCEPT {
return true;
}
@@ -224,7 +185,7 @@ struct null_t {
* @param other A null object.
* @return False in all cases.
*/
[[nodiscard]] constexpr bool operator!=([[maybe_unused]] const null_t other) const noexcept {
[[nodiscard]] constexpr bool operator!=([[maybe_unused]] const null_t other) const ENTT_NOEXCEPT {
return false;
}
@@ -235,9 +196,9 @@ struct null_t {
* @return False if the two elements differ, true otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept {
using traits_type = entt_traits<Entity>;
return traits_type::to_entity(entity) == traits_type::to_entity(*this);
[[nodiscard]] constexpr bool operator==(const Entity entity) const ENTT_NOEXCEPT {
using entity_traits = entt_traits<Entity>;
return entity_traits::to_entity(entity) == entity_traits::to_entity(*this);
}
/**
@@ -247,7 +208,7 @@ struct null_t {
* @return True if the two elements differ, false otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator!=(const Entity entity) const noexcept {
[[nodiscard]] constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT {
return !(entity == *this);
}
};
@@ -260,7 +221,7 @@ struct null_t {
* @return False if the two elements differ, true otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator==(const Entity entity, const null_t other) noexcept {
[[nodiscard]] constexpr bool operator==(const Entity entity, const null_t other) ENTT_NOEXCEPT {
return other.operator==(entity);
}
@@ -272,7 +233,7 @@ template<typename Entity>
* @return True if the two elements differ, false otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator!=(const Entity entity, const null_t other) noexcept {
[[nodiscard]] constexpr bool operator!=(const Entity entity, const null_t other) ENTT_NOEXCEPT {
return !(other == entity);
}
@@ -284,10 +245,9 @@ struct tombstone_t {
* @return The tombstone representation for the given type.
*/
template<typename Entity>
[[nodiscard]] constexpr operator Entity() const noexcept {
using traits_type = entt_traits<Entity>;
constexpr auto value = traits_type::construct(traits_type::entity_mask, traits_type::version_mask);
return value;
[[nodiscard]] constexpr operator Entity() const ENTT_NOEXCEPT {
using entity_traits = entt_traits<Entity>;
return entity_traits::combine(entity_traits::reserved, entity_traits::reserved);
}
/**
@@ -295,7 +255,7 @@ struct tombstone_t {
* @param other A tombstone object.
* @return True in all cases.
*/
[[nodiscard]] constexpr bool operator==([[maybe_unused]] const tombstone_t other) const noexcept {
[[nodiscard]] constexpr bool operator==([[maybe_unused]] const tombstone_t other) const ENTT_NOEXCEPT {
return true;
}
@@ -304,7 +264,7 @@ struct tombstone_t {
* @param other A tombstone object.
* @return False in all cases.
*/
[[nodiscard]] constexpr bool operator!=([[maybe_unused]] const tombstone_t other) const noexcept {
[[nodiscard]] constexpr bool operator!=([[maybe_unused]] const tombstone_t other) const ENTT_NOEXCEPT {
return false;
}
@@ -315,9 +275,9 @@ struct tombstone_t {
* @return False if the two elements differ, true otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept {
using traits_type = entt_traits<Entity>;
return traits_type::to_version(entity) == traits_type::to_version(*this);
[[nodiscard]] constexpr bool operator==(const Entity entity) const ENTT_NOEXCEPT {
using entity_traits = entt_traits<Entity>;
return entity_traits::to_version(entity) == entity_traits::to_version(*this);
}
/**
@@ -327,7 +287,7 @@ struct tombstone_t {
* @return True if the two elements differ, false otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator!=(const Entity entity) const noexcept {
[[nodiscard]] constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT {
return !(entity == *this);
}
};
@@ -340,7 +300,7 @@ struct tombstone_t {
* @return False if the two elements differ, true otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator==(const Entity entity, const tombstone_t other) noexcept {
[[nodiscard]] constexpr bool operator==(const Entity entity, const tombstone_t other) ENTT_NOEXCEPT {
return other.operator==(entity);
}
@@ -352,7 +312,7 @@ template<typename Entity>
* @return True if the two elements differ, false otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator!=(const Entity entity, const tombstone_t other) noexcept {
[[nodiscard]] constexpr bool operator!=(const Entity entity, const tombstone_t other) ENTT_NOEXCEPT {
return !(other == entity);
}

View File

@@ -1,85 +1,31 @@
#ifndef ENTT_ENTITY_FWD_HPP
#define ENTT_ENTITY_FWD_HPP
#include <cstdint>
#include <memory>
#include <type_traits>
#include "../core/fwd.hpp"
#include "../core/type_traits.hpp"
#include "utility.hpp"
namespace entt {
/*! @brief Default entity identifier. */
enum class entity : id_type {};
/*! @brief Storage deletion policy. */
enum class deletion_policy : std::uint8_t {
/*! @brief Swap-and-pop deletion policy. */
swap_and_pop = 0u,
/*! @brief In-place deletion policy. */
in_place = 1u
};
template<typename Entity = entity, typename = std::allocator<Entity>>
template<typename Entity, typename = std::allocator<Entity>>
class basic_sparse_set;
template<typename Type, typename = entity, typename = std::allocator<Type>, typename = void>
template<typename, typename Type, typename = std::allocator<Type>, typename = void>
class basic_storage;
template<typename Type>
class sigh_mixin;
/**
* @brief Provides a common way to define storage types.
* @tparam Type Storage value type.
* @tparam Entity A valid entity type.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Type, typename Entity = entity, typename Allocator = std::allocator<Type>, typename = void>
struct storage_type {
/*! @brief Type-to-storage conversion result. */
using type = sigh_mixin<basic_storage<Type, Entity, Allocator>>;
};
/**
* @brief Helper type.
* @tparam Args Arguments to forward.
*/
template<typename... Args>
using storage_type_t = typename storage_type<Args...>::type;
/**
* Type-to-storage conversion utility that preserves constness.
* @tparam Type Storage value type, eventually const.
* @tparam Entity A valid entity type.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Type, typename Entity = entity, typename Allocator = std::allocator<std::remove_const_t<Type>>>
struct storage_for {
/*! @brief Type-to-storage conversion result. */
using type = constness_as_t<storage_type_t<std::remove_const_t<Type>, Entity, Allocator>, Type>;
};
/**
* @brief Helper type.
* @tparam Args Arguments to forward.
*/
template<typename... Args>
using storage_for_t = typename storage_for<Args...>::type;
template<typename Entity = entity, typename = std::allocator<Entity>>
template<typename>
class basic_registry;
template<typename, typename, typename = void>
template<typename, typename, typename, typename = void>
class basic_view;
template<typename Type, typename = std::allocator<Type *>>
class basic_runtime_view;
template<typename>
struct basic_runtime_view;
template<typename, typename, typename>
template<typename, typename, typename, typename>
class basic_group;
template<typename, typename Mask = std::uint32_t, typename = std::allocator<Mask>>
template<typename>
class basic_observer;
template<typename>
@@ -97,160 +43,74 @@ class basic_snapshot_loader;
template<typename>
class basic_continuous_loader;
/**
* @brief Alias for exclusion lists.
* @tparam Type List of types.
*/
template<typename... Type>
struct exclude_t final: type_list<Type...> {
/*! @brief Default constructor. */
explicit constexpr exclude_t() {}
};
/**
* @brief Variable template for exclusion lists.
* @tparam Type List of types.
*/
template<typename... Type>
inline constexpr exclude_t<Type...> exclude{};
/**
* @brief Alias for lists of observed components.
* @tparam Type List of types.
*/
template<typename... Type>
struct get_t final: type_list<Type...> {
/*! @brief Default constructor. */
explicit constexpr get_t() {}
};
/**
* @brief Variable template for lists of observed components.
* @tparam Type List of types.
*/
template<typename... Type>
inline constexpr get_t<Type...> get{};
/**
* @brief Alias for lists of owned components.
* @tparam Type List of types.
*/
template<typename... Type>
struct owned_t final: type_list<Type...> {
/*! @brief Default constructor. */
explicit constexpr owned_t() {}
};
/**
* @brief Variable template for lists of owned components.
* @tparam Type List of types.
*/
template<typename... Type>
inline constexpr owned_t<Type...> owned{};
/**
* @brief Applies a given _function_ to a get list and generate a new list.
* @tparam Type Types provided by the get list.
* @tparam Op Unary operation as template class with a type member named `type`.
*/
template<typename... Type, template<typename...> class Op>
struct type_list_transform<get_t<Type...>, Op> {
/*! @brief Resulting get list after applying the transform function. */
using type = get_t<typename Op<Type>::type...>;
};
/**
* @brief Applies a given _function_ to an exclude list and generate a new list.
* @tparam Type Types provided by the exclude list.
* @tparam Op Unary operation as template class with a type member named `type`.
*/
template<typename... Type, template<typename...> class Op>
struct type_list_transform<exclude_t<Type...>, Op> {
/*! @brief Resulting exclude list after applying the transform function. */
using type = exclude_t<typename Op<Type>::type...>;
};
/**
* @brief Applies a given _function_ to an owned list and generate a new list.
* @tparam Type Types provided by the owned list.
* @tparam Op Unary operation as template class with a type member named `type`.
*/
template<typename... Type, template<typename...> class Op>
struct type_list_transform<owned_t<Type...>, Op> {
/*! @brief Resulting owned list after applying the transform function. */
using type = owned_t<typename Op<Type>::type...>;
};
/*! @brief Default entity identifier. */
enum class entity : id_type {};
/*! @brief Alias declaration for the most common use case. */
using sparse_set = basic_sparse_set<>;
/**
* @brief Alias declaration for the most common use case.
* @tparam Type Type of objects assigned to the entities.
*/
template<typename Type>
using storage = basic_storage<Type>;
/*! @brief Alias declaration for the most common use case. */
using registry = basic_registry<>;
/*! @brief Alias declaration for the most common use case. */
using observer = basic_observer<registry>;
/*! @brief Alias declaration for the most common use case. */
using organizer = basic_organizer<registry>;
/*! @brief Alias declaration for the most common use case. */
using handle = basic_handle<registry>;
/*! @brief Alias declaration for the most common use case. */
using const_handle = basic_handle<const registry>;
using sparse_set = basic_sparse_set<entity>;
/**
* @brief Alias declaration for the most common use case.
* @tparam Args Other template parameters.
*/
template<typename... Args>
using handle_view = basic_handle<registry, Args...>;
using storage = basic_storage<entity, Args...>;
/*! @brief Alias declaration for the most common use case. */
using registry = basic_registry<entity>;
/*! @brief Alias declaration for the most common use case. */
using observer = basic_observer<entity>;
/*! @brief Alias declaration for the most common use case. */
using organizer = basic_organizer<entity>;
/*! @brief Alias declaration for the most common use case. */
using handle = basic_handle<entity>;
/*! @brief Alias declaration for the most common use case. */
using const_handle = basic_handle<const entity>;
/**
* @brief Alias declaration for the most common use case.
* @tparam Args Other template parameters.
*/
template<typename... Args>
using const_handle_view = basic_handle<const registry, Args...>;
/*! @brief Alias declaration for the most common use case. */
using snapshot = basic_snapshot<registry>;
/*! @brief Alias declaration for the most common use case. */
using snapshot_loader = basic_snapshot_loader<registry>;
/*! @brief Alias declaration for the most common use case. */
using continuous_loader = basic_continuous_loader<registry>;
using handle_view = basic_handle<entity, Args...>;
/**
* @brief Alias declaration for the most common use case.
* @tparam Get Types of storage iterated by the view.
* @tparam Exclude Types of storage used to filter the view.
* @tparam Args Other template parameters.
*/
template<typename... Args>
using const_handle_view = basic_handle<const entity, Args...>;
/*! @brief Alias declaration for the most common use case. */
using snapshot = basic_snapshot<entity>;
/*! @brief Alias declaration for the most common use case. */
using snapshot_loader = basic_snapshot_loader<entity>;
/*! @brief Alias declaration for the most common use case. */
using continuous_loader = basic_continuous_loader<entity>;
/**
* @brief Alias declaration for the most common use case.
* @tparam Get Types of components iterated by the view.
* @tparam Exclude Types of components used to filter the view.
*/
template<typename Get, typename Exclude = exclude_t<>>
using view = basic_view<type_list_transform_t<Get, storage_for>, type_list_transform_t<Exclude, storage_for>>;
using view = basic_view<entity, Get, Exclude>;
/*! @brief Alias declaration for the most common use case. */
using runtime_view = basic_runtime_view<sparse_set>;
/*! @brief Alias declaration for the most common use case. */
using const_runtime_view = basic_runtime_view<const sparse_set>;
/**
* @brief Alias declaration for the most common use case.
* @tparam Owned Types of storage _owned_ by the group.
* @tparam Get Types of storage _observed_ by the group.
* @tparam Exclude Types of storage used to filter the group.
* @tparam Args Other template parameters.
*/
template<typename Owned, typename Get, typename Exclude>
using group = basic_group<type_list_transform_t<Owned, storage_for>, type_list_transform_t<Get, storage_for>, type_list_transform_t<Exclude, storage_for>>;
template<typename... Args>
using group = basic_group<entity, Args...>;
} // namespace entt

File diff suppressed because it is too large Load Diff

View File

@@ -1,109 +1,28 @@
#ifndef ENTT_ENTITY_HANDLE_HPP
#define ENTT_ENTITY_HANDLE_HPP
#include <iterator>
#include <tuple>
#include <type_traits>
#include <utility>
#include "../core/iterator.hpp"
#include "../config/config.h"
#include "../core/type_traits.hpp"
#include "entity.hpp"
#include "fwd.hpp"
#include "registry.hpp"
namespace entt {
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal {
template<typename It>
class handle_storage_iterator final {
template<typename Other>
friend class handle_storage_iterator;
using underlying_type = std::remove_reference_t<typename It::value_type::second_type>;
using entity_type = typename underlying_type::entity_type;
public:
using value_type = typename std::iterator_traits<It>::value_type;
using pointer = input_iterator_pointer<value_type>;
using reference = value_type;
using difference_type = std::ptrdiff_t;
using iterator_category = std::input_iterator_tag;
constexpr handle_storage_iterator() noexcept
: entt{null},
it{},
last{} {}
constexpr handle_storage_iterator(entity_type value, It from, It to) noexcept
: entt{value},
it{from},
last{to} {
while(it != last && !it->second.contains(entt)) {
++it;
}
}
constexpr handle_storage_iterator &operator++() noexcept {
while(++it != last && !it->second.contains(entt)) {}
return *this;
}
constexpr handle_storage_iterator operator++(int) noexcept {
handle_storage_iterator orig = *this;
return ++(*this), orig;
}
[[nodiscard]] constexpr reference operator*() const noexcept {
return *it;
}
[[nodiscard]] constexpr pointer operator->() const noexcept {
return operator*();
}
template<typename ILhs, typename IRhs>
friend constexpr bool operator==(const handle_storage_iterator<ILhs> &, const handle_storage_iterator<IRhs> &) noexcept;
private:
entity_type entt;
It it;
It last;
};
template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr bool operator==(const handle_storage_iterator<ILhs> &lhs, const handle_storage_iterator<IRhs> &rhs) noexcept {
return lhs.it == rhs.it;
}
template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr bool operator!=(const handle_storage_iterator<ILhs> &lhs, const handle_storage_iterator<IRhs> &rhs) noexcept {
return !(lhs == rhs);
}
} // namespace internal
/**
* Internal details not to be documented.
* @endcond
*/
/**
* @brief Non-owning handle to an entity.
*
* Tiny wrapper around a registry and an entity.
*
* @tparam Registry Basic registry type.
* @tparam Scope Types to which to restrict the scope of a handle.
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Type Types to which to restrict the scope of a handle.
*/
template<typename Registry, typename... Scope>
template<typename Entity, typename... Type>
struct basic_handle {
/*! @brief Type of registry accepted by the handle. */
using registry_type = Registry;
using registry_type = constness_as_t<basic_registry<std::remove_const_t<Entity>>, Entity>;
/*! @brief Underlying entity identifier. */
using entity_type = typename registry_type::entity_type;
/*! @brief Underlying version type. */
@@ -112,7 +31,7 @@ struct basic_handle {
using size_type = typename registry_type::size_type;
/*! @brief Constructs an invalid handle. */
basic_handle() noexcept
basic_handle() ENTT_NOEXCEPT
: reg{},
entt{null} {}
@@ -121,37 +40,21 @@ struct basic_handle {
* @param ref An instance of the registry class.
* @param value A valid identifier.
*/
basic_handle(registry_type &ref, entity_type value) noexcept
basic_handle(registry_type &ref, entity_type value) ENTT_NOEXCEPT
: reg{&ref},
entt{value} {}
/**
* @brief Returns an iterable object to use to _visit_ a handle.
*
* The iterable object returns a pair that contains the name and a reference
* to the current storage.<br/>
* Returned storage are those that contain the entity associated with the
* handle.
*
* @return An iterable object to use to _visit_ the handle.
*/
[[nodiscard]] auto storage() const noexcept {
auto iterable = reg->storage();
using iterator_type = internal::handle_storage_iterator<typename decltype(iterable)::iterator>;
return iterable_adaptor{iterator_type{entt, iterable.begin(), iterable.end()}, iterator_type{entt, iterable.end(), iterable.end()}};
}
/**
* @brief Constructs a const handle from a non-const one.
* @tparam Other A valid entity type.
* @tparam Other A valid entity type (see entt_traits for more details).
* @tparam Args Scope of the handle to construct.
* @return A const handle referring to the same registry and the same
* entity.
*/
template<typename Other, typename... Args>
operator basic_handle<Other, Args...>() const noexcept {
static_assert(std::is_same_v<Other, Registry> || std::is_same_v<std::remove_const_t<Other>, Registry>, "Invalid conversion between different handles");
static_assert((sizeof...(Scope) == 0 || ((sizeof...(Args) != 0 && sizeof...(Args) <= sizeof...(Scope)) && ... && (type_list_contains_v<type_list<Scope...>, Args>))), "Invalid conversion between different handles");
operator basic_handle<Other, Args...>() const ENTT_NOEXCEPT {
static_assert(std::is_same_v<Other, Entity> || std::is_same_v<std::remove_const_t<Other>, Entity>, "Invalid conversion between different handles");
static_assert((sizeof...(Type) == 0 || ((sizeof...(Args) != 0 && sizeof...(Args) <= sizeof...(Type)) && ... && (type_list_contains_v<type_list<Type...>, Args>))), "Invalid conversion between different handles");
return reg ? basic_handle<Other, Args...>{*reg, entt} : basic_handle<Other, Args...>{};
}
@@ -160,7 +63,7 @@ struct basic_handle {
* @brief Converts a handle to its underlying entity.
* @return The contained identifier.
*/
[[nodiscard]] operator entity_type() const noexcept {
[[nodiscard]] operator entity_type() const ENTT_NOEXCEPT {
return entity();
}
@@ -168,7 +71,7 @@ struct basic_handle {
* @brief Checks if a handle refers to non-null registry pointer and entity.
* @return True if the handle refers to non-null registry and entity, false otherwise.
*/
[[nodiscard]] explicit operator bool() const noexcept {
[[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT {
return reg && reg->valid(entt);
}
@@ -184,7 +87,7 @@ struct basic_handle {
* @brief Returns a pointer to the underlying registry, if any.
* @return A pointer to the underlying registry, if any.
*/
[[nodiscard]] registry_type *registry() const noexcept {
[[nodiscard]] registry_type *registry() const ENTT_NOEXCEPT {
return reg;
}
@@ -192,25 +95,30 @@ struct basic_handle {
* @brief Returns the entity associated with a handle.
* @return The entity associated with the handle.
*/
[[nodiscard]] entity_type entity() const noexcept {
[[nodiscard]] entity_type entity() const ENTT_NOEXCEPT {
return entt;
}
/*! @brief Destroys the entity associated with a handle. */
void destroy() {
reg->destroy(std::exchange(entt, null));
}
/**
* @brief Destroys the entity associated with a handle.
* @sa basic_registry::destroy
*/
void destroy() {
reg->destroy(entt);
}
/**
* @brief Destroys the entity associated with a handle.
* @sa basic_registry::destroy
* @param version A desired version upon destruction.
*/
void destroy(const version_type version) {
reg->destroy(std::exchange(entt, null), version);
reg->destroy(entt, version);
}
/**
* @brief Assigns the given component to a handle.
* @sa basic_registry::emplace
* @tparam Component Type of component to create.
* @tparam Args Types of arguments to use to construct the component.
* @param args Parameters to use to initialize the component.
@@ -218,12 +126,13 @@ struct basic_handle {
*/
template<typename Component, typename... Args>
decltype(auto) emplace(Args &&...args) const {
static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v<Component, Scope>), "Invalid type");
static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v<Component, Type>), "Invalid type");
return reg->template emplace<Component>(entt, std::forward<Args>(args)...);
}
/**
* @brief Assigns or replaces the given component for a handle.
* @sa basic_registry::emplace_or_replace
* @tparam Component Type of component to assign or replace.
* @tparam Args Types of arguments to use to construct the component.
* @param args Parameters to use to initialize the component.
@@ -231,12 +140,13 @@ struct basic_handle {
*/
template<typename Component, typename... Args>
decltype(auto) emplace_or_replace(Args &&...args) const {
static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v<Component, Scope>), "Invalid type");
static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v<Component, Type>), "Invalid type");
return reg->template emplace_or_replace<Component>(entt, std::forward<Args>(args)...);
}
/**
* @brief Patches the given component for a handle.
* @sa basic_registry::patch
* @tparam Component Type of component to patch.
* @tparam Func Types of the function objects to invoke.
* @param func Valid function objects.
@@ -244,12 +154,13 @@ struct basic_handle {
*/
template<typename Component, typename... Func>
decltype(auto) patch(Func &&...func) const {
static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v<Component, Scope>), "Invalid type");
static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v<Component, Type>), "Invalid type");
return reg->template patch<Component>(entt, std::forward<Func>(func)...);
}
/**
* @brief Replaces the given component for a handle.
* @sa basic_registry::replace
* @tparam Component Type of component to replace.
* @tparam Args Types of arguments to use to construct the component.
* @param args Parameters to use to initialize the component.
@@ -257,33 +168,36 @@ struct basic_handle {
*/
template<typename Component, typename... Args>
decltype(auto) replace(Args &&...args) const {
static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v<Component, Scope>), "Invalid type");
static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v<Component, Type>), "Invalid type");
return reg->template replace<Component>(entt, std::forward<Args>(args)...);
}
/**
* @brief Removes the given components from a handle.
* @sa basic_registry::remove
* @tparam Component Types of components to remove.
* @return The number of components actually removed.
*/
template<typename... Component>
size_type remove() const {
static_assert(sizeof...(Scope) == 0 || (type_list_contains_v<type_list<Scope...>, Component> && ...), "Invalid type");
static_assert(sizeof...(Type) == 0 || (type_list_contains_v<type_list<Type...>, Component> && ...), "Invalid type");
return reg->template remove<Component...>(entt);
}
/**
* @brief Erases the given components from a handle.
* @sa basic_registry::erase
* @tparam Component Types of components to erase.
*/
template<typename... Component>
void erase() const {
static_assert(sizeof...(Scope) == 0 || (type_list_contains_v<type_list<Scope...>, Component> && ...), "Invalid type");
static_assert(sizeof...(Type) == 0 || (type_list_contains_v<type_list<Type...>, Component> && ...), "Invalid type");
reg->template erase<Component...>(entt);
}
/**
* @brief Checks if a handle has all the given components.
* @sa basic_registry::all_of
* @tparam Component Components for which to perform the check.
* @return True if the handle has all the components, false otherwise.
*/
@@ -294,6 +208,7 @@ struct basic_handle {
/**
* @brief Checks if a handle has at least one of the given components.
* @sa basic_registry::any_of
* @tparam Component Components for which to perform the check.
* @return True if the handle has at least one of the given components,
* false otherwise.
@@ -305,17 +220,19 @@ struct basic_handle {
/**
* @brief Returns references to the given components for a handle.
* @sa basic_registry::get
* @tparam Component Types of components to get.
* @return References to the components owned by the handle.
*/
template<typename... Component>
[[nodiscard]] decltype(auto) get() const {
static_assert(sizeof...(Scope) == 0 || (type_list_contains_v<type_list<Scope...>, Component> && ...), "Invalid type");
static_assert(sizeof...(Type) == 0 || (type_list_contains_v<type_list<Type...>, Component> && ...), "Invalid type");
return reg->template get<Component...>(entt);
}
/**
* @brief Returns a reference to the given component for a handle.
* @sa basic_registry::get_or_emplace
* @tparam Component Type of component to get.
* @tparam Args Types of arguments to use to construct the component.
* @param args Parameters to use to initialize the component.
@@ -323,18 +240,19 @@ struct basic_handle {
*/
template<typename Component, typename... Args>
[[nodiscard]] decltype(auto) get_or_emplace(Args &&...args) const {
static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v<Component, Scope>), "Invalid type");
static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v<Component, Type>), "Invalid type");
return reg->template get_or_emplace<Component>(entt, std::forward<Args>(args)...);
}
/**
* @brief Returns pointers to the given components for a handle.
* @sa basic_registry::try_get
* @tparam Component Types of components to get.
* @return Pointers to the components owned by the handle.
*/
template<typename... Component>
[[nodiscard]] auto try_get() const {
static_assert(sizeof...(Scope) == 0 || (type_list_contains_v<type_list<Scope...>, Component> && ...), "Invalid type");
static_assert(sizeof...(Type) == 0 || (type_list_contains_v<type_list<Type...>, Component> && ...), "Invalid type");
return reg->template try_get<Component...>(entt);
}
@@ -346,6 +264,30 @@ struct basic_handle {
return reg->orphan(entt);
}
/**
* @brief Visits a handle and returns the pools for its components.
*
* The signature of the function should be equivalent to the following:
*
* @code{.cpp}
* void(id_type, const basic_sparse_set<entity_type> &);
* @endcode
*
* Returned pools are those that contain the entity associated with the
* handle.
*
* @tparam Func Type of the function object to invoke.
* @param func A valid function object.
*/
template<typename Func>
void visit(Func &&func) const {
for(auto [id, storage]: reg->storage()) {
if(storage.contains(entt)) {
func(id, storage);
}
}
}
private:
registry_type *reg;
entity_type entt;
@@ -361,7 +303,7 @@ private:
* entity, false otherwise.
*/
template<typename... Args, typename... Other>
[[nodiscard]] bool operator==(const basic_handle<Args...> &lhs, const basic_handle<Other...> &rhs) noexcept {
[[nodiscard]] bool operator==(const basic_handle<Args...> &lhs, const basic_handle<Other...> &rhs) ENTT_NOEXCEPT {
return lhs.registry() == rhs.registry() && lhs.entity() == rhs.entity();
}
@@ -375,10 +317,24 @@ template<typename... Args, typename... Other>
* entity, true otherwise.
*/
template<typename... Args, typename... Other>
[[nodiscard]] bool operator!=(const basic_handle<Args...> &lhs, const basic_handle<Other...> &rhs) noexcept {
[[nodiscard]] bool operator!=(const basic_handle<Args...> &lhs, const basic_handle<Other...> &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}
/**
* @brief Deduction guide.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
basic_handle(basic_registry<Entity> &, Entity) -> basic_handle<Entity>;
/**
* @brief Deduction guide.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
basic_handle(const basic_registry<Entity> &, Entity) -> basic_handle<const Entity>;
} // namespace entt
#endif

View File

@@ -1,51 +1,42 @@
#ifndef ENTT_ENTITY_HELPER_HPP
#define ENTT_ENTITY_HELPER_HPP
#include <memory>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "../core/fwd.hpp"
#include "../core/type_traits.hpp"
#include "../signal/delegate.hpp"
#include "fwd.hpp"
#include "group.hpp"
#include "view.hpp"
#include "registry.hpp"
namespace entt {
/**
* @brief Converts a registry to a view.
* @tparam Registry Basic registry type.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Registry>
class as_view {
template<typename... Get, typename... Exclude>
auto dispatch(get_t<Get...>, exclude_t<Exclude...>) const {
return reg.template view<constness_as_t<typename Get::value_type, Get>...>(exclude_t<constness_as_t<typename Exclude::value_type, Exclude>...>{});
}
public:
/*! @brief Type of registry to convert. */
using registry_type = Registry;
template<typename Entity>
struct as_view {
/*! @brief Underlying entity identifier. */
using entity_type = typename registry_type::entity_type;
using entity_type = std::remove_const_t<Entity>;
/*! @brief Type of registry to convert. */
using registry_type = constness_as_t<basic_registry<entity_type>, Entity>;
/**
* @brief Constructs a converter for a given registry.
* @param source A valid reference to a registry.
*/
as_view(registry_type &source) noexcept
: reg{source} {}
as_view(registry_type &source) ENTT_NOEXCEPT: reg{source} {}
/**
* @brief Conversion function from a registry to a view.
* @tparam Get Type of storage used to construct the view.
* @tparam Exclude Types of storage used to filter the view.
* @tparam Exclude Types of components used to filter the view.
* @tparam Component Type of components used to construct the view.
* @return A newly created view.
*/
template<typename Get, typename Exclude>
operator basic_view<Get, Exclude>() const {
return dispatch(Get{}, Exclude{});
template<typename Exclude, typename... Component>
operator basic_view<entity_type, get_t<Component...>, Exclude>() const {
return reg.template view<Component...>(Exclude{});
}
private:
@@ -53,60 +44,81 @@ private:
};
/**
* @brief Converts a registry to a group.
* @tparam Registry Basic registry type.
* @brief Deduction guide.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Registry>
class as_group {
template<typename... Owned, typename... Get, typename... Exclude>
auto dispatch(owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>) const {
if constexpr(std::is_const_v<registry_type>) {
return reg.template group_if_exists<typename Owned::value_type...>(get_t<typename Get::value_type...>{}, exclude_t<typename Exclude::value_type...>{});
} else {
return reg.template group<constness_as_t<typename Owned::value_type, Owned>...>(get_t<constness_as_t<typename Get::value_type, Get>...>{}, exclude_t<constness_as_t<typename Exclude::value_type, Exclude>...>{});
}
}
template<typename Entity>
as_view(basic_registry<Entity> &) -> as_view<Entity>;
public:
/*! @brief Type of registry to convert. */
using registry_type = Registry;
/**
* @brief Deduction guide.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
as_view(const basic_registry<Entity> &) -> as_view<const Entity>;
/**
* @brief Converts a registry to a group.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
struct as_group {
/*! @brief Underlying entity identifier. */
using entity_type = typename registry_type::entity_type;
using entity_type = std::remove_const_t<Entity>;
/*! @brief Type of registry to convert. */
using registry_type = constness_as_t<basic_registry<entity_type>, Entity>;
/**
* @brief Constructs a converter for a given registry.
* @param source A valid reference to a registry.
*/
as_group(registry_type &source) noexcept
: reg{source} {}
as_group(registry_type &source) ENTT_NOEXCEPT: reg{source} {}
/**
* @brief Conversion function from a registry to a group.
* @tparam Owned Types of _owned_ by the group.
* @tparam Get Types of storage _observed_ by the group.
* @tparam Exclude Types of storage used to filter the group.
* @tparam Get Types of components observed by the group.
* @tparam Exclude Types of components used to filter the group.
* @tparam Owned Types of components owned by the group.
* @return A newly created group.
*/
template<typename Owned, typename Get, typename Exclude>
operator basic_group<Owned, Get, Exclude>() const {
return dispatch(Owned{}, Get{}, Exclude{});
template<typename Get, typename Exclude, typename... Owned>
operator basic_group<entity_type, owned_t<Owned...>, Get, Exclude>() const {
if constexpr(std::is_const_v<registry_type>) {
return reg.template group_if_exists<Owned...>(Get{}, Exclude{});
} else {
return reg.template group<Owned...>(Get{}, Exclude{});
}
}
private:
registry_type &reg;
};
/**
* @brief Deduction guide.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
as_group(basic_registry<Entity> &) -> as_group<Entity>;
/**
* @brief Deduction guide.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
as_group(const basic_registry<Entity> &) -> as_group<const Entity>;
/**
* @brief Helper to create a listener that directly invokes a member function.
* @tparam Member Member function to invoke on a component of the given type.
* @tparam Registry Basic registry type.
* @tparam Entity A valid entity type (see entt_traits for more details).
* @param reg A registry that contains the given entity and its components.
* @param entt Entity from which to get the component.
*/
template<auto Member, typename Registry = std::decay_t<nth_argument_t<0u, Member>>>
void invoke(Registry &reg, const typename Registry::entity_type entt) {
template<auto Member, typename Entity = entity>
void invoke(basic_registry<Entity> &reg, const Entity entt) {
static_assert(std::is_member_function_pointer_v<decltype(Member)>, "Invalid pointer to non-static member function");
delegate<void(Registry &, const typename Registry::entity_type)> func;
delegate<void(basic_registry<Entity> &, const Entity)> func;
func.template connect<Member>(reg.template get<member_class_t<decltype(Member)>>(entt));
func(reg, entt);
}
@@ -118,141 +130,27 @@ void invoke(Registry &reg, const typename Registry::entity_type entt) {
* Currently, this function only works correctly with the default pool as it
* makes assumptions about how the components are laid out.
*
* @tparam Registry Basic registry type.
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Component Type of component.
* @param reg A registry that contains the given entity and its components.
* @param instance A valid component instance.
* @return The entity associated with the given component.
*/
template<typename Registry, typename Component>
typename Registry::entity_type to_entity(const Registry &reg, const Component &instance) {
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);
template<typename Entity, typename Component>
Entity to_entity(const basic_registry<Entity> &reg, const Component &instance) {
const auto &storage = reg.template storage<Component>();
const typename basic_registry<Entity>::base_type &base = storage;
const auto *addr = std::addressof(instance);
for(auto it = base.rbegin(), last = base.rend(); it < last; it += page_size) {
if(const auto dist = (addr - std::addressof(storage->get(*it))); dist >= 0 && dist < static_cast<decltype(dist)>(page_size)) {
return *(it + dist);
}
for(auto it = base.rbegin(), last = base.rend(); it < last; it += ENTT_PACKED_PAGE) {
if(const auto dist = (addr - std::addressof(storage.get(*it))); dist >= 0 && dist < ENTT_PACKED_PAGE) {
return *(it + dist);
}
}
return null;
}
/*! @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

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

View File

@@ -6,10 +6,14 @@
#include <limits>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "../core/type_traits.hpp"
#include "../signal/delegate.hpp"
#include "entity.hpp"
#include "fwd.hpp"
#include "registry.hpp"
#include "storage.hpp"
#include "utility.hpp"
namespace entt {
@@ -43,7 +47,7 @@ struct basic_collector<> {
* @return The updated collector.
*/
template<typename... AllOf, typename... NoneOf>
static constexpr auto group(exclude_t<NoneOf...> = exclude_t{}) noexcept {
static constexpr auto group(exclude_t<NoneOf...> = {}) ENTT_NOEXCEPT {
return basic_collector<matcher<type_list<>, type_list<>, type_list<NoneOf...>, AllOf...>>{};
}
@@ -53,7 +57,7 @@ struct basic_collector<> {
* @return The updated collector.
*/
template<typename AnyOf>
static constexpr auto update() noexcept {
static constexpr auto update() ENTT_NOEXCEPT {
return basic_collector<matcher<type_list<>, type_list<>, AnyOf>>{};
}
};
@@ -78,7 +82,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...> = exclude_t{}) noexcept {
static constexpr auto group(exclude_t<NoneOf...> = {}) ENTT_NOEXCEPT {
return basic_collector<matcher<type_list<>, type_list<>, type_list<NoneOf...>, AllOf...>, current_type, Other...>{};
}
@@ -88,7 +92,7 @@ struct basic_collector<matcher<type_list<Reject...>, type_list<Require...>, Rule
* @return The updated collector.
*/
template<typename AnyOf>
static constexpr auto update() noexcept {
static constexpr auto update() ENTT_NOEXCEPT {
return basic_collector<matcher<type_list<>, type_list<>, AnyOf>, current_type, Other...>{};
}
@@ -99,7 +103,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...> = exclude_t{}) noexcept {
static constexpr auto where(exclude_t<NoneOf...> = {}) ENTT_NOEXCEPT {
using extended_type = matcher<type_list<Reject..., NoneOf...>, type_list<Require..., AllOf...>, Rule...>;
return basic_collector<extended_type, Other...>{};
}
@@ -146,7 +150,8 @@ 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.
* way invalidates all the iterators and using them results in undefined
* behavior.
*
* @warning
* Lifetime of an observer doesn't necessarily have to overcome that of the
@@ -154,13 +159,11 @@ inline constexpr basic_collector<> collector{};
* from the registry before being destroyed to avoid crashes due to dangling
* pointers.
*
* @tparam Registry Basic registry type.
* @tparam Mask Mask type.
* @tparam Allocator Type of allocator used to manage memory and elements.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
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 Entity>
class basic_observer {
using payload_type = std::uint32_t;
template<typename>
struct matcher_handler;
@@ -168,43 +171,43 @@ class basic_observer: private basic_storage<Mask, typename Registry::entity_type
template<typename... Reject, typename... Require, typename AnyOf>
struct matcher_handler<matcher<type_list<Reject...>, type_list<Require...>, AnyOf>> {
template<std::size_t Index>
static void maybe_valid_if(basic_observer &obs, Registry &reg, const typename Registry::entity_type entt) {
static void maybe_valid_if(basic_observer &obs, basic_registry<Entity> &reg, const Entity entt) {
if(reg.template all_of<Require...>(entt) && !reg.template any_of<Reject...>(entt)) {
if(!obs.contains(entt)) {
obs.emplace(entt);
if(!obs.storage.contains(entt)) {
obs.storage.emplace(entt);
}
obs.get(entt) |= (1 << Index);
obs.storage.get(entt) |= (1 << Index);
}
}
template<std::size_t Index>
static void discard_if(basic_observer &obs, Registry &, const typename Registry::entity_type entt) {
if(obs.contains(entt) && !(obs.get(entt) &= (~(1 << Index)))) {
obs.erase(entt);
static void discard_if(basic_observer &obs, basic_registry<Entity> &, const Entity entt) {
if(obs.storage.contains(entt) && !(obs.storage.get(entt) &= (~(1 << Index)))) {
obs.storage.erase(entt);
}
}
template<std::size_t Index>
static void connect(basic_observer &obs, Registry &reg) {
static void connect(basic_observer &obs, basic_registry<Entity> &reg) {
(reg.template on_destroy<Require>().template connect<&discard_if<Index>>(obs), ...);
(reg.template on_construct<Reject>().template connect<&discard_if<Index>>(obs), ...);
reg.template on_update<AnyOf>().template connect<&maybe_valid_if<Index>>(obs);
reg.template on_destroy<AnyOf>().template connect<&discard_if<Index>>(obs);
}
static void disconnect(basic_observer &obs, 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);
static void disconnect(basic_observer &obs, basic_registry<Entity> &reg) {
(reg.template on_destroy<Require>().disconnect(obs), ...);
(reg.template on_construct<Reject>().disconnect(obs), ...);
reg.template on_update<AnyOf>().disconnect(obs);
reg.template on_destroy<AnyOf>().disconnect(obs);
}
};
template<typename... Reject, typename... Require, typename... NoneOf, typename... AllOf>
struct matcher_handler<matcher<type_list<Reject...>, type_list<Require...>, type_list<NoneOf...>, AllOf...>> {
template<std::size_t Index, typename... Ignore>
static void maybe_valid_if(basic_observer &obs, Registry &reg, const typename Registry::entity_type entt) {
static void maybe_valid_if(basic_observer &obs, basic_registry<Entity> &reg, const Entity entt) {
auto condition = [&reg, entt]() {
if constexpr(sizeof...(Ignore) == 0) {
return reg.template all_of<AllOf..., Require...>(entt) && !reg.template any_of<NoneOf..., Reject...>(entt);
@@ -214,23 +217,23 @@ class basic_observer: private basic_storage<Mask, typename Registry::entity_type
};
if(condition()) {
if(!obs.contains(entt)) {
obs.emplace(entt);
if(!obs.storage.contains(entt)) {
obs.storage.emplace(entt);
}
obs.get(entt) |= (1 << Index);
obs.storage.get(entt) |= (1 << Index);
}
}
template<std::size_t Index>
static void discard_if(basic_observer &obs, Registry &, const typename Registry::entity_type entt) {
if(obs.contains(entt) && !(obs.get(entt) &= (~(1 << Index)))) {
obs.erase(entt);
static void discard_if(basic_observer &obs, basic_registry<Entity> &, const Entity entt) {
if(obs.storage.contains(entt) && !(obs.storage.get(entt) &= (~(1 << Index)))) {
obs.storage.erase(entt);
}
}
template<std::size_t Index>
static void connect(basic_observer &obs, Registry &reg) {
static void connect(basic_observer &obs, basic_registry<Entity> &reg) {
(reg.template on_destroy<Require>().template connect<&discard_if<Index>>(obs), ...);
(reg.template on_construct<Reject>().template connect<&discard_if<Index>>(obs), ...);
(reg.template on_construct<AllOf>().template connect<&maybe_valid_if<Index>>(obs), ...);
@@ -239,55 +242,43 @@ class basic_observer: private basic_storage<Mask, typename Registry::entity_type
(reg.template on_construct<NoneOf>().template connect<&discard_if<Index>>(obs), ...);
}
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), ...);
static void disconnect(basic_observer &obs, basic_registry<Entity> &reg) {
(reg.template on_destroy<Require>().disconnect(obs), ...);
(reg.template on_construct<Reject>().disconnect(obs), ...);
(reg.template on_construct<AllOf>().disconnect(obs), ...);
(reg.template on_destroy<NoneOf>().disconnect(obs), ...);
(reg.template on_destroy<AllOf>().disconnect(obs), ...);
(reg.template on_construct<NoneOf>().disconnect(obs), ...);
}
};
template<typename... Matcher>
static void disconnect(Registry &reg, basic_observer &obs) {
static void disconnect(basic_registry<Entity> &reg, basic_observer &obs) {
(matcher_handler<Matcher>::disconnect(obs, reg), ...);
}
template<typename... Matcher, std::size_t... Index>
void connect(Registry &reg, std::index_sequence<Index...>) {
static_assert(sizeof...(Matcher) < std::numeric_limits<typename base_type::value_type>::digits, "Too many matchers");
void connect(basic_registry<Entity> &reg, std::index_sequence<Index...>) {
static_assert(sizeof...(Matcher) < std::numeric_limits<payload_type>::digits, "Too many matchers");
(matcher_handler<Matcher>::template connect<Index>(*this, reg), ...);
release.template connect<&basic_observer::disconnect<Matcher...>>(reg);
}
public:
/*! Basic registry type. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */
using entity_type = typename registry_type::entity_type;
using entity_type = Entity;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Random access iterator type. */
using iterator = typename registry_type::common_type::iterator;
using iterator = typename basic_sparse_set<Entity>::iterator;
/*! @brief Default constructor. */
basic_observer()
: 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{} {}
: release{},
storage{} {}
/*! @brief Default copy constructor, deleted on purpose. */
basic_observer(const basic_observer &) = delete;
/*! @brief Default move constructor, deleted on purpose. */
basic_observer(basic_observer &&) = delete;
@@ -295,14 +286,16 @@ 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...>, const allocator_type &allocator = allocator_type{})
: basic_observer{allocator} {
basic_observer(basic_registry<entity_type> &reg, basic_collector<Matcher...>)
: basic_observer{} {
connect<Matcher...>(reg, std::index_sequence_for<Matcher...>{});
}
/*! @brief Default destructor. */
~basic_observer() = default;
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This observer.
@@ -321,10 +314,10 @@ public:
* @param reg A valid reference to a registry.
*/
template<typename... Matcher>
void connect(registry_type &reg, basic_collector<Matcher...>) {
void connect(basic_registry<entity_type> &reg, basic_collector<Matcher...>) {
disconnect();
connect<Matcher...>(reg, std::index_sequence_for<Matcher...>{});
base_type::clear();
storage.clear();
}
/*! @brief Disconnects an observer from the registry it keeps track of. */
@@ -339,16 +332,16 @@ public:
* @brief Returns the number of elements in an observer.
* @return Number of elements.
*/
[[nodiscard]] size_type size() const noexcept {
return base_type::size();
[[nodiscard]] size_type size() const ENTT_NOEXCEPT {
return storage.size();
}
/**
* @brief Checks whether an observer is empty.
* @return True if the observer is empty, false otherwise.
*/
[[nodiscard]] bool empty() const noexcept {
return base_type::empty();
[[nodiscard]] bool empty() const ENTT_NOEXCEPT {
return storage.empty();
}
/**
@@ -363,33 +356,39 @@ public:
*
* @return A pointer to the array of entities.
*/
[[nodiscard]] const entity_type *data() const noexcept {
return base_type::data();
[[nodiscard]] const entity_type *data() const ENTT_NOEXCEPT {
return storage.data();
}
/**
* @brief Returns an iterator to the first entity of the observer.
*
* If the observer is empty, the returned iterator will be equal to `end()`.
* The returned iterator points to the first entity of the observer. If the
* container is empty, the returned iterator will be equal to `end()`.
*
* @return An iterator to the first entity of the observer.
*/
[[nodiscard]] iterator begin() const noexcept {
return base_type::base_type::begin();
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
return storage.basic_sparse_set<entity_type>::begin();
}
/**
* @brief Returns an iterator that is past the last entity of the observer.
*
* The returned iterator points to the entity following the last entity of
* the observer. Attempting to dereference the returned iterator results in
* undefined behavior.
*
* @return An iterator to the entity following the last entity of the
* observer.
*/
[[nodiscard]] iterator end() const noexcept {
return base_type::base_type::end();
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
return storage.basic_sparse_set<entity_type>::end();
}
/*! @brief Clears the underlying container. */
void clear() noexcept {
base_type::clear();
void clear() ENTT_NOEXCEPT {
storage.clear();
}
/**
@@ -429,6 +428,7 @@ public:
private:
delegate<void(basic_observer &)> release;
basic_storage<entity_type, payload_type> storage;
};
} // namespace entt

View File

@@ -1,15 +1,15 @@
#ifndef ENTT_ENTITY_ORGANIZER_HPP
#define ENTT_ENTITY_ORGANIZER_HPP
#include <algorithm>
#include <cstddef>
#include <type_traits>
#include <utility>
#include <vector>
#include "../container/dense_map.hpp"
#include "../core/type_info.hpp"
#include "../core/type_traits.hpp"
#include "../core/utility.hpp"
#include "../graph/adjacency_matrix.hpp"
#include "../graph/flow.hpp"
#include "fwd.hpp"
#include "helper.hpp"
@@ -25,8 +25,8 @@ namespace internal {
template<typename>
struct is_view: std::false_type {};
template<typename... Args>
struct is_view<basic_view<Args...>>: std::true_type {};
template<typename Entity, typename... Component, typename... Exclude>
struct is_view<basic_view<Entity, get_t<Component...>, exclude_t<Exclude...>>>: std::true_type {};
template<typename Type>
inline constexpr bool is_view_v = is_view<Type>::value;
@@ -34,35 +34,35 @@ inline constexpr bool is_view_v = is_view<Type>::value;
template<typename Type, typename Override>
struct unpack_type {
using ro = std::conditional_t<
type_list_contains_v<Override, const Type> || (std::is_const_v<Type> && !type_list_contains_v<Override, std::remove_const_t<Type>>),
type_list_contains_v<Override, std::add_const_t<Type>> || (std::is_const_v<Type> && !type_list_contains_v<Override, std::remove_const_t<Type>>),
type_list<std::remove_const_t<Type>>,
type_list<>>;
using rw = std::conditional_t<
type_list_contains_v<Override, std::remove_const_t<Type>> || (!std::is_const_v<Type> && !type_list_contains_v<Override, const Type>),
type_list_contains_v<Override, std::remove_const_t<Type>> || (!std::is_const_v<Type> && !type_list_contains_v<Override, std::add_const_t<Type>>),
type_list<Type>,
type_list<>>;
};
template<typename... Args, typename... Override>
struct unpack_type<basic_registry<Args...>, type_list<Override...>> {
template<typename Entity, typename... Override>
struct unpack_type<basic_registry<Entity>, type_list<Override...>> {
using ro = type_list<>;
using rw = type_list<>;
};
template<typename... Args, typename... Override>
struct unpack_type<const basic_registry<Args...>, type_list<Override...>>
: unpack_type<basic_registry<Args...>, type_list<Override...>> {};
template<typename Entity, typename... Override>
struct unpack_type<const basic_registry<Entity>, type_list<Override...>>
: unpack_type<basic_registry<Entity>, type_list<Override...>> {};
template<typename... Get, typename... Exclude, typename... Override>
struct unpack_type<basic_view<get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>> {
using ro = type_list_cat_t<type_list<typename Exclude::value_type...>, typename unpack_type<constness_as_t<typename Get::value_type, Get>, type_list<Override...>>::ro...>;
using rw = type_list_cat_t<typename unpack_type<constness_as_t<typename Get::value_type, Get>, type_list<Override...>>::rw...>;
template<typename Entity, typename... Component, typename... Exclude, typename... Override>
struct unpack_type<basic_view<Entity, get_t<Component...>, exclude_t<Exclude...>>, type_list<Override...>> {
using ro = type_list_cat_t<type_list<Exclude...>, typename unpack_type<Component, type_list<Override...>>::ro...>;
using rw = type_list_cat_t<typename unpack_type<Component, type_list<Override...>>::rw...>;
};
template<typename... Get, typename... Exclude, typename... Override>
struct unpack_type<const basic_view<get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>>
: unpack_type<basic_view<get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>> {};
template<typename Entity, typename... Component, typename... Exclude, typename... Override>
struct unpack_type<const basic_view<Entity, get_t<Component...>, exclude_t<Exclude...>>, type_list<Override...>>
: unpack_type<basic_view<Entity, get_t<Component...>, exclude_t<Exclude...>>, type_list<Override...>> {};
template<typename, typename>
struct resource_traits;
@@ -102,12 +102,12 @@ resource_traits<type_list<std::remove_reference_t<Args>...>, type_list<Req...>>
* goal of the tool. Instead, they are returned to the user in the form of a
* graph that allows for safe execution.
*
* @tparam Registry Basic registry type.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Registry>
template<typename Entity>
class basic_organizer final {
using callback_type = void(const void *, Registry &);
using prepare_type = void(Registry &);
using callback_type = void(const void *, basic_registry<Entity> &);
using prepare_type = void(basic_registry<Entity> &);
using dependency_type = std::size_t(const bool, const type_info **, const std::size_t);
struct vertex_data final {
@@ -122,18 +122,18 @@ class basic_organizer final {
};
template<typename Type>
[[nodiscard]] static decltype(auto) extract(Registry &reg) {
if constexpr(std::is_same_v<Type, Registry>) {
[[nodiscard]] static decltype(auto) extract(basic_registry<Entity> &reg) {
if constexpr(std::is_same_v<Type, basic_registry<Entity>>) {
return reg;
} else if constexpr(internal::is_view_v<Type>) {
return static_cast<Type>(as_view{reg});
return as_view{reg};
} else {
return reg.ctx().template emplace<std::remove_reference_t<Type>>();
}
}
template<typename... Args>
[[nodiscard]] static auto to_args(Registry &reg, type_list<Args...>) {
[[nodiscard]] static auto to_args(basic_registry<Entity> &reg, type_list<Args...>) {
return std::tuple<decltype(extract<Args>(reg))...>(extract<Args>(reg)...);
}
@@ -143,29 +143,92 @@ class basic_organizer final {
return {};
} else {
const type_info *info[sizeof...(Type)]{&type_id<Type>()...};
const auto length = count < sizeof...(Type) ? count : sizeof...(Type);
for(std::size_t pos{}; pos < length; ++pos) {
buffer[pos] = info[pos];
}
const auto length = (std::min)(count, sizeof...(Type));
std::copy_n(info, length, buffer);
return length;
}
}
template<typename... RO, typename... RW>
void track_dependencies(std::size_t index, const bool requires_registry, type_list<RO...>, type_list<RW...>) {
builder.bind(static_cast<id_type>(index));
builder.set(type_hash<Registry>::value(), requires_registry || (sizeof...(RO) + sizeof...(RW) == 0u));
(builder.ro(type_hash<RO>::value()), ...);
(builder.rw(type_hash<RW>::value()), ...);
dependencies[type_hash<basic_registry<Entity>>::value()].emplace_back(index, requires_registry || (sizeof...(RO) + sizeof...(RW) == 0u));
(dependencies[type_hash<RO>::value()].emplace_back(index, false), ...);
(dependencies[type_hash<RW>::value()].emplace_back(index, true), ...);
}
[[nodiscard]] std::vector<bool> adjacency_matrix() {
const auto length = vertices.size();
std::vector<bool> edges(length * length, false);
// creates the adjacency matrix
for(const auto &deps: dependencies) {
const auto last = deps.second.cend();
auto it = deps.second.cbegin();
while(it != last) {
if(it->second) {
// rw item
if(auto curr = it++; it != last) {
if(it->second) {
edges[curr->first * length + it->first] = true;
} else {
if(const auto next = std::find_if(it, last, [](const auto &elem) { return elem.second; }); next != last) {
for(; it != next; ++it) {
edges[curr->first * length + it->first] = true;
edges[it->first * length + next->first] = true;
}
} else {
for(; it != next; ++it) {
edges[curr->first * length + it->first] = true;
}
}
}
}
} else {
// ro item, possibly only on first iteration
if(const auto next = std::find_if(it, last, [](const auto &elem) { return elem.second; }); next != last) {
for(; it != next; ++it) {
edges[it->first * length + next->first] = true;
}
} else {
it = last;
}
}
}
}
// computes the transitive closure
for(std::size_t vk{}; vk < length; ++vk) {
for(std::size_t vi{}; vi < length; ++vi) {
for(std::size_t vj{}; vj < length; ++vj) {
edges[vi * length + vj] = edges[vi * length + vj] || (edges[vi * length + vk] && edges[vk * length + vj]);
}
}
}
// applies the transitive reduction
for(std::size_t vert{}; vert < length; ++vert) {
edges[vert * length + vert] = false;
}
for(std::size_t vj{}; vj < length; ++vj) {
for(std::size_t vi{}; vi < length; ++vi) {
if(edges[vi * length + vj]) {
for(std::size_t vk{}; vk < length; ++vk) {
if(edges[vj * length + vk]) {
edges[vi * length + vk] = false;
}
}
}
}
}
return edges;
}
public:
/*! Basic registry type. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */
using entity_type = typename registry_type::entity_type;
using entity_type = Entity;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Raw task function type. */
@@ -191,7 +254,7 @@ public:
* @param length The length of the user-supplied buffer.
* @return The number of type info objects written to the buffer.
*/
size_type ro_dependency(const type_info **buffer, const std::size_t length) const noexcept {
size_type ro_dependency(const type_info **buffer, const std::size_t length) const ENTT_NOEXCEPT {
return node.dependency(false, buffer, length);
}
@@ -202,7 +265,7 @@ public:
* @param length The length of the user-supplied buffer.
* @return The number of type info objects written to the buffer.
*/
size_type rw_dependency(const type_info **buffer, const std::size_t length) const noexcept {
size_type rw_dependency(const type_info **buffer, const std::size_t length) const ENTT_NOEXCEPT {
return node.dependency(true, buffer, length);
}
@@ -210,7 +273,7 @@ public:
* @brief Returns the number of read-only resources of a vertex.
* @return The number of read-only resources of the vertex.
*/
size_type ro_count() const noexcept {
size_type ro_count() const ENTT_NOEXCEPT {
return node.ro_count;
}
@@ -218,7 +281,7 @@ public:
* @brief Returns the number of writable resources of a vertex.
* @return The number of writable resources of the vertex.
*/
size_type rw_count() const noexcept {
size_type rw_count() const ENTT_NOEXCEPT {
return node.rw_count;
}
@@ -226,7 +289,7 @@ public:
* @brief Checks if a vertex is also a top-level one.
* @return True if the vertex is a top-level one, false otherwise.
*/
bool top_level() const noexcept {
bool top_level() const ENTT_NOEXCEPT {
return is_top_level;
}
@@ -234,7 +297,7 @@ public:
* @brief Returns a type info object associated with a vertex.
* @return A properly initialized type info object.
*/
const type_info &info() const noexcept {
const type_info &info() const ENTT_NOEXCEPT {
return *node.info;
}
@@ -242,7 +305,7 @@ public:
* @brief Returns a user defined name associated with a vertex, if any.
* @return The user defined name associated with the vertex, if any.
*/
const char *name() const noexcept {
const char *name() const ENTT_NOEXCEPT {
return node.name;
}
@@ -250,7 +313,7 @@ public:
* @brief Returns the function associated with a vertex.
* @return The function associated with the vertex.
*/
function_type *callback() const noexcept {
function_type *callback() const ENTT_NOEXCEPT {
return node.callback;
}
@@ -258,7 +321,7 @@ public:
* @brief Returns the payload associated with a vertex, if any.
* @return The payload associated with the vertex, if any.
*/
const void *data() const noexcept {
const void *data() const ENTT_NOEXCEPT {
return node.payload;
}
@@ -266,7 +329,7 @@ public:
* @brief Returns the list of nodes reachable from a given vertex.
* @return The list of nodes reachable from the vertex.
*/
const std::vector<std::size_t> &children() const noexcept {
const std::vector<std::size_t> &children() const ENTT_NOEXCEPT {
return reachable;
}
@@ -275,7 +338,7 @@ public:
* are properly instantiated before using them.
* @param reg A valid registry.
*/
void prepare(registry_type &reg) const {
void prepare(basic_registry<entity_type> &reg) const {
node.prepare ? node.prepare(reg) : void();
}
@@ -294,9 +357,9 @@ public:
template<auto Candidate, typename... Req>
void emplace(const char *name = nullptr) {
using resource_type = decltype(internal::free_function_to_resource_traits<Req...>(Candidate));
constexpr auto requires_registry = type_list_contains_v<typename resource_type::args, registry_type>;
constexpr auto requires_registry = type_list_contains_v<typename resource_type::args, basic_registry<entity_type>>;
callback_type *callback = +[](const void *, registry_type &reg) {
callback_type *callback = +[](const void *, basic_registry<entity_type> &reg) {
std::apply(Candidate, to_args(reg, typename resource_type::args{}));
};
@@ -307,7 +370,7 @@ public:
nullptr,
callback,
+[](const bool rw, const type_info **buffer, const std::size_t length) { return rw ? fill_dependencies(typename resource_type::rw{}, buffer, length) : fill_dependencies(typename resource_type::ro{}, buffer, length); },
+[](registry_type &reg) { void(to_args(reg, typename resource_type::args{})); },
+[](basic_registry<entity_type> &reg) { void(to_args(reg, typename resource_type::args{})); },
&type_id<std::integral_constant<decltype(Candidate), Candidate>>()};
track_dependencies(vertices.size(), requires_registry, typename resource_type::ro{}, typename resource_type::rw{});
@@ -326,9 +389,9 @@ public:
template<auto Candidate, typename... Req, typename Type>
void emplace(Type &value_or_instance, const char *name = nullptr) {
using resource_type = decltype(internal::constrained_function_to_resource_traits<Req...>(Candidate));
constexpr auto requires_registry = type_list_contains_v<typename resource_type::args, registry_type>;
constexpr auto requires_registry = type_list_contains_v<typename resource_type::args, basic_registry<entity_type>>;
callback_type *callback = +[](const void *payload, registry_type &reg) {
callback_type *callback = +[](const void *payload, basic_registry<entity_type> &reg) {
Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload));
std::apply(Candidate, std::tuple_cat(std::forward_as_tuple(*curr), to_args(reg, typename resource_type::args{})));
};
@@ -340,7 +403,7 @@ public:
&value_or_instance,
callback,
+[](const bool rw, const type_info **buffer, const std::size_t length) { return rw ? fill_dependencies(typename resource_type::rw{}, buffer, length) : fill_dependencies(typename resource_type::ro{}, buffer, length); },
+[](registry_type &reg) { void(to_args(reg, typename resource_type::args{})); },
+[](basic_registry<entity_type> &reg) { void(to_args(reg, typename resource_type::args{})); },
&type_id<std::integral_constant<decltype(Candidate), Candidate>>()};
track_dependencies(vertices.size(), requires_registry, typename resource_type::ro{}, typename resource_type::rw{});
@@ -378,19 +441,28 @@ public:
* @return The adjacency list of the task graph.
*/
std::vector<vertex> graph() {
const auto edges = adjacency_matrix();
// creates the adjacency list
std::vector<vertex> adjacency_list{};
adjacency_list.reserve(vertices.size());
auto adjacency_matrix = builder.graph();
for(auto curr: adjacency_matrix.vertices()) {
const auto iterable = adjacency_matrix.in_edges(curr);
for(std::size_t col{}, length = vertices.size(); col < length; ++col) {
std::vector<std::size_t> reachable{};
const auto row = col * length;
bool is_top_level = true;
for(auto &&edge: adjacency_matrix.out_edges(curr)) {
reachable.push_back(edge.second);
for(std::size_t next{}; next < length; ++next) {
if(edges[row + next]) {
reachable.push_back(next);
}
}
adjacency_list.emplace_back(iterable.cbegin() == iterable.cend(), vertices[curr], std::move(reachable));
for(std::size_t next{}; next < length && is_top_level; ++next) {
is_top_level = !edges[next * length + col];
}
adjacency_list.emplace_back(is_top_level, vertices[col], std::move(reachable));
}
return adjacency_list;
@@ -398,13 +470,13 @@ public:
/*! @brief Erases all elements from a container. */
void clear() {
builder.clear();
dependencies.clear();
vertices.clear();
}
private:
dense_map<id_type, std::vector<std::pair<std::size_t, bool>>, identity> dependencies;
std::vector<vertex_data> vertices;
flow builder;
};
} // namespace entt

File diff suppressed because it is too large Load Diff

View File

@@ -2,12 +2,14 @@
#define ENTT_ENTITY_RUNTIME_VIEW_HPP
#include <algorithm>
#include <cstddef>
#include <iterator>
#include <type_traits>
#include <utility>
#include <vector>
#include "../config/config.h"
#include "entity.hpp"
#include "fwd.hpp"
#include "sparse_set.hpp"
namespace entt {
@@ -35,13 +37,13 @@ public:
using reference = typename iterator_type::reference;
using iterator_category = std::bidirectional_iterator_tag;
constexpr runtime_view_iterator() noexcept
runtime_view_iterator() ENTT_NOEXCEPT
: pools{},
filter{},
it{},
tombstone_check{} {}
runtime_view_iterator(const std::vector<Set *> &cpools, const std::vector<Set *> &ignore, iterator_type curr) noexcept
runtime_view_iterator(const std::vector<const Set *> &cpools, const std::vector<const Set *> &ignore, iterator_type curr) ENTT_NOEXCEPT
: pools{&cpools},
filter{&ignore},
it{curr},
@@ -71,25 +73,25 @@ public:
return operator--(), orig;
}
[[nodiscard]] pointer operator->() const noexcept {
[[nodiscard]] pointer operator->() const ENTT_NOEXCEPT {
return it.operator->();
}
[[nodiscard]] reference operator*() const noexcept {
[[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
return *operator->();
}
[[nodiscard]] constexpr bool operator==(const runtime_view_iterator &other) const noexcept {
[[nodiscard]] bool operator==(const runtime_view_iterator &other) const ENTT_NOEXCEPT {
return it == other.it;
}
[[nodiscard]] constexpr bool operator!=(const runtime_view_iterator &other) const noexcept {
[[nodiscard]] bool operator!=(const runtime_view_iterator &other) const ENTT_NOEXCEPT {
return !(*this == other);
}
private:
const std::vector<Set *> *pools;
const std::vector<Set *> *filter;
const std::vector<const Set *> *pools;
const std::vector<const Set *> *filter;
iterator_type it;
bool tombstone_check;
};
@@ -101,125 +103,76 @@ private:
* @endcond
*/
/**
* @brief Runtime view implementation.
*
* Primary template isn't defined on purpose. All the specializations give a
* compile-time error, but for a few reasonable cases.
*/
template<typename>
struct basic_runtime_view;
/**
* @brief Generic runtime view.
*
* Runtime views iterate over those entities that are at least in the given
* storage. During initialization, a runtime view looks at the number of
* entities available for each component and uses the smallest set in order to
* get a performance boost when iterating.
* Runtime views iterate over those entities that have at least all the given
* components in their bags. During initialization, a runtime view looks at the
* number of entities available for each component and picks up a reference to
* the smallest set of candidate entities in order to get a performance boost
* when iterate.<br/>
* Order of elements during iterations are highly dependent on the order of the
* underlying data structures. See sparse_set and its specializations for more
* details.
*
* @b Important
*
* Iterators aren't invalidated if:
*
* * New elements are added to the storage.
* * The entity currently pointed is modified (for example, components are added
* or removed from it).
* * New instances of the given components are created and assigned to entities.
* * The entity currently pointed is modified (as an example, if one of the
* given components is removed from the entity to which the iterator points).
* * The entity currently pointed is destroyed.
*
* In all other cases, modifying the storage iterated by the view in any way
* invalidates all the iterators.
* In all the other cases, modifying the pools of the given components in any
* way invalidates all the iterators and using them results in undefined
* behavior.
*
* @tparam Type Common base type.
* @note
* Views share references to the underlying data structures of the registry that
* generated them. Therefore any change to the entities and to the components
* made by means of the registry are immediately reflected by the views, unless
* a pool was missing when the view was built (in this case, the view won't
* have a valid reference and won't be updated accordingly).
*
* @warning
* Lifetime of a view must not overcome that of the registry that generated it.
* In any other case, attempting to use a view results in undefined behavior.
*
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Type, typename Allocator>
class basic_runtime_view {
using alloc_traits = std::allocator_traits<Allocator>;
static_assert(std::is_same_v<typename alloc_traits::value_type, Type *>, "Invalid value type");
using container_type = std::vector<Type *, Allocator>;
public:
/*! @brief Allocator type. */
using allocator_type = Allocator;
template<typename Entity, typename Allocator>
struct basic_runtime_view<basic_sparse_set<Entity, Allocator>> {
/*! @brief Underlying entity identifier. */
using entity_type = typename Type::entity_type;
using entity_type = Entity;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Common type among all storage types. */
using common_type = Type;
using base_type = basic_sparse_set<Entity, Allocator>;
/*! @brief Bidirectional iterator type. */
using iterator = internal::runtime_view_iterator<common_type>;
using iterator = internal::runtime_view_iterator<base_type>;
/*! @brief Default constructor to use to create empty, invalid views. */
basic_runtime_view() noexcept
: basic_runtime_view{allocator_type{}} {}
/**
* @brief Constructs an empty, invalid view with a given allocator.
* @param allocator The allocator to use.
*/
explicit basic_runtime_view(const allocator_type &allocator)
: pools{allocator},
filter{allocator} {}
/*! @brief Default copy constructor. */
basic_runtime_view(const basic_runtime_view &) = default;
/**
* @brief Allocator-extended copy constructor.
* @param other The instance to copy from.
* @param allocator The allocator to use.
*/
basic_runtime_view(const basic_runtime_view &other, const allocator_type &allocator)
: pools{other.pools, allocator},
filter{other.filter, allocator} {}
/*! @brief Default move constructor. */
basic_runtime_view(basic_runtime_view &&) noexcept(std::is_nothrow_move_constructible_v<container_type>) = default;
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
basic_runtime_view(basic_runtime_view &&other, const allocator_type &allocator)
: pools{std::move(other.pools), allocator},
filter{std::move(other.filter), allocator} {}
/**
* @brief Default copy assignment operator.
* @return This container.
*/
basic_runtime_view &operator=(const basic_runtime_view &) = default;
/**
* @brief Default move assignment operator.
* @return This container.
*/
basic_runtime_view &operator=(basic_runtime_view &&) noexcept(std::is_nothrow_move_assignable_v<container_type>) = default;
/**
* @brief Exchanges the contents with those of a given view.
* @param other View to exchange the content with.
*/
void swap(basic_runtime_view &other) {
using std::swap;
swap(pools, other.pools);
swap(filter, other.filter);
}
/**
* @brief Returns the associated allocator.
* @return The associated allocator.
*/
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
return pools.get_allocator();
}
/*! @brief Clears the view. */
void clear() {
pools.clear();
filter.clear();
}
basic_runtime_view() ENTT_NOEXCEPT
: pools{},
filter{} {}
/**
* @brief Appends an opaque storage object to a runtime view.
* @param base An opaque reference to a storage object.
* @return This runtime view.
*/
basic_runtime_view &iterate(common_type &base) {
basic_runtime_view &iterate(const base_type &base) {
if(pools.empty() || !(base.size() < pools[0u]->size())) {
pools.push_back(&base);
} else {
@@ -234,7 +187,7 @@ public:
* @param base An opaque reference to a storage object.
* @return This runtime view.
*/
basic_runtime_view &exclude(common_type &base) {
basic_runtime_view &exclude(const base_type &base) {
filter.push_back(&base);
return *this;
}
@@ -251,7 +204,9 @@ public:
* @brief Returns an iterator to the first entity that has the given
* components.
*
* If the view is empty, the returned iterator will be equal to `end()`.
* The returned iterator points to the first entity that has the given
* components. If the view is empty, the returned iterator will be equal to
* `end()`.
*
* @return An iterator to the first entity that has the given components.
*/
@@ -262,6 +217,11 @@ 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.
*/
@@ -284,7 +244,8 @@ 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.<br/>
* the entity itself. To get the components, users can use the registry with
* which the view was built.<br/>
* The signature of the function should be equivalent to the following:
*
* @code{.cpp}
@@ -302,8 +263,8 @@ public:
}
private:
container_type pools;
container_type filter;
std::vector<const base_type *> pools;
std::vector<const base_type *> filter;
};
} // namespace entt

View File

@@ -0,0 +1,177 @@
#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_iterator = typename Type::basic_iterator;
template<typename Func>
void notify_destruction(basic_iterator first, basic_iterator last, Func func) {
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);
func(it, it + 1u);
}
}
void swap_and_pop(basic_iterator first, basic_iterator last) final {
notify_destruction(std::move(first), std::move(last), [this](auto... args) { Type::swap_and_pop(args...); });
}
void in_place_pop(basic_iterator first, basic_iterator last) final {
notify_destruction(std::move(first), std::move(last), [this](auto... args) { Type::in_place_pop(args...); });
}
basic_iterator try_emplace(const typename 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 Underlying entity identifier. */
using entity_type = typename Type::entity_type;
/*! @brief Inherited constructors. */
using Type::Type;
/**
* @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() ENTT_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() ENTT_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() ENTT_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 mixins, if any.
* @param value A variable wrapped in an opaque container.
*/
void bind(any value) ENTT_NOEXCEPT final {
auto *reg = any_cast<basic_registry<entity_type>>(&value);
owner = reg ? reg : owner;
Type::bind(std::move(value));
}
private:
sigh<void(basic_registry<entity_type> &, const entity_type)> construction{};
sigh<void(basic_registry<entity_type> &, const entity_type)> destruction{};
sigh<void(basic_registry<entity_type> &, const entity_type)> update{};
basic_registry<entity_type> *owner{};
};
} // namespace entt
#endif

View File

@@ -1,6 +1,7 @@
#ifndef ENTT_ENTITY_SNAPSHOT_HPP
#define ENTT_ENTITY_SNAPSHOT_HPP
#include <array>
#include <cstddef>
#include <iterator>
#include <tuple>
@@ -10,37 +11,13 @@
#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"
#include "registry.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.
*
@@ -49,123 +26,109 @@ void orphans(Registry &registry) {
* This type can be used in both cases if provided with a correctly configured
* output archive.
*
* @tparam Registry Basic registry type.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Registry>
template<typename Entity>
class basic_snapshot {
static_assert(!std::is_const_v<Registry>, "Non-const registry type required");
using traits_type = typename Registry::traits_type;
using entity_traits = entt_traits<Entity>;
template<typename Component, typename Archive, typename It>
void get(Archive &archive, std::size_t sz, It first, It last) const {
const auto view = reg->template view<std::add_const_t<Component>>();
archive(typename entity_traits::entity_type(sz));
while(first != last) {
const auto entt = *(first++);
if(reg->template all_of<Component>(entt)) {
std::apply(archive, std::tuple_cat(std::make_tuple(entt), view.get(entt)));
}
}
}
template<typename... Component, typename Archive, typename It, std::size_t... Index>
void component(Archive &archive, It first, It last, std::index_sequence<Index...>) const {
std::array<std::size_t, sizeof...(Index)> size{};
auto begin = first;
while(begin != last) {
const auto entt = *(begin++);
((reg->template all_of<Component>(entt) ? ++size[Index] : 0u), ...);
}
(get<Component>(archive, size[Index], first, last), ...);
}
public:
/*! Basic registry type. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */
using entity_type = typename registry_type::entity_type;
using entity_type = Entity;
/**
* @brief Constructs an instance that is bound to a given registry.
* @param source A valid reference to a registry.
*/
basic_snapshot(const registry_type &source) noexcept
basic_snapshot(const basic_registry<entity_type> &source) ENTT_NOEXCEPT
: reg{&source} {}
/*! @brief Default move constructor. */
basic_snapshot(basic_snapshot &&) noexcept = default;
basic_snapshot(basic_snapshot &&) ENTT_NOEXCEPT = default;
/*! @brief Default move assignment operator. @return This snapshot. */
basic_snapshot &operator=(basic_snapshot &&) noexcept = default;
basic_snapshot &operator=(basic_snapshot &&) ENTT_NOEXCEPT = default;
/**
* @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 Type, typename Archive>
const basic_snapshot &get(Archive &archive, const id_type id = type_hash<Type>::value()) const {
if(const auto *storage = reg->template storage<Type>(id); storage) {
archive(static_cast<typename traits_type::entity_type>(storage->size()));
if constexpr(std::is_same_v<Type, entity_type>) {
archive(static_cast<typename traits_type::entity_type>(storage->in_use()));
for(auto first = storage->data(), last = first + storage->size(); first != last; ++first) {
archive(*first);
}
} else {
for(auto elem: storage->reach()) {
std::apply([&archive](auto &&...args) { (archive(std::forward<decltype(args)>(args)), ...); }, elem);
}
}
} else {
archive(typename traits_type::entity_type{});
}
return *this;
}
/**
* @brief Serializes all elements of a type with associated identifiers for
* the entities in a range.
* @tparam Type Type of elements to serialize.
* @tparam Archive Type of output archive.
* @tparam It Type of input iterator.
* @param archive A valid reference to an output archive.
* @param first An iterator to the first element of the range to serialize.
* @param last An iterator past the last element of the range to serialize.
* @param id Optional name used to map the storage within the registry.
* @return An object of this type to continue creating the snapshot.
*/
template<typename Type, typename Archive, typename It>
const basic_snapshot &get(Archive &archive, It first, It last, const id_type id = type_hash<Type>::value()) const {
static_assert(!std::is_same_v<Type, entity_type>, "Entity types not supported");
if(const auto *storage = reg->template storage<Type>(id); storage && !storage->empty()) {
archive(static_cast<typename traits_type::entity_type>(std::distance(first, last)));
for(; first != last; ++first) {
if(const auto entt = *first; storage->contains(entt)) {
archive(entt);
std::apply([&archive](auto &&...args) { (archive(std::forward<decltype(args)>(args)), ...); }, storage->get_as_tuple(entt));
} else {
archive(static_cast<entity_type>(null));
}
}
} else {
archive(typename traits_type::entity_type{});
}
return *this;
}
/**
* @brief Serializes all identifiers, including those to be recycled.
* @brief Puts aside all the entities from the underlying registry.
*
* Entities are serialized along with their versions. Destroyed entities are
* taken in consideration as well by this function.
*
* @tparam Archive Type of output archive.
* @param archive A valid reference to an output archive.
* @return An object of this type to continue creating the snapshot.
*/
template<typename Archive>
[[deprecated("use .get<Entity>(archive) instead")]] const basic_snapshot &entities(Archive &archive) const {
return get<entity_type>(archive);
const basic_snapshot &entities(Archive &archive) const {
const auto sz = reg->size();
archive(typename entity_traits::entity_type(sz + 1u));
archive(reg->released());
for(auto first = reg->data(), last = first + sz; first != last; ++first) {
archive(*first);
}
return *this;
}
/**
* @brief Serializes all elements of a type with associated identifiers.
* @brief Puts aside the given components.
*
* Each instance is serialized together with the entity to which it belongs.
* Entities are serialized along with their versions.
*
* @tparam Component Types of components to serialize.
* @tparam 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>
[[deprecated("use .get<Type>(archive) instead")]] const basic_snapshot &component(Archive &archive) const {
return (get<Component>(archive), ...);
const basic_snapshot &component(Archive &archive) const {
if constexpr(sizeof...(Component) == 1u) {
const auto view = reg->template view<const Component...>();
(component<Component>(archive, view.rbegin(), view.rend()), ...);
return *this;
} else {
(component<Component>(archive), ...);
return *this;
}
}
/**
* @brief Serializes all elements of a type with associated identifiers for
* the entities in a range.
* @brief Puts aside the given components for the entities in a range.
*
* Each instance is serialized together with the entity to which it belongs.
* Entities are serialized along with their versions.
*
* @tparam Component Types of components to serialize.
* @tparam Archive Type of output archive.
* @tparam It Type of input iterator.
@@ -175,12 +138,13 @@ public:
* @return An object of this type to continue creating the snapshot.
*/
template<typename... Component, typename Archive, typename It>
[[deprecated("use .get<Type>(archive, first, last) instead")]] const basic_snapshot &component(Archive &archive, It first, It last) const {
return (get<Component>(archive, first, last), ...);
const basic_snapshot &component(Archive &archive, It first, It last) const {
component<Component...>(archive, first, last, std::index_sequence_for<Component...>{});
return *this;
}
private:
const registry_type *reg;
const basic_registry<entity_type> *reg;
};
/**
@@ -191,110 +155,101 @@ private:
* originally had.<br/>
* An example of use is the implementation of a save/restore utility.
*
* @tparam Registry Basic registry type.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Registry>
template<typename Entity>
class basic_snapshot_loader {
static_assert(!std::is_const_v<Registry>, "Non-const registry type required");
using traits_type = typename Registry::traits_type;
using entity_traits = entt_traits<Entity>;
template<typename Type, typename Archive>
void assign(Archive &archive) const {
typename entity_traits::entity_type length{};
entity_type entt;
archive(length);
if constexpr(ignore_as_empty_v<Type>) {
while(length--) {
archive(entt);
const auto entity = reg->valid(entt) ? entt : reg->create(entt);
ENTT_ASSERT(entity == entt, "Entity not available for use");
reg->template emplace<Type>(entt);
}
} else {
Type instance;
while(length--) {
archive(entt, instance);
const auto entity = reg->valid(entt) ? entt : reg->create(entt);
ENTT_ASSERT(entity == entt, "Entity not available for use");
reg->template emplace<Type>(entt, std::move(instance));
}
}
}
public:
/*! Basic registry type. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */
using entity_type = typename registry_type::entity_type;
using entity_type = Entity;
/**
* @brief Constructs an instance that is bound to a given registry.
* @param source A valid reference to a registry.
*/
basic_snapshot_loader(registry_type &source) noexcept
basic_snapshot_loader(basic_registry<entity_type> &source) ENTT_NOEXCEPT
: reg{&source} {
// restoring a snapshot as a whole requires a clean registry
ENTT_ASSERT(reg->empty(), "Registry must be empty");
}
/*! @brief Default move constructor. */
basic_snapshot_loader(basic_snapshot_loader &&) noexcept = default;
basic_snapshot_loader(basic_snapshot_loader &&) ENTT_NOEXCEPT = default;
/*! @brief Default move assignment operator. @return This loader. */
basic_snapshot_loader &operator=(basic_snapshot_loader &&) noexcept = default;
basic_snapshot_loader &operator=(basic_snapshot_loader &&) ENTT_NOEXCEPT = default;
/**
* @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 Type, typename Archive>
basic_snapshot_loader &get([[maybe_unused]] Archive &archive, const id_type id = type_hash<Type>::value()) {
auto &storage = reg->template storage<Type>(id);
typename traits_type::entity_type length{};
archive(length);
if constexpr(std::is_same_v<Type, entity_type>) {
typename traits_type::entity_type in_use{};
storage.reserve(length);
archive(in_use);
for(entity_type entity = null; length; --length) {
archive(entity);
storage.emplace(entity);
}
storage.in_use(in_use);
} else {
auto &other = reg->template storage<entity_type>();
entity_type entt{null};
while(length--) {
if(archive(entt); entt != null) {
const auto entity = other.contains(entt) ? entt : other.emplace(entt);
ENTT_ASSERT(entity == entt, "Entity not available for use");
if constexpr(Registry::template storage_for_type<Type>::traits_type::page_size == 0u) {
storage.emplace(entity);
} else {
Type elem{};
archive(elem);
storage.emplace(entity, std::move(elem));
}
}
}
}
return *this;
}
/**
* @brief Restores all identifiers, including those to be recycled.
* @brief Restores entities that were in use during serialization.
*
* This function restores the entities that were in use during serialization
* and gives them the versions they originally had.
*
* @tparam Archive Type of input archive.
* @param archive A valid reference to an input archive.
* @return A valid loader to continue restoring data.
*/
template<typename Archive>
[[deprecated("use .get<Entity>(archive) instead")]] basic_snapshot_loader &entities(Archive &archive) {
return get<entity_type>(archive);
const basic_snapshot_loader &entities(Archive &archive) const {
typename entity_traits::entity_type length{};
archive(length);
std::vector<entity_type> all(length);
for(std::size_t pos{}; pos < length; ++pos) {
archive(all[pos]);
}
reg->assign(++all.cbegin(), all.cend(), all[0u]);
return *this;
}
/**
* @brief Restores all elements of a type with associated identifiers.
* @brief Restores components and assigns them to the right entities.
*
* The template parameter list must be exactly the same used during
* serialization.
* serialization. In the event that the entity to which the component is
* assigned doesn't exist yet, the loader will take care to create it with
* the version it originally had.
*
* @tparam Component Type of component to restore.
* @tparam Component Types of components to restore.
* @tparam Archive Type of input archive.
* @param archive A valid reference to an input archive.
* @return A valid loader to continue restoring data.
*/
template<typename... Component, typename Archive>
[[deprecated("use .get<Type>(archive) instead")]] basic_snapshot_loader &component(Archive &archive) {
return (get<Component>(archive), ...);
const basic_snapshot_loader &component(Archive &archive) const {
(assign<Component>(archive), ...);
return *this;
}
/**
@@ -303,17 +258,22 @@ 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 function helps to identify and destroy those entities.
* This functions helps to identify and destroy those entities.
*
* @return A valid loader to continue restoring data.
*/
basic_snapshot_loader &orphans() {
internal::orphans(*reg);
const basic_snapshot_loader &orphans() const {
reg->each([this](const auto entt) {
if(reg->orphan(entt)) {
reg->release(entt);
}
});
return *this;
}
private:
registry_type *reg;
basic_registry<entity_type> *reg;
};
/**
@@ -326,24 +286,37 @@ private:
* Identifiers that entities originally had are not transferred to the target.
* Instead, the loader maps remote identifiers to local ones while restoring a
* snapshot.<br/>
* An example of use is the implementation of a client-server application with
* An example of use is the implementation of a client-server applications with
* the requirement of transferring somehow parts of the representation side to
* side.
*
* @tparam Registry Basic registry type.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Registry>
template<typename Entity>
class basic_continuous_loader {
static_assert(!std::is_const_v<Registry>, "Non-const registry type required");
using traits_type = typename Registry::traits_type;
using entity_traits = entt_traits<Entity>;
void restore(typename Registry::entity_type entt) {
if(const auto entity = to_entity(entt); remloc.contains(entity) && remloc[entity].first == entt) {
if(!reg->valid(remloc[entity].second)) {
remloc[entity].second = reg->create();
}
void destroy(Entity entt) {
if(const auto it = remloc.find(entt); it == remloc.cend()) {
const auto local = reg->create();
remloc.emplace(entt, std::make_pair(local, true));
reg->destroy(local);
}
}
void restore(Entity entt) {
const auto it = remloc.find(entt);
if(it == remloc.cend()) {
const auto local = reg->create();
remloc.emplace(entt, std::make_pair(local, true));
} else {
remloc.insert_or_assign(entity, std::make_pair(entt, reg->create()));
if(!reg->valid(remloc[entt].first)) {
remloc[entt].first = reg->create();
}
// set the dirty flag
remloc[entt].second = true;
}
}
@@ -380,9 +353,9 @@ class basic_continuous_loader {
}
}
template<typename Component, typename Other, typename Member>
void update([[maybe_unused]] Component &instance, [[maybe_unused]] Member Other::*member) {
if constexpr(!std::is_same_v<Component, Other>) {
template<typename Other, typename Type, typename Member>
void update([[maybe_unused]] Other &instance, [[maybe_unused]] Member Type::*member) {
if constexpr(!std::is_same_v<Other, Type>) {
return;
} else if constexpr(std::is_same_v<Member, entity_type>) {
instance.*member = map(instance.*member);
@@ -392,19 +365,52 @@ class basic_continuous_loader {
}
}
template<typename Component>
void remove_if_exists() {
for(auto &&ref: remloc) {
const auto local = ref.second.first;
if(reg->valid(local)) {
reg->template remove<Component>(local);
}
}
}
template<typename Other, typename Archive, typename... Type, typename... Member>
void assign(Archive &archive, [[maybe_unused]] Member Type::*...member) {
typename entity_traits::entity_type length{};
entity_type entt;
archive(length);
if constexpr(ignore_as_empty_v<Other>) {
while(length--) {
archive(entt);
restore(entt);
reg->template emplace_or_replace<Other>(map(entt));
}
} else {
Other instance;
while(length--) {
archive(entt, instance);
(update(instance, member), ...);
restore(entt);
reg->template emplace_or_replace<Other>(map(entt), std::move(instance));
}
}
}
public:
/*! Basic registry type. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */
using entity_type = typename registry_type::entity_type;
using entity_type = Entity;
/**
* @brief Constructs an instance that is bound to a given registry.
* @param source A valid reference to a registry.
*/
basic_continuous_loader(registry_type &source) noexcept
: remloc{source.get_allocator()},
reg{&source} {}
basic_continuous_loader(basic_registry<entity_type> &source) ENTT_NOEXCEPT
: reg{&source} {}
/*! @brief Default move constructor. */
basic_continuous_loader(basic_continuous_loader &&) = default;
@@ -413,66 +419,31 @@ public:
basic_continuous_loader &operator=(basic_continuous_loader &&) = default;
/**
* @brief Restores all elements of a type with associated identifiers.
* @brief Restores entities that were in use during serialization.
*
* It creates local counterparts for remote elements as needed.<br/>
* Members are either data members of type entity_type or containers of
* entities. In both cases, a loader visits them and replaces entities with
* their local counterpart.
* This function restores the entities that were in use during serialization
* and creates local counterparts for them if required.
*
* @tparam Type Type of elements to restore.
* @tparam Archive Type of input archive.
* @param archive A valid reference to an input archive.
* @param id Optional name used to map the storage within the registry.
* @return A valid loader to continue restoring data.
* @return A non-const reference to this loader.
*/
template<typename Type, typename Archive>
basic_continuous_loader &get([[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};
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);
if constexpr(std::is_same_v<Type, entity_type>) {
typename traits_type::entity_type in_use{};
for(std::size_t pos{}, last = length - 1u; pos < last; ++pos) {
archive(entt);
storage.reserve(length);
archive(in_use);
for(std::size_t pos{}; pos < in_use; ++pos) {
archive(entt);
if(const auto entity = entity_traits::to_entity(entt); entity == pos) {
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));
}
}
} else {
destroy(entt);
}
}
@@ -480,75 +451,58 @@ public:
}
/**
* @brief Restores all identifiers, including those to be recycled.
* @brief Restores components and assigns them to the right entities.
*
* 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>
[[deprecated("use .get<Entity>(archive) instead")]] basic_continuous_loader &entities(Archive &archive) {
return get<entity_type>(archive);
}
/**
* @brief Serializes all elements of a type with associated identifiers.
*
* It creates local counterparts for remote elements as needed.<br/>
* Members are either data members of type entity_type or containers of
* entities. In both cases, a loader visits them and replaces entities with
* their local counterpart.
* The template parameter list must be exactly the same used during
* serialization. In the event that the entity to which the component is
* assigned doesn't exist yet, the loader will take care to create a local
* counterpart for it.<br/>
* Members can be either data members of type entity_type or containers of
* entities. In both cases, the loader will visit them and update the
* entities by replacing each one with its local counterpart.
*
* @tparam Component Type of component to restore.
* @tparam Archive Type of input archive.
* @tparam Type Types of components to update with local counterparts.
* @tparam Member Types of members to update with their local counterparts.
* @param archive A valid reference to an input archive.
* @param member Members to update with their local counterparts.
* @return A non-const reference to this loader.
*/
template<typename... Component, typename Archive, typename... 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>()),
...);
template<typename... Component, typename Archive, typename... Type, typename... Member>
basic_continuous_loader &component(Archive &archive, Member Type::*...member) {
(remove_if_exists<Component>(), ...);
(assign<Component>(archive, member...), ...);
return *this;
}
/**
* @brief Helps to purge entities that no longer have a counterpart.
* @brief Helps to purge entities that no longer have a conterpart.
*
* Users should invoke this member function after restoring each snapshot,
* unless they know exactly what they are doing.
*
* @return A non-const reference to this loader.
*/
[[deprecated("use .get<Entity>(archive) instead")]] basic_continuous_loader &shrink() {
basic_continuous_loader &shrink() {
auto it = remloc.begin();
while(it != remloc.cend()) {
const auto local = it->second.first;
bool &dirty = it->second.second;
if(dirty) {
dirty = false;
++it;
} else {
if(reg->valid(local)) {
reg->destroy(local);
}
it = remloc.erase(it);
}
}
return *this;
}
@@ -558,12 +512,17 @@ 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 function helps to identify and destroy those entities.
* This functions helps to identify and destroy those entities.
*
* @return A non-const reference to this loader.
*/
basic_continuous_loader &orphans() {
internal::orphans(*reg);
reg->each([this](const auto entt) {
if(reg->orphan(entt)) {
reg->release(entt);
}
});
return *this;
}
@@ -572,9 +531,8 @@ public:
* @param entt A valid identifier.
* @return True if `entity` is managed by the loader, false otherwise.
*/
[[nodiscard]] bool contains(entity_type entt) const noexcept {
const auto it = remloc.find(to_entity(entt));
return it != remloc.cend() && it->second.first == entt;
[[nodiscard]] bool contains(entity_type entt) const ENTT_NOEXCEPT {
return (remloc.find(entt) != remloc.cend());
}
/**
@@ -582,17 +540,20 @@ public:
* @param entt A valid identifier.
* @return The local identifier if any, the null entity otherwise.
*/
[[nodiscard]] entity_type map(entity_type entt) const noexcept {
if(const auto it = remloc.find(to_entity(entt)); it != remloc.cend() && it->second.first == entt) {
return it->second.second;
[[nodiscard]] entity_type map(entity_type entt) const ENTT_NOEXCEPT {
const auto it = remloc.find(entt);
entity_type other = null;
if(it != remloc.cend()) {
other = it->second.first;
}
return null;
return other;
}
private:
dense_map<typename traits_type::entity_type, std::pair<entity_type, entity_type>> remloc;
registry_type *reg;
dense_map<entity_type, std::pair<entity_type, bool>> remloc;
basic_registry<entity_type> *reg;
};
} // namespace entt

View File

@@ -32,67 +32,63 @@ struct sparse_set_iterator final {
using difference_type = typename Container::difference_type;
using iterator_category = std::random_access_iterator_tag;
constexpr sparse_set_iterator() noexcept
sparse_set_iterator() ENTT_NOEXCEPT
: packed{},
offset{} {}
constexpr sparse_set_iterator(const Container &ref, const difference_type idx) noexcept
sparse_set_iterator(const Container &ref, const difference_type idx) ENTT_NOEXCEPT
: packed{std::addressof(ref)},
offset{idx} {}
constexpr sparse_set_iterator &operator++() noexcept {
sparse_set_iterator &operator++() ENTT_NOEXCEPT {
return --offset, *this;
}
constexpr sparse_set_iterator operator++(int) noexcept {
sparse_set_iterator operator++(int) ENTT_NOEXCEPT {
sparse_set_iterator orig = *this;
return ++(*this), orig;
}
constexpr sparse_set_iterator &operator--() noexcept {
sparse_set_iterator &operator--() ENTT_NOEXCEPT {
return ++offset, *this;
}
constexpr sparse_set_iterator operator--(int) noexcept {
sparse_set_iterator operator--(int) ENTT_NOEXCEPT {
sparse_set_iterator orig = *this;
return operator--(), orig;
}
constexpr sparse_set_iterator &operator+=(const difference_type value) noexcept {
sparse_set_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT {
offset -= value;
return *this;
}
constexpr sparse_set_iterator operator+(const difference_type value) const noexcept {
sparse_set_iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
sparse_set_iterator copy = *this;
return (copy += value);
}
constexpr sparse_set_iterator &operator-=(const difference_type value) noexcept {
sparse_set_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT {
return (*this += -value);
}
constexpr sparse_set_iterator operator-(const difference_type value) const noexcept {
sparse_set_iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
return (*this + -value);
}
[[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept {
[[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT {
return packed->data()[index() - value];
}
[[nodiscard]] constexpr pointer operator->() const noexcept {
[[nodiscard]] pointer operator->() const ENTT_NOEXCEPT {
return packed->data() + index();
}
[[nodiscard]] constexpr reference operator*() const noexcept {
[[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
return *operator->();
}
[[nodiscard]] constexpr pointer data() const noexcept {
return packed ? packed->data() : nullptr;
}
[[nodiscard]] constexpr difference_type index() const noexcept {
[[nodiscard]] difference_type index() const ENTT_NOEXCEPT {
return offset - 1;
}
@@ -101,38 +97,38 @@ private:
difference_type offset;
};
template<typename Container>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const sparse_set_iterator<Container> &lhs, const sparse_set_iterator<Container> &rhs) noexcept {
template<typename Type, typename Other>
[[nodiscard]] std::ptrdiff_t operator-(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) ENTT_NOEXCEPT {
return rhs.index() - lhs.index();
}
template<typename Container>
[[nodiscard]] constexpr bool operator==(const sparse_set_iterator<Container> &lhs, const sparse_set_iterator<Container> &rhs) noexcept {
template<typename Type, typename Other>
[[nodiscard]] bool operator==(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) ENTT_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 {
template<typename Type, typename Other>
[[nodiscard]] bool operator!=(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}
template<typename Container>
[[nodiscard]] constexpr bool operator<(const sparse_set_iterator<Container> &lhs, const sparse_set_iterator<Container> &rhs) noexcept {
template<typename Type, typename Other>
[[nodiscard]] bool operator<(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) ENTT_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]] bool operator>(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) ENTT_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 {
template<typename Type, typename Other>
[[nodiscard]] bool operator<=(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) ENTT_NOEXCEPT {
return !(lhs > rhs);
}
template<typename Container>
[[nodiscard]] constexpr bool operator>=(const sparse_set_iterator<Container> &lhs, const sparse_set_iterator<Container> &rhs) noexcept {
template<typename Type, typename Other>
[[nodiscard]] bool operator>=(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) ENTT_NOEXCEPT {
return !(lhs < rhs);
}
@@ -143,6 +139,14 @@ template<typename Container>
* @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.
*
@@ -150,6 +154,10 @@ template<typename Container>
* 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.
@@ -159,7 +167,7 @@ template<typename Container>
* 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.
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Entity, typename Allocator>
@@ -168,26 +176,23 @@ 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>(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;
const auto pos = static_cast<size_type>(entity_traits::to_entity(entt));
const auto page = pos / entity_traits::page_size;
return (page < sparse.size() && sparse[page]) ? (sparse[page] + fast_mod(pos, entity_traits::page_size)) : nullptr;
}
[[nodiscard]] auto &sparse_ref(const Entity entt) const {
ENTT_ASSERT(sparse_ptr(entt), "Invalid element");
const auto pos = static_cast<size_type>(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));
const auto pos = static_cast<size_type>(entity_traits::to_entity(entt));
return sparse[pos / entity_traits::page_size][fast_mod(pos, entity_traits::page_size)];
}
[[nodiscard]] auto &assure_at_least(const Entity entt) {
const auto pos = static_cast<size_type>(traits_type::to_entity(entt));
const auto page = pos / traits_type::page_size;
const auto pos = static_cast<size_type>(entity_traits::to_entity(entt));
const auto page = pos / entity_traits::page_size;
if(!(page < sparse.size())) {
sparse.resize(page + 1u, nullptr);
@@ -195,12 +200,12 @@ class basic_sparse_set {
if(!sparse[page]) {
auto page_allocator{packed.get_allocator()};
sparse[page] = alloc_traits::allocate(page_allocator, traits_type::page_size);
std::uninitialized_fill(sparse[page], sparse[page] + traits_type::page_size, null);
sparse[page] = alloc_traits::allocate(page_allocator, entity_traits::page_size);
std::uninitialized_fill(sparse[page], sparse[page] + entity_traits::page_size, null);
}
auto &elem = sparse[page][fast_mod(pos, traits_type::page_size)];
ENTT_ASSERT(elem == null, "Slot not available");
auto &elem = sparse[page][fast_mod(pos, entity_traits::page_size)];
ENTT_ASSERT(entity_traits::to_version(elem) == entity_traits::to_version(tombstone), "Slot not available");
return elem;
}
@@ -209,8 +214,8 @@ class basic_sparse_set {
for(auto &&page: sparse) {
if(page != nullptr) {
std::destroy(page, page + traits_type::page_size);
alloc_traits::deallocate(page_allocator, page, traits_type::page_size);
std::destroy(page, page + entity_traits::page_size);
alloc_traits::deallocate(page_allocator, page, entity_traits::page_size);
page = nullptr;
}
}
@@ -221,90 +226,42 @@ private:
return nullptr;
}
virtual void swap_or_move(const std::size_t, const std::size_t) {}
virtual void swap_at(const std::size_t, const std::size_t) {}
virtual void move_element(const std::size_t, const std::size_t) {}
protected:
/*! @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.
* @brief Erases entities from a sparse set.
* @param first An iterator to the first element of the range of entities.
* @param last An iterator past the last element of the range of entities.
*/
void 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]);
virtual void swap_and_pop(basic_iterator first, basic_iterator last) {
for(; first != last; ++first) {
sparse_ref(packed.back()) = entity_traits::combine(static_cast<typename entity_traits::entity_type>(first.index()), entity_traits::to_integral(packed.back()));
const auto entt = std::exchange(packed[first.index()], packed.back());
// unnecessary but it helps to detect nasty bugs
ENTT_ASSERT((packed.back() = tombstone, true), "");
// lazy self-assignment guard
sparse_ref(entt) = null;
packed.pop_back();
}
}
/**
* @brief Erases an entity from a sparse set.
* @param it An iterator to the element to pop.
*/
void swap_and_pop(const basic_iterator it) {
ENTT_ASSERT(mode == deletion_policy::swap_and_pop, "Deletion policy mismatched");
auto &self = sparse_ref(*it);
const auto entt = 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), "");
// lazy self-assignment guard
self = null;
packed.pop_back();
}
/**
* @brief Erases an entity from a sparse set.
* @param it An iterator to the element to pop.
*/
void in_place_pop(const basic_iterator it) {
ENTT_ASSERT(mode == deletion_policy::in_place, "Deletion policy mismatched");
const auto entt = 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:
/**
* @brief Erases entities from a sparse set.
* @param first An iterator to the first element of the range of entities.
* @param last An iterator past the last element of the range of entities.
*/
virtual void pop(basic_iterator first, basic_iterator last) {
if(mode == deletion_policy::swap_and_pop) {
for(; first != last; ++first) {
swap_and_pop(first);
}
} else {
for(; first != last; ++first) {
in_place_pop(first);
}
virtual void in_place_pop(basic_iterator first, basic_iterator last) {
for(; first != last; ++first) {
sparse_ref(*first) = null;
packed[first.index()] = std::exchange(free_list, entity_traits::combine(static_cast<typename entity_traits::entity_type>(first.index()), entity_traits::reserved));
}
}
/*! @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.
@@ -316,27 +273,25 @@ protected:
if(auto &elem = assure_at_least(entt); free_list == null || force_back) {
packed.push_back(entt);
elem = traits_type::combine(static_cast<typename traits_type::entity_type>(packed.size() - 1u), traits_type::to_integral(entt));
elem = entity_traits::combine(static_cast<typename entity_traits::entity_type>(packed.size() - 1u), entity_traits::to_integral(entt));
return begin();
} else {
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));
const auto pos = static_cast<size_type>(entity_traits::to_entity(free_list));
elem = entity_traits::combine(entity_traits::to_integral(free_list), entity_traits::to_integral(entt));
free_list = std::exchange(packed[pos], entt);
return --(end() - pos);
}
}
public:
/*! @brief Entity traits. */
using traits_type = entt_traits<Entity>;
/*! @brief Underlying entity identifier. */
using entity_type = typename traits_type::value_type;
/*! @brief Underlying version type. */
using version_type = typename traits_type::version_type;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Underlying entity identifier. */
using entity_type = Entity;
/*! @brief Underlying version type. */
using version_type = typename entity_traits::version_type;
/*! @brief Unsigned integer type. */
using size_type = typename packed_container_type::size_type;
/*! @brief Pointer type to contained entities. */
using pointer = typename packed_container_type::const_pointer;
/*! @brief Random access iterator type. */
@@ -346,7 +301,7 @@ public:
/*! @brief Reverse iterator type. */
using reverse_iterator = std::reverse_iterator<iterator>;
/*! @brief Constant reverse iterator type. */
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
using const_reverse_iterator = reverse_iterator;
/*! @brief Default constructor. */
basic_sparse_set()
@@ -370,14 +325,14 @@ public:
/**
* @brief Constructs an empty container with the given value type, policy
* and allocator.
* @param elem Returned value type, if any.
* @param value Returned value type, if any.
* @param pol Type of deletion policy.
* @param allocator The allocator to use (possibly default-constructed).
*/
explicit basic_sparse_set(const type_info &elem, deletion_policy pol = deletion_policy::swap_and_pop, const allocator_type &allocator = {})
explicit basic_sparse_set(const type_info &value, deletion_policy pol = deletion_policy::swap_and_pop, const allocator_type &allocator = {})
: sparse{allocator},
packed{allocator},
info{&elem},
info{&value},
free_list{tombstone},
mode{pol} {}
@@ -385,7 +340,7 @@ public:
* @brief Move constructor.
* @param other The instance to move from.
*/
basic_sparse_set(basic_sparse_set &&other) noexcept
basic_sparse_set(basic_sparse_set &&other) ENTT_NOEXCEPT
: sparse{std::move(other.sparse)},
packed{std::move(other.packed)},
info{other.info},
@@ -397,7 +352,7 @@ public:
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
basic_sparse_set(basic_sparse_set &&other, const allocator_type &allocator) noexcept
basic_sparse_set(basic_sparse_set &&other, const allocator_type &allocator) ENTT_NOEXCEPT
: sparse{std::move(other.sparse), allocator},
packed{std::move(other.packed), allocator},
info{other.info},
@@ -416,7 +371,7 @@ public:
* @param other The instance to move from.
* @return This sparse set.
*/
basic_sparse_set &operator=(basic_sparse_set &&other) noexcept {
basic_sparse_set &operator=(basic_sparse_set &&other) ENTT_NOEXCEPT {
ENTT_ASSERT(alloc_traits::is_always_equal::value || packed.get_allocator() == other.packed.get_allocator(), "Copying a sparse set is not allowed");
release_sparse_pages();
@@ -445,7 +400,7 @@ public:
* @brief Returns the associated allocator.
* @return The associated allocator.
*/
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
[[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT {
return packed.get_allocator();
}
@@ -453,7 +408,7 @@ public:
* @brief Returns the deletion policy of a sparse set.
* @return The deletion policy of the sparse set.
*/
[[nodiscard]] deletion_policy policy() const noexcept {
[[nodiscard]] deletion_policy policy() const ENTT_NOEXCEPT {
return mode;
}
@@ -474,7 +429,7 @@ public:
* allocated space for.
* @return Capacity of the sparse set.
*/
[[nodiscard]] virtual size_type capacity() const noexcept {
[[nodiscard]] virtual size_type capacity() const ENTT_NOEXCEPT {
return packed.capacity();
}
@@ -493,8 +448,8 @@ public:
*
* @return Extent of the sparse set.
*/
[[nodiscard]] size_type extent() const noexcept {
return sparse.size() * traits_type::page_size;
[[nodiscard]] size_type extent() const ENTT_NOEXCEPT {
return sparse.size() * entity_traits::page_size;
}
/**
@@ -507,7 +462,7 @@ public:
*
* @return Number of elements.
*/
[[nodiscard]] size_type size() const noexcept {
[[nodiscard]] size_type size() const ENTT_NOEXCEPT {
return packed.size();
}
@@ -515,87 +470,91 @@ public:
* @brief Checks whether a sparse set is empty.
* @return True if the sparse set is empty, false otherwise.
*/
[[nodiscard]] bool empty() const noexcept {
[[nodiscard]] bool empty() const ENTT_NOEXCEPT {
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.
*/
[[nodiscard]] pointer data() const noexcept {
[[nodiscard]] pointer data() const ENTT_NOEXCEPT {
return packed.data();
}
/**
* @brief Returns an iterator to the beginning.
*
* If the sparse set is empty, the returned iterator will be equal to
* The returned iterator points to the first entity of the internal packed
* array. If the sparse set is empty, the returned iterator will be equal to
* `end()`.
*
* @return An iterator to the first entity of the sparse set.
*/
[[nodiscard]] const_iterator begin() const noexcept {
[[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT {
const auto pos = static_cast<typename iterator::difference_type>(packed.size());
return iterator{packed, pos};
}
/*! @copydoc begin */
[[nodiscard]] const_iterator cbegin() const noexcept {
[[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT {
return begin();
}
/**
* @brief Returns an iterator to the end.
*
* The returned iterator points to the element following the last entity in
* a sparse set. Attempting to dereference the returned iterator results in
* undefined behavior.
*
* @return An iterator to the element following the last entity of a sparse
* set.
*/
[[nodiscard]] iterator end() const noexcept {
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
return iterator{packed, {}};
}
/*! @copydoc end */
[[nodiscard]] const_iterator cend() const noexcept {
[[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT {
return end();
}
/**
* @brief Returns a reverse iterator to the beginning.
*
* If the sparse set is empty, the returned iterator will be equal to
* `rend()`.
* The returned iterator points to the first entity of the reversed internal
* packed array. If the sparse set is empty, the returned iterator will be
* equal to `rend()`.
*
* @return An iterator to the first entity of the reversed internal packed
* array.
*/
[[nodiscard]] const_reverse_iterator rbegin() const noexcept {
[[nodiscard]] const_reverse_iterator rbegin() const ENTT_NOEXCEPT {
return std::make_reverse_iterator(end());
}
/*! @copydoc rbegin */
[[nodiscard]] const_reverse_iterator crbegin() const noexcept {
[[nodiscard]] const_reverse_iterator crbegin() const ENTT_NOEXCEPT {
return rbegin();
}
/**
* @brief Returns a reverse iterator to the end.
*
* The returned iterator points to the element following the last entity in
* the reversed sparse set. Attempting to dereference the returned iterator
* results in undefined behavior.
*
* @return An iterator to the element following the last entity of the
* reversed sparse set.
*/
[[nodiscard]] reverse_iterator rend() const noexcept {
[[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
return std::make_reverse_iterator(begin());
}
/*! @copydoc rend */
[[nodiscard]] const_reverse_iterator crend() const noexcept {
[[nodiscard]] const_reverse_iterator crend() const ENTT_NOEXCEPT {
return rend();
}
@@ -605,8 +564,8 @@ public:
* @return An iterator to the given entity if it's found, past the end
* iterator otherwise.
*/
[[nodiscard]] iterator find(const entity_type entt) const noexcept {
return contains(entt) ? to_iterator(entt) : end();
[[nodiscard]] iterator find(const entity_type entt) const ENTT_NOEXCEPT {
return contains(entt) ? --(end() - index(entt)) : end();
}
/**
@@ -614,11 +573,11 @@ public:
* @param entt A valid identifier.
* @return True if the sparse set contains the entity, false otherwise.
*/
[[nodiscard]] bool contains(const entity_type entt) const noexcept {
[[nodiscard]] bool contains(const entity_type entt) const ENTT_NOEXCEPT {
const auto elem = sparse_ptr(entt);
constexpr auto cap = traits_type::to_entity(null);
constexpr auto cap = entity_traits::to_entity(null);
// testing versions permits to avoid accessing the packed array
return elem && (((~cap & traits_type::to_integral(entt)) ^ traits_type::to_integral(*elem)) < cap);
return elem && (((~cap & entity_traits::to_integral(entt)) ^ entity_traits::to_integral(*elem)) < cap);
}
/**
@@ -627,10 +586,10 @@ public:
* @return The version for the given identifier if present, the tombstone
* version otherwise.
*/
[[nodiscard]] version_type current(const entity_type entt) const noexcept {
[[nodiscard]] version_type current(const entity_type entt) const ENTT_NOEXCEPT {
const auto elem = sparse_ptr(entt);
constexpr auto fallback = traits_type::to_version(tombstone);
return elem ? traits_type::to_version(*elem) : fallback;
constexpr auto fallback = entity_traits::to_version(tombstone);
return elem ? entity_traits::to_version(*elem) : fallback;
}
/**
@@ -643,9 +602,9 @@ public:
* @param entt A valid identifier.
* @return The position of the entity in the sparse set.
*/
[[nodiscard]] size_type index(const entity_type entt) const noexcept {
[[nodiscard]] size_type index(const entity_type entt) const ENTT_NOEXCEPT {
ENTT_ASSERT(contains(entt), "Set does not contain entity");
return static_cast<size_type>(traits_type::to_entity(sparse_ref(entt)));
return static_cast<size_type>(entity_traits::to_entity(sparse_ref(entt)));
}
/**
@@ -653,7 +612,7 @@ public:
* @param pos The position for which to return the entity.
* @return The entity at specified location if any, a null entity otherwise.
*/
[[nodiscard]] entity_type at(const size_type pos) const noexcept {
[[nodiscard]] entity_type at(const size_type pos) const ENTT_NOEXCEPT {
return pos < packed.size() ? packed[pos] : null;
}
@@ -662,7 +621,7 @@ public:
* @param pos The position for which to return the entity.
* @return The entity at specified location.
*/
[[nodiscard]] entity_type operator[](const size_type pos) const noexcept {
[[nodiscard]] entity_type operator[](const size_type pos) const ENTT_NOEXCEPT {
ENTT_ASSERT(pos < packed.size(), "Position is out of bounds");
return packed[pos];
}
@@ -677,13 +636,13 @@ public:
* @param entt A valid identifier.
* @return An opaque pointer to the element assigned to the entity, if any.
*/
[[nodiscard]] const void *value(const entity_type entt) const noexcept {
const void *get(const entity_type entt) const ENTT_NOEXCEPT {
return get_at(index(entt));
}
/*! @copydoc value */
[[nodiscard]] void *value(const entity_type entt) noexcept {
return const_cast<void *>(std::as_const(*this).value(entt));
/*! @copydoc get */
void *get(const entity_type entt) ENTT_NOEXCEPT {
return const_cast<void *>(std::as_const(*this).get(entt));
}
/**
@@ -694,12 +653,27 @@ public:
* results in undefined behavior.
*
* @param entt A valid identifier.
* @param elem Optional opaque element to forward to mixins, if any.
* @param value Optional opaque value to forward to mixins, if any.
* @return Iterator pointing to the emplaced element in case of success, the
* `end()` iterator otherwise.
*/
iterator push(const entity_type entt, const void *elem = nullptr) {
return try_emplace(entt, false, elem);
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);
entity = entity_traits::combine(entity_traits::to_integral(entity), entity_traits::to_integral(entt));
packed[static_cast<size_type>(entity_traits::to_entity(entity))] = entt;
}
/**
@@ -716,7 +690,7 @@ public:
* success, the `end()` iterator otherwise.
*/
template<typename It>
iterator push(It first, It last) {
iterator insert(It first, It last) {
for(auto it = first; it != last; ++it) {
try_emplace(*it, true);
}
@@ -724,24 +698,6 @@ 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.
*
@@ -752,8 +708,8 @@ public:
* @param entt A valid identifier.
*/
void erase(const entity_type entt) {
const auto it = to_iterator(entt);
pop(it, it + 1u);
const auto it = --(end() - index(entt));
(mode == deletion_policy::in_place) ? in_place_pop(it, it + 1u) : swap_and_pop(it, it + 1u);
}
/**
@@ -768,7 +724,7 @@ public:
template<typename It>
void erase(It first, It last) {
if constexpr(std::is_same_v<It, basic_iterator>) {
pop(first, last);
(mode == deletion_policy::in_place) ? in_place_pop(first, last) : swap_and_pop(first, last);
} else {
for(; first != last; ++first) {
erase(*first);
@@ -796,45 +752,29 @@ 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);
}
for(; first != last; ++first) {
count += remove(*first);
}
return count;
}
/*! @brief Removes all tombstones from a sparse set. */
/*! @brief Removes all tombstones from the packed array of a sparse set. */
void compact() {
size_type from = packed.size();
for(; from && packed[from - 1u] == tombstone; --from) {}
for(auto *it = &free_list; *it != null && from; it = std::addressof(packed[traits_type::to_entity(*it)])) {
if(const size_type to = traits_type::to_entity(*it); to < from) {
for(auto *it = &free_list; *it != null && from; it = std::addressof(packed[entity_traits::to_entity(*it)])) {
if(const size_type to = entity_traits::to_entity(*it); to < from) {
--from;
swap_or_move(from, to);
move_element(from, 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]));
using std::swap;
swap(packed[from], packed[to]);
*it = traits_type::combine(static_cast<typename traits_type::entity_type>(from), tombstone);
const auto entity = static_cast<typename entity_traits::entity_type>(to);
sparse_ref(packed[to]) = entity_traits::combine(entity, entity_traits::to_integral(packed[to]));
*it = entity_traits::combine(static_cast<typename entity_traits::entity_type>(from), entity_traits::reserved);
for(; from && packed[from - 1u] == tombstone; --from) {}
}
}
@@ -857,12 +797,21 @@ public:
* @param rhs A valid identifier.
*/
void swap_elements(const entity_type lhs, const entity_type rhs) {
const auto from = index(lhs);
const auto to = index(rhs);
ENTT_ASSERT(contains(lhs) && contains(rhs), "Set does not contain entities");
// basic no-leak guarantee if swapping throws
swap_or_move(from, to);
swap_at(from, to);
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]);
}
/**
@@ -910,9 +859,9 @@ public:
const auto idx = index(packed[next]);
const auto entt = 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]));
swap_at(next, idx);
const auto entity = static_cast<typename entity_traits::entity_type>(curr);
sparse_ref(entt) = entity_traits::combine(entity, entity_traits::to_integral(packed[curr]));
curr = std::exchange(next, idx);
}
}
@@ -940,35 +889,48 @@ 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`.<br/>
* All the other entities goes to the end of the list and there are no
* guarantees on their order.
* according to the order they have in `other`. All the other entities goes
* to the end of the list and there are no guarantees on their order.<br/>
* In other terms, this function can be used to impose the same order on two
* sets by using one of them as a master and the other one as a slave.
*
* Iterating the sparse set with a couple of iterators returns elements in
* the expected order after a call to `respect`. See `begin` and `end` for
* more details.
*
* @param other The sparse sets that imposes the order of the entities.
*/
void sort_as(const basic_sparse_set &other) {
void respect(const basic_sparse_set &other) {
compact();
const auto to = other.end();
auto from = other.begin();
for(auto it = begin(); it.index() && from != to; ++from) {
if(const auto curr = *from; contains(curr)) {
if(const auto entt = *it; entt != curr) {
for(size_type pos = packed.size() - 1; pos && from != to; ++from) {
if(contains(*from)) {
if(*from != packed[pos]) {
// basic no-leak guarantee (with invalid state) if swapping throws
swap_elements(entt, curr);
swap_elements(packed[pos], *from);
}
++it;
--pos;
}
}
}
/*! @brief Clears a sparse set. */
void clear() {
pop_all();
// sanity check to avoid subtle issues due to storage classes
ENTT_ASSERT((compact(), size()) == 0u, "Non-empty set");
if(const auto last = end(); free_list == null) {
in_place_pop(begin(), last);
} else {
for(auto &&entity: *this) {
// tombstone filter on itself
if(const auto it = find(entity); it != last) {
in_place_pop(it, it + 1u);
}
}
}
free_list = tombstone;
packed.clear();
}
@@ -977,12 +939,12 @@ public:
* @brief Returned value type, if any.
* @return Returned value type, if any.
*/
const type_info &type() const noexcept {
const type_info &type() const ENTT_NOEXCEPT {
return *info;
}
/*! @brief Forwards variables to derived classes, if any. */
virtual void bind(any) noexcept {}
/*! @brief Forwards variables to mixins, if any. */
virtual void bind(any) ENTT_NOEXCEPT {}
private:
sparse_container_type sparse;

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,3 @@
// IWYU pragma: begin_exports
#include "config/config.h"
#include "config/macro.h"
#include "config/version.h"
@@ -24,22 +23,20 @@
#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"
#include "entity/runtime_view.hpp"
#include "entity/sigh_storage_mixin.hpp"
#include "entity/snapshot.hpp"
#include "entity/sparse_set.hpp"
#include "entity/storage.hpp"
#include "entity/utility.hpp"
#include "entity/view.hpp"
#include "graph/adjacency_matrix.hpp"
#include "graph/dot.hpp"
#include "graph/flow.hpp"
#include "locator/locator.hpp"
#include "meta/adl_pointer.hpp"
#include "meta/container.hpp"
#include "meta/context.hpp"
#include "meta/ctx.hpp"
#include "meta/factory.hpp"
#include "meta/meta.hpp"
#include "meta/node.hpp"
@@ -61,4 +58,3 @@
#include "signal/dispatcher.hpp"
#include "signal/emitter.hpp"
#include "signal/sigh.hpp"
// IWYU pragma: end_exports

View File

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

View File

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

View File

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

View File

@@ -1,341 +0,0 @@
#ifndef ENTT_GRAPH_FLOW_HPP
#define ENTT_GRAPH_FLOW_HPP
#include <algorithm>
#include <cstddef>
#include <functional>
#include <iterator>
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>
#include "../config/config.h"
#include "../container/dense_map.hpp"
#include "../container/dense_set.hpp"
#include "../core/compressed_pair.hpp"
#include "../core/fwd.hpp"
#include "../core/iterator.hpp"
#include "../core/utility.hpp"
#include "adjacency_matrix.hpp"
#include "fwd.hpp"
namespace entt {
/**
* @brief Utility class for creating task graphs.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Allocator>
class basic_flow {
using alloc_traits = std::allocator_traits<Allocator>;
static_assert(std::is_same_v<typename alloc_traits::value_type, id_type>, "Invalid value type");
using task_container_type = dense_set<id_type, identity, std::equal_to<id_type>, typename alloc_traits::template rebind_alloc<id_type>>;
using ro_rw_container_type = std::vector<std::pair<std::size_t, bool>, typename alloc_traits::template rebind_alloc<std::pair<std::size_t, bool>>>;
using 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");
if(!deps.contains(res) && sync_on != vertices.size()) {
deps[res].emplace_back(sync_on, true);
}
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;
/*! @brief Unsigned integer type. */
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()
: basic_flow{allocator_type{}} {}
/**
* @brief Constructs a flow builder with a given allocator.
* @param allocator The allocator to use.
*/
explicit basic_flow(const allocator_type &allocator)
: index{0u, allocator},
vertices{},
deps{},
sync_on{} {}
/*! @brief Default copy constructor. */
basic_flow(const basic_flow &) = default;
/**
* @brief Allocator-extended copy constructor.
* @param other The instance to copy from.
* @param allocator The allocator to use.
*/
basic_flow(const basic_flow &other, const allocator_type &allocator)
: index{other.index.first(), allocator},
vertices{other.vertices, allocator},
deps{other.deps, allocator},
sync_on{other.sync_on} {}
/*! @brief Default move constructor. */
basic_flow(basic_flow &&) noexcept = default;
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
basic_flow(basic_flow &&other, const allocator_type &allocator)
: index{other.index.first(), allocator},
vertices{std::move(other.vertices), allocator},
deps{std::move(other.deps), allocator},
sync_on{other.sync_on} {}
/**
* @brief Default copy assignment operator.
* @return This flow builder.
*/
basic_flow &operator=(const basic_flow &) = default;
/**
* @brief Default move assignment operator.
* @return This flow builder.
*/
basic_flow &operator=(basic_flow &&) noexcept = default;
/**
* @brief Returns the associated allocator.
* @return The associated allocator.
*/
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
return allocator_type{index.second()};
}
/**
* @brief Returns the identifier at specified location.
* @param pos Position of the identifier to return.
* @return The requested identifier.
*/
[[nodiscard]] id_type operator[](const size_type pos) const {
return vertices.cbegin()[pos];
}
/*! @brief Clears the flow builder. */
void clear() noexcept {
index.first() = {};
vertices.clear();
deps.clear();
sync_on = {};
}
/**
* @brief Exchanges the contents with those of a given flow builder.
* @param other Flow builder to exchange the content with.
*/
void swap(basic_flow &other) {
using std::swap;
std::swap(index, other.index);
std::swap(vertices, other.vertices);
std::swap(deps, other.deps);
std::swap(sync_on, other.sync_on);
}
/**
* @brief Returns the number of tasks.
* @return The number of tasks.
*/
[[nodiscard]] size_type size() const noexcept {
return vertices.size();
}
/**
* @brief Binds a task to a flow builder.
* @param value Task identifier.
* @return This flow builder.
*/
basic_flow &bind(const id_type value) {
sync_on += (sync_on == vertices.size());
const auto it = vertices.emplace(value).first;
index.first() = size_type(it - vertices.begin());
return *this;
}
/**
* @brief Turns the current task into a sync point.
* @return This flow builder.
*/
basic_flow &sync() {
ENTT_ASSERT(index.first() < vertices.size(), "Invalid node");
sync_on = index.first();
for(const auto &elem: deps) {
elem.second.emplace_back(sync_on, true);
}
return *this;
}
/**
* @brief Assigns a resource to the current task with a given access mode.
* @param res Resource identifier.
* @param is_rw Access mode.
* @return This flow builder.
*/
basic_flow &set(const id_type res, bool is_rw = false) {
emplace(res, is_rw);
return *this;
}
/**
* @brief Assigns a read-only resource to the current task.
* @param res Resource identifier.
* @return This flow builder.
*/
basic_flow &ro(const id_type res) {
emplace(res, false);
return *this;
}
/**
* @brief Assigns a range of read-only resources to the current task.
* @tparam It Type of input iterator.
* @param first An iterator to the first element of the range of elements.
* @param last An iterator past the last element of the range of elements.
* @return This flow builder.
*/
template<typename It>
std::enable_if_t<std::is_same_v<std::remove_const_t<typename std::iterator_traits<It>::value_type>, id_type>, basic_flow &>
ro(It first, It last) {
for(; first != last; ++first) {
emplace(*first, false);
}
return *this;
}
/**
* @brief Assigns a writable resource to the current task.
* @param res Resource identifier.
* @return This flow builder.
*/
basic_flow &rw(const id_type res) {
emplace(res, true);
return *this;
}
/**
* @brief Assigns a range of writable resources to the current task.
* @tparam It Type of input iterator.
* @param first An iterator to the first element of the range of elements.
* @param last An iterator past the last element of the range of elements.
* @return This flow builder.
*/
template<typename It>
std::enable_if_t<std::is_same_v<std::remove_const_t<typename std::iterator_traits<It>::value_type>, id_type>, basic_flow &>
rw(It first, It last) {
for(; first != last; ++first) {
emplace(*first, true);
}
return *this;
}
/**
* @brief Generates a task graph for the current content.
* @return The adjacency matrix of the task graph.
*/
[[nodiscard]] graph_type graph() const {
graph_type matrix{vertices.size(), get_allocator()};
setup_graph(matrix);
transitive_closure(matrix);
transitive_reduction(matrix);
return matrix;
}
private:
compressed_pair<size_type, allocator_type> index;
task_container_type vertices;
deps_container_type deps;
size_type sync_on;
};
} // namespace entt
#endif

View File

@@ -1,27 +0,0 @@
#ifndef ENTT_GRAPH_FWD_HPP
#define ENTT_GRAPH_FWD_HPP
#include <cstddef>
#include <memory>
#include "../core/fwd.hpp"
namespace entt {
/*! @brief Undirected graph category tag. */
struct directed_tag {};
/*! @brief Directed graph category tag. */
struct undirected_tag: directed_tag {};
template<typename, typename = std::allocator<std::size_t>>
class adjacency_matrix;
template<typename = std::allocator<id_type>>
class basic_flow;
/*! @brief Alias declaration for the most common use case. */
using flow = basic_flow<>;
} // namespace entt
#endif

View File

@@ -24,17 +24,9 @@ namespace entt {
* @tparam Service Service type.
*/
template<typename Service>
class locator final {
class service_handle {
friend class locator<Service>;
std::shared_ptr<Service> value{};
};
public:
struct locator final {
/*! @brief Service type. */
using type = Service;
/*! @brief Service node type. */
using node_type = service_handle;
/*! @brief Default constructor, deleted on purpose. */
locator() = delete;
@@ -45,7 +37,7 @@ public:
* @brief Checks whether a service locator contains a value.
* @return True if the service locator contains a value, false otherwise.
*/
[[nodiscard]] static bool has_value() noexcept {
[[nodiscard]] static bool has_value() ENTT_NOEXCEPT {
return (service != nullptr);
}
@@ -58,7 +50,7 @@ public:
*
* @return A reference to the service currently set, if any.
*/
[[nodiscard]] static Service &value() noexcept {
[[nodiscard]] static Service &value() ENTT_NOEXCEPT {
ENTT_ASSERT(has_value(), "Service not available");
return *service;
}
@@ -70,76 +62,51 @@ public:
* cases, they are discarded.
*
* @tparam Args Types of arguments to use to construct the fallback service.
* @tparam Type Fallback service type.
* @tparam Impl Fallback service type.
* @param args Parameters to use to construct the fallback service.
* @return A reference to a valid service.
*/
template<typename Type = Service, typename... Args>
template<typename Impl = Service, typename... Args>
[[nodiscard]] static Service &value_or(Args &&...args) {
return service ? *service : emplace<Type>(std::forward<Args>(args)...);
return service ? *service : emplace<Impl>(std::forward<Args>(args)...);
}
/**
* @brief Sets or replaces a service.
* @tparam Type Service type.
* @tparam Impl Service type.
* @tparam Args Types of arguments to use to construct the service.
* @param args Parameters to use to construct the service.
* @return A reference to a valid service.
*/
template<typename Type = Service, typename... Args>
template<typename Impl = Service, typename... Args>
static Service &emplace(Args &&...args) {
service = std::make_shared<Type>(std::forward<Args>(args)...);
service = std::make_shared<Impl>(std::forward<Args>(args)...);
return *service;
}
/**
* @brief Sets or replaces a service using a given allocator.
* @tparam Type Service type.
* @tparam Impl Service type.
* @tparam Allocator Type of allocator used to manage memory and elements.
* @tparam 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 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)...);
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)...);
return *service;
}
/**
* @brief Returns a handle to the underlying service.
* @return A handle to the underlying service.
*/
static node_type handle() noexcept {
node_type node{};
node.value = service;
return node;
}
/**
* @brief Resets or replaces a service.
* @param other Optional handle with which to replace the service.
*/
static void reset(const node_type &other = {}) noexcept {
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)};
/*! @brief Resets a service. */
static void reset() ENTT_NOEXCEPT {
service.reset();
}
private:
// std::shared_ptr because of its type erased allocator which is useful here
inline static std::shared_ptr<Service> service{};
// std::shared_ptr because of its type erased allocator which is pretty useful here
inline static std::shared_ptr<Service> service = nullptr;
};
} // namespace entt

View File

@@ -2,9 +2,6 @@
#define ENTT_META_CONTAINER_HPP
#include <array>
#include <deque>
#include <iterator>
#include <list>
#include <map>
#include <set>
#include <type_traits>
@@ -13,7 +10,6 @@
#include <vector>
#include "../container/dense_map.hpp"
#include "../container/dense_set.hpp"
#include "context.hpp"
#include "meta.hpp"
#include "type_traits.hpp"
@@ -30,7 +26,7 @@ template<typename, typename = void>
struct is_dynamic_sequence_container: std::false_type {};
template<typename Type>
struct is_dynamic_sequence_container<Type, std::void_t<decltype(&Type::clear)>>: std::true_type {};
struct is_dynamic_sequence_container<Type, std::void_t<decltype(&Type::reserve)>>: std::true_type {};
template<typename, typename = void>
struct is_key_only_meta_associative_container: std::true_type {};
@@ -43,7 +39,7 @@ struct basic_meta_sequence_container_traits {
using iterator = meta_sequence_container::iterator;
using size_type = std::size_t;
[[nodiscard]] static size_type size(const any &container) noexcept {
[[nodiscard]] static size_type size(const any &container) ENTT_NOEXCEPT {
return any_cast<const Type &>(container).size();
}
@@ -58,39 +54,39 @@ struct basic_meta_sequence_container_traits {
return false;
}
[[nodiscard]] static iterator iter(const meta_ctx &ctx, any &container, const bool as_end) {
[[nodiscard]] static iterator iter(any &container, const bool as_end) {
if(auto *const cont = any_cast<Type>(&container); cont) {
return iterator{ctx, as_end ? cont->end() : cont->begin()};
return iterator{*cont, static_cast<typename iterator::difference_type>(as_end * cont->size())};
}
const Type &as_const = any_cast<const Type &>(container);
return iterator{ctx, as_end ? as_const.end() : as_const.begin()};
return iterator{as_const, static_cast<typename iterator::difference_type>(as_end * as_const.size())};
}
[[nodiscard]] static iterator insert_or_erase([[maybe_unused]] const meta_ctx &ctx, [[maybe_unused]] any &container, [[maybe_unused]] const any &handle, [[maybe_unused]] meta_any &value) {
[[nodiscard]] static iterator insert([[maybe_unused]] any &container, [[maybe_unused]] const std::ptrdiff_t offset, [[maybe_unused]] meta_any &value) {
if constexpr(is_dynamic_sequence_container<Type>::value) {
if(auto *const cont = any_cast<Type>(&container); cont) {
typename Type::const_iterator it{};
if(auto *non_const = any_cast<typename Type::iterator>(&handle); non_const) {
it = *non_const;
} else {
it = any_cast<const typename Type::const_iterator &>(handle);
}
if(value) {
// this abomination is necessary because only on macos value_type and const_reference are different types for std::vector<bool>
if(value.allow_cast<typename Type::const_reference>() || value.allow_cast<typename Type::value_type>()) {
const auto *element = value.try_cast<std::remove_reference_t<typename Type::const_reference>>();
return iterator{ctx, cont->insert(it, element ? *element : value.cast<typename Type::value_type>())};
}
} else {
return iterator{ctx, cont->erase(it)};
// this abomination is necessary because only on macos value_type and const_reference are different types for std::vector<bool>
if(value.allow_cast<typename Type::const_reference>() || value.allow_cast<typename Type::value_type>()) {
const auto *element = value.try_cast<std::remove_reference_t<typename Type::const_reference>>();
const auto curr = cont->insert(cont->begin() + offset, element ? *element : value.cast<typename Type::value_type>());
return iterator{*cont, curr - cont->begin()};
}
}
}
return iterator{};
return {};
}
[[nodiscard]] static iterator erase([[maybe_unused]] any &container, [[maybe_unused]] const std::ptrdiff_t offset) {
if constexpr(is_dynamic_sequence_container<Type>::value) {
if(auto *const cont = any_cast<Type>(&container); cont) {
const auto curr = cont->erase(cont->begin() + offset);
return iterator{*cont, curr - cont->begin()};
}
}
return {};
}
};
@@ -101,7 +97,7 @@ struct basic_meta_associative_container_traits {
static constexpr auto key_only = is_key_only_meta_associative_container<Type>::value;
[[nodiscard]] static size_type size(const any &container) noexcept {
[[nodiscard]] static size_type size(const any &container) ENTT_NOEXCEPT {
return any_cast<const Type &>(container).size();
}
@@ -114,41 +110,43 @@ struct basic_meta_associative_container_traits {
return false;
}
[[nodiscard]] static iterator iter(const meta_ctx &ctx, any &container, const bool as_end) {
[[nodiscard]] static iterator iter(any &container, const bool as_end) {
if(auto *const cont = any_cast<Type>(&container); cont) {
return iterator{ctx, std::bool_constant<key_only>{}, as_end ? cont->end() : cont->begin()};
return iterator{std::integral_constant<bool, key_only>{}, as_end ? cont->end() : cont->begin()};
}
const auto &as_const = any_cast<const Type &>(container);
return iterator{ctx, std::bool_constant<key_only>{}, as_end ? as_const.end() : as_const.begin()};
return iterator{std::integral_constant<bool, key_only>{}, as_end ? as_const.end() : as_const.begin()};
}
[[nodiscard]] static size_type insert_or_erase(any &container, meta_any &key, meta_any &value) {
if(auto *const cont = any_cast<Type>(&container); cont && key.allow_cast<const typename Type::key_type &>()) {
if(value) {
if constexpr(key_only) {
return cont->insert(key.cast<const typename Type::key_type &>()).second;
} else {
return value.allow_cast<const typename Type::mapped_type &>() && cont->emplace(key.cast<const typename Type::key_type &>(), value.cast<const typename Type::mapped_type &>()).second;
}
} else {
return cont->erase(key.cast<const typename Type::key_type &>());
}
[[nodiscard]] static bool insert(any &container, meta_any &key, [[maybe_unused]] meta_any &value) {
auto *const cont = any_cast<Type>(&container);
if constexpr(is_key_only_meta_associative_container<Type>::value) {
return cont && key.allow_cast<const typename Type::key_type &>()
&& cont->insert(key.cast<const typename Type::key_type &>()).second;
} else {
return cont && key.allow_cast<const typename Type::key_type &>() && value.allow_cast<const typename Type::mapped_type &>()
&& cont->emplace(key.cast<const typename Type::key_type &>(), value.cast<const typename Type::mapped_type &>()).second;
}
return 0u;
}
[[nodiscard]] static iterator find(const meta_ctx &ctx, any &container, meta_any &key) {
[[nodiscard]] static bool erase(any &container, meta_any &key) {
auto *const cont = any_cast<Type>(&container);
return cont && key.allow_cast<const typename Type::key_type &>()
&& (cont->erase(key.cast<const typename Type::key_type &>()) != cont->size());
}
[[nodiscard]] static iterator find(any &container, meta_any &key) {
if(key.allow_cast<const typename Type::key_type &>()) {
if(auto *const cont = any_cast<Type>(&container); cont) {
return iterator{ctx, std::bool_constant<key_only>{}, cont->find(key.cast<const typename Type::key_type &>())};
return iterator{std::integral_constant<bool, key_only>{}, cont->find(key.cast<const typename Type::key_type &>())};
}
return iterator{ctx, std::bool_constant<key_only>{}, any_cast<const Type &>(container).find(key.cast<const typename Type::key_type &>())};
return iterator{std::integral_constant<bool, key_only>{}, any_cast<const Type &>(container).find(key.cast<const typename Type::key_type &>())};
}
return iterator{};
return {};
}
};
@@ -161,86 +159,80 @@ struct basic_meta_associative_container_traits {
/**
* @brief Meta sequence container traits for `std::vector`s of any type.
* @tparam Args Template arguments for the container.
* @tparam Type The type of elements.
* @tparam Args Other arguments.
*/
template<typename... Args>
struct meta_sequence_container_traits<std::vector<Args...>>
: internal::basic_meta_sequence_container_traits<std::vector<Args...>> {};
template<typename Type, typename... Args>
struct meta_sequence_container_traits<std::vector<Type, Args...>>
: internal::basic_meta_sequence_container_traits<std::vector<Type, Args...>> {};
/**
* @brief Meta sequence container traits for `std::array`s of any type.
* @tparam Type Template arguments for the container.
* @tparam N Template arguments for the container.
* @tparam Type The type of elements.
* @tparam N The number of elements.
*/
template<typename Type, auto N>
struct meta_sequence_container_traits<std::array<Type, N>>
: internal::basic_meta_sequence_container_traits<std::array<Type, N>> {};
/**
* @brief Meta sequence container traits for `std::list`s of any type.
* @tparam Args Template arguments for the container.
*/
template<typename... Args>
struct meta_sequence_container_traits<std::list<Args...>>
: internal::basic_meta_sequence_container_traits<std::list<Args...>> {};
/**
* @brief Meta sequence container traits for `std::deque`s of any type.
* @tparam Args Template arguments for the container.
*/
template<typename... Args>
struct meta_sequence_container_traits<std::deque<Args...>>
: internal::basic_meta_sequence_container_traits<std::deque<Args...>> {};
/**
* @brief Meta associative container traits for `std::map`s of any type.
* @tparam Args Template arguments for the container.
* @tparam Key The key type of elements.
* @tparam Value The value type of elements.
* @tparam Args Other arguments.
*/
template<typename... Args>
struct meta_associative_container_traits<std::map<Args...>>
: internal::basic_meta_associative_container_traits<std::map<Args...>> {};
template<typename Key, typename Value, typename... Args>
struct meta_associative_container_traits<std::map<Key, Value, Args...>>
: internal::basic_meta_associative_container_traits<std::map<Key, Value, Args...>> {};
/**
* @brief Meta associative container traits for `std::unordered_map`s of any
* type.
* @tparam Args Template arguments for the container.
* @tparam Key The key type of elements.
* @tparam Value The value type of elements.
* @tparam Args Other arguments.
*/
template<typename... Args>
struct meta_associative_container_traits<std::unordered_map<Args...>>
: internal::basic_meta_associative_container_traits<std::unordered_map<Args...>> {};
template<typename Key, typename Value, typename... Args>
struct meta_associative_container_traits<std::unordered_map<Key, Value, Args...>>
: internal::basic_meta_associative_container_traits<std::unordered_map<Key, Value, Args...>> {};
/**
* @brief Meta associative container traits for `std::set`s of any type.
* @tparam Args Template arguments for the container.
* @tparam Key The type of elements.
* @tparam Args Other arguments.
*/
template<typename... Args>
struct meta_associative_container_traits<std::set<Args...>>
: internal::basic_meta_associative_container_traits<std::set<Args...>> {};
template<typename Key, typename... Args>
struct meta_associative_container_traits<std::set<Key, Args...>>
: internal::basic_meta_associative_container_traits<std::set<Key, Args...>> {};
/**
* @brief Meta associative container traits for `std::unordered_set`s of any
* type.
* @tparam Args Template arguments for the container.
* @tparam Key The type of elements.
* @tparam Args Other arguments.
*/
template<typename... Args>
struct meta_associative_container_traits<std::unordered_set<Args...>>
: internal::basic_meta_associative_container_traits<std::unordered_set<Args...>> {};
template<typename Key, typename... Args>
struct meta_associative_container_traits<std::unordered_set<Key, Args...>>
: internal::basic_meta_associative_container_traits<std::unordered_set<Key, Args...>> {};
/**
* @brief Meta associative container traits for `dense_map`s of any type.
* @tparam Args Template arguments for the container.
* @tparam Key The key type of the elements.
* @tparam Type The value type of the elements.
* @tparam Args Other arguments.
*/
template<typename... Args>
struct meta_associative_container_traits<dense_map<Args...>>
: internal::basic_meta_associative_container_traits<dense_map<Args...>> {};
template<typename Key, typename Type, typename... Args>
struct meta_associative_container_traits<dense_map<Key, Type, Args...>>
: internal::basic_meta_associative_container_traits<dense_map<Key, Type, Args...>> {};
/**
* @brief Meta associative container traits for `dense_set`s of any type.
* @tparam Args Template arguments for the container.
* @tparam Type The value type of the elements.
* @tparam Args Other arguments.
*/
template<typename... Args>
struct meta_associative_container_traits<dense_set<Args...>>
: internal::basic_meta_associative_container_traits<dense_set<Args...>> {};
template<typename Type, typename... Args>
struct meta_associative_container_traits<dense_set<Type, Args...>>
: internal::basic_meta_associative_container_traits<dense_set<Type, Args...>> {};
} // namespace entt

View File

@@ -1,67 +0,0 @@
#ifndef ENTT_META_CTX_HPP
#define ENTT_META_CTX_HPP
#include "../container/dense_map.hpp"
#include "../core/fwd.hpp"
#include "../core/utility.hpp"
namespace entt {
class meta_ctx;
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal {
struct meta_type_node;
struct meta_context {
dense_map<id_type, meta_type_node, identity> value{};
[[nodiscard]] static inline meta_context &from(meta_ctx &ctx);
[[nodiscard]] static inline const meta_context &from(const meta_ctx &ctx);
};
} // namespace internal
/**
* Internal details not to be documented.
* @endcond
*/
/*! @brief Disambiguation tag for constructors and the like. */
class meta_ctx_arg_t final {};
/*! @brief Constant of type meta_context_arg_t used to disambiguate calls. */
inline constexpr meta_ctx_arg_t meta_ctx_arg{};
/*! @brief Opaque meta context type. */
class meta_ctx: private internal::meta_context {
/*! @brief Attorney idiom like model to access the base class. */
friend struct internal::meta_context;
};
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
[[nodiscard]] inline internal::meta_context &internal::meta_context::from(meta_ctx &ctx) {
return ctx;
}
[[nodiscard]] inline const internal::meta_context &internal::meta_context::from(const meta_ctx &ctx) {
return ctx;
}
/**
* Internal details not to be documented.
* @endcond
*/
} // namespace entt
#endif

57
src/entt/meta/ctx.hpp Normal file
View File

@@ -0,0 +1,57 @@
#ifndef ENTT_META_CTX_HPP
#define ENTT_META_CTX_HPP
#include "../config/config.h"
#include "../core/attribute.h"
namespace entt {
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal {
struct meta_type_node;
struct ENTT_API meta_context {
// we could use the lines below but VS2017 returns with an ICE if combined with ENTT_API despite the code being valid C++
// inline static meta_type_node *local = nullptr;
// inline static meta_type_node **global = &local;
[[nodiscard]] static meta_type_node *&local() ENTT_NOEXCEPT {
static meta_type_node *chain = nullptr;
return chain;
}
[[nodiscard]] static meta_type_node **&global() ENTT_NOEXCEPT {
static meta_type_node **chain = &local();
return chain;
}
};
} // namespace internal
/**
* Internal details not to be documented.
* @endcond
*/
/*! @brief Opaque container for a meta context. */
struct meta_ctx {
/**
* @brief Binds the meta system to a given context.
* @param other A valid context to which to bind.
*/
static void bind(meta_ctx other) ENTT_NOEXCEPT {
internal::meta_context::global() = other.ctx;
}
private:
internal::meta_type_node **ctx{&internal::meta_context::local()};
};
} // namespace entt
#endif

View File

@@ -1,9 +1,9 @@
#ifndef ENTT_META_FACTORY_HPP
#define ENTT_META_FACTORY_HPP
#include <algorithm>
#include <cstddef>
#include <functional>
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>
@@ -11,118 +11,219 @@
#include "../core/fwd.hpp"
#include "../core/type_info.hpp"
#include "../core/type_traits.hpp"
#include "../locator/locator.hpp"
#include "context.hpp"
#include "meta.hpp"
#include "node.hpp"
#include "policy.hpp"
#include "range.hpp"
#include "resolve.hpp"
#include "utility.hpp"
namespace entt {
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
* @brief Meta factory to be used for reflection purposes.
*
* The meta factory is an utility class used to reflect types, data members and
* functions of all sorts. This class ensures that the underlying web of types
* is built correctly and performs some checks in debug mode to ensure that
* there are no subtle errors at runtime.
*/
namespace internal {
[[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_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;
}
inline meta_func_node &meta_extend(internal::meta_type_node &parent, const id_type id, meta_func_node node) {
if(auto it = parent.details->func.find(id); it != parent.details->func.end()) {
for(auto *curr = &it->second; curr; curr = curr->next.get()) {
if(curr->invoke == node.invoke) {
node.next = std::move(curr->next);
*curr = std::move(node);
return *curr;
}
}
// locally overloaded function
node.next = std::make_shared<meta_func_node>(std::move(parent.details->func[id]));
}
return parent.details->func.insert_or_assign(id, std::move(node)).first->second;
}
} // namespace internal
template<typename...>
class meta_factory;
/**
* Internal details not to be documented.
* @endcond
* @brief Extended meta factory to be used for reflection purposes.
* @tparam Type Reflected type for which the factory was created.
* @tparam Spec Property specialization pack used to disambiguate overloads.
*/
template<typename Type, typename... Spec>
class meta_factory<Type, Spec...>: public meta_factory<Type> {
void link_prop_if_required(internal::meta_prop_node &node) ENTT_NOEXCEPT {
if(meta_range<internal::meta_prop_node *, internal::meta_prop_node> range{*ref}; std::find(range.cbegin(), range.cend(), &node) == range.cend()) {
ENTT_ASSERT(std::find_if(range.cbegin(), range.cend(), [&node](const auto *curr) { return curr->id == node.id; }) == range.cend(), "Duplicate identifier");
node.next = *ref;
*ref = &node;
}
}
template<std::size_t Step = 0, typename... Property, typename... Other>
void unroll(choice_t<2>, std::tuple<Property...> property, Other &&...other) ENTT_NOEXCEPT {
std::apply([this](auto &&...curr) { (this->unroll<Step>(choice<2>, std::forward<Property>(curr)...)); }, property);
unroll<Step + sizeof...(Property)>(choice<2>, std::forward<Other>(other)...);
}
template<std::size_t Step = 0, typename... Property, typename... Other>
void unroll(choice_t<1>, std::pair<Property...> property, Other &&...other) ENTT_NOEXCEPT {
assign<Step>(std::move(property.first), std::move(property.second));
unroll<Step + 1>(choice<2>, std::forward<Other>(other)...);
}
template<std::size_t Step = 0, typename Property, typename... Other>
void unroll(choice_t<0>, Property &&property, Other &&...other) ENTT_NOEXCEPT {
assign<Step>(std::forward<Property>(property));
unroll<Step + 1>(choice<2>, std::forward<Other>(other)...);
}
template<std::size_t>
void unroll(choice_t<0>) ENTT_NOEXCEPT {}
template<std::size_t = 0>
void assign(meta_any key, meta_any value = {}) {
static meta_any property[2u]{};
static internal::meta_prop_node node{
nullptr,
property[0u],
property[1u]
// tricks clang-format
};
property[0u] = std::move(key);
property[1u] = std::move(value);
link_prop_if_required(node);
}
public:
/**
* @brief Constructs an extended factory from a given node.
* @param target The underlying node to which to assign the properties.
*/
meta_factory(internal::meta_prop_node **target) ENTT_NOEXCEPT
: ref{target} {}
/**
* @brief Assigns a property to the last meta object created.
*
* Both the key and the value (if any) must be at least copy constructible.
*
* @tparam PropertyOrKey Type of the property or property key.
* @tparam Value Optional type of the property value.
* @param property_or_key Property or property key.
* @param value Optional property value.
* @return A meta factory for the parent type.
*/
template<typename PropertyOrKey, typename... Value>
meta_factory<Type> prop(PropertyOrKey &&property_or_key, Value &&...value) {
if constexpr(sizeof...(Value) == 0) {
unroll(choice<2>, std::forward<PropertyOrKey>(property_or_key));
} else {
assign(std::forward<PropertyOrKey>(property_or_key), std::forward<Value>(value)...);
}
return {};
}
/**
* @brief Assigns properties to the last meta object created.
*
* Both key and value (if any) must be at least copy constructible.
*
* @tparam Property Types of the properties.
* @param property Properties to assign to the last meta object created.
* @return A meta factory for the parent type.
*/
template<typename... Property>
meta_factory<Type> props(Property... property) {
unroll(choice<2>, std::forward<Property>(property)...);
return {};
}
private:
internal::meta_prop_node **ref;
};
/**
* @brief Basic meta factory to be used for reflection purposes.
* @tparam Type Reflected type for which the factory was created.
*/
template<typename Type>
class meta_factory {
class meta_factory<Type> {
void link_base_if_required(internal::meta_base_node &node) ENTT_NOEXCEPT {
if(meta_range<internal::meta_base_node *, internal::meta_base_node> range{owner->base}; std::find(range.cbegin(), range.cend(), &node) == range.cend()) {
node.next = owner->base;
owner->base = &node;
}
}
void link_conv_if_required(internal::meta_conv_node &node) ENTT_NOEXCEPT {
if(meta_range<internal::meta_conv_node *, internal::meta_conv_node> range{owner->conv}; std::find(range.cbegin(), range.cend(), &node) == range.cend()) {
node.next = owner->conv;
owner->conv = &node;
}
}
void link_ctor_if_required(internal::meta_ctor_node &node) ENTT_NOEXCEPT {
if(meta_range<internal::meta_ctor_node *, internal::meta_ctor_node> range{owner->ctor}; std::find(range.cbegin(), range.cend(), &node) == range.cend()) {
node.next = owner->ctor;
owner->ctor = &node;
}
}
void link_data_if_required(const id_type id, internal::meta_data_node &node) ENTT_NOEXCEPT {
meta_range<internal::meta_data_node *, internal::meta_data_node> range{owner->data};
ENTT_ASSERT(std::find_if(range.cbegin(), range.cend(), [id, &node](const auto *curr) { return curr != &node && curr->id == id; }) == range.cend(), "Duplicate identifier");
node.id = id;
if(std::find(range.cbegin(), range.cend(), &node) == range.cend()) {
node.next = owner->data;
owner->data = &node;
}
}
void link_func_if_required(const id_type id, internal::meta_func_node &node) ENTT_NOEXCEPT {
node.id = id;
if(meta_range<internal::meta_func_node *, internal::meta_func_node> range{owner->func}; std::find(range.cbegin(), range.cend(), &node) == range.cend()) {
node.next = owner->func;
owner->func = &node;
}
}
template<typename Setter, auto Getter, typename Policy, std::size_t... Index>
void data(const id_type id, std::index_sequence<Index...>) noexcept {
auto data(const id_type id, std::index_sequence<Index...>) ENTT_NOEXCEPT {
using data_type = std::invoke_result_t<decltype(Getter), Type &>;
using args_type = type_list<typename meta_function_helper_t<Type, decltype(value_list_element_v<Index, Setter>)>::args_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_member_object_pointer_v<decltype(value_list_element_v<Index, Setter>)> && ... && std::is_const_v<std::remove_reference_t<data_type>>) ? internal::meta_traits::is_const : internal::meta_traits::is_none,
Setter::size,
&internal::resolve<std::remove_cv_t<std::remove_reference_t<data_type>>>,
&meta_arg<type_list<type_list_element_t<type_list_element_t<Index, args_type>::size != 1u, type_list_element_t<Index, args_type>>...>>,
+[](meta_handle instance, meta_any value) { return (meta_setter<Type, value_list_element_v<Index, Setter>>(*instance.operator->(), value.as_ref()) || ...); },
&meta_getter<Type, Getter, Policy>});
static internal::meta_data_node node{
{},
/* this is never static */
(std::is_member_object_pointer_v<decltype(value_list_element_v<Index, Setter>)> && ... && std::is_const_v<std::remove_reference_t<data_type>>) ? internal::meta_traits::is_const : internal::meta_traits::is_none,
nullptr,
nullptr,
Setter::size,
internal::meta_node<std::remove_cv_t<std::remove_reference_t<data_type>>>::resolve(),
&meta_arg<type_list<type_list_element_t<type_list_element_t<Index, args_type>::size != 1u, type_list_element_t<Index, args_type>>...>>,
[](meta_handle instance, meta_any value) -> bool { return (meta_setter<Type, value_list_element_v<Index, Setter>>(*instance.operator->(), value.as_ref()) || ...); },
&meta_getter<Type, Getter, Policy>
// tricks clang-format
};
bucket = &elem.prop;
link_data_if_required(id, node);
return meta_factory<Type, Setter, std::integral_constant<decltype(Getter), Getter>>{&node.prop};
}
public:
/*! @brief Default constructor. */
meta_factory() noexcept
: meta_factory{locator<meta_ctx>::value_or()} {}
meta_factory() ENTT_NOEXCEPT
: owner{internal::meta_node<Type>::resolve()} {}
/**
* @brief Context aware constructor.
* @param area The context into which to construct meta types.
*/
meta_factory(meta_ctx &area) noexcept
: ctx{&area},
bucket{},
info{&type_id<Type>()} {
auto &&elem = internal::owner(*ctx, *info);
if(!elem.details) {
elem.details = std::make_shared<internal::meta_type_descriptor>();
}
bucket = &elem.details->prop;
}
/**
* @brief Assigns a custom unique identifier to a meta type.
* @param id A custom unique identifier.
* @brief Makes a meta type _searchable_.
* @param id Optional unique identifier.
* @return An extended meta factory for the given type.
*/
auto type(const id_type id) noexcept {
auto &&elem = internal::owner(*ctx, *info);
ENTT_ASSERT(elem.id == id || !resolve(*ctx, id), "Duplicate identifier");
bucket = &elem.details->prop;
elem.id = id;
return *this;
auto type(const id_type id = type_hash<Type>::value()) ENTT_NOEXCEPT {
meta_range<internal::meta_type_node *, internal::meta_type_node> range{*internal::meta_context::global()};
ENTT_ASSERT(std::find_if(range.cbegin(), range.cend(), [id, this](const auto *curr) { return curr != owner && curr->id == id; }) == range.cend(), "Duplicate identifier");
owner->id = id;
if(std::find(range.cbegin(), range.cend(), owner) == range.cend()) {
owner->next = *internal::meta_context::global();
*internal::meta_context::global() = owner;
}
return meta_factory<Type, Type>{&owner->prop};
}
/**
@@ -134,12 +235,24 @@ public:
* @return A meta factory for the parent type.
*/
template<typename Base>
auto base() noexcept {
auto base() ENTT_NOEXCEPT {
static_assert(!std::is_same_v<Type, Base> && std::is_base_of_v<Base, Type>, "Invalid base type");
auto *const op = +[](const void *instance) noexcept { return static_cast<const void *>(static_cast<const Base *>(static_cast<const Type *>(instance))); };
internal::owner(*ctx, *info).details->base.insert_or_assign(type_id<Base>().hash(), internal::meta_base_node{&internal::resolve<Base>, op});
bucket = nullptr;
return *this;
static internal::meta_base_node node{
nullptr,
internal::meta_node<Base>::resolve(),
[](meta_any other) ENTT_NOEXCEPT -> meta_any {
if(auto *ptr = other.data(); ptr) {
return forward_as_meta(*static_cast<Base *>(static_cast<Type *>(ptr)));
}
return forward_as_meta(*static_cast<const Base *>(static_cast<const Type *>(std::as_const(other).data())));
}
// tricks clang-format
};
link_base_if_required(node);
return meta_factory<Type>{};
}
/**
@@ -155,12 +268,18 @@ public:
* @return A meta factory for the parent type.
*/
template<auto Candidate>
auto conv() noexcept {
using conv_type = std::remove_cv_t<std::remove_reference_t<std::invoke_result_t<decltype(Candidate), Type &>>>;
auto *const op = +[](const meta_ctx &area, const void *instance) { return forward_as_meta(area, std::invoke(Candidate, *static_cast<const Type *>(instance))); };
internal::owner(*ctx, *info).details->conv.insert_or_assign(type_id<conv_type>().hash(), internal::meta_conv_node{op});
bucket = nullptr;
return *this;
auto conv() ENTT_NOEXCEPT {
static internal::meta_conv_node node{
nullptr,
internal::meta_node<std::remove_cv_t<std::remove_reference_t<std::invoke_result_t<decltype(Candidate), Type &>>>>::resolve(),
[](const meta_any &instance) -> meta_any {
return forward_as_meta(std::invoke(Candidate, *static_cast<const Type *>(instance.data())));
}
// tricks clang-format
};
link_conv_if_required(node);
return meta_factory<Type>{};
}
/**
@@ -173,12 +292,16 @@ public:
* @return A meta factory for the parent type.
*/
template<typename To>
auto conv() noexcept {
using conv_type = std::remove_cv_t<std::remove_reference_t<To>>;
auto *const op = +[](const meta_ctx &area, const void *instance) { return forward_as_meta(area, static_cast<To>(*static_cast<const Type *>(instance))); };
internal::owner(*ctx, *info).details->conv.insert_or_assign(type_id<conv_type>().hash(), internal::meta_conv_node{op});
bucket = nullptr;
return *this;
auto conv() ENTT_NOEXCEPT {
static internal::meta_conv_node node{
nullptr,
internal::meta_node<std::remove_cv_t<std::remove_reference_t<To>>>::resolve(),
[](const meta_any &instance) -> meta_any { return forward_as_meta(static_cast<To>(*static_cast<const Type *>(instance.data()))); }
// tricks clang-format
};
link_conv_if_required(node);
return meta_factory<Type>{};
}
/**
@@ -195,13 +318,21 @@ public:
* @return An extended meta factory for the parent type.
*/
template<auto Candidate, typename Policy = as_is_t>
auto ctor() noexcept {
auto ctor() ENTT_NOEXCEPT {
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::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;
static internal::meta_ctor_node node{
nullptr,
descriptor::args_type::size,
&meta_arg<typename descriptor::args_type>,
&meta_construct<Type, Candidate, Policy>
// tricks clang-format
};
link_ctor_if_required(node);
return meta_factory<Type>{};
}
/**
@@ -215,15 +346,19 @@ public:
* @return An extended meta factory for the parent type.
*/
template<typename... Args>
auto ctor() noexcept {
// 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::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...>});
}
auto ctor() ENTT_NOEXCEPT {
using descriptor = meta_function_helper_t<Type, Type (*)(Args...)>;
bucket = nullptr;
return *this;
static internal::meta_ctor_node node{
nullptr,
descriptor::args_type::size,
&meta_arg<typename descriptor::args_type>,
&meta_construct<Type, Args...>
// tricks clang-format
};
link_ctor_if_required(node);
return meta_factory<Type>{};
}
/**
@@ -245,12 +380,10 @@ public:
* @return A meta factory for the parent type.
*/
template<auto Func>
auto dtor() noexcept {
auto dtor() ENTT_NOEXCEPT {
static_assert(std::is_invocable_v<decltype(Func), Type &>, "The function doesn't accept an object of the type provided");
auto *const op = +[](void *instance) { std::invoke(Func, *static_cast<Type *>(instance)); };
internal::owner(*ctx, *info).dtor = internal::meta_dtor_node{op};
bucket = nullptr;
return *this;
owner->dtor = [](void *instance) { std::invoke(Func, *static_cast<Type *>(instance)); };
return meta_factory<Type>{};
}
/**
@@ -267,48 +400,45 @@ public:
* @return An extended meta factory for the parent type.
*/
template<auto Data, typename Policy = as_is_t>
auto data(const id_type id) noexcept {
auto data(const id_type id) ENTT_NOEXCEPT {
if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
using data_type = std::invoke_result_t<decltype(Data), Type &>;
static_assert(Policy::template value<data_type>, "Invalid return type for the given policy");
using data_type = std::remove_reference_t<std::invoke_result_t<decltype(Data), Type &>>;
auto &&elem = internal::meta_extend(
internal::owner(*ctx, *info),
id,
internal::meta_data_node{
/* this is never static */
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<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>});
static internal::meta_data_node node{
{},
/* this is never static */
std::is_const_v<data_type> ? internal::meta_traits::is_const : internal::meta_traits::is_none,
nullptr,
nullptr,
1u,
internal::meta_node<std::remove_const_t<data_type>>::resolve(),
&meta_arg<type_list<std::remove_const_t<data_type>>>,
&meta_setter<Type, Data>,
&meta_getter<Type, Data, Policy>
// tricks clang-format
};
bucket = &elem.prop;
link_data_if_required(id, node);
return meta_factory<Type, std::integral_constant<decltype(Data), Data>, std::integral_constant<decltype(Data), Data>>{&node.prop};
} else {
using data_type = std::remove_pointer_t<decltype(Data)>;
using data_type = std::remove_reference_t<std::remove_pointer_t<decltype(Data)>>;
if constexpr(std::is_pointer_v<decltype(Data)>) {
static_assert(Policy::template value<decltype(*Data)>, "Invalid return type for the given policy");
} else {
static_assert(Policy::template value<data_type>, "Invalid return type for the given policy");
}
static internal::meta_data_node node{
{},
((std::is_same_v<Type, std::remove_const_t<data_type>> || std::is_const_v<data_type>) ? internal::meta_traits::is_const : internal::meta_traits::is_none) | internal::meta_traits::is_static,
nullptr,
nullptr,
1u,
internal::meta_node<std::remove_const_t<data_type>>::resolve(),
&meta_arg<type_list<std::remove_const_t<data_type>>>,
&meta_setter<Type, Data>,
&meta_getter<Type, Data, Policy>
// tricks clang-format
};
auto &&elem = internal::meta_extend(
internal::owner(*ctx, *info),
id,
internal::meta_data_node{
((std::is_same_v<Type, std::remove_cv_t<std::remove_reference_t<data_type>>> || std::is_const_v<std::remove_reference_t<data_type>>) ? internal::meta_traits::is_const : internal::meta_traits::is_none) | internal::meta_traits::is_static,
1u,
&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;
link_data_if_required(id, node);
return meta_factory<Type, std::integral_constant<decltype(Data), Data>>{&node.prop};
}
return *this;
}
/**
@@ -332,43 +462,47 @@ public:
* @return An extended meta factory for the parent type.
*/
template<auto Setter, auto Getter, typename Policy = as_is_t>
auto data(const id_type id) noexcept {
auto data(const id_type id) ENTT_NOEXCEPT {
using data_type = std::invoke_result_t<decltype(Getter), Type &>;
static_assert(Policy::template value<data_type>, "Invalid return type for the given policy");
if constexpr(std::is_same_v<decltype(Setter), std::nullptr_t>) {
auto &&elem = internal::meta_extend(
internal::owner(*ctx, *info),
id,
internal::meta_data_node{
/* this is never static */
internal::meta_traits::is_const,
0u,
&internal::resolve<std::remove_cv_t<std::remove_reference_t<data_type>>>,
&meta_arg<type_list<>>,
&meta_setter<Type, Setter>,
&meta_getter<Type, Getter, Policy>});
static internal::meta_data_node node{
{},
/* this is never static */
internal::meta_traits::is_const,
nullptr,
nullptr,
0u,
internal::meta_node<std::remove_cv_t<std::remove_reference_t<data_type>>>::resolve(),
&meta_arg<type_list<>>,
&meta_setter<Type, Setter>,
&meta_getter<Type, Getter, Policy>
// tricks clang-format
};
bucket = &elem.prop;
link_data_if_required(id, node);
return meta_factory<Type, std::integral_constant<decltype(Setter), Setter>, std::integral_constant<decltype(Getter), Getter>>{&node.prop};
} else {
using args_type = typename meta_function_helper_t<Type, decltype(Setter)>::args_type;
auto &&elem = internal::meta_extend(
internal::owner(*ctx, *info),
id,
internal::meta_data_node{
/* this is never static nor const */
internal::meta_traits::is_none,
1u,
&internal::resolve<std::remove_cv_t<std::remove_reference_t<data_type>>>,
&meta_arg<type_list<type_list_element_t<args_type::size != 1u, args_type>>>,
&meta_setter<Type, Setter>,
&meta_getter<Type, Getter, Policy>});
static internal::meta_data_node node{
{},
/* this is never static nor const */
internal::meta_traits::is_none,
nullptr,
nullptr,
1u,
internal::meta_node<std::remove_cv_t<std::remove_reference_t<data_type>>>::resolve(),
&meta_arg<type_list<type_list_element_t<args_type::size != 1u, args_type>>>,
&meta_setter<Type, Setter>,
&meta_getter<Type, Getter, Policy>
// tricks clang-format
};
bucket = &elem.prop;
link_data_if_required(id, node);
return meta_factory<Type, std::integral_constant<decltype(Setter), Setter>, std::integral_constant<decltype(Getter), Getter>>{&node.prop};
}
return *this;
}
/**
@@ -389,9 +523,8 @@ public:
* @return An extended meta factory for the parent type.
*/
template<typename Setter, auto Getter, typename Policy = as_is_t>
auto data(const id_type id) noexcept {
data<Setter, Getter, Policy>(id, std::make_index_sequence<Setter::size>{});
return *this;
auto data(const id_type id) ENTT_NOEXCEPT {
return data<Setter, Getter, Policy>(id, std::make_index_sequence<Setter::size>{});
}
/**
@@ -408,53 +541,28 @@ public:
* @return An extended meta factory for the parent type.
*/
template<auto Candidate, typename Policy = as_is_t>
auto func(const id_type id) noexcept {
auto func(const id_type id) ENTT_NOEXCEPT {
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");
auto &&elem = internal::meta_extend(
internal::owner(*ctx, *info),
id,
internal::meta_func_node{
(descriptor::is_const ? internal::meta_traits::is_const : internal::meta_traits::is_none) | (descriptor::is_static ? internal::meta_traits::is_static : internal::meta_traits::is_none),
descriptor::args_type::size,
&internal::resolve<std::conditional_t<std::is_same_v<Policy, as_void_t>, void, std::remove_cv_t<std::remove_reference_t<typename descriptor::return_type>>>>,
&meta_arg<typename descriptor::args_type>,
&meta_invoke<Type, Candidate, Policy>});
static internal::meta_func_node node{
{},
(descriptor::is_const ? internal::meta_traits::is_const : internal::meta_traits::is_none) | (descriptor::is_static ? internal::meta_traits::is_static : internal::meta_traits::is_none),
nullptr,
nullptr,
descriptor::args_type::size,
internal::meta_node<std::conditional_t<std::is_same_v<Policy, as_void_t>, void, std::remove_cv_t<std::remove_reference_t<typename descriptor::return_type>>>>::resolve(),
&meta_arg<typename descriptor::args_type>,
&meta_invoke<Type, Candidate, Policy>
// tricks clang-format
};
bucket = &elem.prop;
return *this;
}
/**
* @brief Assigns a property to the last meta object created.
*
* Both the key and the value (if any) must be at least copy constructible.
*
* @tparam Value Optional type of the property value.
* @param id Property key.
* @param value Optional property value.
* @return An extended meta factory for the given type.
*/
template<typename... Value>
meta_factory prop(id_type id, [[maybe_unused]] Value &&...value) {
ENTT_ASSERT(bucket != nullptr, "Meta object does not support properties");
if constexpr(sizeof...(Value) == 0u) {
(*bucket)[id] = internal::meta_prop_node{&internal::resolve<void>};
} else {
(*bucket)[id] = internal::meta_prop_node{
&internal::resolve<std::decay_t<Value>>...,
std::make_shared<std::decay_t<Value>>(std::forward<Value>(value))...};
}
return *this;
link_func_if_required(id, node);
return meta_factory<Type, std::integral_constant<decltype(Candidate), Candidate>>{&node.prop};
}
private:
meta_ctx *ctx;
dense_map<id_type, internal::meta_prop_node, identity> *bucket;
const type_info *info;
internal::meta_type_node *owner;
};
/**
@@ -466,31 +574,13 @@ private:
* dedicated factory.
*
* @tparam Type Type to reflect.
* @param ctx The context into which to construct meta types.
* @return A meta factory for the given type.
*/
template<typename Type>
[[nodiscard]] auto meta(meta_ctx &ctx) noexcept {
auto &&context = internal::meta_context::from(ctx);
// make sure the type exists in the context before returning a factory
context.value.try_emplace(type_id<Type>().hash(), internal::resolve<Type>(context));
return meta_factory<Type>{ctx};
}
/**
* @brief Utility function to use for reflection.
*
* This is the point from which everything starts.<br/>
* By invoking this function with a type that is not yet reflected, a meta type
* is created to which it will be possible to attach meta objects through a
* dedicated factory.
*
* @tparam Type Type to reflect.
* @return A meta factory for the given type.
*/
template<typename Type>
[[nodiscard]] auto meta() noexcept {
return meta<Type>(locator<meta_ctx>::value_or());
[[nodiscard]] auto meta() ENTT_NOEXCEPT {
auto *const node = internal::meta_node<Type>::resolve();
// extended meta factory to allow assigning properties to opaque meta types
return meta_factory<Type, Type>{&node->prop};
}
/**
@@ -500,51 +590,38 @@ template<typename Type>
* well as its constructors, destructors and conversion functions if any.<br/>
* Base classes aren't reset but the link between the two types is removed.
*
* The type is also removed from the set of searchable types.
* The type is also removed from the list of searchable types.
*
* @param id Unique identifier.
* @param ctx The context from which to reset meta types.
*/
inline void meta_reset(meta_ctx &ctx, const id_type id) noexcept {
auto &&context = internal::meta_context::from(ctx);
inline void meta_reset(const id_type id) ENTT_NOEXCEPT {
auto clear_chain = [](auto **curr, auto... member) {
for(; *curr; *curr = std::exchange((*curr)->next, nullptr)) {
if constexpr(sizeof...(member) != 0u) {
static_assert(sizeof...(member) == 1u, "Assert in defense of the future me");
for(auto **sub = (&((*curr)->*member), ...); *sub; *sub = std::exchange((*sub)->next, nullptr)) {}
}
}
};
for(auto it = context.value.begin(); it != context.value.end();) {
if(it->second.id == id) {
it = context.value.erase(it);
} else {
++it;
for(auto **it = internal::meta_context::global(); *it; it = &(*it)->next) {
if(auto *node = *it; node->id == id) {
clear_chain(&node->prop);
clear_chain(&node->base);
clear_chain(&node->conv);
clear_chain(&node->ctor);
clear_chain(&node->data, &internal::meta_data_node::prop);
clear_chain(&node->func, &internal::meta_func_node::prop);
node->id = {};
node->dtor = nullptr;
*it = std::exchange(node->next, nullptr);
break;
}
}
}
/**
* @brief Resets a type and all its parts.
*
* Resets a type and all its data members, member functions and properties, as
* well as its constructors, destructors and conversion functions if any.<br/>
* Base classes aren't reset but the link between the two types is removed.
*
* The type is also removed from the set of searchable types.
*
* @param id Unique identifier.
*/
inline void meta_reset(const id_type id) noexcept {
meta_reset(locator<meta_ctx>::value_or(), id);
}
/**
* @brief Resets a type and all its parts.
*
* @sa meta_reset
*
* @tparam Type Type to reset.
* @param ctx The context from which to reset meta types.
*/
template<typename Type>
void meta_reset(meta_ctx &ctx) noexcept {
internal::meta_context::from(ctx).value.erase(type_id<Type>().hash());
}
/**
* @brief Resets a type and all its parts.
*
@@ -553,28 +630,19 @@ void meta_reset(meta_ctx &ctx) noexcept {
* @tparam Type Type to reset.
*/
template<typename Type>
void meta_reset() noexcept {
meta_reset<Type>(locator<meta_ctx>::value_or());
void meta_reset() ENTT_NOEXCEPT {
meta_reset(internal::meta_node<Type>::resolve()->id);
}
/**
* @brief Resets all meta types.
*
* @sa meta_reset
*
* @param ctx The context from which to reset meta types.
*/
inline void meta_reset(meta_ctx &ctx) noexcept {
internal::meta_context::from(ctx).value.clear();
}
/**
* @brief Resets all meta types.
* @brief Resets all searchable types.
*
* @sa meta_reset
*/
inline void meta_reset() noexcept {
meta_reset(locator<meta_ctx>::value_or());
inline void meta_reset() ENTT_NOEXCEPT {
while(*internal::meta_context::global()) {
meta_reset((*internal::meta_context::global())->id);
}
}
} // namespace entt

File diff suppressed because it is too large Load Diff

View File

@@ -2,18 +2,14 @@
#define ENTT_META_NODE_HPP
#include <cstddef>
#include <memory>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "../container/dense_map.hpp"
#include "../core/attribute.h"
#include "../core/enum.hpp"
#include "../core/fwd.hpp"
#include "../core/type_info.hpp"
#include "../core/type_traits.hpp"
#include "../core/utility.hpp"
#include "context.hpp"
#include "type_traits.hpp"
namespace entt {
@@ -34,196 +30,201 @@ enum class meta_traits : std::uint32_t {
is_const = 0x0001,
is_static = 0x0002,
is_arithmetic = 0x0004,
is_integral = 0x0008,
is_signed = 0x0010,
is_array = 0x0020,
is_enum = 0x0040,
is_class = 0x0080,
is_meta_pointer_like = 0x0100,
is_meta_sequence_container = 0x0200,
is_meta_associative_container = 0x0400,
is_array = 0x0008,
is_enum = 0x0010,
is_class = 0x0020,
is_pointer = 0x0040,
is_meta_pointer_like = 0x0080,
is_meta_sequence_container = 0x0100,
is_meta_associative_container = 0x0200,
_entt_enum_as_bitmask
};
struct meta_type_node;
struct meta_prop_node {
meta_type_node (*type)(const meta_context &) noexcept {};
std::shared_ptr<void> value{};
meta_prop_node *next;
const meta_any &id;
meta_any &value;
};
struct meta_base_node {
meta_type_node (*type)(const meta_context &) noexcept {};
const void *(*cast)(const void *) noexcept {};
meta_base_node *next;
meta_type_node *const type;
meta_any (*const cast)(meta_any) ENTT_NOEXCEPT;
};
struct meta_conv_node {
meta_any (*conv)(const meta_ctx &, const void *){};
meta_conv_node *next;
meta_type_node *const type;
meta_any (*const conv)(const meta_any &);
};
struct meta_ctor_node {
using size_type = std::size_t;
size_type arity{0u};
meta_type (*arg)(const meta_ctx &, const size_type) noexcept {};
meta_any (*invoke)(const meta_ctx &, meta_any *const){};
};
struct meta_dtor_node {
void (*dtor)(void *){};
meta_ctor_node *next;
const size_type arity;
meta_type (*const arg)(const size_type) ENTT_NOEXCEPT;
meta_any (*const invoke)(meta_any *const);
};
struct meta_data_node {
using size_type = std::size_t;
meta_traits traits{meta_traits::is_none};
size_type arity{0u};
meta_type_node (*type)(const meta_context &) noexcept {};
meta_type (*arg)(const meta_ctx &, const size_type) noexcept {};
bool (*set)(meta_handle, meta_any){};
meta_any (*get)(const meta_ctx &, meta_handle){};
dense_map<id_type, meta_prop_node, identity> prop{};
id_type id;
const meta_traits traits;
meta_data_node *next;
meta_prop_node *prop;
const size_type arity;
meta_type_node *const type;
meta_type (*const arg)(const size_type) ENTT_NOEXCEPT;
bool (*const set)(meta_handle, meta_any);
meta_any (*const get)(meta_handle);
};
struct meta_func_node {
using size_type = std::size_t;
meta_traits traits{meta_traits::is_none};
size_type arity{0u};
meta_type_node (*ret)(const meta_context &) noexcept {};
meta_type (*arg)(const meta_ctx &, const size_type) noexcept {};
meta_any (*invoke)(const meta_ctx &, meta_handle, meta_any *const){};
std::shared_ptr<meta_func_node> next{};
dense_map<id_type, meta_prop_node, identity> prop{};
id_type id;
const meta_traits traits;
meta_func_node *next;
meta_prop_node *prop;
const size_type arity;
meta_type_node *const ret;
meta_type (*const arg)(const size_type) ENTT_NOEXCEPT;
meta_any (*const invoke)(meta_handle, meta_any *const);
};
struct meta_template_node {
using size_type = std::size_t;
size_type arity{0u};
meta_type_node (*type)(const meta_context &) noexcept {};
meta_type_node (*arg)(const meta_context &, const size_type) noexcept {};
};
struct meta_type_descriptor {
dense_map<id_type, meta_ctor_node, identity> ctor{};
dense_map<id_type, meta_base_node, identity> base{};
dense_map<id_type, meta_conv_node, identity> conv{};
dense_map<id_type, meta_data_node, identity> data{};
dense_map<id_type, meta_func_node, identity> func{};
dense_map<id_type, meta_prop_node, identity> prop{};
const size_type arity;
meta_type_node *const type;
meta_type_node *(*const arg)(const size_type)ENTT_NOEXCEPT;
};
struct meta_type_node {
using size_type = std::size_t;
const type_info *info{};
id_type id{};
meta_traits traits{meta_traits::is_none};
size_type size_of{0u};
meta_type_node (*resolve)(const meta_context &) noexcept {};
meta_type_node (*remove_pointer)(const meta_context &) noexcept {};
meta_any (*default_constructor)(const meta_ctx &){};
double (*conversion_helper)(void *, const void *){};
meta_any (*from_void)(const meta_ctx &, void *, const void *){};
meta_template_node templ{};
meta_dtor_node dtor{};
std::shared_ptr<meta_type_descriptor> details{};
const type_info *info;
id_type id;
const meta_traits traits;
meta_type_node *next;
meta_prop_node *prop;
const size_type size_of;
meta_type_node *(*const remove_pointer)() ENTT_NOEXCEPT;
meta_any (*const default_constructor)();
double (*const conversion_helper)(void *, const void *);
const meta_template_node *const templ;
meta_ctor_node *ctor{nullptr};
meta_base_node *base{nullptr};
meta_conv_node *conv{nullptr};
meta_data_node *data{nullptr};
meta_func_node *func{nullptr};
void (*dtor)(void *){nullptr};
};
template<typename Type>
meta_type_node resolve(const meta_context &) noexcept;
template<typename... Args>
[[nodiscard]] auto meta_arg_node(const meta_context &context, type_list<Args...>, [[maybe_unused]] const std::size_t index) noexcept {
[[maybe_unused]] std::size_t pos{};
meta_type_node (*value)(const meta_context &) noexcept = nullptr;
((value = (pos++ == index ? &resolve<std::remove_cv_t<std::remove_reference_t<Args>>> : value)), ...);
ENTT_ASSERT(value != nullptr, "Out of bounds");
return value(context);
}
meta_type_node *meta_arg_node(type_list<Args...>, const std::size_t index) ENTT_NOEXCEPT;
[[nodiscard]] inline const void *try_cast(const meta_context &context, const meta_type_node &from, const meta_type_node &to, const void *instance) noexcept {
if(from.info && to.info && *from.info == *to.info) {
return instance;
template<typename Type>
class ENTT_API meta_node {
static_assert(std::is_same_v<Type, std::remove_cv_t<std::remove_reference_t<Type>>>, "Invalid type");
[[nodiscard]] static auto *meta_default_constructor() ENTT_NOEXCEPT {
if constexpr(std::is_default_constructible_v<Type>) {
return +[]() { return meta_any{std::in_place_type<Type>}; };
} else {
return static_cast<std::decay_t<decltype(meta_type_node::default_constructor)>>(nullptr);
}
}
if(from.details) {
for(auto &&curr: from.details->base) {
if(const void *elem = try_cast(context, curr.second.type(context), to, curr.second.cast(instance)); elem) {
return elem;
[[nodiscard]] static auto *meta_conversion_helper() ENTT_NOEXCEPT {
if constexpr(std::is_arithmetic_v<Type>) {
return +[](void *bin, const void *value) {
return bin ? static_cast<double>(*static_cast<Type *>(bin) = static_cast<Type>(*static_cast<const double *>(value))) : static_cast<double>(*static_cast<const Type *>(value));
};
} else if constexpr(std::is_enum_v<Type>) {
return +[](void *bin, const void *value) {
return bin ? static_cast<double>(*static_cast<Type *>(bin) = static_cast<Type>(static_cast<std::underlying_type_t<Type>>(*static_cast<const double *>(value)))) : static_cast<double>(*static_cast<const Type *>(value));
};
} else {
return static_cast<std::decay_t<decltype(meta_type_node::conversion_helper)>>(nullptr);
}
}
[[nodiscard]] static meta_template_node *meta_template_info() ENTT_NOEXCEPT {
if constexpr(is_complete_v<meta_template_traits<Type>>) {
static meta_template_node node{
meta_template_traits<Type>::args_type::size,
meta_node<typename meta_template_traits<Type>::class_type>::resolve(),
[](const std::size_t index) ENTT_NOEXCEPT { return meta_arg_node(typename meta_template_traits<Type>::args_type{}, index); }
// tricks clang-format
};
return &node;
} else {
return nullptr;
}
}
public:
[[nodiscard]] static meta_type_node *resolve() ENTT_NOEXCEPT {
static meta_type_node node{
&type_id<Type>(),
{},
internal::meta_traits::is_none
| (std::is_arithmetic_v<Type> ? internal::meta_traits::is_arithmetic : internal::meta_traits::is_none)
| (std::is_array_v<Type> ? internal::meta_traits::is_array : internal::meta_traits::is_none)
| (std::is_enum_v<Type> ? internal::meta_traits::is_enum : internal::meta_traits::is_none)
| (std::is_class_v<Type> ? internal::meta_traits::is_class : internal::meta_traits::is_none)
| (std::is_pointer_v<Type> ? internal::meta_traits::is_pointer : internal::meta_traits::is_none)
| (is_meta_pointer_like_v<Type> ? internal::meta_traits::is_meta_pointer_like : internal::meta_traits::is_none)
| (is_complete_v<meta_sequence_container_traits<Type>> ? internal::meta_traits::is_meta_sequence_container : internal::meta_traits::is_none)
| (is_complete_v<meta_associative_container_traits<Type>> ? internal::meta_traits::is_meta_associative_container : internal::meta_traits::is_none),
nullptr,
nullptr,
size_of_v<Type>,
&meta_node<std::remove_cv_t<std::remove_reference_t<std::remove_pointer_t<Type>>>>::resolve,
meta_default_constructor(),
meta_conversion_helper(),
meta_template_info()
// tricks clang-format
};
return &node;
}
};
template<typename... Args>
[[nodiscard]] meta_type_node *meta_arg_node(type_list<Args...>, const std::size_t index) ENTT_NOEXCEPT {
meta_type_node *args[sizeof...(Args) + 1u]{nullptr, internal::meta_node<std::remove_cv_t<std::remove_reference_t<Args>>>::resolve()...};
return args[index + 1u];
}
template<auto Member, typename Type>
[[nodiscard]] static std::decay_t<decltype(std::declval<internal::meta_type_node>().*Member)> find_by(const Type &info_or_id, const internal::meta_type_node *node) ENTT_NOEXCEPT {
for(auto *curr = node->*Member; curr; curr = curr->next) {
if constexpr(std::is_same_v<Type, type_info>) {
if(*curr->type->info == info_or_id) {
return curr;
}
} else if constexpr(std::is_same_v<decltype(curr), meta_base_node *>) {
if(curr->type->id == info_or_id) {
return curr;
}
} else {
if(curr->id == info_or_id) {
return curr;
}
}
}
for(auto *curr = node->base; curr; curr = curr->next) {
if(auto *ret = find_by<Member>(info_or_id, curr->type); ret) {
return ret;
}
}
return nullptr;
}
[[nodiscard]] inline const meta_type_node *try_resolve(const meta_context &context, const type_info &info) noexcept {
const auto it = context.value.find(info.hash());
return it != context.value.end() ? &it->second : nullptr;
}
template<typename Type>
[[nodiscard]] meta_type_node resolve(const meta_context &context) noexcept {
static_assert(std::is_same_v<Type, std::remove_const_t<std::remove_reference_t<Type>>>, "Invalid type");
if(auto *elem = try_resolve(context, type_id<Type>()); elem) {
return *elem;
}
meta_type_node node{
&type_id<Type>(),
type_id<Type>().hash(),
(std::is_arithmetic_v<Type> ? meta_traits::is_arithmetic : meta_traits::is_none)
| (std::is_integral_v<Type> ? meta_traits::is_integral : meta_traits::is_none)
| (std::is_signed_v<Type> ? meta_traits::is_signed : meta_traits::is_none)
| (std::is_array_v<Type> ? meta_traits::is_array : meta_traits::is_none)
| (std::is_enum_v<Type> ? meta_traits::is_enum : meta_traits::is_none)
| (std::is_class_v<Type> ? meta_traits::is_class : meta_traits::is_none)
| (is_meta_pointer_like_v<Type> ? meta_traits::is_meta_pointer_like : meta_traits::is_none)
| (is_complete_v<meta_sequence_container_traits<Type>> ? meta_traits::is_meta_sequence_container : meta_traits::is_none)
| (is_complete_v<meta_associative_container_traits<Type>> ? meta_traits::is_meta_associative_container : meta_traits::is_none),
size_of_v<Type>,
&resolve<Type>,
&resolve<std::remove_cv_t<std::remove_pointer_t<Type>>>};
if constexpr(std::is_default_constructible_v<Type>) {
node.default_constructor = +[](const meta_ctx &ctx) {
return meta_any{ctx, std::in_place_type<Type>};
};
}
if constexpr(std::is_arithmetic_v<Type>) {
node.conversion_helper = +[](void *bin, const void *value) {
return bin ? static_cast<double>(*static_cast<Type *>(bin) = static_cast<Type>(*static_cast<const double *>(value))) : static_cast<double>(*static_cast<const Type *>(value));
};
} else if constexpr(std::is_enum_v<Type>) {
node.conversion_helper = +[](void *bin, const void *value) {
return bin ? static_cast<double>(*static_cast<Type *>(bin) = static_cast<Type>(static_cast<std::underlying_type_t<Type>>(*static_cast<const double *>(value)))) : static_cast<double>(*static_cast<const Type *>(value));
};
}
if constexpr(!std::is_same_v<Type, void> && !std::is_function_v<Type>) {
node.from_void = +[](const meta_ctx &ctx, void *element, const void *as_const) {
if(element) {
return meta_any{ctx, std::in_place_type<std::decay_t<Type> &>, *static_cast<std::decay_t<Type> *>(element)};
}
return meta_any{ctx, std::in_place_type<const std::decay_t<Type> &>, *static_cast<const std::decay_t<Type> *>(as_const)};
};
}
if constexpr(is_complete_v<meta_template_traits<Type>>) {
node.templ = meta_template_node{
meta_template_traits<Type>::args_type::size,
&resolve<typename meta_template_traits<Type>::class_type>,
+[](const meta_context &area, const std::size_t index) noexcept { return meta_arg_node(area, typename meta_template_traits<Type>::args_type{}, index); }};
}
return node;
}
} // namespace internal
/**

View File

@@ -6,7 +6,7 @@
namespace entt {
/*! @brief Empty class type used to request the _as ref_ policy. */
struct as_ref_t final {
struct as_ref_t {
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
@@ -20,7 +20,7 @@ struct as_ref_t final {
};
/*! @brief Empty class type used to request the _as cref_ policy. */
struct as_cref_t final {
struct as_cref_t {
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
@@ -34,7 +34,7 @@ struct as_cref_t final {
};
/*! @brief Empty class type used to request the _as-is_ policy. */
struct as_is_t final {
struct as_is_t {
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
@@ -48,7 +48,7 @@ struct as_is_t final {
};
/*! @brief Empty class type used to request the _as void_ policy. */
struct as_void_t final {
struct as_void_t {
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
@@ -61,22 +61,6 @@ struct as_void_t final {
*/
};
/**
* @brief Provides the member constant `value` to true if a type also is a meta
* policy, false otherwise.
* @tparam Type Type to check.
*/
template<typename Type>
struct is_meta_policy
: std::bool_constant<std::is_same_v<Type, as_ref_t> || std::is_same_v<Type, as_cref_t> || std::is_same_v<Type, as_is_t> || std::is_same_v<Type, as_void_t>> {};
/**
* @brief Helper variable template.
* @tparam Type Type to check.
*/
template<typename Type>
inline constexpr bool is_meta_policy_v = is_meta_policy<Type>::value;
} // namespace entt
#endif

View File

@@ -3,10 +3,7 @@
#include <cstddef>
#include <iterator>
#include <utility>
#include "../core/fwd.hpp"
#include "../core/iterator.hpp"
#include "context.hpp"
namespace entt {
@@ -17,119 +14,50 @@ namespace entt {
namespace internal {
template<typename Type, typename It>
template<typename Type, typename Node>
struct meta_range_iterator final {
using difference_type = std::ptrdiff_t;
using value_type = std::pair<id_type, Type>;
using value_type = Type;
using pointer = input_iterator_pointer<value_type>;
using reference = value_type;
using iterator_category = std::input_iterator_tag;
using node_type = Node;
constexpr meta_range_iterator() noexcept
: it{},
ctx{} {}
meta_range_iterator() ENTT_NOEXCEPT
: it{} {}
constexpr meta_range_iterator(const meta_ctx &area, const It iter) noexcept
: it{iter},
ctx{&area} {}
meta_range_iterator(node_type *head) ENTT_NOEXCEPT
: it{head} {}
constexpr meta_range_iterator &operator++() noexcept {
return ++it, *this;
meta_range_iterator &operator++() ENTT_NOEXCEPT {
return (it = it->next), *this;
}
constexpr meta_range_iterator operator++(int) noexcept {
meta_range_iterator operator++(int) ENTT_NOEXCEPT {
meta_range_iterator orig = *this;
return ++(*this), orig;
}
constexpr meta_range_iterator &operator--() noexcept {
return --it, *this;
[[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
return it;
}
constexpr meta_range_iterator operator--(int) noexcept {
meta_range_iterator orig = *this;
return operator--(), orig;
}
constexpr meta_range_iterator &operator+=(const difference_type value) noexcept {
it += value;
return *this;
}
constexpr meta_range_iterator operator+(const difference_type value) const noexcept {
meta_range_iterator copy = *this;
return (copy += value);
}
constexpr meta_range_iterator &operator-=(const difference_type value) noexcept {
return (*this += -value);
}
constexpr meta_range_iterator operator-(const difference_type value) const noexcept {
return (*this + -value);
}
[[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept {
return {it[value].first, Type{*ctx, it[value].second}};
}
[[nodiscard]] constexpr pointer operator->() const noexcept {
[[nodiscard]] pointer operator->() const ENTT_NOEXCEPT {
return operator*();
}
[[nodiscard]] constexpr reference operator*() const noexcept {
return {it->first, Type{*ctx, it->second}};
[[nodiscard]] bool operator==(const meta_range_iterator &other) const ENTT_NOEXCEPT {
return it == other.it;
}
template<typename... Args>
friend constexpr std::ptrdiff_t operator-(const meta_range_iterator<Args...> &, const meta_range_iterator<Args...> &) noexcept;
template<typename... Args>
friend constexpr bool operator==(const meta_range_iterator<Args...> &, const meta_range_iterator<Args...> &) noexcept;
template<typename... Args>
friend constexpr bool operator<(const meta_range_iterator<Args...> &, const meta_range_iterator<Args...> &) noexcept;
[[nodiscard]] bool operator!=(const meta_range_iterator &other) const ENTT_NOEXCEPT {
return !(*this == other);
}
private:
It it;
const meta_ctx *ctx;
node_type *it;
};
template<typename... Args>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const meta_range_iterator<Args...> &lhs, const meta_range_iterator<Args...> &rhs) noexcept {
return lhs.it - rhs.it;
}
template<typename... Args>
[[nodiscard]] constexpr bool operator==(const meta_range_iterator<Args...> &lhs, const meta_range_iterator<Args...> &rhs) noexcept {
return lhs.it == rhs.it;
}
template<typename... Args>
[[nodiscard]] constexpr bool operator!=(const meta_range_iterator<Args...> &lhs, const meta_range_iterator<Args...> &rhs) noexcept {
return !(lhs == rhs);
}
template<typename... Args>
[[nodiscard]] constexpr bool operator<(const meta_range_iterator<Args...> &lhs, const meta_range_iterator<Args...> &rhs) noexcept {
return lhs.it < rhs.it;
}
template<typename... Args>
[[nodiscard]] constexpr bool operator>(const meta_range_iterator<Args...> &lhs, const meta_range_iterator<Args...> &rhs) noexcept {
return rhs < lhs;
}
template<typename... Args>
[[nodiscard]] constexpr bool operator<=(const meta_range_iterator<Args...> &lhs, const meta_range_iterator<Args...> &rhs) noexcept {
return !(lhs > rhs);
}
template<typename... Args>
[[nodiscard]] constexpr bool operator>=(const meta_range_iterator<Args...> &lhs, const meta_range_iterator<Args...> &rhs) noexcept {
return !(lhs < rhs);
}
} // namespace internal
/**
@@ -140,10 +68,57 @@ template<typename... Args>
/**
* @brief Iterable range to use to iterate all types of meta objects.
* @tparam Type Type of meta objects returned.
* @tparam It Type of forward iterator.
* @tparam Node Type of meta nodes iterated.
*/
template<typename Type, typename It>
using meta_range = iterable_adaptor<internal::meta_range_iterator<Type, It>>;
template<typename Type, typename Node = typename Type::node_type>
struct meta_range final {
/*! @brief Node type. */
using node_type = Node;
/*! @brief Input iterator type. */
using iterator = internal::meta_range_iterator<Type, Node>;
/*! @brief Constant input iterator type. */
using const_iterator = iterator;
/*! @brief Default constructor. */
meta_range() ENTT_NOEXCEPT = default;
/**
* @brief Constructs a meta range from a given node.
* @param head The underlying node with which to construct the range.
*/
meta_range(node_type *head) ENTT_NOEXCEPT
: node{head} {}
/**
* @brief Returns an iterator to the beginning.
* @return An iterator to the first meta object of the range.
*/
[[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT {
return iterator{node};
}
/*! @copydoc cbegin */
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
return cbegin();
}
/**
* @brief Returns an iterator to the end.
* @return An iterator to the element following the last meta object of the
* range.
*/
[[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT {
return iterator{};
}
/*! @copydoc cend */
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
return cend();
}
private:
node_type *node{nullptr};
};
} // namespace entt

View File

@@ -1,10 +1,9 @@
#ifndef ENTT_META_RESOLVE_HPP
#define ENTT_META_RESOLVE_HPP
#include <type_traits>
#include <algorithm>
#include "../core/type_info.hpp"
#include "../locator/locator.hpp"
#include "context.hpp"
#include "ctx.hpp"
#include "meta.hpp"
#include "node.hpp"
#include "range.hpp"
@@ -14,78 +13,34 @@ namespace entt {
/**
* @brief Returns the meta type associated with a given type.
* @tparam Type Type to use to search for a meta type.
* @param ctx The context from which to search for meta types.
* @return The meta type associated with the given type, if any.
*/
template<typename Type>
[[nodiscard]] meta_type resolve(const meta_ctx &ctx) noexcept {
auto &&context = internal::meta_context::from(ctx);
return {ctx, internal::resolve<std::remove_cv_t<std::remove_reference_t<Type>>>(context)};
}
/**
* @brief Returns the meta type associated with a given type.
* @tparam Type Type to use to search for a meta type.
* @return The meta type associated with the given type, if any.
*/
template<typename Type>
[[nodiscard]] meta_type resolve() noexcept {
return resolve<Type>(locator<meta_ctx>::value_or());
}
/**
* @brief Returns a range to use to visit all meta types.
* @param ctx The context from which to search for meta types.
* @return An iterable range to use to visit all meta types.
*/
[[nodiscard]] inline meta_range<meta_type, typename decltype(internal::meta_context::value)::const_iterator> resolve(const meta_ctx &ctx) noexcept {
auto &&context = internal::meta_context::from(ctx);
return {{ctx, context.value.cbegin()}, {ctx, context.value.cend()}};
[[nodiscard]] meta_type resolve() ENTT_NOEXCEPT {
return internal::meta_node<std::remove_cv_t<std::remove_reference_t<Type>>>::resolve();
}
/**
* @brief Returns a range to use to visit all meta types.
* @return An iterable range to use to visit all meta types.
*/
[[nodiscard]] inline meta_range<meta_type, typename decltype(internal::meta_context::value)::const_iterator> resolve() noexcept {
return resolve(locator<meta_ctx>::value_or());
[[nodiscard]] inline meta_range<meta_type> resolve() ENTT_NOEXCEPT {
return *internal::meta_context::global();
}
/**
* @brief Returns the meta type associated with a given identifier, if any.
* @param ctx The context from which to search for meta types.
* @param id Unique identifier.
* @return The meta type associated with the given identifier, if any.
*/
[[nodiscard]] inline meta_type resolve(const meta_ctx &ctx, const id_type id) noexcept {
for(auto &&curr: resolve(ctx)) {
if(curr.second.id() == id) {
return curr.second;
[[nodiscard]] inline meta_type resolve(const id_type id) ENTT_NOEXCEPT {
for(auto &&curr: resolve()) {
if(curr.id() == id) {
return curr;
}
}
return meta_type{};
}
/**
* @brief Returns the meta type associated with a given identifier, if any.
* @param id Unique identifier.
* @return The meta type associated with the given identifier, if any.
*/
[[nodiscard]] inline meta_type resolve(const id_type id) noexcept {
return resolve(locator<meta_ctx>::value_or(), id);
}
/**
* @brief Returns the meta type associated with a given type info object.
* @param ctx The context from which to search for meta types.
* @param info The type info object of the requested type.
* @return The meta type associated with the given type info object, if any.
*/
[[nodiscard]] inline meta_type resolve(const meta_ctx &ctx, const type_info &info) noexcept {
auto &&context = internal::meta_context::from(ctx);
const auto *elem = internal::try_resolve(context, info);
return elem ? meta_type{ctx, *elem} : meta_type{};
return {};
}
/**
@@ -93,8 +48,14 @@ template<typename Type>
* @param info The type info object of the requested type.
* @return The meta type associated with the given type info object, if any.
*/
[[nodiscard]] inline meta_type resolve(const type_info &info) noexcept {
return resolve(locator<meta_ctx>::value_or(), info);
[[nodiscard]] inline meta_type resolve(const type_info &info) ENTT_NOEXCEPT {
for(auto &&curr: resolve()) {
if(curr.info() == info) {
return curr;
}
}
return {};
}
} // namespace entt

View File

@@ -30,6 +30,7 @@ struct meta_associative_container_traits;
/**
* @brief Provides the member constant `value` to true if a given type is a
* 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

@@ -5,34 +5,14 @@
#include <functional>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "../core/type_traits.hpp"
#include "../locator/locator.hpp"
#include "meta.hpp"
#include "node.hpp"
#include "policy.hpp"
namespace entt {
/**
* @brief Meta function descriptor traits.
* @tparam Ret Function return type.
* @tparam Args Function arguments.
* @tparam Static Function staticness.
* @tparam Const Function constness.
*/
template<typename Ret, typename Args, bool Static, bool Const>
struct meta_function_descriptor_traits {
/*! @brief Meta function return type. */
using return_type = Ret;
/*! @brief Meta function arguments. */
using args_type = Args;
/*! @brief True if the meta function is static, false otherwise. */
static constexpr bool is_static = Static;
/*! @brief True if the meta function is const, false otherwise. */
static constexpr bool is_const = Const;
};
/*! @brief Primary template isn't defined on purpose. */
template<typename, typename>
struct meta_function_descriptor;
@@ -45,12 +25,17 @@ struct meta_function_descriptor;
* @tparam Args Function arguments.
*/
template<typename Type, typename Ret, typename Class, typename... Args>
struct meta_function_descriptor<Type, Ret (Class::*)(Args...) const>
: meta_function_descriptor_traits<
Ret,
std::conditional_t<std::is_base_of_v<Class, Type>, type_list<Args...>, type_list<const Class &, Args...>>,
!std::is_base_of_v<Class, Type>,
true> {};
struct meta_function_descriptor<Type, Ret (Class::*)(Args...) const> {
/*! @brief Meta function return type. */
using return_type = Ret;
/*! @brief Meta function arguments. */
using args_type = std::conditional_t<std::is_base_of_v<Class, Type>, type_list<Args...>, type_list<const Class &, Args...>>;
/*! @brief True if the meta function is const, false otherwise. */
static constexpr auto is_const = true;
/*! @brief True if the meta function is static, false otherwise. */
static constexpr auto is_static = !std::is_base_of_v<Class, Type>;
};
/**
* @brief Meta function descriptor.
@@ -60,12 +45,17 @@ struct meta_function_descriptor<Type, Ret (Class::*)(Args...) const>
* @tparam Args Function arguments.
*/
template<typename Type, typename Ret, typename Class, typename... Args>
struct meta_function_descriptor<Type, Ret (Class::*)(Args...)>
: meta_function_descriptor_traits<
Ret,
std::conditional_t<std::is_base_of_v<Class, Type>, type_list<Args...>, type_list<Class &, Args...>>,
!std::is_base_of_v<Class, Type>,
false> {};
struct meta_function_descriptor<Type, Ret (Class::*)(Args...)> {
/*! @brief Meta function return type. */
using return_type = Ret;
/*! @brief Meta function arguments. */
using args_type = std::conditional_t<std::is_base_of_v<Class, Type>, type_list<Args...>, type_list<Class &, Args...>>;
/*! @brief True if the meta function is const, false otherwise. */
static constexpr auto is_const = false;
/*! @brief True if the meta function is static, false otherwise. */
static constexpr auto is_static = !std::is_base_of_v<Class, Type>;
};
/**
* @brief Meta function descriptor.
@@ -74,12 +64,17 @@ struct meta_function_descriptor<Type, Ret (Class::*)(Args...)>
* @tparam Ret Data member type.
*/
template<typename Type, typename Ret, typename Class>
struct meta_function_descriptor<Type, Ret Class::*>
: meta_function_descriptor_traits<
Ret &,
std::conditional_t<std::is_base_of_v<Class, Type>, type_list<>, type_list<Class &>>,
!std::is_base_of_v<Class, Type>,
false> {};
struct meta_function_descriptor<Type, Ret Class::*> {
/*! @brief Meta data return type. */
using return_type = Ret &;
/*! @brief Meta data arguments. */
using args_type = std::conditional_t<std::is_base_of_v<Class, Type>, type_list<>, type_list<Class &>>;
/*! @brief True if the meta data is const, false otherwise. */
static constexpr auto is_const = false;
/*! @brief True if the meta data is static, false otherwise. */
static constexpr auto is_static = !std::is_base_of_v<Class, Type>;
};
/**
* @brief Meta function descriptor.
@@ -89,15 +84,17 @@ struct meta_function_descriptor<Type, Ret Class::*>
* @tparam Args Other function arguments.
*/
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_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>)> {};
struct meta_function_descriptor<Type, Ret (*)(MaybeType, Args...)> {
/*! @brief Meta function return type. */
using return_type = Ret;
/*! @brief Meta function arguments. */
using args_type = 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...>>;
/*! @brief True if the meta function is const, false otherwise. */
static constexpr auto is_const = std::is_base_of_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type> && std::is_const_v<std::remove_reference_t<MaybeType>>;
/*! @brief True if the meta function is static, false otherwise. */
static constexpr auto is_static = !std::is_base_of_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type>;
};
/**
* @brief Meta function descriptor.
@@ -105,12 +102,17 @@ struct meta_function_descriptor<Type, Ret (*)(MaybeType, Args...)>
* @tparam Ret Function return type.
*/
template<typename Type, typename Ret>
struct meta_function_descriptor<Type, Ret (*)()>
: meta_function_descriptor_traits<
Ret,
type_list<>,
true,
false> {};
struct meta_function_descriptor<Type, Ret (*)()> {
/*! @brief Meta function return type. */
using return_type = Ret;
/*! @brief Meta function arguments. */
using args_type = type_list<>;
/*! @brief True if the meta function is const, false otherwise. */
static constexpr auto is_const = false;
/*! @brief True if the meta function is static, false otherwise. */
static constexpr auto is_static = true;
};
/**
* @brief Meta function helper.
@@ -153,65 +155,34 @@ using meta_function_helper_t = typename meta_function_helper<Type, Candidate>::t
/**
* @brief Wraps a value depending on the given policy.
*
* This function always returns a wrapped value in the requested context.<br/>
* Therefore, if the passed value is itself a wrapped object with a different
* context, it undergoes a rebinding to the requested context.
*
* @tparam Policy Optional policy (no policy set by default).
* @tparam Type Type of value to wrap.
* @param ctx The context from which to search for meta types.
* @param value Value to wrap.
* @return A meta any containing the returned value, if any.
*/
template<typename Policy = as_is_t, typename Type>
[[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_dispatch(const meta_ctx &ctx, [[maybe_unused]] Type &&value) {
meta_any meta_dispatch([[maybe_unused]] Type &&value) {
if constexpr(std::is_same_v<Policy, as_void_t>) {
return meta_any{ctx, std::in_place_type<void>};
return meta_any{std::in_place_type<void>};
} else if constexpr(std::is_same_v<Policy, as_ref_t>) {
return meta_any{ctx, std::in_place_type<Type>, value};
return meta_any{std::in_place_type<Type>, std::forward<Type>(value)};
} else if constexpr(std::is_same_v<Policy, as_cref_t>) {
static_assert(std::is_lvalue_reference_v<Type>, "Invalid type");
return meta_any{ctx, std::in_place_type<const std::remove_reference_t<Type> &>, std::as_const(value)};
return meta_any{std::in_place_type<const std::remove_reference_t<Type> &>, std::as_const(value)};
} else {
return meta_any{ctx, std::forward<Type>(value)};
static_assert(std::is_same_v<Policy, as_is_t>, "Policy not supported");
return meta_any{std::forward<Type>(value)};
}
}
/**
* @brief Wraps a value depending on the given policy.
* @tparam Policy Optional policy (no policy set by default).
* @tparam Type Type of value to wrap.
* @param value Value to wrap.
* @return A meta any containing the returned value, if any.
*/
template<typename Policy = as_is_t, typename Type>
[[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));
}
/**
* @brief Returns the meta type of the i-th element of a list of arguments.
* @tparam Type Type list of the actual types of arguments.
* @param ctx The context from which to search for meta types.
* @param index The index of the element for which to return the meta type.
* @return The meta type of the i-th element of the list of arguments.
*/
template<typename Type>
[[nodiscard]] static meta_type meta_arg(const meta_ctx &ctx, const std::size_t index) noexcept {
auto &&context = internal::meta_context::from(ctx);
return {ctx, internal::meta_arg_node(context, Type{}, index)};
}
/**
* @brief Returns the meta type of the i-th element of a list of arguments.
* @tparam Type Type list of the actual types of arguments.
* @param index The index of the element for which to return the meta type.
* @return The meta type of the i-th element of the list of arguments.
*/
template<typename Type>
[[nodiscard]] static meta_type meta_arg(const std::size_t index) noexcept {
return meta_arg<Type>(locator<meta_ctx>::value_or(), index);
[[nodiscard]] static meta_type meta_arg(const std::size_t index) ENTT_NOEXCEPT {
return internal::meta_arg_node(Type{}, index);
}
/**
@@ -259,60 +230,41 @@ template<typename Type, auto Data>
/**
* @brief Gets the value of a given variable.
*
* @warning
* The context provided is used only for the return type.<br/>
* It's up to the caller to bind the arguments to the right context(s).
*
* @tparam Type Reflected type to which the variable is associated.
* @tparam Data The actual variable to get.
* @tparam Policy Optional policy (no policy set by default).
* @param ctx The context from which to search for meta types.
* @param instance An opaque instance of the underlying type, if required.
* @return A meta any containing the value of the underlying variable.
*/
template<typename Type, auto Data, typename Policy = as_is_t>
[[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_getter(const meta_ctx &ctx, [[maybe_unused]] meta_handle instance) {
[[nodiscard]] meta_any meta_getter([[maybe_unused]] meta_handle instance) {
if constexpr(std::is_member_pointer_v<decltype(Data)> || std::is_function_v<std::remove_reference_t<std::remove_pointer_t<decltype(Data)>>>) {
if constexpr(!std::is_array_v<std::remove_cv_t<std::remove_reference_t<std::invoke_result_t<decltype(Data), Type &>>>>) {
if constexpr(std::is_invocable_v<decltype(Data), Type &>) {
if(auto *clazz = instance->try_cast<Type>(); clazz) {
return meta_dispatch<Policy>(ctx, std::invoke(Data, *clazz));
return meta_dispatch<Policy>(std::invoke(Data, *clazz));
}
}
if constexpr(std::is_invocable_v<decltype(Data), const Type &>) {
if(auto *fallback = instance->try_cast<const Type>(); fallback) {
return meta_dispatch<Policy>(ctx, std::invoke(Data, *fallback));
return meta_dispatch<Policy>(std::invoke(Data, *fallback));
}
}
}
return meta_any{meta_ctx_arg, ctx};
return meta_any{};
} else if constexpr(std::is_pointer_v<decltype(Data)>) {
if constexpr(std::is_array_v<std::remove_pointer_t<decltype(Data)>>) {
return meta_any{meta_ctx_arg, ctx};
return meta_any{};
} else {
return meta_dispatch<Policy>(ctx, *Data);
return meta_dispatch<Policy>(*Data);
}
} else {
return meta_dispatch<Policy>(ctx, Data);
return meta_dispatch<Policy>(Data);
}
}
/**
* @brief Gets the value of a given variable.
* @tparam Type Reflected type to which the variable is associated.
* @tparam Data The actual variable to get.
* @tparam Policy Optional policy (no policy set by default).
* @param instance An opaque instance of the underlying type, if required.
* @return A meta any containing the value of the underlying variable.
*/
template<typename Type, auto Data, typename Policy = as_is_t>
[[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_getter(meta_handle instance) {
return meta_getter<Type, Data, Policy>(locator<meta_ctx>::value_or(), std::move(instance));
}
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
@@ -320,44 +272,44 @@ template<typename Type, auto Data, typename Policy = as_is_t>
namespace internal {
template<typename Policy, typename Candidate, typename... Args>
[[nodiscard]] meta_any meta_invoke_with_args(const meta_ctx &ctx, Candidate &&candidate, Args &&...args) {
if constexpr(std::is_same_v<decltype(std::invoke(std::forward<Candidate>(candidate), args...)), void>) {
std::invoke(std::forward<Candidate>(candidate), args...);
return meta_any{ctx, std::in_place_type<void>};
template<typename Type, typename Policy, typename Candidate, typename... Args>
[[nodiscard]] meta_any meta_invoke_with_args(Candidate &&candidate, Args &&...args) {
if constexpr(std::is_same_v<std::invoke_result_t<decltype(candidate), Args...>, void>) {
std::invoke(candidate, args...);
return meta_any{std::in_place_type<void>};
} else {
return meta_dispatch<Policy>(ctx, std::invoke(std::forward<Candidate>(candidate), args...));
return meta_dispatch<Policy>(std::invoke(candidate, args...));
}
}
template<typename Type, typename Policy, typename Candidate, std::size_t... Index>
[[nodiscard]] meta_any meta_invoke(const meta_ctx &ctx, [[maybe_unused]] meta_handle instance, Candidate &&candidate, [[maybe_unused]] meta_any *args, std::index_sequence<Index...>) {
[[nodiscard]] meta_any meta_invoke([[maybe_unused]] meta_handle instance, Candidate &&candidate, [[maybe_unused]] meta_any *args, std::index_sequence<Index...>) {
using descriptor = meta_function_helper_t<Type, std::remove_reference_t<Candidate>>;
if constexpr(std::is_invocable_v<std::remove_reference_t<Candidate>, const Type &, type_list_element_t<Index, typename descriptor::args_type>...>) {
if(const auto *const clazz = instance->try_cast<const Type>(); clazz && ((args + Index)->allow_cast<type_list_element_t<Index, typename descriptor::args_type>>() && ...)) {
return meta_invoke_with_args<Policy>(ctx, std::forward<Candidate>(candidate), *clazz, (args + Index)->cast<type_list_element_t<Index, typename descriptor::args_type>>()...);
return meta_invoke_with_args<Type, Policy>(std::forward<Candidate>(candidate), *clazz, (args + Index)->cast<type_list_element_t<Index, typename descriptor::args_type>>()...);
}
} else if constexpr(std::is_invocable_v<std::remove_reference_t<Candidate>, Type &, type_list_element_t<Index, typename descriptor::args_type>...>) {
if(auto *const clazz = instance->try_cast<Type>(); clazz && ((args + Index)->allow_cast<type_list_element_t<Index, typename descriptor::args_type>>() && ...)) {
return meta_invoke_with_args<Policy>(ctx, std::forward<Candidate>(candidate), *clazz, (args + Index)->cast<type_list_element_t<Index, typename descriptor::args_type>>()...);
return meta_invoke_with_args<Type, Policy>(std::forward<Candidate>(candidate), *clazz, (args + Index)->cast<type_list_element_t<Index, typename descriptor::args_type>>()...);
}
} else {
if(((args + Index)->allow_cast<type_list_element_t<Index, typename descriptor::args_type>>() && ...)) {
return meta_invoke_with_args<Policy>(ctx, std::forward<Candidate>(candidate), (args + Index)->cast<type_list_element_t<Index, typename descriptor::args_type>>()...);
return meta_invoke_with_args<Type, Policy>(std::forward<Candidate>(candidate), (args + Index)->cast<type_list_element_t<Index, typename descriptor::args_type>>()...);
}
}
return meta_any{meta_ctx_arg, ctx};
return meta_any{};
}
template<typename Type, typename... Args, std::size_t... Index>
[[nodiscard]] meta_any meta_construct(const meta_ctx &ctx, meta_any *const args, std::index_sequence<Index...>) {
[[nodiscard]] meta_any meta_construct(meta_any *const args, std::index_sequence<Index...>) {
if(((args + Index)->allow_cast<Args>() && ...)) {
return meta_any{ctx, std::in_place_type<Type>, (args + Index)->cast<Args>()...};
return meta_any{std::in_place_type<Type>, (args + Index)->cast<Args>()...};
}
return meta_any{meta_ctx_arg, ctx};
return meta_any{};
}
} // namespace internal
@@ -367,27 +319,6 @@ template<typename Type, typename... Args, std::size_t... Index>
* @endcond
*/
/**
* @brief Tries to _invoke_ an object given a list of erased parameters.
*
* @warning
* The context provided is used only for the return type.<br/>
* It's up to the caller to bind the arguments to the right context(s).
*
* @tparam Type Reflected type to which the object to _invoke_ is associated.
* @tparam Policy Optional policy (no policy set by default).
* @param ctx The context from which to search for meta types.
* @tparam Candidate The type of the actual object to _invoke_.
* @param instance An opaque instance of the underlying type, if required.
* @param candidate The actual object to _invoke_.
* @param args Parameters to use to _invoke_ the object.
* @return A meta any containing the returned value, if any.
*/
template<typename Type, typename Policy = as_is_t, typename Candidate>
[[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_invoke(const meta_ctx &ctx, meta_handle instance, Candidate &&candidate, meta_any *const args) {
return internal::meta_invoke<Type, Policy>(ctx, std::move(instance), std::forward<Candidate>(candidate), args, std::make_index_sequence<meta_function_helper_t<Type, std::remove_reference_t<Candidate>>::args_type::size>{});
}
/**
* @brief Tries to _invoke_ an object given a list of erased parameters.
* @tparam Type Reflected type to which the object to _invoke_ is associated.
@@ -399,28 +330,8 @@ template<typename Type, typename Policy = as_is_t, typename Candidate>
* @return A meta any containing the returned value, if any.
*/
template<typename Type, typename Policy = as_is_t, typename Candidate>
[[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_invoke(meta_handle instance, Candidate &&candidate, meta_any *const args) {
return meta_invoke<Type, Policy>(locator<meta_ctx>::value_or(), std::move(instance), std::forward<Candidate>(candidate), args);
}
/**
* @brief Tries to invoke a function given a list of erased parameters.
*
* @warning
* The context provided is used only for the return type.<br/>
* It's up to the caller to bind the arguments to the right context(s).
*
* @tparam Type Reflected type to which the function is associated.
* @tparam Candidate The actual function to invoke.
* @tparam Policy Optional policy (no policy set by default).
* @param ctx The context from which to search for meta types.
* @param instance An opaque instance of the underlying type, if required.
* @param args Parameters to use to invoke the function.
* @return A meta any containing the returned value, if any.
*/
template<typename Type, auto Candidate, typename Policy = as_is_t>
[[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_invoke(const meta_ctx &ctx, meta_handle instance, meta_any *const args) {
return internal::meta_invoke<Type, Policy>(ctx, std::move(instance), Candidate, args, std::make_index_sequence<meta_function_helper_t<Type, std::remove_reference_t<decltype(Candidate)>>::args_type::size>{});
[[nodiscard]] meta_any meta_invoke([[maybe_unused]] meta_handle instance, Candidate &&candidate, [[maybe_unused]] meta_any *const args) {
return internal::meta_invoke<Type, Policy>(std::move(instance), std::forward<Candidate>(candidate), args, std::make_index_sequence<meta_function_helper_t<Type, std::remove_reference_t<Candidate>>::args_type::size>{});
}
/**
@@ -433,26 +344,8 @@ template<typename Type, auto Candidate, typename Policy = as_is_t>
* @return A meta any containing the returned value, if any.
*/
template<typename Type, auto Candidate, typename Policy = as_is_t>
[[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_invoke(meta_handle instance, meta_any *const args) {
return meta_invoke<Type, Candidate, Policy>(locator<meta_ctx>::value_or(), std::move(instance), args);
}
/**
* @brief Tries to construct an instance given a list of erased parameters.
*
* @warning
* The context provided is used only for the return type.<br/>
* It's up to the caller to bind the arguments to the right context(s).
*
* @tparam Type Actual type of the instance to construct.
* @tparam Args Types of arguments expected.
* @param ctx The context from which to search for meta types.
* @param args Parameters to use to construct the instance.
* @return A meta any containing the new instance, if any.
*/
template<typename Type, typename... Args>
[[nodiscard]] meta_any meta_construct(const meta_ctx &ctx, meta_any *const args) {
return internal::meta_construct<Type, Args...>(ctx, args, std::index_sequence_for<Args...>{});
[[nodiscard]] meta_any meta_invoke(meta_handle instance, meta_any *const args) {
return internal::meta_invoke<Type, Policy>(std::move(instance), Candidate, args, std::make_index_sequence<meta_function_helper_t<Type, std::remove_reference_t<decltype(Candidate)>>::args_type::size>{});
}
/**
@@ -464,66 +357,27 @@ template<typename Type, typename... Args>
*/
template<typename Type, typename... Args>
[[nodiscard]] meta_any meta_construct(meta_any *const args) {
return meta_construct<Type, Args...>(locator<meta_ctx>::value_or(), args);
return internal::meta_construct<Type, Args...>(args, std::index_sequence_for<Args...>{});
}
/**
* @brief Tries to construct an instance given a list of erased parameters.
*
* @warning
* The context provided is used only for the return type.<br/>
* It's up to the caller to bind the arguments to the right context(s).
*
* @tparam Type Reflected type to which the object to _invoke_ is associated.
* @tparam Policy Optional policy (no policy set by default).
* @tparam Candidate The type of the actual object to _invoke_.
* @param ctx The context from which to search for meta types.
* @param candidate The actual object to _invoke_.
* @param args Parameters to use to _invoke_ the object.
* @param candidate The actual object to _invoke_.
* @return A meta any containing the returned value, if any.
*/
template<typename Type, typename Policy = as_is_t, typename Candidate>
[[nodiscard]] meta_any meta_construct(const meta_ctx &ctx, Candidate &&candidate, meta_any *const args) {
if constexpr(meta_function_helper_t<Type, Candidate>::is_static || std::is_class_v<std::remove_cv_t<std::remove_reference_t<Candidate>>>) {
return internal::meta_invoke<Type, Policy>(ctx, {}, std::forward<Candidate>(candidate), args, std::make_index_sequence<meta_function_helper_t<Type, std::remove_reference_t<Candidate>>::args_type::size>{});
[[nodiscard]] meta_any meta_construct(Candidate &&candidate, meta_any *const args) {
if constexpr(meta_function_helper_t<Type, Candidate>::is_static) {
return internal::meta_invoke<Type, Policy>({}, std::forward<Candidate>(candidate), args, std::make_index_sequence<meta_function_helper_t<Type, std::remove_reference_t<Candidate>>::args_type::size>{});
} else {
return internal::meta_invoke<Type, Policy>(ctx, *args, std::forward<Candidate>(candidate), args + 1u, std::make_index_sequence<meta_function_helper_t<Type, std::remove_reference_t<Candidate>>::args_type::size>{});
return internal::meta_invoke<Type, Policy>(*args, std::forward<Candidate>(candidate), args + 1u, std::make_index_sequence<meta_function_helper_t<Type, std::remove_reference_t<Candidate>>::args_type::size>{});
}
}
/**
* @brief Tries to construct an instance given a list of erased parameters.
* @tparam Type Reflected type to which the object to _invoke_ is associated.
* @tparam Policy Optional policy (no policy set by default).
* @tparam Candidate The type of the actual object to _invoke_.
* @param candidate The actual object to _invoke_.
* @param args Parameters to use to _invoke_ the object.
* @return A meta any containing the returned value, if any.
*/
template<typename Type, typename Policy = as_is_t, typename Candidate>
[[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_construct(Candidate &&candidate, meta_any *const args) {
return meta_construct<Type, Policy>(locator<meta_ctx>::value_or(), std::forward<Candidate>(candidate), args);
}
/**
* @brief Tries to construct an instance given a list of erased parameters.
*
* @warning
* The context provided is used only for the return type.<br/>
* It's up to the caller to bind the arguments to the right context(s).
*
* @tparam Type Reflected type to which the function is associated.
* @tparam Candidate The actual function to invoke.
* @tparam Policy Optional policy (no policy set by default).
* @param ctx The context from which to search for meta types.
* @param args Parameters to use to invoke the function.
* @return A meta any containing the returned value, if any.
*/
template<typename Type, auto Candidate, typename Policy = as_is_t>
[[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_construct(const meta_ctx &ctx, meta_any *const args) {
return meta_construct<Type, Policy>(ctx, Candidate, args);
}
/**
* @brief Tries to construct an instance given a list of erased parameters.
* @tparam Type Reflected type to which the function is associated.
@@ -533,8 +387,8 @@ template<typename Type, auto Candidate, typename Policy = as_is_t>
* @return A meta any containing the returned value, if any.
*/
template<typename Type, auto Candidate, typename Policy = as_is_t>
[[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_construct(meta_any *const args) {
return meta_construct<Type, Candidate, Policy>(locator<meta_ctx>::value_or(), args);
[[nodiscard]] meta_any meta_construct(meta_any *const args) {
return meta_construct<Type, Policy>(Candidate, args);
}
} // namespace entt

View File

@@ -1,11 +1,12 @@
#ifndef ENTT_POLY_FWD_HPP
#define ENTT_POLY_FWD_HPP
#include <cstddef>
#include <cstdint>
#include <type_traits>
namespace entt {
template<typename, std::size_t Len = sizeof(double[2]), std::size_t = alignof(double[2])>
template<typename, std::size_t Len = sizeof(double[2]), std::size_t = alignof(typename std::aligned_storage_t<Len + !Len>)>
class basic_poly;
/**

View File

@@ -6,6 +6,7 @@
#include <tuple>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "../core/any.hpp"
#include "../core/type_info.hpp"
#include "../core/type_traits.hpp"
@@ -19,7 +20,7 @@ struct poly_inspector {
* @brief Generic conversion operator (definition only).
* @tparam Type Type to which conversion is requested.
*/
template<typename Type>
template<class Type>
operator Type &&() const;
/**
@@ -63,11 +64,11 @@ class poly_vtable {
static auto vtable_entry(Ret (inspector::*)(Args...) const) -> Ret (*)(const basic_any<Len, Align> &, Args...);
template<auto... Candidate>
static auto make_vtable(value_list<Candidate...>) noexcept
static auto make_vtable(value_list<Candidate...>) ENTT_NOEXCEPT
-> decltype(std::make_tuple(vtable_entry(Candidate)...));
template<typename... Func>
[[nodiscard]] static constexpr auto make_vtable(type_list<Func...>) noexcept {
[[nodiscard]] static constexpr auto make_vtable(type_list<Func...>) ENTT_NOEXCEPT {
if constexpr(sizeof...(Func) == 0u) {
return decltype(make_vtable(typename Concept::template impl<inspector>{})){};
} else if constexpr((std::is_function_v<Func> && ...)) {
@@ -76,7 +77,7 @@ class poly_vtable {
}
template<typename Type, auto Candidate, typename Ret, typename Any, typename... Args>
static void fill_vtable_entry(Ret (*&entry)(Any &, Args...)) noexcept {
static void fill_vtable_entry(Ret (*&entry)(Any &, Args...)) ENTT_NOEXCEPT {
if constexpr(std::is_invocable_r_v<Ret, decltype(Candidate), Args...>) {
entry = +[](Any &, Args... args) -> Ret {
return std::invoke(Candidate, std::forward<Args>(args)...);
@@ -89,7 +90,7 @@ class poly_vtable {
}
template<typename Type, auto... Index>
[[nodiscard]] static auto fill_vtable(std::index_sequence<Index...>) noexcept {
[[nodiscard]] static auto fill_vtable(std::index_sequence<Index...>) ENTT_NOEXCEPT {
vtable_type impl{};
(fill_vtable_entry<Type, value_list_element_v<Index, typename Concept::template impl<Type>>>(std::get<Index>(impl)), ...);
return impl;
@@ -108,7 +109,7 @@ public:
* @return A static virtual table for the given concept and type.
*/
template<typename Type>
[[nodiscard]] static type instance() noexcept {
[[nodiscard]] static type instance() ENTT_NOEXCEPT {
static_assert(std::is_same_v<Type, std::decay_t<Type>>, "Type differs from its decayed form");
static const vtable_type vtable = fill_vtable<Type>(std::make_index_sequence<Concept::template impl<Type>::size>{});
@@ -200,7 +201,7 @@ public:
using vtable_type = typename poly_vtable<Concept, Len, Align>::type;
/*! @brief Default constructor. */
basic_poly() noexcept
basic_poly() ENTT_NOEXCEPT
: storage{},
vtable{} {}
@@ -221,14 +222,14 @@ public:
* @param value An instance of an object to use to initialize the poly.
*/
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Type>>, basic_poly>>>
basic_poly(Type &&value) noexcept
basic_poly(Type &&value) ENTT_NOEXCEPT
: basic_poly{std::in_place_type<std::remove_cv_t<std::remove_reference_t<Type>>>, std::forward<Type>(value)} {}
/**
* @brief Returns the object type if any, `type_id<void>()` otherwise.
* @return The object type if any, `type_id<void>()` otherwise.
*/
[[nodiscard]] const type_info &type() const noexcept {
[[nodiscard]] const type_info &type() const ENTT_NOEXCEPT {
return storage.type();
}
@@ -236,12 +237,12 @@ public:
* @brief Returns an opaque pointer to the contained instance.
* @return An opaque pointer the contained instance, if any.
*/
[[nodiscard]] const void *data() const noexcept {
[[nodiscard]] const void *data() const ENTT_NOEXCEPT {
return storage.data();
}
/*! @copydoc data */
[[nodiscard]] void *data() noexcept {
[[nodiscard]] void *data() ENTT_NOEXCEPT {
return storage.data();
}
@@ -267,7 +268,7 @@ public:
* @brief Returns false if a poly is empty, true otherwise.
* @return False if the poly is empty, true otherwise.
*/
[[nodiscard]] explicit operator bool() const noexcept {
[[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT {
return static_cast<bool>(storage);
}
@@ -275,12 +276,12 @@ public:
* @brief Returns a pointer to the underlying concept.
* @return A pointer to the underlying concept.
*/
[[nodiscard]] concept_type *operator->() noexcept {
[[nodiscard]] concept_type *operator->() ENTT_NOEXCEPT {
return this;
}
/*! @copydoc operator-> */
[[nodiscard]] const concept_type *operator->() const noexcept {
[[nodiscard]] const concept_type *operator->() const ENTT_NOEXCEPT {
return this;
}
@@ -288,7 +289,7 @@ public:
* @brief Aliasing constructor.
* @return A poly that shares a reference to an unmanaged object.
*/
[[nodiscard]] basic_poly as_ref() noexcept {
[[nodiscard]] basic_poly as_ref() ENTT_NOEXCEPT {
basic_poly ref{};
ref.storage = storage.as_ref();
ref.vtable = vtable;
@@ -296,7 +297,7 @@ public:
}
/*! @copydoc as_ref */
[[nodiscard]] basic_poly as_ref() const noexcept {
[[nodiscard]] basic_poly as_ref() const ENTT_NOEXCEPT {
basic_poly ref{};
ref.storage = storage.as_ref();
ref.vtable = vtable;

View File

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

View File

@@ -4,6 +4,7 @@
#include <cstdint>
#include <type_traits>
#include <utility>
#include "../config/config.h"
namespace entt {
@@ -109,7 +110,7 @@ class process {
static_cast<Target *>(this)->aborted();
}
void next(...) const noexcept {}
void next(...) const ENTT_NOEXCEPT {}
protected:
/**
@@ -118,7 +119,7 @@ protected:
* The function is idempotent and it does nothing if the process isn't
* alive.
*/
void succeed() noexcept {
void succeed() ENTT_NOEXCEPT {
if(alive()) {
current = state::succeeded;
}
@@ -130,7 +131,7 @@ protected:
* The function is idempotent and it does nothing if the process isn't
* alive.
*/
void fail() noexcept {
void fail() ENTT_NOEXCEPT {
if(alive()) {
current = state::failed;
}
@@ -142,7 +143,7 @@ protected:
* The function is idempotent and it does nothing if the process isn't
* running.
*/
void pause() noexcept {
void pause() ENTT_NOEXCEPT {
if(current == state::running) {
current = state::paused;
}
@@ -154,7 +155,7 @@ protected:
* The function is idempotent and it does nothing if the process isn't
* paused.
*/
void unpause() noexcept {
void unpause() ENTT_NOEXCEPT {
if(current == state::paused) {
current = state::running;
}
@@ -165,7 +166,7 @@ public:
using delta_type = Delta;
/*! @brief Default destructor. */
virtual ~process() noexcept {
virtual ~process() ENTT_NOEXCEPT {
static_assert(std::is_base_of_v<process, Derived>, "Incorrect use of the class template");
}
@@ -191,7 +192,7 @@ public:
* @brief Returns true if a process is either running or paused.
* @return True if the process is still alive, false otherwise.
*/
[[nodiscard]] bool alive() const noexcept {
[[nodiscard]] bool alive() const ENTT_NOEXCEPT {
return current == state::running || current == state::paused;
}
@@ -199,7 +200,7 @@ public:
* @brief Returns true if a process is already terminated.
* @return True if the process is terminated, false otherwise.
*/
[[nodiscard]] bool finished() const noexcept {
[[nodiscard]] bool finished() const ENTT_NOEXCEPT {
return current == state::finished;
}
@@ -207,7 +208,7 @@ public:
* @brief Returns true if a process is currently paused.
* @return True if the process is paused, false otherwise.
*/
[[nodiscard]] bool paused() const noexcept {
[[nodiscard]] bool paused() const ENTT_NOEXCEPT {
return current == state::paused;
}
@@ -215,7 +216,7 @@ public:
* @brief Returns true if a process terminated with errors.
* @return True if the process terminated with errors, false otherwise.
*/
[[nodiscard]] bool rejected() const noexcept {
[[nodiscard]] bool rejected() const ENTT_NOEXCEPT {
return current == state::rejected;
}

View File

@@ -7,7 +7,7 @@
#include <type_traits>
#include <utility>
#include <vector>
#include "fwd.hpp"
#include "../config/config.h"
#include "process.hpp"
namespace entt {
@@ -39,11 +39,11 @@ namespace entt {
* @tparam Delta Type to use to provide elapsed time.
*/
template<typename Delta>
class basic_scheduler {
class scheduler {
struct process_handler {
using instance_type = std::unique_ptr<void, void (*)(void *)>;
using update_fn_type = bool(basic_scheduler &, std::size_t, Delta, void *);
using abort_fn_type = void(basic_scheduler &, std::size_t, bool);
using update_fn_type = bool(scheduler &, std::size_t, Delta, void *);
using abort_fn_type = void(scheduler &, std::size_t, bool);
using next_type = std::unique_ptr<process_handler>;
instance_type instance;
@@ -53,14 +53,14 @@ class basic_scheduler {
};
struct continuation {
continuation(process_handler *ref) noexcept
continuation(process_handler *ref) ENTT_NOEXCEPT
: handler{ref} {}
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)...}, &basic_scheduler::deleter<Proc>};
handler->next.reset(new process_handler{std::move(proc), &basic_scheduler::update<Proc>, &basic_scheduler::abort<Proc>, nullptr});
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});
handler = handler->next.get();
return *this;
}
@@ -75,7 +75,7 @@ class basic_scheduler {
};
template<typename Proc>
[[nodiscard]] static bool update(basic_scheduler &owner, std::size_t pos, const Delta delta, void *data) {
[[nodiscard]] static bool update(scheduler &owner, std::size_t pos, const Delta delta, void *data) {
auto *process = static_cast<Proc *>(owner.handlers[pos].instance.get());
process->tick(delta, data);
@@ -95,7 +95,7 @@ class basic_scheduler {
}
template<typename Proc>
static void abort(basic_scheduler &owner, std::size_t pos, const bool immediately) {
static void abort(scheduler &owner, std::size_t pos, const bool immediately) {
static_cast<Proc *>(owner.handlers[pos].instance.get())->abort(immediately);
}
@@ -105,26 +105,23 @@ class basic_scheduler {
}
public:
/*! @brief Unsigned integer type. */
using delta_type = Delta;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Default constructor. */
basic_scheduler()
: handlers{} {}
scheduler() = default;
/*! @brief Default move constructor. */
basic_scheduler(basic_scheduler &&) = default;
scheduler(scheduler &&) = default;
/*! @brief Default move assignment operator. @return This scheduler. */
basic_scheduler &operator=(basic_scheduler &&) = default;
scheduler &operator=(scheduler &&) = default;
/**
* @brief Number of processes currently scheduled.
* @return Number of processes currently scheduled.
*/
[[nodiscard]] size_type size() const noexcept {
[[nodiscard]] size_type size() const ENTT_NOEXCEPT {
return handlers.size();
}
@@ -132,7 +129,7 @@ public:
* @brief Returns true if at least a process is currently scheduled.
* @return True if there are scheduled processes, false otherwise.
*/
[[nodiscard]] bool empty() const noexcept {
[[nodiscard]] bool empty() const ENTT_NOEXCEPT {
return handlers.empty();
}
@@ -174,8 +171,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)...}, &basic_scheduler::deleter<Proc>};
auto &&ref = handlers.emplace_back(process_handler{std::move(proc), &basic_scheduler::update<Proc>, &basic_scheduler::abort<Proc>, nullptr});
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});
// forces the process to exit the uninitialized state
ref.update(*this, handlers.size() - 1u, {}, nullptr);
return continuation{&handlers.back()};
@@ -249,7 +246,7 @@ public:
* @param delta Elapsed time.
* @param data Optional data.
*/
void update(const delta_type delta, void *data = nullptr) {
void update(const Delta delta, void *data = nullptr) {
for(auto pos = handlers.size(); pos; --pos) {
const auto curr = pos - 1u;

View File

@@ -8,6 +8,7 @@
#include <tuple>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "../container/dense_map.hpp"
#include "../core/compressed_pair.hpp"
#include "../core/fwd.hpp"
@@ -38,108 +39,108 @@ public:
using difference_type = std::ptrdiff_t;
using iterator_category = std::input_iterator_tag;
constexpr resource_cache_iterator() noexcept = default;
resource_cache_iterator() ENTT_NOEXCEPT = default;
constexpr resource_cache_iterator(const It iter) noexcept
resource_cache_iterator(const It iter) ENTT_NOEXCEPT
: it{iter} {}
template<typename Other, typename = std::enable_if_t<!std::is_same_v<It, Other> && std::is_constructible_v<It, Other>>>
constexpr resource_cache_iterator(const resource_cache_iterator<std::remove_const_t<Type>, Other> &other) noexcept
resource_cache_iterator(const resource_cache_iterator<std::remove_const_t<Type>, Other> &other) ENTT_NOEXCEPT
: it{other.it} {}
constexpr resource_cache_iterator &operator++() noexcept {
resource_cache_iterator &operator++() ENTT_NOEXCEPT {
return ++it, *this;
}
constexpr resource_cache_iterator operator++(int) noexcept {
resource_cache_iterator operator++(int) ENTT_NOEXCEPT {
resource_cache_iterator orig = *this;
return ++(*this), orig;
}
constexpr resource_cache_iterator &operator--() noexcept {
resource_cache_iterator &operator--() ENTT_NOEXCEPT {
return --it, *this;
}
constexpr resource_cache_iterator operator--(int) noexcept {
resource_cache_iterator operator--(int) ENTT_NOEXCEPT {
resource_cache_iterator orig = *this;
return operator--(), orig;
}
constexpr resource_cache_iterator &operator+=(const difference_type value) noexcept {
resource_cache_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT {
it += value;
return *this;
}
constexpr resource_cache_iterator operator+(const difference_type value) const noexcept {
resource_cache_iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
resource_cache_iterator copy = *this;
return (copy += value);
}
constexpr resource_cache_iterator &operator-=(const difference_type value) noexcept {
resource_cache_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT {
return (*this += -value);
}
constexpr resource_cache_iterator operator-(const difference_type value) const noexcept {
resource_cache_iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
return (*this + -value);
}
[[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept {
[[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT {
return {it[value].first, resource<Type>{it[value].second}};
}
[[nodiscard]] constexpr reference operator*() const noexcept {
[[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
return (*this)[0];
}
[[nodiscard]] constexpr pointer operator->() const noexcept {
[[nodiscard]] pointer operator->() const ENTT_NOEXCEPT {
return operator*();
}
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 std::ptrdiff_t operator-(const resource_cache_iterator<TLhs, ILhs> &, const resource_cache_iterator<TRhs, IRhs> &) ENTT_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 bool operator==(const resource_cache_iterator<TLhs, ILhs> &, const resource_cache_iterator<TRhs, IRhs> &) ENTT_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 bool operator<(const resource_cache_iterator<TLhs, ILhs> &, const resource_cache_iterator<TRhs, IRhs> &) ENTT_NOEXCEPT;
private:
It it;
};
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
[[nodiscard]] std::ptrdiff_t operator-(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) ENTT_NOEXCEPT {
return lhs.it - rhs.it;
}
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator==(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
[[nodiscard]] bool operator==(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) ENTT_NOEXCEPT {
return lhs.it == rhs.it;
}
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator!=(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
[[nodiscard]] bool operator!=(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator<(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
[[nodiscard]] bool operator<(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) ENTT_NOEXCEPT {
return lhs.it < rhs.it;
}
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator>(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
[[nodiscard]] bool operator>(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) ENTT_NOEXCEPT {
return rhs < lhs;
}
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator<=(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
[[nodiscard]] bool operator<=(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) ENTT_NOEXCEPT {
return !(lhs > rhs);
}
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator>=(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
[[nodiscard]] bool operator>=(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) ENTT_NOEXCEPT {
return !(lhs < rhs);
}
@@ -158,7 +159,7 @@ template<typename... Lhs, typename... Rhs>
*/
template<typename Type, typename Loader, typename Allocator>
class resource_cache {
using alloc_traits = std::allocator_traits<Allocator>;
using alloc_traits = typename std::allocator_traits<Allocator>;
static_assert(std::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type");
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>;
@@ -234,47 +235,53 @@ public:
* @brief Returns the associated allocator.
* @return The associated allocator.
*/
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
[[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT {
return pool.first().get_allocator();
}
/**
* @brief Returns an iterator to the beginning.
*
* If the cache is empty, the returned iterator will be equal to `end()`.
* The returned iterator points to the first instance of the cache. If the
* cache is empty, the returned iterator will be equal to `end()`.
*
* @return An iterator to the first instance of the internal cache.
*/
[[nodiscard]] const_iterator cbegin() const noexcept {
[[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT {
return pool.first().begin();
}
/*! @copydoc cbegin */
[[nodiscard]] const_iterator begin() const noexcept {
[[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT {
return cbegin();
}
/*! @copydoc begin */
[[nodiscard]] iterator begin() noexcept {
[[nodiscard]] iterator begin() ENTT_NOEXCEPT {
return pool.first().begin();
}
/**
* @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.
*/
[[nodiscard]] const_iterator cend() const noexcept {
[[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT {
return pool.first().end();
}
/*! @copydoc cend */
[[nodiscard]] const_iterator end() const noexcept {
[[nodiscard]] const_iterator end() const ENTT_NOEXCEPT {
return cend();
}
/*! @copydoc end */
[[nodiscard]] iterator end() noexcept {
[[nodiscard]] iterator end() ENTT_NOEXCEPT {
return pool.first().end();
}
@@ -282,7 +289,7 @@ public:
* @brief Returns true if a cache contains no resources, false otherwise.
* @return True if the cache contains no resources, false otherwise.
*/
[[nodiscard]] bool empty() const noexcept {
[[nodiscard]] bool empty() const ENTT_NOEXCEPT {
return pool.first().empty();
}
@@ -290,12 +297,12 @@ public:
* @brief Number of resources managed by a cache.
* @return Number of resources currently stored.
*/
[[nodiscard]] size_type size() const noexcept {
[[nodiscard]] size_type size() const ENTT_NOEXCEPT {
return pool.first().size();
}
/*! @brief Clears a cache. */
void clear() noexcept {
void clear() ENTT_NOEXCEPT {
pool.first().clear();
}

View File

@@ -1,5 +1,5 @@
#ifndef ENTT_RESOURCE_LOADER_HPP
#define ENTT_RESOURCE_LOADER_HPP
#ifndef ENTT_RESOURCE_LOADEr_HPP
#define ENTT_RESOURCE_LOADEr_HPP
#include <memory>
#include <utility>

View File

@@ -4,6 +4,7 @@
#include <memory>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "fwd.hpp"
namespace entt {
@@ -28,27 +29,22 @@ class resource {
static constexpr bool is_acceptable_v = !std::is_same_v<Type, Other> && std::is_constructible_v<Type &, Other &>;
public:
/*! @brief Resource type. */
using element_type = Type;
/*! @brief Handle type. */
using handle_type = std::shared_ptr<element_type>;
/*! @brief Default constructor. */
resource() noexcept
resource() ENTT_NOEXCEPT
: value{} {}
/**
* @brief Creates a handle from a weak pointer, namely a resource.
* @param res A weak pointer to a resource.
*/
explicit resource(handle_type res) noexcept
explicit resource(std::shared_ptr<Type> res) ENTT_NOEXCEPT
: value{std::move(res)} {}
/*! @brief Default copy constructor. */
resource(const resource &) noexcept = default;
resource(const resource &) ENTT_NOEXCEPT = default;
/*! @brief Default move constructor. */
resource(resource &&) noexcept = default;
resource(resource &&) ENTT_NOEXCEPT = default;
/**
* @brief Aliasing constructor.
@@ -57,7 +53,7 @@ public:
* @param res Unrelated and unmanaged resources.
*/
template<typename Other>
resource(const resource<Other> &other, element_type &res) noexcept
resource(const resource<Other> &other, Type &res) ENTT_NOEXCEPT
: value{other.value, std::addressof(res)} {}
/**
@@ -66,7 +62,7 @@ public:
* @param other The handle to copy from.
*/
template<typename Other, typename = std::enable_if_t<is_acceptable_v<Other>>>
resource(const resource<Other> &other) noexcept
resource(const resource<Other> &other) ENTT_NOEXCEPT
: value{other.value} {}
/**
@@ -75,20 +71,20 @@ public:
* @param other The handle to move from.
*/
template<typename Other, typename = std::enable_if_t<is_acceptable_v<Other>>>
resource(resource<Other> &&other) noexcept
resource(resource<Other> &&other) ENTT_NOEXCEPT
: value{std::move(other.value)} {}
/**
* @brief Default copy assignment operator.
* @return This resource handle.
*/
resource &operator=(const resource &) noexcept = default;
resource &operator=(const resource &) ENTT_NOEXCEPT = default;
/**
* @brief Default move assignment operator.
* @return This resource handle.
*/
resource &operator=(resource &&) noexcept = default;
resource &operator=(resource &&) ENTT_NOEXCEPT = default;
/**
* @brief Copy assignment operator from foreign handle.
@@ -98,7 +94,7 @@ public:
*/
template<typename Other>
std::enable_if_t<is_acceptable_v<Other>, resource &>
operator=(const resource<Other> &other) noexcept {
operator=(const resource<Other> &other) ENTT_NOEXCEPT {
value = other.value;
return *this;
}
@@ -111,7 +107,7 @@ public:
*/
template<typename Other>
std::enable_if_t<is_acceptable_v<Other>, resource &>
operator=(resource<Other> &&other) noexcept {
operator=(resource<Other> &&other) ENTT_NOEXCEPT {
value = std::move(other.value);
return *this;
}
@@ -124,12 +120,12 @@ public:
*
* @return A reference to the managed resource.
*/
[[nodiscard]] element_type &operator*() const noexcept {
[[nodiscard]] Type &operator*() const ENTT_NOEXCEPT {
return *value;
}
/*! @copydoc operator* */
[[nodiscard]] operator element_type &() const noexcept {
[[nodiscard]] operator Type &() const ENTT_NOEXCEPT {
return *value;
}
@@ -137,7 +133,7 @@ public:
* @brief Returns a pointer to the managed resource.
* @return A pointer to the managed resource.
*/
[[nodiscard]] element_type *operator->() const noexcept {
[[nodiscard]] Type *operator->() const ENTT_NOEXCEPT {
return value.get();
}
@@ -145,99 +141,99 @@ public:
* @brief Returns true if a handle contains a resource, false otherwise.
* @return True if the handle contains a resource, false otherwise.
*/
[[nodiscard]] explicit operator bool() const noexcept {
[[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT {
return static_cast<bool>(value);
}
/**
* @brief Returns the underlying resource handle.
* @return The underlying resource handle.
* @brief Returns the number of handles pointing the same resource.
* @return The number of handles pointing the same resource.
*/
[[nodiscard]] const handle_type &handle() const noexcept {
return value;
[[nodiscard]] long use_count() const ENTT_NOEXCEPT {
return value.use_count();
}
private:
handle_type value;
std::shared_ptr<Type> value;
};
/**
* @brief Compares two handles.
* @tparam Lhs Type of resource managed by the first handle.
* @tparam Rhs Type of resource managed by the second handle.
* @tparam Res Type of resource managed by the first handle.
* @tparam Other 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 Lhs, typename Rhs>
[[nodiscard]] bool operator==(const resource<Lhs> &lhs, const resource<Rhs> &rhs) noexcept {
template<typename Res, typename Other>
[[nodiscard]] bool operator==(const resource<Res> &lhs, const resource<Other> &rhs) ENTT_NOEXCEPT {
return (std::addressof(*lhs) == std::addressof(*rhs));
}
/**
* @brief Compares two handles.
* @tparam Lhs Type of resource managed by the first handle.
* @tparam Rhs Type of resource managed by the second handle.
* @tparam Res Type of resource managed by the first handle.
* @tparam Other 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 resource, true otherwise.
* @return False if both handles refer to the same registry, true otherwise.
*/
template<typename Lhs, typename Rhs>
[[nodiscard]] bool operator!=(const resource<Lhs> &lhs, const resource<Rhs> &rhs) noexcept {
template<typename Res, typename Other>
[[nodiscard]] bool operator!=(const resource<Res> &lhs, const resource<Other> &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}
/**
* @brief Compares two handles.
* @tparam Lhs Type of resource managed by the first handle.
* @tparam Rhs Type of resource managed by the second handle.
* @tparam Res Type of resource managed by the first handle.
* @tparam Other 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 Lhs, typename Rhs>
[[nodiscard]] bool operator<(const resource<Lhs> &lhs, const resource<Rhs> &rhs) noexcept {
template<typename Res, typename Other>
[[nodiscard]] bool operator<(const resource<Res> &lhs, const resource<Other> &rhs) ENTT_NOEXCEPT {
return (std::addressof(*lhs) < std::addressof(*rhs));
}
/**
* @brief Compares two handles.
* @tparam Lhs Type of resource managed by the first handle.
* @tparam Rhs Type of resource managed by the second handle.
* @tparam Res Type of resource managed by the first handle.
* @tparam Other 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 Lhs, typename Rhs>
[[nodiscard]] bool operator>(const resource<Lhs> &lhs, const resource<Rhs> &rhs) noexcept {
return rhs < lhs;
template<typename Res, typename Other>
[[nodiscard]] bool operator>(const resource<Res> &lhs, const resource<Other> &rhs) ENTT_NOEXCEPT {
return (std::addressof(*lhs) > std::addressof(*rhs));
}
/**
* @brief Compares two handles.
* @tparam Lhs Type of resource managed by the first handle.
* @tparam Rhs Type of resource managed by the second handle.
* @tparam Res Type of resource managed by the first handle.
* @tparam Other 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 Lhs, typename Rhs>
[[nodiscard]] bool operator<=(const resource<Lhs> &lhs, const resource<Rhs> &rhs) noexcept {
template<typename Res, typename Other>
[[nodiscard]] bool operator<=(const resource<Res> &lhs, const resource<Other> &rhs) ENTT_NOEXCEPT {
return !(lhs > rhs);
}
/**
* @brief Compares two handles.
* @tparam Lhs Type of resource managed by the first handle.
* @tparam Rhs Type of resource managed by the second handle.
* @tparam Res Type of resource managed by the first handle.
* @tparam Other 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 Lhs, typename Rhs>
[[nodiscard]] bool operator>=(const resource<Lhs> &lhs, const resource<Rhs> &rhs) noexcept {
template<typename Res, typename Other>
[[nodiscard]] bool operator>=(const resource<Res> &lhs, const resource<Other> &rhs) ENTT_NOEXCEPT {
return !(lhs < rhs);
}

View File

@@ -20,22 +20,22 @@ namespace entt {
namespace internal {
template<typename Ret, typename... Args>
constexpr auto function_pointer(Ret (*)(Args...)) -> Ret (*)(Args...);
auto function_pointer(Ret (*)(Args...)) -> Ret (*)(Args...);
template<typename Ret, typename Type, typename... Args, typename Other>
constexpr auto function_pointer(Ret (*)(Type, Args...), Other &&) -> Ret (*)(Args...);
auto function_pointer(Ret (*)(Type, Args...), Other &&) -> Ret (*)(Args...);
template<typename Class, typename Ret, typename... Args, typename... Other>
constexpr auto function_pointer(Ret (Class::*)(Args...), Other &&...) -> Ret (*)(Args...);
auto function_pointer(Ret (Class::*)(Args...), Other &&...) -> Ret (*)(Args...);
template<typename Class, typename Ret, typename... Args, typename... Other>
constexpr auto function_pointer(Ret (Class::*)(Args...) const, Other &&...) -> Ret (*)(Args...);
auto function_pointer(Ret (Class::*)(Args...) const, Other &&...) -> Ret (*)(Args...);
template<typename Class, typename Type, typename... Other>
constexpr auto function_pointer(Type Class::*, Other &&...) -> Type (*)();
auto function_pointer(Type Class::*, Other &&...) -> Type (*)();
template<typename... Type>
using function_pointer_t = decltype(function_pointer(std::declval<Type>()...));
using function_pointer_t = decltype(internal::function_pointer(std::declval<Type>()...));
template<typename... Class, typename Ret, typename... Args>
[[nodiscard]] constexpr auto index_sequence_for(Ret (*)(Args...)) {
@@ -49,6 +49,14 @@ template<typename... Class, typename Ret, typename... Args>
* @endcond
*/
/*! @brief Used to wrap a function or a member of a specified type. */
template<auto>
struct connect_arg_t {};
/*! @brief Constant of type connect_arg_t used to disambiguate calls. */
template<auto Func>
inline constexpr connect_arg_t<Func> connect_arg{};
/**
* @brief Basic delegate implementation.
*
@@ -73,46 +81,28 @@ class delegate;
template<typename Ret, typename... Args>
class delegate<Ret(Args...)> {
template<auto Candidate, std::size_t... Index>
[[nodiscard]] auto wrap(std::index_sequence<Index...>) noexcept {
[[nodiscard]] auto wrap(std::index_sequence<Index...>) ENTT_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))...));
}
return static_cast<Ret>(std::invoke(Candidate, std::forward<type_list_element_t<Index, type_list<Args...>>>(std::get<Index>(arguments))...));
};
}
template<auto Candidate, typename Type, std::size_t... Index>
[[nodiscard]] auto wrap(Type &, std::index_sequence<Index...>) noexcept {
[[nodiscard]] auto wrap(Type &, std::index_sequence<Index...>) ENTT_NOEXCEPT {
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))...));
}
return static_cast<Ret>(std::invoke(Candidate, *curr, std::forward<type_list_element_t<Index, type_list<Args...>>>(std::get<Index>(arguments))...));
};
}
template<auto Candidate, typename Type, std::size_t... Index>
[[nodiscard]] auto wrap(Type *, std::index_sequence<Index...>) noexcept {
[[nodiscard]] auto wrap(Type *, std::index_sequence<Index...>) ENTT_NOEXCEPT {
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))...));
}
return static_cast<Ret>(std::invoke(Candidate, curr, std::forward<type_list_element_t<Index, type_list<Args...>>>(std::get<Index>(arguments))...));
};
}
@@ -125,19 +115,30 @@ public:
using result_type = Ret;
/*! @brief Default constructor. */
delegate() noexcept
delegate() ENTT_NOEXCEPT
: instance{nullptr},
fn{nullptr} {}
/**
* @brief Constructs a delegate with a given object or payload, if any.
* @brief Constructs a delegate and connects a free function or an unbound
* member.
* @tparam Candidate Function or member to connect to the delegate.
* @tparam Type Type of class or type of payload, if any.
* @param value_or_instance Optional valid object that fits the purpose.
*/
template<auto Candidate, typename... Type>
delegate(connect_arg_t<Candidate>, Type &&...value_or_instance) noexcept {
connect<Candidate>(std::forward<Type>(value_or_instance)...);
template<auto Candidate>
delegate(connect_arg_t<Candidate>) ENTT_NOEXCEPT {
connect<Candidate>();
}
/**
* @brief Constructs a delegate and connects a free function with payload or
* a bound member.
* @tparam Candidate Function or member to connect to the delegate.
* @tparam Type Type of class or type of payload.
* @param value_or_instance A valid object that fits the purpose.
*/
template<auto Candidate, typename Type>
delegate(connect_arg_t<Candidate>, Type &&value_or_instance) ENTT_NOEXCEPT {
connect<Candidate>(std::forward<Type>(value_or_instance));
}
/**
@@ -146,7 +147,7 @@ public:
* @param function Function to connect to the delegate.
* @param payload User defined arbitrary data.
*/
delegate(function_type *function, const void *payload = nullptr) noexcept {
delegate(function_type *function, const void *payload = nullptr) ENTT_NOEXCEPT {
connect(function, payload);
}
@@ -155,7 +156,7 @@ public:
* @tparam Candidate Function or member to connect to the delegate.
*/
template<auto Candidate>
void connect() noexcept {
void connect() ENTT_NOEXCEPT {
instance = nullptr;
if constexpr(std::is_invocable_r_v<Ret, decltype(Candidate), Args...>) {
@@ -185,7 +186,7 @@ public:
* @param value_or_instance A valid reference that fits the purpose.
*/
template<auto Candidate, typename Type>
void connect(Type &value_or_instance) noexcept {
void connect(Type &value_or_instance) ENTT_NOEXCEPT {
instance = &value_or_instance;
if constexpr(std::is_invocable_r_v<Ret, decltype(Candidate), Type &, Args...>) {
@@ -209,7 +210,7 @@ public:
* @param value_or_instance A valid pointer that fits the purpose.
*/
template<auto Candidate, typename Type>
void connect(Type *value_or_instance) noexcept {
void connect(Type *value_or_instance) ENTT_NOEXCEPT {
instance = value_or_instance;
if constexpr(std::is_invocable_r_v<Ret, decltype(Candidate), Type *, Args...>) {
@@ -235,8 +236,7 @@ public:
* @param function Function to connect to the delegate.
* @param payload User defined arbitrary data.
*/
void connect(function_type *function, const void *payload = nullptr) noexcept {
ENTT_ASSERT(function != nullptr, "Uninitialized function pointer");
void connect(function_type *function, const void *payload = nullptr) ENTT_NOEXCEPT {
instance = payload;
fn = function;
}
@@ -246,24 +246,16 @@ public:
*
* After a reset, a delegate cannot be invoked anymore.
*/
void reset() noexcept {
void reset() ENTT_NOEXCEPT {
instance = nullptr;
fn = nullptr;
}
/**
* @brief Returns a pointer to the stored callable function target, if any.
* @return An opaque pointer to the stored callable function target.
*/
[[nodiscard]] function_type *target() const noexcept {
return fn;
}
/**
* @brief Returns the instance or the payload linked to a delegate, if any.
* @return An opaque pointer to the underlying data.
*/
[[nodiscard]] const void *data() const noexcept {
[[nodiscard]] const void *data() const ENTT_NOEXCEPT {
return instance;
}
@@ -288,7 +280,7 @@ public:
* @brief Checks whether a delegate actually stores a listener.
* @return False if the delegate is empty, true otherwise.
*/
[[nodiscard]] explicit operator bool() const noexcept {
[[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT {
// no need to also test instance
return !(fn == nullptr);
}
@@ -298,7 +290,7 @@ public:
* @param other Delegate with which to compare.
* @return False if the two contents differ, true otherwise.
*/
[[nodiscard]] bool operator==(const delegate<Ret(Args...)> &other) const noexcept {
[[nodiscard]] bool operator==(const delegate<Ret(Args...)> &other) const ENTT_NOEXCEPT {
return fn == other.fn && instance == other.instance;
}
@@ -316,7 +308,7 @@ private:
* @return True if the two contents differ, false otherwise.
*/
template<typename Ret, typename... Args>
[[nodiscard]] bool operator!=(const delegate<Ret(Args...)> &lhs, const delegate<Ret(Args...)> &rhs) noexcept {
[[nodiscard]] bool operator!=(const delegate<Ret(Args...)> &lhs, const delegate<Ret(Args...)> &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}

View File

@@ -1,12 +1,13 @@
#ifndef ENTT_SIGNAL_DISPATCHER_HPP
#define ENTT_SIGNAL_DISPATCHER_HPP
#include <algorithm>
#include <cstddef>
#include <functional>
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>
#include "../config/config.h"
#include "../container/dense_map.hpp"
#include "../core/compressed_pair.hpp"
#include "../core/fwd.hpp"
@@ -28,17 +29,17 @@ struct basic_dispatcher_handler {
virtual ~basic_dispatcher_handler() = default;
virtual void publish() = 0;
virtual void disconnect(void *) = 0;
virtual void clear() noexcept = 0;
virtual std::size_t size() const noexcept = 0;
virtual void clear() ENTT_NOEXCEPT = 0;
virtual std::size_t size() const ENTT_NOEXCEPT = 0;
};
template<typename Type, typename Allocator>
template<typename Event, typename Allocator>
class dispatcher_handler final: public basic_dispatcher_handler {
static_assert(std::is_same_v<Type, std::decay_t<Type>>, "Invalid type");
static_assert(std::is_same_v<Event, std::decay_t<Event>>, "Invalid event type");
using alloc_traits = std::allocator_traits<Allocator>;
using signal_type = sigh<void(Type &), Allocator>;
using container_type = std::vector<Type, typename alloc_traits::template rebind_alloc<Type>>;
using signal_type = sigh<void(Event &), typename alloc_traits::template rebind_alloc<void (*)(Event &)>>;
using container_type = std::vector<Event, typename alloc_traits::template rebind_alloc<Event>>;
public:
using allocator_type = Allocator;
@@ -61,28 +62,29 @@ public:
bucket().disconnect(instance);
}
void clear() noexcept override {
void clear() ENTT_NOEXCEPT override {
events.clear();
}
[[nodiscard]] auto bucket() noexcept {
return typename signal_type::sink_type{signal};
[[nodiscard]] auto bucket() ENTT_NOEXCEPT {
using sink_type = typename sigh<void(Event &)>::sink_type;
return sink_type{signal};
}
void trigger(Type event) {
void trigger(Event event) {
signal.publish(event);
}
template<typename... Args>
void enqueue(Args &&...args) {
if constexpr(std::is_aggregate_v<Type> && (sizeof...(Args) != 0u || !std::is_default_constructible_v<Type>)) {
events.push_back(Type{std::forward<Args>(args)...});
if constexpr(std::is_aggregate_v<Event>) {
events.push_back(Event{std::forward<Args>(args)...});
} else {
events.emplace_back(std::forward<Args>(args)...);
}
}
std::size_t size() const noexcept override {
std::size_t size() const ENTT_NOEXCEPT override {
return events.size();
}
@@ -104,8 +106,8 @@ private:
* A dispatcher can be used either to trigger an immediate event or to enqueue
* events to be published all together once per tick.<br/>
* Listeners are provided in the form of member functions. For each event of
* type `Type`, listeners are such that they can be invoked with an argument of
* type `Type &`, no matter what the return type is.
* type `Event`, listeners are such that they can be invoked with an argument of
* type `Event &`, no matter what the return type is.
*
* The dispatcher creates instances of the `sigh` class internally. Refer to the
* documentation of the latter for more details.
@@ -114,36 +116,35 @@ private:
*/
template<typename Allocator>
class basic_dispatcher {
template<typename Type>
using handler_type = internal::dispatcher_handler<Type, Allocator>;
template<typename Event>
using handler_type = internal::dispatcher_handler<Event, Allocator>;
using key_type = id_type;
// std::shared_ptr because of its type erased allocator which is useful here
// std::shared_ptr because of its type erased allocator which is pretty useful here
using mapped_type = std::shared_ptr<internal::basic_dispatcher_handler>;
using alloc_traits = std::allocator_traits<Allocator>;
using container_allocator = typename alloc_traits::template rebind_alloc<std::pair<const key_type, mapped_type>>;
using container_type = dense_map<key_type, mapped_type, identity, std::equal_to<key_type>, container_allocator>;
using container_type = dense_map<id_type, mapped_type, identity, std::equal_to<id_type>, container_allocator>;
template<typename Type>
[[nodiscard]] handler_type<Type> &assure(const id_type id) {
static_assert(std::is_same_v<Type, std::decay_t<Type>>, "Non-decayed types not allowed");
template<typename Event>
[[nodiscard]] handler_type<Event> &assure(const id_type id) {
auto &&ptr = pools.first()[id];
if(!ptr) {
const auto &allocator = get_allocator();
ptr = std::allocate_shared<handler_type<Type>>(allocator, allocator);
const auto &allocator = pools.second();
ptr = std::allocate_shared<handler_type<Event>>(allocator, allocator);
}
return static_cast<handler_type<Type> &>(*ptr);
return static_cast<handler_type<Event> &>(*ptr);
}
template<typename Type>
[[nodiscard]] const handler_type<Type> *assure(const id_type id) const {
static_assert(std::is_same_v<Type, std::decay_t<Type>>, "Non-decayed types not allowed");
template<typename Event>
[[nodiscard]] const handler_type<Event> *assure(const id_type id) const {
auto &container = pools.first();
if(auto it = pools.first().find(id); it != pools.first().cend()) {
return static_cast<const handler_type<Type> *>(it->second.get());
if(const auto it = container.find(id); it != container.end()) {
return static_cast<const handler_type<Event> *>(it->second.get());
}
return nullptr;
@@ -170,7 +171,7 @@ public:
* @brief Move constructor.
* @param other The instance to move from.
*/
basic_dispatcher(basic_dispatcher &&other) noexcept
basic_dispatcher(basic_dispatcher &&other) ENTT_NOEXCEPT
: pools{std::move(other.pools)} {}
/**
@@ -178,19 +179,15 @@ public:
* @param other The instance to move from.
* @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} {
ENTT_ASSERT(alloc_traits::is_always_equal::value || pools.second() == other.pools.second(), "Copying a dispatcher is not allowed");
}
basic_dispatcher(basic_dispatcher &&other, const allocator_type &allocator) ENTT_NOEXCEPT
: pools{container_type{std::move(other.pools.first()), allocator}, allocator} {}
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @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");
basic_dispatcher &operator=(basic_dispatcher &&other) ENTT_NOEXCEPT {
pools = std::move(other.pools);
return *this;
}
@@ -208,19 +205,19 @@ public:
* @brief Returns the associated allocator.
* @return The associated allocator.
*/
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
[[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT {
return pools.second();
}
/**
* @brief Returns the number of pending events for a given type.
* @tparam Type Type of event for which to return the count.
* @tparam Event Type of event for which to return the count.
* @param id Name used to map the event queue within the dispatcher.
* @return The number of pending events for the given type.
*/
template<typename Type>
size_type size(const id_type id = type_hash<Type>::value()) const noexcept {
const auto *cpool = assure<std::decay_t<Type>>(id);
template<typename Event>
size_type size(const id_type id = type_hash<Event>::value()) const ENTT_NOEXCEPT {
const auto *cpool = assure<Event>(id);
return cpool ? cpool->size() : 0u;
}
@@ -228,7 +225,7 @@ public:
* @brief Returns the total number of pending events.
* @return The total number of pending events.
*/
size_type size() const noexcept {
size_type size() const ENTT_NOEXCEPT {
size_type count{};
for(auto &&cpool: pools.first()) {
@@ -244,87 +241,86 @@ public:
* A sink is an opaque object used to connect listeners to events.
*
* The function type for a listener is _compatible_ with:
*
* @code{.cpp}
* void(Type &);
* void(Event &);
* @endcode
*
* The order of invocation of the listeners isn't guaranteed.
*
* @sa sink
*
* @tparam Type Type of event of which to get the sink.
* @tparam Event Type of event of which to get the sink.
* @param id Name used to map the event queue within the dispatcher.
* @return A temporary sink object.
*/
template<typename Type>
[[nodiscard]] auto sink(const id_type id = type_hash<Type>::value()) {
return assure<Type>(id).bucket();
template<typename Event>
[[nodiscard]] auto sink(const id_type id = type_hash<Event>::value()) {
return assure<Event>(id).bucket();
}
/**
* @brief Triggers an immediate event of a given type.
* @tparam Type Type of event to trigger.
* @param value An instance of the given type of event.
* @tparam Event Type of event to trigger.
* @param event An instance of the given type of event.
*/
template<typename Type>
void trigger(Type &&value = {}) {
trigger(type_hash<std::decay_t<Type>>::value(), std::forward<Type>(value));
template<typename Event>
void trigger(Event &&event = {}) {
trigger(type_hash<std::decay_t<Event>>::value(), std::forward<Event>(event));
}
/**
* @brief Triggers an immediate event on a queue of a given type.
* @tparam Type Type of event to trigger.
* @param value An instance of the given type of event.
* @tparam Event Type of event to trigger.
* @param event An instance of the given type of event.
* @param id Name used to map the event queue within the dispatcher.
*/
template<typename Type>
void trigger(const id_type id, Type &&value = {}) {
assure<std::decay_t<Type>>(id).trigger(std::forward<Type>(value));
template<typename Event>
void trigger(const id_type id, Event &&event = {}) {
assure<std::decay_t<Event>>(id).trigger(std::forward<Event>(event));
}
/**
* @brief Enqueues an event of the given type.
* @tparam Type Type of event to enqueue.
* @tparam Event Type of event to enqueue.
* @tparam Args Types of arguments to use to construct the event.
* @param args Arguments to use to construct the event.
*/
template<typename Type, typename... Args>
template<typename Event, typename... Args>
void enqueue(Args &&...args) {
enqueue_hint<Type>(type_hash<Type>::value(), std::forward<Args>(args)...);
enqueue_hint<Event>(type_hash<Event>::value(), std::forward<Args>(args)...);
}
/**
* @brief Enqueues an event of the given type.
* @tparam Type Type of event to enqueue.
* @param value An instance of the given type of event.
* @tparam Event Type of event to enqueue.
* @param event An instance of the given type of event.
*/
template<typename Type>
void enqueue(Type &&value) {
enqueue_hint(type_hash<std::decay_t<Type>>::value(), std::forward<Type>(value));
template<typename Event>
void enqueue(Event &&event) {
enqueue_hint(type_hash<std::decay_t<Event>>::value(), std::forward<Event>(event));
}
/**
* @brief Enqueues an event of the given type.
* @tparam Type Type of event to enqueue.
* @tparam Event Type of event to enqueue.
* @tparam Args Types of arguments to use to construct the event.
* @param id Name used to map the event queue within the dispatcher.
* @param args Arguments to use to construct the event.
*/
template<typename Type, typename... Args>
template<typename Event, typename... Args>
void enqueue_hint(const id_type id, Args &&...args) {
assure<Type>(id).enqueue(std::forward<Args>(args)...);
assure<Event>(id).enqueue(std::forward<Args>(args)...);
}
/**
* @brief Enqueues an event of the given type.
* @tparam Type Type of event to enqueue.
* @tparam Event Type of event to enqueue.
* @param id Name used to map the event queue within the dispatcher.
* @param value An instance of the given type of event.
* @param event An instance of the given type of event.
*/
template<typename Type>
void enqueue_hint(const id_type id, Type &&value) {
assure<std::decay_t<Type>>(id).enqueue(std::forward<Type>(value));
template<typename Event>
void enqueue_hint(const id_type id, Event &&event) {
assure<std::decay_t<Event>>(id).enqueue(std::forward<Event>(event));
}
/**
@@ -353,16 +349,16 @@ public:
/**
* @brief Discards all the events stored so far in a given queue.
* @tparam Type Type of event to discard.
* @tparam Event Type of event to discard.
* @param id Name used to map the event queue within the dispatcher.
*/
template<typename Type>
void clear(const id_type id = type_hash<Type>::value()) {
assure<Type>(id).clear();
template<typename Event>
void clear(const id_type id = type_hash<Event>::value()) {
assure<Event>(id).clear();
}
/*! @brief Discards all the events queued so far. */
void clear() noexcept {
void clear() ENTT_NOEXCEPT {
for(auto &&cpool: pools.first()) {
cpool.second->clear();
}
@@ -370,12 +366,12 @@ public:
/**
* @brief Delivers all the pending events of a given queue.
* @tparam Type Type of event to send.
* @tparam Event Type of event to send.
* @param id Name used to map the event queue within the dispatcher.
*/
template<typename Type>
void update(const id_type id = type_hash<Type>::value()) {
assure<Type>(id).publish();
template<typename Event>
void update(const id_type id = type_hash<Event>::value()) {
assure<Event>(id).publish();
}
/*! @brief Delivers all the pending events. */

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