Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d417984ff3 | ||
|
|
d38b3e641b | ||
|
|
28ce491dd5 | ||
|
|
c260d72125 | ||
|
|
d1d1b3156d | ||
|
|
472064b751 | ||
|
|
95ab9a0b70 | ||
|
|
4b03f6a039 | ||
|
|
c3460727fa | ||
|
|
2cc1850212 | ||
|
|
2d7443acaf | ||
|
|
13d0b0940c | ||
|
|
c101797924 | ||
|
|
83b55f8e3f | ||
|
|
b3b6362cd9 | ||
|
|
fc9af32d5f | ||
|
|
4cd1025011 | ||
|
|
5233fe8abc | ||
|
|
041e31ea78 | ||
|
|
7a3e881099 | ||
|
|
631bf42f84 | ||
|
|
1f704a7019 | ||
|
|
d295c88474 | ||
|
|
1dd9da4dff | ||
|
|
f2eb0c8427 | ||
|
|
c8ba11faf8 | ||
|
|
a2e243d992 | ||
|
|
c588fff5ca | ||
|
|
87f9599fea | ||
|
|
0459599b1d | ||
|
|
9447b1a696 | ||
|
|
0ccb7443c2 | ||
|
|
02cf27091f | ||
|
|
fdfbd04503 | ||
|
|
866c18200a | ||
|
|
c1cada49d4 | ||
|
|
7bf550a75f | ||
|
|
9c540c03aa | ||
|
|
b3df46db19 | ||
|
|
7ca615a1c1 | ||
|
|
c83db557a6 | ||
|
|
d54594f11d | ||
|
|
434e38608f | ||
|
|
871f090ca0 | ||
|
|
d1d235e025 | ||
|
|
e822a5fd53 | ||
|
|
7b82a4ae50 | ||
|
|
c532e9f2eb | ||
|
|
3fd034816e | ||
|
|
bb4b868c79 | ||
|
|
3b3da11a36 | ||
|
|
f2cbb5306b | ||
|
|
94ede1b324 | ||
|
|
0367248338 | ||
|
|
936db30e58 | ||
|
|
4822f0dd11 | ||
|
|
456d220829 | ||
|
|
b459ba6ea7 | ||
|
|
a19ef9bd16 | ||
|
|
59cec88a28 | ||
|
|
3ebc75af80 | ||
|
|
4dce474e03 | ||
|
|
31a18da578 | ||
|
|
8c499850fc | ||
|
|
6b6998a247 | ||
|
|
a6cb0fc856 | ||
|
|
e36b93e87b | ||
|
|
1e3723b8bb | ||
|
|
412372289e | ||
|
|
96f7e66073 | ||
|
|
6040f8f263 | ||
|
|
9761b6e14a | ||
|
|
cb49910ed2 | ||
|
|
62bd742673 | ||
|
|
42d0a3d734 | ||
|
|
f0f8681455 | ||
|
|
c801afddcb | ||
|
|
20e0e1333e | ||
|
|
a6b373fec4 | ||
|
|
41c77720bb | ||
|
|
92e6340120 | ||
|
|
1221f63cbd | ||
|
|
0f24418891 | ||
|
|
f477c0ab87 | ||
|
|
9358691901 | ||
|
|
cd343ba598 | ||
|
|
50069d3743 | ||
|
|
1e03f27f23 | ||
|
|
36bb55a9ce | ||
|
|
451e4050db | ||
|
|
367fd3e87f | ||
|
|
a67a2e12fd | ||
|
|
292978daf0 | ||
|
|
85a4a76a14 | ||
|
|
9d0ab7ed70 | ||
|
|
3d5b6a5e0b | ||
|
|
ab20372093 | ||
|
|
ab887f30e4 | ||
|
|
6cb6a8c25f | ||
|
|
9d1d2aca0a | ||
|
|
75cb2cd1f7 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1 @@
|
||||
# QtCreator
|
||||
*.user
|
||||
|
||||
@@ -16,7 +16,7 @@ endif()
|
||||
# Project configuration
|
||||
#
|
||||
|
||||
project(entt VERSION 2.2.0)
|
||||
project(entt VERSION 2.5.0)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
@@ -29,7 +29,7 @@ set(PROJECT_AUTHOR_EMAIL "michele.caini@gmail.com")
|
||||
|
||||
message("*")
|
||||
message("* ${PROJECT_NAME} v${PROJECT_VERSION} (${CMAKE_BUILD_TYPE})")
|
||||
message("* Copyright (c) 2017 ${PROJECT_AUTHOR} <${PROJECT_AUTHOR_EMAIL}>")
|
||||
message("* Copyright (c) 2018 ${PROJECT_AUTHOR} <${PROJECT_AUTHOR_EMAIL}>")
|
||||
message("*")
|
||||
|
||||
#
|
||||
@@ -37,13 +37,28 @@ message("*")
|
||||
#
|
||||
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
if(NOT MSVC)
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
|
||||
include(CheckCXXSourceCompiles)
|
||||
|
||||
set(OLD_CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
||||
|
||||
check_cxx_source_compiles("
|
||||
#include<type_traits>
|
||||
int main() { return std::is_same<int, int>::value ? 0 : 1; }
|
||||
" HAS_LIBCPP)
|
||||
|
||||
if(NOT HAS_LIBCPP)
|
||||
set(CMAKE_CXX_FLAGS "${OLD_CMAKE_CXX_FLAGS}")
|
||||
endif()
|
||||
|
||||
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")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
# it seems that -O3 ruins the performance when using clang ...
|
||||
@@ -55,14 +70,10 @@ if(NOT MSVC)
|
||||
endif()
|
||||
|
||||
#
|
||||
# CMake configuration
|
||||
# Include EnTT
|
||||
#
|
||||
|
||||
set(PROJECT_CMAKE_IN ${entt_SOURCE_DIR}/cmake/in)
|
||||
set(PROJECT_DEPS_DIR ${entt_SOURCE_DIR}/deps)
|
||||
set(PROJECT_SRC_DIR ${entt_SOURCE_DIR}/src)
|
||||
|
||||
set(PROJECT_RUNTIME_OUTPUT_DIRECTORY bin)
|
||||
include_directories(${entt_SOURCE_DIR}/src)
|
||||
|
||||
#
|
||||
# Tests
|
||||
@@ -74,9 +85,13 @@ if(BUILD_TESTING)
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
option(BUILD_BENCHMARK "Build benchmark." OFF)
|
||||
option(BUILD_MOD "Build mod example." OFF)
|
||||
option(BUILD_SNAPSHOT "Build snapshot example." OFF)
|
||||
|
||||
# gtest, gtest_main, gmock and gmock_main targets are available from now on
|
||||
set(GOOGLETEST_DEPS_DIR ${PROJECT_DEPS_DIR}/googletest)
|
||||
configure_file(${PROJECT_CMAKE_IN}/googletest.in ${GOOGLETEST_DEPS_DIR}/CMakeLists.txt)
|
||||
set(GOOGLETEST_DEPS_DIR ${entt_SOURCE_DIR}/deps/googletest)
|
||||
configure_file(${entt_SOURCE_DIR}/cmake/in/googletest.in ${GOOGLETEST_DEPS_DIR}/CMakeLists.txt)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY ${GOOGLETEST_DEPS_DIR})
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} --build . WORKING_DIRECTORY ${GOOGLETEST_DEPS_DIR})
|
||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||
@@ -95,3 +110,18 @@ find_package(Doxygen 1.8)
|
||||
if(DOXYGEN_FOUND)
|
||||
add_subdirectory(docs)
|
||||
endif()
|
||||
|
||||
#
|
||||
# AOB
|
||||
#
|
||||
|
||||
add_custom_target(
|
||||
entt_aob
|
||||
SOURCES
|
||||
appveyor.yml
|
||||
AUTHORS
|
||||
LICENSE
|
||||
README.md
|
||||
TODO
|
||||
.travis.yml
|
||||
)
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Michele Caini
|
||||
Copyright (c) 2018 Michele Caini
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
9
TODO
Normal file
9
TODO
Normal file
@@ -0,0 +1,9 @@
|
||||
* custom allocators and EnTT allocator-aware in general (long term feature, I don't actually need it at the moment) - see #22
|
||||
* to analyze, long term feature: systems organizer based on dependency graphs for implicit parallelism (I don't want to think anymore in future :-))
|
||||
* scene management (I prefer the concept of spaces, that is a kind of scene anyway)
|
||||
* blueprint registry - kind of factory to create entitites template for initialization (get rid of the extra versions of Registry::create)
|
||||
* review doc: separate it in multiple md/dox files, reduce the readme to a minimum and provide users with links to the online documentation on gh-pages
|
||||
* debugging tools (#60): the issue online already contains interesting tips on this, look at it
|
||||
* signals on component creation/destruction: crtp + internal detection, probably it works - test it!!
|
||||
* define a macro for the noexcept policy, so as to provide users with an easy way to disable exception handling
|
||||
* AOB
|
||||
@@ -14,7 +14,7 @@ configuration:
|
||||
|
||||
before_build:
|
||||
- cd %BUILD_DIR%
|
||||
- cmake .. -G"Visual Studio 15 2017"
|
||||
- cmake .. -DCMAKE_CXX_FLAGS=/D_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING -G"Visual Studio 15 2017"
|
||||
|
||||
build:
|
||||
parallel: true
|
||||
|
||||
19
cmake/in/cereal.in
Normal file
19
cmake/in/cereal.in
Normal file
@@ -0,0 +1,19 @@
|
||||
project(cereal-download NONE)
|
||||
cmake_minimum_required(VERSION 3.2)
|
||||
|
||||
include(ExternalProject)
|
||||
|
||||
ExternalProject_Add(
|
||||
cereal
|
||||
GIT_REPOSITORY https://github.com/USCiLab/cereal.git
|
||||
GIT_TAG v1.2.2
|
||||
DOWNLOAD_DIR ${CEREAL_DEPS_DIR}
|
||||
TMP_DIR ${CEREAL_DEPS_DIR}/tmp
|
||||
STAMP_DIR ${CEREAL_DEPS_DIR}/stamp
|
||||
SOURCE_DIR ${CEREAL_DEPS_DIR}/src
|
||||
BINARY_DIR ${CEREAL_DEPS_DIR}/build
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
TEST_COMMAND ""
|
||||
)
|
||||
19
cmake/in/duktape.in
Normal file
19
cmake/in/duktape.in
Normal file
@@ -0,0 +1,19 @@
|
||||
project(duktape-download NONE)
|
||||
cmake_minimum_required(VERSION 3.2)
|
||||
|
||||
include(ExternalProject)
|
||||
|
||||
ExternalProject_Add(
|
||||
duktape
|
||||
GIT_REPOSITORY https://github.com/svaarala/duktape-releases.git
|
||||
GIT_TAG v2.2.0
|
||||
DOWNLOAD_DIR ${DUKTAPE_DEPS_DIR}
|
||||
TMP_DIR ${DUKTAPE_DEPS_DIR}/tmp
|
||||
STAMP_DIR ${DUKTAPE_DEPS_DIR}/stamp
|
||||
SOURCE_DIR ${DUKTAPE_DEPS_DIR}/src
|
||||
BINARY_DIR ${DUKTAPE_DEPS_DIR}/build
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
TEST_COMMAND ""
|
||||
)
|
||||
@@ -4,21 +4,18 @@
|
||||
|
||||
set(TARGET_DOCS docs)
|
||||
|
||||
set(DOXY_IN_FILE doxy.in)
|
||||
|
||||
set(DOXY_SOURCE_DIRECTORY ${PROJECT_SRC_DIR})
|
||||
set(DOXY_SOURCE_DIRECTORY ${entt_SOURCE_DIR}/src)
|
||||
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)
|
||||
configure_file(doxy.in doxy.cfg @ONLY)
|
||||
|
||||
add_custom_target(
|
||||
${TARGET_DOCS}
|
||||
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/${DOXY_CFG_FILE}
|
||||
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxy.cfg
|
||||
WORKING_DIRECTORY ${entt_SOURCE_DIR}
|
||||
VERBATIM
|
||||
SOURCES ${DOXY_IN_FILE}
|
||||
SOURCES doxy.in
|
||||
)
|
||||
|
||||
install(
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include<type_traits>
|
||||
#include<cstddef>
|
||||
#include<atomic>
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -18,14 +19,11 @@ namespace entt {
|
||||
*/
|
||||
template<typename...>
|
||||
class Family {
|
||||
static std::size_t identifier() noexcept {
|
||||
static std::size_t value = 0;
|
||||
return value++;
|
||||
}
|
||||
static std::atomic<std::size_t> identifier;
|
||||
|
||||
template<typename...>
|
||||
static std::size_t family() noexcept {
|
||||
static const std::size_t value = identifier();
|
||||
static const std::size_t value = identifier.fetch_add(1);
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -38,12 +36,16 @@ public:
|
||||
* @return Statically generated unique identifier for the given type.
|
||||
*/
|
||||
template<typename... Type>
|
||||
static family_type type() noexcept {
|
||||
inline static family_type type() noexcept {
|
||||
return family<std::decay_t<Type>...>();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename... Types>
|
||||
std::atomic<std::size_t> Family<Types...>::identifier{};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define ENTT_CORE_HASHED_STRING_HPP
|
||||
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
@@ -24,8 +25,8 @@ class HashedString final {
|
||||
const char *str;
|
||||
};
|
||||
|
||||
static constexpr std::uint64_t offset = 14695981039346656037u;
|
||||
static constexpr std::uint64_t prime = 1099511628211u;
|
||||
static constexpr std::uint64_t offset = 14695981039346656037ull;
|
||||
static constexpr std::uint64_t prime = 1099511628211ull;
|
||||
|
||||
// Fowler–Noll–Vo hash function v. 1a - the good
|
||||
static constexpr std::uint64_t helper(std::uint64_t partial, const char *str) noexcept {
|
||||
|
||||
@@ -22,6 +22,8 @@ template<typename Entity, typename Delta>
|
||||
struct Actor {
|
||||
/*! @brief Type of registry used internally. */
|
||||
using registry_type = Registry<Entity>;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
/*! @brief Type used to provide elapsed time. */
|
||||
using delta_type = Delta;
|
||||
|
||||
@@ -63,8 +65,8 @@ struct Actor {
|
||||
* @return A reference to the newly created component.
|
||||
*/
|
||||
template<typename Component, typename... Args>
|
||||
Component & set(Args&&... args) {
|
||||
return reg.template accomodate<Component>(entity, std::forward<Args>(args)...);
|
||||
Component & set(Args &&... args) {
|
||||
return reg.template accommodate<Component>(entity, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,7 +145,7 @@ private:
|
||||
* @tparam Delta Type to use to provide elapsed time.
|
||||
*/
|
||||
template<typename Delta>
|
||||
using DefaultActor = Actor<std::uint32_t, Delta>;
|
||||
using DefaultActor = Actor<DefaultRegistry::entity_type, Delta>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef ENTT_ENTITY_ENTT_HPP
|
||||
#define ENTT_ENTITY_ENTT_HPP
|
||||
#ifndef ENTT_ENTITY_ENTT_TRAITS_HPP
|
||||
#define ENTT_ENTITY_ENTT_TRAITS_HPP
|
||||
|
||||
|
||||
#include <cstdint>
|
||||
@@ -58,11 +58,11 @@ struct entt_traits<std::uint32_t> {
|
||||
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;
|
||||
static constexpr auto entity_mask = 0xFFFFF;
|
||||
/*! @brief Mask to use to get the version out of an identifier. */
|
||||
static constexpr auto version_mask = 0xFF;
|
||||
static constexpr auto version_mask = 0xFFF;
|
||||
/*! @brief Extent of the entity number within an identifier. */
|
||||
static constexpr auto entity_shift = 24;
|
||||
static constexpr auto entity_shift = 20;
|
||||
};
|
||||
|
||||
|
||||
@@ -93,4 +93,4 @@ struct entt_traits<std::uint64_t> {
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_ENTT_HPP
|
||||
#endif // ENTT_ENTITY_ENTT_TRAITS_HPP
|
||||
@@ -2,15 +2,19 @@
|
||||
#define ENTT_ENTITY_REGISTRY_HPP
|
||||
|
||||
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#include "../core/family.hpp"
|
||||
#include "entt_traits.hpp"
|
||||
#include "snapshot.hpp"
|
||||
#include "sparse_set.hpp"
|
||||
#include "traits.hpp"
|
||||
#include "view.hpp"
|
||||
|
||||
|
||||
@@ -54,7 +58,7 @@ class Registry {
|
||||
using test_fn_type = bool(Registry::*)(Entity) const;
|
||||
|
||||
template<typename... Args>
|
||||
Component & construct(Registry ®istry, Entity entity, Args&&... args) {
|
||||
Component & construct(Registry ®istry, Entity entity, Args &&... args) {
|
||||
auto &component = SparseSet<Entity, Component>::construct(entity, std::forward<Args>(args)...);
|
||||
|
||||
for(auto &&listener: listeners) {
|
||||
@@ -106,12 +110,11 @@ class Registry {
|
||||
|
||||
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() {
|
||||
Pool<Component> & assure() {
|
||||
const auto ctype = component_family::type<Component>();
|
||||
|
||||
if(!(ctype < pools.size())) {
|
||||
@@ -136,7 +139,6 @@ class Registry {
|
||||
|
||||
if(!handlers[vtype]) {
|
||||
using accumulator_type = int[];
|
||||
|
||||
auto set = std::make_unique<SparseSet<Entity>>();
|
||||
|
||||
for(auto entity: view<Component...>()) {
|
||||
@@ -144,7 +146,7 @@ class Registry {
|
||||
}
|
||||
|
||||
accumulator_type accumulator = {
|
||||
(ensure<Component>().append(set.get(), &Registry::has<Component...>), 0)...
|
||||
(assure<Component>().append(set.get(), &Registry::has<Component...>), 0)...
|
||||
};
|
||||
|
||||
handlers[vtype] = std::move(set);
|
||||
@@ -161,6 +163,10 @@ public:
|
||||
using version_type = typename traits_type::version_type;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using tag_type = typename tag_family::family_type;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using component_type = typename component_family::family_type;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
Registry() = default;
|
||||
@@ -175,6 +181,40 @@ public:
|
||||
/*! @brief Default move assignment operator. @return This registry. */
|
||||
Registry & operator=(Registry &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Returns the numeric identifier of a type of tag at runtime.
|
||||
*
|
||||
* The given tag doesn't need to be necessarily in use. However, the
|
||||
* registry could decide to prepare internal data structures for it for
|
||||
* later uses.<br/>
|
||||
* Do not use this functionality to provide numeric identifiers to types at
|
||||
* runtime.
|
||||
*
|
||||
* @tparam Tag Type of tag to query.
|
||||
* @return Runtime numeric identifier of the given type of tag.
|
||||
*/
|
||||
template<typename Tag>
|
||||
tag_type tag() const noexcept {
|
||||
return tag_family::type<Tag>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the numeric identifier of a type of component at runtime.
|
||||
*
|
||||
* The given component doesn't need to be necessarily in use. However, the
|
||||
* registry could decide to prepare internal data structures for it for
|
||||
* later uses.<br/>
|
||||
* Do not use this functionality to provide numeric identifiers to types at
|
||||
* runtime.
|
||||
*
|
||||
* @tparam Component Type of component to query.
|
||||
* @return Runtime numeric identifier of the given type of component.
|
||||
*/
|
||||
template<typename Component>
|
||||
component_type component() const noexcept {
|
||||
return component_family::type<Component>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of existing components of the given type.
|
||||
* @tparam Component Type of component of which to return the size.
|
||||
@@ -190,7 +230,33 @@ public:
|
||||
* @return Number of entities still in use.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
return entities.size() - available.size();
|
||||
return entities.size() - available;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Increases the capacity of the pool for a given component.
|
||||
*
|
||||
* If the new capacity is greater than the current capacity, new storage is
|
||||
* allocated, otherwise the method does nothing.
|
||||
*
|
||||
* @tparam Component Type of component for which to reserve storage.
|
||||
* @param cap Desired capacity.
|
||||
*/
|
||||
template<typename Component>
|
||||
void reserve(size_type cap) {
|
||||
assure<Component>().reserve(cap);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Increases the capacity of a registry in terms of entities.
|
||||
*
|
||||
* If the new capacity is greater than the current capacity, new storage is
|
||||
* allocated, otherwise the method does nothing.
|
||||
*
|
||||
* @param cap Desired capacity.
|
||||
*/
|
||||
void reserve(size_type cap) {
|
||||
entities.reserve(cap);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,17 +283,41 @@ public:
|
||||
* @return True if at least an entity is still in use, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
return entities.size() == available.size();
|
||||
return entities.size() == available;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Verifies if an entity identifier still refers to a valid entity.
|
||||
* @brief Checks if an entity identifier refers to a valid entity.
|
||||
* @param entity An entity identifier, either valid or not.
|
||||
* @return True if the identifier is still valid, false otherwise.
|
||||
* @return True if the identifier is 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);
|
||||
const auto pos = size_type(entity & traits_type::entity_mask);
|
||||
return (pos < entities.size() && entities[pos] == entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if an entity identifier refers to a valid entity.
|
||||
*
|
||||
* Alternative version of `valid`. It accesses the internal data structures
|
||||
* without bounds checking and thus it's both unsafe and risky to use.<br/>
|
||||
* You should not invoke directly this function unless you know exactly what
|
||||
* you are doing. Prefer the `valid` member function instead.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn't belong to the registry can
|
||||
* result in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* bounds violation.
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
* @return True if the identifier is valid, false otherwise.
|
||||
*/
|
||||
bool fast(entity_type entity) const noexcept {
|
||||
const auto pos = size_type(entity & traits_type::entity_mask);
|
||||
assert(pos < entities.size());
|
||||
// the in-use control bit permits to avoid accessing the direct vector
|
||||
return (entities[pos] == entity);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -257,9 +347,9 @@ public:
|
||||
* @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::entity_shift) & traits_type::version_mask);
|
||||
const auto pos = size_type(entity & traits_type::entity_mask);
|
||||
assert(pos < entities.size());
|
||||
return version_type((entities[pos] >> traits_type::entity_shift) & traits_type::version_mask);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -282,10 +372,10 @@ public:
|
||||
* @return A valid entity identifier.
|
||||
*/
|
||||
template<typename... Component>
|
||||
entity_type create(Component&&... components) noexcept {
|
||||
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)... };
|
||||
accumulator_type accumulator = { 0, (assure<std::decay_t<Component>>().construct(*this, entity, std::forward<Component>(components)), 0)... };
|
||||
(void)accumulator;
|
||||
return entity;
|
||||
}
|
||||
@@ -312,7 +402,7 @@ public:
|
||||
entity_type create() noexcept {
|
||||
using accumulator_type = int[];
|
||||
const auto entity = create();
|
||||
accumulator_type accumulator = { 0, (ensure<Component>().construct(*this, entity), 0)... };
|
||||
accumulator_type accumulator = { 0, (assure<Component>().construct(*this, entity), 0)... };
|
||||
(void)accumulator;
|
||||
return entity;
|
||||
}
|
||||
@@ -337,14 +427,18 @@ public:
|
||||
entity_type create() noexcept {
|
||||
entity_type entity;
|
||||
|
||||
if(available.empty()) {
|
||||
if(available) {
|
||||
const auto entt = next;
|
||||
const auto version = entities[entt] & (~traits_type::entity_mask);
|
||||
|
||||
entity = entt | version;
|
||||
next = entities[entt] & traits_type::entity_mask;
|
||||
entities[entt] = entity;
|
||||
--available;
|
||||
} else {
|
||||
entity = entity_type(entities.size());
|
||||
assert(entity < traits_type::entity_mask);
|
||||
assert((entity >> traits_type::entity_shift) == entity_type{});
|
||||
entities.push_back(entity);
|
||||
} else {
|
||||
entity = available.back();
|
||||
available.pop_back();
|
||||
}
|
||||
|
||||
return entity;
|
||||
@@ -367,12 +461,13 @@ public:
|
||||
*/
|
||||
void destroy(entity_type entity) {
|
||||
assert(valid(entity));
|
||||
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
const auto version = 1 + ((entity >> traits_type::entity_shift) & traits_type::version_mask);
|
||||
const auto next = entt | (version << traits_type::entity_shift);
|
||||
entities[entt] = next;
|
||||
available.push_back(next);
|
||||
const auto version = (((entity >> traits_type::entity_shift) + 1) & traits_type::version_mask) << traits_type::entity_shift;
|
||||
const auto node = (available ? next : ((entt + 1) & traits_type::entity_mask)) | version;
|
||||
|
||||
entities[entt] = node;
|
||||
next = entt;
|
||||
++available;
|
||||
|
||||
for(auto &&cpool: pools) {
|
||||
if(cpool && cpool->has(entity)) {
|
||||
@@ -382,7 +477,7 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Attaches a tag to an entity.
|
||||
* @brief Attaches the given tag to an entity.
|
||||
*
|
||||
* Usually, pools of components allocate enough memory to store a bunch of
|
||||
* elements even if only one of them is used. On the other hand, there are
|
||||
@@ -403,7 +498,7 @@ public:
|
||||
* @return A reference to the newly created tag.
|
||||
*/
|
||||
template<typename Tag, typename... Args>
|
||||
Tag & attach(entity_type entity, Args&&... args) {
|
||||
Tag & attach(entity_type entity, Args &&... args) {
|
||||
assert(valid(entity));
|
||||
assert(!has<Tag>());
|
||||
const auto ttype = tag_family::type<Tag>();
|
||||
@@ -412,14 +507,13 @@ public:
|
||||
tags.resize(ttype + 1);
|
||||
}
|
||||
|
||||
tags[ttype].reset(new Attaching<Tag>{entity, { std::forward<Args>(args)... }});
|
||||
tags[ttype]->entity = entity;
|
||||
tags[ttype].reset(new Attaching<Tag>{entity, Tag{std::forward<Args>(args)...}});
|
||||
|
||||
return static_cast<Attaching<Tag> *>(tags[ttype].get())->tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes a tag from its owner, if any.
|
||||
* @brief Removes the given tag from its owner, if any.
|
||||
* @tparam Tag Type of tag to remove.
|
||||
*/
|
||||
template<typename Tag>
|
||||
@@ -430,7 +524,7 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a tag has an owner.
|
||||
* @brief Checks if the given tag has an owner.
|
||||
* @tparam Tag Type of tag for which to perform the check.
|
||||
* @return True if the tag already has an owner, false otherwise.
|
||||
*/
|
||||
@@ -445,7 +539,7 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to a tag.
|
||||
* @brief Returns a reference to the given tag.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to get a tag that hasn't an owner results in undefined
|
||||
@@ -463,7 +557,7 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to a tag.
|
||||
* @brief Returns a reference to the given tag.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to get a tag that hasn't an owner results in undefined
|
||||
@@ -480,7 +574,56 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the owner of a tag, if any.
|
||||
* @brief Replaces the given tag.
|
||||
*
|
||||
* A new instance of the given tag is created and initialized with the
|
||||
* arguments provided (the tag must have a proper constructor or be of
|
||||
* aggregate type).
|
||||
*
|
||||
* @warning
|
||||
* Attempting to replace a tag that hasn't an owner results in undefined
|
||||
* behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* tag hasn't been previously attached to an entity.
|
||||
*
|
||||
* @tparam Tag Type of tag to replace.
|
||||
* @tparam Args Types of arguments to use to construct the tag.
|
||||
* @param args Parameters to use to initialize the tag.
|
||||
* @return A reference to the tag.
|
||||
*/
|
||||
template<typename Tag, typename... Args>
|
||||
Tag & set(Args &&... args) {
|
||||
return get<Tag>() = Tag{std::forward<Args>(args)...};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Changes the owner of the given tag.
|
||||
*
|
||||
* The ownership of the tag is transferred from one entity to another.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity or to transfer the ownership of a tag
|
||||
* that hasn't an owner results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity or if the tag hasn't been previously attached to an
|
||||
* entity.
|
||||
*
|
||||
* @tparam Tag Type of tag of which to transfer the ownership.
|
||||
* @param entity A valid entity identifier.
|
||||
* @return A valid entity identifier.
|
||||
*/
|
||||
template<typename Tag>
|
||||
entity_type move(entity_type entity) {
|
||||
assert(valid(entity));
|
||||
assert(has<Tag>());
|
||||
const auto ttype = tag_family::type<Tag>();
|
||||
const auto owner = tags[ttype]->entity;
|
||||
tags[ttype]->entity = entity;
|
||||
return owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the owner of the given tag, if any.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to get the owner of a tag that hasn't been previously attached
|
||||
@@ -518,9 +661,9 @@ public:
|
||||
* @return A reference to the newly created component.
|
||||
*/
|
||||
template<typename Component, typename... Args>
|
||||
Component & assign(entity_type entity, Args&&... args) {
|
||||
Component & assign(entity_type entity, Args &&... args) {
|
||||
assert(valid(entity));
|
||||
return ensure<Component>().construct(*this, entity, std::forward<Args>(args)...);
|
||||
return assure<Component>().construct(*this, entity, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -556,11 +699,10 @@ public:
|
||||
*/
|
||||
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))... };
|
||||
accumulator_type accumulator = { all, (all = all && managed<Component>() && pool<Component>().has(entity))... };
|
||||
(void)accumulator;
|
||||
return all;
|
||||
}
|
||||
@@ -577,7 +719,7 @@ public:
|
||||
*
|
||||
* @tparam Component Type of component to get.
|
||||
* @param entity A valid entity identifier.
|
||||
* @return A reference to the instance of the component owned by the entity.
|
||||
* @return A reference to the component owned by the entity.
|
||||
*/
|
||||
template<typename Component>
|
||||
const Component & get(entity_type entity) const noexcept {
|
||||
@@ -597,13 +739,53 @@ public:
|
||||
*
|
||||
* @tparam Component Type of component to get.
|
||||
* @param entity A valid entity identifier.
|
||||
* @return A reference to the instance of the component owned by the entity.
|
||||
* @return A reference to 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 Returns a reference to the given components for an entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity or to get components from an entity
|
||||
* that doesn't own them 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 instances of the given
|
||||
* components.
|
||||
*
|
||||
* @tparam Component Type of components to get.
|
||||
* @param entity A valid entity identifier.
|
||||
* @return References to the components owned by the entity.
|
||||
*/
|
||||
template<typename... Component>
|
||||
std::enable_if_t<(sizeof...(Component) > 1), std::tuple<const Component &...>>
|
||||
get(entity_type entity) const noexcept {
|
||||
return std::tuple<const Component &...>{get<Component>(entity)...};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given components for an entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity or to get components from an entity
|
||||
* that doesn't own them 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 instances of the given
|
||||
* components.
|
||||
*
|
||||
* @tparam Component Type of components to get.
|
||||
* @param entity A valid entity identifier.
|
||||
* @return References to the components owned by the entity.
|
||||
*/
|
||||
template<typename... Component>
|
||||
std::enable_if_t<(sizeof...(Component) > 1), std::tuple<Component &...>>
|
||||
get(entity_type entity) noexcept {
|
||||
return std::tuple<Component &...>{get<Component>(entity)...};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Replaces the given component for an entity.
|
||||
*
|
||||
@@ -625,9 +807,8 @@ public:
|
||||
* @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)...});
|
||||
Component & replace(entity_type entity, Args &&... args) {
|
||||
return (get<Component>(entity) = Component{std::forward<Args>(args)...});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -643,7 +824,7 @@ public:
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* Prefer this function anyway because it has slighlty better
|
||||
* Prefer this function anyway because it has slightly better
|
||||
* performance.
|
||||
*
|
||||
* @warning
|
||||
@@ -658,9 +839,9 @@ public:
|
||||
* @return A reference to the newly created component.
|
||||
*/
|
||||
template<typename Component, typename... Args>
|
||||
Component & accomodate(entity_type entity, Args&&... args) {
|
||||
Component & accommodate(entity_type entity, Args &&... args) {
|
||||
assert(valid(entity));
|
||||
auto &cpool = ensure<Component>();
|
||||
auto &cpool = assure<Component>();
|
||||
|
||||
return (cpool.has(entity)
|
||||
? (cpool.get(entity) = Component{std::forward<Args>(args)...})
|
||||
@@ -670,7 +851,7 @@ public:
|
||||
/**
|
||||
* @brief Sorts the pool of entities for the given component.
|
||||
*
|
||||
* The order of the elements in a pool is highly affected by assignements
|
||||
* The order of the elements in a pool is highly affected by assignments
|
||||
* 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/>
|
||||
@@ -683,28 +864,22 @@ public:
|
||||
* comparison function should be equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* bool(auto e1, auto e2)
|
||||
* bool(const Component &, const Component &)
|
||||
* @endcode
|
||||
*
|
||||
* Where `e1` and `e2` are valid entity identifiers.
|
||||
*
|
||||
* @tparam Component Type of components to sort.
|
||||
* @tparam Compare Type of 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)));
|
||||
});
|
||||
assure<Component>().sort(std::move(compare));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sorts two pools of components in the same way.
|
||||
*
|
||||
* The order of the elements in a pool is highly affected by assignements
|
||||
* The order of the elements in a pool is highly affected by assignments
|
||||
* 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.
|
||||
@@ -722,9 +897,9 @@ public:
|
||||
* the following rules:
|
||||
*
|
||||
* * All the entities in `A` that are also in `B` are returned first
|
||||
* according to the order they have in `B`.
|
||||
* 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.
|
||||
* particular order after all the other entities.
|
||||
*
|
||||
* Any subsequent change to `B` won't affect the order in `A`.
|
||||
*
|
||||
@@ -733,7 +908,7 @@ public:
|
||||
*/
|
||||
template<typename To, typename From>
|
||||
void sort() {
|
||||
ensure<To>().respect(ensure<From>());
|
||||
assure<To>().respect(assure<From>());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -776,11 +951,11 @@ public:
|
||||
if(managed<Component>()) {
|
||||
auto &cpool = pool<Component>();
|
||||
|
||||
for(auto entity: entities) {
|
||||
each([&cpool](auto entity) {
|
||||
if(cpool.has(entity)) {
|
||||
cpool.destroy(entity);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -788,34 +963,104 @@ public:
|
||||
* @brief Resets a 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
|
||||
* still in use 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();
|
||||
each([this](auto entity) {
|
||||
destroy(entity);
|
||||
});
|
||||
}
|
||||
|
||||
for(auto &&entity: entities) {
|
||||
const auto version = 1 + ((entity >> traits_type::entity_shift) & traits_type::version_mask);
|
||||
entity = (entity & traits_type::entity_mask) | (version << traits_type::entity_shift);
|
||||
available.push_back(entity);
|
||||
}
|
||||
/**
|
||||
* @brief Iterates all the entities that are still in use.
|
||||
*
|
||||
* The function object is invoked for each entity that is still in use.<br/>
|
||||
* The signature of the function should be equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(entity_type);
|
||||
* @endcode
|
||||
*
|
||||
* This function is fairly slow and should not be used frequently.<br/>
|
||||
* Consider using a view if the goal is to iterate entities that have a
|
||||
* determinate set of components. A view is usually faster than combining
|
||||
* this function with a bunch of custom tests.<br/>
|
||||
* On the other side, this function can be used to iterate all the entities
|
||||
* that are in use, regardless of their components.
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func func) const {
|
||||
if(available) {
|
||||
for(auto pos = entities.size(); pos; --pos) {
|
||||
const entity_type curr = pos - 1;
|
||||
const auto entity = entities[curr];
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
|
||||
for(auto &&handler: handlers) {
|
||||
if(handler) {
|
||||
handler->reset();
|
||||
if(curr == entt) {
|
||||
func(entity);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(auto pos = entities.size(); pos; --pos) {
|
||||
func(entities[pos-1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(auto &&pool: pools) {
|
||||
if(pool) {
|
||||
pool->reset();
|
||||
/**
|
||||
* @brief Checks if an entity is an orphan.
|
||||
*
|
||||
* An orphan is an entity that has neither assigned components nor
|
||||
* tags.
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
* @return True if the entity is an orphan, false otherwise.
|
||||
*/
|
||||
bool orphan(entity_type entity) const {
|
||||
assert(valid(entity));
|
||||
bool orphan = true;
|
||||
|
||||
for(std::size_t i = 0; i < pools.size() && orphan; ++i) {
|
||||
const auto &pool = pools[i];
|
||||
orphan = !(pool && pool->has(entity));
|
||||
}
|
||||
|
||||
for(std::size_t i = 0; i < tags.size() && orphan; ++i) {
|
||||
const auto &tag = tags[i];
|
||||
orphan = !(tag && (tag->entity == entity));
|
||||
}
|
||||
|
||||
return orphan;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterates orphans and applies them the given function object.
|
||||
*
|
||||
* The function object is invoked for each entity that is still in use and
|
||||
* has neither assigned components nor tags.<br/>
|
||||
* The signature of the function should be equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(entity_type);
|
||||
* @endcode
|
||||
*
|
||||
* This function can be very slow and should not be used frequently.
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void orphans(Func func) const {
|
||||
each([func = std::move(func), this](auto entity) {
|
||||
if(orphan(entity)) {
|
||||
func(entity);
|
||||
}
|
||||
}
|
||||
|
||||
for(auto &&tag: tags) {
|
||||
tag.reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -829,13 +1074,13 @@ public:
|
||||
* 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:
|
||||
* entities. In particular:
|
||||
*
|
||||
* * Single component views are incredibly fast and iterate a packed array
|
||||
* of entities, all of which has the given component.
|
||||
* 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.
|
||||
* 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
|
||||
@@ -846,13 +1091,14 @@ public:
|
||||
* @see View
|
||||
* @see View<Entity, Component>
|
||||
* @see PersistentView
|
||||
* @see RawView
|
||||
*
|
||||
* @tparam Component Type of 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>()...};
|
||||
return View<Entity, Component...>{assure<Component>()...};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -867,7 +1113,7 @@ public:
|
||||
* 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.
|
||||
* that would have been used otherwise to construct the view.
|
||||
*
|
||||
* @tparam Component Types of components used to prepare the view.
|
||||
*/
|
||||
@@ -926,16 +1172,16 @@ public:
|
||||
* 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
|
||||
* Persistent views are the right choice to iterate entities 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.
|
||||
* 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.
|
||||
* 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.
|
||||
@@ -949,6 +1195,7 @@ public:
|
||||
* @see View
|
||||
* @see View<Entity, Component>
|
||||
* @see PersistentView
|
||||
* @see RawView
|
||||
*
|
||||
* @tparam Component Types of components used to construct the view.
|
||||
* @return A newly created persistent view.
|
||||
@@ -959,12 +1206,112 @@ public:
|
||||
return PersistentView<Entity, Component...>{handler<Component...>(), pool<Component>()...};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a raw view for the given component.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Raw views are incredibly fast and must be considered the best tool to
|
||||
* iterate components whenever knowing the entities to which they belong
|
||||
* isn't required.
|
||||
*
|
||||
* @see View
|
||||
* @see View<Entity, Component>
|
||||
* @see PersistentView
|
||||
* @see RawView
|
||||
*
|
||||
* @tparam Component Type of component used to construct the view.
|
||||
* @return A newly created raw view.
|
||||
*/
|
||||
template<typename Component>
|
||||
RawView<Entity, Component> raw() {
|
||||
return RawView<Entity, Component>{assure<Component>()};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a temporary object to use to create snapshots.
|
||||
*
|
||||
* A snapshot is either a full or a partial dump of a registry.<br/>
|
||||
* It can be used to save and restore its internal state or to keep two or
|
||||
* more instances of this class in sync, as an example in a client-server
|
||||
* architecture.
|
||||
*
|
||||
* @return A not movable and not copyable object to use to take snasphosts.
|
||||
*/
|
||||
Snapshot<Entity> snapshot() const {
|
||||
using follow_fn_type = entity_type(*)(const Registry &, entity_type);
|
||||
using raw_fn_type = const entity_type *(*)(const Registry &, component_type);
|
||||
const entity_type seed = available ? (next | (entities[next] & ~traits_type::entity_mask)) : next;
|
||||
|
||||
follow_fn_type follow = [](const Registry ®istry, entity_type entity) -> entity_type {
|
||||
const auto &entities = registry.entities;
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
const auto next = entities[entt] & traits_type::entity_mask;
|
||||
return (next | (entities[next] & ~traits_type::entity_mask));
|
||||
};
|
||||
|
||||
raw_fn_type raw = [](const Registry ®istry, component_type component) -> const entity_type * {
|
||||
const auto &pools = registry.pools;
|
||||
return (component < pools.size() && pools[component]) ? pools[component]->data() : nullptr;
|
||||
};
|
||||
|
||||
return { *this, seed, available, follow, raw };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a temporary object to use to load snapshots.
|
||||
*
|
||||
* A snapshot is either a full or a partial dump of a registry.<br/>
|
||||
* It can be used to save and restore its internal state or to keep two or
|
||||
* more instances of this class in sync, as an example in a client-server
|
||||
* architecture.
|
||||
*
|
||||
* @warning
|
||||
* The loader returned by this function requires that the registry be empty.
|
||||
* In case it isn't, all the data will be automatically deleted before to
|
||||
* return.
|
||||
*
|
||||
* @return A not movable and not copyable object to use to load snasphosts.
|
||||
*/
|
||||
SnapshotLoader<Entity> restore() {
|
||||
using assure_fn_type = void(*)(Registry &, entity_type, bool);
|
||||
|
||||
assure_fn_type assure = [](Registry ®istry, entity_type entity, bool destroyed) {
|
||||
using promotion_type = std::conditional_t<sizeof(size_type) >= sizeof(entity_type), size_type, entity_type>;
|
||||
// explicit promotion to avoid warnings with std::uint16_t
|
||||
const auto entt = promotion_type{entity} & traits_type::entity_mask;
|
||||
auto &entities = registry.entities;
|
||||
|
||||
if(!(entt < entities.size())) {
|
||||
auto curr = entities.size();
|
||||
entities.resize(entt + 1);
|
||||
std::iota(entities.data() + curr, entities.data() + entt, entity_type(curr));
|
||||
}
|
||||
|
||||
entities[entt] = entity;
|
||||
|
||||
if(destroyed) {
|
||||
registry.destroy(entity);
|
||||
const auto version = (entity & (~traits_type::entity_mask));
|
||||
entities[entt] = ((entities[entt] & traits_type::entity_mask) | version);
|
||||
}
|
||||
};
|
||||
|
||||
return { (*this = {}), assure };
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<SparseSet<Entity>>> handlers;
|
||||
std::vector<std::unique_ptr<SparseSet<Entity>>> pools;
|
||||
std::vector<std::unique_ptr<Attachee>> tags;
|
||||
std::vector<entity_type> available;
|
||||
std::vector<entity_type> entities;
|
||||
size_type available{};
|
||||
entity_type next{};
|
||||
};
|
||||
|
||||
|
||||
|
||||
713
src/entt/entity/snapshot.hpp
Normal file
713
src/entt/entity/snapshot.hpp
Normal file
@@ -0,0 +1,713 @@
|
||||
#ifndef ENTT_ENTITY_SNAPSHOT_HPP
|
||||
#define ENTT_ENTITY_SNAPSHOT_HPP
|
||||
|
||||
|
||||
#include <unordered_map>
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <cassert>
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
#include "entt_traits.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Forward declaration of the registry class.
|
||||
*/
|
||||
template<typename>
|
||||
class Registry;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Utility class to create snapshots from a registry.
|
||||
*
|
||||
* A _snapshot_ can be either a dump of the entire registry or a narrower
|
||||
* selection of components and tags of interest.<br/>
|
||||
* This type can be used in both cases if provided with a correctly configured
|
||||
* output archive.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
class Snapshot final {
|
||||
/*! @brief A registry is allowed to create snapshots. */
|
||||
friend class Registry<Entity>;
|
||||
|
||||
using follow_fn_type = Entity(*)(const Registry<Entity> &, Entity);
|
||||
using raw_fn_type = const Entity *(*)(const Registry<Entity> &, typename Registry<Entity>::component_type);
|
||||
|
||||
Snapshot(const Registry<Entity> ®istry, Entity seed, std::size_t size, follow_fn_type follow, raw_fn_type raw) noexcept
|
||||
: registry{registry},
|
||||
seed{seed},
|
||||
size{size},
|
||||
follow{follow},
|
||||
raw{raw}
|
||||
{}
|
||||
|
||||
Snapshot(const Snapshot &) = default;
|
||||
Snapshot(Snapshot &&) = default;
|
||||
|
||||
Snapshot & operator=(const Snapshot &) = default;
|
||||
Snapshot & operator=(Snapshot &&) = default;
|
||||
|
||||
template<typename Component, typename Archive>
|
||||
void get(Archive &archive, const Registry<Entity> ®istry) {
|
||||
const auto component = registry.template component<Component>();
|
||||
const auto sz = registry.template size<Component>();
|
||||
const auto *entities = raw(registry, component);
|
||||
|
||||
archive(static_cast<Entity>(sz));
|
||||
|
||||
for(std::remove_const_t<decltype(sz)> i{}; i < sz; ++i) {
|
||||
const auto entity = entities[i];
|
||||
archive(entity);
|
||||
archive(registry.template get<Component>(entity));
|
||||
};
|
||||
}
|
||||
|
||||
template<typename Tag, typename Archive>
|
||||
void get(Archive &archive) {
|
||||
const bool has = registry.template has<Tag>();
|
||||
|
||||
// numerical length is forced for tags to facilitate loading
|
||||
archive(has ? Entity(1): Entity{});
|
||||
|
||||
if(has) {
|
||||
archive(registry.template attachee<Tag>());
|
||||
archive(registry.template get<Tag>());
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Puts aside all the entities that are still in use.
|
||||
*
|
||||
* Entities are serialized along with their versions. Destroyed entities are
|
||||
* not taken in consideration by this function.
|
||||
*
|
||||
* @tparam Archive Type of output archive.
|
||||
* @param archive A valid reference to an output archive.
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename Archive>
|
||||
Snapshot entities(Archive &archive) && {
|
||||
archive(static_cast<Entity>(registry.size()));
|
||||
registry.each([&archive, this](auto entity) { archive(entity); });
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Puts aside destroyed entities.
|
||||
*
|
||||
* Entities are serialized along with their versions. Entities that are
|
||||
* still in use are not taken in consideration by this function.
|
||||
*
|
||||
* @tparam Archive Type of output archive.
|
||||
* @param archive A valid reference to an output archive.
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename Archive>
|
||||
Snapshot destroyed(Archive &archive) && {
|
||||
archive(static_cast<Entity>(size));
|
||||
|
||||
if(size) {
|
||||
auto curr = seed;
|
||||
archive(curr);
|
||||
|
||||
for(auto i = size - 1; i; --i) {
|
||||
curr = follow(registry, curr);
|
||||
archive(curr);
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Puts aside the given components.
|
||||
*
|
||||
* Each component is serialized together with the entity to which it
|
||||
* belongs. Entities are serialized along with their versions.
|
||||
*
|
||||
* @tparam Component Types of components to serialize.
|
||||
* @tparam Archive Type of output archive.
|
||||
* @param archive A valid reference to an output archive.
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename... Component, typename Archive>
|
||||
Snapshot component(Archive &archive) && {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (get<Component>(archive, registry), 0)... };
|
||||
(void)accumulator;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Puts aside the given tags.
|
||||
*
|
||||
* Each tag is serialized together with the entity to which it belongs.
|
||||
* Entities are serialized along with their versions.
|
||||
*
|
||||
* @tparam Tag Types of tags to serialize.
|
||||
* @tparam Archive Type of output archive.
|
||||
* @param archive A valid reference to an output archive.
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename... Tag, typename Archive>
|
||||
Snapshot tag(Archive &archive) && {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (get<Tag>(archive), 0)... };
|
||||
(void)accumulator;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
const Registry<Entity> ®istry;
|
||||
const Entity seed;
|
||||
const std::size_t size;
|
||||
follow_fn_type follow;
|
||||
raw_fn_type raw;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Utility class to restore a snapshot as a whole.
|
||||
*
|
||||
* A snapshot loader requires that the destination registry be empty and loads
|
||||
* all the data at once while keeping intact the identifiers that the entities
|
||||
* originally had.<br/>
|
||||
* An example of use is the implementation of a save/restore utility.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
class SnapshotLoader final {
|
||||
/*! @brief A registry is allowed to create snapshot loaders. */
|
||||
friend class Registry<Entity>;
|
||||
|
||||
using assure_fn_type = void(*)(Registry<Entity> &, Entity, bool);
|
||||
|
||||
SnapshotLoader(Registry<Entity> ®istry, assure_fn_type assure_fn) noexcept
|
||||
: registry{registry},
|
||||
assure_fn{assure_fn}
|
||||
{
|
||||
// restore a snapshot as a whole requires a clean registry
|
||||
assert(!registry.capacity());
|
||||
}
|
||||
|
||||
SnapshotLoader(const SnapshotLoader &) = default;
|
||||
SnapshotLoader(SnapshotLoader &&) = default;
|
||||
|
||||
SnapshotLoader & operator=(const SnapshotLoader &) = default;
|
||||
SnapshotLoader & operator=(SnapshotLoader &&) = default;
|
||||
|
||||
template<typename Archive, typename Func>
|
||||
void each(Archive &archive, Func func) {
|
||||
Entity length{};
|
||||
archive(length);
|
||||
|
||||
while(length) {
|
||||
Entity entity{};
|
||||
archive(entity);
|
||||
func(entity);
|
||||
--length;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Component, typename Archive>
|
||||
void assign(Archive &archive) {
|
||||
each(archive, [&archive, this](auto entity) {
|
||||
static constexpr auto destroyed = false;
|
||||
assure_fn(registry, entity, destroyed);
|
||||
archive(registry.template assign<Component>(entity));
|
||||
});
|
||||
}
|
||||
|
||||
template<typename Tag, typename Archive>
|
||||
void attach(Archive &archive) {
|
||||
each(archive, [&archive, this](auto entity) {
|
||||
static constexpr auto destroyed = false;
|
||||
assure_fn(registry, entity, destroyed);
|
||||
archive(registry.template attach<Tag>(entity));
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Restores entities that were in use during serialization.
|
||||
*
|
||||
* This function restores the entities that were in use during serialization
|
||||
* and gives them the versions they originally had.
|
||||
*
|
||||
* @tparam Archive Type of input archive.
|
||||
* @param archive A valid reference to an input archive.
|
||||
* @return A valid loader to continue restoring data.
|
||||
*/
|
||||
template<typename Archive>
|
||||
SnapshotLoader entities(Archive &archive) && {
|
||||
each(archive, [this](auto entity) {
|
||||
static constexpr auto destroyed = false;
|
||||
assure_fn(registry, entity, destroyed);
|
||||
});
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restores entities that were destroyed during serialization.
|
||||
*
|
||||
* This function restores the entities that were destroyed during
|
||||
* serialization and gives them the versions they originally had.
|
||||
*
|
||||
* @tparam Archive Type of input archive.
|
||||
* @param archive A valid reference to an input archive.
|
||||
* @return A valid loader to continue restoring data.
|
||||
*/
|
||||
template<typename Archive>
|
||||
SnapshotLoader destroyed(Archive &archive) && {
|
||||
each(archive, [this](auto entity) {
|
||||
static constexpr auto destroyed = true;
|
||||
assure_fn(registry, entity, destroyed);
|
||||
});
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restores components and assigns them to the right entities.
|
||||
*
|
||||
* The template parameter list must be exactly the same used during
|
||||
* serialization. In the event that the entity to which the component is
|
||||
* assigned doesn't exist yet, the loader will take care to create it with
|
||||
* the version it originally had.
|
||||
*
|
||||
* @tparam Component Types of components to restore.
|
||||
* @tparam Archive Type of input archive.
|
||||
* @param archive A valid reference to an input archive.
|
||||
* @return A valid loader to continue restoring data.
|
||||
*/
|
||||
template<typename... Component, typename Archive>
|
||||
SnapshotLoader component(Archive &archive) && {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (assign<Component>(archive), 0)... };
|
||||
(void)accumulator;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restores tags and assigns them to the right entities.
|
||||
*
|
||||
* The template parameter list must be exactly the same used during
|
||||
* serialization. In the event that the entity to which the tag is assigned
|
||||
* doesn't exist yet, the loader will take care to create it with the
|
||||
* version it originally had.
|
||||
*
|
||||
* @tparam Tag Types of tags to restore.
|
||||
* @tparam Archive Type of input archive.
|
||||
* @param archive A valid reference to an input archive.
|
||||
* @return A valid loader to continue restoring data.
|
||||
*/
|
||||
template<typename... Tag, typename Archive>
|
||||
SnapshotLoader tag(Archive &archive) && {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (attach<Tag>(archive), 0)... };
|
||||
(void)accumulator;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Destroys those entities that have neither components nor tags.
|
||||
*
|
||||
* In case all the entities were serialized but only part of the components
|
||||
* and tags was saved, it could happen that some of the entities have
|
||||
* neither components nor tags once restored.<br/>
|
||||
* This functions helps to identify and destroy those entities.
|
||||
*
|
||||
* @return A valid loader to continue restoring data.
|
||||
*/
|
||||
SnapshotLoader orphans() && {
|
||||
registry.orphans([this](auto entity) {
|
||||
registry.destroy(entity);
|
||||
});
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
Registry<Entity> ®istry;
|
||||
assure_fn_type assure_fn;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Utility class for _continuous loading_.
|
||||
*
|
||||
* A _continuous loader_ is designed to load data from a source registry to a
|
||||
* (possibly) non-empty destination. The loader can accomodate in a registry
|
||||
* more than one snapshot in a sort of _continuous loading_ that updates the
|
||||
* destination one step at a time.<br/>
|
||||
* Identifiers that entities originally had are not transferred to the target.
|
||||
* Instead, the loader maps remote identifiers to local ones while restoring a
|
||||
* snapshot.<br/>
|
||||
* An example of use is the implementation of a client-server applications with
|
||||
* the requirement of transferring somehow parts of the representation side to
|
||||
* side.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
class ContinuousLoader final {
|
||||
using traits_type = entt_traits<Entity>;
|
||||
|
||||
Entity destroy(Entity entity) {
|
||||
const auto it = remloc.find(entity);
|
||||
|
||||
if(it == remloc.cend()) {
|
||||
const auto local = registry.create();
|
||||
remloc.emplace(entity, std::make_pair(local, true));
|
||||
registry.destroy(local);
|
||||
}
|
||||
|
||||
return remloc[entity].first;
|
||||
}
|
||||
|
||||
Entity restore(Entity entity) {
|
||||
const auto it = remloc.find(entity);
|
||||
|
||||
if(it == remloc.cend()) {
|
||||
const auto local = registry.create();
|
||||
remloc.emplace(entity, std::make_pair(local, true));
|
||||
} else {
|
||||
remloc[entity].first =
|
||||
registry.valid(remloc[entity].first)
|
||||
? remloc[entity].first
|
||||
: registry.create();
|
||||
|
||||
// set the dirty flag
|
||||
remloc[entity].second = true;
|
||||
}
|
||||
|
||||
return remloc[entity].first;
|
||||
}
|
||||
|
||||
template<typename Instance, typename Type>
|
||||
std::enable_if_t<std::is_same<Type, Entity>::value>
|
||||
update(Instance &instance, Type Instance::*member) {
|
||||
instance.*member = map(instance.*member);
|
||||
}
|
||||
|
||||
template<typename Instance, typename Type>
|
||||
std::enable_if_t<std::is_same<typename std::iterator_traits<typename Type::iterator>::value_type, Entity>::value>
|
||||
update(Instance &instance, Type Instance::*member) {
|
||||
for(auto &entity: (instance.*member)) {
|
||||
entity = map(entity);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Archive, typename Func>
|
||||
void each(Archive &archive, Func func) {
|
||||
Entity length{};
|
||||
archive(length);
|
||||
|
||||
while(length) {
|
||||
Entity entity{};
|
||||
archive(entity);
|
||||
func(entity);
|
||||
--length;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Component>
|
||||
void reset() {
|
||||
for(auto &&ref: remloc) {
|
||||
const auto local = ref.second.first;
|
||||
|
||||
if(registry.valid(local)) {
|
||||
registry.template reset<Component>(local);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Component, typename Archive>
|
||||
void assign(Archive &archive) {
|
||||
reset<Component>();
|
||||
|
||||
each(archive, [&archive, this](auto entity) {
|
||||
entity = restore(entity);
|
||||
archive(registry.template accommodate<Component>(entity));
|
||||
});
|
||||
}
|
||||
|
||||
template<typename Component, typename Archive, typename... Type>
|
||||
void assign(Archive &archive, Type Component::*... member) {
|
||||
reset<Component>();
|
||||
|
||||
each(archive, [&archive, member..., this](auto entity) {
|
||||
entity = restore(entity);
|
||||
auto &component = registry.template accommodate<Component>(entity);
|
||||
archive(component);
|
||||
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (update(component, member), 0)... };
|
||||
(void)accumulator;
|
||||
});
|
||||
}
|
||||
|
||||
template<typename Tag, typename Archive>
|
||||
void attach(Archive &archive) {
|
||||
registry.template remove<Tag>();
|
||||
|
||||
each(archive, [&archive, this](auto entity) {
|
||||
entity = restore(entity);
|
||||
archive(registry.template attach<Tag>(entity));
|
||||
});
|
||||
}
|
||||
|
||||
template<typename Tag, typename Archive, typename... Type>
|
||||
void attach(Archive &archive, Type Tag::*... member) {
|
||||
registry.template remove<Tag>();
|
||||
|
||||
each(archive, [&archive, member..., this](auto entity) {
|
||||
entity = restore(entity);
|
||||
auto &tag = registry.template attach<Tag>(entity);
|
||||
archive(tag);
|
||||
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (update(tag, member), 0)... };
|
||||
(void)accumulator;
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
|
||||
/**
|
||||
* @brief Constructs a loader that is bound to a given registry.
|
||||
* @param registry A valid reference to a registry.
|
||||
*/
|
||||
ContinuousLoader(Registry<entity_type> ®istry) noexcept
|
||||
: registry{registry}
|
||||
{}
|
||||
|
||||
/*! @brief Default copy constructor. */
|
||||
ContinuousLoader(const ContinuousLoader &) = default;
|
||||
/*! @brief Default move constructor. */
|
||||
ContinuousLoader(ContinuousLoader &&) = default;
|
||||
|
||||
/*! @brief Default copy assignment operator. @return This loader. */
|
||||
ContinuousLoader & operator=(const ContinuousLoader &) = default;
|
||||
/*! @brief Default move assignment operator. @return This loader. */
|
||||
ContinuousLoader & operator=(ContinuousLoader &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Restores entities that were in use during serialization.
|
||||
*
|
||||
* This function restores the entities that were in use during serialization
|
||||
* and creates local counterparts for them if required.
|
||||
*
|
||||
* @tparam Archive Type of input archive.
|
||||
* @param archive A valid reference to an input archive.
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
template<typename Archive>
|
||||
ContinuousLoader & entities(Archive &archive) {
|
||||
each(archive, [this](auto entity) { restore(entity); });
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restores entities that were destroyed during serialization.
|
||||
*
|
||||
* This function restores the entities that were destroyed during
|
||||
* serialization and creates local counterparts for them if required.
|
||||
*
|
||||
* @tparam Archive Type of input archive.
|
||||
* @param archive A valid reference to an input archive.
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
template<typename Archive>
|
||||
ContinuousLoader & destroyed(Archive &archive) {
|
||||
each(archive, [this](auto entity) { destroy(entity); });
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restores components and assigns them to the right entities.
|
||||
*
|
||||
* The template parameter list must be exactly the same used during
|
||||
* serialization. In the event that the entity to which the component is
|
||||
* assigned doesn't exist yet, the loader will take care to create a local
|
||||
* counterpart for it.
|
||||
*
|
||||
* @tparam Component Types of components to restore.
|
||||
* @tparam Archive Type of input archive.
|
||||
* @param archive A valid reference to an input archive.
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
template<typename... Component, typename Archive>
|
||||
ContinuousLoader & component(Archive &archive) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (assign<Component>(archive), 0)... };
|
||||
(void)accumulator;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restores components and assigns them to the right entities.
|
||||
*
|
||||
* The template parameter list must be exactly the same used during
|
||||
* serialization. In the event that the entity to which the component is
|
||||
* assigned doesn't exist yet, the loader will take care to create a local
|
||||
* counterpart for it.<br/>
|
||||
* Members can be either data members of type entity_type or containers of
|
||||
* entities. In both cases, the loader will visit them and update the
|
||||
* entities by replacing each one with its local counterpart.
|
||||
*
|
||||
* @tparam Component Type of component to restore.
|
||||
* @tparam Archive Type of input archive.
|
||||
* @tparam Type Types of members to update with their local counterparts.
|
||||
* @param archive A valid reference to an input archive.
|
||||
* @param member Members to update with their local counterparts.
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
template<typename Component, typename Archive, typename... Type>
|
||||
ContinuousLoader & component(Archive &archive, Type Component::*... member) {
|
||||
assign(archive, member...);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restores tags and assigns them to the right entities.
|
||||
*
|
||||
* The template parameter list must be exactly the same used during
|
||||
* serialization. In the event that the entity to which the tag is assigned
|
||||
* doesn't exist yet, the loader will take care to create a local
|
||||
* counterpart for it.
|
||||
*
|
||||
* @tparam Tag Types of tags to restore.
|
||||
* @tparam Archive Type of input archive.
|
||||
* @param archive A valid reference to an input archive.
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
template<typename... Tag, typename Archive>
|
||||
ContinuousLoader & tag(Archive &archive) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (attach<Tag>(archive), 0)... };
|
||||
(void)accumulator;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restores tags and assigns them to the right entities.
|
||||
*
|
||||
* The template parameter list must be exactly the same used during
|
||||
* serialization. In the event that the entity to which the tag is assigned
|
||||
* doesn't exist yet, the loader will take care to create a local
|
||||
* counterpart for it.<br/>
|
||||
* Members can be either data members of type entity_type or containers of
|
||||
* entities. In both cases, the loader will visit them and update the
|
||||
* entities by replacing each one with its local counterpart.
|
||||
*
|
||||
* @tparam Tag Type of tag to restore.
|
||||
* @tparam Archive Type of input archive.
|
||||
* @tparam Type Types of members to update with their local counterparts.
|
||||
* @param archive A valid reference to an input archive.
|
||||
* @param member Members to update with their local counterparts.
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
template<typename Tag, typename Archive, typename... Type>
|
||||
ContinuousLoader & tag(Archive &archive, Type Tag::*... member) {
|
||||
attach<Tag>(archive, member...);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Helps to purge entities that no longer have a conterpart.
|
||||
*
|
||||
* Users should invoke this member function after restoring each snapshot,
|
||||
* unless they know exactly what they are doing.
|
||||
*
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
ContinuousLoader & shrink() {
|
||||
auto it = remloc.begin();
|
||||
|
||||
while(it != remloc.cend()) {
|
||||
const auto local = it->second.first;
|
||||
bool &dirty = it->second.second;
|
||||
|
||||
if(dirty) {
|
||||
dirty = false;
|
||||
++it;
|
||||
} else {
|
||||
if(registry.valid(local)) {
|
||||
registry.destroy(local);
|
||||
}
|
||||
|
||||
it = remloc.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destroys those entities that have neither components nor tags.
|
||||
*
|
||||
* In case all the entities were serialized but only part of the components
|
||||
* and tags was saved, it could happen that some of the entities have
|
||||
* neither components nor tags once restored.<br/>
|
||||
* This functions helps to identify and destroy those entities.
|
||||
*
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
ContinuousLoader & orphans() {
|
||||
registry.orphans([this](auto entity) {
|
||||
registry.destroy(entity);
|
||||
});
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Tests if a loader knows about a given entity.
|
||||
* @param entity An entity identifier.
|
||||
* @return True if `entity` is managed by the loader, false otherwise.
|
||||
*/
|
||||
bool has(entity_type entity) {
|
||||
return !(remloc.find(entity) == remloc.cend());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the identifier to which an entity refers.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that isn't managed by the loader results in
|
||||
* undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* loader doesn't knows about the entity.
|
||||
*
|
||||
* @param entity An entity identifier.
|
||||
* @return The identifier to which `entity` refers in the target registry.
|
||||
*/
|
||||
entity_type map(entity_type entity) {
|
||||
assert(has(entity));
|
||||
return remloc[entity].first;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<Entity, std::pair<Entity, bool>> remloc;
|
||||
Registry<Entity> ®istry;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_SNAPSHOT_HPP
|
||||
@@ -3,11 +3,14 @@
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <cstddef>
|
||||
#include <cassert>
|
||||
#include "traits.hpp"
|
||||
#include <type_traits>
|
||||
#include "entt_traits.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -54,10 +57,14 @@ template<typename Entity>
|
||||
class SparseSet<Entity> {
|
||||
using traits_type = entt_traits<Entity>;
|
||||
|
||||
struct Iterator {
|
||||
struct Iterator final {
|
||||
using difference_type = std::size_t;
|
||||
using value_type = Entity;
|
||||
using pointer = value_type *;
|
||||
using reference = value_type;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
|
||||
Iterator(const std::vector<Entity> *direct, std::size_t pos)
|
||||
Iterator(const std::vector<value_type> &direct, std::size_t pos)
|
||||
: direct{direct}, pos{pos}
|
||||
{}
|
||||
|
||||
@@ -70,24 +77,33 @@ class SparseSet<Entity> {
|
||||
return ++(*this), orig;
|
||||
}
|
||||
|
||||
Iterator & operator+=(difference_type value) noexcept {
|
||||
pos -= value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator operator+(difference_type value) noexcept {
|
||||
return Iterator{direct, pos-value};
|
||||
}
|
||||
|
||||
bool operator==(const Iterator &other) const noexcept {
|
||||
return other.pos == pos && other.direct == direct;
|
||||
return other.pos == pos;
|
||||
}
|
||||
|
||||
bool operator!=(const Iterator &other) const noexcept {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
value_type operator*() const noexcept {
|
||||
return (*direct)[pos-1];
|
||||
reference operator*() const noexcept {
|
||||
return direct[pos-1];
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<Entity> *direct;
|
||||
const std::vector<value_type> &direct;
|
||||
std::size_t pos;
|
||||
};
|
||||
|
||||
static constexpr Entity in_use = 1 << traits_type::entity_shift;
|
||||
static constexpr Entity in_use = (Entity{1} << traits_type::entity_shift);
|
||||
|
||||
public:
|
||||
/*! @brief Underlying entity identifier. */
|
||||
@@ -115,6 +131,32 @@ public:
|
||||
/*! @brief Default move assignment operator. @return This sparse set. */
|
||||
SparseSet & operator=(SparseSet &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Increases the capacity of a sparse set.
|
||||
*
|
||||
* If the new capacity is greater than the current capacity, new storage is
|
||||
* allocated, otherwise the method does nothing.
|
||||
*
|
||||
* @param cap Desired capacity.
|
||||
*/
|
||||
void reserve(size_type cap) {
|
||||
direct.reserve(cap);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the extent of a sparse set.
|
||||
*
|
||||
* The extent of a sparse set is also the size of the internal sparse array.
|
||||
* There is no guarantee that the internal packed 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 Extent of the sparse set.
|
||||
*/
|
||||
size_type extent() const noexcept {
|
||||
return reverse.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of elements in a sparse set.
|
||||
*
|
||||
@@ -144,7 +186,7 @@ public:
|
||||
* always a valid range, even if the container is empty.
|
||||
*
|
||||
* @note
|
||||
* There are no guarantees on the order, even though `sort` has been
|
||||
* There are no guarantees on the order, even though `respect` 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
|
||||
@@ -159,34 +201,34 @@ public:
|
||||
/**
|
||||
* @brief Returns an iterator to the beginning.
|
||||
*
|
||||
* The returned iterator points to the first element of the internal packed
|
||||
* The returned iterator points to the first entity of the internal packed
|
||||
* array. If the sparse set is empty, the returned iterator will be equal to
|
||||
* `end()`.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed by a call to `sort`.
|
||||
* Input iterators stay true to the order imposed by a call to `respect`.
|
||||
*
|
||||
* @return An iterator to the first element of the internal packed array.
|
||||
* @return An iterator to the first entity of the internal packed array.
|
||||
*/
|
||||
iterator_type begin() const noexcept {
|
||||
return Iterator{&direct, direct.size()};
|
||||
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 returned iterator points to the element following the last entity 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`.
|
||||
* Input iterators stay true to the order imposed by a call to `respect`.
|
||||
*
|
||||
* @return An iterator to the element following the last element of the
|
||||
* @return An iterator to the element following the last entity of the
|
||||
* internal packed array.
|
||||
*/
|
||||
iterator_type end() const noexcept {
|
||||
return Iterator{&direct, 0};
|
||||
return Iterator{direct, 0};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -195,9 +237,33 @@ public:
|
||||
* @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;
|
||||
const auto pos = size_type(entity & traits_type::entity_mask);
|
||||
// the in-use control bit permits to avoid accessing the direct vector
|
||||
return (entt < reverse.size()) && (reverse[entt] & in_use);
|
||||
return (pos < reverse.size()) && (reverse[pos] & in_use);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a sparse set contains an entity (unsafe).
|
||||
*
|
||||
* Alternative version of `has`. It accesses the underlying data structures
|
||||
* without bounds checking and thus it's both unsafe and risky to use.<br/>
|
||||
* You should not invoke directly this function unless you know exactly what
|
||||
* you are doing. Prefer the `has` member function instead.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn't belong to the sparse set can
|
||||
* result in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* bounds violation.
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
* @return True if the sparse set contains the entity, false otherwise.
|
||||
*/
|
||||
bool fast(entity_type entity) const noexcept {
|
||||
const auto pos = size_type(entity & traits_type::entity_mask);
|
||||
assert(pos < reverse.size());
|
||||
// the in-use control bit permits to avoid accessing the direct vector
|
||||
return (reverse[pos] & in_use);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -216,7 +282,7 @@ public:
|
||||
assert(has(entity));
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
// we must get rid of the in-use bit for it's not part of the position
|
||||
return reverse[entt] & ~in_use;
|
||||
return reverse[entt] & traits_type::entity_mask;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -232,15 +298,15 @@ public:
|
||||
*/
|
||||
void construct(entity_type entity) {
|
||||
assert(!has(entity));
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
const auto pos = size_type(entity & traits_type::entity_mask);
|
||||
|
||||
if(!(entt < reverse.size())) {
|
||||
reverse.resize(entt+1, pos_type{});
|
||||
if(!(pos < reverse.size())) {
|
||||
reverse.resize(pos+1, pos_type{});
|
||||
}
|
||||
|
||||
// we exploit the fact that pos_type is equal to entity_type and pos has
|
||||
// traits_type::version_mask bits unused we can use to mark it as in-use
|
||||
reverse[entt] = pos_type(direct.size()) | in_use;
|
||||
reverse[pos] = pos_type(direct.size()) | in_use;
|
||||
direct.emplace_back(entity);
|
||||
}
|
||||
|
||||
@@ -259,10 +325,9 @@ public:
|
||||
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] & ~in_use;
|
||||
// the order matters: if back and entt are the same (for the sparse set
|
||||
// has size 1), switching the two lines below doesn't work as expected
|
||||
reverse[back] = pos | in_use;
|
||||
// we must get rid of the in-use bit for it's not part of the position
|
||||
const auto pos = reverse[entt] & traits_type::entity_mask;
|
||||
reverse[back] = reverse[entt];
|
||||
reverse[entt] = pos;
|
||||
// swapping isn't required here, we are getting rid of the last element
|
||||
direct[pos] = direct.back();
|
||||
@@ -281,50 +346,20 @@ public:
|
||||
* 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.
|
||||
* @param lhs A valid position within the sparse set.
|
||||
* @param rhs A valid position within the sparse set.
|
||||
*/
|
||||
virtual void swap(entity_type lhs, entity_type rhs) {
|
||||
assert(has(lhs));
|
||||
assert(has(rhs));
|
||||
auto &le = reverse[lhs & traits_type::entity_mask];
|
||||
auto &re = reverse[rhs & traits_type::entity_mask];
|
||||
// we must get rid of the in-use bit for it's not part of the position
|
||||
std::swap(direct[le & ~in_use], direct[re & ~in_use]);
|
||||
std::swap(le, re);
|
||||
void swap(pos_type lhs, pos_type rhs) noexcept {
|
||||
assert(lhs < direct.size());
|
||||
assert(rhs < direct.size());
|
||||
const auto src = direct[lhs] & traits_type::entity_mask;
|
||||
const auto dst = direct[rhs] & traits_type::entity_mask;
|
||||
std::swap(reverse[src], reverse[dst]);
|
||||
std::swap(direct[lhs], direct[rhs]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 a sparse set.
|
||||
* @brief Sort entities according to their order in another sparse set.
|
||||
*
|
||||
* Entities that are part of both the sparse sets are ordered internally
|
||||
* according to the order they have in `other`. All the other entities goes
|
||||
@@ -333,41 +368,32 @@ public:
|
||||
* 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.
|
||||
* the expected order after a call to `respect`. 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.
|
||||
* gives no guarantees on the order, even though `respect` 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()));
|
||||
void respect(const SparseSet<Entity> &other) noexcept {
|
||||
auto from = other.begin();
|
||||
auto to = other.end();
|
||||
|
||||
for(auto entity: other.direct) {
|
||||
check[entity & traits_type::entity_mask].value = true;
|
||||
}
|
||||
pos_type pos = direct.size() - 1;
|
||||
|
||||
sort([this, &other, &check](auto lhs, auto rhs) {
|
||||
const auto le = lhs & traits_type::entity_mask;
|
||||
const auto re = rhs & traits_type::entity_mask;
|
||||
while(pos && from != to) {
|
||||
if(has(*from)) {
|
||||
if(*from != direct[pos]) {
|
||||
swap(pos, get(*from));
|
||||
}
|
||||
|
||||
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;
|
||||
--pos;
|
||||
}
|
||||
|
||||
return compare;
|
||||
});
|
||||
++from;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -410,6 +436,56 @@ template<typename Entity, typename Type>
|
||||
class SparseSet<Entity, Type>: public SparseSet<Entity> {
|
||||
using underlying_type = SparseSet<Entity>;
|
||||
|
||||
struct Iterator final {
|
||||
using difference_type = std::size_t;
|
||||
using value_type = Type;
|
||||
using pointer = value_type *;
|
||||
using reference = value_type &;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
|
||||
Iterator(std::vector<value_type> &instances, std::size_t pos)
|
||||
: instances{instances}, pos{pos}
|
||||
{}
|
||||
|
||||
Iterator & operator++() noexcept {
|
||||
return --pos, *this;
|
||||
}
|
||||
|
||||
Iterator operator++(int) noexcept {
|
||||
Iterator orig = *this;
|
||||
return ++(*this), orig;
|
||||
}
|
||||
|
||||
Iterator & operator+=(difference_type value) noexcept {
|
||||
pos -= value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator operator+(difference_type value) noexcept {
|
||||
return Iterator{instances, pos-value};
|
||||
}
|
||||
|
||||
bool operator==(const Iterator &other) const noexcept {
|
||||
return other.pos == pos;
|
||||
}
|
||||
|
||||
bool operator!=(const Iterator &other) const noexcept {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
reference operator*() noexcept {
|
||||
return instances[pos-1];
|
||||
}
|
||||
|
||||
pointer operator->() noexcept {
|
||||
return &instances.data()[pos-1];
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<value_type> &instances;
|
||||
std::size_t pos;
|
||||
};
|
||||
|
||||
public:
|
||||
/*! @brief Type of the objects associated to the entities. */
|
||||
using object_type = Type;
|
||||
@@ -420,7 +496,7 @@ public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename underlying_type::size_type;
|
||||
/*! @brief Input iterator type. */
|
||||
using iterator_type = typename underlying_type::iterator_type;
|
||||
using iterator_type = Iterator;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
SparseSet() noexcept = default;
|
||||
@@ -435,6 +511,19 @@ public:
|
||||
/*! @brief Default move assignment operator. @return This sparse set. */
|
||||
SparseSet & operator=(SparseSet &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Increases the capacity of a sparse set.
|
||||
*
|
||||
* If the new capacity is greater than the current capacity, new storage is
|
||||
* allocated, otherwise the method does nothing.
|
||||
*
|
||||
* @param cap Desired capacity.
|
||||
*/
|
||||
void reserve(size_type cap) {
|
||||
underlying_type::reserve(cap);
|
||||
instances.reserve(cap);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the array of objects.
|
||||
*
|
||||
@@ -442,11 +531,11 @@ public:
|
||||
* 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.
|
||||
* There are no guarantees on the order, even though either `sort` or
|
||||
* `respect` 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.
|
||||
*/
|
||||
@@ -461,11 +550,11 @@ public:
|
||||
* 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.
|
||||
* There are no guarantees on the order, even though either `sort` or
|
||||
* `respect` 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.
|
||||
*/
|
||||
@@ -473,6 +562,40 @@ public:
|
||||
return instances.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the beginning.
|
||||
*
|
||||
* The returned iterator points to the first instance of the given type. 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 either `sort`
|
||||
* or `respect`.
|
||||
*
|
||||
* @return An iterator to the first instance of the given type.
|
||||
*/
|
||||
iterator_type begin() noexcept {
|
||||
return Iterator{instances, instances.size()};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the end.
|
||||
*
|
||||
* The returned iterator points to the element following the last instance
|
||||
* of the given type. Attempting to dereference the returned iterator
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed by a call to either `sort`
|
||||
* or `respect`.
|
||||
*
|
||||
* @return An iterator to the element following the last instance of the
|
||||
* given type.
|
||||
*/
|
||||
iterator_type end() noexcept {
|
||||
return Iterator{instances, 0};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the object associated to an entity.
|
||||
*
|
||||
@@ -508,6 +631,12 @@ public:
|
||||
/**
|
||||
* @brief Assigns an entity to a sparse set and constructs its object.
|
||||
*
|
||||
* @note
|
||||
* _Sfinae'd_ function.<br/>
|
||||
* This version is used for types that can be constructed in place directly.
|
||||
* It doesn't work well with aggregates because of the placement new usually
|
||||
* performed under the hood during an _emplace back_.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that already belongs to the sparse set
|
||||
* results in undefined behavior.<br/>
|
||||
@@ -520,10 +649,38 @@ public:
|
||||
* @return The object associated to the entity.
|
||||
*/
|
||||
template<typename... Args>
|
||||
object_type & construct(entity_type entity, Args&&... args) {
|
||||
std::enable_if_t<std::is_constructible<Type, Args...>::value, object_type &>
|
||||
construct(entity_type entity, Args &&... args) {
|
||||
underlying_type::construct(entity);
|
||||
// emplace_back doesn't work well with PODs because of its placement new
|
||||
instances.push_back({ std::forward<Args>(args)... });
|
||||
instances.emplace_back(std::forward<Args>(args)...);
|
||||
return instances.back();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns an entity to a sparse set and constructs its object.
|
||||
*
|
||||
* @note
|
||||
* _Sfinae'd_ function.<br/>
|
||||
* Fallback for aggregates and types in general that do not work well with a
|
||||
* placement new as performed usually under the hood during an
|
||||
* _emplace back_.
|
||||
*
|
||||
* @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>
|
||||
std::enable_if_t<!std::is_constructible<Type, Args...>::value, object_type &>
|
||||
construct(entity_type entity, Args &&... args) {
|
||||
underlying_type::construct(entity);
|
||||
instances.emplace_back(Type{std::forward<Args>(args)...});
|
||||
return instances.back();
|
||||
}
|
||||
|
||||
@@ -540,30 +697,106 @@ public:
|
||||
*/
|
||||
void destroy(entity_type entity) override {
|
||||
// swapping isn't required here, we are getting rid of the last element
|
||||
instances[underlying_type::get(entity)] = std::move(instances.back());
|
||||
// however, we must protect ourselves from self assignments (see #37)
|
||||
auto tmp = std::move(instances.back());
|
||||
instances[underlying_type::get(entity)] = std::move(tmp);
|
||||
instances.pop_back();
|
||||
underlying_type::destroy(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Swaps two entities and their objects.
|
||||
* @brief Sort components 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.
|
||||
*
|
||||
* 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(const Type &, const Type &)
|
||||
* @endcode
|
||||
*
|
||||
* @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.
|
||||
* Attempting to iterate elements using a raw pointer returned by a call to
|
||||
* either `data` or `raw` gives no guarantees on the order, even though
|
||||
* `sort` has been invoked.
|
||||
*
|
||||
* @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.
|
||||
* @tparam Compare Type of comparison function object.
|
||||
* @param compare A valid comparison function object.
|
||||
*/
|
||||
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);
|
||||
template<typename Compare>
|
||||
void sort(Compare compare) {
|
||||
std::vector<pos_type> copy(instances.size());
|
||||
std::iota(copy.begin(), copy.end(), 0);
|
||||
|
||||
std::sort(copy.begin(), copy.end(), [this, compare = std::move(compare)](auto lhs, auto rhs) {
|
||||
return compare(const_cast<const object_type &>(instances[rhs]), const_cast<const object_type &>(instances[lhs]));
|
||||
});
|
||||
|
||||
for(pos_type pos = 0, last = copy.size(); pos < last; ++pos) {
|
||||
auto curr = pos;
|
||||
auto next = copy[curr];
|
||||
|
||||
while(curr != next) {
|
||||
const auto lhs = copy[curr];
|
||||
const auto rhs = copy[next];
|
||||
std::swap(instances[lhs], instances[rhs]);
|
||||
underlying_type::swap(lhs, rhs);
|
||||
copy[curr] = curr;
|
||||
curr = next;
|
||||
next = copy[curr];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sort components according to the order of the entities in another
|
||||
* sparse set.
|
||||
*
|
||||
* Entities that are part of both the sparse sets are ordered internally
|
||||
* according to the order they have in `other`. All the other entities goes
|
||||
* to the end of the list and there are no guarantess on their order.
|
||||
* Components are sorted according to the entities to which they
|
||||
* belong.<br/>
|
||||
* In other terms, this function can be used to impose the same order on two
|
||||
* sets by using one of them as a master and the other one as a slave.
|
||||
*
|
||||
* Iterating the sparse set with a couple of iterators returns elements in
|
||||
* the expected order after a call to `respect`. See `begin` and `end` for
|
||||
* more details.
|
||||
*
|
||||
* @note
|
||||
* Attempting to iterate elements using a raw pointer returned by a call to
|
||||
* either `data` or `raw` gives no guarantees on the order, even though
|
||||
* `respect` has been invoked.
|
||||
*
|
||||
* @param other The sparse sets that imposes the order of the entities.
|
||||
*/
|
||||
void respect(const SparseSet<Entity> &other) noexcept {
|
||||
auto from = other.begin();
|
||||
auto to = other.end();
|
||||
|
||||
pos_type pos = underlying_type::size() - 1;
|
||||
const auto *local = underlying_type::data();
|
||||
|
||||
while(pos && from != to) {
|
||||
const auto curr = *from;
|
||||
|
||||
if(underlying_type::has(curr)) {
|
||||
if(curr != *(local + pos)) {
|
||||
auto candidate = underlying_type::get(curr);
|
||||
std::swap(instances[pos], instances[candidate]);
|
||||
underlying_type::swap(pos, candidate);
|
||||
}
|
||||
|
||||
--pos;
|
||||
}
|
||||
|
||||
++from;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,9 +2,10 @@
|
||||
#include "core/hashed_string.hpp"
|
||||
#include "core/ident.hpp"
|
||||
#include "entity/actor.hpp"
|
||||
#include "entity/entt_traits.hpp"
|
||||
#include "entity/registry.hpp"
|
||||
#include "entity/snapshot.hpp"
|
||||
#include "entity/sparse_set.hpp"
|
||||
#include "entity/traits.hpp"
|
||||
#include "entity/view.hpp"
|
||||
#include "locator/locator.hpp"
|
||||
#include "process/process.hpp"
|
||||
@@ -14,6 +15,7 @@
|
||||
#include "resource/loader.hpp"
|
||||
#include "signal/bus.hpp"
|
||||
#include "signal/delegate.hpp"
|
||||
#include "signal/dispatcher.hpp"
|
||||
#include "signal/emitter.hpp"
|
||||
#include "signal/sigh.hpp"
|
||||
#include "signal/signal.hpp"
|
||||
|
||||
@@ -14,7 +14,7 @@ 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
|
||||
* Usually service locators are tightly 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.
|
||||
@@ -78,7 +78,7 @@ struct ServiceLocator final {
|
||||
* @param args Parameters to use to construct the service.
|
||||
*/
|
||||
template<typename Impl = Service, typename... Args>
|
||||
inline static void set(Args&&... args) {
|
||||
inline static void set(Args &&... args) {
|
||||
service = std::make_shared<Impl>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,28 +10,6 @@
|
||||
namespace entt {
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
struct BaseProcess {
|
||||
enum class State: unsigned int {
|
||||
UNINITIALIZED = 0,
|
||||
RUNNING,
|
||||
PAUSED,
|
||||
SUCCEEDED,
|
||||
FAILED,
|
||||
ABORTED,
|
||||
FINISHED
|
||||
};
|
||||
|
||||
template<State state>
|
||||
using tag = std::integral_constant<State, state>;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Base class for processes.
|
||||
*
|
||||
@@ -41,33 +19,42 @@ struct BaseProcess {
|
||||
* required:
|
||||
*
|
||||
* * @code{.cpp}
|
||||
* void update(Delta);
|
||||
* void update(Delta, void *);
|
||||
* @endcode
|
||||
*
|
||||
* It's invoked once per tick until a process is explicitly aborted or it
|
||||
* terminates either with or without errors. Even though it's not mandatory to
|
||||
* declare this member function, as a rule of thumb each process should at
|
||||
* least define it to work properly.
|
||||
* least define it to work properly. The `void *` parameter is an opaque
|
||||
* pointer to user data (if any) forwarded directly to the process during an
|
||||
* update.
|
||||
*
|
||||
* * @code{.cpp}
|
||||
* void init();
|
||||
* void init(void *);
|
||||
* @endcode
|
||||
* It's invoked at the first tick, immediately before an update.
|
||||
*
|
||||
* It's invoked at the first tick, immediately before an update. The `void *`
|
||||
* parameter is an opaque pointer to user data (if any) forwarded directly to
|
||||
* the process during an update.
|
||||
*
|
||||
* * @code{.cpp}
|
||||
* void succeeded();
|
||||
* @endcode
|
||||
*
|
||||
* It's invoked in case of success, immediately after an update and during the
|
||||
* same tick.
|
||||
*
|
||||
* * @code{.cpp}
|
||||
* void failed();
|
||||
* @endcode
|
||||
*
|
||||
* It's invoked in case of errors, immediately after an update and during the
|
||||
* same tick.
|
||||
*
|
||||
* * @code{.cpp}
|
||||
* void aborted();
|
||||
* @endcode
|
||||
*
|
||||
* It's invoked only if a process is explicitly aborted. There is no guarantee
|
||||
* that it executes in the same tick, this depends solely on whether the
|
||||
* process is aborted immediately or not.
|
||||
@@ -82,17 +69,30 @@ struct BaseProcess {
|
||||
* @tparam Delta Type to use to provide elapsed time.
|
||||
*/
|
||||
template<typename Derived, typename Delta>
|
||||
class Process: private BaseProcess {
|
||||
class Process {
|
||||
enum class State: unsigned int {
|
||||
UNINITIALIZED = 0,
|
||||
RUNNING,
|
||||
PAUSED,
|
||||
SUCCEEDED,
|
||||
FAILED,
|
||||
ABORTED,
|
||||
FINISHED
|
||||
};
|
||||
|
||||
template<State state>
|
||||
using tag = std::integral_constant<State, state>;
|
||||
|
||||
template<typename Target = Derived>
|
||||
auto tick(int, tag<State::UNINITIALIZED>)
|
||||
-> decltype(std::declval<Target>().init()) {
|
||||
static_cast<Target *>(this)->init();
|
||||
auto tick(int, tag<State::UNINITIALIZED>, void *data)
|
||||
-> decltype(std::declval<Target>().init(data)) {
|
||||
static_cast<Target *>(this)->init(data);
|
||||
}
|
||||
|
||||
template<typename Target = Derived>
|
||||
auto tick(int, tag<State::RUNNING>, Delta delta)
|
||||
-> decltype(std::declval<Target>().update(delta)) {
|
||||
static_cast<Target *>(this)->update(delta);
|
||||
auto tick(int, tag<State::RUNNING>, Delta delta, void *data)
|
||||
-> decltype(std::declval<Target>().update(delta, data)) {
|
||||
static_cast<Target *>(this)->update(delta, data);
|
||||
}
|
||||
|
||||
template<typename Target = Derived>
|
||||
@@ -114,7 +114,7 @@ class Process: private BaseProcess {
|
||||
}
|
||||
|
||||
template<State S, typename... Args>
|
||||
void tick(char, tag<S>, Args&&...) {}
|
||||
void tick(char, tag<S>, Args &&...) {}
|
||||
|
||||
protected:
|
||||
/**
|
||||
@@ -170,7 +170,7 @@ public:
|
||||
using delta_type = Delta;
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
~Process() noexcept {
|
||||
virtual ~Process() noexcept {
|
||||
static_assert(std::is_base_of<Process, Derived>::value, "!");
|
||||
}
|
||||
|
||||
@@ -227,15 +227,16 @@ public:
|
||||
/**
|
||||
* @brief Updates a process and its internal state if required.
|
||||
* @param delta Elapsed time.
|
||||
* @param data Optional data.
|
||||
*/
|
||||
void tick(Delta delta) {
|
||||
void tick(Delta delta, void *data = nullptr) {
|
||||
switch (current) {
|
||||
case State::UNINITIALIZED:
|
||||
tick(0, tag<State::UNINITIALIZED>{});
|
||||
tick(0, tag<State::UNINITIALIZED>{}, data);
|
||||
current = State::RUNNING;
|
||||
// no break on purpose, tasks are executed immediately
|
||||
case State::RUNNING:
|
||||
tick(0, tag<State::RUNNING>{}, delta);
|
||||
tick(0, tag<State::RUNNING>{}, delta, data);
|
||||
default:
|
||||
// suppress warnings
|
||||
break;
|
||||
@@ -281,12 +282,13 @@ private:
|
||||
* following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(Delta delta, auto succeed, auto fail);
|
||||
* void(Delta delta, void *data, auto succeed, auto fail);
|
||||
* @endcode
|
||||
*
|
||||
* Where:
|
||||
*
|
||||
* * `delta` is the elapsed time.
|
||||
* * `data` is an opaque pointer to user data if any, `nullptr` otherwise.
|
||||
* * `succeed` is a function to call when a process terminates with success.
|
||||
* * `fail` is a function to call when a process terminates with errors.
|
||||
*
|
||||
@@ -315,16 +317,17 @@ struct ProcessAdaptor: Process<ProcessAdaptor<Func, Delta>, Delta>, private Func
|
||||
* @param args Parameters to use to initialize the actual process.
|
||||
*/
|
||||
template<typename... Args>
|
||||
ProcessAdaptor(Args&&... args)
|
||||
ProcessAdaptor(Args &&... args)
|
||||
: Func{std::forward<Args>(args)...}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Updates a process and its internal state if required.
|
||||
* @param delta Elapsed time.
|
||||
* @param data Optional data.
|
||||
*/
|
||||
void update(Delta delta) {
|
||||
Func::operator()(delta, [this](){ this->succeed(); }, [this](){ this->fail(); });
|
||||
void update(Delta delta, void *data) {
|
||||
Func::operator()(delta, data, [this]() { this->succeed(); }, [this]() { this->fail(); });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace entt {
|
||||
* Example of use (pseudocode):
|
||||
*
|
||||
* @code{.cpp}
|
||||
* scheduler.attach([](auto delta, auto succeed, auto fail) {
|
||||
* scheduler.attach([](auto delta, void *, auto succeed, auto fail) {
|
||||
* // code
|
||||
* }).then<MyProcess>(arguments...);
|
||||
* @endcode
|
||||
@@ -47,7 +47,7 @@ class Scheduler final {
|
||||
|
||||
struct ProcessHandler final {
|
||||
using instance_type = std::unique_ptr<void, void(*)(void *)>;
|
||||
using update_type = bool(*)(ProcessHandler &, Delta);
|
||||
using update_type = bool(*)(ProcessHandler &, Delta, void *);
|
||||
using abort_type = void(*)(ProcessHandler &, bool);
|
||||
using next_type = std::unique_ptr<ProcessHandler>;
|
||||
|
||||
@@ -64,7 +64,7 @@ class Scheduler final {
|
||||
{}
|
||||
|
||||
template<typename Proc, typename... Args>
|
||||
decltype(auto) then(Args&&... args) && {
|
||||
decltype(auto) then(Args &&... args) && {
|
||||
static_assert(std::is_base_of<Process<Proc, Delta>, Proc>::value, "!");
|
||||
handler = Lambda::operator()(handler, tag<Proc>{}, std::forward<Args>(args)...);
|
||||
return std::move(*this);
|
||||
@@ -81,16 +81,16 @@ class Scheduler final {
|
||||
};
|
||||
|
||||
template<typename Proc>
|
||||
static bool update(ProcessHandler &handler, Delta delta) {
|
||||
static bool update(ProcessHandler &handler, Delta delta, void *data) {
|
||||
auto *process = static_cast<Proc *>(handler.instance.get());
|
||||
process->tick(delta);
|
||||
process->tick(delta, data);
|
||||
|
||||
auto dead = process->dead();
|
||||
|
||||
if(dead) {
|
||||
if(handler.next && !process->rejected()) {
|
||||
handler = std::move(*handler.next);
|
||||
dead = handler.update(handler, delta);
|
||||
dead = handler.update(handler, delta, data);
|
||||
} else {
|
||||
handler.instance.reset();
|
||||
}
|
||||
@@ -110,11 +110,11 @@ class Scheduler final {
|
||||
}
|
||||
|
||||
auto then(ProcessHandler *handler) {
|
||||
auto lambda = [this](ProcessHandler *handler, auto next, auto... args) {
|
||||
auto lambda = [](ProcessHandler *handler, auto next, auto... args) {
|
||||
using Proc = typename decltype(next)::type;
|
||||
|
||||
if(handler) {
|
||||
auto proc = typename ProcessHandler::instance_type{ new Proc{std::forward<decltype(args)>(args)...}, &Scheduler::deleter<Proc> };
|
||||
auto proc = typename ProcessHandler::instance_type{new Proc{std::forward<decltype(args)>(args)...}, &Scheduler::deleter<Proc>};
|
||||
handler->next.reset(new ProcessHandler{std::move(proc), &Scheduler::update<Proc>, &Scheduler::abort<Proc>, nullptr});
|
||||
handler = handler->next.get();
|
||||
}
|
||||
@@ -181,7 +181,7 @@ public:
|
||||
* // schedules a task in the form of a process class
|
||||
* scheduler.attach<MyProcess>(arguments...)
|
||||
* // appends a child in the form of a lambda function
|
||||
* .then([](auto delta, auto succeed, auto fail) {
|
||||
* .then([](auto delta, void *, auto succeed, auto fail) {
|
||||
* // code
|
||||
* })
|
||||
* // appends a child in the form of another process class
|
||||
@@ -194,10 +194,10 @@ public:
|
||||
* @return An opaque object to use to concatenate processes.
|
||||
*/
|
||||
template<typename Proc, typename... Args>
|
||||
auto attach(Args&&... args) {
|
||||
auto attach(Args &&... args) {
|
||||
static_assert(std::is_base_of<Process<Proc, Delta>, Proc>::value, "!");
|
||||
|
||||
auto proc = typename ProcessHandler::instance_type{ new Proc{std::forward<Args>(args)...}, &Scheduler::deleter<Proc> };
|
||||
auto proc = typename ProcessHandler::instance_type{new Proc{std::forward<Args>(args)...}, &Scheduler::deleter<Proc>};
|
||||
ProcessHandler handler{std::move(proc), &Scheduler::update<Proc>, &Scheduler::abort<Proc>, nullptr};
|
||||
handlers.push_back(std::move(handler));
|
||||
|
||||
@@ -237,11 +237,11 @@ public:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* // schedules a task in the form of a lambda function
|
||||
* scheduler.attach([](auto delta, auto succeed, auto fail) {
|
||||
* scheduler.attach([](auto delta, void *, auto succeed, auto fail) {
|
||||
* // code
|
||||
* })
|
||||
* // appends a child in the form of another lambda function
|
||||
* .then([](auto delta, auto succeed, auto fail) {
|
||||
* .then([](auto delta, void *, auto succeed, auto fail) {
|
||||
* // code
|
||||
* })
|
||||
* // appends a child in the form of a process class
|
||||
@@ -269,18 +269,19 @@ public:
|
||||
* with its child.
|
||||
*
|
||||
* @param delta Elapsed time.
|
||||
* @param data Optional data.
|
||||
*/
|
||||
void update(Delta delta) {
|
||||
void update(Delta delta, void *data = nullptr) {
|
||||
bool clean = false;
|
||||
|
||||
for(auto i = handlers.size(); i > 0; --i) {
|
||||
auto &handler = handlers[i-1];
|
||||
const bool dead = handler.update(handler, delta);
|
||||
for(auto pos = handlers.size(); pos; --pos) {
|
||||
auto &handler = handlers[pos-1];
|
||||
const bool dead = handler.update(handler, delta, data);
|
||||
clean = clean || dead;
|
||||
}
|
||||
|
||||
if(clean) {
|
||||
handlers.erase(std::remove_if(handlers.begin(), handlers.end(), [delta](auto &handler) {
|
||||
handlers.erase(std::remove_if(handlers.begin(), handlers.end(), [](auto &handler) {
|
||||
return !handler.instance;
|
||||
}), handlers.end());
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Loads the resource that corresponds to the given identifier.
|
||||
* @brief Loads the resource that corresponds to a given identifier.
|
||||
*
|
||||
* In case an identifier isn't already present in the cache, it loads its
|
||||
* resource and stores it aside for future uses. Arguments are forwarded
|
||||
@@ -92,7 +92,7 @@ public:
|
||||
* @return True if the resource is ready to use, false otherwise.
|
||||
*/
|
||||
template<typename Loader, typename... Args>
|
||||
bool load(resource_type id, Args&&... args) {
|
||||
bool load(resource_type id, Args &&... args) {
|
||||
static_assert(std::is_base_of<ResourceLoader<Loader, Resource>, Loader>::value, "!");
|
||||
|
||||
bool loaded = true;
|
||||
@@ -125,12 +125,29 @@ public:
|
||||
* @return True if the resource is ready to use, false otherwise.
|
||||
*/
|
||||
template<typename Loader, typename... Args>
|
||||
void reload(resource_type id, Args&&... args) {
|
||||
return (discard(id), load(id, std::forward<Args>(args)...));
|
||||
bool reload(resource_type id, Args &&... args) {
|
||||
return (discard(id), load<Loader>(id, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a handle for the given resource identifier.
|
||||
* @brief Creates a temporary handle for a resource.
|
||||
*
|
||||
* Arguments are forwarded directly to the loader in order to construct
|
||||
* properly the requested resource. The handle isn't stored aside and the
|
||||
* cache isn't in charge of the lifetime of the resource itself.
|
||||
*
|
||||
* @tparam Loader Type of loader to use to load the resource.
|
||||
* @tparam Args Types of arguments to use to load the resource.
|
||||
* @param args Arguments to use to load the resource.
|
||||
* @return A handle for the given resource.
|
||||
*/
|
||||
template<typename Loader, typename... Args>
|
||||
ResourceHandle<Resource> temp(Args &&... args) const {
|
||||
return { Loader{}.get(std::forward<Args>(args)...) };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a handle for a given resource identifier.
|
||||
*
|
||||
* A resource handle can be in a either valid or invalid state. In other
|
||||
* terms, a resource handle is properly initialized with a resource if the
|
||||
@@ -148,7 +165,7 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a cache contains the given identifier.
|
||||
* @brief Checks if a cache contains a given identifier.
|
||||
* @param id Unique resource identifier.
|
||||
* @return True if the cache contains the resource, false otherwise.
|
||||
*/
|
||||
@@ -157,7 +174,7 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Discards the resource that corresponds to the given identifier.
|
||||
* @brief Discards the resource that corresponds to a given identifier.
|
||||
*
|
||||
* Handles are not invalidated and the memory used by the resource isn't
|
||||
* freed as long as at least a handle keeps the resource itself alive.
|
||||
|
||||
@@ -17,7 +17,7 @@ class ResourceCache;
|
||||
*
|
||||
* Resource loaders must inherit from this class and stay true to the CRTP
|
||||
* idiom. Moreover, a resource loader must expose a public, const member
|
||||
* function named `load` that accepts a variable number of arguments and return
|
||||
* function named `load` that accepts a variable number of arguments and returns
|
||||
* a shared pointer to the resource just created.<br/>
|
||||
* As an example:
|
||||
*
|
||||
@@ -50,7 +50,7 @@ class ResourceLoader {
|
||||
friend class ResourceCache<Resource>;
|
||||
|
||||
template<typename... Args>
|
||||
std::shared_ptr<Resource> get(Args&&... args) const {
|
||||
std::shared_ptr<Resource> get(Args &&... args) const {
|
||||
return static_cast<const Loader *>(this)->load(std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -142,7 +142,7 @@ public:
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename Type, typename... Args>
|
||||
void publish(Args&&... args) {
|
||||
void publish(Args &&... args) {
|
||||
Bus<Sig, Type>::publish(std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
@@ -263,7 +263,7 @@ public:
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename... Args>
|
||||
void publish(Args&&... args) {
|
||||
void publish(Args &&... args) {
|
||||
signal.publish({ std::forward<Args>(args)... });
|
||||
}
|
||||
|
||||
|
||||
@@ -40,8 +40,8 @@ class Dispatcher final {
|
||||
|
||||
template<typename Event>
|
||||
struct SignalWrapper final: BaseSignalWrapper {
|
||||
void publish(std::size_t current) final override {
|
||||
for(auto &&event: events[current]) {
|
||||
void publish(std::size_t current) override {
|
||||
for(const auto &event: events[current]) {
|
||||
signal.publish(event);
|
||||
}
|
||||
|
||||
@@ -59,12 +59,12 @@ class Dispatcher final {
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void trigger(Args&&... args) {
|
||||
inline void trigger(Args &&... args) {
|
||||
signal.publish({ std::forward<Args>(args)... });
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void enqueue(std::size_t current, Args&&... args) {
|
||||
inline void enqueue(std::size_t current, Args &&... args) {
|
||||
events[current].push_back({ std::forward<Args>(args)... });
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ class Dispatcher final {
|
||||
|
||||
template<typename Event>
|
||||
SignalWrapper<Event> & wrapper() {
|
||||
auto type = event_family::type<Event>();
|
||||
const auto type = event_family::type<Event>();
|
||||
|
||||
if(!(type < wrappers.size())) {
|
||||
wrappers.resize(type + 1);
|
||||
@@ -127,8 +127,9 @@ public:
|
||||
* 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.
|
||||
* Disconnecting 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.
|
||||
@@ -151,7 +152,7 @@ public:
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename Event, typename... Args>
|
||||
void trigger(Args&&... args) {
|
||||
void trigger(Args &&... args) {
|
||||
wrapper<Event>().trigger(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
@@ -166,7 +167,7 @@ public:
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename Event, typename... Args>
|
||||
void enqueue(Args&&... args) {
|
||||
void enqueue(Args &&... args) {
|
||||
wrapper<Event>().enqueue(buffer(mode), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
@@ -178,10 +179,12 @@ public:
|
||||
* to reduce at a minimum the time spent in the bodies of the listeners.
|
||||
*/
|
||||
void update() {
|
||||
auto buf = buffer(mode);
|
||||
const auto buf = buffer(mode);
|
||||
mode = !mode;
|
||||
|
||||
for(auto &&wrapper: wrappers) {
|
||||
for(auto pos = wrappers.size(); pos; --pos) {
|
||||
auto &wrapper = wrappers[pos-1];
|
||||
|
||||
if(wrapper) {
|
||||
wrapper->publish(buf);
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ class Emitter {
|
||||
using connection_type = typename container_type::iterator;
|
||||
|
||||
bool empty() const noexcept override {
|
||||
auto pred = [](auto &&element){ return element.first; };
|
||||
auto pred = [](auto &&element) { return element.first; };
|
||||
|
||||
return std::all_of(onceL.cbegin(), onceL.cend(), pred) &&
|
||||
std::all_of(onL.cbegin(), onL.cend(), pred);
|
||||
@@ -60,7 +60,7 @@ class Emitter {
|
||||
|
||||
void clear() noexcept override {
|
||||
if(publishing) {
|
||||
auto func = [](auto &&element){ element.first = true; };
|
||||
auto func = [](auto &&element) { element.first = true; };
|
||||
std::for_each(onceL.begin(), onceL.end(), func);
|
||||
std::for_each(onL.begin(), onL.end(), func);
|
||||
} else {
|
||||
@@ -81,7 +81,7 @@ class Emitter {
|
||||
conn->first = true;
|
||||
|
||||
if(!publishing) {
|
||||
auto pred = [](auto &&element){ return element.first; };
|
||||
auto pred = [](auto &&element) { return element.first; };
|
||||
onceL.remove_if(pred);
|
||||
onL.remove_if(pred);
|
||||
}
|
||||
@@ -102,7 +102,7 @@ class Emitter {
|
||||
|
||||
publishing = false;
|
||||
|
||||
onL.remove_if([](auto &&element){ return element.first; });
|
||||
onL.remove_if([](auto &&element) { return element.first; });
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -124,7 +124,7 @@ class Emitter {
|
||||
|
||||
template<typename Event>
|
||||
Handler<Event> & handler() noexcept {
|
||||
std::size_t family = type<Event>();
|
||||
const std::size_t family = type<Event>();
|
||||
|
||||
if(!(family < handlers.size())) {
|
||||
handlers.resize(family+1);
|
||||
@@ -215,7 +215,7 @@ public:
|
||||
* @param args Parameters to use to initialize the event.
|
||||
*/
|
||||
template<typename Event, typename... Args>
|
||||
void publish(Args&&... args) {
|
||||
void publish(Args &&... args) {
|
||||
handler<Event>().publish({ std::forward<Args>(args)... }, *static_cast<Derived *>(this));
|
||||
}
|
||||
|
||||
@@ -287,7 +287,7 @@ public:
|
||||
* @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.
|
||||
* invalidated. Using them results in undefined behavior.
|
||||
*
|
||||
* @tparam Event Type of event to reset.
|
||||
*/
|
||||
@@ -300,11 +300,11 @@ public:
|
||||
* @brief Disconnects all the listeners.
|
||||
*
|
||||
* All the connections previously returned are invalidated. Using them
|
||||
* results in undefined behaviour.
|
||||
* results in undefined behavior.
|
||||
*/
|
||||
void clear() noexcept {
|
||||
std::for_each(handlers.begin(), handlers.end(),
|
||||
[](auto &&handler){ if(handler) { handler->clear(); } });
|
||||
[](auto &&handler) { if(handler) { handler->clear(); } });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -314,7 +314,7 @@ public:
|
||||
*/
|
||||
template<typename Event>
|
||||
bool empty() const noexcept {
|
||||
std::size_t family = type<Event>();
|
||||
const std::size_t family = type<Event>();
|
||||
|
||||
return (!(family < handlers.size()) ||
|
||||
!handlers[family] ||
|
||||
@@ -327,7 +327,7 @@ public:
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
return std::all_of(handlers.cbegin(), handlers.cend(),
|
||||
[](auto &&handler){ return !handler || handler->empty(); });
|
||||
[](auto &&handler) { return !handler || handler->empty(); });
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -24,21 +24,25 @@ struct Invoker<Ret(Args...), Collector> {
|
||||
|
||||
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) {
|
||||
bool invoke(Collector &collector, proto_type proto, void *instance, Args... args) {
|
||||
return collector(proto(instance, args...));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename... Args, typename Collector>
|
||||
struct Invoker<void(Args...), Collector> {
|
||||
using proto_type = void(*)(void *, Args...);
|
||||
using call_type = std::pair<void *, proto_type>;
|
||||
|
||||
virtual ~Invoker() = default;
|
||||
|
||||
bool invoke(Collector &, proto_type proto, void *instance, Args... args) {
|
||||
return (proto(instance, args...), true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename Ret>
|
||||
struct NullCollector final {
|
||||
using result_type = Ret;
|
||||
@@ -100,7 +104,7 @@ class SigH;
|
||||
*
|
||||
* * `Param` is a type to which `Ret` can be converted.
|
||||
* * The return type is true if the handler must stop collecting data, false
|
||||
* otherwise.
|
||||
* otherwise.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
@@ -229,7 +233,8 @@ public:
|
||||
* @param args Arguments to use to invoke listeners.
|
||||
*/
|
||||
void publish(Args... args) {
|
||||
for(auto &&call: calls) {
|
||||
for(auto pos = calls.size(); pos; --pos) {
|
||||
auto &call = calls[pos-1];
|
||||
call.second(call.first, args...);
|
||||
}
|
||||
}
|
||||
@@ -242,7 +247,9 @@ public:
|
||||
collector_type collect(Args... args) {
|
||||
collector_type collector;
|
||||
|
||||
for(auto &&call: calls) {
|
||||
for(auto pos = calls.size(); pos; --pos) {
|
||||
auto &call = calls[pos-1];
|
||||
|
||||
if(!this->invoke(collector, call.second, call.first, args...)) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -167,11 +167,17 @@ public:
|
||||
* @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());
|
||||
std::vector<call_type> next;
|
||||
|
||||
for(auto pos = calls.size(); pos; --pos) {
|
||||
auto &call = calls[pos-1];
|
||||
|
||||
if((call.second)(call.first, args...)) {
|
||||
next.push_back(call);
|
||||
}
|
||||
}
|
||||
|
||||
calls.swap(next);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,22 +2,59 @@
|
||||
# Tests configuration
|
||||
#
|
||||
|
||||
include_directories(${PROJECT_SRC_DIR})
|
||||
|
||||
add_library(odr OBJECT odr.cpp)
|
||||
|
||||
# Test benchmark
|
||||
|
||||
if(CMAKE_BUILD_TYPE MATCHES Release)
|
||||
if(BUILD_BENCHMARK)
|
||||
add_executable(
|
||||
benchmark
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/entity/benchmark.cpp
|
||||
benchmark/benchmark.cpp
|
||||
)
|
||||
target_link_libraries(benchmark PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME benchmark COMMAND benchmark)
|
||||
endif()
|
||||
|
||||
# Test mod
|
||||
|
||||
if(BUILD_MOD)
|
||||
set(DUKTAPE_DEPS_DIR ${entt_SOURCE_DIR}/deps/duktape)
|
||||
configure_file(${entt_SOURCE_DIR}/cmake/in/duktape.in ${DUKTAPE_DEPS_DIR}/CMakeLists.txt)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY ${DUKTAPE_DEPS_DIR})
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} --build . WORKING_DIRECTORY ${DUKTAPE_DEPS_DIR})
|
||||
set(DUKTAPE_SRC_DIR ${DUKTAPE_DEPS_DIR}/src/src)
|
||||
|
||||
add_executable(
|
||||
mod
|
||||
$<TARGET_OBJECTS:odr>
|
||||
${DUKTAPE_SRC_DIR}/duktape.c
|
||||
mod/mod.cpp
|
||||
)
|
||||
target_include_directories(mod PRIVATE ${DUKTAPE_SRC_DIR})
|
||||
target_link_libraries(mod PRIVATE gtest_main Threads::Threads m)
|
||||
add_test(NAME mod COMMAND mod)
|
||||
endif()
|
||||
|
||||
# Test snapshot
|
||||
|
||||
if(BUILD_SNAPSHOT)
|
||||
set(CEREAL_DEPS_DIR ${entt_SOURCE_DIR}/deps/cereal)
|
||||
configure_file(${entt_SOURCE_DIR}/cmake/in/cereal.in ${CEREAL_DEPS_DIR}/CMakeLists.txt)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY ${CEREAL_DEPS_DIR})
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} --build . WORKING_DIRECTORY ${CEREAL_DEPS_DIR})
|
||||
set(CEREAL_SRC_DIR ${CEREAL_DEPS_DIR}/src/include)
|
||||
|
||||
add_executable(
|
||||
snapshot
|
||||
$<TARGET_OBJECTS:odr>
|
||||
snapshot/snapshot.cpp
|
||||
)
|
||||
target_include_directories(snapshot PRIVATE ${CEREAL_SRC_DIR})
|
||||
target_link_libraries(snapshot PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME snapshot COMMAND snapshot)
|
||||
endif()
|
||||
|
||||
# Test core
|
||||
|
||||
add_executable(
|
||||
@@ -37,6 +74,7 @@ add_executable(
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/entity/actor.cpp
|
||||
entt/entity/registry.cpp
|
||||
entt/entity/snapshot.cpp
|
||||
entt/entity/sparse_set.cpp
|
||||
entt/entity/view.cpp
|
||||
)
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <iostream>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
struct Position {
|
||||
uint64_t x;
|
||||
uint64_t y;
|
||||
std::uint64_t x;
|
||||
std::uint64_t y;
|
||||
};
|
||||
|
||||
struct Velocity {
|
||||
uint64_t x;
|
||||
uint64_t y;
|
||||
std::uint64_t x;
|
||||
std::uint64_t y;
|
||||
};
|
||||
|
||||
template<std::size_t>
|
||||
struct Comp {};
|
||||
struct Comp { int x; };
|
||||
|
||||
struct Timer final {
|
||||
Timer(): start{std::chrono::system_clock::now()} {}
|
||||
@@ -33,11 +33,11 @@ private:
|
||||
TEST(Benchmark, Construct) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Constructing 10000000 entities" << std::endl;
|
||||
std::cout << "Constructing 1000000 entities" << std::endl;
|
||||
|
||||
Timer timer;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
registry.create();
|
||||
}
|
||||
|
||||
@@ -46,19 +46,18 @@ TEST(Benchmark, Construct) {
|
||||
|
||||
TEST(Benchmark, Destroy) {
|
||||
entt::DefaultRegistry registry;
|
||||
std::vector<entt::DefaultRegistry::entity_type> entities{};
|
||||
|
||||
std::cout << "Destroying 10000000 entities" << std::endl;
|
||||
std::cout << "Destroying 1000000 entities" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
entities.push_back(registry.create());
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
registry.create();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
for(auto entity: entities) {
|
||||
registry.each([®istry](auto entity) {
|
||||
registry.destroy(entity);
|
||||
}
|
||||
});
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
@@ -78,21 +77,24 @@ TEST(Benchmark, IterateCreateDeleteSingleComponent) {
|
||||
}
|
||||
|
||||
for(auto entity: view) {
|
||||
const auto &position = view.get(entity);
|
||||
(void)position;
|
||||
|
||||
if(rand() % 2 == 0) {
|
||||
registry.destroy(entity);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateSingleComponent10M) {
|
||||
TEST(Benchmark, IterateSingleComponent1M) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, one component" << std::endl;
|
||||
std::cout << "Iterating over 1000000 entities, one component" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
registry.create<Position>();
|
||||
}
|
||||
|
||||
@@ -101,12 +103,28 @@ TEST(Benchmark, IterateSingleComponent10M) {
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTwoComponents10M) {
|
||||
TEST(Benchmark, IterateSingleComponentRaw1M) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components" << std::endl;
|
||||
std::cout << "Iterating over 1000000 entities, one component, raw view" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
registry.create<Position>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
for(auto &&component: registry.raw<Position>()) {
|
||||
(void)component;
|
||||
}
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTwoComponents1M) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 1000000 entities, two components" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
registry.create<Position, Velocity>();
|
||||
}
|
||||
|
||||
@@ -115,12 +133,12 @@ TEST(Benchmark, IterateTwoComponents10M) {
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTwoComponents10MHalf) {
|
||||
TEST(Benchmark, IterateTwoComponents1MHalf) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components, half of the entities have all the components" << std::endl;
|
||||
std::cout << "Iterating over 1000000 entities, two components, half of the entities have all the components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
auto entity = registry.create<Velocity>();
|
||||
if(i % 2) { registry.assign<Position>(entity); }
|
||||
}
|
||||
@@ -130,12 +148,12 @@ TEST(Benchmark, IterateTwoComponents10MHalf) {
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTwoComponents10MOne) {
|
||||
TEST(Benchmark, IterateTwoComponents1MOne) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components, only one entity has all the components" << std::endl;
|
||||
std::cout << "Iterating over 1000000 entities, two components, only one entity has all the components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
auto entity = registry.create<Velocity>();
|
||||
if(i == 5000000L) { registry.assign<Position>(entity); }
|
||||
}
|
||||
@@ -145,13 +163,13 @@ TEST(Benchmark, IterateTwoComponents10MOne) {
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTwoComponentsPersistent10M) {
|
||||
TEST(Benchmark, IterateTwoComponentsPersistent1M) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components, persistent view" << std::endl;
|
||||
std::cout << "Iterating over 1000000 entities, two components, persistent view" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
registry.create<Position, Velocity>();
|
||||
}
|
||||
|
||||
@@ -160,44 +178,12 @@ TEST(Benchmark, IterateTwoComponentsPersistent10M) {
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, 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(Benchmark, 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(Benchmark, IterateFiveComponents10M) {
|
||||
TEST(Benchmark, IterateFiveComponents1M) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, five components" << std::endl;
|
||||
std::cout << "Iterating over 1000000 entities, five components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
|
||||
}
|
||||
|
||||
@@ -206,57 +192,43 @@ TEST(Benchmark, IterateFiveComponents10M) {
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTenComponents10M) {
|
||||
TEST(Benchmark, IterateFiveComponents1MHalf) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components" << std::endl;
|
||||
std::cout << "Iterating over 1000000 entities, five components, half of the entities have all the 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(Benchmark, 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>>();
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>>();
|
||||
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 &...) {});
|
||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTenComponents10MOne) {
|
||||
TEST(Benchmark, IterateFiveComponents1MOne) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components, only one entity has all the components" << std::endl;
|
||||
std::cout << "Iterating over 1000000 entities, five 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>>();
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>>();
|
||||
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 &...) {});
|
||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateFiveComponentsPersistent10M) {
|
||||
TEST(Benchmark, IterateFiveComponentsPersistent1M) {
|
||||
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;
|
||||
std::cout << "Iterating over 1000000 entities, five components, persistent view" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
|
||||
}
|
||||
|
||||
@@ -265,46 +237,58 @@ TEST(Benchmark, IterateFiveComponentsPersistent10M) {
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTenComponentsPersistent10M) {
|
||||
TEST(Benchmark, IterateTenComponents1M) {
|
||||
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;
|
||||
std::cout << "Iterating over 1000000 entities, ten components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
for(std::uint64_t i = 0; i < 1000000L; 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 &...) {});
|
||||
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(Benchmark, IterateTenComponentsPersistent10MHalf) {
|
||||
TEST(Benchmark, IterateTenComponents1MHalf) {
|
||||
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;
|
||||
std::cout << "Iterating over 1000000 entities, ten components, half of the entities have all the components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
for(std::uint64_t i = 0; i < 1000000L; 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 &...) {});
|
||||
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(Benchmark, IterateTenComponentsPersistent10MOne) {
|
||||
TEST(Benchmark, IterateTenComponents1MOne) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 1000000 entities, ten components, only one entity has all the components" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 1000000L; 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(Benchmark, IterateTenComponentsPersistent1M) {
|
||||
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;
|
||||
std::cout << "Iterating over 1000000 entities, ten components, persistent view" << 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); }
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
@@ -314,13 +298,11 @@ TEST(Benchmark, IterateTenComponentsPersistent10MOne) {
|
||||
|
||||
TEST(Benchmark, 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);
|
||||
for(std::uint64_t i = 0; i < 150000L; i++) {
|
||||
registry.create<Position>({ i, i });
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
@@ -334,13 +316,11 @@ TEST(Benchmark, SortSingle) {
|
||||
|
||||
TEST(Benchmark, 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);
|
||||
for(std::uint64_t i = 0; i < 150000L; i++) {
|
||||
registry.create<Position, Velocity>({ i, i }, { i, i });
|
||||
}
|
||||
|
||||
registry.sort<Position>([](const auto &lhs, const auto &rhs) {
|
||||
@@ -1,7 +1,18 @@
|
||||
#include <cstddef>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/hashed_string.hpp>
|
||||
|
||||
constexpr bool check(const char *str) {
|
||||
constexpr bool ptr(const char *str) {
|
||||
using hash_type = entt::HashedString::hash_type;
|
||||
|
||||
return (static_cast<hash_type>(entt::HashedString{str}) == entt::HashedString{str}
|
||||
&& static_cast<const char *>(entt::HashedString{str}) == str
|
||||
&& entt::HashedString{str} == entt::HashedString{str}
|
||||
&& !(entt::HashedString{str} != entt::HashedString{str}));
|
||||
}
|
||||
|
||||
template<std::size_t N>
|
||||
constexpr bool ref(const char (&str)[N]) {
|
||||
using hash_type = entt::HashedString::hash_type;
|
||||
|
||||
return (static_cast<hash_type>(entt::HashedString{str}) == entt::HashedString{str}
|
||||
@@ -11,8 +22,9 @@ constexpr bool check(const char *str) {
|
||||
}
|
||||
|
||||
TEST(HashedString, Constexprness) {
|
||||
// how would you test a constepxr otherwise?
|
||||
static_assert(check("foobar"), "!");
|
||||
// how would you test a constexpr otherwise?
|
||||
static_assert(ptr("foo"), "!");
|
||||
static_assert(ref("bar"), "!");
|
||||
ASSERT_TRUE(true);
|
||||
}
|
||||
|
||||
@@ -21,15 +33,15 @@ TEST(HashedString, Functionalities) {
|
||||
|
||||
const char *bar = "bar";
|
||||
|
||||
auto fooHs = entt::HashedString("foo");
|
||||
auto barHs = entt::HashedString(bar);
|
||||
auto fooHs = entt::HashedString{"foo"};
|
||||
auto barHs = entt::HashedString{bar};
|
||||
|
||||
ASSERT_NE(static_cast<hash_type>(fooHs), static_cast<hash_type>(barHs));
|
||||
ASSERT_EQ(static_cast<const char *>(fooHs), "foo");
|
||||
ASSERT_EQ(static_cast<const char *>(barHs), bar);
|
||||
|
||||
ASSERT_TRUE(fooHs == fooHs);
|
||||
ASSERT_FALSE(fooHs == barHs);
|
||||
ASSERT_TRUE(fooHs != barHs);
|
||||
|
||||
entt::HashedString hs{"foobar"};
|
||||
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/entt_traits.hpp>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
TEST(DefaultRegistry, Functionalities) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{0});
|
||||
ASSERT_NO_THROW(registry.reserve(42));
|
||||
ASSERT_NO_THROW(registry.reserve<int>(8));
|
||||
ASSERT_NO_THROW(registry.reserve<char>(8));
|
||||
ASSERT_TRUE(registry.empty());
|
||||
|
||||
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{0});
|
||||
@@ -14,8 +21,11 @@ TEST(DefaultRegistry, Functionalities) {
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
ASSERT_TRUE(registry.empty<char>());
|
||||
|
||||
auto e1 = registry.create();
|
||||
auto e2 = registry.create<int, char>();
|
||||
auto e0 = registry.create();
|
||||
auto e1 = registry.create<int, char>();
|
||||
|
||||
ASSERT_TRUE(registry.has<>(e0));
|
||||
ASSERT_TRUE(registry.has<>(e1));
|
||||
|
||||
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{2});
|
||||
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{1});
|
||||
@@ -23,63 +33,70 @@ TEST(DefaultRegistry, Functionalities) {
|
||||
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_NE(e0, e1);
|
||||
|
||||
ASSERT_FALSE(registry.has<int>(e0));
|
||||
ASSERT_TRUE(registry.has<int>(e1));
|
||||
ASSERT_FALSE(registry.has<int>(e2));
|
||||
ASSERT_FALSE(registry.has<char>(e0));
|
||||
ASSERT_TRUE(registry.has<char>(e1));
|
||||
ASSERT_FALSE(registry.has<char>(e2));
|
||||
ASSERT_FALSE((registry.has<int, char>(e0)));
|
||||
ASSERT_TRUE((registry.has<int, char>(e1)));
|
||||
ASSERT_FALSE((registry.has<int, char>(e2)));
|
||||
|
||||
auto e3 = registry.create();
|
||||
ASSERT_EQ(registry.assign<int>(e0, 42), 42);
|
||||
ASSERT_EQ(registry.assign<char>(e0, 'c'), 'c');
|
||||
ASSERT_NO_THROW(registry.remove<int>(e1));
|
||||
ASSERT_NO_THROW(registry.remove<char>(e1));
|
||||
|
||||
registry.accomodate<int>(e3, registry.get<int>(e1));
|
||||
registry.accomodate<char>(e3, registry.get<char>(e1));
|
||||
ASSERT_TRUE(registry.has<int>(e0));
|
||||
ASSERT_FALSE(registry.has<int>(e1));
|
||||
ASSERT_TRUE(registry.has<char>(e0));
|
||||
ASSERT_FALSE(registry.has<char>(e1));
|
||||
ASSERT_TRUE((registry.has<int, char>(e0)));
|
||||
ASSERT_FALSE((registry.has<int, 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));
|
||||
auto e2 = registry.create();
|
||||
|
||||
ASSERT_NO_THROW(registry.replace<int>(e1, 0));
|
||||
ASSERT_EQ(registry.get<int>(e1), 0);
|
||||
registry.accommodate<int>(e2, registry.get<int>(e0));
|
||||
registry.accommodate<char>(e2, registry.get<char>(e0));
|
||||
|
||||
ASSERT_NO_THROW(registry.accomodate<int>(e1, 1));
|
||||
ASSERT_NO_THROW(registry.accomodate<int>(e2, 1));
|
||||
ASSERT_TRUE(registry.has<int>(e2));
|
||||
ASSERT_TRUE(registry.has<char>(e2));
|
||||
ASSERT_EQ(registry.get<int>(e0), 42);
|
||||
ASSERT_EQ(registry.get<char>(e0), 'c');
|
||||
|
||||
ASSERT_EQ(std::get<0>(registry.get<int, char>(e0)), 42);
|
||||
ASSERT_EQ(std::get<1>(static_cast<const entt::DefaultRegistry &>(registry).get<int, char>(e0)), 'c');
|
||||
|
||||
ASSERT_EQ(registry.get<int>(e0), registry.get<int>(e2));
|
||||
ASSERT_EQ(registry.get<char>(e0), registry.get<char>(e2));
|
||||
ASSERT_NE(®istry.get<int>(e0), ®istry.get<int>(e2));
|
||||
ASSERT_NE(®istry.get<char>(e0), ®istry.get<char>(e2));
|
||||
|
||||
ASSERT_NO_THROW(registry.replace<int>(e0, 0));
|
||||
ASSERT_EQ(registry.get<int>(e0), 0);
|
||||
|
||||
ASSERT_NO_THROW(registry.accommodate<int>(e0, 1));
|
||||
ASSERT_NO_THROW(registry.accommodate<int>(e1, 1));
|
||||
ASSERT_EQ(static_cast<const entt::DefaultRegistry &>(registry).get<int>(e0), 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.version(e2), entt::DefaultRegistry::version_type{0});
|
||||
ASSERT_EQ(registry.current(e2), entt::DefaultRegistry::version_type{0});
|
||||
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{3});
|
||||
ASSERT_NO_THROW(registry.destroy(e3));
|
||||
ASSERT_NO_THROW(registry.destroy(e2));
|
||||
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_EQ(registry.version(e2), entt::DefaultRegistry::version_type{0});
|
||||
ASSERT_EQ(registry.current(e2), entt::DefaultRegistry::version_type{1});
|
||||
|
||||
ASSERT_TRUE(registry.valid(e0));
|
||||
ASSERT_TRUE(registry.fast(e0));
|
||||
ASSERT_TRUE(registry.valid(e1));
|
||||
ASSERT_TRUE(registry.valid(e2));
|
||||
ASSERT_FALSE(registry.valid(e3));
|
||||
ASSERT_TRUE(registry.fast(e1));
|
||||
ASSERT_FALSE(registry.valid(e2));
|
||||
ASSERT_FALSE(registry.fast(e2));
|
||||
|
||||
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{2});
|
||||
ASSERT_FALSE(registry.empty());
|
||||
@@ -110,31 +127,165 @@ TEST(DefaultRegistry, Functionalities) {
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
ASSERT_TRUE(registry.empty<char>());
|
||||
|
||||
e1 = registry.create<int>();
|
||||
e2 = registry.create();
|
||||
e0 = registry.create<int>();
|
||||
e1 = registry.create();
|
||||
|
||||
ASSERT_NO_THROW(registry.reset<int>(e0));
|
||||
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) {
|
||||
TEST(DefaultRegistry, CreateDestroyCornerCase) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
auto pre = registry.create<double>();
|
||||
registry.destroy(pre);
|
||||
auto post = registry.create<double>();
|
||||
auto e0 = registry.create();
|
||||
auto e1 = registry.create();
|
||||
|
||||
registry.destroy(e0);
|
||||
registry.destroy(e1);
|
||||
|
||||
registry.each([](auto) { FAIL(); });
|
||||
|
||||
ASSERT_EQ(registry.current(e0), entt::DefaultRegistry::version_type{1});
|
||||
ASSERT_EQ(registry.current(e1), entt::DefaultRegistry::version_type{1});
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, VersionOverflow) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
auto entity = registry.create();
|
||||
registry.destroy(entity);
|
||||
|
||||
ASSERT_EQ(registry.version(entity), entt::DefaultRegistry::version_type{});
|
||||
|
||||
for(auto i = entt::entt_traits<entt::DefaultRegistry::entity_type>::version_mask; i; --i) {
|
||||
ASSERT_NE(registry.current(entity), registry.version(entity));
|
||||
registry.destroy(registry.create());
|
||||
}
|
||||
|
||||
ASSERT_EQ(registry.current(entity), registry.version(entity));
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, Each) {
|
||||
entt::DefaultRegistry registry;
|
||||
entt::DefaultRegistry::size_type tot;
|
||||
entt::DefaultRegistry::size_type match;
|
||||
|
||||
registry.create();
|
||||
registry.create<int>();
|
||||
registry.create();
|
||||
registry.create<int>();
|
||||
registry.create();
|
||||
|
||||
tot = 0u;
|
||||
match = 0u;
|
||||
|
||||
registry.each([&](auto entity) {
|
||||
if(registry.has<int>(entity)) { ++match; }
|
||||
registry.create();
|
||||
++tot;
|
||||
});
|
||||
|
||||
ASSERT_EQ(tot, 5u);
|
||||
ASSERT_EQ(match, 2u);
|
||||
|
||||
tot = 0u;
|
||||
match = 0u;
|
||||
|
||||
registry.each([&](auto entity) {
|
||||
if(registry.has<int>(entity)) {
|
||||
registry.destroy(entity);
|
||||
++match;
|
||||
}
|
||||
|
||||
++tot;
|
||||
});
|
||||
|
||||
ASSERT_EQ(tot, 10u);
|
||||
ASSERT_EQ(match, 2u);
|
||||
|
||||
tot = 0u;
|
||||
match = 0u;
|
||||
|
||||
registry.each([&](auto entity) {
|
||||
if(registry.has<int>(entity)) { ++match; }
|
||||
registry.destroy(entity);
|
||||
++tot;
|
||||
});
|
||||
|
||||
ASSERT_EQ(tot, 8u);
|
||||
ASSERT_EQ(match, 0u);
|
||||
|
||||
registry.each([&](auto) { FAIL(); });
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, Orphans) {
|
||||
entt::DefaultRegistry registry;
|
||||
entt::DefaultRegistry::size_type tot{};
|
||||
|
||||
registry.create<int>();
|
||||
registry.create();
|
||||
registry.create<int>();
|
||||
registry.create();
|
||||
registry.attach<double>(registry.create());
|
||||
|
||||
registry.orphans([&](auto) { ++tot; });
|
||||
ASSERT_EQ(tot, 2u);
|
||||
tot = 0u;
|
||||
|
||||
registry.each([&](auto entity) { registry.reset<int>(entity); });
|
||||
registry.orphans([&](auto) { ++tot; });
|
||||
ASSERT_EQ(tot, 4u);
|
||||
registry.reset();
|
||||
tot = 0u;
|
||||
|
||||
registry.orphans([&](auto) { ++tot; });
|
||||
ASSERT_EQ(tot, 0u);
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, Types) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
ASSERT_EQ(registry.tag<int>(), registry.tag<int>());
|
||||
ASSERT_EQ(registry.component<int>(), registry.component<int>());
|
||||
|
||||
ASSERT_NE(registry.tag<int>(), registry.tag<double>());
|
||||
ASSERT_NE(registry.component<int>(), registry.component<double>());
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, CreateDestroyEntities) {
|
||||
entt::DefaultRegistry registry;
|
||||
entt::DefaultRegistry::entity_type pre{}, post{};
|
||||
|
||||
for(int i = 0; i < 10; ++i) {
|
||||
registry.create<double>();
|
||||
}
|
||||
|
||||
registry.reset();
|
||||
|
||||
for(int i = 0; i < 7; ++i) {
|
||||
auto entity = registry.create<int>();
|
||||
if(i == 3) { pre = entity; }
|
||||
}
|
||||
|
||||
registry.reset();
|
||||
|
||||
for(int i = 0; i < 5; ++i) {
|
||||
auto entity = registry.create();
|
||||
if(i == 3) { post = entity; }
|
||||
}
|
||||
|
||||
ASSERT_FALSE(registry.valid(pre));
|
||||
ASSERT_TRUE(registry.valid(post));
|
||||
ASSERT_NE(registry.version(pre), registry.version(post));
|
||||
ASSERT_EQ(registry.version(pre) + 1, registry.version(post));
|
||||
ASSERT_EQ(registry.current(pre), registry.current(post));
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, AttachRemoveTags) {
|
||||
TEST(DefaultRegistry, AttachSetRemoveTags) {
|
||||
entt::DefaultRegistry registry;
|
||||
const auto &cregistry = registry;
|
||||
|
||||
@@ -148,6 +299,21 @@ TEST(DefaultRegistry, AttachRemoveTags) {
|
||||
ASSERT_EQ(cregistry.get<int>(), 42);
|
||||
ASSERT_EQ(registry.attachee<int>(), entity);
|
||||
|
||||
registry.set<int>(3);
|
||||
|
||||
ASSERT_TRUE(registry.has<int>());
|
||||
ASSERT_EQ(registry.get<int>(), 3);
|
||||
ASSERT_EQ(cregistry.get<int>(), 3);
|
||||
ASSERT_EQ(registry.attachee<int>(), entity);
|
||||
|
||||
auto other = registry.create();
|
||||
registry.move<int>(other);
|
||||
|
||||
ASSERT_TRUE(registry.has<int>());
|
||||
ASSERT_EQ(registry.get<int>(), 3);
|
||||
ASSERT_EQ(cregistry.get<int>(), 3);
|
||||
ASSERT_EQ(registry.attachee<int>(), other);
|
||||
|
||||
registry.remove<int>();
|
||||
|
||||
ASSERT_FALSE(registry.has<int>());
|
||||
@@ -287,3 +453,58 @@ TEST(DefaultRegistry, SortMulti) {
|
||||
ASSERT_EQ(registry.get<int>(entity), ival++);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, ComponentsWithTypesFromStandardTemplateLibrary) {
|
||||
// see #37 - the test shouldn't crash, that's all
|
||||
entt::DefaultRegistry registry;
|
||||
auto entity = registry.create();
|
||||
registry.assign<std::unordered_set<int>>(entity).insert(42);
|
||||
registry.destroy(entity);
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, ConstructWithComponents) {
|
||||
// it should compile, that's all
|
||||
entt::DefaultRegistry registry;
|
||||
const auto value = 0;
|
||||
registry.create(value);
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, MergeTwoRegistries) {
|
||||
using entity_type = entt::DefaultRegistry::entity_type;
|
||||
|
||||
entt::DefaultRegistry src;
|
||||
entt::DefaultRegistry dst;
|
||||
|
||||
std::unordered_map<entity_type, entity_type> ref;
|
||||
|
||||
auto merge = [&ref](const auto &view, auto &dst) {
|
||||
view.each([&](auto entity, const auto &component) {
|
||||
if(ref.find(entity) == ref.cend()) {
|
||||
ref.emplace(entity, dst.create(component));
|
||||
} else {
|
||||
using component_type = std::decay_t<decltype(component)>;
|
||||
dst.template assign<component_type>(ref[entity], component);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
src.create<int, float, double>();
|
||||
src.create<char, float, int>();
|
||||
|
||||
dst.create<int, char, double>();
|
||||
dst.create<float, int>();
|
||||
|
||||
auto eq = [](auto begin, auto end) { ASSERT_EQ(begin, end); };
|
||||
auto ne = [](auto begin, auto end) { ASSERT_NE(begin, end); };
|
||||
|
||||
eq(dst.view<int, float, double>().begin(), dst.view<int, float, double>().end());
|
||||
eq(dst.view<char, float, int>().begin(), dst.view<char, float, int>().end());
|
||||
|
||||
merge(src.view<int>(), dst);
|
||||
merge(src.view<char>(), dst);
|
||||
merge(src.view<double>(), dst);
|
||||
merge(src.view<float>(), dst);
|
||||
|
||||
ne(dst.view<int, float, double>().begin(), dst.view<int, float, double>().end());
|
||||
ne(dst.view<char, float, int>().begin(), dst.view<char, float, int>().end());
|
||||
}
|
||||
|
||||
489
test/entt/entity/snapshot.cpp
Normal file
489
test/entt/entity/snapshot.cpp
Normal file
@@ -0,0 +1,489 @@
|
||||
#include <tuple>
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
template<typename Storage>
|
||||
struct OutputArchive {
|
||||
OutputArchive(Storage &storage)
|
||||
: storage{storage}
|
||||
{}
|
||||
|
||||
template<typename Value>
|
||||
void operator()(const Value &value) {
|
||||
std::get<std::queue<Value>>(storage).push(value);
|
||||
}
|
||||
|
||||
private:
|
||||
Storage &storage;
|
||||
};
|
||||
|
||||
template<typename Storage>
|
||||
struct InputArchive {
|
||||
InputArchive(Storage &storage)
|
||||
: storage{storage}
|
||||
{}
|
||||
|
||||
template<typename Value>
|
||||
void operator()(Value &value) {
|
||||
auto &queue = std::get<std::queue<Value>>(storage);
|
||||
value = queue.front();
|
||||
queue.pop();
|
||||
}
|
||||
|
||||
private:
|
||||
Storage &storage;
|
||||
};
|
||||
|
||||
struct AComponent {};
|
||||
|
||||
struct AnotherComponent {
|
||||
int key;
|
||||
int value;
|
||||
};
|
||||
|
||||
struct Foo {
|
||||
entt::DefaultRegistry::entity_type bar;
|
||||
std::vector<entt::DefaultRegistry::entity_type> quux;
|
||||
};
|
||||
|
||||
TEST(Snapshot, Dump) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
auto e0 = registry.create();
|
||||
registry.assign<int>(e0, 42);
|
||||
registry.assign<char>(e0, 'c');
|
||||
registry.assign<double>(e0, .1);
|
||||
|
||||
auto e1 = registry.create();
|
||||
|
||||
auto e2 = registry.create();
|
||||
registry.assign<int>(e2, 3);
|
||||
|
||||
auto e3 = registry.create();
|
||||
registry.assign<char>(e3, '0');
|
||||
registry.attach<float>(e3, .3f);
|
||||
|
||||
auto e4 = registry.create();
|
||||
registry.attach<AComponent>(e4);
|
||||
|
||||
registry.destroy(e1);
|
||||
auto v1 = registry.current(e1);
|
||||
|
||||
using storage_type = std::tuple<
|
||||
std::queue<entt::DefaultRegistry::entity_type>,
|
||||
std::queue<int>,
|
||||
std::queue<char>,
|
||||
std::queue<double>,
|
||||
std::queue<float>,
|
||||
std::queue<bool>,
|
||||
std::queue<AComponent>,
|
||||
std::queue<AnotherComponent>,
|
||||
std::queue<Foo>
|
||||
>;
|
||||
|
||||
storage_type storage;
|
||||
OutputArchive<storage_type> output{storage};
|
||||
InputArchive<storage_type> input{storage};
|
||||
|
||||
registry.snapshot()
|
||||
.entities(output)
|
||||
.destroyed(output)
|
||||
.component<int, char, AnotherComponent, double>(output)
|
||||
.tag<float, bool, AComponent>(output);
|
||||
|
||||
registry.reset();
|
||||
|
||||
ASSERT_FALSE(registry.valid(e0));
|
||||
ASSERT_FALSE(registry.valid(e1));
|
||||
ASSERT_FALSE(registry.valid(e2));
|
||||
ASSERT_FALSE(registry.valid(e3));
|
||||
ASSERT_FALSE(registry.valid(e4));
|
||||
|
||||
registry.restore()
|
||||
.entities(input)
|
||||
.destroyed(input)
|
||||
.component<int, char, AnotherComponent, double>(input)
|
||||
.tag<float, bool, AComponent>(input)
|
||||
.orphans();
|
||||
|
||||
ASSERT_TRUE(registry.valid(e0));
|
||||
ASSERT_FALSE(registry.valid(e1));
|
||||
ASSERT_TRUE(registry.valid(e2));
|
||||
ASSERT_TRUE(registry.valid(e3));
|
||||
ASSERT_TRUE(registry.valid(e4));
|
||||
|
||||
ASSERT_FALSE(registry.orphan(e0));
|
||||
ASSERT_FALSE(registry.orphan(e2));
|
||||
ASSERT_FALSE(registry.orphan(e3));
|
||||
ASSERT_FALSE(registry.orphan(e4));
|
||||
|
||||
ASSERT_EQ(registry.get<int>(e0), 42);
|
||||
ASSERT_EQ(registry.get<char>(e0), 'c');
|
||||
ASSERT_EQ(registry.get<double>(e0), .1);
|
||||
ASSERT_EQ(registry.current(e1), v1);
|
||||
ASSERT_EQ(registry.get<int>(e2), 3);
|
||||
ASSERT_EQ(registry.get<char>(e3), '0');
|
||||
|
||||
ASSERT_TRUE(registry.has<float>());
|
||||
ASSERT_EQ(registry.attachee<float>(), e3);
|
||||
ASSERT_EQ(registry.get<float>(), .3f);
|
||||
|
||||
ASSERT_TRUE(registry.has<AComponent>());
|
||||
ASSERT_EQ(registry.attachee<AComponent>(), e4);
|
||||
|
||||
ASSERT_TRUE(registry.empty<AnotherComponent>());
|
||||
ASSERT_FALSE(registry.has<long int>());
|
||||
}
|
||||
|
||||
TEST(Snapshot, Partial) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
auto e0 = registry.create();
|
||||
registry.assign<int>(e0, 42);
|
||||
registry.assign<char>(e0, 'c');
|
||||
registry.assign<double>(e0, .1);
|
||||
|
||||
auto e1 = registry.create();
|
||||
|
||||
auto e2 = registry.create();
|
||||
registry.assign<int>(e2, 3);
|
||||
|
||||
auto e3 = registry.create();
|
||||
registry.assign<char>(e3, '0');
|
||||
registry.attach<float>(e3, .3f);
|
||||
|
||||
auto e4 = registry.create();
|
||||
registry.attach<AComponent>(e4);
|
||||
|
||||
registry.destroy(e1);
|
||||
auto v1 = registry.current(e1);
|
||||
|
||||
using storage_type = std::tuple<
|
||||
std::queue<entt::DefaultRegistry::entity_type>,
|
||||
std::queue<int>,
|
||||
std::queue<char>,
|
||||
std::queue<double>,
|
||||
std::queue<float>,
|
||||
std::queue<bool>,
|
||||
std::queue<AComponent>,
|
||||
std::queue<Foo>
|
||||
>;
|
||||
|
||||
storage_type storage;
|
||||
OutputArchive<storage_type> output{storage};
|
||||
InputArchive<storage_type> input{storage};
|
||||
|
||||
registry.snapshot()
|
||||
.entities(output)
|
||||
.destroyed(output)
|
||||
.component<char, int>(output)
|
||||
.tag<bool, float>(output);
|
||||
|
||||
registry.reset();
|
||||
|
||||
ASSERT_FALSE(registry.valid(e0));
|
||||
ASSERT_FALSE(registry.valid(e1));
|
||||
ASSERT_FALSE(registry.valid(e2));
|
||||
ASSERT_FALSE(registry.valid(e3));
|
||||
ASSERT_FALSE(registry.valid(e4));
|
||||
|
||||
registry.restore()
|
||||
.entities(input)
|
||||
.destroyed(input)
|
||||
.component<char, int>(input)
|
||||
.tag<bool, float>(input);
|
||||
|
||||
ASSERT_TRUE(registry.valid(e0));
|
||||
ASSERT_FALSE(registry.valid(e1));
|
||||
ASSERT_TRUE(registry.valid(e2));
|
||||
ASSERT_TRUE(registry.valid(e3));
|
||||
ASSERT_TRUE(registry.valid(e4));
|
||||
|
||||
ASSERT_EQ(registry.get<int>(e0), 42);
|
||||
ASSERT_EQ(registry.get<char>(e0), 'c');
|
||||
ASSERT_FALSE(registry.has<double>(e0));
|
||||
ASSERT_EQ(registry.current(e1), v1);
|
||||
ASSERT_EQ(registry.get<int>(e2), 3);
|
||||
ASSERT_EQ(registry.get<char>(e3), '0');
|
||||
ASSERT_TRUE(registry.orphan(e4));
|
||||
|
||||
ASSERT_TRUE(registry.has<float>());
|
||||
ASSERT_EQ(registry.attachee<float>(), e3);
|
||||
ASSERT_EQ(registry.get<float>(), .3f);
|
||||
ASSERT_FALSE(registry.has<long int>());
|
||||
|
||||
registry.snapshot()
|
||||
.tag<float>(output)
|
||||
.destroyed(output)
|
||||
.entities(output);
|
||||
|
||||
registry.reset();
|
||||
|
||||
ASSERT_FALSE(registry.valid(e0));
|
||||
ASSERT_FALSE(registry.valid(e1));
|
||||
ASSERT_FALSE(registry.valid(e2));
|
||||
ASSERT_FALSE(registry.valid(e3));
|
||||
ASSERT_FALSE(registry.valid(e4));
|
||||
|
||||
registry.restore()
|
||||
.tag<float>(input)
|
||||
.destroyed(input)
|
||||
.entities(input)
|
||||
.orphans();
|
||||
|
||||
ASSERT_FALSE(registry.valid(e0));
|
||||
ASSERT_FALSE(registry.valid(e1));
|
||||
ASSERT_FALSE(registry.valid(e2));
|
||||
ASSERT_TRUE(registry.valid(e3));
|
||||
ASSERT_FALSE(registry.valid(e4));
|
||||
}
|
||||
|
||||
TEST(Snapshot, Continuous) {
|
||||
using entity_type = entt::DefaultRegistry::entity_type;
|
||||
|
||||
entt::DefaultRegistry src;
|
||||
entt::DefaultRegistry dst;
|
||||
|
||||
entt::ContinuousLoader<entity_type> loader{dst};
|
||||
|
||||
std::vector<entity_type> entities;
|
||||
entity_type entity;
|
||||
|
||||
using storage_type = std::tuple<
|
||||
std::queue<entity_type>,
|
||||
std::queue<AComponent>,
|
||||
std::queue<AnotherComponent>,
|
||||
std::queue<Foo>,
|
||||
std::queue<double>
|
||||
>;
|
||||
|
||||
storage_type storage;
|
||||
OutputArchive<storage_type> output{storage};
|
||||
InputArchive<storage_type> input{storage};
|
||||
|
||||
for(int i = 0; i < 10; ++i) {
|
||||
src.create();
|
||||
}
|
||||
|
||||
src.each([&src](auto entity) {
|
||||
src.destroy(entity);
|
||||
});
|
||||
|
||||
for(int i = 0; i < 5; ++i) {
|
||||
entity = src.create();
|
||||
entities.push_back(entity);
|
||||
|
||||
src.assign<AComponent>(entity);
|
||||
src.assign<AnotherComponent>(entity, i, i);
|
||||
|
||||
if(i % 2) {
|
||||
src.assign<Foo>(entity, entity);
|
||||
} else if(i == 2) {
|
||||
src.attach<double>(entity, .3);
|
||||
}
|
||||
}
|
||||
|
||||
src.view<Foo>().each([&entities](auto, auto &foo) {
|
||||
foo.quux.insert(foo.quux.begin(), entities.begin(), entities.end());
|
||||
});
|
||||
|
||||
entity = dst.create();
|
||||
dst.assign<AComponent>(entity);
|
||||
dst.assign<AnotherComponent>(entity, -1, -1);
|
||||
|
||||
src.snapshot()
|
||||
.entities(output)
|
||||
.destroyed(output)
|
||||
.component<AComponent, AnotherComponent, Foo>(output)
|
||||
.tag<double>(output);
|
||||
|
||||
loader.entities(input)
|
||||
.destroyed(input)
|
||||
.component<AComponent, AnotherComponent>(input)
|
||||
.component<Foo>(input, &Foo::bar, &Foo::quux)
|
||||
.tag<double>(input)
|
||||
.orphans();
|
||||
|
||||
decltype(dst.size()) aComponentCnt{};
|
||||
decltype(dst.size()) anotherComponentCnt{};
|
||||
decltype(dst.size()) fooCnt{};
|
||||
|
||||
dst.each([&dst, &aComponentCnt](auto entity) {
|
||||
ASSERT_TRUE(dst.has<AComponent>(entity));
|
||||
++aComponentCnt;
|
||||
});
|
||||
|
||||
dst.view<AnotherComponent>().each([&anotherComponentCnt](auto, const auto &component) {
|
||||
ASSERT_EQ(component.value, component.key < 0 ? -1 : component.key);
|
||||
++anotherComponentCnt;
|
||||
});
|
||||
|
||||
dst.view<Foo>().each([&dst, &fooCnt](auto entity, const auto &component) {
|
||||
ASSERT_EQ(entity, component.bar);
|
||||
|
||||
for(auto entity: component.quux) {
|
||||
ASSERT_TRUE(dst.valid(entity));
|
||||
}
|
||||
|
||||
++fooCnt;
|
||||
});
|
||||
|
||||
ASSERT_TRUE(dst.has<double>());
|
||||
ASSERT_EQ(dst.get<double>(), .3);
|
||||
|
||||
src.view<AnotherComponent>().each([](auto, auto &component) {
|
||||
component.value = 2 * component.key;
|
||||
});
|
||||
|
||||
auto size = dst.size();
|
||||
|
||||
src.snapshot()
|
||||
.entities(output)
|
||||
.destroyed(output)
|
||||
.component<AComponent, AnotherComponent, Foo>(output)
|
||||
.tag<double>(output);
|
||||
|
||||
loader.entities(input)
|
||||
.destroyed(input)
|
||||
.component<AComponent, AnotherComponent>(input)
|
||||
.component<Foo>(input, &Foo::bar, &Foo::quux)
|
||||
.tag<double>(input)
|
||||
.orphans();
|
||||
|
||||
ASSERT_EQ(size, dst.size());
|
||||
|
||||
ASSERT_EQ(dst.size<AComponent>(), aComponentCnt);
|
||||
ASSERT_EQ(dst.size<AnotherComponent>(), anotherComponentCnt);
|
||||
ASSERT_EQ(dst.size<Foo>(), fooCnt);
|
||||
ASSERT_TRUE(dst.has<double>());
|
||||
|
||||
dst.view<AnotherComponent>().each([](auto, auto &component) {
|
||||
ASSERT_EQ(component.value, component.key < 0 ? -1 : (2 * component.key));
|
||||
});
|
||||
|
||||
entity = src.create();
|
||||
|
||||
src.view<Foo>().each([entity](auto, auto &component) {
|
||||
component.bar = entity;
|
||||
});
|
||||
|
||||
src.snapshot()
|
||||
.entities(output)
|
||||
.destroyed(output)
|
||||
.component<AComponent, AnotherComponent, Foo>(output)
|
||||
.tag<double>(output);
|
||||
|
||||
loader.entities(input)
|
||||
.destroyed(input)
|
||||
.component<AComponent, AnotherComponent>(input)
|
||||
.component<Foo>(input, &Foo::bar, &Foo::quux)
|
||||
.tag<double>(input)
|
||||
.orphans();
|
||||
|
||||
dst.view<Foo>().each([&loader, entity](auto, auto &component) {
|
||||
ASSERT_EQ(component.bar, loader.map(entity));
|
||||
});
|
||||
|
||||
entities.clear();
|
||||
for(auto entity: src.view<AComponent>()) {
|
||||
entities.push_back(entity);
|
||||
}
|
||||
|
||||
src.destroy(entity);
|
||||
loader.shrink();
|
||||
|
||||
src.snapshot()
|
||||
.entities(output)
|
||||
.destroyed(output)
|
||||
.component<AComponent, AnotherComponent, Foo>(output)
|
||||
.tag<double>(output);
|
||||
|
||||
loader.entities(input)
|
||||
.destroyed(input)
|
||||
.component<AComponent, AnotherComponent>(input)
|
||||
.component<Foo>(input, &Foo::bar, &Foo::quux)
|
||||
.tag<double>(input)
|
||||
.orphans()
|
||||
.shrink();
|
||||
|
||||
dst.view<Foo>().each([&dst, &loader, entity](auto, auto &component) {
|
||||
ASSERT_FALSE(dst.valid(component.bar));
|
||||
});
|
||||
|
||||
ASSERT_FALSE(loader.has(entity));
|
||||
|
||||
entity = src.create();
|
||||
|
||||
src.view<Foo>().each([entity](auto, auto &component) {
|
||||
component.bar = entity;
|
||||
});
|
||||
|
||||
dst.reset<AComponent>();
|
||||
aComponentCnt = src.size<AComponent>();
|
||||
|
||||
src.snapshot()
|
||||
.entities(output)
|
||||
.destroyed(output)
|
||||
.component<AComponent, AnotherComponent, Foo>(output)
|
||||
.tag<double>(output);
|
||||
|
||||
loader.entities(input)
|
||||
.destroyed(input)
|
||||
.component<AComponent, AnotherComponent>(input)
|
||||
.component<Foo>(input, &Foo::bar, &Foo::quux)
|
||||
.tag<double>(input)
|
||||
.orphans();
|
||||
|
||||
ASSERT_EQ(dst.size<AComponent>(), aComponentCnt);
|
||||
ASSERT_TRUE(dst.has<double>());
|
||||
|
||||
src.reset<AComponent>();
|
||||
src.remove<double>();
|
||||
aComponentCnt = {};
|
||||
|
||||
src.snapshot()
|
||||
.entities(output)
|
||||
.destroyed(output)
|
||||
.component<AComponent, AnotherComponent, Foo>(output)
|
||||
.tag<double>(output);
|
||||
|
||||
loader.entities(input)
|
||||
.destroyed(input)
|
||||
.component<AComponent, AnotherComponent>(input)
|
||||
.component<Foo>(input, &Foo::bar, &Foo::quux)
|
||||
.tag<double>(input)
|
||||
.orphans();
|
||||
|
||||
ASSERT_EQ(dst.size<AComponent>(), aComponentCnt);
|
||||
ASSERT_FALSE(dst.has<double>());
|
||||
}
|
||||
|
||||
TEST(Snapshot, ContinuousMoreOnShrink) {
|
||||
using entity_type = entt::DefaultRegistry::entity_type;
|
||||
|
||||
entt::DefaultRegistry src;
|
||||
entt::DefaultRegistry dst;
|
||||
|
||||
entt::ContinuousLoader<entity_type> loader{dst};
|
||||
|
||||
using storage_type = std::tuple<
|
||||
std::queue<entity_type>,
|
||||
std::queue<AComponent>
|
||||
>;
|
||||
|
||||
storage_type storage;
|
||||
OutputArchive<storage_type> output{storage};
|
||||
InputArchive<storage_type> input{storage};
|
||||
|
||||
auto entity = src.create();
|
||||
src.snapshot().entities(output);
|
||||
loader.entities(input).shrink();
|
||||
|
||||
ASSERT_TRUE(dst.valid(entity));
|
||||
|
||||
loader.shrink();
|
||||
|
||||
ASSERT_FALSE(dst.valid(entity));
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
#include <unordered_set>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/sparse_set.hpp>
|
||||
|
||||
TEST(SparseSetNoType, Functionalities) {
|
||||
entt::SparseSet<unsigned int> set;
|
||||
|
||||
ASSERT_NO_THROW(set.reserve(42));
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
@@ -19,6 +21,7 @@ TEST(SparseSetNoType, Functionalities) {
|
||||
ASSERT_NE(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_TRUE(set.has(42));
|
||||
ASSERT_TRUE(set.fast(42));
|
||||
ASSERT_EQ(set.get(42), 0u);
|
||||
|
||||
set.destroy(42);
|
||||
@@ -61,24 +64,189 @@ TEST(SparseSetNoType, DataBeginEnd) {
|
||||
ASSERT_EQ(*(set.data() + 1u), 12u);
|
||||
ASSERT_EQ(*(set.data() + 2u), 42u);
|
||||
|
||||
auto it = set.begin();
|
||||
|
||||
ASSERT_EQ(*it, 42u);
|
||||
ASSERT_EQ(*(it+1), 12u);
|
||||
ASSERT_EQ(*(it+2), 3u);
|
||||
ASSERT_EQ(it += 3, set.end());
|
||||
|
||||
auto begin = set.begin();
|
||||
auto end = set.end();
|
||||
|
||||
ASSERT_EQ(*(begin++), 42u);
|
||||
ASSERT_EQ(*(begin++), 12u);
|
||||
ASSERT_EQ(*(begin++), 3u);
|
||||
|
||||
ASSERT_EQ(begin, end);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, AggregatesMustWork) {
|
||||
struct AggregateType { int value; };
|
||||
// the goal of this test is to enforce the requirements for aggregate types
|
||||
entt::SparseSet<unsigned int, AggregateType>{}.construct(0, 42);
|
||||
TEST(SparseSetNoType, RespectDisjoint) {
|
||||
entt::SparseSet<unsigned int> lhs;
|
||||
entt::SparseSet<unsigned int> rhs;
|
||||
const auto &clhs = lhs;
|
||||
|
||||
lhs.construct(3);
|
||||
lhs.construct(12);
|
||||
lhs.construct(42);
|
||||
|
||||
ASSERT_EQ(lhs.get(3), 0u);
|
||||
ASSERT_EQ(lhs.get(12), 1u);
|
||||
ASSERT_EQ(lhs.get(42), 2u);
|
||||
|
||||
lhs.respect(rhs);
|
||||
|
||||
ASSERT_EQ(clhs.get(3), 0u);
|
||||
ASSERT_EQ(clhs.get(12), 1u);
|
||||
ASSERT_EQ(clhs.get(42), 2u);
|
||||
}
|
||||
|
||||
TEST(SparseSetNoType, RespectOverlap) {
|
||||
entt::SparseSet<unsigned int> lhs;
|
||||
entt::SparseSet<unsigned int> rhs;
|
||||
const auto &clhs = lhs;
|
||||
|
||||
lhs.construct(3);
|
||||
lhs.construct(12);
|
||||
lhs.construct(42);
|
||||
|
||||
rhs.construct(12);
|
||||
|
||||
ASSERT_EQ(lhs.get(3), 0u);
|
||||
ASSERT_EQ(lhs.get(12), 1u);
|
||||
ASSERT_EQ(lhs.get(42), 2u);
|
||||
|
||||
lhs.respect(rhs);
|
||||
|
||||
ASSERT_EQ(clhs.get(3), 0u);
|
||||
ASSERT_EQ(clhs.get(12), 2u);
|
||||
ASSERT_EQ(clhs.get(42), 1u);
|
||||
}
|
||||
|
||||
TEST(SparseSetNoType, RespectOrdered) {
|
||||
entt::SparseSet<unsigned int> lhs;
|
||||
entt::SparseSet<unsigned int> rhs;
|
||||
|
||||
lhs.construct(1);
|
||||
lhs.construct(2);
|
||||
lhs.construct(3);
|
||||
lhs.construct(4);
|
||||
lhs.construct(5);
|
||||
|
||||
ASSERT_EQ(lhs.get(1), 0u);
|
||||
ASSERT_EQ(lhs.get(2), 1u);
|
||||
ASSERT_EQ(lhs.get(3), 2u);
|
||||
ASSERT_EQ(lhs.get(4), 3u);
|
||||
ASSERT_EQ(lhs.get(5), 4u);
|
||||
|
||||
rhs.construct(6);
|
||||
rhs.construct(1);
|
||||
rhs.construct(2);
|
||||
rhs.construct(3);
|
||||
rhs.construct(4);
|
||||
rhs.construct(5);
|
||||
|
||||
ASSERT_EQ(rhs.get(6), 0u);
|
||||
ASSERT_EQ(rhs.get(1), 1u);
|
||||
ASSERT_EQ(rhs.get(2), 2u);
|
||||
ASSERT_EQ(rhs.get(3), 3u);
|
||||
ASSERT_EQ(rhs.get(4), 4u);
|
||||
ASSERT_EQ(rhs.get(5), 5u);
|
||||
|
||||
rhs.respect(lhs);
|
||||
|
||||
ASSERT_EQ(rhs.get(6), 0u);
|
||||
ASSERT_EQ(rhs.get(1), 1u);
|
||||
ASSERT_EQ(rhs.get(2), 2u);
|
||||
ASSERT_EQ(rhs.get(3), 3u);
|
||||
ASSERT_EQ(rhs.get(4), 4u);
|
||||
ASSERT_EQ(rhs.get(5), 5u);
|
||||
}
|
||||
|
||||
TEST(SparseSetNoType, RespectReverse) {
|
||||
entt::SparseSet<unsigned int> lhs;
|
||||
entt::SparseSet<unsigned int> rhs;
|
||||
|
||||
lhs.construct(1);
|
||||
lhs.construct(2);
|
||||
lhs.construct(3);
|
||||
lhs.construct(4);
|
||||
lhs.construct(5);
|
||||
|
||||
ASSERT_EQ(lhs.get(1), 0u);
|
||||
ASSERT_EQ(lhs.get(2), 1u);
|
||||
ASSERT_EQ(lhs.get(3), 2u);
|
||||
ASSERT_EQ(lhs.get(4), 3u);
|
||||
ASSERT_EQ(lhs.get(5), 4u);
|
||||
|
||||
rhs.construct(5);
|
||||
rhs.construct(4);
|
||||
rhs.construct(3);
|
||||
rhs.construct(2);
|
||||
rhs.construct(1);
|
||||
rhs.construct(6);
|
||||
|
||||
ASSERT_EQ(rhs.get(5), 0u);
|
||||
ASSERT_EQ(rhs.get(4), 1u);
|
||||
ASSERT_EQ(rhs.get(3), 2u);
|
||||
ASSERT_EQ(rhs.get(2), 3u);
|
||||
ASSERT_EQ(rhs.get(1), 4u);
|
||||
ASSERT_EQ(rhs.get(6), 5u);
|
||||
|
||||
rhs.respect(lhs);
|
||||
|
||||
ASSERT_EQ(rhs.get(6), 0u);
|
||||
ASSERT_EQ(rhs.get(1), 1u);
|
||||
ASSERT_EQ(rhs.get(2), 2u);
|
||||
ASSERT_EQ(rhs.get(3), 3u);
|
||||
ASSERT_EQ(rhs.get(4), 4u);
|
||||
ASSERT_EQ(rhs.get(5), 5u);
|
||||
}
|
||||
|
||||
TEST(SparseSetNoType, RespectUnordered) {
|
||||
entt::SparseSet<unsigned int> lhs;
|
||||
entt::SparseSet<unsigned int> rhs;
|
||||
|
||||
lhs.construct(1);
|
||||
lhs.construct(2);
|
||||
lhs.construct(3);
|
||||
lhs.construct(4);
|
||||
lhs.construct(5);
|
||||
|
||||
ASSERT_EQ(lhs.get(1), 0u);
|
||||
ASSERT_EQ(lhs.get(2), 1u);
|
||||
ASSERT_EQ(lhs.get(3), 2u);
|
||||
ASSERT_EQ(lhs.get(4), 3u);
|
||||
ASSERT_EQ(lhs.get(5), 4u);
|
||||
|
||||
rhs.construct(3);
|
||||
rhs.construct(2);
|
||||
rhs.construct(6);
|
||||
rhs.construct(1);
|
||||
rhs.construct(4);
|
||||
rhs.construct(5);
|
||||
|
||||
ASSERT_EQ(rhs.get(3), 0u);
|
||||
ASSERT_EQ(rhs.get(2), 1u);
|
||||
ASSERT_EQ(rhs.get(6), 2u);
|
||||
ASSERT_EQ(rhs.get(1), 3u);
|
||||
ASSERT_EQ(rhs.get(4), 4u);
|
||||
ASSERT_EQ(rhs.get(5), 5u);
|
||||
|
||||
rhs.respect(lhs);
|
||||
|
||||
ASSERT_EQ(rhs.get(6), 0u);
|
||||
ASSERT_EQ(rhs.get(1), 1u);
|
||||
ASSERT_EQ(rhs.get(2), 2u);
|
||||
ASSERT_EQ(rhs.get(3), 3u);
|
||||
ASSERT_EQ(rhs.get(4), 4u);
|
||||
ASSERT_EQ(rhs.get(5), 5u);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, Functionalities) {
|
||||
entt::SparseSet<unsigned int, int> set;
|
||||
|
||||
ASSERT_NO_THROW(set.reserve(42));
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
@@ -93,6 +261,7 @@ TEST(SparseSetWithType, Functionalities) {
|
||||
ASSERT_NE(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_TRUE(set.has(42));
|
||||
ASSERT_TRUE(set.fast(42));
|
||||
ASSERT_EQ(set.get(42), 3);
|
||||
|
||||
set.destroy(42);
|
||||
@@ -120,6 +289,19 @@ TEST(SparseSetWithType, Functionalities) {
|
||||
other = std::move(set);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, AggregatesMustWork) {
|
||||
struct AggregateType { int value; };
|
||||
// the goal of this test is to enforce the requirements for aggregate types
|
||||
entt::SparseSet<unsigned int, AggregateType>{}.construct(0, 42);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, TypesFromStandardTemplateLibraryMustWork) {
|
||||
// see #37 - this test shouldn't crash, that's all
|
||||
entt::SparseSet<unsigned int, std::unordered_set<int>> set;
|
||||
set.construct(0).insert(42);
|
||||
set.destroy(0);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, RawBeginEnd) {
|
||||
entt::SparseSet<unsigned int, int> set;
|
||||
|
||||
@@ -138,9 +320,9 @@ TEST(SparseSetWithType, RawBeginEnd) {
|
||||
auto begin = set.begin();
|
||||
auto end = set.end();
|
||||
|
||||
ASSERT_EQ(set.get(*(begin++)), 9);
|
||||
ASSERT_EQ(set.get(*(begin++)), 6);
|
||||
ASSERT_EQ(set.get(*(begin++)), 3);
|
||||
ASSERT_EQ(*(begin++), 9);
|
||||
ASSERT_EQ(*(begin++), 6);
|
||||
ASSERT_EQ(*(begin++), 3);
|
||||
ASSERT_EQ(begin, end);
|
||||
}
|
||||
|
||||
@@ -159,8 +341,8 @@ TEST(SparseSetWithType, SortOrdered) {
|
||||
ASSERT_EQ(set.get(3), 3);
|
||||
ASSERT_EQ(set.get(9), 1);
|
||||
|
||||
set.sort([&set](auto lhs, auto rhs) {
|
||||
return set.get(lhs) < set.get(rhs);
|
||||
set.sort([](auto lhs, auto rhs) {
|
||||
return lhs < rhs;
|
||||
});
|
||||
|
||||
ASSERT_EQ(*(set.raw() + 0u), 12);
|
||||
@@ -172,11 +354,11 @@ TEST(SparseSetWithType, SortOrdered) {
|
||||
auto begin = set.begin();
|
||||
auto end = set.end();
|
||||
|
||||
ASSERT_EQ(set.get(*(begin++)), 1);
|
||||
ASSERT_EQ(set.get(*(begin++)), 3);
|
||||
ASSERT_EQ(set.get(*(begin++)), 6);
|
||||
ASSERT_EQ(set.get(*(begin++)), 9);
|
||||
ASSERT_EQ(set.get(*(begin++)), 12);
|
||||
ASSERT_EQ(*(begin++), 1);
|
||||
ASSERT_EQ(*(begin++), 3);
|
||||
ASSERT_EQ(*(begin++), 6);
|
||||
ASSERT_EQ(*(begin++), 9);
|
||||
ASSERT_EQ(*(begin++), 12);
|
||||
ASSERT_EQ(begin, end);
|
||||
}
|
||||
|
||||
@@ -195,8 +377,8 @@ TEST(SparseSetWithType, SortReverse) {
|
||||
ASSERT_EQ(set.get(3), 9);
|
||||
ASSERT_EQ(set.get(9), 12);
|
||||
|
||||
set.sort([&set](auto lhs, auto rhs) {
|
||||
return set.get(lhs) < set.get(rhs);
|
||||
set.sort([](auto lhs, auto rhs) {
|
||||
return lhs < rhs;
|
||||
});
|
||||
|
||||
ASSERT_EQ(*(set.raw() + 0u), 12);
|
||||
@@ -208,11 +390,11 @@ TEST(SparseSetWithType, SortReverse) {
|
||||
auto begin = set.begin();
|
||||
auto end = set.end();
|
||||
|
||||
ASSERT_EQ(set.get(*(begin++)), 1);
|
||||
ASSERT_EQ(set.get(*(begin++)), 3);
|
||||
ASSERT_EQ(set.get(*(begin++)), 6);
|
||||
ASSERT_EQ(set.get(*(begin++)), 9);
|
||||
ASSERT_EQ(set.get(*(begin++)), 12);
|
||||
ASSERT_EQ(*(begin++), 1);
|
||||
ASSERT_EQ(*(begin++), 3);
|
||||
ASSERT_EQ(*(begin++), 6);
|
||||
ASSERT_EQ(*(begin++), 9);
|
||||
ASSERT_EQ(*(begin++), 12);
|
||||
ASSERT_EQ(begin, end);
|
||||
}
|
||||
|
||||
@@ -231,8 +413,8 @@ TEST(SparseSetWithType, SortUnordered) {
|
||||
ASSERT_EQ(set.get(3), 9);
|
||||
ASSERT_EQ(set.get(9), 12);
|
||||
|
||||
set.sort([&set](auto lhs, auto rhs) {
|
||||
return set.get(lhs) < set.get(rhs);
|
||||
set.sort([](auto lhs, auto rhs) {
|
||||
return lhs < rhs;
|
||||
});
|
||||
|
||||
ASSERT_EQ(*(set.raw() + 0u), 12);
|
||||
@@ -244,11 +426,11 @@ TEST(SparseSetWithType, SortUnordered) {
|
||||
auto begin = set.begin();
|
||||
auto end = set.end();
|
||||
|
||||
ASSERT_EQ(set.get(*(begin++)), 1);
|
||||
ASSERT_EQ(set.get(*(begin++)), 3);
|
||||
ASSERT_EQ(set.get(*(begin++)), 6);
|
||||
ASSERT_EQ(set.get(*(begin++)), 9);
|
||||
ASSERT_EQ(set.get(*(begin++)), 12);
|
||||
ASSERT_EQ(*(begin++), 1);
|
||||
ASSERT_EQ(*(begin++), 3);
|
||||
ASSERT_EQ(*(begin++), 6);
|
||||
ASSERT_EQ(*(begin++), 9);
|
||||
ASSERT_EQ(*(begin++), 12);
|
||||
ASSERT_EQ(begin, end);
|
||||
}
|
||||
|
||||
@@ -261,9 +443,9 @@ TEST(SparseSetWithType, RespectDisjoint) {
|
||||
lhs.construct(12, 6);
|
||||
lhs.construct(42, 9);
|
||||
|
||||
ASSERT_EQ(lhs.get(3), 3);
|
||||
ASSERT_EQ(lhs.get(12), 6);
|
||||
ASSERT_EQ(lhs.get(42), 9);
|
||||
ASSERT_EQ(clhs.get(3), 3);
|
||||
ASSERT_EQ(clhs.get(12), 6);
|
||||
ASSERT_EQ(clhs.get(42), 9);
|
||||
|
||||
lhs.respect(rhs);
|
||||
|
||||
@@ -271,12 +453,12 @@ TEST(SparseSetWithType, RespectDisjoint) {
|
||||
ASSERT_EQ(*(clhs.raw() + 1u), 6);
|
||||
ASSERT_EQ(*(clhs.raw() + 2u), 9);
|
||||
|
||||
auto begin = clhs.begin();
|
||||
auto end = clhs.end();
|
||||
auto begin = lhs.begin();
|
||||
auto end = lhs.end();
|
||||
|
||||
ASSERT_EQ(clhs.get(*(begin++)), 9);
|
||||
ASSERT_EQ(clhs.get(*(begin++)), 6);
|
||||
ASSERT_EQ(clhs.get(*(begin++)), 3);
|
||||
ASSERT_EQ(*(begin++), 9);
|
||||
ASSERT_EQ(*(begin++), 6);
|
||||
ASSERT_EQ(*(begin++), 3);
|
||||
ASSERT_EQ(begin, end);
|
||||
}
|
||||
|
||||
@@ -290,9 +472,9 @@ TEST(SparseSetWithType, RespectOverlap) {
|
||||
lhs.construct(42, 9);
|
||||
rhs.construct(12, 6);
|
||||
|
||||
ASSERT_EQ(lhs.get(3), 3);
|
||||
ASSERT_EQ(lhs.get(12), 6);
|
||||
ASSERT_EQ(lhs.get(42), 9);
|
||||
ASSERT_EQ(clhs.get(3), 3);
|
||||
ASSERT_EQ(clhs.get(12), 6);
|
||||
ASSERT_EQ(clhs.get(42), 9);
|
||||
ASSERT_EQ(rhs.get(12), 6);
|
||||
|
||||
lhs.respect(rhs);
|
||||
@@ -301,12 +483,12 @@ TEST(SparseSetWithType, RespectOverlap) {
|
||||
ASSERT_EQ(*(clhs.raw() + 1u), 9);
|
||||
ASSERT_EQ(*(clhs.raw() + 2u), 6);
|
||||
|
||||
auto begin = clhs.begin();
|
||||
auto end = clhs.end();
|
||||
auto begin = lhs.begin();
|
||||
auto end = lhs.end();
|
||||
|
||||
ASSERT_EQ(clhs.get(*(begin++)), 6);
|
||||
ASSERT_EQ(clhs.get(*(begin++)), 9);
|
||||
ASSERT_EQ(clhs.get(*(begin++)), 3);
|
||||
ASSERT_EQ(*(begin++), 6);
|
||||
ASSERT_EQ(*(begin++), 9);
|
||||
ASSERT_EQ(*(begin++), 3);
|
||||
ASSERT_EQ(begin, end);
|
||||
}
|
||||
|
||||
@@ -447,3 +629,33 @@ TEST(SparseSetWithType, RespectUnordered) {
|
||||
ASSERT_EQ(*(rhs.data() + 4u), 4u);
|
||||
ASSERT_EQ(*(rhs.data() + 5u), 5u);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, ReferencesGuaranteed) {
|
||||
struct Type { int value; };
|
||||
|
||||
entt::SparseSet<unsigned int, Type> set;
|
||||
|
||||
set.construct(0, 0);
|
||||
set.construct(1, 1);
|
||||
|
||||
ASSERT_EQ(set.get(0).value, 0);
|
||||
ASSERT_EQ(set.get(1).value, 1);
|
||||
|
||||
for(auto &&type: set) {
|
||||
if(type.value) {
|
||||
type.value = 42;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_EQ(set.get(0).value, 0);
|
||||
ASSERT_EQ(set.get(1).value, 42);
|
||||
|
||||
auto begin = set.begin();
|
||||
|
||||
while(begin != set.end()) {
|
||||
(begin++)->value = 3;
|
||||
}
|
||||
|
||||
ASSERT_EQ(set.get(0).value, 3);
|
||||
ASSERT_EQ(set.get(1).value, 3);
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
TEST(View, SingleComponent) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
auto e1 = registry.create();
|
||||
auto e2 = registry.create<int, char>();
|
||||
auto e0 = registry.create();
|
||||
auto e1 = registry.create<int, char>();
|
||||
|
||||
ASSERT_NO_THROW(registry.view<char>().begin()++);
|
||||
ASSERT_NO_THROW(++registry.view<char>().begin());
|
||||
@@ -16,30 +16,44 @@ TEST(View, SingleComponent) {
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
|
||||
registry.assign<char>(e1);
|
||||
registry.assign<char>(e0);
|
||||
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{2});
|
||||
|
||||
view.get(e1) = '1';
|
||||
view.get(e2) = '2';
|
||||
view.get(e0) = '1';
|
||||
view.get(e1) = '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.data() + 0), e1);
|
||||
ASSERT_EQ(*(view.data() + 1), e0);
|
||||
|
||||
ASSERT_EQ(*(view.raw() + 0), '2');
|
||||
ASSERT_EQ(*(static_cast<const decltype(view) &>(view).raw() + 1), '1');
|
||||
|
||||
registry.remove<char>(e0);
|
||||
registry.remove<char>(e1);
|
||||
registry.remove<char>(e2);
|
||||
|
||||
ASSERT_EQ(view.begin(), view.end());
|
||||
}
|
||||
|
||||
TEST(View, SingleComponentContains) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
auto e0 = registry.create<int>();
|
||||
auto e1 = registry.create<int>();
|
||||
|
||||
registry.destroy(e0);
|
||||
|
||||
auto view = registry.view<int>();
|
||||
|
||||
ASSERT_FALSE(view.contains(e0));
|
||||
ASSERT_TRUE(view.contains(e1));
|
||||
}
|
||||
|
||||
TEST(View, SingleComponentEmpty) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
@@ -78,8 +92,14 @@ TEST(View, SingleComponentEach) {
|
||||
TEST(View, MultipleComponent) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
auto e1 = registry.create<char>();
|
||||
auto e2 = registry.create<int, char>();
|
||||
auto e0 = registry.create<char>();
|
||||
auto e1 = registry.create<int, char>();
|
||||
|
||||
auto it = registry.view<char>().begin();
|
||||
|
||||
ASSERT_EQ(*it, e1);
|
||||
ASSERT_EQ(*(it+1), e0);
|
||||
ASSERT_EQ(it += 2, registry.view<char>().end());
|
||||
|
||||
ASSERT_NO_THROW((registry.view<int, char>().begin()++));
|
||||
ASSERT_NO_THROW((++registry.view<int, char>().begin()));
|
||||
@@ -87,22 +107,41 @@ TEST(View, MultipleComponent) {
|
||||
auto view = registry.view<int, char>();
|
||||
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
ASSERT_EQ(view.begin()+1, view.end());
|
||||
ASSERT_EQ(view.size(), decltype(view.size()){1});
|
||||
|
||||
view.get<char>(e1) = '1';
|
||||
view.get<char>(e2) = '2';
|
||||
registry.get<char>(e0) = '1';
|
||||
registry.get<char>(e1) = '2';
|
||||
registry.get<int>(e1) = 42;
|
||||
|
||||
for(auto entity: view) {
|
||||
const auto &cview = static_cast<const decltype(view) &>(view);
|
||||
ASSERT_TRUE(cview.get<char>(entity) == '2');
|
||||
ASSERT_EQ(std::get<0>(cview.get<int, char>(entity)), 42);
|
||||
ASSERT_EQ(std::get<1>(view.get<int, char>(entity)), '2');
|
||||
ASSERT_EQ(cview.get<char>(entity), '2');
|
||||
}
|
||||
|
||||
registry.remove<char>(e0);
|
||||
registry.remove<char>(e1);
|
||||
registry.remove<char>(e2);
|
||||
view.reset();
|
||||
|
||||
ASSERT_EQ(view.begin(), view.end());
|
||||
}
|
||||
|
||||
TEST(View, MultipleComponentContains) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
auto e0 = registry.create<int, char>();
|
||||
auto e1 = registry.create<int, char>();
|
||||
|
||||
registry.destroy(e0);
|
||||
|
||||
auto view = registry.view<int, char>();
|
||||
|
||||
ASSERT_FALSE(view.contains(e0));
|
||||
ASSERT_TRUE(view.contains(e1));
|
||||
}
|
||||
|
||||
TEST(View, MultipleComponentEmpty) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
@@ -140,8 +179,8 @@ TEST(PersistentView, Prepare) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<int, char>();
|
||||
|
||||
auto e1 = registry.create<char>();
|
||||
auto e2 = registry.create<int, char>();
|
||||
auto e0 = registry.create<char>();
|
||||
auto e1 = registry.create<int, char>();
|
||||
|
||||
ASSERT_NO_THROW((registry.persistent<int, char>().begin()++));
|
||||
ASSERT_NO_THROW((++registry.persistent<int, char>().begin()));
|
||||
@@ -151,26 +190,29 @@ TEST(PersistentView, Prepare) {
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
|
||||
registry.assign<int>(e1);
|
||||
registry.assign<int>(e0);
|
||||
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{2});
|
||||
|
||||
registry.remove<int>(e1);
|
||||
registry.remove<int>(e0);
|
||||
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
|
||||
view.get<char>(e1) = '1';
|
||||
view.get<char>(e2) = '2';
|
||||
registry.get<char>(e0) = '1';
|
||||
registry.get<char>(e1) = '2';
|
||||
registry.get<int>(e1) = 42;
|
||||
|
||||
for(auto entity: view) {
|
||||
const auto &cview = static_cast<const decltype(view) &>(view);
|
||||
ASSERT_TRUE(cview.get<char>(entity) == '2');
|
||||
ASSERT_EQ(std::get<0>(cview.get<int, char>(entity)), 42);
|
||||
ASSERT_EQ(std::get<1>(view.get<int, char>(entity)), '2');
|
||||
ASSERT_EQ(cview.get<char>(entity), '2');
|
||||
}
|
||||
|
||||
ASSERT_EQ(*(view.data() + 0), e2);
|
||||
ASSERT_EQ(*(view.data() + 0), e1);
|
||||
|
||||
registry.remove<char>(e0);
|
||||
registry.remove<char>(e1);
|
||||
registry.remove<char>(e2);
|
||||
|
||||
ASSERT_EQ(view.begin(), view.end());
|
||||
}
|
||||
@@ -178,8 +220,8 @@ TEST(PersistentView, Prepare) {
|
||||
TEST(PersistentView, NoPrepare) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
auto e1 = registry.create<char>();
|
||||
auto e2 = registry.create<int, char>();
|
||||
auto e0 = registry.create<char>();
|
||||
auto e1 = registry.create<int, char>();
|
||||
|
||||
ASSERT_NO_THROW((registry.persistent<int, char>().begin()++));
|
||||
ASSERT_NO_THROW((++registry.persistent<int, char>().begin()));
|
||||
@@ -189,30 +231,47 @@ TEST(PersistentView, NoPrepare) {
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
|
||||
registry.assign<int>(e1);
|
||||
registry.assign<int>(e0);
|
||||
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{2});
|
||||
|
||||
registry.remove<int>(e1);
|
||||
registry.remove<int>(e0);
|
||||
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
|
||||
view.get<char>(e1) = '1';
|
||||
view.get<char>(e2) = '2';
|
||||
registry.get<char>(e0) = '1';
|
||||
registry.get<char>(e1) = '2';
|
||||
registry.get<int>(e1) = 42;
|
||||
|
||||
for(auto entity: view) {
|
||||
const auto &cview = static_cast<const decltype(view) &>(view);
|
||||
ASSERT_TRUE(cview.get<char>(entity) == '2');
|
||||
ASSERT_EQ(std::get<0>(cview.get<int, char>(entity)), 42);
|
||||
ASSERT_EQ(std::get<1>(view.get<int, char>(entity)), '2');
|
||||
ASSERT_EQ(cview.get<char>(entity), '2');
|
||||
}
|
||||
|
||||
ASSERT_EQ(*(view.data() + 0), e2);
|
||||
ASSERT_EQ(*(view.data() + 0), e1);
|
||||
|
||||
registry.remove<char>(e0);
|
||||
registry.remove<char>(e1);
|
||||
registry.remove<char>(e2);
|
||||
|
||||
ASSERT_EQ(view.begin(), view.end());
|
||||
}
|
||||
|
||||
TEST(PersistentView, Contains) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
auto e0 = registry.create<int, char>();
|
||||
auto e1 = registry.create<int, char>();
|
||||
|
||||
registry.destroy(e0);
|
||||
|
||||
auto view = registry.persistent<int, char>();
|
||||
|
||||
ASSERT_FALSE(view.contains(e0));
|
||||
ASSERT_TRUE(view.contains(e1));
|
||||
}
|
||||
|
||||
TEST(PersistentView, Empty) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
@@ -254,20 +313,20 @@ TEST(PersistentView, Sort) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<int, unsigned int>();
|
||||
|
||||
auto e0 = registry.create();
|
||||
auto e1 = registry.create();
|
||||
auto e2 = registry.create();
|
||||
auto e3 = registry.create();
|
||||
|
||||
auto uval = 0u;
|
||||
auto ival = 0;
|
||||
|
||||
registry.assign<unsigned int>(e0, uval++);
|
||||
registry.assign<unsigned int>(e1, uval++);
|
||||
registry.assign<unsigned int>(e2, uval++);
|
||||
registry.assign<unsigned int>(e3, uval++);
|
||||
|
||||
registry.assign<int>(e0, ival++);
|
||||
registry.assign<int>(e1, ival++);
|
||||
registry.assign<int>(e2, ival++);
|
||||
registry.assign<int>(e3, ival++);
|
||||
|
||||
auto view = registry.persistent<int, unsigned int>();
|
||||
|
||||
@@ -284,3 +343,65 @@ TEST(PersistentView, Sort) {
|
||||
ASSERT_EQ(view.get<int>(entity), ival++);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RawView, Functionalities) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
auto e0 = registry.create();
|
||||
auto e1 = registry.create<int, char>();
|
||||
|
||||
ASSERT_NO_THROW(registry.raw<char>().begin()++);
|
||||
ASSERT_NO_THROW(++registry.raw<char>().begin());
|
||||
|
||||
auto view = registry.raw<char>();
|
||||
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
|
||||
registry.assign<char>(e0);
|
||||
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{2});
|
||||
|
||||
registry.get<char>(e0) = '1';
|
||||
registry.get<char>(e1) = '2';
|
||||
|
||||
for(auto &&component: view) {
|
||||
ASSERT_TRUE(component == '1' || component == '2');
|
||||
}
|
||||
|
||||
ASSERT_EQ(*(view.data() + 0), e1);
|
||||
ASSERT_EQ(*(view.data() + 1), e0);
|
||||
|
||||
ASSERT_EQ(*(view.raw() + 0), '2');
|
||||
ASSERT_EQ(*(static_cast<const decltype(view) &>(view).raw() + 1), '1');
|
||||
|
||||
for(auto &&component: view) {
|
||||
// verifies that iterators return references to components
|
||||
component = '0';
|
||||
}
|
||||
|
||||
for(auto &&component: view) {
|
||||
ASSERT_TRUE(component == '0');
|
||||
}
|
||||
|
||||
registry.remove<char>(e0);
|
||||
registry.remove<char>(e1);
|
||||
|
||||
ASSERT_EQ(view.begin(), view.end());
|
||||
}
|
||||
|
||||
TEST(RawView, Empty) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
registry.create<char, double>();
|
||||
registry.create<char>();
|
||||
|
||||
auto view = registry.raw<int>();
|
||||
|
||||
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{0});
|
||||
|
||||
for(auto &&component: view) {
|
||||
(void)component;
|
||||
FAIL();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <cstdint>
|
||||
#include <entt/process/process.hpp>
|
||||
|
||||
struct FakeProcess: entt::Process<FakeProcess, int> {
|
||||
@@ -9,12 +10,19 @@ struct FakeProcess: entt::Process<FakeProcess, int> {
|
||||
void pause() noexcept { process_type::pause(); }
|
||||
void unpause() noexcept { process_type::unpause(); }
|
||||
|
||||
void init() { initInvoked = true; }
|
||||
void update(delta_type) { updateInvoked = true; }
|
||||
void init(void *) { initInvoked = true; }
|
||||
void succeeded() { succeededInvoked = true; }
|
||||
void failed() { failedInvoked = true; }
|
||||
void aborted() { abortedInvoked = true; }
|
||||
|
||||
void update(delta_type, void *data) {
|
||||
if(data) {
|
||||
(*static_cast<int *>(data))++;
|
||||
}
|
||||
|
||||
updateInvoked = true;
|
||||
}
|
||||
|
||||
bool initInvoked{false};
|
||||
bool updateInvoked{false};
|
||||
bool succeededInvoked{false};
|
||||
@@ -94,6 +102,26 @@ TEST(Process, Fail) {
|
||||
ASSERT_FALSE(process.abortedInvoked);
|
||||
}
|
||||
|
||||
TEST(Process, Data) {
|
||||
FakeProcess process;
|
||||
int value = 0;
|
||||
|
||||
process.tick(0, &value);
|
||||
process.succeed();
|
||||
process.tick(0, &value);
|
||||
|
||||
ASSERT_FALSE(process.alive());
|
||||
ASSERT_TRUE(process.dead());
|
||||
ASSERT_FALSE(process.paused());
|
||||
|
||||
ASSERT_EQ(value, 1);
|
||||
ASSERT_TRUE(process.initInvoked);
|
||||
ASSERT_TRUE(process.updateInvoked);
|
||||
ASSERT_TRUE(process.succeededInvoked);
|
||||
ASSERT_FALSE(process.failedInvoked);
|
||||
ASSERT_FALSE(process.abortedInvoked);
|
||||
}
|
||||
|
||||
TEST(Process, AbortNextTick) {
|
||||
FakeProcess process;
|
||||
|
||||
@@ -131,13 +159,13 @@ TEST(Process, AbortImmediately) {
|
||||
|
||||
TEST(ProcessAdaptor, Resolved) {
|
||||
bool updated = false;
|
||||
auto lambda = [&updated](uint64_t, auto resolve, auto) {
|
||||
auto lambda = [&updated](std::uint64_t, void *, auto resolve, auto) {
|
||||
ASSERT_FALSE(updated);
|
||||
updated = true;
|
||||
resolve();
|
||||
};
|
||||
|
||||
auto process = entt::ProcessAdaptor<decltype(lambda), uint64_t>{lambda};
|
||||
auto process = entt::ProcessAdaptor<decltype(lambda), std::uint64_t>{lambda};
|
||||
|
||||
process.tick(0);
|
||||
|
||||
@@ -147,16 +175,32 @@ TEST(ProcessAdaptor, Resolved) {
|
||||
|
||||
TEST(ProcessAdaptor, Rejected) {
|
||||
bool updated = false;
|
||||
auto lambda = [&updated](uint64_t, auto, auto rejected) {
|
||||
auto lambda = [&updated](std::uint64_t, void *, auto, auto rejected) {
|
||||
ASSERT_FALSE(updated);
|
||||
updated = true;
|
||||
rejected();
|
||||
};
|
||||
|
||||
auto process = entt::ProcessAdaptor<decltype(lambda), uint64_t>{lambda};
|
||||
auto process = entt::ProcessAdaptor<decltype(lambda), std::uint64_t>{lambda};
|
||||
|
||||
process.tick(0);
|
||||
|
||||
ASSERT_TRUE(process.rejected());
|
||||
ASSERT_TRUE(updated);
|
||||
}
|
||||
|
||||
TEST(ProcessAdaptor, Data) {
|
||||
int value = 0;
|
||||
|
||||
auto lambda = [](std::uint64_t, void *data, auto resolve, auto) {
|
||||
*static_cast<int *>(data) = 42;
|
||||
resolve();
|
||||
};
|
||||
|
||||
auto process = entt::ProcessAdaptor<decltype(lambda), std::uint64_t>{lambda};
|
||||
|
||||
process.tick(0, &value);
|
||||
|
||||
ASSERT_TRUE(process.dead());
|
||||
ASSERT_EQ(value, 42);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ struct FooProcess: entt::Process<FooProcess, int> {
|
||||
: onUpdate{onUpdate}, onAborted{onAborted}
|
||||
{}
|
||||
|
||||
void update(delta_type) { onUpdate(); }
|
||||
void update(delta_type, void *) { onUpdate(); }
|
||||
void aborted() { onAborted(); }
|
||||
|
||||
std::function<void()> onUpdate;
|
||||
@@ -16,7 +16,7 @@ struct FooProcess: entt::Process<FooProcess, int> {
|
||||
};
|
||||
|
||||
struct SucceededProcess: entt::Process<SucceededProcess, int> {
|
||||
void update(delta_type) {
|
||||
void update(delta_type, void *) {
|
||||
ASSERT_FALSE(updated);
|
||||
updated = true;
|
||||
++invoked;
|
||||
@@ -30,7 +30,7 @@ struct SucceededProcess: entt::Process<SucceededProcess, int> {
|
||||
unsigned int SucceededProcess::invoked = 0;
|
||||
|
||||
struct FailedProcess: entt::Process<FailedProcess, int> {
|
||||
void update(delta_type) {
|
||||
void update(delta_type, void *) {
|
||||
ASSERT_FALSE(updated);
|
||||
updated = true;
|
||||
fail();
|
||||
@@ -92,11 +92,11 @@ TEST(Scheduler, Functor) {
|
||||
bool firstFunctor = false;
|
||||
bool secondFunctor = false;
|
||||
|
||||
scheduler.attach([&firstFunctor](auto, auto resolve, auto){
|
||||
scheduler.attach([&firstFunctor](auto, void *, auto resolve, auto){
|
||||
ASSERT_FALSE(firstFunctor);
|
||||
firstFunctor = true;
|
||||
resolve();
|
||||
}).then([&secondFunctor](auto, auto, auto reject){
|
||||
}).then([&secondFunctor](auto, void *, auto, auto reject){
|
||||
ASSERT_FALSE(secondFunctor);
|
||||
secondFunctor = true;
|
||||
reject();
|
||||
|
||||
@@ -27,6 +27,7 @@ TEST(ResourceCache, Functionalities) {
|
||||
ASSERT_FALSE(cache.contains(hs2));
|
||||
|
||||
ASSERT_FALSE(cache.load<BrokenLoader>(hs1, 42));
|
||||
ASSERT_FALSE(cache.reload<BrokenLoader>(hs1, 42));
|
||||
|
||||
ASSERT_EQ(cache.size(), entt::ResourceCache<Resource>::size_type{});
|
||||
ASSERT_TRUE(cache.empty());
|
||||
@@ -34,6 +35,7 @@ TEST(ResourceCache, Functionalities) {
|
||||
ASSERT_FALSE(cache.contains(hs2));
|
||||
|
||||
ASSERT_TRUE(cache.load<Loader>(hs1, 42));
|
||||
ASSERT_TRUE(cache.reload<Loader>(hs1, 42));
|
||||
|
||||
ASSERT_NE(cache.size(), entt::ResourceCache<Resource>::size_type{});
|
||||
ASSERT_FALSE(cache.empty());
|
||||
@@ -77,4 +79,7 @@ TEST(ResourceCache, Functionalities) {
|
||||
|
||||
ASSERT_EQ(cache.size(), entt::ResourceCache<Resource>::size_type{});
|
||||
ASSERT_TRUE(cache.empty());
|
||||
|
||||
ASSERT_TRUE(cache.temp<Loader>(42));
|
||||
ASSERT_TRUE(cache.empty());
|
||||
}
|
||||
|
||||
@@ -86,6 +86,21 @@ TEST(SigH, Clear) {
|
||||
ASSERT_TRUE(sigh.empty());
|
||||
}
|
||||
|
||||
TEST(SigH, Swap) {
|
||||
entt::SigH<void(int &)> sigh1;
|
||||
entt::SigH<void(int &)> sigh2;
|
||||
|
||||
sigh1.connect<&S::f>();
|
||||
|
||||
ASSERT_FALSE(sigh1.empty());
|
||||
ASSERT_TRUE(sigh2.empty());
|
||||
|
||||
std::swap(sigh1, sigh2);
|
||||
|
||||
ASSERT_TRUE(sigh1.empty());
|
||||
ASSERT_FALSE(sigh2.empty());
|
||||
}
|
||||
|
||||
TEST(SigH, Functions) {
|
||||
entt::SigH<void(int &)> sigh;
|
||||
int v = 0;
|
||||
|
||||
@@ -91,6 +91,21 @@ TEST(Signal, Clear) {
|
||||
ASSERT_TRUE(signal.empty());
|
||||
}
|
||||
|
||||
TEST(Signal, Swap) {
|
||||
entt::Signal<void(const int &)> sig1;
|
||||
entt::Signal<void(const int &)> sig2;
|
||||
|
||||
sig1.connect<&S::f>();
|
||||
|
||||
ASSERT_FALSE(sig1.empty());
|
||||
ASSERT_TRUE(sig2.empty());
|
||||
|
||||
std::swap(sig1, sig2);
|
||||
|
||||
ASSERT_TRUE(sig1.empty());
|
||||
ASSERT_FALSE(sig2.empty());
|
||||
}
|
||||
|
||||
TEST(Signal, Functions) {
|
||||
entt::Signal<void(const int &)> signal;
|
||||
auto val = S::i + 1;
|
||||
|
||||
426
test/mod/mod.cpp
Normal file
426
test/mod/mod.cpp
Normal file
@@ -0,0 +1,426 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <cassert>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <duktape.h>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
template<typename Type>
|
||||
struct tag { using type = Type; };
|
||||
|
||||
struct Position {
|
||||
double x;
|
||||
double y;
|
||||
};
|
||||
|
||||
struct Renderable {};
|
||||
|
||||
struct DuktapeRuntime {
|
||||
std::map<duk_uint_t, std::string> components;
|
||||
};
|
||||
|
||||
template<typename Comp>
|
||||
duk_ret_t set(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
registry.accommodate<Comp>(entity);
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<>
|
||||
duk_ret_t set<Position>(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
const auto x = duk_require_number(ctx, 2);
|
||||
const auto y = duk_require_number(ctx, 3);
|
||||
registry.accommodate<Position>(entity, x, y);
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<>
|
||||
duk_ret_t set<DuktapeRuntime>(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
const auto type = duk_require_uint(ctx, 1);
|
||||
|
||||
duk_dup(ctx, 2);
|
||||
|
||||
if(!registry.has<DuktapeRuntime>(entity)) {
|
||||
registry.assign<DuktapeRuntime>(entity).components[type] = duk_json_encode(ctx, -1);
|
||||
} else {
|
||||
registry.get<DuktapeRuntime>(entity).components[type] = duk_json_encode(ctx, -1);
|
||||
}
|
||||
|
||||
duk_pop(ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
duk_ret_t unset(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
registry.remove<Comp>(entity);
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<>
|
||||
duk_ret_t unset<DuktapeRuntime>(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
const auto type = duk_require_uint(ctx, 1);
|
||||
|
||||
auto &components = registry.get<DuktapeRuntime>(entity).components;
|
||||
assert(components.find(type) != components.cend());
|
||||
components.erase(type);
|
||||
|
||||
if(components.empty()) {
|
||||
registry.remove<DuktapeRuntime>(entity);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
duk_ret_t has(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
duk_push_boolean(ctx, registry.has<Comp>(entity));
|
||||
return 1;
|
||||
}
|
||||
|
||||
template<>
|
||||
duk_ret_t has<DuktapeRuntime>(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
duk_push_boolean(ctx, registry.has<DuktapeRuntime>(entity));
|
||||
|
||||
if(registry.has<DuktapeRuntime>(entity)) {
|
||||
const auto type = duk_require_uint(ctx, 1);
|
||||
const auto &components = registry.get<DuktapeRuntime>(entity).components;
|
||||
duk_push_boolean(ctx, components.find(type) != components.cend());
|
||||
} else {
|
||||
duk_push_false(ctx);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
duk_ret_t get(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
assert(registry.has<Comp>(duk_require_uint(ctx, 0)));
|
||||
duk_push_object(ctx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
template<>
|
||||
duk_ret_t get<Position>(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
const auto &position = registry.get<Position>(entity);
|
||||
|
||||
const auto idx = duk_push_object(ctx);
|
||||
|
||||
duk_push_string(ctx, "x");
|
||||
duk_push_number(ctx, position.x);
|
||||
duk_def_prop(ctx, idx, DUK_DEFPROP_HAVE_VALUE);
|
||||
|
||||
duk_push_string(ctx, "y");
|
||||
duk_push_number(ctx, position.y);
|
||||
duk_def_prop(ctx, idx, DUK_DEFPROP_HAVE_VALUE);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
template<>
|
||||
duk_ret_t get<DuktapeRuntime>(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
const auto type = duk_require_uint(ctx, 1);
|
||||
|
||||
auto &runtime = registry.get<DuktapeRuntime>(entity);
|
||||
assert(runtime.components.find(type) != runtime.components.cend());
|
||||
|
||||
duk_push_string(ctx, runtime.components[type].c_str());
|
||||
duk_json_decode(ctx, -1);
|
||||
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
class DuktapeRegistry {
|
||||
// I'm pretty sure I won't have more than 99 components in the example
|
||||
static constexpr entt::DefaultRegistry::component_type udef = 100;
|
||||
|
||||
struct Func {
|
||||
using func_type = duk_ret_t(*)(duk_context *, entt::DefaultRegistry &);
|
||||
using test_type = bool(entt::DefaultRegistry:: *)(entt::DefaultRegistry::entity_type) const;
|
||||
|
||||
func_type set;
|
||||
func_type unset;
|
||||
func_type has;
|
||||
func_type get;
|
||||
test_type test;
|
||||
};
|
||||
|
||||
template<typename... Comp>
|
||||
void reg() {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type acc = { (func[registry.component<Comp>()] = {
|
||||
&::set<Comp>,
|
||||
&::unset<Comp>,
|
||||
&::has<Comp>,
|
||||
&::get<Comp>,
|
||||
&entt::DefaultRegistry::has<Comp>
|
||||
}, 0)... };
|
||||
(void)acc;
|
||||
}
|
||||
|
||||
static DuktapeRegistry & instance(duk_context *ctx) {
|
||||
duk_push_this(ctx);
|
||||
|
||||
duk_push_string(ctx, DUK_HIDDEN_SYMBOL("dreg"));
|
||||
duk_get_prop(ctx, -2);
|
||||
auto &dreg = *static_cast<DuktapeRegistry *>(duk_require_pointer(ctx, -1));
|
||||
duk_pop_2(ctx);
|
||||
|
||||
return dreg;
|
||||
}
|
||||
|
||||
template<Func::func_type Func::*Op>
|
||||
static duk_ret_t invoke(duk_context *ctx) {
|
||||
auto &dreg = instance(ctx);
|
||||
auto &func = dreg.func;
|
||||
auto ®istry = dreg.registry;
|
||||
auto type = duk_require_uint(ctx, 1);
|
||||
|
||||
if(type >= udef) {
|
||||
type = registry.component<DuktapeRuntime>();
|
||||
}
|
||||
|
||||
assert(func.find(type) != func.cend());
|
||||
|
||||
return (func[type].*Op)(ctx, registry);
|
||||
}
|
||||
|
||||
public:
|
||||
DuktapeRegistry(entt::DefaultRegistry ®istry)
|
||||
: registry{registry}
|
||||
{
|
||||
reg<Position, Renderable, DuktapeRuntime>();
|
||||
}
|
||||
|
||||
static duk_ret_t identifier(duk_context *ctx) {
|
||||
static auto next = udef;
|
||||
duk_push_uint(ctx, next++);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static duk_ret_t create(duk_context *ctx) {
|
||||
auto &dreg = instance(ctx);
|
||||
duk_push_uint(ctx, dreg.registry.create());
|
||||
return 1;
|
||||
}
|
||||
|
||||
static duk_ret_t set(duk_context *ctx) {
|
||||
return invoke<&Func::set>(ctx);
|
||||
}
|
||||
|
||||
static duk_ret_t unset(duk_context *ctx) {
|
||||
return invoke<&Func::unset>(ctx);
|
||||
}
|
||||
|
||||
static duk_ret_t has(duk_context *ctx) {
|
||||
return invoke<&Func::has>(ctx);
|
||||
}
|
||||
|
||||
static duk_ret_t get(duk_context *ctx) {
|
||||
return invoke<&Func::get>(ctx);
|
||||
}
|
||||
|
||||
static duk_ret_t entities(duk_context *ctx) {
|
||||
const duk_idx_t nargs = duk_get_top(ctx);
|
||||
auto &dreg = instance(ctx);
|
||||
duk_uarridx_t pos = 0;
|
||||
|
||||
duk_push_array(ctx);
|
||||
|
||||
dreg.registry.each([ctx, nargs, &pos, &dreg](auto entity) {
|
||||
auto ®istry = dreg.registry;
|
||||
auto &func = dreg.func;
|
||||
bool match = true;
|
||||
|
||||
for (duk_idx_t arg = 0; match && arg < nargs; arg++) {
|
||||
auto type = duk_require_uint(ctx, arg);
|
||||
|
||||
if(type < udef) {
|
||||
assert(func.find(type) != func.cend());
|
||||
match = (registry.*func[type].test)(entity);
|
||||
} else {
|
||||
const auto ctype = registry.component<DuktapeRuntime>();
|
||||
assert(func.find(ctype) != func.cend());
|
||||
match = (registry.*func[ctype].test)(entity);
|
||||
|
||||
if(match) {
|
||||
auto &components = registry.get<DuktapeRuntime>(entity).components;
|
||||
match = (components.find(type) != components.cend());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(match) {
|
||||
duk_push_uint(ctx, entity);
|
||||
duk_put_prop_index(ctx, -2, pos++);
|
||||
}
|
||||
});
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<duk_uint_t, Func> func;
|
||||
entt::DefaultRegistry ®istry;
|
||||
};
|
||||
|
||||
const duk_function_list_entry js_DuktapeRegistry_methods[] = {
|
||||
{ "identifier", &DuktapeRegistry::identifier, 0 },
|
||||
{ "create", &DuktapeRegistry::create, 0 },
|
||||
{ "set", &DuktapeRegistry::set, DUK_VARARGS },
|
||||
{ "unset", &DuktapeRegistry::unset, 2 },
|
||||
{ "has", &DuktapeRegistry::has, 2 },
|
||||
{ "get", &DuktapeRegistry::get, 2 },
|
||||
{ "entities", &DuktapeRegistry::entities, DUK_VARARGS },
|
||||
{ nullptr, nullptr, 0 }
|
||||
};
|
||||
|
||||
void exportTypes(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
auto exportType = [](auto *ctx, auto ®istry, auto idx, auto type, const auto *name) {
|
||||
duk_push_string(ctx, name);
|
||||
duk_push_uint(ctx, registry.template component<typename decltype(type)::type>());
|
||||
duk_def_prop(ctx, idx, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_CLEAR_WRITABLE);
|
||||
};
|
||||
|
||||
auto idx = duk_push_object(ctx);
|
||||
|
||||
exportType(ctx, registry, idx, tag<Position>{}, "POSITION");
|
||||
exportType(ctx, registry, idx, tag<Renderable>{}, "RENDERABLE");
|
||||
|
||||
duk_put_global_string(ctx, "Types");
|
||||
}
|
||||
|
||||
void exportDuktapeRegistry(duk_context *ctx, DuktapeRegistry &dreg) {
|
||||
auto idx = duk_push_object(ctx);
|
||||
|
||||
duk_push_string(ctx, DUK_HIDDEN_SYMBOL("dreg"));
|
||||
duk_push_pointer(ctx, &dreg);
|
||||
duk_put_prop(ctx, idx);
|
||||
|
||||
duk_put_function_list(ctx, idx, js_DuktapeRegistry_methods);
|
||||
duk_put_global_string(ctx, "Registry");
|
||||
}
|
||||
|
||||
TEST(Mod, Duktape) {
|
||||
entt::DefaultRegistry registry;
|
||||
DuktapeRegistry dreg{registry};
|
||||
duk_context *ctx = duk_create_heap_default();
|
||||
|
||||
if(!ctx) {
|
||||
FAIL();
|
||||
}
|
||||
|
||||
exportTypes(ctx, registry);
|
||||
exportDuktapeRegistry(ctx, dreg);
|
||||
|
||||
const char *s0 = ""
|
||||
"Types[\"PLAYING_CHARACTER\"] = Registry.identifier();"
|
||||
"Types[\"VELOCITY\"] = Registry.identifier();"
|
||||
"";
|
||||
|
||||
if(duk_peval_string(ctx, s0)) {
|
||||
FAIL();
|
||||
}
|
||||
|
||||
registry.create(Position{ 0., 0. }, Renderable{});
|
||||
registry.create(Position{ 0., 0. });
|
||||
|
||||
const char *s1 = ""
|
||||
"Registry.entities(Types.POSITION, Types.RENDERABLE).forEach(function(entity) {"
|
||||
"Registry.set(entity, Types.POSITION, 100., 100.);"
|
||||
"});"
|
||||
"var entity = Registry.create();"
|
||||
"Registry.set(entity, Types.POSITION, 100., 100.);"
|
||||
"Registry.set(entity, Types.RENDERABLE);"
|
||||
"";
|
||||
|
||||
if(duk_peval_string(ctx, s1)) {
|
||||
FAIL();
|
||||
}
|
||||
|
||||
ASSERT_EQ(registry.view<DuktapeRuntime>().size(), 0u);
|
||||
ASSERT_EQ(registry.view<Position>().size(), 3u);
|
||||
ASSERT_EQ(registry.view<Renderable>().size(), 2u);
|
||||
|
||||
registry.view<Position>().each([®istry](auto entity, const auto &position) {
|
||||
ASSERT_FALSE(registry.has<DuktapeRuntime>(entity));
|
||||
|
||||
if(registry.has<Renderable>(entity)) {
|
||||
ASSERT_EQ(position.x, 100.);
|
||||
ASSERT_EQ(position.y, 100.);
|
||||
} else {
|
||||
ASSERT_EQ(position.x, 0.);
|
||||
ASSERT_EQ(position.y, 0.);
|
||||
}
|
||||
});
|
||||
|
||||
const char *s2 = ""
|
||||
"Registry.entities(Types.POSITION).forEach(function(entity) {"
|
||||
"if(!Registry.has(entity, Types.RENDERABLE)) {"
|
||||
"Registry.set(entity, Types.VELOCITY, { \"dx\": -100., \"dy\": -100. });"
|
||||
"Registry.set(entity, Types.PLAYING_CHARACTER, {});"
|
||||
"}"
|
||||
"});"
|
||||
"";
|
||||
|
||||
if(duk_peval_string(ctx, s2)) {
|
||||
FAIL();
|
||||
}
|
||||
|
||||
ASSERT_EQ(registry.view<DuktapeRuntime>().size(), 1u);
|
||||
ASSERT_EQ(registry.view<Position>().size(), 3u);
|
||||
ASSERT_EQ(registry.view<Renderable>().size(), 2u);
|
||||
|
||||
registry.view<DuktapeRuntime>().each([](auto, const DuktapeRuntime &runtime) {
|
||||
ASSERT_EQ(runtime.components.size(), 2u);
|
||||
});
|
||||
|
||||
const char *s3 = ""
|
||||
"Registry.entities(Types.POSITION, Types.RENDERABLE, Types.VELOCITY, Types.PLAYING_CHARACTER).forEach(function(entity) {"
|
||||
"var velocity = Registry.get(entity, Types.VELOCITY);"
|
||||
"Registry.set(entity, Types.POSITION, velocity.dx, velocity.dy)"
|
||||
"});"
|
||||
"";
|
||||
|
||||
if(duk_peval_string(ctx, s3)) {
|
||||
FAIL();
|
||||
}
|
||||
|
||||
ASSERT_EQ(registry.view<DuktapeRuntime>().size(), 1u);
|
||||
ASSERT_EQ(registry.view<Position>().size(), 3u);
|
||||
ASSERT_EQ(registry.view<Renderable>().size(), 2u);
|
||||
|
||||
registry.view<Position, Renderable, DuktapeRuntime>().each([](auto, const Position &position, const auto &...) {
|
||||
ASSERT_EQ(position.x, -100.);
|
||||
ASSERT_EQ(position.y, -100.);
|
||||
});
|
||||
|
||||
const char *s4 = ""
|
||||
"Registry.entities(Types.VELOCITY, Types.PLAYING_CHARACTER).forEach(function(entity) {"
|
||||
"Registry.unset(entity, Types.VELOCITY);"
|
||||
"Registry.unset(entity, Types.PLAYING_CHARACTER);"
|
||||
"});"
|
||||
"Registry.entities(Types.POSITION).forEach(function(entity) {"
|
||||
"Registry.unset(entity, Types.POSITION);"
|
||||
"});"
|
||||
"";
|
||||
|
||||
if(duk_peval_string(ctx, s4)) {
|
||||
FAIL();
|
||||
}
|
||||
|
||||
ASSERT_EQ(registry.view<DuktapeRuntime>().size(), 0u);
|
||||
ASSERT_EQ(registry.view<Position>().size(), 0u);
|
||||
ASSERT_EQ(registry.view<Renderable>().size(), 2u);
|
||||
|
||||
duk_destroy_heap(ctx);
|
||||
}
|
||||
182
test/snapshot/snapshot.cpp
Normal file
182
test/snapshot/snapshot.cpp
Normal file
@@ -0,0 +1,182 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <cereal/archives/json.hpp>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
struct Position {
|
||||
float x;
|
||||
float y;
|
||||
};
|
||||
|
||||
struct Timer {
|
||||
int duration;
|
||||
int elapsed{0};
|
||||
};
|
||||
|
||||
struct Relationship {
|
||||
entt::DefaultRegistry::entity_type parent;
|
||||
};
|
||||
|
||||
template<class Archive>
|
||||
void serialize(Archive &archive, Position &position) {
|
||||
archive(position.x, position.y);
|
||||
}
|
||||
|
||||
template<class Archive>
|
||||
void serialize(Archive &archive, Timer &timer) {
|
||||
archive(timer.duration);
|
||||
}
|
||||
|
||||
template<class Archive>
|
||||
void serialize(Archive &archive, Relationship &relationship) {
|
||||
archive(relationship.parent);
|
||||
}
|
||||
|
||||
TEST(Snapshot, Full) {
|
||||
std::stringstream storage;
|
||||
|
||||
entt::DefaultRegistry source;
|
||||
entt::DefaultRegistry destination;
|
||||
|
||||
auto e0 = source.create();
|
||||
source.assign<Position>(e0, 16.f, 16.f);
|
||||
|
||||
source.destroy(source.create());
|
||||
|
||||
auto e1 = source.create();
|
||||
source.assign<Position>(e1, .8f, .0f);
|
||||
source.assign<Relationship>(e1, e0);
|
||||
|
||||
auto e2 = source.create();
|
||||
|
||||
auto e3 = source.create();
|
||||
source.assign<Timer>(e3, 1000, 100);
|
||||
|
||||
source.destroy(e2);
|
||||
auto v2 = source.current(e2);
|
||||
|
||||
{
|
||||
// output finishes flushing its contents when it goes out of scope
|
||||
cereal::JSONOutputArchive output{storage};
|
||||
source.snapshot().entities(output).destroyed(output)
|
||||
.component<Position, Timer, Relationship>(output);
|
||||
}
|
||||
|
||||
cereal::JSONInputArchive input{storage};
|
||||
destination.restore().entities(input).destroyed(input)
|
||||
.component<Position, Timer, Relationship>(input);
|
||||
|
||||
ASSERT_TRUE(destination.valid(e0));
|
||||
ASSERT_TRUE(destination.has<Position>(e0));
|
||||
ASSERT_EQ(destination.get<Position>(e0).x, 16.f);
|
||||
ASSERT_EQ(destination.get<Position>(e0).y, 16.f);
|
||||
|
||||
ASSERT_TRUE(destination.valid(e1));
|
||||
ASSERT_TRUE(destination.has<Position>(e1));
|
||||
ASSERT_EQ(destination.get<Position>(e1).x, .8f);
|
||||
ASSERT_EQ(destination.get<Position>(e1).y, .0f);
|
||||
ASSERT_TRUE(destination.has<Relationship>(e1));
|
||||
ASSERT_EQ(destination.get<Relationship>(e1).parent, e0);
|
||||
|
||||
ASSERT_FALSE(destination.valid(e2));
|
||||
ASSERT_EQ(destination.current(e2), v2);
|
||||
|
||||
ASSERT_TRUE(destination.valid(e3));
|
||||
ASSERT_TRUE(destination.has<Timer>(e3));
|
||||
ASSERT_EQ(destination.get<Timer>(e3).duration, 1000);
|
||||
ASSERT_EQ(destination.get<Timer>(e3).elapsed, 0);
|
||||
}
|
||||
|
||||
TEST(Snapshot, Continuous) {
|
||||
std::stringstream storage;
|
||||
|
||||
entt::DefaultRegistry source;
|
||||
entt::DefaultRegistry destination;
|
||||
|
||||
std::vector<entt::DefaultRegistry::entity_type> entities;
|
||||
for(auto i = 0; i < 10; ++i) {
|
||||
entities.push_back(source.create());
|
||||
}
|
||||
|
||||
for(auto entity: entities) {
|
||||
source.destroy(entity);
|
||||
}
|
||||
|
||||
auto e0 = source.create();
|
||||
source.assign<Position>(e0, 0.f, 0.f);
|
||||
source.assign<Relationship>(e0, e0);
|
||||
|
||||
auto e1 = source.create();
|
||||
source.assign<Position>(e1, 1.f, 1.f);
|
||||
source.assign<Relationship>(e1, e0);
|
||||
|
||||
auto e2 = source.create();
|
||||
source.assign<Position>(e2, .2f, .2f);
|
||||
source.assign<Relationship>(e2, e0);
|
||||
|
||||
auto e3 = source.create();
|
||||
source.assign<Timer>(e3, 1000, 1000);
|
||||
source.assign<Relationship>(e3, e2);
|
||||
|
||||
{
|
||||
// output finishes flushing its contents when it goes out of scope
|
||||
cereal::JSONOutputArchive output{storage};
|
||||
source.snapshot().entities(output).component<Position, Relationship, Timer>(output);
|
||||
}
|
||||
|
||||
cereal::JSONInputArchive input{storage};
|
||||
entt::ContinuousLoader<entt::DefaultRegistry::entity_type> loader{destination};
|
||||
loader.entities(input)
|
||||
.component<Position>(input)
|
||||
.component<Relationship>(input, &Relationship::parent)
|
||||
.component<Timer>(input);
|
||||
|
||||
ASSERT_FALSE(destination.valid(e0));
|
||||
ASSERT_TRUE(loader.has(e0));
|
||||
|
||||
auto l0 = loader.map(e0);
|
||||
|
||||
ASSERT_TRUE(destination.valid(l0));
|
||||
ASSERT_TRUE(destination.has<Position>(l0));
|
||||
ASSERT_EQ(destination.get<Position>(l0).x, 0.f);
|
||||
ASSERT_EQ(destination.get<Position>(l0).y, 0.f);
|
||||
ASSERT_TRUE(destination.has<Relationship>(l0));
|
||||
ASSERT_EQ(destination.get<Relationship>(l0).parent, l0);
|
||||
|
||||
ASSERT_FALSE(destination.valid(e1));
|
||||
ASSERT_TRUE(loader.has(e1));
|
||||
|
||||
auto l1 = loader.map(e1);
|
||||
|
||||
ASSERT_TRUE(destination.valid(l1));
|
||||
ASSERT_TRUE(destination.has<Position>(l1));
|
||||
ASSERT_EQ(destination.get<Position>(l1).x, 1.f);
|
||||
ASSERT_EQ(destination.get<Position>(l1).y, 1.f);
|
||||
ASSERT_TRUE(destination.has<Relationship>(l1));
|
||||
ASSERT_EQ(destination.get<Relationship>(l1).parent, l0);
|
||||
|
||||
ASSERT_FALSE(destination.valid(e2));
|
||||
ASSERT_TRUE(loader.has(e2));
|
||||
|
||||
auto l2 = loader.map(e2);
|
||||
|
||||
ASSERT_TRUE(destination.valid(l2));
|
||||
ASSERT_TRUE(destination.has<Position>(l2));
|
||||
ASSERT_EQ(destination.get<Position>(l2).x, .2f);
|
||||
ASSERT_EQ(destination.get<Position>(l2).y, .2f);
|
||||
ASSERT_TRUE(destination.has<Relationship>(l2));
|
||||
ASSERT_EQ(destination.get<Relationship>(l2).parent, l0);
|
||||
|
||||
ASSERT_FALSE(destination.valid(e3));
|
||||
ASSERT_TRUE(loader.has(e3));
|
||||
|
||||
auto l3 = loader.map(e3);
|
||||
|
||||
ASSERT_TRUE(destination.valid(l3));
|
||||
ASSERT_TRUE(destination.has<Timer>(l3));
|
||||
ASSERT_EQ(destination.get<Timer>(l3).duration, 1000);
|
||||
ASSERT_EQ(destination.get<Timer>(l3).elapsed, 0);
|
||||
ASSERT_TRUE(destination.has<Relationship>(l3));
|
||||
ASSERT_EQ(destination.get<Relationship>(l3).parent, l2);
|
||||
}
|
||||
Reference in New Issue
Block a user