Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4f3b6f7bd | ||
|
|
71b464f44a | ||
|
|
438070ed58 | ||
|
|
a06c891969 | ||
|
|
a935bd09aa | ||
|
|
fb8745ccf0 | ||
|
|
53a4c4be7f | ||
|
|
c0a110ea8a | ||
|
|
c426a8e331 | ||
|
|
526e4f69a4 | ||
|
|
f901fa50ff | ||
|
|
bea9eeac16 | ||
|
|
3055da5316 | ||
|
|
3706fbdfee | ||
|
|
b4d18e94da | ||
|
|
41523d9555 |
26
.travis.yml
26
.travis.yml
@@ -11,6 +11,13 @@ matrix:
|
||||
sources: ['ubuntu-toolchain-r-test']
|
||||
packages: ['g++-6']
|
||||
env: COMPILER=g++-6
|
||||
- os: linux
|
||||
compiler: gcc
|
||||
addons:
|
||||
apt:
|
||||
sources: ['ubuntu-toolchain-r-test']
|
||||
packages: ['g++-7']
|
||||
env: COMPILER=g++-7
|
||||
- os: linux
|
||||
compiler: clang
|
||||
addons:
|
||||
@@ -18,23 +25,34 @@ matrix:
|
||||
sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-4.0']
|
||||
packages: ['clang-4.0', 'libstdc++-4.9-dev']
|
||||
env: COMPILER=clang++-4.0
|
||||
- os: linux
|
||||
compiler: clang
|
||||
addons:
|
||||
apt:
|
||||
sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-5.0']
|
||||
packages: ['clang-5.0', 'libstdc++-4.9-dev']
|
||||
env: COMPILER=clang++-5.0
|
||||
- os: osx
|
||||
osx_image: xcode8.3
|
||||
compiler: clang
|
||||
env: COMPILER=clang++
|
||||
- os: osx
|
||||
osx_image: xcode9.1
|
||||
compiler: clang
|
||||
env: COMPILER=clang++
|
||||
- os: linux
|
||||
compiler: gcc
|
||||
addons:
|
||||
apt:
|
||||
sources: ['ubuntu-toolchain-r-test']
|
||||
packages: ['g++-6']
|
||||
packages: ['g++-7']
|
||||
env:
|
||||
- COMPILER=g++-6
|
||||
- COMPILER=g++-7
|
||||
- CXXFLAGS="-O0 --coverage -fno-inline -fno-inline-small-functions -fno-default-inline"
|
||||
before_script:
|
||||
- pip install --user cpp-coveralls
|
||||
after_success:
|
||||
- coveralls --gcov gcov-6 --gcov-options '\-lp' --root ${TRAVIS_BUILD_DIR} --build-root ${TRAVIS_BUILD_DIR}/build --extension cpp --extension hpp --exclude deps --include src
|
||||
- coveralls --gcov gcov-7 --gcov-options '\-lp' --root ${TRAVIS_BUILD_DIR} --build-root ${TRAVIS_BUILD_DIR}/build --extension cpp --extension hpp --exclude deps --include src
|
||||
|
||||
notifications:
|
||||
email:
|
||||
@@ -51,5 +69,5 @@ install:
|
||||
|
||||
script:
|
||||
- mkdir -p build && cd build
|
||||
- cmake -DCMAKE_BUILD_TYPE=Release .. && make -j4
|
||||
- cmake .. && make -j4
|
||||
- CTEST_OUTPUT_ON_FAILURE=1 make test
|
||||
|
||||
@@ -16,7 +16,7 @@ endif()
|
||||
# Project configuration
|
||||
#
|
||||
|
||||
project(entt VERSION 1.1.0)
|
||||
project(entt VERSION 2.1.0)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
@@ -33,7 +33,7 @@ message("* Copyright (c) 2017 ${PROJECT_AUTHOR} <${PROJECT_AUTHOR_EMAIL}>")
|
||||
message("*")
|
||||
|
||||
#
|
||||
# Compile stuff
|
||||
# Compiler stuff
|
||||
#
|
||||
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
@@ -41,7 +41,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
if(NOT MSVC)
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic -Wall -Wconversion")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic -Wall")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DRELEASE")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g -DDEBUG")
|
||||
|
||||
@@ -65,15 +65,11 @@ set(PROJECT_SRC_DIR ${entt_SOURCE_DIR}/src)
|
||||
set(PROJECT_RUNTIME_OUTPUT_DIRECTORY bin)
|
||||
|
||||
#
|
||||
# Enable test support using ctest-like interface
|
||||
# Tests
|
||||
#
|
||||
|
||||
option(BUILD_TESTING "Enable testing with ctest." ON)
|
||||
|
||||
#
|
||||
# build testing stuff if required
|
||||
#
|
||||
|
||||
if(BUILD_TESTING)
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
@@ -89,3 +85,13 @@ if(BUILD_TESTING)
|
||||
enable_testing()
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
|
||||
#
|
||||
# Documentation
|
||||
#
|
||||
|
||||
find_package(Doxygen 1.8)
|
||||
|
||||
if(DOXYGEN_FOUND)
|
||||
add_subdirectory(docs)
|
||||
endif()
|
||||
|
||||
27
docs/CMakeLists.txt
Normal file
27
docs/CMakeLists.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
#
|
||||
# Doxygen configuration (documentation)
|
||||
#
|
||||
|
||||
set(TARGET_DOCS docs)
|
||||
|
||||
set(DOXY_IN_FILE doxy.in)
|
||||
|
||||
set(DOXY_SOURCE_DIRECTORY ${PROJECT_SRC_DIR})
|
||||
set(DOXY_DOCS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
set(DOXY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
set(DOXY_CFG_FILE doxy.cfg)
|
||||
|
||||
configure_file(${DOXY_IN_FILE} ${DOXY_CFG_FILE} @ONLY)
|
||||
|
||||
add_custom_target(
|
||||
${TARGET_DOCS}
|
||||
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/${DOXY_CFG_FILE}
|
||||
WORKING_DIRECTORY ${entt_SOURCE_DIR}
|
||||
VERBATIM
|
||||
SOURCES ${DOXY_IN_FILE}
|
||||
)
|
||||
|
||||
install(
|
||||
DIRECTORY ${DOXY_OUTPUT_DIRECTORY}/html
|
||||
DESTINATION share/${PROJECT_NAME}-${PROJECT_VERSION}/
|
||||
)
|
||||
395
docs/LICENSE
Normal file
395
docs/LICENSE
Normal file
@@ -0,0 +1,395 @@
|
||||
Attribution 4.0 International
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
||||
does not provide legal services or legal advice. Distribution of
|
||||
Creative Commons public licenses does not create a lawyer-client or
|
||||
other relationship. Creative Commons makes its licenses and related
|
||||
information available on an "as-is" basis. Creative Commons gives no
|
||||
warranties regarding its licenses, any material licensed under their
|
||||
terms and conditions, or any related information. Creative Commons
|
||||
disclaims all liability for damages resulting from their use to the
|
||||
fullest extent possible.
|
||||
|
||||
Using Creative Commons Public Licenses
|
||||
|
||||
Creative Commons public licenses provide a standard set of terms and
|
||||
conditions that creators and other rights holders may use to share
|
||||
original works of authorship and other material subject to copyright
|
||||
and certain other rights specified in the public license below. The
|
||||
following considerations are for informational purposes only, are not
|
||||
exhaustive, and do not form part of our licenses.
|
||||
|
||||
Considerations for licensors: Our public licenses are
|
||||
intended for use by those authorized to give the public
|
||||
permission to use material in ways otherwise restricted by
|
||||
copyright and certain other rights. Our licenses are
|
||||
irrevocable. Licensors should read and understand the terms
|
||||
and conditions of the license they choose before applying it.
|
||||
Licensors should also secure all rights necessary before
|
||||
applying our licenses so that the public can reuse the
|
||||
material as expected. Licensors should clearly mark any
|
||||
material not subject to the license. This includes other CC-
|
||||
licensed material, or material used under an exception or
|
||||
limitation to copyright. More considerations for licensors:
|
||||
wiki.creativecommons.org/Considerations_for_licensors
|
||||
|
||||
Considerations for the public: By using one of our public
|
||||
licenses, a licensor grants the public permission to use the
|
||||
licensed material under specified terms and conditions. If
|
||||
the licensor's permission is not necessary for any reason--for
|
||||
example, because of any applicable exception or limitation to
|
||||
copyright--then that use is not regulated by the license. Our
|
||||
licenses grant only permissions under copyright and certain
|
||||
other rights that a licensor has authority to grant. Use of
|
||||
the licensed material may still be restricted for other
|
||||
reasons, including because others have copyright or other
|
||||
rights in the material. A licensor may make special requests,
|
||||
such as asking that all changes be marked or described.
|
||||
Although not required by our licenses, you are encouraged to
|
||||
respect those requests where reasonable. More_considerations
|
||||
for the public:
|
||||
wiki.creativecommons.org/Considerations_for_licensees
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Attribution 4.0 International Public License
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree
|
||||
to be bound by the terms and conditions of this Creative Commons
|
||||
Attribution 4.0 International Public License ("Public License"). To the
|
||||
extent this Public License may be interpreted as a contract, You are
|
||||
granted the Licensed Rights in consideration of Your acceptance of
|
||||
these terms and conditions, and the Licensor grants You such rights in
|
||||
consideration of benefits the Licensor receives from making the
|
||||
Licensed Material available under these terms and conditions.
|
||||
|
||||
|
||||
Section 1 -- Definitions.
|
||||
|
||||
a. Adapted Material means material subject to Copyright and Similar
|
||||
Rights that is derived from or based upon the Licensed Material
|
||||
and in which the Licensed Material is translated, altered,
|
||||
arranged, transformed, or otherwise modified in a manner requiring
|
||||
permission under the Copyright and Similar Rights held by the
|
||||
Licensor. For purposes of this Public License, where the Licensed
|
||||
Material is a musical work, performance, or sound recording,
|
||||
Adapted Material is always produced where the Licensed Material is
|
||||
synched in timed relation with a moving image.
|
||||
|
||||
b. Adapter's License means the license You apply to Your Copyright
|
||||
and Similar Rights in Your contributions to Adapted Material in
|
||||
accordance with the terms and conditions of this Public License.
|
||||
|
||||
c. Copyright and Similar Rights means copyright and/or similar rights
|
||||
closely related to copyright including, without limitation,
|
||||
performance, broadcast, sound recording, and Sui Generis Database
|
||||
Rights, without regard to how the rights are labeled or
|
||||
categorized. For purposes of this Public License, the rights
|
||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||
Rights.
|
||||
|
||||
d. Effective Technological Measures means those measures that, in the
|
||||
absence of proper authority, may not be circumvented under laws
|
||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||
Treaty adopted on December 20, 1996, and/or similar international
|
||||
agreements.
|
||||
|
||||
e. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||
any other exception or limitation to Copyright and Similar Rights
|
||||
that applies to Your use of the Licensed Material.
|
||||
|
||||
f. Licensed Material means the artistic or literary work, database,
|
||||
or other material to which the Licensor applied this Public
|
||||
License.
|
||||
|
||||
g. Licensed Rights means the rights granted to You subject to the
|
||||
terms and conditions of this Public License, which are limited to
|
||||
all Copyright and Similar Rights that apply to Your use of the
|
||||
Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
h. Licensor means the individual(s) or entity(ies) granting rights
|
||||
under this Public License.
|
||||
|
||||
i. Share means to provide material to the public by any means or
|
||||
process that requires permission under the Licensed Rights, such
|
||||
as reproduction, public display, public performance, distribution,
|
||||
dissemination, communication, or importation, and to make material
|
||||
available to the public including in ways that members of the
|
||||
public may access the material from a place and at a time
|
||||
individually chosen by them.
|
||||
|
||||
j. Sui Generis Database Rights means rights other than copyright
|
||||
resulting from Directive 96/9/EC of the European Parliament and of
|
||||
the Council of 11 March 1996 on the legal protection of databases,
|
||||
as amended and/or succeeded, as well as other essentially
|
||||
equivalent rights anywhere in the world.
|
||||
|
||||
k. You means the individual or entity exercising the Licensed Rights
|
||||
under this Public License. Your has a corresponding meaning.
|
||||
|
||||
|
||||
Section 2 -- Scope.
|
||||
|
||||
a. License grant.
|
||||
|
||||
1. Subject to the terms and conditions of this Public License,
|
||||
the Licensor hereby grants You a worldwide, royalty-free,
|
||||
non-sublicensable, non-exclusive, irrevocable license to
|
||||
exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
a. reproduce and Share the Licensed Material, in whole or
|
||||
in part; and
|
||||
|
||||
b. produce, reproduce, and Share Adapted Material.
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||
Exceptions and Limitations apply to Your use, this Public
|
||||
License does not apply, and You do not need to comply with
|
||||
its terms and conditions.
|
||||
|
||||
3. Term. The term of this Public License is specified in Section
|
||||
6(a).
|
||||
|
||||
4. Media and formats; technical modifications allowed. The
|
||||
Licensor authorizes You to exercise the Licensed Rights in
|
||||
all media and formats whether now known or hereafter created,
|
||||
and to make technical modifications necessary to do so. The
|
||||
Licensor waives and/or agrees not to assert any right or
|
||||
authority to forbid You from making technical modifications
|
||||
necessary to exercise the Licensed Rights, including
|
||||
technical modifications necessary to circumvent Effective
|
||||
Technological Measures. For purposes of this Public License,
|
||||
simply making modifications authorized by this Section 2(a)
|
||||
(4) never produces Adapted Material.
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
a. Offer from the Licensor -- Licensed Material. Every
|
||||
recipient of the Licensed Material automatically
|
||||
receives an offer from the Licensor to exercise the
|
||||
Licensed Rights under the terms and conditions of this
|
||||
Public License.
|
||||
|
||||
b. No downstream restrictions. You may not offer or impose
|
||||
any additional or different terms or conditions on, or
|
||||
apply any Effective Technological Measures to, the
|
||||
Licensed Material if doing so restricts exercise of the
|
||||
Licensed Rights by any recipient of the Licensed
|
||||
Material.
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or
|
||||
may be construed as permission to assert or imply that You
|
||||
are, or that Your use of the Licensed Material is, connected
|
||||
with, or sponsored, endorsed, or granted official status by,
|
||||
the Licensor or others designated to receive attribution as
|
||||
provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
b. Other rights.
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not
|
||||
licensed under this Public License, nor are publicity,
|
||||
privacy, and/or other similar personality rights; however, to
|
||||
the extent possible, the Licensor waives and/or agrees not to
|
||||
assert any such rights held by the Licensor to the limited
|
||||
extent necessary to allow You to exercise the Licensed
|
||||
Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this
|
||||
Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to
|
||||
collect royalties from You for the exercise of the Licensed
|
||||
Rights, whether directly or through a collecting society
|
||||
under any voluntary or waivable statutory or compulsory
|
||||
licensing scheme. In all other cases the Licensor expressly
|
||||
reserves any right to collect such royalties.
|
||||
|
||||
|
||||
Section 3 -- License Conditions.
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the
|
||||
following conditions.
|
||||
|
||||
a. Attribution.
|
||||
|
||||
1. If You Share the Licensed Material (including in modified
|
||||
form), You must:
|
||||
|
||||
a. retain the following if it is supplied by the Licensor
|
||||
with the Licensed Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed
|
||||
Material and any others designated to receive
|
||||
attribution, in any reasonable manner requested by
|
||||
the Licensor (including by pseudonym if
|
||||
designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of
|
||||
warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the
|
||||
extent reasonably practicable;
|
||||
|
||||
b. indicate if You modified the Licensed Material and
|
||||
retain an indication of any previous modifications; and
|
||||
|
||||
c. indicate the Licensed Material is licensed under this
|
||||
Public License, and include the text of, or the URI or
|
||||
hyperlink to, this Public License.
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||
reasonable manner based on the medium, means, and context in
|
||||
which You Share the Licensed Material. For example, it may be
|
||||
reasonable to satisfy the conditions by providing a URI or
|
||||
hyperlink to a resource that includes the required
|
||||
information.
|
||||
|
||||
3. If requested by the Licensor, You must remove any of the
|
||||
information required by Section 3(a)(1)(A) to the extent
|
||||
reasonably practicable.
|
||||
|
||||
4. If You Share Adapted Material You produce, the Adapter's
|
||||
License You apply must not prevent recipients of the Adapted
|
||||
Material from complying with this Public License.
|
||||
|
||||
|
||||
Section 4 -- Sui Generis Database Rights.
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that
|
||||
apply to Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||
to extract, reuse, reproduce, and Share all or a substantial
|
||||
portion of the contents of the database;
|
||||
|
||||
b. if You include all or a substantial portion of the database
|
||||
contents in a database in which You have Sui Generis Database
|
||||
Rights, then the database in which You have Sui Generis Database
|
||||
Rights (but not its individual contents) is Adapted Material; and
|
||||
|
||||
c. You must comply with the conditions in Section 3(a) if You Share
|
||||
all or a substantial portion of the contents of the database.
|
||||
|
||||
For the avoidance of doubt, this Section 4 supplements and does not
|
||||
replace Your obligations under this Public License where the Licensed
|
||||
Rights include other Copyright and Similar Rights.
|
||||
|
||||
|
||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||
|
||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided
|
||||
above shall be interpreted in a manner that, to the extent
|
||||
possible, most closely approximates an absolute disclaimer and
|
||||
waiver of all liability.
|
||||
|
||||
|
||||
Section 6 -- Term and Termination.
|
||||
|
||||
a. This Public License applies for the term of the Copyright and
|
||||
Similar Rights licensed here. However, if You fail to comply with
|
||||
this Public License, then Your rights under this Public License
|
||||
terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under
|
||||
Section 6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided
|
||||
it is cured within 30 days of Your discovery of the
|
||||
violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||
right the Licensor may have to seek remedies for Your violations
|
||||
of this Public License.
|
||||
|
||||
c. For the avoidance of doubt, the Licensor may also offer the
|
||||
Licensed Material under separate terms or conditions or stop
|
||||
distributing the Licensed Material at any time; however, doing so
|
||||
will not terminate this Public License.
|
||||
|
||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||
License.
|
||||
|
||||
|
||||
Section 7 -- Other Terms and Conditions.
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different
|
||||
terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the
|
||||
Licensed Material not stated herein are separate from and
|
||||
independent of the terms and conditions of this Public License.
|
||||
|
||||
|
||||
Section 8 -- Interpretation.
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and
|
||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||
conditions on any use of the Licensed Material that could lawfully
|
||||
be made without permission under this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is
|
||||
deemed unenforceable, it shall be automatically reformed to the
|
||||
minimum extent necessary to make it enforceable. If the provision
|
||||
cannot be reformed, it shall be severed from this Public License
|
||||
without affecting the enforceability of the remaining terms and
|
||||
conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no
|
||||
failure to comply consented to unless expressly agreed to by the
|
||||
Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted
|
||||
as a limitation upon, or waiver of, any privileges and immunities
|
||||
that apply to the Licensor or You, including from the legal
|
||||
processes of any jurisdiction or authority.
|
||||
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons is not a party to its public
|
||||
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
||||
its public licenses to material it publishes and in those instances
|
||||
will be considered the “Licensor.” The text of the Creative Commons
|
||||
public licenses is dedicated to the public domain under the CC0 Public
|
||||
Domain Dedication. Except for the limited purpose of indicating that
|
||||
material is shared under a Creative Commons public license or as
|
||||
otherwise permitted by the Creative Commons policies published at
|
||||
creativecommons.org/policies, Creative Commons does not authorize the
|
||||
use of the trademark "Creative Commons" or any other trademark or logo
|
||||
of Creative Commons without its prior written consent including,
|
||||
without limitation, in connection with any unauthorized modifications
|
||||
to any of its public licenses or any other arrangements,
|
||||
understandings, or agreements concerning use of licensed material. For
|
||||
the avoidance of doubt, this paragraph does not form part of the
|
||||
public licenses.
|
||||
|
||||
Creative Commons may be contacted at creativecommons.org.
|
||||
2446
docs/doxy.in
Normal file
2446
docs/doxy.in
Normal file
File diff suppressed because it is too large
Load Diff
5
docs/extra.dox
Normal file
5
docs/extra.dox
Normal file
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* @namespace entt
|
||||
*
|
||||
* @brief `EnTT` default namespace.
|
||||
*/
|
||||
50
src/entt/core/family.hpp
Normal file
50
src/entt/core/family.hpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#ifndef ENTT_CORE_FAMILY_HPP
|
||||
#define ENTT_CORE_FAMILY_HPP
|
||||
|
||||
|
||||
#include<type_traits>
|
||||
#include<cstddef>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Dynamic identifier generator.
|
||||
*
|
||||
* Utility class template that can be used to assign unique identifiers to types
|
||||
* at runtime. Use different specializations to create separate sets of
|
||||
* identifiers.
|
||||
*/
|
||||
template<typename...>
|
||||
class Family {
|
||||
static std::size_t identifier() noexcept {
|
||||
static std::size_t value = 0;
|
||||
return value++;
|
||||
}
|
||||
|
||||
template<typename...>
|
||||
static std::size_t family() noexcept {
|
||||
static const std::size_t value = identifier();
|
||||
return value;
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using family_type = std::size_t;
|
||||
|
||||
/**
|
||||
* @brief Returns an unique identifier for the given type.
|
||||
* @return Statically generated unique identifier for the given type.
|
||||
*/
|
||||
template<typename... Type>
|
||||
static family_type type() noexcept {
|
||||
return family<std::decay_t<Type>...>();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_CORE_FAMILY_HPP
|
||||
96
src/entt/core/ident.hpp
Normal file
96
src/entt/core/ident.hpp
Normal file
@@ -0,0 +1,96 @@
|
||||
#ifndef ENTT_CORE_IDENT_HPP
|
||||
#define ENTT_CORE_IDENT_HPP
|
||||
|
||||
|
||||
#include<type_traits>
|
||||
#include<cstddef>
|
||||
#include<utility>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
template<typename... Types>
|
||||
struct Identifier final: Identifier<Types>... {
|
||||
using identifier_type = std::size_t;
|
||||
|
||||
template<std::size_t... Indexes>
|
||||
constexpr Identifier(std::index_sequence<Indexes...>)
|
||||
: Identifier<Types>{std::index_sequence<Indexes>{}}...
|
||||
{}
|
||||
|
||||
template<typename Type>
|
||||
constexpr std::size_t get() const {
|
||||
return Identifier<std::decay_t<Type>>::get();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename Type>
|
||||
struct Identifier<Type> {
|
||||
using identifier_type = std::size_t;
|
||||
|
||||
template<std::size_t Index>
|
||||
constexpr Identifier(std::index_sequence<Index>)
|
||||
: index{Index}
|
||||
{}
|
||||
|
||||
constexpr std::size_t get() const {
|
||||
return index;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::size_t index;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Types identifers.
|
||||
*
|
||||
* Variable template used to generate identifiers at compile-time for the given
|
||||
* types. Use the `constexpr` `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}
|
||||
* constexpr auto identifiers = entt::ident<AType, AnotherType>;
|
||||
*
|
||||
* switch(aTypeIdentifier) {
|
||||
* case identifers.get<AType>():
|
||||
* // ...
|
||||
* break;
|
||||
* case identifers.get<AnotherType>():
|
||||
* // ...
|
||||
* break;
|
||||
* default:
|
||||
* // ...
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @note
|
||||
* In case of single type list, `get` isn't a member function template:
|
||||
* @code{.cpp}
|
||||
* func(std::integral_constant<
|
||||
* entt::ident<AType>::identifier_type,
|
||||
* entt::ident<AType>::get()
|
||||
* >{});
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Types List of types for which to generate identifiers.
|
||||
*/
|
||||
template<typename... Types>
|
||||
constexpr auto ident = Identifier<std::decay_t<Types>...>{std::make_index_sequence<sizeof...(Types)>{}};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_CORE_IDENT_HPP
|
||||
779
src/entt/entity/registry.hpp
Normal file
779
src/entt/entity/registry.hpp
Normal file
@@ -0,0 +1,779 @@
|
||||
#ifndef ENTT_ENTITY_REGISTRY_HPP
|
||||
#define ENTT_ENTITY_REGISTRY_HPP
|
||||
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <cstddef>
|
||||
#include <cassert>
|
||||
#include "../core/family.hpp"
|
||||
#include "sparse_set.hpp"
|
||||
#include "traits.hpp"
|
||||
#include "view.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Fast and reliable entity-component system.
|
||||
*
|
||||
* The registry is the core class of the entity-component framework.<br/>
|
||||
* It stores entities and arranges pools of components on a per request basis.
|
||||
* By means of a registry, users can manage entities and components and thus
|
||||
* create views to iterate them.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
class Registry {
|
||||
using component_family = Family<struct InternalRegistryComponentFamily>;
|
||||
using view_family = Family<struct InternalRegistryViewFamily>;
|
||||
using traits_type = entt_traits<Entity>;
|
||||
|
||||
template<typename Component>
|
||||
struct Pool: SparseSet<Entity, Component> {
|
||||
using test_fn_type = bool(Registry::*)(Entity) const;
|
||||
|
||||
template<typename... Args>
|
||||
Component & construct(Registry ®istry, Entity entity, Args&&... args) {
|
||||
auto &component = SparseSet<Entity, Component>::construct(entity, std::forward<Args>(args)...);
|
||||
|
||||
for(auto &&listener: listeners) {
|
||||
if((registry.*listener.second)(entity)) {
|
||||
listener.first.construct(entity);
|
||||
}
|
||||
}
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
void destroy(Entity entity) override {
|
||||
SparseSet<Entity, Component>::destroy(entity);
|
||||
|
||||
for(auto &&listener: listeners) {
|
||||
auto &handler = listener.first;
|
||||
|
||||
if(handler.has(entity)) {
|
||||
handler.destroy(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void append(SparseSet<Entity> &handler, test_fn_type fn) {
|
||||
listeners.emplace_back(handler, fn);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::pair<SparseSet<Entity> &, test_fn_type>> listeners;
|
||||
};
|
||||
|
||||
template<typename Component>
|
||||
bool managed() const noexcept {
|
||||
const auto ctype = component_family::type<Component>();
|
||||
return ctype < pools.size() && pools[ctype];
|
||||
}
|
||||
|
||||
template<typename Component>
|
||||
const Pool<Component> & pool() const noexcept {
|
||||
assert(managed<Component>());
|
||||
return static_cast<Pool<Component> &>(*pools[component_family::type<Component>()]);
|
||||
}
|
||||
|
||||
template<typename Component>
|
||||
Pool<Component> & pool() noexcept {
|
||||
assert(managed<Component>());
|
||||
return const_cast<Pool<Component> &>(const_cast<const Registry *>(this)->pool<Component>());
|
||||
}
|
||||
|
||||
template<typename Component>
|
||||
Pool<Component> & ensure() {
|
||||
const auto ctype = component_family::type<Component>();
|
||||
|
||||
if(!(ctype < pools.size())) {
|
||||
pools.resize(ctype + 1);
|
||||
}
|
||||
|
||||
if(!pools[ctype]) {
|
||||
pools[ctype] = std::make_unique<Pool<Component>>();
|
||||
}
|
||||
|
||||
return pool<Component>();
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = typename traits_type::entity_type;
|
||||
/*! @brief Underlying version type. */
|
||||
using version_type = typename traits_type::version_type;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
|
||||
/*! @brief Default constructor, explicit on purpose. */
|
||||
explicit Registry() = default;
|
||||
/*! @brief Default destructor. */
|
||||
~Registry() = default;
|
||||
|
||||
/*! @brief Copying a registry isn't allowed. */
|
||||
Registry(const Registry &) = delete;
|
||||
/*! @brief Moving a registry isn't allowed. */
|
||||
Registry(Registry &&) = delete;
|
||||
|
||||
/*! @brief Copying a registry isn't allowed. @return This registry. */
|
||||
Registry & operator=(const Registry &) = delete;
|
||||
/*! @brief Moving a registry isn't allowed. @return This registry. */
|
||||
Registry & operator=(Registry &&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Returns the number of existing components of the given type.
|
||||
* @tparam Component Type of component of which to return the size.
|
||||
* @return Number of existing components of the given type.
|
||||
*/
|
||||
template<typename Component>
|
||||
size_type size() const noexcept {
|
||||
return managed<Component>() ? pool<Component>().size() : size_type{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of entities still in use.
|
||||
* @return Number of entities still in use.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
return entities.size() - available.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of entities ever created.
|
||||
* @return Number of entities ever created.
|
||||
*/
|
||||
size_type capacity() const noexcept {
|
||||
return entities.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks whether the pool for the given component is empty.
|
||||
* @tparam Component Type of component in which one is interested.
|
||||
* @return True if the pool for the given component is empty, false
|
||||
* otherwise.
|
||||
*/
|
||||
template<typename Component>
|
||||
bool empty() const noexcept {
|
||||
return managed<Component>() ? pool<Component>().empty() : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if there exists at least an entity still in use.
|
||||
* @return True if at least an entity is still in use, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
return entities.size() == available.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Verifies if the entity identifier still refers to a valid entity.
|
||||
* @param entity An entity identifier, either valid or not.
|
||||
* @return True if the identifier is still valid, false otherwise.
|
||||
*/
|
||||
bool valid(entity_type entity) const noexcept {
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
return (entt < entities.size() && entities[entt] == entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the version stored along with the given entity identifier.
|
||||
* @param entity An entity identifier, either valid or not.
|
||||
* @return Version stored along with the given entity identifier.
|
||||
*/
|
||||
version_type version(entity_type entity) const noexcept {
|
||||
return version_type((entity >> traits_type::version_shift) & traits_type::version_mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the actual version for the given entity identifier.
|
||||
*
|
||||
* In case entity identifers are stored around, this function can be used to
|
||||
* know if they are still valid or the entity has been destroyed and
|
||||
* potentially recycled.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn't belong to the registry results
|
||||
* in undefined behavior. An entity belongs to the registry even if it has
|
||||
* been previously destroyed and/or recycled.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* registry doesn't own the given entity.
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
* @return Actual version for the given entity identifier.
|
||||
*/
|
||||
version_type current(entity_type entity) const noexcept {
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
assert(entt < entities.size());
|
||||
return version_type((entities[entt] >> traits_type::version_shift) & traits_type::version_mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a new entity initialized with the given components.
|
||||
*
|
||||
* There are two kinds of entity identifiers:
|
||||
* * Newly created ones in case no entities have been previously destroyed.
|
||||
* * Recycled one with updated versions.
|
||||
*
|
||||
* Users should not care about the type of the returned entity identifier.
|
||||
* In case entity identifers are stored around, the `current` member
|
||||
* function can be used to know if they are still valid or the entity has
|
||||
* been destroyed and potentially recycled.
|
||||
*
|
||||
* The returned entity has fully initialized components assigned.
|
||||
*
|
||||
* @tparam Component A list of components to assign to the entity.
|
||||
* @param components Instances with which to initialize components.
|
||||
* @return A valid entity identifier.
|
||||
*/
|
||||
template<typename... Component>
|
||||
entity_type create(Component&&... components) noexcept {
|
||||
using accumulator_type = int[];
|
||||
const auto entity = create();
|
||||
accumulator_type accumulator = { 0, (ensure<Component>().construct(*this, entity, std::forward<Component>(components)), 0)... };
|
||||
(void)accumulator;
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a new entity to which the given components are assigned.
|
||||
*
|
||||
* There are two kinds of entity identifiers:
|
||||
* * Newly created ones in case no entities have been previously destroyed.
|
||||
* * Recycled one with updated versions.
|
||||
*
|
||||
* Users should not care about the type of the returned entity identifier.
|
||||
* In case entity identifers are stored around, the `current` member
|
||||
* function can be used to know if they are still valid or the entity has
|
||||
* been destroyed and potentially recycled.
|
||||
*
|
||||
* The returned entity has default initialized components assigned.
|
||||
*
|
||||
* @tparam Component A list of components to assign to the entity.
|
||||
* @return A valid entity identifier.
|
||||
*/
|
||||
template<typename... Component>
|
||||
entity_type create() noexcept {
|
||||
using accumulator_type = int[];
|
||||
const auto entity = create();
|
||||
accumulator_type accumulator = { 0, (ensure<Component>().construct(*this, entity), 0)... };
|
||||
(void)accumulator;
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a new entity and returns it.
|
||||
*
|
||||
* There are two kinds of entity identifiers:
|
||||
* * Newly created ones in case no entities have been previously destroyed.
|
||||
* * Recycled one with updated versions.
|
||||
*
|
||||
* Users should not care about the type of the returned entity identifier.
|
||||
* In case entity identifers are stored around, the `current` member
|
||||
* function can be used to know if they are still valid or the entity has
|
||||
* been destroyed and potentially recycled.
|
||||
*
|
||||
* The returned entity has no components assigned.
|
||||
*
|
||||
* @return A valid entity identifier.
|
||||
*/
|
||||
entity_type create() noexcept {
|
||||
entity_type entity;
|
||||
|
||||
if(available.empty()) {
|
||||
entity = entity_type(entities.size());
|
||||
assert(entity < traits_type::entity_mask);
|
||||
assert((entity >> traits_type::version_shift) == entity_type{});
|
||||
entities.push_back(entity);
|
||||
} else {
|
||||
entity = available.back();
|
||||
available.pop_back();
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destroys an entity and lets the registry recycle the identifier.
|
||||
*
|
||||
* When an entity is destroyed, its version is updated and the identifier
|
||||
* can be recycled at any time. In case entity identifers are stored around,
|
||||
* the `current` member function can be used to know if they are still valid
|
||||
* or the entity has been destroyed and potentially recycled.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity.
|
||||
*
|
||||
* @param entity A valid entity identifier
|
||||
*/
|
||||
void destroy(entity_type entity) {
|
||||
assert(valid(entity));
|
||||
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
const auto version = 1 + ((entity >> traits_type::version_shift) & traits_type::version_mask);
|
||||
const auto next = entt | (version << traits_type::version_shift);
|
||||
entities[entt] = next;
|
||||
available.push_back(next);
|
||||
|
||||
for(auto &&cpool: pools) {
|
||||
if(cpool && cpool->has(entity)) {
|
||||
cpool->destroy(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns the given component to the given entity.
|
||||
*
|
||||
* A new instance of the given component is created and initialized with the
|
||||
* arguments provided (the component must have a proper constructor or be of
|
||||
* aggregate type). Then the component is assigned to the given entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity or to assign a component to an entity
|
||||
* that already owns it results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity or if the entity already owns an instance of the given
|
||||
* component.
|
||||
*
|
||||
* @tparam Component Type of the component to create.
|
||||
* @tparam Args Types of arguments to use to construct the component.
|
||||
* @param entity A valid entity identifier.
|
||||
* @param args Parameters to use to initialize the component.
|
||||
* @return A reference to the newly created component.
|
||||
*/
|
||||
template<typename Component, typename... Args>
|
||||
Component & assign(entity_type entity, Args&&... args) {
|
||||
assert(valid(entity));
|
||||
return ensure<Component>().construct(*this, entity, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes the given component from the given entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity or to remove a component from an
|
||||
* entity that doesn't own it results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity or if the entity doesn't own an instance of the given
|
||||
* component.
|
||||
*
|
||||
* @tparam Component Type of the component to remove.
|
||||
* @param entity A valid entity identifier.
|
||||
*/
|
||||
template<typename Component>
|
||||
void remove(entity_type entity) {
|
||||
assert(valid(entity));
|
||||
return pool<Component>().destroy(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the given entity has all the given components.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity.
|
||||
*
|
||||
* @tparam Component Components for which to perform the check.
|
||||
* @param entity A valid entity identifier.
|
||||
* @return True if the entity has all the components, false otherwise.
|
||||
*/
|
||||
template<typename... Component>
|
||||
bool has(entity_type entity) const noexcept {
|
||||
static_assert(sizeof...(Component) > 0, "!");
|
||||
assert(valid(entity));
|
||||
using accumulator_type = bool[];
|
||||
bool all = true;
|
||||
accumulator_type accumulator = { (all = all && managed<Component>() && pool<Component>().has(entity))... };
|
||||
(void)accumulator;
|
||||
return all;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets a reference to the given component owned by the given entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity or to get a component from an entity
|
||||
* that doesn't own it results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity or if the entity doesn't own an instance of the given
|
||||
* component.
|
||||
*
|
||||
* @tparam Component Type of the component to get.
|
||||
* @param entity A valid entity identifier.
|
||||
* @return A reference to the instance of the component owned by the entity.
|
||||
*/
|
||||
template<typename Component>
|
||||
const Component & get(entity_type entity) const noexcept {
|
||||
assert(valid(entity));
|
||||
return pool<Component>().get(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets a reference to the given component owned by the given entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity or to get a component from an entity
|
||||
* that doesn't own it results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity or if the entity doesn't own an instance of the given
|
||||
* component.
|
||||
*
|
||||
* @tparam Component Type of the component to get.
|
||||
* @param entity A valid entity identifier.
|
||||
* @return A reference to the instance of the component owned by the entity.
|
||||
*/
|
||||
template<typename Component>
|
||||
Component & get(entity_type entity) noexcept {
|
||||
return const_cast<Component &>(const_cast<const Registry *>(this)->get<Component>(entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Replaces the given component for the given entity.
|
||||
*
|
||||
* A new instance of the given component is created and initialized with the
|
||||
* arguments provided (the component must have a proper constructor or be of
|
||||
* aggregate type). Then the component is assigned to the given entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity or to replace a component of an
|
||||
* entity that doesn't own it results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity or if the entity doesn't own an instance of the given
|
||||
* component.
|
||||
*
|
||||
* @tparam Component Type of the component to replace.
|
||||
* @tparam Args Types of arguments to use to construct the component.
|
||||
* @param entity A valid entity identifier.
|
||||
* @param args Parameters to use to initialize the component.
|
||||
* @return A reference to the newly created component.
|
||||
*/
|
||||
template<typename Component, typename... Args>
|
||||
Component & replace(entity_type entity, Args&&... args) {
|
||||
assert(valid(entity));
|
||||
return (pool<Component>().get(entity) = Component{std::forward<Args>(args)...});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns or replaces the given component to the given entity.
|
||||
*
|
||||
* Equivalent to the following snippet (pseudocode):
|
||||
*
|
||||
* @code{.cpp}
|
||||
* if(registry.has<Component>(entity)) {
|
||||
* registry.replace<Component>(entity, args...);
|
||||
* } else {
|
||||
* registry.assign<Component>(entity, args...);
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* Prefer this function anyway because it has slighlty better
|
||||
* performance.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity.
|
||||
*
|
||||
* @tparam Component Type of the component to assign or replace.
|
||||
* @tparam Args Types of arguments to use to construct the component.
|
||||
* @param entity A valid entity identifier.
|
||||
* @param args Parameters to use to initialize the component.
|
||||
* @return A reference to the newly created component.
|
||||
*/
|
||||
template<typename Component, typename... Args>
|
||||
Component & accomodate(entity_type entity, Args&&... args) {
|
||||
assert(valid(entity));
|
||||
auto &cpool = ensure<Component>();
|
||||
|
||||
return (cpool.has(entity)
|
||||
? (cpool.get(entity) = Component{std::forward<Args>(args)...})
|
||||
: cpool.construct(*this, entity, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sorts the pool of the given component.
|
||||
*
|
||||
* The order of the elements in a pool is highly affected by assignements
|
||||
* of components to entities and deletions. Components are arranged to
|
||||
* maximize the performance during iterations and users should not make any
|
||||
* assumption on the order.<br/>
|
||||
* This function can be used to impose an order to the elements in the pool
|
||||
* for the given component. The order is kept valid until a component of the
|
||||
* given type is assigned or removed from an entity.
|
||||
*
|
||||
* The comparison function object must return `true` if the first element
|
||||
* is _less_ than the second one, `false` otherwise. The signature of the
|
||||
* comparison function should be equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* bool(auto e1, auto e2)
|
||||
* @endcode
|
||||
*
|
||||
* Where `e1` and `e2` are valid entity identifiers.
|
||||
*
|
||||
* @tparam Component Type of the components to sort.
|
||||
* @tparam Compare Type of the comparison function object.
|
||||
* @param compare A valid comparison function object.
|
||||
*/
|
||||
template<typename Component, typename Compare>
|
||||
void sort(Compare compare) {
|
||||
auto &cpool = ensure<Component>();
|
||||
|
||||
cpool.sort([&cpool, compare = std::move(compare)](auto lhs, auto rhs) {
|
||||
return compare(static_cast<const Component &>(cpool.get(lhs)), static_cast<const Component &>(cpool.get(rhs)));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sorts two pools of components in the same way.
|
||||
*
|
||||
* The order of the elements in a pool is highly affected by assignements
|
||||
* of components to entities and deletions. Components are arranged to
|
||||
* maximize the performance during iterations and users should not make any
|
||||
* assumption on the order.
|
||||
*
|
||||
* It happens that different pools of components must be sorted the same way
|
||||
* because of runtime and/or performance constraints. This function can be
|
||||
* used to order a pool of components according to the order between the
|
||||
* entities in another pool of components.
|
||||
*
|
||||
* @b How @b it @b works
|
||||
*
|
||||
* Being `A` and `B` the two sets where `B` is the master (the one the order
|
||||
* of which rules) and `A` is the slave (the one to sort), after a call to
|
||||
* this function an iterator for `A` will return the entities according to
|
||||
* the following rules:
|
||||
*
|
||||
* * All the entities in `A` that are also in `B` are returned first
|
||||
* according to the order they have in `B`.
|
||||
* * All the entities in `A` that are not in `B` are returned in no
|
||||
* particular order after all the other entities.
|
||||
*
|
||||
* Any subsequent change to `B` won't affect the order in `A`.
|
||||
*
|
||||
* @tparam To Type of the components to sort.
|
||||
* @tparam From Type of the components to use to sort.
|
||||
*/
|
||||
template<typename To, typename From>
|
||||
void sort() {
|
||||
ensure<To>().respect(ensure<From>());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resets the given component for the given entity.
|
||||
*
|
||||
* If the entity has an instance of the component, this function removes the
|
||||
* component from the entity. Otherwise it does nothing.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity.
|
||||
*
|
||||
* @tparam Component Type of the component to reset.
|
||||
* @param entity A valid entity identifier.
|
||||
*/
|
||||
template<typename Component>
|
||||
void reset(entity_type entity) {
|
||||
assert(valid(entity));
|
||||
|
||||
if(managed<Component>()) {
|
||||
auto &cpool = pool<Component>();
|
||||
|
||||
if(cpool.has(entity)) {
|
||||
cpool.destroy(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resets the pool of the given component.
|
||||
*
|
||||
* For each entity that has an instance of the given component, the
|
||||
* component itself is removed and thus destroyed.
|
||||
*
|
||||
* @tparam Component type of the component whose pool must be reset.
|
||||
*/
|
||||
template<typename Component>
|
||||
void reset() {
|
||||
if(managed<Component>()) {
|
||||
auto &cpool = pool<Component>();
|
||||
|
||||
for(auto entity: entities) {
|
||||
if(cpool.has(entity)) {
|
||||
cpool.destroy(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resets the whole registry.
|
||||
*
|
||||
* Destroys all the entities. After a call to `reset`, all the entities
|
||||
* previously created are recycled with a new version number. In case entity
|
||||
* identifers are stored around, the `current` member function can be used
|
||||
* to know if they are still valid.
|
||||
*/
|
||||
void reset() {
|
||||
available.clear();
|
||||
pools.clear();
|
||||
|
||||
for(auto &&entity: entities) {
|
||||
const auto version = 1 + ((entity >> traits_type::version_shift) & traits_type::version_mask);
|
||||
entity = (entity & traits_type::entity_mask) | (version << traits_type::version_shift);
|
||||
available.push_back(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a standard view for the given components.
|
||||
*
|
||||
* This kind of views are created on the fly and share with the registry its
|
||||
* internal data structures.<br/>
|
||||
* Feel free to discard a view after the use. Creating and destroying a view
|
||||
* is an incredibly cheap operation because they do not require any type of
|
||||
* initialization.<br/>
|
||||
* As a rule of thumb, storing a view should never be an option.
|
||||
*
|
||||
* Standard views do their best to iterate the smallest set of candidate
|
||||
* entites. In particular:
|
||||
* * Single component views are incredibly fast and iterate a packed array
|
||||
* of entities, all of which has the given component.
|
||||
* * Multi component views look at the number of entities available for each
|
||||
* component and pick up a reference to the smallest set of candidates to
|
||||
* test for the given components.
|
||||
*
|
||||
* @note
|
||||
* Multi component views are pretty fast. However their performance tend to
|
||||
* degenerate when the number of components to iterate grows up and the most
|
||||
* of the entities have all the given components.<br/>
|
||||
* To get a performance boost, consider using a PersistentView instead.
|
||||
*
|
||||
* @see View
|
||||
* @see View<Entity, Component>
|
||||
* @see PersistentView
|
||||
*
|
||||
* @tparam Component Type of the components used to construct the view.
|
||||
* @return A newly created standard view.
|
||||
*/
|
||||
template<typename... Component>
|
||||
View<Entity, Component...> view() {
|
||||
return View<Entity, Component...>{ensure<Component>()...};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Prepares the internal data structures used by persistent views.
|
||||
*
|
||||
* Persistent views are an incredibly fast tool used to iterate a packed
|
||||
* array of entities all of which have specific components.<br/>
|
||||
* The initialization of a persistent view is also a pretty cheap operation,
|
||||
* but for the first time they are created. That's mainly because of the
|
||||
* internal data structures of the registry that are dedicated to this kind
|
||||
* of views and that don't exist yet the very first time they are
|
||||
* requested.<br/>
|
||||
* To avoid costly operations, internal data structures for persistent views
|
||||
* can be prepared with this function. Just use the same set of components
|
||||
* that would have been used otherwise to contruct the view.
|
||||
*
|
||||
* @tparam Component Types of the components used to prepare the view.
|
||||
*/
|
||||
template<typename... Component>
|
||||
void prepare() {
|
||||
static_assert(sizeof...(Component) > 1, "!");
|
||||
const auto vtype = view_family::type<Component...>();
|
||||
|
||||
if(!(vtype < handlers.size())) {
|
||||
handlers.resize(vtype + 1);
|
||||
}
|
||||
|
||||
if(!handlers[vtype]) {
|
||||
using accumulator_type = int[];
|
||||
|
||||
auto handler = std::make_unique<SparseSet<Entity>>();
|
||||
|
||||
for(auto entity: view<Component...>()) {
|
||||
handler->construct(entity);
|
||||
}
|
||||
|
||||
accumulator_type accumulator = {
|
||||
(ensure<Component>().append(*handler, &Registry::has<Component...>), 0)...
|
||||
};
|
||||
|
||||
handlers[vtype] = std::move(handler);
|
||||
(void)accumulator;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a persistent view for the given components.
|
||||
*
|
||||
* This kind of views are created on the fly and share with the registry its
|
||||
* internal data structures.<br/>
|
||||
* Feel free to discard a view after the use. Creating and destroying a view
|
||||
* is an incredibly cheap operation because they do not require any type of
|
||||
* initialization.<br/>
|
||||
* As a rule of thumb, storing a view should never be an option.
|
||||
*
|
||||
* Persistent views are the right choice to iterate entites when the number
|
||||
* of components grows up and the most of the entities have all the given
|
||||
* components.<br/>
|
||||
* However they have also drawbacks:
|
||||
* * Each kind of persistent view requires a dedicated data structure that
|
||||
* is allocated within the registry and it increases memory pressure.
|
||||
* * Internal data structures used to construct persistent views must be
|
||||
* kept updated and it affects slightly construction and destruction of
|
||||
* entities and components.
|
||||
*
|
||||
* That being said, persistent views are an incredibly powerful tool if used
|
||||
* with care and offer a boost of performance undoubtedly.
|
||||
*
|
||||
* @note
|
||||
* Consider to use the `prepare` member function to initialize the internal
|
||||
* data structures used by persistent views when the registry is still
|
||||
* empty. Initialization could be a costly operation otherwise and it will
|
||||
* be performed the very first time each view is created.
|
||||
*
|
||||
* @see View
|
||||
* @see View<Entity, Component>
|
||||
* @see PersistentView
|
||||
*
|
||||
* @tparam Component Types of the components used to construct the view.
|
||||
* @return A newly created persistent view.
|
||||
*/
|
||||
template<typename... Component>
|
||||
PersistentView<Entity, Component...> persistent() {
|
||||
static_assert(sizeof...(Component) > 1, "!");
|
||||
prepare<Component...>();
|
||||
return PersistentView<Entity, Component...>{*handlers[view_family::type<Component...>()], ensure<Component>()...};
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<SparseSet<Entity>>> handlers;
|
||||
std::vector<std::unique_ptr<SparseSet<Entity>>> pools;
|
||||
std::vector<entity_type> available;
|
||||
std::vector<entity_type> entities;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Default registry class.
|
||||
*
|
||||
* The default registry is the best choice for almost all the applications.<br/>
|
||||
* Users should have a really good reason to choose something different.
|
||||
*/
|
||||
using DefaultRegistry = Registry<std::uint32_t>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_REGISTRY_HPP
|
||||
575
src/entt/entity/sparse_set.hpp
Normal file
575
src/entt/entity/sparse_set.hpp
Normal file
@@ -0,0 +1,575 @@
|
||||
#ifndef ENTT_ENTITY_SPARSE_SET_HPP
|
||||
#define ENTT_ENTITY_SPARSE_SET_HPP
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <cstddef>
|
||||
#include <cassert>
|
||||
#include "traits.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Sparse set.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error, but for a few reasonable cases.
|
||||
*/
|
||||
template<typename...>
|
||||
class SparseSet;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Basic sparse set implementation.
|
||||
*
|
||||
* Sparse set or packed array or whatever is the name users give it.<br/>
|
||||
* 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. View and PersistentView are 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.
|
||||
*
|
||||
* @note
|
||||
* There are 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.
|
||||
*
|
||||
* @note
|
||||
* Internal data structures arrange elements to maximize performance. Because of
|
||||
* that, there are no guarantees that elements have the expected order when
|
||||
* iterate directly the internal packed array (see `data` and `size` member
|
||||
* functions for that). Use `begin` and `end` instead.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
class SparseSet<Entity> {
|
||||
using traits_type = entt_traits<Entity>;
|
||||
|
||||
struct Iterator {
|
||||
using value_type = Entity;
|
||||
|
||||
Iterator(const std::vector<Entity> *direct, std::size_t pos)
|
||||
: direct{direct}, pos{pos}
|
||||
{}
|
||||
|
||||
Iterator & operator++() noexcept {
|
||||
return --pos, *this;
|
||||
}
|
||||
|
||||
Iterator operator++(int) noexcept {
|
||||
Iterator orig = *this;
|
||||
return ++(*this), orig;
|
||||
}
|
||||
|
||||
bool operator==(const Iterator &other) const noexcept {
|
||||
return other.pos == pos && other.direct == direct;
|
||||
}
|
||||
|
||||
bool operator!=(const Iterator &other) const noexcept {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
value_type operator*() const noexcept {
|
||||
return (*direct)[pos-1];
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<Entity> *direct;
|
||||
std::size_t pos;
|
||||
};
|
||||
|
||||
public:
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
/*! @brief Entity dependent position type. */
|
||||
using pos_type = entity_type;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Input iterator type. */
|
||||
using iterator_type = Iterator;
|
||||
|
||||
/*! @brief Default constructor, explicit on purpose. */
|
||||
explicit SparseSet() noexcept = default;
|
||||
|
||||
/*! @brief Copying a sparse set isn't allowed. */
|
||||
SparseSet(const SparseSet &) = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
SparseSet(SparseSet &&) = default;
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~SparseSet() noexcept = default;
|
||||
|
||||
/*! @brief Copying a sparse set isn't allowed. @return This sparse set. */
|
||||
SparseSet & operator=(const SparseSet &) = delete;
|
||||
/*! @brief Default move assignment operator. @return This sparse set. */
|
||||
SparseSet & operator=(SparseSet &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Returns the number of elements in the sparse set.
|
||||
*
|
||||
* The number of elements is also the size of the internal packed array.
|
||||
* There is no guarantee that the internal sparse array has the same size.
|
||||
* Usually the size of the internal sparse array is equal or greater than
|
||||
* the one of the internal packed array.
|
||||
*
|
||||
* @return Number of elements.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
return direct.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks whether the sparse set is empty.
|
||||
* @return True is the sparse set is empty, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
return direct.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the internal packed array.
|
||||
*
|
||||
* The returned pointer is such that range `[data(), data() + size()]` is
|
||||
* always a valid range, even if the container is empty.
|
||||
*
|
||||
* @note
|
||||
* There are no guarantees on the order, even though `sort` has been
|
||||
* previously invoked. Internal data structures arrange elements to maximize
|
||||
* performance. Accessing them directly gives a performance boost but less
|
||||
* guarantees. Use `begin` and `end` if you want to iterate the sparse set
|
||||
* in the expected order.
|
||||
*
|
||||
* @return A pointer to the internal packed array.
|
||||
*/
|
||||
const entity_type * data() const noexcept {
|
||||
return direct.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the beginning.
|
||||
*
|
||||
* The returned iterator points to the first element of the internal packed
|
||||
* array. If the sparse set is empty, the returned iterator will be equal to
|
||||
* `end()`.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed by a call to `sort`.
|
||||
*
|
||||
* @return An iterator to the first element of the internal packed array.
|
||||
*/
|
||||
iterator_type begin() const noexcept {
|
||||
return Iterator{&direct, direct.size()};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the end.
|
||||
*
|
||||
* The returned iterator points to the element following the last element in
|
||||
* the internal packed array. Attempting to dereference the returned
|
||||
* iterator results in undefined behavior.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed by a call to `sort`.
|
||||
*
|
||||
* @return An iterator to the element following the last element of the
|
||||
* internal packed array.
|
||||
*/
|
||||
iterator_type end() const noexcept {
|
||||
return Iterator{&direct, 0};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the sparse set contains the given entity.
|
||||
* @param entity A valid entity identifier.
|
||||
* @return True if the sparse set contains the entity, false otherwise.
|
||||
*/
|
||||
bool has(entity_type entity) const noexcept {
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
return entt < reverse.size() && reverse[entt] < direct.size() && direct[reverse[entt]] == entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the position of the entity in the sparse set.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to get the position of an entity that doesn't belong to the
|
||||
* sparse set results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* sparse set doesn't contain the given entity.
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
* @return The position of the entity in the sparse set.
|
||||
*/
|
||||
pos_type get(entity_type entity) const noexcept {
|
||||
assert(has(entity));
|
||||
return reverse[entity & traits_type::entity_mask];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns an entity to the sparse set.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to assign an entity that already belongs to the sparse set
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* sparse set already contains the given entity.
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
* @return The position of the entity in the internal packed array.
|
||||
*/
|
||||
pos_type construct(entity_type entity) {
|
||||
assert(!has(entity));
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
|
||||
if(!(entt < reverse.size())) {
|
||||
reverse.resize(entt+1);
|
||||
}
|
||||
|
||||
const auto pos = pos_type(direct.size());
|
||||
reverse[entt] = pos;
|
||||
direct.emplace_back(entity);
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes the given entity from the sparse set.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to remove an entity that doesn't belong to the sparse set
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* sparse set doesn't contain the given entity.
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
*/
|
||||
virtual void destroy(entity_type entity) {
|
||||
assert(has(entity));
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
const auto back = direct.back() & traits_type::entity_mask;
|
||||
const auto pos = reverse[entt];
|
||||
reverse[back] = pos;
|
||||
direct[pos] = direct.back();
|
||||
direct.pop_back();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Swaps the position of the entities in the internal packed array.
|
||||
*
|
||||
* For what it's worth, this function affects both the internal sparse array
|
||||
* and the internal packed array. Users should not care of that anyway.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to swap entities that don't belong to the sparse set results
|
||||
* in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* sparse set doesn't contain the given entities.
|
||||
*
|
||||
* @param lhs A valid entity identifier.
|
||||
* @param rhs A valid entity identifier.
|
||||
*/
|
||||
virtual void swap(entity_type lhs, entity_type rhs) {
|
||||
assert(has(lhs));
|
||||
assert(has(rhs));
|
||||
const auto le = lhs & traits_type::entity_mask;
|
||||
const auto re = rhs & traits_type::entity_mask;
|
||||
std::swap(direct[reverse[le]], direct[reverse[re]]);
|
||||
std::swap(reverse[le], reverse[re]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sort entities according to the given comparison function.
|
||||
*
|
||||
* Sort the elements so that iterating the sparse set with a couple of
|
||||
* iterators returns them in the expected order. See `begin` and `end` for
|
||||
* more details.
|
||||
*
|
||||
* @note
|
||||
* Attempting to iterate elements using the raw pointer returned by `data`
|
||||
* gives no guarantees on the order, even though `sort` has been invoked.
|
||||
*
|
||||
* @tparam Compare Type of the comparison function.
|
||||
* @param compare A comparison function whose signature shall be equivalent
|
||||
* to: `bool(Entity, Entity)`.
|
||||
*/
|
||||
template<typename Compare>
|
||||
void sort(Compare compare) {
|
||||
std::vector<pos_type> copy{direct.cbegin(), direct.cend()};
|
||||
std::sort(copy.begin(), copy.end(), [compare = std::move(compare)](auto... args) {
|
||||
return !compare(args...);
|
||||
});
|
||||
|
||||
for(pos_type i = 0; i < copy.size(); ++i) {
|
||||
if(direct[i] != copy[i]) {
|
||||
swap(direct[i], copy[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sort entities according to their order in the given sparse set.
|
||||
*
|
||||
* Entities that are part of both the sparse sets are ordered internally
|
||||
* according to the order they have in `other`. All the other entities goes
|
||||
* to the end of the list and there are no guarantess 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 `sort`. See `begin` and `end` for more
|
||||
* details.
|
||||
*
|
||||
* @note
|
||||
* Attempting to iterate elements using the raw pointer returned by `data`
|
||||
* gives no guarantees on the order, even though `sort` has been invoked.
|
||||
*
|
||||
* @param other The sparse sets that imposes the order of the entities.
|
||||
*/
|
||||
void respect(const SparseSet<Entity> &other) {
|
||||
struct Bool { bool value{false}; };
|
||||
std::vector<Bool> check(std::max(other.reverse.size(), reverse.size()));
|
||||
|
||||
for(auto entity: other.direct) {
|
||||
check[entity & traits_type::entity_mask].value = true;
|
||||
}
|
||||
|
||||
sort([this, &other, &check](auto lhs, auto rhs) {
|
||||
const auto le = lhs & traits_type::entity_mask;
|
||||
const auto re = rhs & traits_type::entity_mask;
|
||||
|
||||
const bool bLhs = check[le].value;
|
||||
const bool bRhs = check[re].value;
|
||||
bool compare = false;
|
||||
|
||||
if(bLhs && bRhs) {
|
||||
compare = other.get(rhs) < other.get(lhs);
|
||||
} else if(!bLhs && !bRhs) {
|
||||
compare = re < le;
|
||||
} else {
|
||||
compare = bLhs;
|
||||
}
|
||||
|
||||
return compare;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resets the sparse set.
|
||||
*/
|
||||
virtual void reset() {
|
||||
reverse.clear();
|
||||
direct.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<entity_type> reverse;
|
||||
std::vector<entity_type> direct;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Extended sparse set implementation.
|
||||
*
|
||||
* This specialization of a sparse set associates an object to an entity. The
|
||||
* main purpose of this class is to use sparse sets to store components in a
|
||||
* Registry. It guarantees fast access both to the elements and to the entities.
|
||||
*
|
||||
* @note
|
||||
* Entities and objects have the same order. It's guaranteed both in case of raw
|
||||
* access (either to entities or objects) and when using input iterators.
|
||||
*
|
||||
* @note
|
||||
* Internal data structures arrange elements to maximize performance. Because of
|
||||
* that, there are no guarantees that elements have the expected order when
|
||||
* iterate directly the internal packed array (see `raw` and `size` member
|
||||
* functions for that). Use `begin` and `end` instead.
|
||||
*
|
||||
* @sa SparseSet<Entity>
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Type Type of objects assigned to the entities.
|
||||
*/
|
||||
template<typename Entity, typename Type>
|
||||
class SparseSet<Entity, Type>: public SparseSet<Entity> {
|
||||
using underlying_type = SparseSet<Entity>;
|
||||
|
||||
public:
|
||||
/*! @brief Type of the objects associated to the entities. */
|
||||
using type = Type;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = typename underlying_type::entity_type;
|
||||
/*! @brief Entity dependent position type. */
|
||||
using pos_type = typename underlying_type::pos_type;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename underlying_type::size_type;
|
||||
/*! @brief Input iterator type. */
|
||||
using iterator_type = typename underlying_type::iterator_type;
|
||||
|
||||
/*! @brief Default constructor, explicit on purpose. */
|
||||
explicit SparseSet() noexcept = default;
|
||||
|
||||
/*! @brief Copying a sparse set isn't allowed. */
|
||||
SparseSet(const SparseSet &) = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
SparseSet(SparseSet &&) = default;
|
||||
|
||||
/*! @brief Copying a sparse set isn't allowed. @return This sparse set. */
|
||||
SparseSet & operator=(const SparseSet &) = delete;
|
||||
/*! @brief Default move assignment operator. @return This sparse set. */
|
||||
SparseSet & operator=(SparseSet &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Direct access to the array of objects.
|
||||
*
|
||||
* The returned pointer is such that range `[raw(), raw() + size()]` is
|
||||
* always a valid range, even if the container is empty.
|
||||
*
|
||||
* @note
|
||||
* There are no guarantees on the order, even though `sort` has been
|
||||
* previously invoked. Internal data structures arrange elements to maximize
|
||||
* performance. Accessing them directly gives a performance boost but less
|
||||
* guarantees. Use `begin` and `end` if you want to iterate the sparse set
|
||||
* in the expected order.
|
||||
*
|
||||
* @return A pointer to the array of objects.
|
||||
*/
|
||||
const type * raw() const noexcept {
|
||||
return instances.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the array of objects.
|
||||
*
|
||||
* The returned pointer is such that range `[raw(), raw() + size()]` is
|
||||
* always a valid range, even if the container is empty.
|
||||
*
|
||||
* @note
|
||||
* There are no guarantees on the order, even though `sort` has been
|
||||
* previously invoked. Internal data structures arrange elements to maximize
|
||||
* performance. Accessing them directly gives a performance boost but less
|
||||
* guarantees. Use `begin` and `end` if you want to iterate the sparse set
|
||||
* in the expected order.
|
||||
*
|
||||
* @return A pointer to the array of objects.
|
||||
*/
|
||||
type * raw() noexcept {
|
||||
return instances.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the object associated to the given entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn't belong to the sparse set results
|
||||
* in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* sparse set doesn't contain the given entity.
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
* @return The object associated to the entity.
|
||||
*/
|
||||
const type & get(entity_type entity) const noexcept {
|
||||
return instances[underlying_type::get(entity)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the object associated to the given entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn't belong to the sparse set results
|
||||
* in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* sparse set doesn't contain the given entity.
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
* @return The object associated to the entity.
|
||||
*/
|
||||
type & get(entity_type entity) noexcept {
|
||||
return const_cast<type &>(const_cast<const SparseSet *>(this)->get(entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns an entity to the sparse set and constructs its object.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that already belongs to the sparse set
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* sparse set already contains the given entity.
|
||||
*
|
||||
* @tparam Args Types of arguments to use to construct the object.
|
||||
* @param entity A valid entity identifier.
|
||||
* @param args Parameters to use to construct an object for the entity.
|
||||
* @return The object associated to the entity.
|
||||
*/
|
||||
template<typename... Args>
|
||||
type & construct(entity_type entity, Args&&... args) {
|
||||
underlying_type::construct(entity);
|
||||
instances.push_back({ std::forward<Args>(args)... });
|
||||
return instances.back();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes an entity from the sparse set and destroies its object.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn't belong to the sparse set results
|
||||
* in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* sparse set doesn't contain the given entity.
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
*/
|
||||
void destroy(entity_type entity) override {
|
||||
instances[underlying_type::get(entity)] = std::move(instances.back());
|
||||
instances.pop_back();
|
||||
underlying_type::destroy(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Swaps the two entities and their objects.
|
||||
*
|
||||
* @note
|
||||
* This function doesn't swap objects between entities. It exchanges entity
|
||||
* and object positions in the sparse set. It's used mainly for sorting.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use entities that don't belong to the sparse set results
|
||||
* in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* sparse set doesn't contain the given entities.
|
||||
*
|
||||
* @param lhs A valid entity identifier.
|
||||
* @param rhs A valid entity identifier.
|
||||
*/
|
||||
void swap(entity_type lhs, entity_type rhs) override {
|
||||
std::swap(instances[underlying_type::get(lhs)], instances[underlying_type::get(rhs)]);
|
||||
underlying_type::swap(lhs, rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resets the sparse set.
|
||||
*/
|
||||
void reset() override {
|
||||
underlying_type::reset();
|
||||
instances.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<type> instances;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_SPARSE_SET_HPP
|
||||
93
src/entt/entity/traits.hpp
Normal file
93
src/entt/entity/traits.hpp
Normal file
@@ -0,0 +1,93 @@
|
||||
#ifndef ENTT_ENTITY_ENTT_HPP
|
||||
#define ENTT_ENTITY_ENTT_HPP
|
||||
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Entity traits.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error unless the template parameter is an accepted entity type.
|
||||
*/
|
||||
template<typename>
|
||||
struct entt_traits;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Entity traits for a 16 bits entity identifier.
|
||||
*
|
||||
* A 16 bits entity identifier guarantees:
|
||||
* * 12 bits for the entity number (up to 4k entities).
|
||||
* * 4 bit for the version (resets in [0-15]).
|
||||
*/
|
||||
template<>
|
||||
struct entt_traits<std::uint16_t> {
|
||||
/*! @brief Underlying entity type. */
|
||||
using entity_type = std::uint16_t;
|
||||
/*! @brief Underlying version type. */
|
||||
using version_type = std::uint8_t;
|
||||
|
||||
/*! @brief Mask to use to get the entity number out of an identifier. */
|
||||
static constexpr auto entity_mask = 0xFFF;
|
||||
/*! @brief Mask to use to get the version out of an identifier. */
|
||||
static constexpr auto version_mask = 0xF;
|
||||
/*! @brief Extent of the entity number within an identifier. */
|
||||
static constexpr auto version_shift = 12;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Entity traits for a 32 bits entity identifier.
|
||||
*
|
||||
* A 32 bits entity identifier guarantees:
|
||||
* * 24 bits for the entity number (suitable for almost all the games).
|
||||
* * 8 bit for the version (resets in [0-255]).
|
||||
*/
|
||||
template<>
|
||||
struct entt_traits<std::uint32_t> {
|
||||
/*! @brief Underlying entity type. */
|
||||
using entity_type = std::uint32_t;
|
||||
/*! @brief Underlying version type. */
|
||||
using version_type = std::uint16_t;
|
||||
|
||||
/*! @brief Mask to use to get the entity number out of an identifier. */
|
||||
static constexpr auto entity_mask = 0xFFFFFF;
|
||||
/*! @brief Mask to use to get the version out of an identifier. */
|
||||
static constexpr auto version_mask = 0xFF;
|
||||
/*! @brief Extent of the entity number within an identifier. */
|
||||
static constexpr auto version_shift = 24;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Entity traits for a 64 bits entity identifier.
|
||||
*
|
||||
* A 64 bits entity identifier guarantees:
|
||||
* * 40 bits for the entity number (an indecently large number).
|
||||
* * 24 bit for the version (an indecently large number).
|
||||
*/
|
||||
template<>
|
||||
struct entt_traits<std::uint64_t> {
|
||||
/*! @brief Underlying entity type. */
|
||||
using entity_type = std::uint64_t;
|
||||
/*! @brief Underlying version type. */
|
||||
using version_type = std::uint32_t;
|
||||
|
||||
/*! @brief Mask to use to get the entity number out of an identifier. */
|
||||
static constexpr auto entity_mask = 0xFFFFFFFFFF;
|
||||
/*! @brief Mask to use to get the version out of an identifier. */
|
||||
static constexpr auto version_mask = 0xFFFFFF;
|
||||
/*! @brief Extent of the entity number within an identifier. */
|
||||
static constexpr auto version_shift = 40;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_ENTT_HPP
|
||||
764
src/entt/entity/view.hpp
Normal file
764
src/entt/entity/view.hpp
Normal file
@@ -0,0 +1,764 @@
|
||||
#ifndef ENTT_ENTITY_VIEW_HPP
|
||||
#define ENTT_ENTITY_VIEW_HPP
|
||||
|
||||
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include "sparse_set.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Persistent view.
|
||||
*
|
||||
* A persistent view returns all the entities and only the entities that have
|
||||
* at least the given components. Moreover, it's guaranteed that the entity list
|
||||
* is thightly packed in memory for fast iterations.<br/>
|
||||
* In general, persistent views don't stay true to the order of any set of
|
||||
* components unless users explicitly sort them.
|
||||
*
|
||||
* @b Important
|
||||
*
|
||||
* Iterators aren't invalidated if:
|
||||
*
|
||||
* * New instances of the given components are created and assigned to entities.
|
||||
* * The entity currently pointed is modified (as an example, if one of the
|
||||
* given components is removed from the entity to which the iterator points).
|
||||
*
|
||||
* In all the other cases, modify the pools of the given components somehow
|
||||
* invalidates all the iterators and using them results in undefined behavior.
|
||||
*
|
||||
* @note
|
||||
* Views share references to the underlying data structures with 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
|
||||
* views.<br/>
|
||||
* Moreover, sorting a persistent view affects all the other views of the same
|
||||
* type (it means that users don't have to call `sort` on each view to sort all
|
||||
* of them because they share the set of entities).
|
||||
*
|
||||
* @warning
|
||||
* Lifetime of a view must overcome the one of the registry that generated it.
|
||||
* In any other case, attempting to use a view results in undefined behavior.
|
||||
*
|
||||
* @sa View
|
||||
* @sa View<Entity, Component>
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Component Types of components iterated by the view.
|
||||
*/
|
||||
template<typename Entity, typename... Component>
|
||||
class PersistentView final {
|
||||
static_assert(sizeof...(Component) > 1, "!");
|
||||
|
||||
template<typename Comp>
|
||||
using pool_type = SparseSet<Entity, Comp>;
|
||||
|
||||
using view_type = SparseSet<Entity>;
|
||||
|
||||
public:
|
||||
/*! Input iterator type. */
|
||||
using iterator_type = typename view_type::iterator_type;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = typename view_type::entity_type;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename view_type::size_type;
|
||||
|
||||
/**
|
||||
* @brief Constructs a persistent view around a dedicated pool of entities.
|
||||
*
|
||||
* A persistent view is created out of:
|
||||
* * A dedicated pool of entities that is shared between all the persistent
|
||||
* views of the same type.
|
||||
* * A bunch of pools of components to which to refer to get instances.
|
||||
*
|
||||
* @param view Shared reference to a dedicated pool of entities.
|
||||
* @param pools References to pools of components.
|
||||
*/
|
||||
explicit PersistentView(view_type &view, pool_type<Component>&... pools) noexcept
|
||||
: view{view}, pools{pools...}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of entities that have the given components.
|
||||
* @return Number of entities that have the given components.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
return view.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the list of entities.
|
||||
*
|
||||
* The returned pointer is such that range `[data(), data() + size()]` is
|
||||
* always a valid range, even if the container is empty.
|
||||
*
|
||||
* @note
|
||||
* There are no guarantees on the order of the entities. Use `begin` and
|
||||
* `end` if you want to iterate the view in the expected order.
|
||||
*
|
||||
* @return A pointer to the array of entities.
|
||||
*/
|
||||
const entity_type * data() const noexcept {
|
||||
return view.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the first entity that has the given
|
||||
* components.
|
||||
*
|
||||
* The returned iterator points to the first entity that has the given
|
||||
* components. If the view is empty, the returned iterator will be equal to
|
||||
* `end()`.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed to the underlying data
|
||||
* structures.
|
||||
*
|
||||
* @return An iterator to the first entity that has the given components.
|
||||
*/
|
||||
iterator_type begin() const noexcept {
|
||||
return view.begin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed to the underlying data
|
||||
* structures.
|
||||
*
|
||||
* @return An iterator to the entity following the last entity that has the
|
||||
* given components.
|
||||
*/
|
||||
iterator_type end() const noexcept {
|
||||
return view.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the component assigned to the given entity.
|
||||
*
|
||||
* Prefer this function instead of `Registry::get` during iterations. It has
|
||||
* far better performance than its companion function.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid component type results in a compilation
|
||||
* error. Attempting to use an entity that doesn't belong to the view
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if
|
||||
* the view doesn't contain the given entity.
|
||||
*
|
||||
* @tparam Comp Type of the component to get.
|
||||
* @param entity A valid entity identifier.
|
||||
* @return The component assigned to the entity.
|
||||
*/
|
||||
template<typename Comp>
|
||||
const Comp & get(entity_type entity) const noexcept {
|
||||
return std::get<pool_type<Comp> &>(pools).get(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the component assigned to the given entity.
|
||||
*
|
||||
* Prefer this function instead of `Registry::get` during iterations.
|
||||
* It has far better performance than its companion function.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid component type results in a compilation
|
||||
* error. Attempting to use an entity that doesn't belong to the view
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if
|
||||
* the view doesn't contain the given entity.
|
||||
*
|
||||
* @tparam Comp Type of the component to get.
|
||||
* @param entity A valid entity identifier.
|
||||
* @return The component assigned to the entity.
|
||||
*/
|
||||
template<typename Comp>
|
||||
Comp & get(entity_type entity) noexcept {
|
||||
return const_cast<Comp &>(const_cast<const PersistentView *>(this)->get<Comp>(entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterate the entities and applies them the given function object.
|
||||
*
|
||||
* The function object is invoked for each entity. It is provided with the
|
||||
* entity itself and a set of references to all the components of the
|
||||
* view.<br/>
|
||||
* The signature of the function should be equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(entity_type, Component &...);
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func &&func) {
|
||||
for(auto entity: *this) {
|
||||
std::forward<Func>(func)(entity, get<Component>(entity)...);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterate the entities and applies them the given function object.
|
||||
*
|
||||
* The function object is invoked for each entity. It is provided with the
|
||||
* entity itself and a set of const references to all the components of the
|
||||
* view.<br/>
|
||||
* The signature of the function should be equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(entity_type, const Component &...);
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func &&func) const {
|
||||
for(auto entity: *this) {
|
||||
std::forward<Func>(func)(entity, get<Component>(entity)...);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sort the shared pool of entities according to the given component.
|
||||
*
|
||||
* Persistent views of the same type share with the Registry a pool of
|
||||
* entities with its own order that doesn't depend on the order of any pool
|
||||
* of components. Users can order the underlying data structure so that it
|
||||
* respects the order of the pool of the given component.
|
||||
*
|
||||
* @note
|
||||
* The shared pool of entities and thus its order is affected by the changes
|
||||
* to each and every pool of components that it tracks. Therefore changes to
|
||||
* the pools of components can quickly ruin the order imposed to the pool of
|
||||
* entities shared between the persistent views.
|
||||
*
|
||||
* @tparam Comp Type of the component to use to impose the order.
|
||||
*/
|
||||
template<typename Comp>
|
||||
void sort() {
|
||||
view.respect(std::get<pool_type<Comp> &>(pools));
|
||||
}
|
||||
|
||||
private:
|
||||
view_type &view;
|
||||
std::tuple<pool_type<Component> &...> pools;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Multi component view.
|
||||
*
|
||||
* Multi component views iterate over those entities that have at least all the
|
||||
* given components in their bags. During initialization, a multi component 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 strctures. See SparseSet and its specializations for more
|
||||
* details.
|
||||
*
|
||||
* @b Important
|
||||
*
|
||||
* Iterators aren't invalidated if:
|
||||
*
|
||||
* * New instances of the given components are created and assigned to entities.
|
||||
* * The entity currently pointed is modified (as an example, if one of the
|
||||
* given components is removed from the entity to which the iterator points).
|
||||
*
|
||||
* In all the other cases, modify the pools of the given components somehow
|
||||
* invalidates all the iterators and using them results in undefined behavior.
|
||||
*
|
||||
* @note
|
||||
* Views share references to the underlying data structures with 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 views.
|
||||
*
|
||||
* @warning
|
||||
* Lifetime of a view must overcome the one of the registry that generated it.
|
||||
* In any other case, attempting to use a view results in undefined behavior.
|
||||
*
|
||||
* @sa View<Entity, Component>
|
||||
* @sa PersistentView
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam First One of the components to iterate.
|
||||
* @tparam Other The rest of the components to iterate.
|
||||
*/
|
||||
template<typename Entity, typename First, typename... Other>
|
||||
class View final {
|
||||
template<typename Component>
|
||||
using pool_type = SparseSet<Entity, Component>;
|
||||
|
||||
using base_pool_type = SparseSet<Entity>;
|
||||
using underlying_iterator_type = typename base_pool_type::iterator_type;
|
||||
using repo_type = std::tuple<pool_type<First> &, pool_type<Other> &...>;
|
||||
|
||||
class Iterator {
|
||||
inline bool valid() const noexcept {
|
||||
using accumulator_type = bool[];
|
||||
auto entity = *begin;
|
||||
bool all = std::get<pool_type<First> &>(pools).has(entity);
|
||||
accumulator_type accumulator = { (all = all && std::get<pool_type<Other> &>(pools).has(entity))... };
|
||||
(void)accumulator;
|
||||
return all;
|
||||
}
|
||||
|
||||
public:
|
||||
using value_type = typename base_pool_type::entity_type;
|
||||
|
||||
Iterator(const repo_type &pools, underlying_iterator_type begin, underlying_iterator_type end) noexcept
|
||||
: pools{pools}, begin{begin}, end{end}
|
||||
{
|
||||
if(begin != end && !valid()) {
|
||||
++(*this);
|
||||
}
|
||||
}
|
||||
|
||||
Iterator & operator++() noexcept {
|
||||
++begin;
|
||||
while(begin != end && !valid()) { ++begin; }
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator operator++(int) noexcept {
|
||||
Iterator orig = *this;
|
||||
return ++(*this), orig;
|
||||
}
|
||||
|
||||
bool operator==(const Iterator &other) const noexcept {
|
||||
return other.begin == begin;
|
||||
}
|
||||
|
||||
bool operator!=(const Iterator &other) const noexcept {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
value_type operator*() const noexcept {
|
||||
return *begin;
|
||||
}
|
||||
|
||||
private:
|
||||
const repo_type &pools;
|
||||
underlying_iterator_type begin;
|
||||
underlying_iterator_type end;
|
||||
};
|
||||
|
||||
public:
|
||||
/*! Input iterator type. */
|
||||
using iterator_type = Iterator;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = typename base_pool_type::entity_type;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename base_pool_type::size_type;
|
||||
|
||||
/**
|
||||
* @brief Constructs a view out of a bunch of pools of components.
|
||||
* @param pool A reference to a pool of components.
|
||||
* @param other Other references to pools of components.
|
||||
*/
|
||||
explicit View(pool_type<First> &pool, pool_type<Other>&... other) noexcept
|
||||
: pools{pool, other...}, view{nullptr}
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the first entity that has the given
|
||||
* components.
|
||||
*
|
||||
* The returned iterator points to the first entity that has the given
|
||||
* components. If the view is empty, the returned iterator will be equal to
|
||||
* `end()`.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed to the underlying data
|
||||
* structures.
|
||||
*
|
||||
* @return An iterator to the first entity that has the given components.
|
||||
*/
|
||||
iterator_type begin() const noexcept {
|
||||
return Iterator{pools, view->begin(), view->end()};
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed to the underlying data
|
||||
* structures.
|
||||
*
|
||||
* @return An iterator to the entity following the last entity that has the
|
||||
* given components.
|
||||
*/
|
||||
iterator_type end() const noexcept {
|
||||
return Iterator{pools, view->end(), view->end()};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the component assigned to the given entity.
|
||||
*
|
||||
* Prefer this function instead of `Registry::get` during iterations.
|
||||
* It has far better performance than its companion function.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid component type results in a compilation
|
||||
* error. Attempting to use an entity that doesn't belong to the view
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if
|
||||
* the view doesn't contain the given entity.
|
||||
*
|
||||
* @tparam Component Type of the component to get.
|
||||
* @param entity A valid entity identifier.
|
||||
* @return The component assigned to the entity.
|
||||
*/
|
||||
template<typename Component>
|
||||
const Component & get(entity_type entity) const noexcept {
|
||||
return std::get<pool_type<Component> &>(pools).get(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the component assigned to the given entity.
|
||||
*
|
||||
* Prefer this function instead of `Registry::get` during iterations. It has
|
||||
* far better performance than its companion function.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid component type results in a compilation
|
||||
* error. Attempting to use an entity that doesn't belong to the view
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if
|
||||
* the view doesn't contain the given entity.
|
||||
*
|
||||
* @tparam Component Type of the component to get.
|
||||
* @param entity A valid entity identifier.
|
||||
* @return The component assigned to the entity.
|
||||
*/
|
||||
template<typename Component>
|
||||
Component & get(entity_type entity) noexcept {
|
||||
return const_cast<Component &>(const_cast<const View *>(this)->get<Component>(entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterate the entities and applies them the given function object.
|
||||
*
|
||||
* The function object is invoked for each entity. It is provided with the
|
||||
* entity itself and a set of references to all the components of the
|
||||
* view.<br/>
|
||||
* The signature of the function should be equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(entity_type, Component &...);
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func &&func) {
|
||||
for(auto entity: *this) {
|
||||
std::forward<Func>(func)(entity, get<First>(entity), get<Other>(entity)...);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterate the entities and applies them the given function object.
|
||||
*
|
||||
* The function object is invoked for each entity. It is provided with the
|
||||
* entity itself and a set of const references to all the components of the
|
||||
* view.<br/>
|
||||
* The signature of the function should be equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(entity_type, const Component &...);
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func &&func) const {
|
||||
for(auto entity: *this) {
|
||||
std::forward<Func>(func)(entity, get<First>(entity), get<Other>(entity)...);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resets the view and reinitializes it.
|
||||
*
|
||||
* A multi component view keeps a reference to the smallest set of candidate
|
||||
* entities to iterate. Resetting a view means querying the underlying data
|
||||
* structures and reinitializing the view.<br/>
|
||||
* Use it only if copies of views are stored around and there is a
|
||||
* possibility that a component has become the best candidate in the
|
||||
* meantime.
|
||||
*/
|
||||
void reset() {
|
||||
using accumulator_type = void *[];
|
||||
view = &std::get<pool_type<First> &>(pools);
|
||||
accumulator_type accumulator = { (std::get<pool_type<Other> &>(pools).size() < view->size() ? (view = &std::get<pool_type<Other> &>(pools)) : nullptr)... };
|
||||
(void)accumulator;
|
||||
}
|
||||
|
||||
private:
|
||||
repo_type pools;
|
||||
base_pool_type *view;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Single component view specialization.
|
||||
*
|
||||
* Single component views are specialized in order to get a boost in terms of
|
||||
* performance. This kind of views can access the underlying data structure
|
||||
* directly and avoid superflous checks.<br/>
|
||||
* Order of elements during iterations are highly dependent on the order of the
|
||||
* underlying data structure. See SparseSet and its specializations for more
|
||||
* details.
|
||||
*
|
||||
* @b Important
|
||||
*
|
||||
* Iterators aren't invalidated if:
|
||||
*
|
||||
* * New instances of the given components are created and assigned to entities.
|
||||
* * The entity currently pointed is modified (as an example, if one of the
|
||||
* given components is removed from the entity to which the iterator points).
|
||||
*
|
||||
* In all the other cases, modify the pools of the given components somehow
|
||||
* invalidates all the iterators and using them results in undefined behavior.
|
||||
*
|
||||
* @note
|
||||
* Views share a reference to the underlying data structure with 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 views.
|
||||
*
|
||||
* @warning
|
||||
* Lifetime of a view must overcome the one of the registry that generated it.
|
||||
* In any other case, attempting to use a view results in undefined behavior.
|
||||
*
|
||||
* @sa View
|
||||
* @sa PersistentView
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Component Type of the component iterated by the view.
|
||||
*/
|
||||
template<typename Entity, typename Component>
|
||||
class View<Entity, Component> final {
|
||||
using pool_type = SparseSet<Entity, Component>;
|
||||
|
||||
public:
|
||||
/*! Input iterator type. */
|
||||
using iterator_type = typename pool_type::iterator_type;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = typename pool_type::entity_type;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename pool_type::size_type;
|
||||
/*! Type of the component iterated by the view. */
|
||||
using raw_type = typename pool_type::type;
|
||||
|
||||
/**
|
||||
* @brief Constructs a view out of a pool of components.
|
||||
* @param pool A reference to a pool of components.
|
||||
*/
|
||||
explicit View(pool_type &pool) noexcept
|
||||
: pool{pool}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of entities that have the given component.
|
||||
* @return Number of entities that have the given component.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
return pool.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the list of components.
|
||||
*
|
||||
* The returned pointer is such that range `[raw(), raw() + size()]` is
|
||||
* always a valid range, even if the container is empty.
|
||||
*
|
||||
* @note
|
||||
* There are no guarantees on the order of the components. Use `begin` and
|
||||
* `end` if you want to iterate the view in the expected order.
|
||||
*
|
||||
* @return A pointer to the array of components.
|
||||
*/
|
||||
raw_type * raw() noexcept {
|
||||
return pool.raw();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the list of components.
|
||||
*
|
||||
* The returned pointer is such that range `[raw(), raw() + size()]` is
|
||||
* always a valid range, even if the container is empty.
|
||||
*
|
||||
* @note
|
||||
* There are no guarantees on the order of the components. Use `begin` and
|
||||
* `end` if you want to iterate the view in the expected order.
|
||||
*
|
||||
* @return A pointer to the array of components.
|
||||
*/
|
||||
const raw_type * raw() const noexcept {
|
||||
return pool.raw();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the list of entities.
|
||||
*
|
||||
* The returned pointer is such that range `[data(), data() + size()]` is
|
||||
* always a valid range, even if the container is empty.
|
||||
*
|
||||
* @note
|
||||
* There are no guarantees on the order of the entities. Use `begin` and
|
||||
* `end` if you want to iterate the view in the expected order.
|
||||
*
|
||||
* @return A pointer to the array of entities.
|
||||
*/
|
||||
const entity_type * data() const noexcept {
|
||||
return pool.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the first entity that has the given
|
||||
* component.
|
||||
*
|
||||
* The returned iterator points to the first entity that has the given
|
||||
* component. If the view is empty, the returned iterator will be equal to
|
||||
* `end()`.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed to the underlying data
|
||||
* structures.
|
||||
*
|
||||
* @return An iterator to the first entity that has the given component.
|
||||
*/
|
||||
iterator_type begin() const noexcept {
|
||||
return pool.begin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator that is past the last entity that has the
|
||||
* given component.
|
||||
*
|
||||
* The returned iterator points to the entity following the last entity that
|
||||
* has the given component. Attempting to dereference the returned iterator
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed to the underlying data
|
||||
* structures.
|
||||
*
|
||||
* @return An iterator to the entity following the last entity that has the
|
||||
* given component.
|
||||
*/
|
||||
iterator_type end() const noexcept {
|
||||
return pool.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the component assigned to the given entity.
|
||||
*
|
||||
* Prefer this function instead of `Registry::get` during iterations. It has
|
||||
* far better performance than its companion function.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn't belong to the view results in
|
||||
* undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* view doesn't contain the given entity.
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
* @return The component assigned to the entity.
|
||||
*/
|
||||
const Component & get(entity_type entity) const noexcept {
|
||||
return pool.get(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the component assigned to the given entity.
|
||||
*
|
||||
* Prefer this function instead of `Registry::get` during iterations. It has
|
||||
* far better performance than its companion function.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn't belong to the view results in
|
||||
* undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* view doesn't contain the given entity.
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
* @return The component assigned to the entity.
|
||||
*/
|
||||
Component & get(entity_type entity) noexcept {
|
||||
return const_cast<Component &>(const_cast<const View *>(this)->get(entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterate the entities and applies them the given function object.
|
||||
*
|
||||
* The function object is invoked for each entity. It is provided with the
|
||||
* entity itself and a reference to the component of the view.<br/>
|
||||
* The signature of the function should be equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(entity_type, Component &);
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func &&func) {
|
||||
for(auto entity: *this) {
|
||||
std::forward<Func>(func)(entity, get(entity));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterate the entities and applies them the given function object.
|
||||
*
|
||||
* The function object is invoked for each entity. It is provided with the
|
||||
* entity itself and a const reference to the component of the view.<br/>
|
||||
* The signature of the function should be equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(entity_type, const Component &);
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func &&func) const {
|
||||
for(auto entity: *this) {
|
||||
std::forward<Func>(func)(entity, get(entity));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
pool_type &pool;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_VIEW_HPP
|
||||
12
src/entt/entt.hpp
Normal file
12
src/entt/entt.hpp
Normal file
@@ -0,0 +1,12 @@
|
||||
#include "core/family.hpp"
|
||||
#include "core/ident.hpp"
|
||||
#include "entity/registry.hpp"
|
||||
#include "entity/sparse_set.hpp"
|
||||
#include "entity/traits.hpp"
|
||||
#include "entity/view.hpp"
|
||||
#include "locator/locator.hpp"
|
||||
#include "signal/bus.hpp"
|
||||
#include "signal/delegate.hpp"
|
||||
#include "signal/emitter.hpp"
|
||||
#include "signal/sigh.hpp"
|
||||
#include "signal/signal.hpp"
|
||||
115
src/entt/locator/locator.hpp
Normal file
115
src/entt/locator/locator.hpp
Normal file
@@ -0,0 +1,115 @@
|
||||
#ifndef ENTT_LOCATOR_LOCATOR_HPP
|
||||
#define ENTT_LOCATOR_LOCATOR_HPP
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <cassert>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Service locator, nothing more.
|
||||
*
|
||||
* A service locator can be used to do what it promises: locate services.<br/>
|
||||
* Usually service locators are tighly bound to the services they expose and
|
||||
* thus it's hard to define a general purpose class to do that. This template
|
||||
* based implementation tries to fill the gap and to get rid of the burden of
|
||||
* defining a different specific locator for each application.
|
||||
*
|
||||
* @tparam Service Type of service managed by the locator.
|
||||
*/
|
||||
template<typename Service>
|
||||
struct ServiceLocator final {
|
||||
/*! @brief Type of service offered. */
|
||||
using service_type = Service;
|
||||
|
||||
/*! @brief Default constructor, deleted on purpose. */
|
||||
ServiceLocator() = delete;
|
||||
/*! @brief Default destructor, deleted on purpose. */
|
||||
~ServiceLocator() = delete;
|
||||
|
||||
/**
|
||||
* @brief Tests if a valid service implementation is set.
|
||||
* @return True if the service is set, false otherwise.
|
||||
*/
|
||||
inline static bool empty() noexcept {
|
||||
return !static_cast<bool>(service);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a weak pointer to a service implementation, if any.
|
||||
*
|
||||
* Clients of a service shouldn't retain references to it. The recommended
|
||||
* way is to retrieve the service implementation currently set each and
|
||||
* every time the need of using it arises. Otherwise users can incur in
|
||||
* unexpected behaviors.
|
||||
*
|
||||
* @return A reference to the service implementation currently set, if any.
|
||||
*/
|
||||
inline static std::weak_ptr<Service> get() noexcept {
|
||||
return service;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a weak reference to a service implementation, if any.
|
||||
*
|
||||
* Clients of a service shouldn't retain references to it. The recommended
|
||||
* way is to retrieve the service implementation currently set each and
|
||||
* every time the need of using it arises. Otherwise users can incur in
|
||||
* unexpected behaviors.
|
||||
*
|
||||
* @warning
|
||||
* In case no service implementation has been set, a call to this function
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @return A reference to the service implementation currently set, if any.
|
||||
*/
|
||||
inline static Service & ref() noexcept {
|
||||
return *service;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets or replaces a service.
|
||||
* @tparam Impl Type of the new service to use.
|
||||
* @tparam Args Types of arguments to use to construct the service.
|
||||
* @param args Parameters to use to construct the service.
|
||||
*/
|
||||
template<typename Impl = Service, typename... Args>
|
||||
inline static void set(Args&&... args) {
|
||||
service = std::make_shared<Impl>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets or replaces a service.
|
||||
* @param ptr Service to use to replace the current one.
|
||||
*/
|
||||
inline static void set(std::shared_ptr<Service> ptr) {
|
||||
assert(static_cast<bool>(ptr));
|
||||
service = std::move(ptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resets a service.
|
||||
*
|
||||
* The service is no longer valid after a reset.
|
||||
*/
|
||||
inline static void reset() {
|
||||
service.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
static std::shared_ptr<Service> service;
|
||||
};
|
||||
|
||||
|
||||
template<typename Service>
|
||||
std::shared_ptr<Service> ServiceLocator<Service>::service{};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_LOCATOR_LOCATOR_HPP
|
||||
335
src/entt/signal/bus.hpp
Normal file
335
src/entt/signal/bus.hpp
Normal file
@@ -0,0 +1,335 @@
|
||||
#ifndef ENTT_SIGNAL_BUS_HPP
|
||||
#define ENTT_SIGNAL_BUS_HPP
|
||||
|
||||
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include "signal.hpp"
|
||||
#include "sigh.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Minimal event bus.
|
||||
*
|
||||
* Primary template isn't defined on purpose. The main reason for which it
|
||||
* exists is to work around the doxygen's parsing capabilities. In fact, there
|
||||
* is no need to declare it actually.
|
||||
*/
|
||||
template<template<typename...> class, typename...>
|
||||
class Bus;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Event bus specialization for multiple types.
|
||||
*
|
||||
* The event bus is designed to allow an easy registration of specific member
|
||||
* functions to a bunch of signal handlers (either manager or unmanaged).
|
||||
* Classes must publicly expose the required member functions to allow the bus
|
||||
* to detect them for the purpose of registering and unregistering
|
||||
* instances.<br/>
|
||||
* In particular, for each event type `E`, a matching member function has the
|
||||
* following signature: `void receive(const E &)`. Events will be properly
|
||||
* redirected to all the listeners by calling the right member functions, if
|
||||
* any.
|
||||
*
|
||||
* @tparam Sig Type of signal handler to use.
|
||||
* @tparam Event The list of events managed by the bus.
|
||||
*/
|
||||
template<template<typename...> class Sig, typename Event, typename... Other>
|
||||
class Bus<Sig, Event, Other...>
|
||||
: private Bus<Sig, Event>, private Bus<Sig, Other>...
|
||||
{
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
|
||||
/*! @brief Default constructor, explicit on purpose. */
|
||||
explicit Bus() noexcept = default;
|
||||
/*! @brief Default destructor. */
|
||||
~Bus() noexcept = default;
|
||||
|
||||
/*! @brief Default copy constructor. */
|
||||
Bus(const Bus &) = default;
|
||||
/*! @brief Default move constructor. */
|
||||
Bus(Bus &&) = default;
|
||||
|
||||
/*! @brief Default copy assignment operator. @return This bus. */
|
||||
Bus & operator=(const Bus &) = default;
|
||||
/*! @brief Default move assignment operator. @return This bus. */
|
||||
Bus & operator=(Bus &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Unregisters all the member functions of an instance.
|
||||
*
|
||||
* A bus is used to convey a certain set of events. This method detects
|
||||
* and unregisters from the bus all the matching member functions of an
|
||||
* instance.<br/>
|
||||
* For each event type `E`, a matching member function has the following
|
||||
* signature: `void receive(const E &)`.
|
||||
*
|
||||
* @tparam Instance Type of instance to unregister.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Instance>
|
||||
void unreg(Instance instance) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = {
|
||||
(Bus<Sig, Event>::unreg(instance), 0),
|
||||
(Bus<Sig, Other>::unreg(instance), 0)...
|
||||
};
|
||||
return void(accumulator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Registers all the member functions of an instance.
|
||||
*
|
||||
* A bus is used to convey a certain set of events. This method detects
|
||||
* and registers to the bus all the matching member functions of an
|
||||
* instance.<br/>
|
||||
* For each event type `E`, a matching member function has the following
|
||||
* signature: `void receive(const E &)`.
|
||||
*
|
||||
* @tparam Instance Type of instance to register.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Instance>
|
||||
void reg(Instance instance) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = {
|
||||
(Bus<Sig, Event>::reg(instance), 0),
|
||||
(Bus<Sig, Other>::reg(instance), 0)...
|
||||
};
|
||||
return void(accumulator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Number of listeners connected to the bus.
|
||||
* @return Number of listeners currently connected.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
using accumulator_type = std::size_t[];
|
||||
std::size_t sz = Bus<Sig, Event>::size();
|
||||
accumulator_type accumulator = { sz, (sz += Bus<Sig, Other>::size())... };
|
||||
return void(accumulator), sz;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns false is at least a listener is connected to the bus.
|
||||
* @return True if the bus has no listeners connected, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
using accumulator_type = bool[];
|
||||
bool ret = Bus<Sig, Event>::empty();
|
||||
accumulator_type accumulator = { ret, (ret = ret && Bus<Sig, Other>::empty())... };
|
||||
return void(accumulator), ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function to the bus.
|
||||
* @tparam Type Type of event to which to connect the function.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<typename Type, void(*Function)(const Type &)>
|
||||
void connect() {
|
||||
Bus<Sig, Type>::template connect<Function>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function from the bus.
|
||||
* @tparam Type Type of event from which to disconnect the function.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<typename Type, void(*Function)(const Type &)>
|
||||
void disconnect() {
|
||||
Bus<Sig, Type>::template disconnect<Function>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publishes an event.
|
||||
*
|
||||
* All the listeners are notified. Order isn't guaranteed.
|
||||
*
|
||||
* @tparam Type Type of event to publish.
|
||||
* @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>
|
||||
void publish(Args&&... args) {
|
||||
Bus<Sig, Type>::publish(std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Event bus specialization for a single type.
|
||||
*
|
||||
* The event bus is designed to allow an easy registration of a specific member
|
||||
* function to a signal handler (either manager or unmanaged).
|
||||
* Classes must publicly expose the required member function to allow the bus to
|
||||
* detect it for the purpose of registering and unregistering instances.<br/>
|
||||
* In particular, a matching member function has the following signature:
|
||||
* `void receive(const Event &)`. Events of the given type will be properly
|
||||
* redirected to all the listeners by calling the right member function, if any.
|
||||
*
|
||||
* @tparam Sig Type of signal handler to use.
|
||||
* @tparam Event Type of event managed by the bus.
|
||||
*/
|
||||
template<template<typename...> class Sig, typename Event>
|
||||
class Bus<Sig, Event> {
|
||||
using signal_type = Sig<void(const Event &)>;
|
||||
|
||||
template<typename Class>
|
||||
using instance_type = typename signal_type::template instance_type<Class>;
|
||||
|
||||
template<typename Class>
|
||||
auto disconnect(int, instance_type<Class> instance)
|
||||
-> decltype(std::declval<Class>().receive(std::declval<Event>()), void()) {
|
||||
signal.template disconnect<Class, &Class::receive>(std::move(instance));
|
||||
}
|
||||
|
||||
template<typename Class>
|
||||
auto connect(int, instance_type<Class> instance)
|
||||
-> decltype(std::declval<Class>().receive(std::declval<Event>()), void()) {
|
||||
signal.template connect<Class, &Class::receive>(std::move(instance));
|
||||
}
|
||||
|
||||
template<typename Class> void disconnect(char, instance_type<Class>) {}
|
||||
template<typename Class> void connect(char, instance_type<Class>) {}
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename signal_type::size_type;
|
||||
|
||||
/*! @brief Default constructor, explicit on purpose. */
|
||||
explicit Bus() noexcept = default;
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~Bus() noexcept = default;
|
||||
|
||||
/*! @brief Default copy constructor. */
|
||||
Bus(const Bus &) = default;
|
||||
/*! @brief Default move constructor. */
|
||||
Bus(Bus &&) = default;
|
||||
|
||||
/*! @brief Default copy assignment operator. @return This bus. */
|
||||
Bus & operator=(const Bus &) = default;
|
||||
/*! @brief Default move assignment operator. @return This bus. */
|
||||
Bus & operator=(Bus &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Unregisters member functions of instances.
|
||||
*
|
||||
* This method tries to detect and unregister from the bus matching member
|
||||
* functions of instances.<br/>
|
||||
* A matching member function has the following signature:
|
||||
* `void receive(const Event &)`.
|
||||
*
|
||||
* @tparam Class Type of instance to unregister.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Class>
|
||||
void unreg(instance_type<Class> instance) {
|
||||
disconnect(0, std::move(instance));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Tries to register an instance.
|
||||
*
|
||||
* This method tries to detect and register to the bus matching member
|
||||
* functions of instances.<br/>
|
||||
* A matching member function has the following signature:
|
||||
* `void receive(const Event &)`.
|
||||
*
|
||||
* @tparam Class Type of instance to register.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Class>
|
||||
void reg(instance_type<Class> instance) {
|
||||
connect(0, std::move(instance));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Number of listeners connected to the bus.
|
||||
* @return Number of listeners currently connected.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
return signal.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns false is at least a listener is connected to the bus.
|
||||
* @return True if the bus has no listeners connected, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
return signal.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function to the bus.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<void(*Function)(const Event &)>
|
||||
void connect() {
|
||||
signal.template connect<Function>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function from the bus.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<void(*Function)(const Event &)>
|
||||
void disconnect() {
|
||||
signal.template disconnect<Function>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publishes an event.
|
||||
*
|
||||
* All the listeners are notified. Order isn't guaranteed.
|
||||
*
|
||||
* @tparam Args Types of arguments to use to construct the event.
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename... Args>
|
||||
void publish(Args&&... args) {
|
||||
signal.publish({ std::forward<Args>(args)... });
|
||||
}
|
||||
|
||||
private:
|
||||
signal_type signal;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Managed event bus.
|
||||
*
|
||||
* A managed event bus uses the Signal class template as an underlying type. The
|
||||
* type of the instances is the one required by the signal handler:
|
||||
* `std::shared_ptr<Class>` (a shared pointer).
|
||||
*
|
||||
* @tparam Event The list of events managed by the bus.
|
||||
*/
|
||||
template<typename... Event>
|
||||
using ManagedBus = Bus<Signal, Event...>;
|
||||
|
||||
/**
|
||||
* @brief Unmanaged event bus.
|
||||
*
|
||||
* An unmanaged event bus uses the SigH class template as an underlying type.
|
||||
* The type of the instances is the one required by the signal handler:
|
||||
* `Class *` (a naked pointer).<br/>
|
||||
* When it comes to work with this kind of bus, users must guarantee that the
|
||||
* lifetimes of the instances overcome the one of the bus itself.
|
||||
*
|
||||
* @tparam Event The list of events managed by the bus.
|
||||
*/
|
||||
template<typename... Event>
|
||||
using UnmanagedBus = Bus<SigH, Event...>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_SIGNAL_BUS_HPP
|
||||
137
src/entt/signal/delegate.hpp
Normal file
137
src/entt/signal/delegate.hpp
Normal file
@@ -0,0 +1,137 @@
|
||||
#ifndef ENTT_SIGNAL_DELEGATE_HPP
|
||||
#define ENTT_SIGNAL_DELEGATE_HPP
|
||||
|
||||
|
||||
#include <utility>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Basic delegate implementation.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error unless the template parameter is a function type.
|
||||
*/
|
||||
template<typename>
|
||||
class Delegate;
|
||||
|
||||
|
||||
/**
|
||||
* @brief A delegate class to send around functions and member functions.
|
||||
*
|
||||
* Unmanaged delegate for function pointers and member functions. Users of this
|
||||
* class are in charge of disconnecting instances before deleting them.
|
||||
*
|
||||
* A delegate can be used as general purpose invoker with no memory overhead for
|
||||
* free functions and member functions provided along with an instance on which
|
||||
* to invoke them.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
*/
|
||||
template<typename Ret, typename... Args>
|
||||
class Delegate<Ret(Args...)> final {
|
||||
using proto_type = Ret(*)(void *, Args...);
|
||||
using stub_type = std::pair<void *, proto_type>;
|
||||
|
||||
static Ret fallback(void *, Args...) noexcept { return {}; }
|
||||
|
||||
template<Ret(*Function)(Args...)>
|
||||
static Ret proto(void *, Args... args) {
|
||||
return (Function)(args...);
|
||||
}
|
||||
|
||||
template<typename Class, Ret(Class::*Member)(Args...)>
|
||||
static Ret proto(void *instance, Args... args) {
|
||||
return (static_cast<Class *>(instance)->*Member)(args...);
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Default constructor, explicit on purpose. */
|
||||
explicit Delegate() noexcept
|
||||
: stub{std::make_pair(nullptr, &fallback)}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Binds a free function to a delegate.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<Ret(*Function)(Args...)>
|
||||
void connect() noexcept {
|
||||
stub = std::make_pair(nullptr, &proto<Function>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a member function for a given instance to a delegate.
|
||||
*
|
||||
* The delegate isn't responsible for the connected object. Users must
|
||||
* guarantee that the lifetime of the instance overcomes the one of the
|
||||
* delegate.
|
||||
*
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the delegate.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class, Ret(Class::*Member)(Args...)>
|
||||
void connect(Class *instance) noexcept {
|
||||
stub = std::make_pair(instance, &proto<Class, Member>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resets a delegate.
|
||||
*
|
||||
* After a reset, a delegate can be safely invoked with no effect.
|
||||
*/
|
||||
void reset() noexcept {
|
||||
stub = std::make_pair(nullptr, &fallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Triggers a delegate.
|
||||
* @param args Arguments to use to invoke the underlying function.
|
||||
* @return The value returned by the underlying function.
|
||||
*/
|
||||
Ret operator()(Args... args) {
|
||||
return stub.second(stub.first, args...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two delegates are different.
|
||||
*
|
||||
* Two delegates are identical if they contain the same listener.
|
||||
*
|
||||
* @param other Delegate with which to compare.
|
||||
* @return True if the two delegates are identical, false otherwise.
|
||||
*/
|
||||
bool operator==(const Delegate<Ret(Args...)> &other) const noexcept {
|
||||
return stub.first == other.stub.first && stub.second == other.stub.second;
|
||||
}
|
||||
|
||||
private:
|
||||
stub_type stub;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two delegates are different.
|
||||
*
|
||||
* Two delegates are identical if they contain the same listener.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
* @param lhs A valid delegate object.
|
||||
* @param rhs A valid delegate object.
|
||||
* @return True if the two delegates are different, false otherwise.
|
||||
*/
|
||||
template<typename Ret, typename... Args>
|
||||
bool operator!=(const Delegate<Ret(Args...)> &lhs, const Delegate<Ret(Args...)> &rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_SIGNAL_DELEGATE_HPP
|
||||
235
src/entt/signal/dispatcher.hpp
Normal file
235
src/entt/signal/dispatcher.hpp
Normal file
@@ -0,0 +1,235 @@
|
||||
#ifndef ENTT_SIGNAL_DISPATCHER_HPP
|
||||
#define ENTT_SIGNAL_DISPATCHER_HPP
|
||||
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <cstdint>
|
||||
#include "../core/family.hpp"
|
||||
#include "signal.hpp"
|
||||
#include "sigh.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Basic dispatcher implementation.
|
||||
*
|
||||
* 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 `Event`, listeners must have the following signature:
|
||||
* `void(const Event &)`. Member functions named `receive` are automatically
|
||||
* detected and registered or unregistered by the dispatcher.
|
||||
*
|
||||
* @tparam Sig Type of the signal handler to use.
|
||||
*/
|
||||
template<template<typename...> class Sig>
|
||||
class Dispatcher final {
|
||||
using event_family = Family<struct InternalDispatcherEventFamily>;
|
||||
|
||||
template<typename Class, typename Event>
|
||||
using instance_type = typename Sig<void(const Event &)>::template instance_type<Class>;
|
||||
|
||||
struct BaseSignalWrapper {
|
||||
virtual ~BaseSignalWrapper() = default;
|
||||
virtual void publish(std::size_t) = 0;
|
||||
};
|
||||
|
||||
template<typename Event>
|
||||
struct SignalWrapper final: BaseSignalWrapper {
|
||||
void publish(std::size_t current) final override {
|
||||
for(auto &&event: events[current]) {
|
||||
signal.publish(event);
|
||||
}
|
||||
|
||||
events[current].clear();
|
||||
}
|
||||
|
||||
template<typename Class, void(Class::*Member)(const Event &)>
|
||||
inline void connect(instance_type<Class, Event> instance) noexcept {
|
||||
signal.template connect<Class, Member>(std::move(instance));
|
||||
}
|
||||
|
||||
template<typename Class, void(Class::*Member)(const Event &)>
|
||||
inline void disconnect(instance_type<Class, Event> instance) noexcept {
|
||||
signal.template disconnect<Class, Member>(std::move(instance));
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void trigger(Args&&... args) {
|
||||
signal.publish({ std::forward<Args>(args)... });
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void enqueue(std::size_t current, Args&&... args) {
|
||||
events[current].push_back({ std::forward<Args>(args)... });
|
||||
}
|
||||
|
||||
private:
|
||||
Sig<void(const Event &)> signal{};
|
||||
std::vector<Event> events[2];
|
||||
};
|
||||
|
||||
inline static std::size_t buffer(bool mode) {
|
||||
return mode ? 0 : 1;
|
||||
}
|
||||
|
||||
template<typename Event>
|
||||
SignalWrapper<Event> & wrapper() {
|
||||
auto type = event_family::type<Event>();
|
||||
|
||||
if(!(type < wrappers.size())) {
|
||||
wrappers.resize(type + 1);
|
||||
}
|
||||
|
||||
if(!wrappers[type]) {
|
||||
wrappers[type] = std::make_unique<SignalWrapper<Event>>();
|
||||
}
|
||||
|
||||
return static_cast<SignalWrapper<Event> &>(*wrappers[type]);
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Default constructor, explicit on purpose. */
|
||||
explicit Dispatcher() noexcept
|
||||
: wrappers{}, mode{false}
|
||||
{}
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
~Dispatcher() = default;
|
||||
|
||||
/*! @brief Default copy constructor. */
|
||||
Dispatcher(const Dispatcher &) = default;
|
||||
/*! @brief Default move constructor. */
|
||||
Dispatcher(Dispatcher &&) = default;
|
||||
|
||||
/*! @brief Default copy assignment operator. @return This dispatcher. */
|
||||
Dispatcher & operator=(const Dispatcher &) = default;
|
||||
/*! @brief Default move assignment operator. @return This dispatcher. */
|
||||
Dispatcher & operator=(Dispatcher &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Registers a listener given in the form of a member function.
|
||||
*
|
||||
* A matching member function has the following signature:
|
||||
* `void receive(const Event &)`. Member functions named `receive` are
|
||||
* automatically detected and registered if available.
|
||||
*
|
||||
* @warning
|
||||
* Connecting a listener during an update may lead to unexpected behavior.
|
||||
* Register listeners before or after invoking the update if possible.
|
||||
*
|
||||
* @tparam Event Type of event to which to connect the function.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Event, typename Class, void(Class::*Member)(const Event &) = &Class::receive>
|
||||
void connect(instance_type<Class, Event> instance) noexcept {
|
||||
wrapper<Event>().template connect<Class, Member>(std::move(instance));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unregisters a listener given in the form of a member function.
|
||||
*
|
||||
* A matching member function has the following signature:
|
||||
* `void receive(const Event &)`. Member functions named `receive` are
|
||||
* automatically detected and unregistered if available.
|
||||
*
|
||||
* @warning
|
||||
* Disonnecting a listener during an update may lead to unexpected behavior.
|
||||
* Unregister listeners before or after invoking the update if possible.
|
||||
*
|
||||
* @tparam Event Type of event from which to disconnect the function.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Event, typename Class, void(Class::*Member)(const Event &) = &Class::receive>
|
||||
void disconnect(instance_type<Class, Event> instance) noexcept {
|
||||
wrapper<Event>().template disconnect<Class, Member>(std::move(instance));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Triggers an immediate event of the given type.
|
||||
*
|
||||
* All the listeners registered for the given type are immediately notified.
|
||||
* The event is discarded after the execution.
|
||||
*
|
||||
* @tparam Event Type of event to trigger.
|
||||
* @tparam Args Types of arguments to use to construct the event.
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename Event, typename... Args>
|
||||
void trigger(Args&&... args) {
|
||||
wrapper<Event>().trigger(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enqueues an event of the given type.
|
||||
*
|
||||
* An event of the given type is queued. No listener is invoked. Use the
|
||||
* `update` member function to notify listeners when ready.
|
||||
*
|
||||
* @tparam Event Type of event to trigger.
|
||||
* @tparam Args Types of arguments to use to construct the event.
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename Event, typename... Args>
|
||||
void enqueue(Args&&... args) {
|
||||
wrapper<Event>().enqueue(buffer(mode), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Delivers all the pending events.
|
||||
*
|
||||
* This method is blocking and it doesn't return until all the events are
|
||||
* delivered to the registered listeners. It's responsability of the users
|
||||
* to reduce at a minimum the time spent in the bodies of the listeners.
|
||||
*/
|
||||
void update() {
|
||||
auto buf = buffer(mode);
|
||||
mode = !mode;
|
||||
|
||||
for(auto &&wrapper: wrappers) {
|
||||
if(wrapper) {
|
||||
wrapper->publish(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<BaseSignalWrapper>> wrappers;
|
||||
bool mode;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Managed dispatcher.
|
||||
*
|
||||
* A managed dispatcher uses the Signal class template as an underlying type.
|
||||
* The type of the instances is the one required by the signal handler:
|
||||
* `std::shared_ptr<Class>` (a shared pointer).
|
||||
*/
|
||||
using ManagedDispatcher = Dispatcher<Signal>;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Unmanaged dispatcher.
|
||||
*
|
||||
* An unmanaged dispatcher uses the SigH class template as an underlying type.
|
||||
* The type of the instances is the one required by the signal handler:
|
||||
* `Class *` (a naked pointer).<br/>
|
||||
* When it comes to work with this kind of dispatcher, users must guarantee that
|
||||
* the lifetimes of the instances overcome the one of the dispatcher itself.
|
||||
*/
|
||||
using UnmanagedDispatcher = Dispatcher<SigH>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_SIGNAL_DISPATCHER_HPP
|
||||
344
src/entt/signal/emitter.hpp
Normal file
344
src/entt/signal/emitter.hpp
Normal file
@@ -0,0 +1,344 @@
|
||||
#ifndef ENTT_SIGNAL_EMITTER_HPP
|
||||
#define ENTT_SIGNAL_EMITTER_HPP
|
||||
|
||||
|
||||
#include <type_traits>
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief General purpose event emitter.
|
||||
*
|
||||
* The emitter class template follows the CRTP idiom. To create a custom emitter
|
||||
* type, derived classes must inherit directly from the base class as:
|
||||
*
|
||||
* ```cpp
|
||||
* struct MyEmitter: Emitter<MyEmitter> {
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Handlers for the type of events are created internally on the fly. It's not
|
||||
* required to specify in advance the full list of accepted types.<br/>
|
||||
* Moreover, whenever an event is published, an emitter provides the listeners
|
||||
* with a reference to itself along with a const 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.
|
||||
*
|
||||
* @tparam Derived Actual type of emitter that extends the class template.
|
||||
*/
|
||||
template<typename Derived>
|
||||
class Emitter {
|
||||
struct BaseHandler {
|
||||
virtual ~BaseHandler() = default;
|
||||
virtual bool empty() const noexcept = 0;
|
||||
virtual void clear() noexcept = 0;
|
||||
};
|
||||
|
||||
template<typename Event>
|
||||
struct Handler final: BaseHandler {
|
||||
using listener_type = std::function<void(const Event &, Derived &)>;
|
||||
using element_type = std::pair<bool, listener_type>;
|
||||
using container_type = std::list<element_type>;
|
||||
using connection_type = typename container_type::iterator;
|
||||
|
||||
bool empty() const noexcept override {
|
||||
auto pred = [](auto &&element){ return element.first; };
|
||||
|
||||
return std::all_of(onceL.cbegin(), onceL.cend(), pred) &&
|
||||
std::all_of(onL.cbegin(), onL.cend(), pred);
|
||||
}
|
||||
|
||||
void clear() noexcept override {
|
||||
if(publishing) {
|
||||
auto func = [](auto &&element){ element.first = true; };
|
||||
std::for_each(onceL.begin(), onceL.end(), func);
|
||||
std::for_each(onL.begin(), onL.end(), func);
|
||||
} else {
|
||||
onceL.clear();
|
||||
onL.clear();
|
||||
}
|
||||
}
|
||||
|
||||
inline connection_type once(listener_type listener) {
|
||||
return onceL.emplace(onceL.cend(), false, std::move(listener));
|
||||
}
|
||||
|
||||
inline connection_type on(listener_type listener) {
|
||||
return onL.emplace(onL.cend(), false, std::move(listener));
|
||||
}
|
||||
|
||||
void erase(connection_type conn) noexcept {
|
||||
conn->first = true;
|
||||
|
||||
if(!publishing) {
|
||||
auto pred = [](auto &&element){ return element.first; };
|
||||
onceL.remove_if(pred);
|
||||
onL.remove_if(pred);
|
||||
}
|
||||
}
|
||||
|
||||
void publish(const Event &event, Derived &ref) {
|
||||
container_type currentL;
|
||||
onceL.swap(currentL);
|
||||
|
||||
auto func = [&event, &ref](auto &&element) {
|
||||
return element.first ? void() : element.second(event, ref);
|
||||
};
|
||||
|
||||
publishing = true;
|
||||
|
||||
std::for_each(onL.rbegin(), onL.rend(), func);
|
||||
std::for_each(currentL.rbegin(), currentL.rend(), func);
|
||||
|
||||
publishing = false;
|
||||
|
||||
onL.remove_if([](auto &&element){ return element.first; });
|
||||
}
|
||||
|
||||
private:
|
||||
bool publishing{false};
|
||||
container_type onceL{};
|
||||
container_type onL{};
|
||||
};
|
||||
|
||||
static std::size_t next() noexcept {
|
||||
static std::size_t counter = 0;
|
||||
return counter++;
|
||||
}
|
||||
|
||||
template<typename>
|
||||
static std::size_t type() noexcept {
|
||||
static std::size_t value = next();
|
||||
return value;
|
||||
}
|
||||
|
||||
template<typename Event>
|
||||
Handler<Event> & handler() noexcept {
|
||||
std::size_t family = type<Event>();
|
||||
|
||||
if(!(family < handlers.size())) {
|
||||
handlers.resize(family+1);
|
||||
}
|
||||
|
||||
if(!handlers[family]) {
|
||||
handlers[family] = std::make_unique<Handler<Event>>();
|
||||
}
|
||||
|
||||
return static_cast<Handler<Event> &>(*handlers[family]);
|
||||
}
|
||||
|
||||
public:
|
||||
/** @brief Type of listeners accepted for the given type of event. */
|
||||
template<typename Event>
|
||||
using Listener = typename Handler<Event>::listener_type;
|
||||
|
||||
/**
|
||||
* @brief Generic connection type for events.
|
||||
*
|
||||
* Type of the connection object returned by the event emitter whenever a
|
||||
* listener for the given type is registered.<br/>
|
||||
* It can be used to break connections still in use.
|
||||
*
|
||||
* @tparam Event Type of event for which the connection is created.
|
||||
*/
|
||||
template<typename Event>
|
||||
struct Connection final: private Handler<Event>::connection_type {
|
||||
/** @brief Event emitters are friend classes of connections. */
|
||||
friend class Emitter;
|
||||
|
||||
/*! @brief Default constructor, explicit on purpose. */
|
||||
explicit Connection() = default;
|
||||
|
||||
/*! @brief Default copy constructor. */
|
||||
Connection(const Connection &) = default;
|
||||
/*! @brief Default move constructor. */
|
||||
Connection(Connection &&) = default;
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
~Connection() = default;
|
||||
|
||||
/**
|
||||
* @brief Creates a connection that wraps its underlying instance.
|
||||
* @param conn A connection object to wrap.
|
||||
*/
|
||||
Connection(typename Handler<Event>::connection_type conn)
|
||||
: Handler<Event>::connection_type{std::move(conn)}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Default copy assignament operator.
|
||||
* @return This connection.
|
||||
*/
|
||||
Connection & operator=(const Connection &) = default;
|
||||
|
||||
/**
|
||||
* @brief Default move assignment operator.
|
||||
* @return This connection.
|
||||
*/
|
||||
Connection & operator=(Connection &&) = default;
|
||||
};
|
||||
|
||||
/*! @brief Default constructor, explicit on purpose. */
|
||||
explicit Emitter() noexcept = default;
|
||||
|
||||
/*! @brief Copying an emitter isn't allowed. */
|
||||
Emitter(const Emitter &) = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
Emitter(Emitter &&) = default;
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~Emitter() noexcept {
|
||||
static_assert(std::is_base_of<Emitter<Derived>, Derived>::value, "!");
|
||||
}
|
||||
|
||||
/*! @brief Copying an emitter isn't allowed. @return This emitter. */
|
||||
Emitter & operator=(const Emitter &) = delete;
|
||||
/*! @brief Default move assignament operator. @return This emitter. */
|
||||
Emitter & operator=(Emitter &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Emits the given event.
|
||||
*
|
||||
* All the listeners registered for the specific event type are invoked with
|
||||
* the given event. The event type must either have a proper constructor for
|
||||
* the arguments provided or be an aggregate type.
|
||||
*
|
||||
* @tparam Event Type of event to publish.
|
||||
* @tparam Args Types of arguments to use to construct the event.
|
||||
* @param args Parameters to use to initialize the event.
|
||||
*/
|
||||
template<typename Event, typename... Args>
|
||||
void publish(Args&&... args) {
|
||||
handler<Event>().publish({ std::forward<Args>(args)... }, *static_cast<Derived *>(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Registers a long-lived listener with the event emitter.
|
||||
*
|
||||
* This method can be used to register a listener designed to be invoked
|
||||
* more than once for the given event type.<br/>
|
||||
* The connection returned by the method can be freely discarded. It's meant
|
||||
* to be used later to disconnect the listener if required.
|
||||
*
|
||||
* The listener is as a callable object that can be moved and the type of
|
||||
* which is `void(const Event &, Derived &)`.
|
||||
*
|
||||
* @note
|
||||
* Whenever an event is emitted, the emitter provides the listener with a
|
||||
* reference to the derived class. Listeners don't have to capture those
|
||||
* instances for later uses.
|
||||
*
|
||||
* @tparam Event Type of event to which to connect the listener.
|
||||
* @param listener The listener to register.
|
||||
* @return Connection object that can be used to disconnect the listener.
|
||||
*/
|
||||
template<typename Event>
|
||||
Connection<Event> on(Listener<Event> listener) {
|
||||
return handler<Event>().on(std::move(listener));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Registers a short-lived listener with the event emitter.
|
||||
*
|
||||
* This method can be used to register a listener designed to be invoked
|
||||
* only once for the given event type.<br/>
|
||||
* The connection returned by the method can be freely discarded. It's meant
|
||||
* to be used later to disconnect the listener if required.
|
||||
*
|
||||
* The listener is as a callable object that can be moved and the type of
|
||||
* which is `void(const Event &, Derived &)`.
|
||||
*
|
||||
* @note
|
||||
* Whenever an event is emitted, the emitter provides the listener with a
|
||||
* reference to the derived class. Listeners don't have to capture those
|
||||
* instances for later uses.
|
||||
*
|
||||
* @tparam Event Type of event to which to connect the listener.
|
||||
* @param listener The listener to register.
|
||||
* @return Connection object that can be used to disconnect the listener.
|
||||
*/
|
||||
template<typename Event>
|
||||
Connection<Event> once(Listener<Event> listener) {
|
||||
return handler<Event>().once(std::move(listener));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a listener from the event emitter.
|
||||
*
|
||||
* Do not use twice the same connection to disconnect a listener, it results
|
||||
* in undefined behavior. Once used, discard the connection object.
|
||||
*
|
||||
* @tparam Event Type of event of the connection.
|
||||
* @param conn A valid connection.
|
||||
*/
|
||||
template<typename Event>
|
||||
void erase(Connection<Event> conn) noexcept {
|
||||
handler<Event>().erase(std::move(conn));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects all the listeners for the given event type.
|
||||
*
|
||||
* All the connections previously returned for the given event are
|
||||
* invalidated. Using them results in undefined behaviour.
|
||||
*
|
||||
* @tparam Event Type of event to reset.
|
||||
*/
|
||||
template<typename Event>
|
||||
void clear() noexcept {
|
||||
handler<Event>().clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects all the listeners.
|
||||
*
|
||||
* All the connections previously returned are invalidated. Using them
|
||||
* results in undefined behaviour.
|
||||
*/
|
||||
void clear() noexcept {
|
||||
std::for_each(handlers.begin(), handlers.end(),
|
||||
[](auto &&handler){ if(handler) { handler->clear(); } });
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if there are listeners registered for the specific event.
|
||||
* @tparam Event Type of event to test.
|
||||
* @return True if there are no listeners registered, false otherwise.
|
||||
*/
|
||||
template<typename Event>
|
||||
bool empty() const noexcept {
|
||||
std::size_t family = type<Event>();
|
||||
|
||||
return (!(family < handlers.size()) ||
|
||||
!handlers[family] ||
|
||||
static_cast<Handler<Event> &>(*handlers[family]).empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if there are listeners registered with the event emitter.
|
||||
* @return True if there are no listeners registered, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
return std::all_of(handlers.cbegin(), handlers.cend(),
|
||||
[](auto &&handler){ return !handler || handler->empty(); });
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<BaseHandler>> handlers{};
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_SIGNAL_EMITTER_HPP
|
||||
346
src/entt/signal/sigh.hpp
Normal file
346
src/entt/signal/sigh.hpp
Normal file
@@ -0,0 +1,346 @@
|
||||
#ifndef ENTT_SIGNAL_SIGH_HPP
|
||||
#define ENTT_SIGNAL_SIGH_HPP
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
template<typename, typename>
|
||||
struct Invoker;
|
||||
|
||||
|
||||
template<typename Ret, typename... Args, typename Collector>
|
||||
struct Invoker<Ret(Args...), Collector> {
|
||||
using proto_type = Ret(*)(void *, Args...);
|
||||
using call_type = std::pair<void *, proto_type>;
|
||||
|
||||
virtual ~Invoker() = default;
|
||||
|
||||
template<typename SFINAE = Ret>
|
||||
typename std::enable_if<std::is_void<SFINAE>::value, bool>::type
|
||||
invoke(Collector &, proto_type proto, void *instance, Args... args) {
|
||||
proto(instance, args...);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename SFINAE = Ret>
|
||||
typename std::enable_if<!std::is_void<SFINAE>::value, bool>::type
|
||||
invoke(Collector &collector, proto_type proto, void *instance, Args... args) {
|
||||
return collector(proto(instance, args...));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename Ret>
|
||||
struct NullCollector final {
|
||||
using result_type = Ret;
|
||||
bool operator()(result_type) const noexcept { return true; }
|
||||
};
|
||||
|
||||
|
||||
template<>
|
||||
struct NullCollector<void> final {
|
||||
using result_type = void;
|
||||
bool operator()() const noexcept { return true; }
|
||||
};
|
||||
|
||||
|
||||
template<typename>
|
||||
struct DefaultCollector;
|
||||
|
||||
|
||||
template<typename Ret, typename... Args>
|
||||
struct DefaultCollector<Ret(Args...)> final {
|
||||
using collector_type = NullCollector<Ret>;
|
||||
};
|
||||
|
||||
|
||||
template<typename Function>
|
||||
using DefaultCollectorType = typename DefaultCollector<Function>::collector_type;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Unmanaged signal handler declaration.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error unless the template parameter is a function type.
|
||||
*
|
||||
* @tparam Function A valid function type.
|
||||
* @tparam Collector Type of collector to use, if any.
|
||||
*/
|
||||
template<typename Function, typename Collector = DefaultCollectorType<Function>>
|
||||
class SigH;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Unmanaged signal handler definition.
|
||||
*
|
||||
* Unmanaged signal handler. It works directly with naked pointers to classes
|
||||
* and pointers to member functions as well as pointers to free functions. Users
|
||||
* of this class are in charge of disconnecting instances before deleting them.
|
||||
*
|
||||
* This class serves mainly two purposes:
|
||||
* * Creating signals used later to notify a bunch of listeners.
|
||||
* * Collecting results from a set of functions like in a voting system.
|
||||
*
|
||||
* The default collector does nothing. To properly collect data, define and use
|
||||
* a class that has a call operator the signature of which is `bool(Param)` and:
|
||||
* * `Param` is a type to which `Ret` can be converted.
|
||||
* * The return type is true if the handler must stop collecting data, false
|
||||
* otherwise.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
* @tparam Collector Type of collector to use, if any.
|
||||
*/
|
||||
template<typename Ret, typename... Args, typename Collector>
|
||||
class SigH<Ret(Args...), Collector> final: private Invoker<Ret(Args...), Collector> {
|
||||
using typename Invoker<Ret(Args...), Collector>::call_type;
|
||||
|
||||
template<Ret(*Function)(Args...)>
|
||||
static Ret proto(void *, Args... args) {
|
||||
return (Function)(args...);
|
||||
}
|
||||
|
||||
template<typename Class, Ret(Class::*Member)(Args... args)>
|
||||
static Ret proto(void *instance, Args... args) {
|
||||
return (static_cast<Class *>(instance)->*Member)(args...);
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename std::vector<call_type>::size_type;
|
||||
/*! @brief Collector type. */
|
||||
using collector_type = Collector;
|
||||
|
||||
/**
|
||||
* @brief Instance type when it comes to connecting member functions.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
*/
|
||||
template<typename Class>
|
||||
using instance_type = Class *;
|
||||
|
||||
/*! @brief Default constructor, explicit on purpose. */
|
||||
explicit SigH() noexcept = default;
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
~SigH() noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief Copy constructor, listeners are also connected to this signal.
|
||||
* @param other A signal to use as source to initialize this instance.
|
||||
*/
|
||||
SigH(const SigH &other)
|
||||
: calls{other.calls}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Default move constructor.
|
||||
* @param other A signal to use as source to initialize this instance.
|
||||
*/
|
||||
SigH(SigH &&other): SigH{} {
|
||||
swap(*this, other);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy assignment operator.
|
||||
*
|
||||
* Listeners are also connected to this signal.
|
||||
*
|
||||
* @param other A signal to use as source to initialize this instance.
|
||||
* @return This signal.
|
||||
*/
|
||||
SigH & operator=(const SigH &other) {
|
||||
calls = other.calls;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator.
|
||||
* @param other A signal to use as source to initialize this instance.
|
||||
* @return This signal.
|
||||
*/
|
||||
SigH & operator=(SigH &&other) {
|
||||
swap(*this, other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Number of listeners connected to the signal.
|
||||
* @return Number of listeners currently connected.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
return calls.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns false is at least a listener is connected to the signal.
|
||||
* @return True if the signal has no listeners connected, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
return calls.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects all the listeners from a signal.
|
||||
*/
|
||||
void clear() noexcept {
|
||||
calls.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function to a signal.
|
||||
*
|
||||
* The signal handler performs checks to avoid multiple connections for free
|
||||
* functions.
|
||||
*
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<Ret(*Function)(Args...)>
|
||||
void connect() {
|
||||
disconnect<Function>();
|
||||
calls.emplace_back(nullptr, &proto<Function>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a member function for a given instance to a signal.
|
||||
*
|
||||
* The signal isn't responsible for the connected object. Users must
|
||||
* guarantee that the lifetime of the instance overcomes the one of the
|
||||
* signal. On the other side, the signal handler performs checks to avoid
|
||||
* multiple connections for the same member function of a given instance.
|
||||
*
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template <typename Class, Ret(Class::*Member)(Args...)>
|
||||
void connect(instance_type<Class> instance) {
|
||||
disconnect<Class, Member>(instance);
|
||||
calls.emplace_back(instance, &proto<Class, Member>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function from a signal.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<Ret(*Function)(Args...)>
|
||||
void disconnect() {
|
||||
call_type target{nullptr, &proto<Function>};
|
||||
calls.erase(std::remove(calls.begin(), calls.end(), std::move(target)), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects the given member function from a signal.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class, Ret(Class::*Member)(Args...)>
|
||||
void disconnect(instance_type<Class> instance) {
|
||||
call_type target{instance, &proto<Class, Member>};
|
||||
calls.erase(std::remove(calls.begin(), calls.end(), std::move(target)), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes all existing connections for the given instance.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class>
|
||||
void disconnect(instance_type<Class> instance) {
|
||||
auto func = [instance](const call_type &call) { return call.first == instance; };
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(), std::move(func)), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Triggers a signal.
|
||||
*
|
||||
* All the listeners are notified. Order isn't guaranteed.
|
||||
*
|
||||
* @param args Arguments to use to invoke listeners.
|
||||
*/
|
||||
void publish(Args... args) {
|
||||
for(auto &&call: calls) {
|
||||
call.second(call.first, args...);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Collects return values from the listeners.
|
||||
* @param args Arguments to use to invoke listeners.
|
||||
* @return An instance of the collector filled with collected data.
|
||||
*/
|
||||
collector_type collect(Args... args) {
|
||||
collector_type collector;
|
||||
|
||||
for(auto &&call: calls) {
|
||||
if(!this->invoke(collector, call.second, call.first, args...)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return collector;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Swaps listeners between the two signals.
|
||||
* @param lhs A valid signal object.
|
||||
* @param rhs A valid signal object.
|
||||
*/
|
||||
friend void swap(SigH &lhs, SigH &rhs) {
|
||||
using std::swap;
|
||||
swap(lhs.calls, rhs.calls);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two signals are identical.
|
||||
*
|
||||
* Two signals are identical if they have the same size and the same
|
||||
* listeners registered exactly in the same order.
|
||||
*
|
||||
* @param other Signal with which to compare.
|
||||
* @return True if the two signals are identical, false otherwise.
|
||||
*/
|
||||
bool operator==(const SigH &other) const noexcept {
|
||||
return std::equal(calls.cbegin(), calls.cend(), other.calls.cbegin(), other.calls.cend());
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<call_type> calls;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two signals are different.
|
||||
*
|
||||
* Two signals are identical if they have the same size and the same
|
||||
* listeners registered exactly in the same order.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
* @param lhs A valid signal object.
|
||||
* @param rhs A valid signal object.
|
||||
* @return True if the two signals are different, false otherwise.
|
||||
*/
|
||||
template<typename Ret, typename... Args>
|
||||
bool operator!=(const SigH<Ret(Args...)> &lhs, const SigH<Ret(Args...)> &rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_SIGNAL_SIGH_HPP
|
||||
272
src/entt/signal/signal.hpp
Normal file
272
src/entt/signal/signal.hpp
Normal file
@@ -0,0 +1,272 @@
|
||||
#ifndef ENTT_SIGNAL_SIGNAL_HPP
|
||||
#define ENTT_SIGNAL_SIGNAL_HPP
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Managed signal handler declaration.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error unless the template parameter is a function type.
|
||||
*/
|
||||
template<typename>
|
||||
class Signal;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Managed signal handler definition.
|
||||
*
|
||||
* Managed signal handler. It works with weak pointers to classes and pointers
|
||||
* to member functions as well as pointers to free functions. References are
|
||||
* automatically removed when the instances to which they point are freed.
|
||||
*
|
||||
* This class can be used to create signals used later to notify a bunch of
|
||||
* listeners.
|
||||
*
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
*/
|
||||
template<typename... Args>
|
||||
class Signal<void(Args...)> final {
|
||||
using proto_type = bool(*)(std::weak_ptr<void> &, Args...);
|
||||
using call_type = std::pair<std::weak_ptr<void>, proto_type>;
|
||||
|
||||
template<void(*Function)(Args...)>
|
||||
static bool proto(std::weak_ptr<void> &, Args... args) {
|
||||
Function(args...);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename Class, void(Class::*Member)(Args...)>
|
||||
static bool proto(std::weak_ptr<void> &wptr, Args... args) {
|
||||
bool ret = false;
|
||||
|
||||
if(!wptr.expired()) {
|
||||
auto ptr = std::static_pointer_cast<Class>(wptr.lock());
|
||||
(ptr.get()->*Member)(args...);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
|
||||
/**
|
||||
* @brief Instance type when it comes to connecting member functions.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
*/
|
||||
template<typename Class>
|
||||
using instance_type = std::shared_ptr<Class>;
|
||||
|
||||
/*! @brief Default constructor, explicit on purpose. */
|
||||
explicit Signal() noexcept = default;
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
~Signal() noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief Copy constructor, listeners are also connected to this signal.
|
||||
* @param other A signal to use as source to initialize this instance.
|
||||
*/
|
||||
Signal(const Signal &other)
|
||||
: calls{other.calls}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Default move constructor.
|
||||
* @param other A signal to use as source to initialize this instance.
|
||||
*/
|
||||
Signal(Signal &&other): Signal{} {
|
||||
swap(*this, other);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy assignment operator.
|
||||
*
|
||||
* Listeners are also connected to this signal.
|
||||
*
|
||||
* @param other A signal to use as source to initialize this instance.
|
||||
* @return This signal.
|
||||
*/
|
||||
Signal & operator=(const Signal &other) {
|
||||
calls = other.calls;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator.
|
||||
* @param other A signal to use as source to initialize this instance.
|
||||
* @return This signal.
|
||||
*/
|
||||
Signal & operator=(Signal &&other) {
|
||||
swap(*this, other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Number of listeners connected to the signal.
|
||||
* @return Number of listeners currently connected.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
return calls.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns false is at least a listener is connected to the signal.
|
||||
* @return True if the signal has no listeners connected, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
return calls.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects all the listeners from a signal.
|
||||
*/
|
||||
void clear() noexcept {
|
||||
calls.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function to a signal.
|
||||
*
|
||||
* The signal handler performs checks to avoid multiple connections for free
|
||||
* functions.
|
||||
*
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<void(*Function)(Args...)>
|
||||
void connect() {
|
||||
disconnect<Function>();
|
||||
calls.emplace_back(std::weak_ptr<void>{}, &proto<Function>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a member function for a given instance to a signal.
|
||||
*
|
||||
* The signal handler performs checks to avoid multiple connections for the
|
||||
* same member function of a given instance.
|
||||
*
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class, void(Class::*Member)(Args...)>
|
||||
void connect(instance_type<Class> instance) {
|
||||
disconnect<Class, Member>(instance);
|
||||
calls.emplace_back(std::move(instance), &proto<Class, Member>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function from a signal.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<void(*Function)(Args...)>
|
||||
void disconnect() {
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(),
|
||||
[](const call_type &call) { return call.second == &proto<Function> && !call.first.lock(); }
|
||||
), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects the given member function from a signal.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class, void(Class::*Member)(Args...)>
|
||||
void disconnect(instance_type<Class> instance) {
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(),
|
||||
[instance{std::move(instance)}](const call_type &call) { return call.second == &proto<Class, Member> && call.first.lock() == instance; }
|
||||
), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes all existing connections for the given instance.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class>
|
||||
void disconnect(instance_type<Class> instance) {
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(),
|
||||
[instance{std::move(instance)}](const call_type &call) { return call.first.lock() == instance; }
|
||||
), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Triggers a signal.
|
||||
*
|
||||
* All the listeners are notified. Order isn't guaranteed.
|
||||
*
|
||||
* @param args Arguments to use to invoke listeners.
|
||||
*/
|
||||
void publish(Args... args) {
|
||||
for(auto it = calls.rbegin(), end = calls.rend(); it != end; it++) {
|
||||
if(!(it->second)(it->first, args...)) {
|
||||
calls.erase(std::next(it).base());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Swaps listeners between the two signals.
|
||||
* @param lhs A valid signal object.
|
||||
* @param rhs A valid signal object.
|
||||
*/
|
||||
friend void swap(Signal &lhs, Signal &rhs) {
|
||||
using std::swap;
|
||||
swap(lhs.calls, rhs.calls);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two signals are identical.
|
||||
*
|
||||
* Two signals are identical if they have the same size and the same
|
||||
* listeners registered exactly in the same order.
|
||||
*
|
||||
* @param other Signal with which to compare.
|
||||
* @return True if the two signals are identical, false otherwise.
|
||||
*/
|
||||
bool operator==(const Signal &other) const noexcept {
|
||||
return std::equal(calls.cbegin(), calls.cend(), other.calls.cbegin(), other.calls.cend(), [](const auto &lhs, const auto &rhs) {
|
||||
return (lhs.second == rhs.second) && (lhs.first.lock() == rhs.first.lock());
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<call_type> calls;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two signals are different.
|
||||
*
|
||||
* Two signals are identical if they have the same size and the same
|
||||
* listeners registered exactly in the same order.
|
||||
*
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
* @param lhs A valid signal object.
|
||||
* @param rhs A valid signal object.
|
||||
* @return True if the two signals are different, false otherwise.
|
||||
*/
|
||||
template<typename... Args>
|
||||
bool operator!=(const Signal<void(Args...)> &lhs, const Signal<void(Args...)> &rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_SIGNAL_SIGNAL_HPP
|
||||
@@ -1,43 +0,0 @@
|
||||
#ifndef ENTT_IDENT_HPP
|
||||
#define ENTT_IDENT_HPP
|
||||
|
||||
|
||||
#include<type_traits>
|
||||
#include<cstddef>
|
||||
#include<utility>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
namespace details {
|
||||
|
||||
|
||||
template<typename Type>
|
||||
struct Wrapper {
|
||||
using type = Type;
|
||||
constexpr Wrapper(std::size_t index): index{index} {}
|
||||
const std::size_t index;
|
||||
};
|
||||
|
||||
template<typename... Types>
|
||||
struct Identifier final: Wrapper<Types>... {
|
||||
template<std::size_t... Indexes>
|
||||
constexpr Identifier(std::index_sequence<Indexes...>): Wrapper<Types>{Indexes}... {}
|
||||
|
||||
template<typename Type>
|
||||
constexpr std::size_t get() const { return Wrapper<std::decay_t<Type>>::index; }
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
template<typename... Types>
|
||||
constexpr auto ident = details::Identifier<std::decay_t<Types>...>{std::make_index_sequence<sizeof...(Types)>{}};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_IDENT_HPP
|
||||
443
src/registry.hpp
443
src/registry.hpp
@@ -1,443 +0,0 @@
|
||||
#ifndef ENTT_REGISTRY_HPP
|
||||
#define ENTT_REGISTRY_HPP
|
||||
|
||||
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#include <bitset>
|
||||
#include <utility>
|
||||
#include <cstddef>
|
||||
#include <cassert>
|
||||
#include <type_traits>
|
||||
#include "sparse_set.hpp"
|
||||
#include "ident.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
template<typename, std::size_t...>
|
||||
class View;
|
||||
|
||||
|
||||
template<typename Pool, std::size_t Ident, std::size_t... Other>
|
||||
class View<Pool, Ident, Other...> final {
|
||||
using pool_type = Pool;
|
||||
using mask_type = std::bitset<std::tuple_size<Pool>::value + 1>;
|
||||
using underlying_iterator_type = typename std::tuple_element_t<Ident, Pool>::iterator_type;
|
||||
|
||||
class ViewIterator;
|
||||
|
||||
public:
|
||||
using iterator_type = ViewIterator;
|
||||
using entity_type = typename std::tuple_element_t<Ident, Pool>::index_type;
|
||||
using size_type = typename std::tuple_element_t<Ident, Pool>::size_type;
|
||||
|
||||
private:
|
||||
class ViewIterator {
|
||||
inline bool valid() const noexcept {
|
||||
return ((mask[*begin] & bitmask) == bitmask);
|
||||
}
|
||||
|
||||
public:
|
||||
using value_type = entity_type;
|
||||
|
||||
ViewIterator(underlying_iterator_type begin, underlying_iterator_type end, const mask_type &bitmask, const mask_type *mask) noexcept
|
||||
: begin{begin}, end{end}, bitmask{bitmask}, mask{mask}
|
||||
{
|
||||
if(begin != end && !valid()) {
|
||||
++(*this);
|
||||
}
|
||||
}
|
||||
|
||||
ViewIterator & operator++() noexcept {
|
||||
++begin;
|
||||
while(begin != end && !valid()) { ++begin; }
|
||||
return *this;
|
||||
}
|
||||
|
||||
ViewIterator operator++(int) noexcept {
|
||||
ViewIterator orig = *this;
|
||||
return ++(*this), orig;
|
||||
}
|
||||
|
||||
bool operator==(const ViewIterator &other) const noexcept {
|
||||
return other.begin == begin;
|
||||
}
|
||||
|
||||
bool operator!=(const ViewIterator &other) const noexcept {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
value_type operator*() const noexcept {
|
||||
return *begin;
|
||||
}
|
||||
|
||||
private:
|
||||
underlying_iterator_type begin;
|
||||
underlying_iterator_type end;
|
||||
const mask_type bitmask;
|
||||
const mask_type *mask;
|
||||
};
|
||||
|
||||
template<std::size_t Idx>
|
||||
void prefer(size_type &size) noexcept {
|
||||
auto &&cpool = std::get<Idx>(*pool);
|
||||
auto sz = cpool.size();
|
||||
|
||||
if(sz < size) {
|
||||
from = cpool.begin();
|
||||
to = cpool.end();
|
||||
size = sz;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
explicit View(const pool_type *pool, const mask_type *mask) noexcept
|
||||
: from{std::get<Ident>(*pool).begin()},
|
||||
to{std::get<Ident>(*pool).end()},
|
||||
pool{pool},
|
||||
mask{mask}
|
||||
{
|
||||
using accumulator_type = int[];
|
||||
size_type size = std::get<Ident>(*pool).size();
|
||||
bitmask.set(Ident);
|
||||
accumulator_type types = { 0, (bitmask.set(Other), 0)... };
|
||||
accumulator_type pref = { 0, (prefer<Other>(size), 0)... };
|
||||
(void)types, (void)pref;
|
||||
}
|
||||
|
||||
iterator_type begin() const noexcept {
|
||||
return ViewIterator{from, to, bitmask, mask};
|
||||
}
|
||||
|
||||
iterator_type end() const noexcept {
|
||||
return ViewIterator{to, to, bitmask, mask};
|
||||
}
|
||||
|
||||
void reset() noexcept {
|
||||
using accumulator_type = int[];
|
||||
auto &&cpool = std::get<Ident>(*pool);
|
||||
from = cpool.begin();
|
||||
to = cpool.end();
|
||||
size_type size = cpool.size();
|
||||
accumulator_type accumulator = { 0, (prefer<Other>(size), 0)... };
|
||||
(void)accumulator;
|
||||
}
|
||||
|
||||
private:
|
||||
underlying_iterator_type from;
|
||||
underlying_iterator_type to;
|
||||
const pool_type *pool;
|
||||
const mask_type *mask;
|
||||
mask_type bitmask;
|
||||
};
|
||||
|
||||
|
||||
template<typename Pool, std::size_t Ident>
|
||||
class View<Pool, Ident> final {
|
||||
using pool_type = std::tuple_element_t<Ident, Pool>;
|
||||
|
||||
public:
|
||||
using iterator_type = typename pool_type::iterator_type;
|
||||
using entity_type = typename pool_type::index_type;
|
||||
using size_type = typename pool_type::size_type;
|
||||
using raw_type = typename pool_type::type;
|
||||
|
||||
explicit View(const Pool *pool) noexcept
|
||||
: pool{&std::get<Ident>(*pool)}
|
||||
{}
|
||||
|
||||
raw_type * raw() noexcept {
|
||||
return pool->raw();
|
||||
}
|
||||
|
||||
const raw_type * raw() const noexcept {
|
||||
return pool->raw();
|
||||
}
|
||||
|
||||
const entity_type * data() const noexcept {
|
||||
return pool->data();
|
||||
}
|
||||
|
||||
size_type size() const noexcept {
|
||||
return pool->size();
|
||||
}
|
||||
|
||||
iterator_type begin() const noexcept {
|
||||
return pool->begin();
|
||||
}
|
||||
|
||||
iterator_type end() const noexcept {
|
||||
return pool->end();
|
||||
}
|
||||
|
||||
private:
|
||||
const pool_type *pool;
|
||||
};
|
||||
|
||||
|
||||
template<typename Entity, typename... Component>
|
||||
class Registry {
|
||||
using pool_type = std::tuple<SparseSet<Entity, Component>...>;
|
||||
using mask_type = std::bitset<sizeof...(Component)+1>;
|
||||
|
||||
static constexpr auto validity_bit = sizeof...(Component);
|
||||
|
||||
// variable templates are fine as well, but for the fact that MSVC goes crazy
|
||||
template<typename Comp>
|
||||
struct identifier {
|
||||
static constexpr auto value = ident<Component...>.template get<Comp>();
|
||||
};
|
||||
|
||||
public:
|
||||
using entity_type = Entity;
|
||||
using size_type = typename std::vector<mask_type>::size_type;
|
||||
|
||||
template<typename... Comp>
|
||||
using view_type = View<pool_type, identifier<Comp>::value...>;
|
||||
|
||||
private:
|
||||
template<typename Comp>
|
||||
void clone(entity_type to, entity_type from) {
|
||||
if(entities[from].test(identifier<Comp>::value)) {
|
||||
assign<Comp>(to, std::get<identifier<Comp>::value>(pool).get(from));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
void sync(entity_type to, entity_type from) {
|
||||
bool src = entities[from].test(identifier<Comp>::value);
|
||||
bool dst = entities[to].test(identifier<Comp>::value);
|
||||
|
||||
if(src && dst) {
|
||||
copy<Comp>(to, from);
|
||||
} else if(src) {
|
||||
clone<Comp>(to, from);
|
||||
} else if(dst) {
|
||||
remove<Comp>(to);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
explicit Registry() = default;
|
||||
~Registry() = default;
|
||||
|
||||
Registry(const Registry &) = delete;
|
||||
Registry(Registry &&) = delete;
|
||||
|
||||
Registry & operator=(const Registry &) = delete;
|
||||
Registry & operator=(Registry &&) = delete;
|
||||
|
||||
template<typename Comp>
|
||||
size_type size() const noexcept {
|
||||
return std::get<identifier<Comp>::value>(pool).size();
|
||||
}
|
||||
|
||||
size_type size() const noexcept {
|
||||
return entities.size() - available.size();
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
size_type capacity() const noexcept {
|
||||
return std::get<identifier<Comp>::value>(pool).capacity();
|
||||
}
|
||||
|
||||
size_type capacity() const noexcept {
|
||||
return entities.size();
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
bool empty() const noexcept {
|
||||
return std::get<identifier<Comp>::value>(pool).empty();
|
||||
}
|
||||
|
||||
bool empty() const noexcept {
|
||||
return entities.empty();
|
||||
}
|
||||
|
||||
bool valid(entity_type entity) const noexcept {
|
||||
return (entity < entities.size() && entities[entity].test(validity_bit));
|
||||
}
|
||||
|
||||
template<typename... Comp>
|
||||
entity_type create() noexcept {
|
||||
using accumulator_type = int[];
|
||||
auto entity = create();
|
||||
accumulator_type accumulator = { 0, (assign<Comp>(entity), 0)... };
|
||||
(void)accumulator;
|
||||
return entity;
|
||||
}
|
||||
|
||||
entity_type create() noexcept {
|
||||
entity_type entity;
|
||||
|
||||
if(available.empty()) {
|
||||
entity = entity_type(entities.size());
|
||||
entities.emplace_back();
|
||||
} else {
|
||||
entity = available.back();
|
||||
available.pop_back();
|
||||
}
|
||||
|
||||
entities[entity].set(validity_bit);
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
void destroy(entity_type entity) {
|
||||
assert(valid(entity));
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (reset<Component>(entity), 0)... };
|
||||
available.push_back(entity);
|
||||
entities[entity].reset();
|
||||
(void)accumulator;
|
||||
}
|
||||
|
||||
template<typename Comp, typename... Args>
|
||||
Comp & assign(entity_type entity, Args... args) {
|
||||
assert(valid(entity));
|
||||
entities[entity].set(identifier<Comp>::value);
|
||||
return std::get<identifier<Comp>::value>(pool).construct(entity, args...);
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
void remove(entity_type entity) {
|
||||
assert(valid(entity));
|
||||
entities[entity].reset(identifier<Comp>::value);
|
||||
std::get<identifier<Comp>::value>(pool).destroy(entity);
|
||||
}
|
||||
|
||||
template<typename... Comp>
|
||||
bool has(entity_type entity) const noexcept {
|
||||
assert(valid(entity));
|
||||
using accumulator_type = bool[];
|
||||
bool all = true;
|
||||
auto &mask = entities[entity];
|
||||
accumulator_type accumulator = { true, (all = all && mask.test(identifier<Comp>::value))... };
|
||||
(void)accumulator;
|
||||
return all;
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
const Comp & get(entity_type entity) const noexcept {
|
||||
assert(valid(entity));
|
||||
return std::get<identifier<Comp>::value>(pool).get(entity);
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
Comp & get(entity_type entity) noexcept {
|
||||
assert(valid(entity));
|
||||
return std::get<identifier<Comp>::value>(pool).get(entity);
|
||||
}
|
||||
|
||||
template<typename Comp, typename... Args>
|
||||
Comp & replace(entity_type entity, Args... args) {
|
||||
assert(valid(entity));
|
||||
return (std::get<identifier<Comp>::value>(pool).get(entity) = Comp{args...});
|
||||
}
|
||||
|
||||
template<typename Comp, typename... Args>
|
||||
Comp & accomodate(entity_type entity, Args... args) {
|
||||
assert(valid(entity));
|
||||
|
||||
return (entities[entity].test(identifier<Comp>::value)
|
||||
? this->template replace<Comp>(entity, std::forward<Args>(args)...)
|
||||
: this->template assign<Comp>(entity, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
entity_type clone(entity_type from) {
|
||||
assert(valid(from));
|
||||
using accumulator_type = int[];
|
||||
auto to = create();
|
||||
accumulator_type accumulator = { 0, (clone<Component>(to, from), 0)... };
|
||||
(void)accumulator;
|
||||
return to;
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
Comp & copy(entity_type to, entity_type from) {
|
||||
assert(valid(to));
|
||||
assert(valid(from));
|
||||
auto &&cpool = std::get<identifier<Comp>::value>(pool);
|
||||
return (cpool.get(to) = cpool.get(from));
|
||||
}
|
||||
|
||||
void copy(entity_type to, entity_type from) {
|
||||
assert(valid(to));
|
||||
assert(valid(from));
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (sync<Component>(to, from), 0)... };
|
||||
(void)accumulator;
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
void swap(entity_type lhs, entity_type rhs) {
|
||||
assert(valid(lhs));
|
||||
assert(valid(rhs));
|
||||
std::get<identifier<Comp>::value>(pool).swap(lhs, rhs);
|
||||
}
|
||||
|
||||
template<typename Comp, typename Compare>
|
||||
void sort(Compare compare) {
|
||||
std::get<identifier<Comp>::value>(pool).sort(std::move(compare));
|
||||
}
|
||||
|
||||
template<typename To, typename From>
|
||||
void sort() {
|
||||
auto &&to = std::get<identifier<To>::value>(pool);
|
||||
auto &&from = std::get<identifier<From>::value>(pool);
|
||||
to.respect(from);
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
void reset(entity_type entity) {
|
||||
assert(valid(entity));
|
||||
|
||||
if(entities[entity].test(identifier<Comp>::value)) {
|
||||
remove<Comp>(entity);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
void reset() {
|
||||
for(entity_type entity = 0, last = entity_type(entities.size()); entity < last; ++entity) {
|
||||
if(entities[entity].test(identifier<Comp>::value)) {
|
||||
remove<Comp>(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void reset() {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type acc = { 0, (std::get<identifier<Component>::value>(pool).reset(), 0)... };
|
||||
entities.clear();
|
||||
available.clear();
|
||||
(void)acc;
|
||||
}
|
||||
|
||||
template<typename... Comp>
|
||||
// view_type<Comp...> is fine as well, but for the fact that MSVC dislikes it
|
||||
std::enable_if_t<(sizeof...(Comp) == 1), View<pool_type, identifier<Comp>::value...>>
|
||||
view() noexcept { return view_type<Comp...>{&pool}; }
|
||||
|
||||
template<typename... Comp>
|
||||
// view_type<Comp...> is fine as well, but for the fact that MSVC dislikes it
|
||||
std::enable_if_t<(sizeof...(Comp) > 1), View<pool_type, identifier<Comp>::value...>>
|
||||
view() noexcept { return view_type<Comp...>{&pool, entities.data()}; }
|
||||
|
||||
private:
|
||||
std::vector<mask_type> entities;
|
||||
std::vector<entity_type> available;
|
||||
pool_type pool;
|
||||
};
|
||||
|
||||
|
||||
template<typename... Component>
|
||||
using DefaultRegistry = Registry<std::uint32_t, Component...>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_REGISTRY_HPP
|
||||
@@ -1,277 +0,0 @@
|
||||
#ifndef ENTT_COMPONENT_POOL_HPP
|
||||
#define ENTT_COMPONENT_POOL_HPP
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
#include <cstddef>
|
||||
#include <cassert>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
template<typename...>
|
||||
class SparseSet;
|
||||
|
||||
|
||||
template<typename Index>
|
||||
class SparseSet<Index> {
|
||||
struct SparseSetIterator;
|
||||
|
||||
public:
|
||||
using index_type = Index;
|
||||
using pos_type = index_type;
|
||||
using size_type = std::size_t;
|
||||
using iterator_type = SparseSetIterator;
|
||||
|
||||
private:
|
||||
struct SparseSetIterator {
|
||||
using value_type = index_type;
|
||||
|
||||
SparseSetIterator(const std::vector<index_type> *direct, size_type pos)
|
||||
: direct{direct}, pos{pos}
|
||||
{}
|
||||
|
||||
SparseSetIterator & operator++() noexcept {
|
||||
return --pos, *this;
|
||||
}
|
||||
|
||||
SparseSetIterator operator++(int) noexcept {
|
||||
SparseSetIterator orig = *this;
|
||||
return ++(*this), orig;
|
||||
}
|
||||
|
||||
bool operator==(const SparseSetIterator &other) const noexcept {
|
||||
return other.pos == pos && other.direct == direct;
|
||||
}
|
||||
|
||||
bool operator!=(const SparseSetIterator &other) const noexcept {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
value_type operator*() const noexcept {
|
||||
return (*direct)[pos-1];
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<index_type> *direct;
|
||||
size_type pos;
|
||||
};
|
||||
|
||||
inline bool valid(Index idx) const noexcept {
|
||||
return idx < reverse.size() && reverse[idx] < direct.size() && direct[reverse[idx]] == idx;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit SparseSet() = default;
|
||||
|
||||
SparseSet(const SparseSet &) = delete;
|
||||
SparseSet(SparseSet &&) = default;
|
||||
|
||||
~SparseSet() noexcept {
|
||||
assert(empty());
|
||||
}
|
||||
|
||||
SparseSet & operator=(const SparseSet &) = delete;
|
||||
SparseSet & operator=(SparseSet &&) = default;
|
||||
|
||||
size_type size() const noexcept {
|
||||
return direct.size();
|
||||
}
|
||||
|
||||
size_t capacity() const noexcept {
|
||||
return direct.capacity();
|
||||
}
|
||||
|
||||
bool empty() const noexcept {
|
||||
return direct.empty();
|
||||
}
|
||||
|
||||
const index_type * data() const noexcept {
|
||||
return direct.data();
|
||||
}
|
||||
|
||||
iterator_type begin() const noexcept {
|
||||
return SparseSetIterator{&direct, direct.size()};
|
||||
}
|
||||
|
||||
iterator_type end() const noexcept {
|
||||
return SparseSetIterator{&direct, 0};
|
||||
}
|
||||
|
||||
bool has(index_type idx) const noexcept {
|
||||
return valid(idx);
|
||||
}
|
||||
|
||||
pos_type get(index_type idx) const noexcept {
|
||||
assert(valid(idx));
|
||||
return reverse[idx];
|
||||
}
|
||||
|
||||
pos_type construct(index_type idx) {
|
||||
assert(!valid(idx));
|
||||
|
||||
if(!(idx < reverse.size())) {
|
||||
reverse.resize(idx+1);
|
||||
}
|
||||
|
||||
auto pos = pos_type(direct.size());
|
||||
reverse[idx] = pos;
|
||||
direct.emplace_back(idx);
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
pos_type destroy(index_type idx) {
|
||||
assert(valid(idx));
|
||||
|
||||
auto last = direct.size() - 1;
|
||||
auto pos = reverse[idx];
|
||||
|
||||
reverse[direct[last]] = pos;
|
||||
direct[pos] = direct[last];
|
||||
direct.pop_back();
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
void swap(index_type lhs, index_type rhs) {
|
||||
assert(valid(lhs));
|
||||
assert(valid(rhs));
|
||||
|
||||
std::swap(direct[reverse[lhs]], direct[reverse[rhs]]);
|
||||
std::swap(reverse[lhs], reverse[rhs]);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
reverse.clear();
|
||||
direct.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<pos_type> reverse;
|
||||
std::vector<index_type> direct;
|
||||
};
|
||||
|
||||
|
||||
template<typename Index, typename Type>
|
||||
class SparseSet<Index, Type> final: public SparseSet<Index> {
|
||||
template<typename Compare>
|
||||
void arrange(Compare compare) {
|
||||
const auto *data = SparseSet<Index>::data();
|
||||
const auto size = SparseSet<Index>::size();
|
||||
std::vector<pos_type> copy(size);
|
||||
|
||||
std::iota(copy.begin(), copy.end(), pos_type{});
|
||||
std::sort(copy.begin(), copy.end(), compare);
|
||||
|
||||
for(pos_type i = 0; i < copy.size(); ++i) {
|
||||
const auto target = i;
|
||||
auto curr = i;
|
||||
|
||||
while(copy[curr] != target) {
|
||||
SparseSet<Index>::swap(*(data + copy[curr]), *(data + curr));
|
||||
std::swap(instances[copy[curr]], instances[curr]);
|
||||
std::swap(copy[curr], curr);
|
||||
}
|
||||
|
||||
copy[curr] = curr;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
using type = Type;
|
||||
using index_type = typename SparseSet<Index>::index_type;
|
||||
using pos_type = typename SparseSet<Index>::pos_type;
|
||||
using size_type = typename SparseSet<Index>::size_type;
|
||||
using iterator_type = typename SparseSet<Index>::iterator_type;
|
||||
|
||||
explicit SparseSet() = default;
|
||||
|
||||
SparseSet(const SparseSet &) = delete;
|
||||
SparseSet(SparseSet &&) = default;
|
||||
|
||||
SparseSet & operator=(const SparseSet &) = delete;
|
||||
SparseSet & operator=(SparseSet &&) = default;
|
||||
|
||||
type * raw() noexcept {
|
||||
return instances.data();
|
||||
}
|
||||
|
||||
const type * raw() const noexcept {
|
||||
return instances.data();
|
||||
}
|
||||
|
||||
const type & get(index_type idx) const noexcept {
|
||||
return instances[SparseSet<Index>::get(idx)];
|
||||
}
|
||||
|
||||
type & get(index_type idx) noexcept {
|
||||
return const_cast<type &>(const_cast<const SparseSet *>(this)->get(idx));
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
type & construct(index_type idx, Args&&... args) {
|
||||
SparseSet<Index>::construct(idx);
|
||||
instances.push_back({ std::forward<Args>(args)... });
|
||||
return instances.back();
|
||||
}
|
||||
|
||||
void destroy(index_type idx) {
|
||||
auto pos = SparseSet<Index>::destroy(idx);
|
||||
instances[pos] = std::move(instances[SparseSet<Index>::size()]);
|
||||
instances.pop_back();
|
||||
}
|
||||
|
||||
void swap(index_type lhs, index_type rhs) {
|
||||
std::swap(instances[SparseSet<Index>::get(lhs)], instances[SparseSet<Index>::get(rhs)]);
|
||||
}
|
||||
|
||||
template<typename Compare>
|
||||
void sort(Compare compare) {
|
||||
arrange([this, compare = std::move(compare)](auto lhs, auto rhs) {
|
||||
return !compare(instances[lhs], instances[rhs]);
|
||||
});
|
||||
}
|
||||
|
||||
template<typename Idx>
|
||||
void respect(const SparseSet<Idx> &other) {
|
||||
const auto *data = SparseSet<Index>::data();
|
||||
|
||||
arrange([data, &other](auto lhs, auto rhs) {
|
||||
auto eLhs = *(data + lhs);
|
||||
auto eRhs = *(data + rhs);
|
||||
|
||||
bool bLhs = other.has(eLhs);
|
||||
bool bRhs = other.has(eRhs);
|
||||
bool compare = false;
|
||||
|
||||
if(bLhs && bRhs) {
|
||||
compare = other.get(eLhs) < other.get(eRhs);
|
||||
} else if(!bLhs && !bRhs) {
|
||||
compare = eLhs < eRhs;
|
||||
} else {
|
||||
compare = bRhs;
|
||||
}
|
||||
|
||||
return compare;
|
||||
});
|
||||
}
|
||||
|
||||
void reset() {
|
||||
SparseSet<Index>::reset();
|
||||
instances.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<type> instances;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_COMPONENT_POOL_HPP
|
||||
@@ -2,25 +2,66 @@
|
||||
# Tests configuration
|
||||
#
|
||||
|
||||
set(COMMON_LINK_LIBS gtest_main Threads::Threads)
|
||||
include_directories(${PROJECT_SRC_DIR})
|
||||
|
||||
# List of available targets
|
||||
add_library(odr OBJECT odr.cpp)
|
||||
|
||||
set(TARGET_ENTT entt)
|
||||
set(TARGET_BENCHMARK benchmark)
|
||||
# Test benchmark
|
||||
|
||||
# Test TARGET_ENTT
|
||||
if(CMAKE_BUILD_TYPE MATCHES Release)
|
||||
add_executable(
|
||||
benchmark
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/entity/benchmark.cpp
|
||||
)
|
||||
target_link_libraries(benchmark PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME benchmark COMMAND benchmark)
|
||||
endif()
|
||||
|
||||
add_executable(${TARGET_ENTT} ident.cpp registry.cpp sparse_set.cpp)
|
||||
target_include_directories(${TARGET_ENTT} PRIVATE ${PROJECT_SRC_DIR})
|
||||
target_link_libraries(${TARGET_ENTT} PRIVATE ${COMMON_LINK_LIBS})
|
||||
add_test(NAME ${TARGET_ENTT} COMMAND ${TARGET_ENTT})
|
||||
# Test core
|
||||
|
||||
# Test TARGET_BENCHMARK
|
||||
add_executable(
|
||||
core
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/core/family.cpp
|
||||
entt/core/ident.cpp
|
||||
)
|
||||
target_link_libraries(core PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME core COMMAND core)
|
||||
|
||||
IF(CMAKE_BUILD_TYPE MATCHES Release)
|
||||
add_executable(${TARGET_BENCHMARK} benchmark.cpp)
|
||||
target_include_directories(${TARGET_BENCHMARK} PRIVATE ${PROJECT_SRC_DIR})
|
||||
target_link_libraries(${TARGET_BENCHMARK} PRIVATE ${COMMON_LINK_LIBS})
|
||||
add_test(NAME ${TARGET_BENCHMARK} COMMAND ${TARGET_BENCHMARK})
|
||||
ENDIF()
|
||||
# Test entity
|
||||
|
||||
add_executable(
|
||||
entity
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/entity/registry.cpp
|
||||
entt/entity/sparse_set.cpp
|
||||
entt/entity/view.cpp
|
||||
)
|
||||
target_link_libraries(entity PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME entity COMMAND entity)
|
||||
|
||||
# Test locator
|
||||
|
||||
add_executable(
|
||||
locator
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/locator/locator.cpp
|
||||
)
|
||||
target_link_libraries(locator PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME locator COMMAND locator)
|
||||
|
||||
# Test locator
|
||||
|
||||
add_executable(
|
||||
signal
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/signal/bus.cpp
|
||||
entt/signal/delegate.cpp
|
||||
entt/signal/dispatcher.cpp
|
||||
entt/signal/emitter.cpp
|
||||
entt/signal/sigh.cpp
|
||||
entt/signal/signal.cpp
|
||||
)
|
||||
target_link_libraries(signal PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME signal COMMAND signal)
|
||||
|
||||
@@ -1,410 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <registry.hpp>
|
||||
#include <iostream>
|
||||
#include <cstddef>
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
|
||||
struct Position {
|
||||
uint64_t x;
|
||||
uint64_t y;
|
||||
};
|
||||
|
||||
struct Velocity {
|
||||
uint64_t x;
|
||||
uint64_t y;
|
||||
};
|
||||
|
||||
template<std::size_t>
|
||||
struct Comp {};
|
||||
|
||||
struct Timer final {
|
||||
Timer(): start{std::chrono::system_clock::now()} {}
|
||||
|
||||
void elapsed() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
std::cout << std::chrono::duration<double>(now - start).count() << " seconds" << std::endl;
|
||||
}
|
||||
|
||||
private:
|
||||
std::chrono::time_point<std::chrono::system_clock> start;
|
||||
};
|
||||
|
||||
TEST(DefaultRegistry, Construct) {
|
||||
using registry_type = entt::DefaultRegistry<Position, Velocity>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
std::cout << "Constructing 10000000 entities" << std::endl;
|
||||
|
||||
Timer timer;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create();
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, Destroy) {
|
||||
using registry_type = entt::DefaultRegistry<Position, Velocity>;
|
||||
|
||||
registry_type registry;
|
||||
std::vector<registry_type::entity_type> entities{};
|
||||
|
||||
std::cout << "Destroying 10000000 entities" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
entities.push_back(registry.create());
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
for (auto entity: entities) {
|
||||
registry.destroy(entity);
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateCreateDeleteSingleComponent) {
|
||||
using registry_type = entt::DefaultRegistry<Position, Velocity>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
std::cout << "Looping 10000 times creating and deleting a random number of entities" << std::endl;
|
||||
|
||||
Timer timer;
|
||||
|
||||
for(int i = 0; i < 10000; i++) {
|
||||
for(int j = 0; j < 10000; j++) {
|
||||
registry.create<Position>();
|
||||
}
|
||||
|
||||
auto view = registry.view<Position>();
|
||||
|
||||
for(auto entity: view) {
|
||||
if(rand() % 2 == 0) {
|
||||
registry.destroy(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateSingleComponent10M) {
|
||||
using registry_type = entt::DefaultRegistry<Position, Velocity>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, one component" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = registry.get<Position>(entity);
|
||||
(void)position;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTwoComponents10M) {
|
||||
using registry_type = entt::DefaultRegistry<Position, Velocity>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position, Velocity>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = registry.get<Position>(entity);
|
||||
auto &velocity = registry.get<Velocity>(entity);
|
||||
(void)position;
|
||||
(void)velocity;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTwoComponents10MHalf) {
|
||||
using registry_type = entt::DefaultRegistry<Position, Velocity>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components, half of the entities have all the components" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity>();
|
||||
if(i % 2) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position, Velocity>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = registry.get<Position>(entity);
|
||||
auto &velocity = registry.get<Velocity>(entity);
|
||||
(void)position;
|
||||
(void)velocity;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTwoComponents10MOne) {
|
||||
using registry_type = entt::DefaultRegistry<Position, Velocity>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components, only one entity has all the components" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity>();
|
||||
if(i == 5000000L) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position, Velocity>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = registry.get<Position>(entity);
|
||||
auto &velocity = registry.get<Velocity>(entity);
|
||||
(void)position;
|
||||
(void)velocity;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateSingleComponent50M) {
|
||||
using registry_type = entt::DefaultRegistry<Position, Velocity>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
std::cout << "Iterating over 50000000 entities, one component" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 50000000L; i++) {
|
||||
registry.create<Position>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = registry.get<Position>(entity);
|
||||
(void)position;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTwoComponents50M) {
|
||||
using registry_type = entt::DefaultRegistry<Position, Velocity>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
std::cout << "Iterating over 50000000 entities, two components" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 50000000L; i++) {
|
||||
registry.create<Position, Velocity>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position, Velocity>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = registry.get<Position>(entity);
|
||||
auto &velocity = registry.get<Velocity>(entity);
|
||||
(void)position;
|
||||
(void)velocity;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateFiveComponents10M) {
|
||||
using registry_type = entt::DefaultRegistry<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, five components" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = registry.get<Position>(entity);
|
||||
auto &velocity = registry.get<Velocity>(entity);
|
||||
auto &comp1 = registry.get<Comp<1>>(entity);
|
||||
auto &comp2 = registry.get<Comp<2>>(entity);
|
||||
auto &comp3 = registry.get<Comp<3>>(entity);
|
||||
(void)position;
|
||||
(void)velocity;
|
||||
(void)comp1;
|
||||
(void)comp2;
|
||||
(void)comp3;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTenComponents10M) {
|
||||
using registry_type = entt::DefaultRegistry<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = registry.get<Position>(entity);
|
||||
auto &velocity = registry.get<Velocity>(entity);
|
||||
auto &comp1 = registry.get<Comp<1>>(entity);
|
||||
auto &comp2 = registry.get<Comp<2>>(entity);
|
||||
auto &comp3 = registry.get<Comp<3>>(entity);
|
||||
auto &comp4 = registry.get<Comp<4>>(entity);
|
||||
auto &comp5 = registry.get<Comp<5>>(entity);
|
||||
auto &comp6 = registry.get<Comp<6>>(entity);
|
||||
auto &comp7 = registry.get<Comp<7>>(entity);
|
||||
auto &comp8 = registry.get<Comp<8>>(entity);
|
||||
(void)position;
|
||||
(void)velocity;
|
||||
(void)comp1;
|
||||
(void)comp2;
|
||||
(void)comp3;
|
||||
(void)comp4;
|
||||
(void)comp5;
|
||||
(void)comp6;
|
||||
(void)comp7;
|
||||
(void)comp8;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTenComponents10MHalf) {
|
||||
using registry_type = entt::DefaultRegistry<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components, half of the entities have all the components" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
if(i % 2) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = registry.get<Position>(entity);
|
||||
auto &velocity = registry.get<Velocity>(entity);
|
||||
auto &comp1 = registry.get<Comp<1>>(entity);
|
||||
auto &comp2 = registry.get<Comp<2>>(entity);
|
||||
auto &comp3 = registry.get<Comp<3>>(entity);
|
||||
auto &comp4 = registry.get<Comp<4>>(entity);
|
||||
auto &comp5 = registry.get<Comp<5>>(entity);
|
||||
auto &comp6 = registry.get<Comp<6>>(entity);
|
||||
auto &comp7 = registry.get<Comp<7>>(entity);
|
||||
auto &comp8 = registry.get<Comp<8>>(entity);
|
||||
(void)position;
|
||||
(void)velocity;
|
||||
(void)comp1;
|
||||
(void)comp2;
|
||||
(void)comp3;
|
||||
(void)comp4;
|
||||
(void)comp5;
|
||||
(void)comp6;
|
||||
(void)comp7;
|
||||
(void)comp8;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTenComponents10MOne) {
|
||||
using registry_type = entt::DefaultRegistry<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components, only one entity has all the components" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
if(i == 5000000L) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = registry.get<Position>(entity);
|
||||
auto &velocity = registry.get<Velocity>(entity);
|
||||
auto &comp1 = registry.get<Comp<1>>(entity);
|
||||
auto &comp2 = registry.get<Comp<2>>(entity);
|
||||
auto &comp3 = registry.get<Comp<3>>(entity);
|
||||
auto &comp4 = registry.get<Comp<4>>(entity);
|
||||
auto &comp5 = registry.get<Comp<5>>(entity);
|
||||
auto &comp6 = registry.get<Comp<6>>(entity);
|
||||
auto &comp7 = registry.get<Comp<7>>(entity);
|
||||
auto &comp8 = registry.get<Comp<8>>(entity);
|
||||
(void)position;
|
||||
(void)velocity;
|
||||
(void)comp1;
|
||||
(void)comp2;
|
||||
(void)comp3;
|
||||
(void)comp4;
|
||||
(void)comp5;
|
||||
(void)comp6;
|
||||
(void)comp7;
|
||||
(void)comp8;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
22
test/entt/core/family.cpp
Normal file
22
test/entt/core/family.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/family.hpp>
|
||||
|
||||
using my_family = entt::Family<struct MyFamily>;
|
||||
using your_family = entt::Family<struct YourFamily>;
|
||||
|
||||
TEST(Family, Functionalities) {
|
||||
auto myFamilyType = my_family::type<struct MyFamilyType>();
|
||||
auto mySameFamilyType = my_family::type<struct MyFamilyType>();
|
||||
auto myOtherFamilyType = my_family::type<struct MyOtherFamilyType>();
|
||||
auto yourFamilyType = your_family::type<struct YourFamilyType>();
|
||||
|
||||
ASSERT_EQ(myFamilyType, mySameFamilyType);
|
||||
ASSERT_NE(myFamilyType, myOtherFamilyType);
|
||||
ASSERT_EQ(myFamilyType, yourFamilyType);
|
||||
}
|
||||
|
||||
TEST(Family, Uniqueness) {
|
||||
ASSERT_EQ(my_family::type<int>(), my_family::type<int &>());
|
||||
ASSERT_EQ(my_family::type<int>(), my_family::type<int &&>());
|
||||
ASSERT_EQ(my_family::type<int>(), my_family::type<const int &>());
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <type_traits>
|
||||
#include <gtest/gtest.h>
|
||||
#include <ident.hpp>
|
||||
#include <entt/core/ident.hpp>
|
||||
|
||||
struct A {};
|
||||
struct B {};
|
||||
@@ -24,3 +25,9 @@ TEST(Identifier, Uniqueness) {
|
||||
SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Identifier, SingleType) {
|
||||
constexpr auto ID = entt::ident<A>;
|
||||
std::integral_constant<decltype(ID)::identifier_type, ID.get()> ic;
|
||||
(void)ic;
|
||||
}
|
||||
355
test/entt/entity/benchmark.cpp
Normal file
355
test/entt/entity/benchmark.cpp
Normal file
@@ -0,0 +1,355 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <iostream>
|
||||
#include <cstddef>
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
struct Position {
|
||||
uint64_t x;
|
||||
uint64_t y;
|
||||
};
|
||||
|
||||
struct Velocity {
|
||||
uint64_t x;
|
||||
uint64_t y;
|
||||
};
|
||||
|
||||
template<std::size_t>
|
||||
struct Comp {};
|
||||
|
||||
struct Timer final {
|
||||
Timer(): start{std::chrono::system_clock::now()} {}
|
||||
|
||||
void elapsed() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
std::cout << std::chrono::duration<double>(now - start).count() << " seconds" << std::endl;
|
||||
}
|
||||
|
||||
private:
|
||||
std::chrono::time_point<std::chrono::system_clock> start;
|
||||
};
|
||||
|
||||
TEST(DefaultRegistry, Construct) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Constructing 10000000 entities" << std::endl;
|
||||
|
||||
Timer timer;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create();
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, Destroy) {
|
||||
entt::DefaultRegistry registry;
|
||||
std::vector<entt::DefaultRegistry::entity_type> entities{};
|
||||
|
||||
std::cout << "Destroying 10000000 entities" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
entities.push_back(registry.create());
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
for(auto entity: entities) {
|
||||
registry.destroy(entity);
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateCreateDeleteSingleComponent) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Looping 10000 times creating and deleting a random number of entities" << std::endl;
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position>();
|
||||
|
||||
for(int i = 0; i < 10000; i++) {
|
||||
for(int j = 0; j < 10000; j++) {
|
||||
registry.create<Position>();
|
||||
}
|
||||
|
||||
for(auto entity: view) {
|
||||
if(rand() % 2 == 0) {
|
||||
registry.destroy(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateSingleComponent10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, one component" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position>().each([](auto, auto &) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTwoComponents10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTwoComponents10MHalf) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components, half of the entities have all the components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity>();
|
||||
if(i % 2) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTwoComponents10MOne) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components, only one entity has all the components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity>();
|
||||
if(i == 5000000L) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTwoComponentsPersistent10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components, persistent view" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.persistent<Position, Velocity>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTwoComponentsPersistent10MHalf) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components, persistent view, half of the entities have all the components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity>();
|
||||
if(i % 2) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.persistent<Position, Velocity>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTwoComponentsPersistent10MOne) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components, persistent view, only one entity has all the components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity>();
|
||||
if(i == 5000000L) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.persistent<Position, Velocity>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateFiveComponents10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, five components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTenComponents10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTenComponents10MHalf) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components, half of the entities have all the components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
if(i % 2) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTenComponents10MOne) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components, only one entity has all the components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
if(i == 5000000L) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateFiveComponentsPersistent10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, five components, persistent view" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTenComponentsPersistent10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components, persistent view" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTenComponentsPersistent10MHalf) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components, persistent view, half of the entities have all the components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
if(i % 2) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTenComponentsPersistent10MOne) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components, persistent view, only one entity has all the components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
if(i == 5000000L) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, SortSingle) {
|
||||
entt::DefaultRegistry registry;
|
||||
std::vector<entt::DefaultRegistry::entity_type> entities{};
|
||||
|
||||
std::cout << "Sort 150000 entities, one component" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 150000L; i++) {
|
||||
auto entity = registry.create<Position>({ i, i });
|
||||
entities.push_back(entity);
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
registry.sort<Position>([](const auto &lhs, const auto &rhs) {
|
||||
return lhs.x < rhs.x && lhs.y < rhs.y;
|
||||
});
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, SortMulti) {
|
||||
entt::DefaultRegistry registry;
|
||||
std::vector<entt::DefaultRegistry::entity_type> entities{};
|
||||
|
||||
std::cout << "Sort 150000 entities, two components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 150000L; i++) {
|
||||
auto entity = registry.create<Position, Velocity>({ i, i }, { i, i });
|
||||
entities.push_back(entity);
|
||||
}
|
||||
|
||||
registry.sort<Position>([](const auto &lhs, const auto &rhs) {
|
||||
return lhs.x < rhs.x && lhs.y < rhs.y;
|
||||
});
|
||||
|
||||
Timer timer;
|
||||
|
||||
registry.sort<Velocity, Position>();
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
185
test/entt/entity/registry.cpp
Normal file
185
test/entt/entity/registry.cpp
Normal file
@@ -0,0 +1,185 @@
|
||||
#include <functional>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
TEST(DefaultRegistry, Functionalities) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{0});
|
||||
ASSERT_TRUE(registry.empty());
|
||||
|
||||
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{0});
|
||||
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{0});
|
||||
ASSERT_EQ(registry.size<char>(), entt::DefaultRegistry::size_type{0});
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
ASSERT_TRUE(registry.empty<char>());
|
||||
|
||||
auto e1 = registry.create();
|
||||
auto e2 = registry.create<int, char>();
|
||||
|
||||
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{2});
|
||||
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{1});
|
||||
ASSERT_EQ(registry.size<char>(), entt::DefaultRegistry::size_type{1});
|
||||
ASSERT_FALSE(registry.empty<int>());
|
||||
ASSERT_FALSE(registry.empty<char>());
|
||||
|
||||
ASSERT_NE(e1, e2);
|
||||
|
||||
ASSERT_FALSE(registry.has<int>(e1));
|
||||
ASSERT_TRUE(registry.has<int>(e2));
|
||||
ASSERT_FALSE(registry.has<char>(e1));
|
||||
ASSERT_TRUE(registry.has<char>(e2));
|
||||
ASSERT_FALSE((registry.has<int, char>(e1)));
|
||||
ASSERT_TRUE((registry.has<int, char>(e2)));
|
||||
|
||||
ASSERT_EQ(registry.assign<int>(e1, 42), 42);
|
||||
ASSERT_EQ(registry.assign<char>(e1, 'c'), 'c');
|
||||
ASSERT_NO_THROW(registry.remove<int>(e2));
|
||||
ASSERT_NO_THROW(registry.remove<char>(e2));
|
||||
|
||||
ASSERT_TRUE(registry.has<int>(e1));
|
||||
ASSERT_FALSE(registry.has<int>(e2));
|
||||
ASSERT_TRUE(registry.has<char>(e1));
|
||||
ASSERT_FALSE(registry.has<char>(e2));
|
||||
ASSERT_TRUE((registry.has<int, char>(e1)));
|
||||
ASSERT_FALSE((registry.has<int, char>(e2)));
|
||||
|
||||
auto e3 = registry.create();
|
||||
|
||||
registry.accomodate<int>(e3, registry.get<int>(e1));
|
||||
registry.accomodate<char>(e3, registry.get<char>(e1));
|
||||
|
||||
ASSERT_TRUE(registry.has<int>(e3));
|
||||
ASSERT_TRUE(registry.has<char>(e3));
|
||||
ASSERT_EQ(registry.get<int>(e1), 42);
|
||||
ASSERT_EQ(registry.get<char>(e1), 'c');
|
||||
ASSERT_EQ(registry.get<int>(e1), registry.get<int>(e3));
|
||||
ASSERT_EQ(registry.get<char>(e1), registry.get<char>(e3));
|
||||
ASSERT_NE(®istry.get<int>(e1), ®istry.get<int>(e3));
|
||||
ASSERT_NE(®istry.get<char>(e1), ®istry.get<char>(e3));
|
||||
|
||||
ASSERT_NO_THROW(registry.replace<int>(e1, 0));
|
||||
ASSERT_EQ(registry.get<int>(e1), 0);
|
||||
|
||||
ASSERT_NO_THROW(registry.accomodate<int>(e1, 1));
|
||||
ASSERT_NO_THROW(registry.accomodate<int>(e2, 1));
|
||||
ASSERT_EQ(static_cast<const entt::DefaultRegistry &>(registry).get<int>(e1), 1);
|
||||
ASSERT_EQ(static_cast<const entt::DefaultRegistry &>(registry).get<int>(e2), 1);
|
||||
|
||||
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{3});
|
||||
ASSERT_FALSE(registry.empty());
|
||||
|
||||
ASSERT_EQ(registry.version(e3), entt::DefaultRegistry::version_type{0});
|
||||
ASSERT_EQ(registry.current(e3), entt::DefaultRegistry::version_type{0});
|
||||
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{3});
|
||||
ASSERT_NO_THROW(registry.destroy(e3));
|
||||
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{3});
|
||||
ASSERT_EQ(registry.version(e3), entt::DefaultRegistry::version_type{0});
|
||||
ASSERT_EQ(registry.current(e3), entt::DefaultRegistry::version_type{1});
|
||||
|
||||
ASSERT_TRUE(registry.valid(e1));
|
||||
ASSERT_TRUE(registry.valid(e2));
|
||||
ASSERT_FALSE(registry.valid(e3));
|
||||
|
||||
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{2});
|
||||
ASSERT_FALSE(registry.empty());
|
||||
|
||||
ASSERT_NO_THROW(registry.reset());
|
||||
|
||||
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{0});
|
||||
ASSERT_TRUE(registry.empty());
|
||||
|
||||
registry.create<int, char>();
|
||||
|
||||
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{1});
|
||||
ASSERT_EQ(registry.size<char>(), entt::DefaultRegistry::size_type{1});
|
||||
ASSERT_FALSE(registry.empty<int>());
|
||||
ASSERT_FALSE(registry.empty<char>());
|
||||
|
||||
ASSERT_NO_THROW(registry.reset<int>());
|
||||
|
||||
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{0});
|
||||
ASSERT_EQ(registry.size<char>(), entt::DefaultRegistry::size_type{1});
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
ASSERT_FALSE(registry.empty<char>());
|
||||
|
||||
ASSERT_NO_THROW(registry.reset());
|
||||
|
||||
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{0});
|
||||
ASSERT_EQ(registry.size<char>(), entt::DefaultRegistry::size_type{0});
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
ASSERT_TRUE(registry.empty<char>());
|
||||
|
||||
e1 = registry.create<int>();
|
||||
e2 = registry.create();
|
||||
|
||||
ASSERT_NO_THROW(registry.reset<int>(e1));
|
||||
ASSERT_NO_THROW(registry.reset<int>(e2));
|
||||
|
||||
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{0});
|
||||
ASSERT_EQ(registry.size<char>(), entt::DefaultRegistry::size_type{0});
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, CreateDestroyEntities) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
auto pre = registry.create<double>();
|
||||
registry.destroy(pre);
|
||||
auto post = registry.create<double>();
|
||||
|
||||
ASSERT_FALSE(registry.valid(pre));
|
||||
ASSERT_TRUE(registry.valid(post));
|
||||
ASSERT_NE(registry.version(pre), registry.version(post));
|
||||
ASSERT_EQ(registry.current(pre), registry.current(post));
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, SortSingle) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
int val = 0;
|
||||
|
||||
registry.create(val++);
|
||||
registry.create(val++);
|
||||
registry.create(val++);
|
||||
|
||||
for(auto entity: registry.view<int>()) {
|
||||
ASSERT_EQ(registry.get<int>(entity), --val);
|
||||
}
|
||||
|
||||
registry.sort<int>(std::less<int>{});
|
||||
|
||||
for(auto entity: registry.view<int>()) {
|
||||
ASSERT_EQ(registry.get<int>(entity), val++);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, SortMulti) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
unsigned int uval = 0u;
|
||||
int ival = 0;
|
||||
|
||||
registry.create(uval++, ival++);
|
||||
registry.create(uval++, ival++);
|
||||
registry.create(uval++, ival++);
|
||||
|
||||
for(auto entity: registry.view<unsigned int>()) {
|
||||
ASSERT_EQ(registry.get<unsigned int>(entity), --uval);
|
||||
}
|
||||
|
||||
for(auto entity: registry.view<int>()) {
|
||||
ASSERT_EQ(registry.get<int>(entity), --ival);
|
||||
}
|
||||
|
||||
registry.sort<unsigned int>(std::less<unsigned int>{});
|
||||
registry.sort<int, unsigned int>();
|
||||
|
||||
for(auto entity: registry.view<unsigned int>()) {
|
||||
ASSERT_EQ(registry.get<unsigned int>(entity), uval++);
|
||||
}
|
||||
|
||||
for(auto entity: registry.view<int>()) {
|
||||
ASSERT_EQ(registry.get<int>(entity), ival++);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <sparse_set.hpp>
|
||||
#include <entt/entity/sparse_set.hpp>
|
||||
|
||||
TEST(SparseSetNoType, Functionalities) {
|
||||
using SparseSet = entt::SparseSet<unsigned int>;
|
||||
|
||||
SparseSet set;
|
||||
entt::SparseSet<unsigned int> set;
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(set.capacity(), 0u);
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
@@ -17,17 +14,15 @@ TEST(SparseSetNoType, Functionalities) {
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 1u);
|
||||
ASSERT_GE(set.capacity(), 1u);
|
||||
ASSERT_NE(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_TRUE(set.has(42));
|
||||
ASSERT_EQ(set.get(42), 0u);
|
||||
|
||||
ASSERT_EQ(set.destroy(42), 0u);
|
||||
set.destroy(42);
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_GE(set.capacity(), 1u);
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
@@ -38,16 +33,17 @@ TEST(SparseSetNoType, Functionalities) {
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_GE(set.capacity(), 0u);
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
|
||||
(void)entt::SparseSet<unsigned int>{std::move(set)};
|
||||
entt::SparseSet<unsigned int> other;
|
||||
other = std::move(set);
|
||||
}
|
||||
|
||||
TEST(SparseSetNoType, DataBeginEnd) {
|
||||
using SparseSet = entt::SparseSet<unsigned int>;
|
||||
|
||||
SparseSet set;
|
||||
entt::SparseSet<unsigned int> set;
|
||||
|
||||
ASSERT_EQ(set.construct(3), 0u);
|
||||
ASSERT_EQ(set.construct(12), 1u);
|
||||
@@ -64,37 +60,13 @@ TEST(SparseSetNoType, DataBeginEnd) {
|
||||
ASSERT_EQ(*(begin++), 12u);
|
||||
ASSERT_EQ(*(begin++), 3u);
|
||||
ASSERT_EQ(begin, end);
|
||||
|
||||
set.reset();
|
||||
}
|
||||
|
||||
TEST(SparseSetNoType, Swap) {
|
||||
using SparseSet = entt::SparseSet<unsigned int>;
|
||||
|
||||
SparseSet set;
|
||||
|
||||
ASSERT_EQ(set.construct(3), 0u);
|
||||
ASSERT_EQ(set.construct(12), 1u);
|
||||
|
||||
ASSERT_EQ(*(set.data() + 0u), 3u);
|
||||
ASSERT_EQ(*(set.data() + 1u), 12u);
|
||||
|
||||
set.swap(3, 12);
|
||||
|
||||
ASSERT_EQ(*(set.data() + 0u), 12u);
|
||||
ASSERT_EQ(*(set.data() + 1u), 3u);
|
||||
|
||||
set.reset();
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, Functionalities) {
|
||||
using SparseSet = entt::SparseSet<unsigned int, int>;
|
||||
|
||||
SparseSet set;
|
||||
entt::SparseSet<unsigned int, int> set;
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(set.capacity(), 0u);
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
@@ -103,7 +75,6 @@ TEST(SparseSetWithType, Functionalities) {
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 1u);
|
||||
ASSERT_GE(set.capacity(), 1u);
|
||||
ASSERT_NE(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_TRUE(set.has(42));
|
||||
@@ -113,7 +84,6 @@ TEST(SparseSetWithType, Functionalities) {
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_GE(set.capacity(), 1u);
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
@@ -124,16 +94,17 @@ TEST(SparseSetWithType, Functionalities) {
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_GE(set.capacity(), 0u);
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
|
||||
(void)entt::SparseSet<unsigned int>{std::move(set)};
|
||||
entt::SparseSet<unsigned int> other;
|
||||
other = std::move(set);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, RawBeginEnd) {
|
||||
using SparseSet = entt::SparseSet<unsigned int, int>;
|
||||
|
||||
SparseSet set;
|
||||
entt::SparseSet<unsigned int, int> set;
|
||||
|
||||
ASSERT_EQ(set.construct(3, 3), 3);
|
||||
ASSERT_EQ(set.construct(12, 6), 6);
|
||||
@@ -150,33 +121,10 @@ TEST(SparseSetWithType, RawBeginEnd) {
|
||||
ASSERT_EQ(set.get(*(begin++)), 6);
|
||||
ASSERT_EQ(set.get(*(begin++)), 3);
|
||||
ASSERT_EQ(begin, end);
|
||||
|
||||
set.reset();
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, Swap) {
|
||||
using SparseSet = entt::SparseSet<unsigned int, int>;
|
||||
|
||||
SparseSet set;
|
||||
|
||||
ASSERT_EQ(set.construct(3, 3), 3);
|
||||
ASSERT_EQ(set.construct(12, 6), 6);
|
||||
|
||||
ASSERT_EQ(*(set.raw() + 0u), 3);
|
||||
ASSERT_EQ(*(set.raw() + 1u), 6);
|
||||
|
||||
set.swap(3, 12);
|
||||
|
||||
ASSERT_EQ(*(set.raw() + 0u), 6);
|
||||
ASSERT_EQ(*(set.raw() + 1u), 3);
|
||||
|
||||
set.reset();
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, SortOrdered) {
|
||||
using SparseSet = entt::SparseSet<unsigned int, int>;
|
||||
|
||||
SparseSet set;
|
||||
entt::SparseSet<unsigned int, int> set;
|
||||
|
||||
ASSERT_EQ(set.construct(12, 12), 12);
|
||||
ASSERT_EQ(set.construct(42, 9), 9);
|
||||
@@ -184,8 +132,8 @@ TEST(SparseSetWithType, SortOrdered) {
|
||||
ASSERT_EQ(set.construct(3, 3), 3);
|
||||
ASSERT_EQ(set.construct(9, 1), 1);
|
||||
|
||||
set.sort([](auto lhs, auto rhs) {
|
||||
return lhs < rhs;
|
||||
set.sort([&set](auto lhs, auto rhs) {
|
||||
return set.get(lhs) < set.get(rhs);
|
||||
});
|
||||
|
||||
ASSERT_EQ(*(set.raw() + 0u), 12);
|
||||
@@ -203,14 +151,10 @@ TEST(SparseSetWithType, SortOrdered) {
|
||||
ASSERT_EQ(set.get(*(begin++)), 9);
|
||||
ASSERT_EQ(set.get(*(begin++)), 12);
|
||||
ASSERT_EQ(begin, end);
|
||||
|
||||
set.reset();
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, SortReverse) {
|
||||
using SparseSet = entt::SparseSet<unsigned int, int>;
|
||||
|
||||
SparseSet set;
|
||||
entt::SparseSet<unsigned int, int> set;
|
||||
|
||||
ASSERT_EQ(set.construct(12, 1), 1);
|
||||
ASSERT_EQ(set.construct(42, 3), 3);
|
||||
@@ -218,8 +162,8 @@ TEST(SparseSetWithType, SortReverse) {
|
||||
ASSERT_EQ(set.construct(3, 9), 9);
|
||||
ASSERT_EQ(set.construct(9, 12), 12);
|
||||
|
||||
set.sort([](auto lhs, auto rhs) {
|
||||
return lhs < rhs;
|
||||
set.sort([&set](auto lhs, auto rhs) {
|
||||
return set.get(lhs) < set.get(rhs);
|
||||
});
|
||||
|
||||
ASSERT_EQ(*(set.raw() + 0u), 12);
|
||||
@@ -237,14 +181,10 @@ TEST(SparseSetWithType, SortReverse) {
|
||||
ASSERT_EQ(set.get(*(begin++)), 9);
|
||||
ASSERT_EQ(set.get(*(begin++)), 12);
|
||||
ASSERT_EQ(begin, end);
|
||||
|
||||
set.reset();
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, SortUnordered) {
|
||||
using SparseSet = entt::SparseSet<unsigned int, int>;
|
||||
|
||||
SparseSet set;
|
||||
entt::SparseSet<unsigned int, int> set;
|
||||
|
||||
ASSERT_EQ(set.construct(12, 6), 6);
|
||||
ASSERT_EQ(set.construct(42, 3), 3);
|
||||
@@ -252,8 +192,8 @@ TEST(SparseSetWithType, SortUnordered) {
|
||||
ASSERT_EQ(set.construct(3, 9), 9);
|
||||
ASSERT_EQ(set.construct(9, 12), 12);
|
||||
|
||||
set.sort([](auto lhs, auto rhs) {
|
||||
return lhs < rhs;
|
||||
set.sort([&set](auto lhs, auto rhs) {
|
||||
return set.get(lhs) < set.get(rhs);
|
||||
});
|
||||
|
||||
ASSERT_EQ(*(set.raw() + 0u), 12);
|
||||
@@ -271,15 +211,60 @@ TEST(SparseSetWithType, SortUnordered) {
|
||||
ASSERT_EQ(set.get(*(begin++)), 9);
|
||||
ASSERT_EQ(set.get(*(begin++)), 12);
|
||||
ASSERT_EQ(begin, end);
|
||||
}
|
||||
|
||||
set.reset();
|
||||
TEST(SparseSetWithType, RespectDisjoint) {
|
||||
entt::SparseSet<unsigned int, int> lhs;
|
||||
entt::SparseSet<unsigned int, int> rhs;
|
||||
const auto &clhs = lhs;
|
||||
|
||||
ASSERT_EQ(lhs.construct(3, 3), 3);
|
||||
ASSERT_EQ(lhs.construct(12, 6), 6);
|
||||
ASSERT_EQ(lhs.construct(42, 9), 9);
|
||||
|
||||
lhs.respect(rhs);
|
||||
|
||||
ASSERT_EQ(*(clhs.raw() + 0u), 3);
|
||||
ASSERT_EQ(*(clhs.raw() + 1u), 6);
|
||||
ASSERT_EQ(*(clhs.raw() + 2u), 9);
|
||||
|
||||
auto begin = clhs.begin();
|
||||
auto end = clhs.end();
|
||||
|
||||
ASSERT_EQ(clhs.get(*(begin++)), 9);
|
||||
ASSERT_EQ(clhs.get(*(begin++)), 6);
|
||||
ASSERT_EQ(clhs.get(*(begin++)), 3);
|
||||
ASSERT_EQ(begin, end);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, RespectOverlap) {
|
||||
entt::SparseSet<unsigned int, int> lhs;
|
||||
entt::SparseSet<unsigned int, int> rhs;
|
||||
const auto &clhs = lhs;
|
||||
|
||||
ASSERT_EQ(lhs.construct(3, 3), 3);
|
||||
ASSERT_EQ(lhs.construct(12, 6), 6);
|
||||
ASSERT_EQ(lhs.construct(42, 9), 9);
|
||||
ASSERT_EQ(rhs.construct(12, 6), 6);
|
||||
|
||||
lhs.respect(rhs);
|
||||
|
||||
ASSERT_EQ(*(clhs.raw() + 0u), 3);
|
||||
ASSERT_EQ(*(clhs.raw() + 1u), 9);
|
||||
ASSERT_EQ(*(clhs.raw() + 2u), 6);
|
||||
|
||||
auto begin = clhs.begin();
|
||||
auto end = clhs.end();
|
||||
|
||||
ASSERT_EQ(clhs.get(*(begin++)), 6);
|
||||
ASSERT_EQ(clhs.get(*(begin++)), 9);
|
||||
ASSERT_EQ(clhs.get(*(begin++)), 3);
|
||||
ASSERT_EQ(begin, end);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, RespectOrdered) {
|
||||
using SparseSet = entt::SparseSet<unsigned int, int>;
|
||||
|
||||
SparseSet lhs;
|
||||
SparseSet rhs;
|
||||
entt::SparseSet<unsigned int, int> lhs;
|
||||
entt::SparseSet<unsigned int, int> rhs;
|
||||
|
||||
ASSERT_EQ(lhs.construct(1, 0), 0);
|
||||
ASSERT_EQ(lhs.construct(2, 0), 0);
|
||||
@@ -308,16 +293,11 @@ TEST(SparseSetWithType, RespectOrdered) {
|
||||
ASSERT_EQ(*(rhs.data() + 3u), 3u);
|
||||
ASSERT_EQ(*(rhs.data() + 4u), 4u);
|
||||
ASSERT_EQ(*(rhs.data() + 5u), 5u);
|
||||
|
||||
lhs.reset();
|
||||
rhs.reset();
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, RespectReverse) {
|
||||
using SparseSet = entt::SparseSet<unsigned int, int>;
|
||||
|
||||
SparseSet lhs;
|
||||
SparseSet rhs;
|
||||
entt::SparseSet<unsigned int, int> lhs;
|
||||
entt::SparseSet<unsigned int, int> rhs;
|
||||
|
||||
ASSERT_EQ(lhs.construct(1, 0), 0);
|
||||
ASSERT_EQ(lhs.construct(2, 0), 0);
|
||||
@@ -346,16 +326,11 @@ TEST(SparseSetWithType, RespectReverse) {
|
||||
ASSERT_EQ(*(rhs.data() + 3u), 3u);
|
||||
ASSERT_EQ(*(rhs.data() + 4u), 4u);
|
||||
ASSERT_EQ(*(rhs.data() + 5u), 5u);
|
||||
|
||||
lhs.reset();
|
||||
rhs.reset();
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, RespectUnordered) {
|
||||
using SparseSet = entt::SparseSet<unsigned int, int>;
|
||||
|
||||
SparseSet lhs;
|
||||
SparseSet rhs;
|
||||
entt::SparseSet<unsigned int, int> lhs;
|
||||
entt::SparseSet<unsigned int, int> rhs;
|
||||
|
||||
ASSERT_EQ(lhs.construct(1, 0), 0);
|
||||
ASSERT_EQ(lhs.construct(2, 0), 0);
|
||||
@@ -384,7 +359,4 @@ TEST(SparseSetWithType, RespectUnordered) {
|
||||
ASSERT_EQ(*(rhs.data() + 3u), 3u);
|
||||
ASSERT_EQ(*(rhs.data() + 4u), 4u);
|
||||
ASSERT_EQ(*(rhs.data() + 5u), 5u);
|
||||
|
||||
lhs.reset();
|
||||
rhs.reset();
|
||||
}
|
||||
286
test/entt/entity/view.cpp
Normal file
286
test/entt/entity/view.cpp
Normal file
@@ -0,0 +1,286 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/registry.hpp>
|
||||
#include <entt/entity/view.hpp>
|
||||
|
||||
TEST(View, SingleComponent) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
auto e1 = registry.create();
|
||||
auto e2 = registry.create<int, char>();
|
||||
|
||||
ASSERT_NO_THROW(registry.view<char>().begin()++);
|
||||
ASSERT_NO_THROW(++registry.view<char>().begin());
|
||||
|
||||
auto view = registry.view<char>();
|
||||
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
|
||||
registry.assign<char>(e1);
|
||||
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{2});
|
||||
|
||||
view.get(e1) = '1';
|
||||
view.get(e2) = '2';
|
||||
|
||||
for(auto entity: view) {
|
||||
const auto &cview = static_cast<const decltype(view) &>(view);
|
||||
ASSERT_TRUE(cview.get(entity) == '1' || cview.get(entity) == '2');
|
||||
}
|
||||
|
||||
ASSERT_EQ(*(view.data() + 0), e2);
|
||||
ASSERT_EQ(*(view.data() + 1), e1);
|
||||
|
||||
ASSERT_EQ(*(view.raw() + 0), '2');
|
||||
ASSERT_EQ(*(static_cast<const decltype(view) &>(view).raw() + 1), '1');
|
||||
|
||||
registry.remove<char>(e1);
|
||||
registry.remove<char>(e2);
|
||||
|
||||
ASSERT_EQ(view.begin(), view.end());
|
||||
}
|
||||
|
||||
TEST(View, SingleComponentEmpty) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
registry.create<char, double>();
|
||||
registry.create<char>();
|
||||
|
||||
auto view = registry.view<int>();
|
||||
|
||||
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{0});
|
||||
|
||||
for(auto entity: view) {
|
||||
(void)entity;
|
||||
FAIL();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(View, SingleComponentEach) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
registry.create<int, char>();
|
||||
registry.create<int, char>();
|
||||
|
||||
auto view = registry.view<int>();
|
||||
const auto &cview = static_cast<const decltype(view) &>(view);
|
||||
std::size_t cnt = 0;
|
||||
|
||||
view.each([&cnt](auto, int &) { ++cnt; });
|
||||
|
||||
ASSERT_EQ(cnt, std::size_t{2});
|
||||
|
||||
cview.each([&cnt](auto, const int &) { --cnt; });
|
||||
|
||||
ASSERT_EQ(cnt, std::size_t{0});
|
||||
}
|
||||
|
||||
TEST(View, MultipleComponent) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
auto e1 = registry.create<char>();
|
||||
auto e2 = registry.create<int, char>();
|
||||
|
||||
ASSERT_NO_THROW((registry.view<int, char>().begin()++));
|
||||
ASSERT_NO_THROW((++registry.view<int, char>().begin()));
|
||||
|
||||
auto view = registry.view<int, char>();
|
||||
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
|
||||
view.get<char>(e1) = '1';
|
||||
view.get<char>(e2) = '2';
|
||||
|
||||
for(auto entity: view) {
|
||||
const auto &cview = static_cast<const decltype(view) &>(view);
|
||||
ASSERT_TRUE(cview.get<char>(entity) == '2');
|
||||
}
|
||||
|
||||
registry.remove<char>(e1);
|
||||
registry.remove<char>(e2);
|
||||
view.reset();
|
||||
|
||||
ASSERT_EQ(view.begin(), view.end());
|
||||
}
|
||||
|
||||
TEST(View, MultipleComponentEmpty) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
registry.create<double, int, float>();
|
||||
registry.create<char, float>();
|
||||
|
||||
auto view = registry.view<char, int, float>();
|
||||
|
||||
for(auto entity: view) {
|
||||
(void)entity;
|
||||
FAIL();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(View, MultipleComponentEach) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
registry.create<int, char>();
|
||||
registry.create<int, char>();
|
||||
|
||||
auto view = registry.view<int, char>();
|
||||
const auto &cview = static_cast<const decltype(view) &>(view);
|
||||
std::size_t cnt = 0;
|
||||
|
||||
view.each([&cnt](auto, int &, char &) { ++cnt; });
|
||||
|
||||
ASSERT_EQ(cnt, std::size_t{2});
|
||||
|
||||
cview.each([&cnt](auto, const int &, const char &) { --cnt; });
|
||||
|
||||
ASSERT_EQ(cnt, std::size_t{0});
|
||||
}
|
||||
|
||||
TEST(PersistentView, Prepare) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<int, char>();
|
||||
|
||||
auto e1 = registry.create<char>();
|
||||
auto e2 = registry.create<int, char>();
|
||||
|
||||
ASSERT_NO_THROW((registry.persistent<int, char>().begin()++));
|
||||
ASSERT_NO_THROW((++registry.persistent<int, char>().begin()));
|
||||
|
||||
auto view = registry.persistent<int, char>();
|
||||
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
|
||||
registry.assign<int>(e1);
|
||||
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{2});
|
||||
|
||||
registry.remove<int>(e1);
|
||||
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
|
||||
view.get<char>(e1) = '1';
|
||||
view.get<char>(e2) = '2';
|
||||
|
||||
for(auto entity: view) {
|
||||
const auto &cview = static_cast<const decltype(view) &>(view);
|
||||
ASSERT_TRUE(cview.get<char>(entity) == '2');
|
||||
}
|
||||
|
||||
ASSERT_EQ(*(view.data() + 0), e2);
|
||||
|
||||
registry.remove<char>(e1);
|
||||
registry.remove<char>(e2);
|
||||
|
||||
ASSERT_EQ(view.begin(), view.end());
|
||||
}
|
||||
|
||||
TEST(PersistentView, NoPrepare) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
auto e1 = registry.create<char>();
|
||||
auto e2 = registry.create<int, char>();
|
||||
|
||||
ASSERT_NO_THROW((registry.persistent<int, char>().begin()++));
|
||||
ASSERT_NO_THROW((++registry.persistent<int, char>().begin()));
|
||||
|
||||
auto view = registry.persistent<int, char>();
|
||||
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
|
||||
registry.assign<int>(e1);
|
||||
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{2});
|
||||
|
||||
registry.remove<int>(e1);
|
||||
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
|
||||
view.get<char>(e1) = '1';
|
||||
view.get<char>(e2) = '2';
|
||||
|
||||
for(auto entity: view) {
|
||||
const auto &cview = static_cast<const decltype(view) &>(view);
|
||||
ASSERT_TRUE(cview.get<char>(entity) == '2');
|
||||
}
|
||||
|
||||
ASSERT_EQ(*(view.data() + 0), e2);
|
||||
|
||||
registry.remove<char>(e1);
|
||||
registry.remove<char>(e2);
|
||||
|
||||
ASSERT_EQ(view.begin(), view.end());
|
||||
}
|
||||
|
||||
TEST(PersistentView, Empty) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
registry.create<double, int, float>();
|
||||
registry.create<char, float>();
|
||||
|
||||
for(auto entity: registry.persistent<char, int, float>()) {
|
||||
(void)entity;
|
||||
FAIL();
|
||||
}
|
||||
|
||||
for(auto entity: registry.persistent<double, char, int, float>()) {
|
||||
(void)entity;
|
||||
FAIL();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(PersistentView, Each) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<int, char>();
|
||||
|
||||
registry.create<int, char>();
|
||||
registry.create<int, char>();
|
||||
|
||||
auto view = registry.persistent<int, char>();
|
||||
const auto &cview = static_cast<const decltype(view) &>(view);
|
||||
std::size_t cnt = 0;
|
||||
|
||||
view.each([&cnt](auto, int &, char &) { ++cnt; });
|
||||
|
||||
ASSERT_EQ(cnt, std::size_t{2});
|
||||
|
||||
cview.each([&cnt](auto, const int &, const char &) { --cnt; });
|
||||
|
||||
ASSERT_EQ(cnt, std::size_t{0});
|
||||
}
|
||||
|
||||
TEST(PersistentView, Sort) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<int, unsigned int>();
|
||||
|
||||
auto e1 = registry.create();
|
||||
auto e2 = registry.create();
|
||||
auto e3 = registry.create();
|
||||
|
||||
auto uval = 0u;
|
||||
auto ival = 0;
|
||||
|
||||
registry.assign<unsigned int>(e1, uval++);
|
||||
registry.assign<unsigned int>(e2, uval++);
|
||||
registry.assign<unsigned int>(e3, uval++);
|
||||
|
||||
registry.assign<int>(e1, ival++);
|
||||
registry.assign<int>(e2, ival++);
|
||||
registry.assign<int>(e3, ival++);
|
||||
|
||||
auto view = registry.persistent<int, unsigned int>();
|
||||
|
||||
for(auto entity: view) {
|
||||
ASSERT_EQ(view.get<unsigned int>(entity), --uval);
|
||||
ASSERT_EQ(view.get<int>(entity), --ival);
|
||||
}
|
||||
|
||||
registry.sort<unsigned int>(std::less<unsigned int>{});
|
||||
view.sort<unsigned int>();
|
||||
|
||||
for(auto entity: view) {
|
||||
ASSERT_EQ(view.get<unsigned int>(entity), uval++);
|
||||
ASSERT_EQ(view.get<int>(entity), ival++);
|
||||
}
|
||||
}
|
||||
49
test/entt/locator/locator.cpp
Normal file
49
test/entt/locator/locator.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/locator/locator.hpp>
|
||||
|
||||
struct A {};
|
||||
|
||||
struct B {
|
||||
virtual void f(bool) = 0;
|
||||
bool check{false};
|
||||
};
|
||||
|
||||
struct D: B {
|
||||
D(int): B{} {}
|
||||
void f(bool b) override { check = b; }
|
||||
};
|
||||
|
||||
TEST(ServiceLocator, Functionalities) {
|
||||
using entt::ServiceLocator;
|
||||
|
||||
ASSERT_TRUE(ServiceLocator<A>::empty());
|
||||
ASSERT_TRUE(ServiceLocator<B>::empty());
|
||||
|
||||
ServiceLocator<A>::set();
|
||||
|
||||
ASSERT_FALSE(ServiceLocator<A>::empty());
|
||||
ASSERT_TRUE(ServiceLocator<B>::empty());
|
||||
|
||||
ServiceLocator<A>::reset();
|
||||
|
||||
ASSERT_TRUE(ServiceLocator<A>::empty());
|
||||
ASSERT_TRUE(ServiceLocator<B>::empty());
|
||||
|
||||
ServiceLocator<A>::set(std::make_shared<A>());
|
||||
|
||||
ASSERT_FALSE(ServiceLocator<A>::empty());
|
||||
ASSERT_TRUE(ServiceLocator<B>::empty());
|
||||
|
||||
ServiceLocator<B>::set<D>(42);
|
||||
|
||||
ASSERT_FALSE(ServiceLocator<A>::empty());
|
||||
ASSERT_FALSE(ServiceLocator<B>::empty());
|
||||
|
||||
ServiceLocator<B>::get().lock()->f(!ServiceLocator<B>::get().lock()->check);
|
||||
|
||||
ASSERT_TRUE(ServiceLocator<B>::get().lock()->check);
|
||||
|
||||
ServiceLocator<B>::ref().f(!ServiceLocator<B>::get().lock()->check);
|
||||
|
||||
ASSERT_FALSE(ServiceLocator<B>::get().lock()->check);
|
||||
}
|
||||
141
test/entt/signal/bus.cpp
Normal file
141
test/entt/signal/bus.cpp
Normal file
@@ -0,0 +1,141 @@
|
||||
#include <memory>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/signal/bus.hpp>
|
||||
|
||||
struct EventA
|
||||
{
|
||||
EventA(int x, int y): value{x+y} {}
|
||||
int value;
|
||||
};
|
||||
|
||||
struct EventB {};
|
||||
struct EventC {};
|
||||
|
||||
struct MyListener
|
||||
{
|
||||
void receive(const EventA &) { A++; }
|
||||
static void listen(const EventB &) { B++; }
|
||||
void receive(const EventC &) { C++; }
|
||||
void reset() { A = 0; B = 0; C = 0; }
|
||||
int A{0};
|
||||
static int B;
|
||||
int C{0};
|
||||
};
|
||||
|
||||
int MyListener::B = 0;
|
||||
|
||||
template<typename Bus, typename Listener>
|
||||
void testRegUnregEmit(Listener listener) {
|
||||
Bus bus;
|
||||
|
||||
listener->reset();
|
||||
bus.template publish<EventA>(40, 2);
|
||||
bus.template publish<EventB>();
|
||||
bus.template publish<EventC>();
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))0);
|
||||
ASSERT_TRUE(bus.empty());
|
||||
ASSERT_EQ(listener->A, 0);
|
||||
ASSERT_EQ(listener->B, 0);
|
||||
ASSERT_EQ(listener->C, 0);
|
||||
|
||||
bus.reg(listener);
|
||||
bus.template connect<EventB, &MyListener::listen>();
|
||||
|
||||
listener->reset();
|
||||
bus.template publish<EventA>(40, 2);
|
||||
bus.template publish<EventB>();
|
||||
bus.template publish<EventC>();
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))3);
|
||||
ASSERT_FALSE(bus.empty());
|
||||
ASSERT_EQ(listener->A, 1);
|
||||
ASSERT_EQ(listener->B, 1);
|
||||
ASSERT_EQ(listener->C, 1);
|
||||
|
||||
bus.unreg(listener);
|
||||
|
||||
listener->reset();
|
||||
bus.template publish<EventA>(40, 2);
|
||||
bus.template publish<EventB>();
|
||||
bus.template publish<EventC>();
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))1);
|
||||
ASSERT_FALSE(bus.empty());
|
||||
ASSERT_EQ(listener->A, 0);
|
||||
ASSERT_EQ(listener->B, 1);
|
||||
ASSERT_EQ(listener->C, 0);
|
||||
|
||||
bus.template disconnect<EventB, MyListener::listen>();
|
||||
|
||||
listener->reset();
|
||||
bus.template publish<EventA>(40, 2);
|
||||
bus.template publish<EventB>();
|
||||
bus.template publish<EventC>();
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))0);
|
||||
ASSERT_TRUE(bus.empty());
|
||||
ASSERT_EQ(listener->A, 0);
|
||||
ASSERT_EQ(listener->B, 0);
|
||||
ASSERT_EQ(listener->C, 0);
|
||||
}
|
||||
|
||||
TEST(ManagedBus, RegUnregEmit) {
|
||||
using MyManagedBus = entt::ManagedBus<EventA, EventB, EventC>;
|
||||
testRegUnregEmit<MyManagedBus>(std::make_shared<MyListener>());
|
||||
}
|
||||
|
||||
TEST(ManagedBus, ExpiredListeners) {
|
||||
entt::ManagedBus<EventA, EventB, EventC> bus;
|
||||
auto listener = std::make_shared<MyListener>();
|
||||
|
||||
listener->reset();
|
||||
bus.reg(listener);
|
||||
bus.template publish<EventA>(40, 2);
|
||||
bus.template publish<EventB>();
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
|
||||
ASSERT_FALSE(bus.empty());
|
||||
ASSERT_EQ(listener->A, 1);
|
||||
ASSERT_EQ(listener->B, 0);
|
||||
|
||||
listener->reset();
|
||||
listener = nullptr;
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
|
||||
ASSERT_FALSE(bus.empty());
|
||||
|
||||
EXPECT_NO_THROW(bus.template publish<EventA>(40, 2));
|
||||
EXPECT_NO_THROW(bus.template publish<EventC>());
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))0);
|
||||
ASSERT_TRUE(bus.empty());
|
||||
}
|
||||
|
||||
TEST(UnmanagedBus, RegUnregEmit) {
|
||||
using MyUnmanagedBus = entt::UnmanagedBus<EventA, EventB, EventC>;
|
||||
auto ptr = std::make_unique<MyListener>();
|
||||
testRegUnregEmit<MyUnmanagedBus>(ptr.get());
|
||||
}
|
||||
|
||||
TEST(UnmanagedBus, ExpiredListeners) {
|
||||
entt::UnmanagedBus<EventA, EventB, EventC> bus;
|
||||
auto listener = std::make_unique<MyListener>();
|
||||
|
||||
listener->reset();
|
||||
bus.reg(listener.get());
|
||||
bus.template publish<EventA>(40, 2);
|
||||
bus.template publish<EventB>();
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
|
||||
ASSERT_FALSE(bus.empty());
|
||||
ASSERT_EQ(listener->A, 1);
|
||||
ASSERT_EQ(listener->B, 0);
|
||||
|
||||
listener->reset();
|
||||
listener = nullptr;
|
||||
|
||||
// dangling pointer inside ... well, unmanaged means unmanaged!! :-)
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
|
||||
ASSERT_FALSE(bus.empty());
|
||||
}
|
||||
45
test/entt/signal/delegate.cpp
Normal file
45
test/entt/signal/delegate.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/signal/delegate.hpp>
|
||||
|
||||
int f(int i) {
|
||||
return i*i;
|
||||
}
|
||||
|
||||
struct S {
|
||||
int f(int i) {
|
||||
return i+i;
|
||||
}
|
||||
};
|
||||
|
||||
TEST(Delegate, Functionalities) {
|
||||
entt::Delegate<int(int)> ffdel;
|
||||
entt::Delegate<int(int)> mfdel;
|
||||
S test;
|
||||
|
||||
ASSERT_EQ(ffdel(42), int{});
|
||||
ASSERT_EQ(mfdel(42), int{});
|
||||
|
||||
ffdel.connect<&f>();
|
||||
mfdel.connect<S, &S::f>(&test);
|
||||
|
||||
ASSERT_EQ(ffdel(3), 9);
|
||||
ASSERT_EQ(mfdel(3), 6);
|
||||
|
||||
ffdel.reset();
|
||||
mfdel.reset();
|
||||
|
||||
ASSERT_EQ(ffdel(42), int{});
|
||||
ASSERT_EQ(mfdel(42), int{});
|
||||
}
|
||||
|
||||
TEST(Delegate, Comparison) {
|
||||
entt::Delegate<int(int)> delegate;
|
||||
entt::Delegate<int(int)> def;
|
||||
delegate.connect<&f>();
|
||||
|
||||
ASSERT_EQ(def, entt::Delegate<int(int)>{});
|
||||
ASSERT_NE(def, delegate);
|
||||
|
||||
ASSERT_TRUE(def == entt::Delegate<int(int)>{});
|
||||
ASSERT_TRUE (def != delegate);
|
||||
}
|
||||
47
test/entt/signal/dispatcher.cpp
Normal file
47
test/entt/signal/dispatcher.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include <memory>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/signal/dispatcher.hpp>
|
||||
|
||||
struct Event {};
|
||||
|
||||
struct Receiver {
|
||||
void receive(const Event &) { ++cnt; }
|
||||
void reset() { cnt = 0; }
|
||||
std::size_t cnt{0};
|
||||
};
|
||||
|
||||
template<typename Dispatcher, typename Rec>
|
||||
void testDispatcher(Rec receiver) {
|
||||
Dispatcher dispatcher;
|
||||
|
||||
dispatcher.template connect<Event>(receiver);
|
||||
dispatcher.template trigger<Event>();
|
||||
dispatcher.template enqueue<Event>();
|
||||
|
||||
ASSERT_EQ(receiver->cnt, static_cast<decltype(receiver->cnt)>(1));
|
||||
|
||||
dispatcher.update();
|
||||
dispatcher.update();
|
||||
dispatcher.template trigger<Event>();
|
||||
|
||||
ASSERT_EQ(receiver->cnt, static_cast<decltype(receiver->cnt)>(3));
|
||||
|
||||
receiver->reset();
|
||||
|
||||
dispatcher.template disconnect<Event>(receiver);
|
||||
dispatcher.template trigger<Event>();
|
||||
dispatcher.template enqueue<Event>();
|
||||
dispatcher.update();
|
||||
dispatcher.template trigger<Event>();
|
||||
|
||||
ASSERT_EQ(receiver->cnt, static_cast<decltype(receiver->cnt)>(0));
|
||||
}
|
||||
|
||||
TEST(ManagedDispatcher, Basics) {
|
||||
testDispatcher<entt::ManagedDispatcher>(std::make_shared<Receiver>());
|
||||
}
|
||||
|
||||
TEST(UnmanagedDispatcher, Basics) {
|
||||
auto ptr = std::make_unique<Receiver>();
|
||||
testDispatcher<entt::UnmanagedDispatcher>(ptr.get());
|
||||
}
|
||||
117
test/entt/signal/emitter.cpp
Normal file
117
test/entt/signal/emitter.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/signal/emitter.hpp>
|
||||
|
||||
struct TestEmitter: entt::Emitter<TestEmitter> {};
|
||||
|
||||
struct FooEvent { int i; char c; };
|
||||
struct BarEvent {};
|
||||
|
||||
TEST(Emitter, Clear) {
|
||||
TestEmitter emitter;
|
||||
|
||||
ASSERT_TRUE(emitter.empty());
|
||||
|
||||
emitter.on<FooEvent>([](const auto &, const auto &){});
|
||||
|
||||
ASSERT_FALSE(emitter.empty());
|
||||
ASSERT_FALSE(emitter.empty<FooEvent>());
|
||||
ASSERT_TRUE(emitter.empty<BarEvent>());
|
||||
|
||||
emitter.clear<BarEvent>();
|
||||
|
||||
ASSERT_FALSE(emitter.empty());
|
||||
ASSERT_FALSE(emitter.empty<FooEvent>());
|
||||
ASSERT_TRUE(emitter.empty<BarEvent>());
|
||||
|
||||
emitter.clear<FooEvent>();
|
||||
|
||||
ASSERT_TRUE(emitter.empty());
|
||||
ASSERT_TRUE(emitter.empty<FooEvent>());
|
||||
ASSERT_TRUE(emitter.empty<BarEvent>());
|
||||
|
||||
emitter.on<FooEvent>([](const auto &, const auto &){});
|
||||
emitter.on<BarEvent>([](const auto &, const auto &){});
|
||||
|
||||
ASSERT_FALSE(emitter.empty());
|
||||
ASSERT_FALSE(emitter.empty<FooEvent>());
|
||||
ASSERT_FALSE(emitter.empty<BarEvent>());
|
||||
|
||||
emitter.clear();
|
||||
|
||||
ASSERT_TRUE(emitter.empty());
|
||||
ASSERT_TRUE(emitter.empty<FooEvent>());
|
||||
ASSERT_TRUE(emitter.empty<BarEvent>());
|
||||
}
|
||||
|
||||
TEST(Emitter, ClearPublishing) {
|
||||
TestEmitter emitter;
|
||||
bool invoked = false;
|
||||
|
||||
ASSERT_TRUE(emitter.empty());
|
||||
|
||||
emitter.on<BarEvent>([&invoked](const auto &, auto &em){
|
||||
invoked = true;
|
||||
em.clear();
|
||||
});
|
||||
|
||||
emitter.publish<BarEvent>();
|
||||
|
||||
ASSERT_TRUE(emitter.empty());
|
||||
ASSERT_TRUE(invoked);
|
||||
}
|
||||
|
||||
TEST(Emitter, On) {
|
||||
TestEmitter emitter;
|
||||
|
||||
emitter.on<FooEvent>([](const auto &, const auto &){});
|
||||
|
||||
ASSERT_FALSE(emitter.empty());
|
||||
ASSERT_FALSE(emitter.empty<FooEvent>());
|
||||
|
||||
emitter.publish<FooEvent>(0, 'c');
|
||||
|
||||
ASSERT_FALSE(emitter.empty());
|
||||
ASSERT_FALSE(emitter.empty<FooEvent>());
|
||||
}
|
||||
|
||||
TEST(Emitter, Once) {
|
||||
TestEmitter emitter;
|
||||
|
||||
emitter.once<BarEvent>([](const auto &, const auto &){});
|
||||
|
||||
ASSERT_FALSE(emitter.empty());
|
||||
ASSERT_FALSE(emitter.empty<BarEvent>());
|
||||
|
||||
emitter.publish<BarEvent>();
|
||||
|
||||
ASSERT_TRUE(emitter.empty());
|
||||
ASSERT_TRUE(emitter.empty<BarEvent>());
|
||||
}
|
||||
|
||||
TEST(Emitter, OnceAndErase) {
|
||||
TestEmitter emitter;
|
||||
|
||||
auto conn = emitter.once<FooEvent>([](const auto &, const auto &){});
|
||||
|
||||
ASSERT_FALSE(emitter.empty());
|
||||
ASSERT_FALSE(emitter.empty<FooEvent>());
|
||||
|
||||
emitter.erase(conn);
|
||||
|
||||
ASSERT_TRUE(emitter.empty());
|
||||
ASSERT_TRUE(emitter.empty<FooEvent>());
|
||||
}
|
||||
|
||||
TEST(Emitter, OnAndErase) {
|
||||
TestEmitter emitter;
|
||||
|
||||
auto conn = emitter.on<BarEvent>([](const auto &, const auto &){});
|
||||
|
||||
ASSERT_FALSE(emitter.empty());
|
||||
ASSERT_FALSE(emitter.empty<BarEvent>());
|
||||
|
||||
emitter.erase(conn);
|
||||
|
||||
ASSERT_TRUE(emitter.empty());
|
||||
ASSERT_TRUE(emitter.empty<BarEvent>());
|
||||
}
|
||||
210
test/entt/signal/sigh.cpp
Normal file
210
test/entt/signal/sigh.cpp
Normal file
@@ -0,0 +1,210 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/signal/sigh.hpp>
|
||||
|
||||
TEST(SigH, Lifetime) {
|
||||
using signal = entt::SigH<void(void)>;
|
||||
|
||||
ASSERT_NO_THROW(signal{});
|
||||
|
||||
signal src{}, other{};
|
||||
|
||||
ASSERT_NO_THROW(signal{src});
|
||||
ASSERT_NO_THROW(signal{std::move(other)});
|
||||
ASSERT_NO_THROW(src = other);
|
||||
ASSERT_NO_THROW(src = std::move(other));
|
||||
|
||||
ASSERT_NO_THROW(delete new signal{});
|
||||
}
|
||||
|
||||
TEST(SigH, Comparison) {
|
||||
struct S {
|
||||
void f() {}
|
||||
void g() {}
|
||||
};
|
||||
|
||||
entt::SigH<void()> sig1;
|
||||
entt::SigH<void()> sig2;
|
||||
|
||||
S s1;
|
||||
S s2;
|
||||
|
||||
sig1.connect<S, &S::f>(&s1);
|
||||
sig2.connect<S, &S::f>(&s2);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
ASSERT_TRUE(sig1 != sig2);
|
||||
|
||||
sig1.disconnect<S, &S::f>(&s1);
|
||||
sig2.disconnect<S, &S::f>(&s2);
|
||||
|
||||
sig1.connect<S, &S::f>(&s1);
|
||||
sig2.connect<S, &S::g>(&s1);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
ASSERT_TRUE(sig1 != sig2);
|
||||
|
||||
sig1.disconnect<S, &S::f>(&s1);
|
||||
sig2.disconnect<S, &S::g>(&s1);
|
||||
|
||||
ASSERT_TRUE(sig1 == sig2);
|
||||
ASSERT_FALSE(sig1 != sig2);
|
||||
|
||||
sig1.connect<S, &S::f>(&s1);
|
||||
sig1.connect<S, &S::g>(&s1);
|
||||
sig2.connect<S, &S::f>(&s1);
|
||||
sig2.connect<S, &S::g>(&s1);
|
||||
|
||||
ASSERT_TRUE(sig1 == sig2);
|
||||
|
||||
sig1.disconnect<S, &S::f>(&s1);
|
||||
sig1.disconnect<S, &S::g>(&s1);
|
||||
sig2.disconnect<S, &S::f>(&s1);
|
||||
sig2.disconnect<S, &S::g>(&s1);
|
||||
|
||||
sig1.connect<S, &S::f>(&s1);
|
||||
sig1.connect<S, &S::g>(&s1);
|
||||
sig2.connect<S, &S::g>(&s1);
|
||||
sig2.connect<S, &S::f>(&s1);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
}
|
||||
|
||||
struct S {
|
||||
static void f(int &v) { v = 42; }
|
||||
};
|
||||
|
||||
TEST(SigH, Clear) {
|
||||
entt::SigH<void(int &)> sigh;
|
||||
sigh.connect<&S::f>();
|
||||
|
||||
ASSERT_FALSE(sigh.empty());
|
||||
|
||||
sigh.clear();
|
||||
|
||||
ASSERT_TRUE(sigh.empty());
|
||||
}
|
||||
|
||||
TEST(SigH, Functions) {
|
||||
entt::SigH<void(int &)> sigh;
|
||||
int v = 0;
|
||||
|
||||
sigh.connect<&S::f>();
|
||||
sigh.publish(v);
|
||||
|
||||
ASSERT_FALSE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)1, sigh.size());
|
||||
ASSERT_EQ(42, v);
|
||||
|
||||
v = 0;
|
||||
sigh.disconnect<&S::f>();
|
||||
sigh.publish(v);
|
||||
|
||||
ASSERT_TRUE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
|
||||
ASSERT_EQ(0, v);
|
||||
|
||||
sigh.connect<&S::f>();
|
||||
}
|
||||
|
||||
TEST(SigH, Members) {
|
||||
struct S {
|
||||
bool f(int) { b = !b; return true; }
|
||||
bool g(int) { return b; }
|
||||
bool b{false};
|
||||
};
|
||||
|
||||
S s;
|
||||
S *ptr = &s;
|
||||
entt::SigH<bool(int)> sigh;
|
||||
|
||||
sigh.connect<S, &S::f>(ptr);
|
||||
sigh.publish(42);
|
||||
|
||||
ASSERT_TRUE(s.b);
|
||||
ASSERT_FALSE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)1, sigh.size());
|
||||
|
||||
sigh.disconnect<S, &S::f>(ptr);
|
||||
sigh.publish(42);
|
||||
|
||||
ASSERT_TRUE(s.b);
|
||||
ASSERT_TRUE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
|
||||
|
||||
sigh.connect<S, &S::f>(ptr);
|
||||
sigh.connect<S, &S::g>(ptr);
|
||||
|
||||
ASSERT_FALSE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)2, sigh.size());
|
||||
|
||||
sigh.disconnect(ptr);
|
||||
|
||||
ASSERT_TRUE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
|
||||
}
|
||||
|
||||
template<typename Ret>
|
||||
struct TestCollectAll {
|
||||
std::vector<Ret> vec{};
|
||||
static int f() { return 42; }
|
||||
static int g() { return 42; }
|
||||
bool operator()(Ret r) noexcept {
|
||||
vec.push_back(r);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct TestCollectAll<void> {
|
||||
std::vector<int> vec{};
|
||||
static void h() {}
|
||||
bool operator()() noexcept {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Ret>
|
||||
struct TestCollectFirst {
|
||||
std::vector<Ret> vec{};
|
||||
static int f() { return 42; }
|
||||
bool operator()(Ret r) noexcept {
|
||||
vec.push_back(r);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
TEST(SigH, Collector) {
|
||||
entt::SigH<void(), TestCollectAll<void>> sigh_void;
|
||||
|
||||
sigh_void.connect<&TestCollectAll<void>::h>();
|
||||
auto collector_void = sigh_void.collect();
|
||||
|
||||
ASSERT_FALSE(sigh_void.empty());
|
||||
ASSERT_TRUE(collector_void.vec.empty());
|
||||
|
||||
entt::SigH<int(), TestCollectAll<int>> sigh_all;
|
||||
|
||||
sigh_all.connect<&TestCollectAll<int>::f>();
|
||||
sigh_all.connect<&TestCollectAll<int>::f>();
|
||||
sigh_all.connect<&TestCollectAll<int>::g>();
|
||||
auto collector_all = sigh_all.collect();
|
||||
|
||||
ASSERT_FALSE(sigh_all.empty());
|
||||
ASSERT_FALSE(collector_all.vec.empty());
|
||||
ASSERT_EQ((std::vector<int>::size_type)2, collector_all.vec.size());
|
||||
ASSERT_EQ(42, collector_all.vec[0]);
|
||||
ASSERT_EQ(42, collector_all.vec[1]);
|
||||
|
||||
entt::SigH<int(), TestCollectFirst<int>> sigh_first;
|
||||
|
||||
sigh_first.connect<&TestCollectFirst<int>::f>();
|
||||
sigh_first.connect<&TestCollectFirst<int>::f>();
|
||||
auto collector_first = sigh_first.collect();
|
||||
|
||||
ASSERT_FALSE(sigh_first.empty());
|
||||
ASSERT_FALSE(collector_first.vec.empty());
|
||||
ASSERT_EQ((std::vector<int>::size_type)1, collector_first.vec.size());
|
||||
ASSERT_EQ(42, collector_first.vec[0]);
|
||||
}
|
||||
164
test/entt/signal/signal.cpp
Normal file
164
test/entt/signal/signal.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/signal/signal.hpp>
|
||||
|
||||
struct S {
|
||||
static void f(const int &j) { i = j; }
|
||||
void g(const int &j) { i = j; }
|
||||
void h(const int &) {}
|
||||
static int i;
|
||||
};
|
||||
|
||||
int S::i = 0;
|
||||
|
||||
TEST(Signal, Lifetime) {
|
||||
using signal = entt::Signal<void(void)>;
|
||||
|
||||
ASSERT_NO_THROW(signal{});
|
||||
|
||||
signal src{}, other{};
|
||||
|
||||
ASSERT_NO_THROW(signal{src});
|
||||
ASSERT_NO_THROW(signal{std::move(other)});
|
||||
ASSERT_NO_THROW(src = other);
|
||||
ASSERT_NO_THROW(src = std::move(other));
|
||||
|
||||
ASSERT_NO_THROW(delete new signal{});
|
||||
}
|
||||
|
||||
TEST(Signal, Comparison) {
|
||||
struct S {
|
||||
void f() {}
|
||||
void g() {}
|
||||
};
|
||||
|
||||
entt::Signal<void()> sig1;
|
||||
entt::Signal<void()> sig2;
|
||||
|
||||
auto s1 = std::make_shared<S>();
|
||||
auto s2 = std::make_shared<S>();
|
||||
|
||||
sig1.connect<S, &S::f>(s1);
|
||||
sig2.connect<S, &S::f>(s2);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
ASSERT_TRUE(sig1 != sig2);
|
||||
|
||||
sig1.disconnect<S, &S::f>(s1);
|
||||
sig2.disconnect<S, &S::f>(s2);
|
||||
|
||||
sig1.connect<S, &S::f>(s1);
|
||||
sig2.connect<S, &S::g>(s1);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
ASSERT_TRUE(sig1 != sig2);
|
||||
|
||||
sig1.disconnect<S, &S::f>(s1);
|
||||
sig2.disconnect<S, &S::g>(s1);
|
||||
|
||||
ASSERT_TRUE(sig1 == sig2);
|
||||
ASSERT_FALSE(sig1 != sig2);
|
||||
|
||||
sig1.connect<S, &S::f>(s1);
|
||||
sig1.connect<S, &S::g>(s1);
|
||||
sig2.connect<S, &S::f>(s1);
|
||||
sig2.connect<S, &S::g>(s1);
|
||||
|
||||
ASSERT_TRUE(sig1 == sig2);
|
||||
|
||||
sig1.disconnect<S, &S::f>(s1);
|
||||
sig1.disconnect<S, &S::g>(s1);
|
||||
sig2.disconnect<S, &S::f>(s1);
|
||||
sig2.disconnect<S, &S::g>(s1);
|
||||
|
||||
sig1.connect<S, &S::f>(s1);
|
||||
sig1.connect<S, &S::g>(s1);
|
||||
sig2.connect<S, &S::g>(s1);
|
||||
sig2.connect<S, &S::f>(s1);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
}
|
||||
|
||||
TEST(Signal, Clear) {
|
||||
entt::Signal<void(const int &)> signal;
|
||||
signal.connect<&S::f>();
|
||||
|
||||
ASSERT_FALSE(signal.empty());
|
||||
|
||||
signal.clear();
|
||||
|
||||
ASSERT_TRUE(signal.empty());
|
||||
}
|
||||
|
||||
TEST(Signal, Functions) {
|
||||
entt::Signal<void(const int &)> signal;
|
||||
auto val = S::i + 1;
|
||||
|
||||
signal.connect<&S::f>();
|
||||
signal.publish(val);
|
||||
|
||||
ASSERT_FALSE(signal.empty());
|
||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{1}, signal.size());
|
||||
ASSERT_EQ(S::i, val);
|
||||
|
||||
signal.disconnect<&S::f>();
|
||||
signal.publish(val+1);
|
||||
|
||||
ASSERT_TRUE(signal.empty());
|
||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
|
||||
ASSERT_EQ(S::i, val);
|
||||
}
|
||||
|
||||
TEST(Signal, Members) {
|
||||
entt::Signal<void(const int &)> signal;
|
||||
auto ptr = std::make_shared<S>();
|
||||
auto val = S::i + 1;
|
||||
|
||||
signal.connect<S, &S::g>(ptr);
|
||||
signal.publish(val);
|
||||
|
||||
ASSERT_FALSE(signal.empty());
|
||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{1}, signal.size());
|
||||
ASSERT_EQ(S::i, val);
|
||||
|
||||
signal.disconnect<S, &S::g>(ptr);
|
||||
signal.publish(val+1);
|
||||
|
||||
ASSERT_TRUE(signal.empty());
|
||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
|
||||
ASSERT_EQ(S::i, val);
|
||||
|
||||
++val;
|
||||
|
||||
signal.connect<S, &S::g>(ptr);
|
||||
signal.connect<S, &S::h>(ptr);
|
||||
signal.publish(val);
|
||||
|
||||
ASSERT_FALSE(signal.empty());
|
||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{2}, signal.size());
|
||||
ASSERT_EQ(S::i, val);
|
||||
|
||||
signal.disconnect(ptr);
|
||||
signal.publish(val+1);
|
||||
|
||||
ASSERT_TRUE(signal.empty());
|
||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
|
||||
ASSERT_EQ(S::i, val);
|
||||
}
|
||||
|
||||
TEST(Signal, Cleanup) {
|
||||
entt::Signal<void(const int &)> signal;
|
||||
auto ptr = std::make_shared<S>();
|
||||
signal.connect<S, &S::g>(ptr);
|
||||
auto val = S::i;
|
||||
ptr = nullptr;
|
||||
|
||||
ASSERT_FALSE(signal.empty());
|
||||
ASSERT_EQ(S::i, val);
|
||||
|
||||
signal.publish(val);
|
||||
|
||||
ASSERT_TRUE(signal.empty());
|
||||
ASSERT_EQ(S::i, val);
|
||||
}
|
||||
1
test/odr.cpp
Normal file
1
test/odr.cpp
Normal file
@@ -0,0 +1 @@
|
||||
#include <entt/entt.hpp>
|
||||
@@ -1,357 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <registry.hpp>
|
||||
#include <functional>
|
||||
|
||||
TEST(DefaultRegistry, Functionalities) {
|
||||
using registry_type = entt::DefaultRegistry<int, char>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
ASSERT_EQ(registry.size(), registry_type::size_type{0});
|
||||
ASSERT_EQ(registry.capacity(), registry_type::size_type{0});
|
||||
ASSERT_TRUE(registry.empty());
|
||||
|
||||
ASSERT_EQ(registry.size<int>(), registry_type::size_type{0});
|
||||
ASSERT_EQ(registry.size<char>(), registry_type::size_type{0});
|
||||
ASSERT_EQ(registry.capacity<int>(), registry_type::size_type{0});
|
||||
ASSERT_EQ(registry.capacity<char>(), registry_type::size_type{0});
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
ASSERT_TRUE(registry.empty<char>());
|
||||
|
||||
registry_type::entity_type e1 = registry.create();
|
||||
registry_type::entity_type e2 = registry.create<int, char>();
|
||||
|
||||
ASSERT_EQ(registry.size<int>(), registry_type::size_type{1});
|
||||
ASSERT_EQ(registry.size<char>(), registry_type::size_type{1});
|
||||
ASSERT_GE(registry.capacity<int>(), registry_type::size_type{1});
|
||||
ASSERT_GE(registry.capacity<char>(), registry_type::size_type{1});
|
||||
ASSERT_FALSE(registry.empty<int>());
|
||||
ASSERT_FALSE(registry.empty<char>());
|
||||
|
||||
ASSERT_NE(e1, e2);
|
||||
|
||||
ASSERT_FALSE(registry.has<int>(e1));
|
||||
ASSERT_TRUE(registry.has<int>(e2));
|
||||
ASSERT_FALSE(registry.has<char>(e1));
|
||||
ASSERT_TRUE(registry.has<char>(e2));
|
||||
ASSERT_TRUE((registry.has<int, char>(e2)));
|
||||
ASSERT_FALSE((registry.has<int, char>(e1)));
|
||||
|
||||
ASSERT_EQ(registry.assign<int>(e1, 42), 42);
|
||||
ASSERT_EQ(registry.assign<char>(e1, 'c'), 'c');
|
||||
ASSERT_NO_THROW(registry.remove<int>(e2));
|
||||
ASSERT_NO_THROW(registry.remove<char>(e2));
|
||||
|
||||
ASSERT_TRUE(registry.has<int>(e1));
|
||||
ASSERT_FALSE(registry.has<int>(e2));
|
||||
ASSERT_TRUE(registry.has<char>(e1));
|
||||
ASSERT_FALSE(registry.has<char>(e2));
|
||||
ASSERT_TRUE((registry.has<int, char>(e1)));
|
||||
ASSERT_FALSE((registry.has<int, char>(e2)));
|
||||
|
||||
registry_type::entity_type e3 = registry.clone(e1);
|
||||
|
||||
ASSERT_TRUE(registry.has<int>(e3));
|
||||
ASSERT_TRUE(registry.has<char>(e3));
|
||||
ASSERT_EQ(registry.get<int>(e1), 42);
|
||||
ASSERT_EQ(registry.get<char>(e1), 'c');
|
||||
ASSERT_EQ(registry.get<int>(e1), registry.get<int>(e3));
|
||||
ASSERT_EQ(registry.get<char>(e1), registry.get<char>(e3));
|
||||
ASSERT_NE(®istry.get<int>(e1), ®istry.get<int>(e3));
|
||||
ASSERT_NE(®istry.get<char>(e1), ®istry.get<char>(e3));
|
||||
|
||||
ASSERT_NO_THROW(registry.copy(e2, e1));
|
||||
ASSERT_TRUE(registry.has<int>(e2));
|
||||
ASSERT_TRUE(registry.has<char>(e2));
|
||||
ASSERT_EQ(registry.get<int>(e1), 42);
|
||||
ASSERT_EQ(registry.get<char>(e1), 'c');
|
||||
ASSERT_EQ(registry.get<int>(e1), registry.get<int>(e2));
|
||||
ASSERT_EQ(registry.get<char>(e1), registry.get<char>(e2));
|
||||
ASSERT_NE(®istry.get<int>(e1), ®istry.get<int>(e2));
|
||||
ASSERT_NE(®istry.get<char>(e1), ®istry.get<char>(e2));
|
||||
|
||||
ASSERT_NO_THROW(registry.replace<int>(e1, 0));
|
||||
ASSERT_EQ(registry.get<int>(e1), 0);
|
||||
ASSERT_NO_THROW(registry.copy<int>(e2, e1));
|
||||
ASSERT_EQ(registry.get<int>(e2), 0);
|
||||
ASSERT_NE(®istry.get<int>(e1), ®istry.get<int>(e2));
|
||||
|
||||
ASSERT_NO_THROW(registry.remove<int>(e2));
|
||||
ASSERT_NO_THROW(registry.accomodate<int>(e1, 1));
|
||||
ASSERT_NO_THROW(registry.accomodate<int>(e2, 1));
|
||||
ASSERT_EQ(static_cast<const registry_type &>(registry).get<int>(e1), 1);
|
||||
ASSERT_EQ(static_cast<const registry_type &>(registry).get<int>(e2), 1);
|
||||
|
||||
ASSERT_EQ(registry.size(), registry_type::size_type{3});
|
||||
ASSERT_EQ(registry.capacity(), registry_type::size_type{3});
|
||||
ASSERT_FALSE(registry.empty());
|
||||
|
||||
ASSERT_NO_THROW(registry.destroy(e3));
|
||||
|
||||
ASSERT_TRUE(registry.valid(e1));
|
||||
ASSERT_TRUE(registry.valid(e2));
|
||||
ASSERT_FALSE(registry.valid(e3));
|
||||
|
||||
ASSERT_EQ(registry.size(), registry_type::size_type{2});
|
||||
ASSERT_EQ(registry.capacity(), registry_type::size_type{3});
|
||||
ASSERT_FALSE(registry.empty());
|
||||
|
||||
ASSERT_NO_THROW(registry.reset());
|
||||
|
||||
ASSERT_EQ(registry.size(), registry_type::size_type{0});
|
||||
ASSERT_EQ(registry.capacity(), registry_type::size_type{0});
|
||||
ASSERT_TRUE(registry.empty());
|
||||
|
||||
registry.create<int, char>();
|
||||
|
||||
ASSERT_EQ(registry.size<int>(), registry_type::size_type{1});
|
||||
ASSERT_EQ(registry.size<char>(), registry_type::size_type{1});
|
||||
ASSERT_GE(registry.capacity<int>(), registry_type::size_type{1});
|
||||
ASSERT_GE(registry.capacity<char>(), registry_type::size_type{1});
|
||||
ASSERT_FALSE(registry.empty<int>());
|
||||
ASSERT_FALSE(registry.empty<char>());
|
||||
|
||||
ASSERT_NO_THROW(registry.reset<int>());
|
||||
|
||||
ASSERT_EQ(registry.size<int>(), registry_type::size_type{0});
|
||||
ASSERT_EQ(registry.size<char>(), registry_type::size_type{1});
|
||||
ASSERT_GE(registry.capacity<int>(), registry_type::size_type{0});
|
||||
ASSERT_GE(registry.capacity<char>(), registry_type::size_type{1});
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
ASSERT_FALSE(registry.empty<char>());
|
||||
|
||||
ASSERT_NO_THROW(registry.reset());
|
||||
|
||||
ASSERT_EQ(registry.size<int>(), registry_type::size_type{0});
|
||||
ASSERT_EQ(registry.size<char>(), registry_type::size_type{0});
|
||||
ASSERT_GE(registry.capacity<int>(), registry_type::size_type{0});
|
||||
ASSERT_GE(registry.capacity<char>(), registry_type::size_type{1});
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
ASSERT_TRUE(registry.empty<char>());
|
||||
|
||||
e1 = registry.create<int>();
|
||||
e2 = registry.create();
|
||||
|
||||
ASSERT_NO_THROW(registry.reset<int>(e1));
|
||||
ASSERT_NO_THROW(registry.reset<int>(e2));
|
||||
|
||||
ASSERT_EQ(registry.size<int>(), registry_type::size_type{0});
|
||||
ASSERT_EQ(registry.size<char>(), registry_type::size_type{0});
|
||||
ASSERT_GE(registry.capacity<int>(), registry_type::size_type{0});
|
||||
ASSERT_GE(registry.capacity<char>(), registry_type::size_type{0});
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, Copy) {
|
||||
using registry_type = entt::DefaultRegistry<int, char, double>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
registry_type::entity_type e1 = registry.create<int, char>();
|
||||
registry_type::entity_type e2 = registry.create<int, double>();
|
||||
|
||||
ASSERT_TRUE(registry.has<int>(e1));
|
||||
ASSERT_TRUE(registry.has<char>(e1));
|
||||
ASSERT_FALSE(registry.has<double>(e1));
|
||||
|
||||
ASSERT_TRUE(registry.has<int>(e2));
|
||||
ASSERT_FALSE(registry.has<char>(e2));
|
||||
ASSERT_TRUE(registry.has<double>(e2));
|
||||
|
||||
ASSERT_NO_THROW(registry.copy(e2, e1));
|
||||
|
||||
ASSERT_TRUE(registry.has<int>(e1));
|
||||
ASSERT_TRUE(registry.has<char>(e1));
|
||||
ASSERT_FALSE(registry.has<double>(e1));
|
||||
|
||||
ASSERT_TRUE(registry.has<int>(e2));
|
||||
ASSERT_TRUE(registry.has<char>(e2));
|
||||
ASSERT_FALSE(registry.has<double>(e2));
|
||||
|
||||
ASSERT_FALSE(registry.empty<int>());
|
||||
ASSERT_FALSE(registry.empty<char>());
|
||||
ASSERT_TRUE(registry.empty<double>());
|
||||
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, Swap) {
|
||||
using registry_type = entt::DefaultRegistry<int, char>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
registry_type::entity_type e1 = registry.create<int, char>();
|
||||
registry_type::entity_type e2 = registry.create<int, char>();
|
||||
|
||||
registry.get<int>(e1) = 0;
|
||||
registry.get<char>(e1) = 'a';
|
||||
registry.get<int>(e2) = 1;
|
||||
registry.get<char>(e2) = 'b';
|
||||
|
||||
registry.swap<int>(e1, e2);
|
||||
|
||||
ASSERT_EQ(registry.get<int>(e1), 1);
|
||||
ASSERT_EQ(registry.get<char>(e1), 'a');
|
||||
ASSERT_EQ(registry.get<int>(e2), 0);
|
||||
ASSERT_EQ(registry.get<char>(e2), 'b');
|
||||
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, SortSingle) {
|
||||
using registry_type = entt::DefaultRegistry<int>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
registry_type::entity_type e1 = registry.create();
|
||||
registry_type::entity_type e2 = registry.create();
|
||||
registry_type::entity_type e3 = registry.create();
|
||||
|
||||
auto val = 0;
|
||||
|
||||
registry.assign<int>(e1, val++);
|
||||
registry.assign<int>(e2, val++);
|
||||
registry.assign<int>(e3, val++);
|
||||
|
||||
for(auto entity: registry.view<int>()) {
|
||||
ASSERT_EQ(registry.get<int>(entity), --val);
|
||||
}
|
||||
|
||||
registry.sort<int>(std::less<int>{});
|
||||
|
||||
for(auto entity: registry.view<int>()) {
|
||||
ASSERT_EQ(registry.get<int>(entity), val++);
|
||||
}
|
||||
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, SortMulti) {
|
||||
using registry_type = entt::DefaultRegistry<int, unsigned int>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
registry_type::entity_type e1 = registry.create();
|
||||
registry_type::entity_type e2 = registry.create();
|
||||
registry_type::entity_type e3 = registry.create();
|
||||
|
||||
auto uval = 0u;
|
||||
auto ival = 0;
|
||||
|
||||
registry.assign<unsigned int>(e1, uval++);
|
||||
registry.assign<unsigned int>(e2, uval++);
|
||||
registry.assign<unsigned int>(e3, uval++);
|
||||
|
||||
registry.assign<int>(e1, ival++);
|
||||
registry.assign<int>(e2, ival++);
|
||||
registry.assign<int>(e3, ival++);
|
||||
|
||||
for(auto entity: registry.view<unsigned int>()) {
|
||||
ASSERT_EQ(registry.get<unsigned int>(entity), --uval);
|
||||
}
|
||||
|
||||
for(auto entity: registry.view<int>()) {
|
||||
ASSERT_EQ(registry.get<int>(entity), --ival);
|
||||
}
|
||||
|
||||
registry.sort<unsigned int>(std::less<unsigned int>{});
|
||||
registry.sort<int, unsigned int>();
|
||||
|
||||
for(auto entity: registry.view<unsigned int>()) {
|
||||
ASSERT_EQ(registry.get<unsigned int>(entity), uval++);
|
||||
}
|
||||
|
||||
for(auto entity: registry.view<int>()) {
|
||||
ASSERT_EQ(registry.get<int>(entity), ival++);
|
||||
}
|
||||
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, ViewSingleComponent) {
|
||||
using registry_type = entt::DefaultRegistry<int, char>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
registry_type::entity_type e1 = registry.create();
|
||||
registry_type::entity_type e2 = registry.create<int, char>();
|
||||
|
||||
ASSERT_NO_THROW(registry.view<char>().begin()++);
|
||||
ASSERT_NO_THROW(++registry.view<char>().begin());
|
||||
|
||||
auto view = registry.view<char>();
|
||||
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
|
||||
registry.assign<char>(e1);
|
||||
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{2});
|
||||
|
||||
registry.remove<char>(e1);
|
||||
registry.remove<char>(e2);
|
||||
|
||||
ASSERT_EQ(view.begin(), view.end());
|
||||
ASSERT_NO_THROW(registry.reset());
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, ViewMultipleComponent) {
|
||||
using registry_type = entt::DefaultRegistry<int, char>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
registry_type::entity_type e1 = registry.create<char>();
|
||||
registry_type::entity_type e2 = registry.create<int, char>();
|
||||
|
||||
ASSERT_NO_THROW((registry.view<int, char>().begin()++));
|
||||
ASSERT_NO_THROW((++registry.view<int, char>().begin()));
|
||||
|
||||
auto view = registry.view<int, char>();
|
||||
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
|
||||
registry.remove<char>(e1);
|
||||
registry.remove<char>(e2);
|
||||
view.reset();
|
||||
|
||||
ASSERT_EQ(view.begin(), view.end());
|
||||
ASSERT_NO_THROW(registry.reset());
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, ViewSingleComponentEmpty) {
|
||||
using registry_type = entt::DefaultRegistry<char, int, double>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
registry.create<char, double>();
|
||||
registry.create<char>();
|
||||
|
||||
auto view = registry.view<int>();
|
||||
|
||||
ASSERT_EQ(view.size(), registry_type::size_type{0});
|
||||
|
||||
for(auto entity: view) {
|
||||
(void)entity;
|
||||
FAIL();
|
||||
}
|
||||
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, ViewMultipleComponentEmpty) {
|
||||
using registry_type = entt::DefaultRegistry<char, int, float, double>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
registry.create<double, int, float>();
|
||||
registry.create<char, float>();
|
||||
|
||||
auto view = registry.view<char, int, float>();
|
||||
|
||||
for(auto entity: view) {
|
||||
(void)entity;
|
||||
FAIL();
|
||||
}
|
||||
|
||||
registry.reset();
|
||||
}
|
||||
Reference in New Issue
Block a user