Compare commits

..

42 Commits

Author SHA1 Message Date
Paolo Monteverde
59cec88a28 fixing clang build (#38) 2018-02-08 14:56:07 +01:00
Michele Caini
3ebc75af80 updated version 2018-02-08 12:42:38 +01:00
Michele Caini
4dce474e03 revert: too risky a function 2018-02-08 12:27:48 +01:00
Michele Caini
31a18da578 fix #37 2018-02-08 12:23:48 +01:00
Michele Caini
8c499850fc fixed doc 2018-02-04 12:36:50 +01:00
Michele Caini
6b6998a247 duktape is now an external 2018-02-04 12:31:24 +01:00
Michele Caini
a6cb0fc856 added Registry::alive and Registry::orphans 2018-02-02 17:35:15 +01:00
Michele Caini
e36b93e87b fixed 2018-02-02 12:58:10 +01:00
Michele Caini
1e3723b8bb minor changes 2018-02-02 12:38:17 +01:00
Michele Caini
412372289e updated copyright 2018-01-26 17:28:40 +01:00
Michele Caini
96f7e66073 fixed 2018-01-14 00:53:55 +01:00
Michele Caini
6040f8f263 issue #31: multi component get 2018-01-14 00:32:23 +01:00
Michele Caini
9761b6e14a updated version 2017-12-29 18:29:38 +01:00
Michele Caini
cb49910ed2 allow attaching listeners at any time, allow removing current listener 2017-12-29 18:25:49 +01:00
Michele Caini
62bd742673 fixed doc 2017-12-27 17:59:57 +01:00
Michele Caini
42d0a3d734 v2.4.0 2017-12-27 17:57:04 +01:00
Michele Caini
f0f8681455 bug fixing 2017-12-27 17:55:26 +01:00
Michele Caini
c801afddcb added optional data to process::init 2017-12-23 00:30:00 +01:00
Michele Caini
20e0e1333e minor changes 2017-12-23 00:21:05 +01:00
Michele Caini
a6b373fec4 minor changes 2017-12-23 00:18:23 +01:00
Michele Caini
41c77720bb added optional data to scheduler/process 2017-12-22 23:59:07 +01:00
Michele Caini
92e6340120 cleanup 2017-12-22 23:58:49 +01:00
Michele Caini
1221f63cbd updated doc 2017-12-22 09:24:56 +01:00
Michele Caini
0f24418891 added ResourceCache::temp 2017-12-20 13:39:23 +01:00
Michele Caini
f477c0ab87 fixed reserve 2017-12-18 14:57:23 +01:00
Michele Caini
9358691901 added reserve 2017-12-18 14:08:38 +01:00
Michele Caini
cd343ba598 updated appveyor.yml (waiting for a new stable release of googletest) 2017-12-15 23:06:43 +01:00
Michele Caini
50069d3743 fixed docs 2017-12-14 23:15:47 +01:00
Michele Caini
1e03f27f23 v2.3.0 2017-12-14 22:56:40 +01:00
Michele Caini
36bb55a9ce doc: fixed 2017-12-13 16:20:36 +01:00
Michele Caini
451e4050db cleanup 2017-12-11 22:35:48 +01:00
Michele Caini
367fd3e87f minor changes 2017-12-11 16:04:25 +01:00
Michele Caini
a67a2e12fd minor changes 2017-12-11 15:03:43 +01:00
Michele Caini
292978daf0 #23: runtime components (doc) 2017-12-11 15:03:35 +01:00
Michele Caini
85a4a76a14 mod example with duktape 2017-12-10 17:43:48 +01:00
Michele Caini
9d0ab7ed70 added target entt_aob 2017-12-04 15:10:52 +01:00
Michele Caini
3d5b6a5e0b exposed family types 2017-12-04 14:59:08 +01:00
Michele Caini
ab20372093 minor changes 2017-12-04 14:06:10 +01:00
Michele Caini
ab887f30e4 typo 2017-11-21 08:33:48 +01:00
Michele Caini
6cb6a8c25f minor changes 2017-11-20 15:45:08 +01:00
Michele Caini
9d1d2aca0a updated build system 2017-11-18 17:31:11 +01:00
Michele Caini
75cb2cd1f7 improved sort functionalities 2017-11-18 15:54:04 +01:00
31 changed files with 1352 additions and 352 deletions

2
.gitignore vendored
View File

@@ -1,2 +1,2 @@
# QtCreator
*.user
TODO

View File

@@ -16,7 +16,7 @@ endif()
# Project configuration
#
project(entt VERSION 2.2.0)
project(entt VERSION 2.4.2)
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("*")
#
@@ -55,14 +55,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 +70,12 @@ 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)
# 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 +94,17 @@ 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
.travis.yml
)

View File

@@ -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

141
README.md
View File

@@ -136,7 +136,7 @@ int main() {
I started working on `EnTT` because of the wrong reason: my goal was to design
an entity-component system that beated another well known open source solution
in terms of performance.<br/>
I did it, of course, but it wasn't much satisfying. Actually it wasn't
In the end, I did it, but it wasn't much satisfying. Actually it wasn't
satisfying at all. The fastest and nothing more, fairly little indeed. When I
realized it, I tried hard to keep intact the great performance of `EnTT` and to
add all the features I wanted to see in *my* entity-component system at the same
@@ -149,7 +149,7 @@ of course.
## Performance
As it stands right now, `EnTT` is just fast enough for my requirements if
compared to my first choice (that was already amazingly fast indeed).<br/>
compared to my first choice (it was already amazingly fast actually).<br/>
Here is a comparision between the two (both of them compiled with GCC 7.2.0 on a
Dell XPS 13 out of the mid 2014):
@@ -168,8 +168,8 @@ Dell XPS 13 out of the mid 2014):
| Standard view, 10M entities, ten components<br/>Half of the entities have all the components | **0.0090s** | 0.0620s |
| Standard view, 10M entities, ten components<br/>One of the entities has all the components | 0.0070s | **1.3e-06s** |
| Persistent view, 10M entities, ten components | 0.0105s | **6.2e-07s** |
| Sort 150k entities, one component | - | **0.0084s** |
| Sort 150k entities, enforce permutation | - | **0.0067s** |
| Sort 150k entities, one component<br/>Arrays are in reverse order | - | **0.0043s** |
| Sort 150k entities, enforce permutation<br/>Arrays are in reverse order | - | **0.0006s** |
`EnTT` includes its own tests and benchmarks. See
[benchmark.cpp](https://github.com/skypjack/entt/blob/master/test/benchmark.cpp)
@@ -178,8 +178,8 @@ On Github users can find also a
[benchmark suite](https://github.com/abeimler/ecs_benchmark) that compares a
bunch of different projects, one of which is `EnTT`.
Of course, probably I'll try to get out of `EnTT` more features and better
performance in the future, mainly for fun.<br/>
Probably I'll try to get out of `EnTT` more features and better performance in
the future, mainly for fun.<br/>
If you want to contribute and/or have any suggestion, feel free to make a PR or
open an issue to discuss your idea.
@@ -260,7 +260,7 @@ Benchmarks are compiled only in release mode currently.
`EnTT` is a _bitset-free_ entity-component system that doesn't require users to
specify the component set at compile-time.<br/>
That's the reason for which users can instantiate the core class simply as:
This is why users can instantiate the core class simply like:
```cpp
entt::DefaultRegistry registry;
@@ -277,8 +277,8 @@ entt::DefaultRegistry<Comp0, Comp1, ..., CompN> registry;
`EnTT` is entirely designed around the principle that users have to pay only for
what they want.
When it comes to use an entity-componet system, the tradeoff is usually between
performance and memory usage. The faster it is, the more memory it uses.
When it comes to using an entity-componet system, the tradeoff is usually
between performance and memory usage. The faster it is, the more memory it uses.
However, slightly worse performance along non-critical paths are the right price
to pay to reduce memory usage and I've always wondered why this kind of tools do
not leave me the choice.<br/>
@@ -377,7 +377,7 @@ registry.assign<Position>(entity, 0., 0.);
// ...
auto &velocity = registry.assign<Velocity>(entity);
Velocity &velocity = registry.assign<Velocity>(entity);
velocity.dx = 0.;
velocity.dy = 0.;
```
@@ -390,22 +390,22 @@ registry.replace<Position>(entity, 0., 0.);
// ...
auto &velocity = registry.replace<Velocity>(entity);
Velocity &velocity = registry.replace<Velocity>(entity);
velocity.dx = 0.;
velocity.dy = 0.;
```
In case users want to assign a component to an entity, but it's unknown whether
the entity already has it or not, `accomodate` does the work in a single call
(there is a performance penalty to pay for that mainly due to the fact that it
must check if `entity` already has the given component or not):
(there is a performance penalty to pay for this mainly due to the fact that it
has to check if `entity` already has the given component or not):
```cpp
registry.accomodate<Position>(entity, 0., 0.);
// ...
auto &velocity = registry.accomodate<Velocity>(entity);
Velocity &velocity = registry.accomodate<Velocity>(entity);
velocity.dx = 0.;
velocity.dy = 0.;
```
@@ -438,7 +438,7 @@ registry.remove<Position>(entity);
Otherwise consider to use the `reset` member function. It behaves similarly to
`remove` but with a strictly defined behaviour (and a performance penalty is the
price to pay for that). In particular it removes the component if and only if it
price to pay for this). In particular it removes the component if and only if it
exists, otherwise it returns safely to the caller:
```cpp
@@ -464,13 +464,16 @@ their components are destroyed:
Finally, references to components can be retrieved simply by doing this:
```cpp
// either a non-const reference ...
entt::DefaultRegistry registry;
auto &position = registry.get<Position>(entity);
// ... or a const one
const auto &cregistry = registry;
const auto &position = cregistry.get<Position>(entity);
// const and non-const reference
const Position &position = cregistry.get<Position>(entity);
Position &position = registry.get<Position>(entity);
// const and non-const references
std::tuple<const Position &, const Velocity &> tup = cregistry.get<Position, Velocity>(entity);
std::tuple<Position &, Velocity &> tup = registry.get<Position, Velocity>(entity);
```
The `get` member function template gives direct access to the component of an
@@ -516,11 +519,11 @@ References to tags can be retrieved simply by doing this:
```cpp
// either a non-const reference ...
entt::DefaultRegistry registry;
auto &player = registry.get<PlayingCharacter>();
PlayingCharacter &player = registry.get<PlayingCharacter>();
// ... or a const one
const auto &cregistry = registry;
const auto &camera = cregistry.get<Camera>();
const Camera &camera = cregistry.get<Camera>();
```
The `get` member function template gives direct access to the tag as stored in
@@ -538,6 +541,56 @@ auto player = registry.attachee<PlayingCharacter>();
Note that iterating tags isn't possible for obvious reasons. Tags give direct
access to single entities and nothing more.
### Runtime components
Defining components at runtime is useful to support plugins and mods in general.
However, it seems impossible with a tool designed around a bunch of templates.
Indeed it's not that difficult.<br/>
Of course, some features cannot be easily exported into a runtime
environment. As an example, sorting a group of components defined at runtime
isn't for free if compared to most of the other operations. However, the basic
functionalities of an entity-component system such as `EnTT` fit the problem
perfectly and can also be used to manage runtime components if required.<br/>
All that is necessary to do it is to know the identifiers of the components. An
identifier is nothing more than a number or similar that can be used at runtime
to work with the type system.
In `EnTT`, identifiers are easily accessible:
```cpp
entt::DefaultRegistry registry;
// standard component identifier
auto ctype = registry.component<Position>();
// single instance component identifier
auto ttype = registry.tag<PlayingCharacter>();
```
Once the identifiers are made available, almost everything becomes pretty
simple.
#### A journey through a plugin
`EnTT` comes with an example (actually a test) that shows how to integrate
compile-time and runtime components in a stack based JavaScript environment. It
uses [`duktape`](https://github.com/svaarala/duktape) under the hood, mainly
because I wanted to learn how it works at the time I was writing the code.
It's not production-ready and overall performance can be highly improved.
However, I sacrificed optimizations in favor of a more readable piece of
code. I hope I succeeded.<br/>
Note also that this isn't neither the only nor (probably) the best way to do it.
In fact, the right way depends on the scripting language and the problem one is
facing in general.
The basic idea is that of creating a compile-time component aimed to map all the
runtime components assigned to an entity.<br/>
Identifiers come in use to address the right function from a map when invoked
from the runtime environment and to filter entities when iterating.<br/>
With a bit of gymnastic, one can narrow views and improve the performance to
some extent but it was not the goal of the example.
### Sorting: is it possible?
It goes without saying that sorting entities and components is possible with
@@ -655,7 +708,7 @@ To iterate a single component standard view, either use it in range-for loop:
auto view = registry.view<Renderable>();
for(auto entity: view) {
auto &renderable = view.get(entity);
Renderable &renderable = view.get(entity);
// ...
}
@@ -699,8 +752,12 @@ To iterate a multi component standard view, either use it in range-for loop:
auto view = registry.view<Position, Velocity>();
for(auto entity: view) {
auto &position = view.get<Position>(entity);
auto &velocity = view.get<Velocity>(entity);
// a component at a time ...
Position &position = view.get<Position>(entity);
Velocity &velocity = view.get<Velocity>(entity);
// ... or multiple components at once
std::tuple<Position &, Velocity &> tup = view.get<Position, Velocity>(entity);
// ...
}
@@ -767,8 +824,12 @@ To iterate a persistent view, either use it in range-for loop:
auto view = registry.persistent<Position, Velocity>();
for(auto entity: view) {
auto &position = view.get<Position>(entity);
auto &velocity = view.get<Velocity>(entity);
// a component at a time ...
Position &position = view.get<Position>(entity);
Velocity &velocity = view.get<Velocity>(entity);
// ... or multiple components at once
std::tuple<Position &, Velocity &> tup = view.get<Position, Velocity>(entity);
// ...
}
@@ -790,6 +851,28 @@ whether all the components have to be accessed or not.
function template of a registry during iterations, if possible. However, keep in
mind that it works only with the components of the view itself.
### Give me everything
Views are narrow windows on the entire list of entities. They work by filtering
entities according to their components.<br/>
In some cases there may be the need to iterate all the entities regardless of
their components. The registry offers a specific member function to do that:
```cpp
registry.each([](auto entity) {
// ...
});
```
Each entity ever created is returned, no matter if it's in use or not.<br/>
Usually, filtering entities that aren't currently in use is more expensive than
iterating them all and filtering out those in which one isn't interested.
As a rule of thumb, 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/>
In all the other cases, this is the way to go.
## Side notes
* Entity identifiers are numbers and nothing more. They are not classes and they
@@ -799,7 +882,7 @@ mind that it works only with the components of the view itself.
* As shown in the examples above, the preferred way to get references to the
components while iterating a view is by using the view itself. It's a faster
alternative to the `get` member function template that is part of the API of
the Registry. That's because the registry must ensure that a pool for the
the Registry. This is because the registry must ensure that a pool for the
given component exists before to use it; on the other side, views force the
construction of the pools for all their components and access them directly,
thus avoiding all the checks.
@@ -846,7 +929,7 @@ who has partecipated so far.
# License
Code and documentation Copyright (c) 2017 Michele Caini.<br/>
Code and documentation Copyright (c) 2018 Michele Caini.<br/>
Code released under
[the MIT license](https://github.com/skypjack/entt/blob/master/LICENSE).
Docs released under

View File

@@ -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/duktape.in Normal file
View 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 ""
)

View File

@@ -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(

View File

@@ -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;
// FowlerNollVo hash function v. 1a - the good
static constexpr std::uint64_t helper(std::uint64_t partial, const char *str) noexcept {

View File

@@ -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;

View File

@@ -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>
@@ -93,4 +93,4 @@ struct entt_traits<std::uint64_t> {
}
#endif // ENTT_ENTITY_ENTT_HPP
#endif // ENTT_ENTITY_ENTT_TRAITS_HPP

View File

@@ -2,15 +2,18 @@
#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 "sparse_set.hpp"
#include "traits.hpp"
#include "view.hpp"
@@ -106,7 +109,6 @@ class Registry {
template<typename Component>
Pool<Component> & pool() noexcept {
assert(managed<Component>());
return const_cast<Pool<Component> &>(const_cast<const Registry *>(this)->pool<Component>());
}
@@ -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.
@@ -193,6 +233,33 @@ public:
return entities.size() - available.size();
}
/**
* @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) {
ensure<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);
available.reserve(cap);
}
/**
* @brief Returns the number of entities ever created.
* @return Number of entities ever created.
@@ -226,7 +293,9 @@ public:
* @return True if the identifier is still valid, false otherwise.
*/
bool valid(entity_type entity) const noexcept {
const auto entt = entity & traits_type::entity_mask;
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 entity_type entt = promotion_type{entity} & traits_type::entity_mask;
return (entt < entities.size() && entities[entt] == entity);
}
@@ -257,7 +326,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;
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;
assert(entt < entities.size());
return version_type((entities[entt] >> traits_type::entity_shift) & traits_type::version_mask);
}
@@ -367,10 +438,10 @@ 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 version = version_type{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);
@@ -413,7 +484,6 @@ public:
}
tags[ttype].reset(new Attaching<Tag>{entity, { std::forward<Args>(args)... }});
tags[ttype]->entity = entity;
return static_cast<Attaching<Tag> *>(tags[ttype].get())->tag;
}
@@ -556,11 +626,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 +646,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 +666,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.
*
@@ -683,22 +792,16 @@ 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)));
});
ensure<Component>().sort(std::move(compare));
}
/**
@@ -796,7 +899,7 @@ public:
available.clear();
for(auto &&entity: entities) {
const auto version = 1 + ((entity >> traits_type::entity_shift) & traits_type::version_mask);
const auto version = version_type{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);
}
@@ -818,6 +921,31 @@ public:
}
}
/**
* @brief Iterate entities and applies them the given function object.
*
* The function object is invoked for each entity, no matter if it's in use
* or not.<br/>
* The signature of the function should be equivalent to the following:
*
* @code{.cpp}
* void(entity_type);
* @endcode
*
* 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.
*
* @tparam Func Type of the function object to invoke.
* @param func A valid function object.
*/
template<typename Func>
void each(Func func) const {
for(auto pos = entities.size(); pos > size_type{0}; --pos) {
func(entities[pos-1]);
}
}
/**
* @brief Returns a standard view for the given components.
*

View File

@@ -3,11 +3,13 @@
#include <algorithm>
#include <numeric>
#include <utility>
#include <vector>
#include <cstddef>
#include <cassert>
#include "traits.hpp"
#include <type_traits>
#include "entt_traits.hpp"
namespace entt {
@@ -54,10 +56,10 @@ template<typename Entity>
class SparseSet<Entity> {
using traits_type = entt_traits<Entity>;
struct Iterator {
struct Iterator final {
using value_type = Entity;
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}
{}
@@ -83,11 +85,11 @@ class SparseSet<Entity> {
}
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 +117,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) {
reverse.reserve(cap);
direct.reserve(cap);
}
/**
* @brief Returns the number of elements in a sparse set.
*
@@ -195,7 +210,9 @@ 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;
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;
// the in-use control bit permits to avoid accessing the direct vector
return (entt < reverse.size()) && (reverse[entt] & in_use);
}
@@ -232,7 +249,9 @@ public:
*/
void construct(entity_type entity) {
assert(!has(entity));
const auto entt = entity & traits_type::entity_mask;
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;
if(!(entt < reverse.size())) {
reverse.resize(entt+1, pos_type{});
@@ -281,50 +300,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
@@ -342,32 +331,23 @@ public:
*
* @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()));
virtual 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 > 0 && 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;
}
}
/**
@@ -435,6 +415,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.
*
@@ -523,7 +516,7 @@ public:
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.push_back(object_type{ std::forward<Args>(args)... });
return instances.back();
}
@@ -540,30 +533,102 @@ 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 the raw pointer returned by `data`
* 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) {
auto lhs = copy[curr];
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 `sort`. See `begin` and `end` for more
* details.
*
* @note
* Attempting to iterate elements using the raw pointer returned by `data`
* gives no guarantees on the order, even though `sort` has been invoked.
*
* @param other The sparse sets that imposes the order of the entities.
*/
void respect(const SparseSet<Entity> &other) noexcept override {
auto from = other.begin();
auto to = other.end();
pos_type pos = underlying_type::size() - 1;
const auto *direct = underlying_type::data();
while(pos > 0 && from != to) {
if(underlying_type::has(*from)) {
if(*from != *(direct + pos)) {
auto candidate = underlying_type::get(*from);
std::swap(instances[pos], instances[candidate]);
underlying_type::swap(pos, candidate);
}
--pos;
}
++from;
}
}
/**

View File

@@ -4,6 +4,8 @@
#include <tuple>
#include <utility>
#include <algorithm>
#include <type_traits>
#include "sparse_set.hpp"
@@ -188,25 +190,49 @@ public:
}
/**
* @brief Iterate the entities and applies them the given function object.
* @brief Returns the components assigned to the given entity.
*
* The function object is invoked for each entity. It is provided with the
* entity itself and a set of references to all the components of the
* view.<br/>
* The signature of the function should be equivalent to the following:
* Prefer this function instead of `Registry::get` during iterations. It has
* far better performance than its companion function.
*
* @code{.cpp}
* void(entity_type, Component &...);
* @endcode
* @warning
* Attempting to use invalid component types results in a compilation error.
* Attempting to use an entity that doesn't belong to the view results in
* undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode if
* the view doesn't contain the given entity.
*
* @tparam Func Type of the function object to invoke.
* @param func A valid function object.
* @tparam Comp Types of the components to get.
* @param entity A valid entity identifier.
* @return The components assigned to the entity.
*/
template<typename Func>
void each(Func &&func) {
for(auto entity: *this) {
std::forward<Func>(func)(entity, get<Component>(entity)...);
}
template<typename... Comp>
std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<const Comp &...>>
get(entity_type entity) const noexcept {
return std::tuple<const Comp &...>{ get<Comp>(entity)... };
}
/**
* @brief Returns the components assigned to the given entity.
*
* Prefer this function instead of `Registry::get` during iterations. It has
* far better performance than its companion function.
*
* @warning
* Attempting to use invalid component types results in a compilation error.
* Attempting to use an entity that doesn't belong to the view results in
* undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode if
* the view doesn't contain the given entity.
*
* @tparam Comp Types of the components to get.
* @param entity A valid entity identifier.
* @return The components assigned to the entity.
*/
template<typename... Comp>
std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<Comp &...>>
get(entity_type entity) noexcept {
return std::tuple<Comp &...>{ get<Comp>(entity)... };
}
/**
@@ -225,12 +251,34 @@ public:
* @param func A valid function object.
*/
template<typename Func>
void each(Func &&func) const {
void each(Func func) const {
for(auto entity: *this) {
std::forward<Func>(func)(entity, get<Component>(entity)...);
func(entity, get<Component>(entity)...);
}
}
/**
* @brief Iterate the entities and applies them the given function object.
*
* The function object is invoked for each entity. It is provided with the
* entity itself and a set of references to all the components of the
* view.<br/>
* The signature of the function should be equivalent to the following:
*
* @code{.cpp}
* void(entity_type, Component &...);
* @endcode
*
* @tparam Func Type of the function object to invoke.
* @param func A valid function object.
*/
template<typename Func>
void each(Func func) {
const_cast<const PersistentView *>(this)->each([&func](entity_type entity, const Component &... component) {
func(entity, const_cast<Component &>(component)...);
});
}
/**
* @brief Sort the shared pool of entities according to the given component.
*
@@ -294,24 +342,25 @@ private:
* @sa PersistentView
*
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam First One of the components to iterate.
* @tparam Other The rest of the components to iterate.
* @tparam Component Types of components iterated by the view.
*/
template<typename Entity, typename First, typename... Other>
template<typename Entity, typename... Component>
class View final {
template<typename Component>
using pool_type = SparseSet<Entity, Component>;
static_assert(sizeof...(Component) > 1, "!");
template<typename Comp>
using pool_type = SparseSet<Entity, Comp>;
using base_pool_type = SparseSet<Entity>;
using underlying_iterator_type = typename base_pool_type::iterator_type;
using repo_type = std::tuple<pool_type<First> &, pool_type<Other> &...>;
using repo_type = std::tuple<pool_type<Component> &...>;
class Iterator {
inline bool valid() const noexcept {
using accumulator_type = bool[];
auto entity = *begin;
bool all = std::get<pool_type<First> &>(pools).has(entity);
accumulator_type accumulator = { (all = all && std::get<pool_type<Other> &>(pools).has(entity))... };
bool all = true;
accumulator_type accumulator = { all, (all = all && std::get<pool_type<Component> &>(pools).has(entity))... };
(void)accumulator;
return all;
}
@@ -366,11 +415,10 @@ public:
/**
* @brief Constructs a view out of a bunch of pools of components.
* @param pool A reference to a pool of components.
* @param other Other references to pools of components.
* @param pools References to pools of components.
*/
View(pool_type<First> &pool, pool_type<Other>&... other) noexcept
: pools{pool, other...}, view{nullptr}
View(pool_type<Component>&... pools) noexcept
: pools{pools...}, view{nullptr}
{
reset();
}
@@ -425,13 +473,13 @@ public:
* An assertion will abort the execution at runtime in debug mode if
* the view doesn't contain the given entity.
*
* @tparam Component Type of the component to get.
* @tparam Comp Type of the component to get.
* @param entity A valid entity identifier.
* @return The component assigned to the entity.
*/
template<typename Component>
const Component & get(entity_type entity) const noexcept {
return std::get<pool_type<Component> &>(pools).get(entity);
template<typename Comp>
const Comp & get(entity_type entity) const noexcept {
return std::get<pool_type<Comp> &>(pools).get(entity);
}
/**
@@ -447,35 +495,59 @@ public:
* An assertion will abort the execution at runtime in debug mode if
* the view doesn't contain the given entity.
*
* @tparam Component Type of the component to get.
* @tparam Comp Type of the component to get.
* @param entity A valid entity identifier.
* @return The component assigned to the entity.
*/
template<typename Component>
Component & get(entity_type entity) noexcept {
return const_cast<Component &>(const_cast<const View *>(this)->get<Component>(entity));
template<typename Comp>
Comp & get(entity_type entity) noexcept {
return const_cast<Comp &>(const_cast<const View *>(this)->get<Comp>(entity));
}
/**
* @brief Iterate the entities and applies them the given function object.
* @brief Returns the components assigned to the given entity.
*
* The function object is invoked for each entity. It is provided with the
* entity itself and a set of references to all the components of the
* view.<br/>
* The signature of the function should be equivalent to the following:
* Prefer this function instead of `Registry::get` during iterations. It has
* far better performance than its companion function.
*
* @code{.cpp}
* void(entity_type, Component &...);
* @endcode
* @warning
* Attempting to use invalid component types results in a compilation error.
* Attempting to use an entity that doesn't belong to the view results in
* undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode if
* the view doesn't contain the given entity.
*
* @tparam Func Type of the function object to invoke.
* @param func A valid function object.
* @tparam Comp Types of the components to get.
* @param entity A valid entity identifier.
* @return The components assigned to the entity.
*/
template<typename Func>
void each(Func &&func) {
for(auto entity: *this) {
std::forward<Func>(func)(entity, get<First>(entity), get<Other>(entity)...);
}
template<typename... Comp>
std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<const Comp &...>>
get(entity_type entity) const noexcept {
return std::tuple<const Comp &...>{ get<Comp>(entity)... };
}
/**
* @brief Returns the components assigned to the given entity.
*
* Prefer this function instead of `Registry::get` during iterations. It has
* far better performance than its companion function.
*
* @warning
* Attempting to use invalid component types results in a compilation error.
* Attempting to use an entity that doesn't belong to the view results in
* undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode if
* the view doesn't contain the given entity.
*
* @tparam Comp Types of the components to get.
* @param entity A valid entity identifier.
* @return The components assigned to the entity.
*/
template<typename... Comp>
std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<Comp &...>>
get(entity_type entity) noexcept {
return std::tuple<Comp &...>{ get<Comp>(entity)... };
}
/**
@@ -494,12 +566,34 @@ public:
* @param func A valid function object.
*/
template<typename Func>
void each(Func &&func) const {
void each(Func func) const {
for(auto entity: *this) {
std::forward<Func>(func)(entity, get<First>(entity), get<Other>(entity)...);
func(entity, get<Component>(entity)...);
}
}
/**
* @brief Iterate the entities and applies them the given function object.
*
* The function object is invoked for each entity. It is provided with the
* entity itself and a set of references to all the components of the
* view.<br/>
* The signature of the function should be equivalent to the following:
*
* @code{.cpp}
* void(entity_type, Component &...);
* @endcode
*
* @tparam Func Type of the function object to invoke.
* @param func A valid function object.
*/
template<typename Func>
void each(Func func) {
const_cast<const View *>(this)->each([&func](entity_type entity, const Component &... component) {
func(entity, const_cast<Component &>(component)...);
});
}
/**
* @brief Resets the view and reinitializes it.
*
@@ -511,9 +605,10 @@ public:
* meantime.
*/
void reset() {
using accumulator_type = void *[];
view = &std::get<pool_type<First> &>(pools);
accumulator_type accumulator = { (std::get<pool_type<Other> &>(pools).size() < view->size() ? (view = &std::get<pool_type<Other> &>(pools)) : nullptr)... };
using accumulator_type = size_type[];
auto probe = [this](auto sz, auto &pool) { return pool.size() < sz ? (view = &pool, pool.size()) : sz; };
size_type sz = std::max({ std::get<pool_type<Component> &>(pools).size()... }) + std::size_t{1};
accumulator_type accumulator = { sz, (sz = probe(sz, std::get<pool_type<Component> &>(pools)))... };
(void)accumulator;
}
@@ -712,27 +807,6 @@ public:
return const_cast<Component &>(const_cast<const View *>(this)->get(entity));
}
/**
* @brief Iterate the entities and applies them the given function object.
*
* The function object is invoked for each entity. It is provided with the
* entity itself and a reference to the component of the view.<br/>
* The signature of the function should be equivalent to the following:
*
* @code{.cpp}
* void(entity_type, Component &);
* @endcode
*
* @tparam Func Type of the function object to invoke.
* @param func A valid function object.
*/
template<typename Func>
void each(Func &&func) {
for(auto entity: *this) {
std::forward<Func>(func)(entity, get(entity));
}
}
/**
* @brief Iterate the entities and applies them the given function object.
*
@@ -748,12 +822,33 @@ public:
* @param func A valid function object.
*/
template<typename Func>
void each(Func &&func) const {
void each(Func func) const {
for(auto entity: *this) {
std::forward<Func>(func)(entity, get(entity));
func(entity, get(entity));
}
}
/**
* @brief Iterate the entities and applies them the given function object.
*
* The function object is invoked for each entity. It is provided with the
* entity itself and a reference to the component of the view.<br/>
* The signature of the function should be equivalent to the following:
*
* @code{.cpp}
* void(entity_type, Component &);
* @endcode
*
* @tparam Func Type of the function object to invoke.
* @param func A valid function object.
*/
template<typename Func>
void each(Func func) {
const_cast<const View *>(this)->each([&func](entity_type entity, const Component &component) {
func(entity, const_cast<Component &>(component));
});
}
private:
pool_type &pool;
};

View File

@@ -2,9 +2,9 @@
#include "core/hashed_string.hpp"
#include "core/ident.hpp"
#include "entity/actor.hpp"
#include "entity/entt_traits.hpp"
#include "entity/registry.hpp"
#include "entity/sparse_set.hpp"
#include "entity/traits.hpp"
#include "entity/view.hpp"
#include "locator/locator.hpp"
#include "process/process.hpp"

View File

@@ -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.
*
@@ -82,17 +60,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>
@@ -227,15 +218,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;
@@ -322,9 +314,10 @@ struct ProcessAdaptor: Process<ProcessAdaptor<Func, Delta>, Delta>, private Func
/**
* @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(); });
}
};

View File

@@ -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>;
@@ -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();
}
@@ -197,7 +197,7 @@ public:
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));
@@ -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 > 0; --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());
}

View File

@@ -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
@@ -130,7 +130,24 @@ public:
}
/**
* @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.

View File

@@ -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);
}
@@ -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);
@@ -178,10 +178,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 > decltype(pos){0}; --pos) {
auto &wrapper = wrappers[pos-1];
if(wrapper) {
wrapper->publish(buf);
}

View File

@@ -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);
@@ -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] ||

View File

@@ -25,14 +25,14 @@ struct Invoker<Ret(Args...), Collector> {
virtual ~Invoker() = default;
template<typename SFINAE = Ret>
typename std::enable_if<std::is_void<SFINAE>::value, bool>::type
typename std::enable_if_t<std::is_void<SFINAE>::value, bool>
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
typename std::enable_if_t<!std::is_void<SFINAE>::value, bool>
invoke(Collector &collector, proto_type proto, void *instance, Args... args) {
return collector(proto(instance, args...));
}
@@ -229,7 +229,8 @@ public:
* @param args Arguments to use to invoke listeners.
*/
void publish(Args... args) {
for(auto &&call: calls) {
for(auto pos = calls.size(); pos > size_type{0}; --pos) {
auto &call = calls[pos-1];
call.second(call.first, args...);
}
}
@@ -242,7 +243,9 @@ public:
collector_type collect(Args... args) {
collector_type collector;
for(auto &&call: calls) {
for(auto pos = calls.size(); pos > size_type{0}; --pos) {
auto &call = calls[pos-1];
if(!this->invoke(collector, call.second, call.first, args...)) {
break;
}

View File

@@ -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 > size_type{0}; --pos) {
auto &call = calls[pos-1];
if((call.second)(call.first, args...)) {
next.push_back(call);
}
}
calls.swap(next);
}
/**

View File

@@ -2,22 +2,40 @@
# 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 core
add_executable(

View File

@@ -1,18 +1,19 @@
#include <gtest/gtest.h>
#include <iostream>
#include <cstddef>
#include <cstdint>
#include <chrono>
#include <vector>
#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>
@@ -37,7 +38,7 @@ TEST(Benchmark, Construct) {
Timer timer;
for(uint64_t i = 0; i < 10000000L; i++) {
for(std::uint64_t i = 0; i < 10000000L; i++) {
registry.create();
}
@@ -50,7 +51,7 @@ TEST(Benchmark, Destroy) {
std::cout << "Destroying 10000000 entities" << std::endl;
for(uint64_t i = 0; i < 10000000L; i++) {
for(std::uint64_t i = 0; i < 10000000L; i++) {
entities.push_back(registry.create());
}
@@ -92,7 +93,7 @@ TEST(Benchmark, IterateSingleComponent10M) {
std::cout << "Iterating over 10000000 entities, one component" << std::endl;
for(uint64_t i = 0; i < 10000000L; i++) {
for(std::uint64_t i = 0; i < 10000000L; i++) {
registry.create<Position>();
}
@@ -106,7 +107,7 @@ TEST(Benchmark, IterateTwoComponents10M) {
std::cout << "Iterating over 10000000 entities, two components" << std::endl;
for(uint64_t i = 0; i < 10000000L; i++) {
for(std::uint64_t i = 0; i < 10000000L; i++) {
registry.create<Position, Velocity>();
}
@@ -120,7 +121,7 @@ TEST(Benchmark, IterateTwoComponents10MHalf) {
std::cout << "Iterating over 10000000 entities, two components, half of the entities have all the components" << std::endl;
for(uint64_t i = 0; i < 10000000L; i++) {
for(std::uint64_t i = 0; i < 10000000L; i++) {
auto entity = registry.create<Velocity>();
if(i % 2) { registry.assign<Position>(entity); }
}
@@ -135,7 +136,7 @@ TEST(Benchmark, IterateTwoComponents10MOne) {
std::cout << "Iterating over 10000000 entities, two components, only one entity has all the components" << std::endl;
for(uint64_t i = 0; i < 10000000L; i++) {
for(std::uint64_t i = 0; i < 10000000L; i++) {
auto entity = registry.create<Velocity>();
if(i == 5000000L) { registry.assign<Position>(entity); }
}
@@ -151,7 +152,7 @@ TEST(Benchmark, IterateTwoComponentsPersistent10M) {
std::cout << "Iterating over 10000000 entities, two components, persistent view" << std::endl;
for(uint64_t i = 0; i < 10000000L; i++) {
for(std::uint64_t i = 0; i < 10000000L; i++) {
registry.create<Position, Velocity>();
}
@@ -166,7 +167,7 @@ TEST(Benchmark, IterateTwoComponentsPersistent10MHalf) {
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++) {
for(std::uint64_t i = 0; i < 10000000L; i++) {
auto entity = registry.create<Velocity>();
if(i % 2) { registry.assign<Position>(entity); }
}
@@ -182,7 +183,7 @@ TEST(Benchmark, IterateTwoComponentsPersistent10MOne) {
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++) {
for(std::uint64_t i = 0; i < 10000000L; i++) {
auto entity = registry.create<Velocity>();
if(i == 5000000L) { registry.assign<Position>(entity); }
}
@@ -197,7 +198,7 @@ TEST(Benchmark, IterateFiveComponents10M) {
std::cout << "Iterating over 10000000 entities, five components" << std::endl;
for(uint64_t i = 0; i < 10000000L; i++) {
for(std::uint64_t i = 0; i < 10000000L; i++) {
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
}
@@ -211,7 +212,7 @@ TEST(Benchmark, IterateTenComponents10M) {
std::cout << "Iterating over 10000000 entities, ten components" << std::endl;
for(uint64_t i = 0; i < 10000000L; i++) {
for(std::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>>();
}
@@ -225,7 +226,7 @@ TEST(Benchmark, IterateTenComponents10MHalf) {
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++) {
for(std::uint64_t i = 0; i < 10000000L; i++) {
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
if(i % 2) { registry.assign<Position>(entity); }
}
@@ -240,7 +241,7 @@ TEST(Benchmark, IterateTenComponents10MOne) {
std::cout << "Iterating over 10000000 entities, ten components, only one entity has all the components" << std::endl;
for(uint64_t i = 0; i < 10000000L; i++) {
for(std::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); }
}
@@ -256,7 +257,7 @@ TEST(Benchmark, IterateFiveComponentsPersistent10M) {
std::cout << "Iterating over 10000000 entities, five components, persistent view" << std::endl;
for(uint64_t i = 0; i < 10000000L; i++) {
for(std::uint64_t i = 0; i < 10000000L; i++) {
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
}
@@ -271,7 +272,7 @@ TEST(Benchmark, IterateTenComponentsPersistent10M) {
std::cout << "Iterating over 10000000 entities, ten components, persistent view" << std::endl;
for(uint64_t i = 0; i < 10000000L; i++) {
for(std::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>>();
}
@@ -286,7 +287,7 @@ TEST(Benchmark, IterateTenComponentsPersistent10MHalf) {
std::cout << "Iterating over 10000000 entities, ten components, persistent view, half of the entities have all the components" << std::endl;
for(uint64_t i = 0; i < 10000000L; i++) {
for(std::uint64_t i = 0; i < 10000000L; i++) {
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
if(i % 2) { registry.assign<Position>(entity); }
}
@@ -302,7 +303,7 @@ TEST(Benchmark, IterateTenComponentsPersistent10MOne) {
std::cout << "Iterating over 10000000 entities, ten components, persistent view, only one entity has all the components" << std::endl;
for(uint64_t i = 0; i < 10000000L; i++) {
for(std::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); }
}
@@ -318,7 +319,7 @@ TEST(Benchmark, SortSingle) {
std::cout << "Sort 150000 entities, one component" << std::endl;
for(uint64_t i = 0; i < 150000L; i++) {
for(std::uint64_t i = 0; i < 150000L; i++) {
auto entity = registry.create<Position>({ i, i });
entities.push_back(entity);
}
@@ -338,7 +339,7 @@ TEST(Benchmark, SortMulti) {
std::cout << "Sort 150000 entities, two components" << std::endl;
for(uint64_t i = 0; i < 150000L; i++) {
for(std::uint64_t i = 0; i < 150000L; i++) {
auto entity = registry.create<Position, Velocity>({ i, i }, { i, i });
entities.push_back(entity);
}

View File

@@ -21,8 +21,8 @@ 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");

View File

@@ -1,3 +1,4 @@
#include <unordered_set>
#include <functional>
#include <gtest/gtest.h>
#include <entt/entity/registry.hpp>
@@ -6,6 +7,7 @@ TEST(DefaultRegistry, Functionalities) {
entt::DefaultRegistry registry;
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{0});
ASSERT_NO_THROW(registry.reserve(42));
ASSERT_TRUE(registry.empty());
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{0});
@@ -17,6 +19,9 @@ TEST(DefaultRegistry, Functionalities) {
auto e1 = registry.create();
auto e2 = registry.create<int, char>();
ASSERT_TRUE(registry.has<>(e1));
ASSERT_TRUE(registry.has<>(e2));
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{2});
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{1});
ASSERT_EQ(registry.size<char>(), entt::DefaultRegistry::size_type{1});
@@ -53,6 +58,10 @@ TEST(DefaultRegistry, Functionalities) {
ASSERT_TRUE(registry.has<char>(e3));
ASSERT_EQ(registry.get<int>(e1), 42);
ASSERT_EQ(registry.get<char>(e1), 'c');
ASSERT_EQ(std::get<0>(registry.get<int, char>(e1)), 42);
ASSERT_EQ(std::get<1>(static_cast<const entt::DefaultRegistry &>(registry).get<int, 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(&registry.get<int>(e1), &registry.get<int>(e3));
@@ -121,6 +130,61 @@ TEST(DefaultRegistry, Functionalities) {
ASSERT_TRUE(registry.empty<int>());
}
TEST(DefaultRegistry, Each) {
entt::DefaultRegistry registry;
entt::DefaultRegistry::size_type tot;
entt::DefaultRegistry::size_type match;
registry.create<int>();
registry.create<int>();
tot = 0u;
match = 0u;
registry.each([&](auto entity) {
if(registry.has<int>(entity)) { ++match; }
registry.create();
++tot;
});
ASSERT_EQ(tot, 2u);
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, 4u);
ASSERT_EQ(match, 2u);
tot = 0u;
match = 0u;
registry.each([&](auto entity) {
if(registry.has<int>(entity)) { ++match; }
++tot;
});
ASSERT_EQ(tot, 4u);
ASSERT_EQ(match, 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;
@@ -287,3 +351,11 @@ 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);
}

View File

@@ -4,6 +4,7 @@
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());
@@ -79,6 +80,7 @@ TEST(SparseSetWithType, AggregatesMustWork) {
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());
@@ -159,8 +161,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);
@@ -195,8 +197,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);
@@ -231,8 +233,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);

View File

@@ -90,10 +90,13 @@ TEST(View, MultipleComponent) {
view.get<char>(e1) = '1';
view.get<char>(e2) = '2';
view.get<int>(e2) = 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>(e1);
@@ -161,10 +164,13 @@ TEST(PersistentView, Prepare) {
view.get<char>(e1) = '1';
view.get<char>(e2) = '2';
view.get<int>(e2) = 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);
@@ -199,10 +205,13 @@ TEST(PersistentView, NoPrepare) {
view.get<char>(e1) = '1';
view.get<char>(e2) = '2';
view.get<int>(e2) = 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);

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -77,4 +77,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());
}

426
test/mod/mod.cpp Normal file
View File

@@ -0,0 +1,426 @@
#include <gtest/gtest.h>
#include <cassert>
#include <map>
#include <string>
#include <entt/entity/registry.hpp>
#include "duktape.h"
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 &registry) {
const auto entity = duk_require_uint(ctx, 0);
registry.accomodate<Comp>(entity);
return 0;
}
template<>
duk_ret_t set<Position>(duk_context *ctx, entt::DefaultRegistry &registry) {
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.accomodate<Position>(entity, x, y);
return 0;
}
template<>
duk_ret_t set<DuktapeRuntime>(duk_context *ctx, entt::DefaultRegistry &registry) {
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 &registry) {
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 &registry) {
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 &registry) {
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 &registry) {
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 &registry) {
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 &registry) {
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 &registry) {
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 &registry = 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 &registry)
: 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 &registry = 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 &registry;
};
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 &registry) {
auto exportType = [](auto *ctx, auto &registry, 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([&registry](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);
}