Compare commits

..

15 Commits

Author SHA1 Message Date
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
20 changed files with 101061 additions and 269 deletions

View File

@@ -16,7 +16,7 @@ endif()
# Project configuration
#
project(entt VERSION 2.2.0)
project(entt VERSION 2.3.0)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
@@ -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

@@ -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/>
@@ -397,8 +397,8 @@ 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.);
@@ -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
@@ -538,6 +538,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
@@ -799,7 +849,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.

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

@@ -24,8 +24,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

@@ -6,6 +6,7 @@
#include <memory>
#include <utility>
#include <cstddef>
#include <cstdint>
#include <cassert>
#include <algorithm>
#include "../core/family.hpp"
@@ -106,7 +107,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 +161,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 +179,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.
@@ -226,7 +264,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 +297,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 +409,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 +455,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 +597,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;
}
@@ -683,22 +723,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 +830,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 +852,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,10 +3,12 @@
#include <algorithm>
#include <numeric>
#include <utility>
#include <vector>
#include <cstddef>
#include <cassert>
#include <type_traits>
#include "traits.hpp"
@@ -87,7 +89,7 @@ class SparseSet<Entity> {
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. */
@@ -195,7 +197,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 +236,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 +287,18 @@ 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());
std::swap(reverse[direct[lhs]], reverse[direct[rhs]]);
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 +316,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;
}
}
/**
@@ -546,24 +511,94 @@ public:
}
/**
* @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 i = 0; i < copy.size(); ++i) {
auto curr = i;
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,7 @@
#include <tuple>
#include <utility>
#include <algorithm>
#include "sparse_set.hpp"
@@ -187,28 +188,6 @@ public:
return const_cast<Comp &>(const_cast<const PersistentView *>(this)->get<Comp>(entity));
}
/**
* @brief Iterate the entities and applies them the given function object.
*
* The function object is invoked for each entity. It is provided with the
* entity itself and a set of references to all the components of the
* view.<br/>
* The signature of the function should be equivalent to the following:
*
* @code{.cpp}
* void(entity_type, Component &...);
* @endcode
*
* @tparam Func Type of the function object to invoke.
* @param func A valid function object.
*/
template<typename Func>
void each(Func &&func) {
for(auto entity: *this) {
std::forward<Func>(func)(entity, get<Component>(entity)...);
}
}
/**
* @brief Iterate the entities and applies them the given function object.
*
@@ -225,12 +204,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 +295,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 +368,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 +426,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 +448,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>
Component & get(entity_type entity) noexcept {
return const_cast<Component &>(const_cast<const View *>(this)->get<Component>(entity));
}
/**
* @brief Iterate the entities and applies them the given function object.
*
* The function object is invoked for each entity. It is provided with the
* entity itself and a set of references to all the components of the
* view.<br/>
* The signature of the function should be equivalent to the following:
*
* @code{.cpp}
* void(entity_type, Component &...);
* @endcode
*
* @tparam Func Type of the function object to invoke.
* @param func A valid function object.
*/
template<typename Func>
void each(Func &&func) {
for(auto entity: *this) {
std::forward<Func>(func)(entity, get<First>(entity), get<Other>(entity)...);
}
template<typename Comp>
Comp & get(entity_type entity) noexcept {
return const_cast<Comp &>(const_cast<const View *>(this)->get<Comp>(entity));
}
/**
@@ -494,12 +473,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 +512,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 +714,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 +729,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

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

@@ -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,7 +178,7 @@ 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) {

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

@@ -2,22 +2,33 @@
# 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)
add_executable(
mod
$<TARGET_OBJECTS:odr>
mod/duktape.c
mod/mod.cpp
)
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

@@ -17,6 +17,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});
@@ -121,6 +124,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;

View File

@@ -159,8 +159,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 +195,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 +231,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

@@ -1,4 +1,5 @@
#include <gtest/gtest.h>
#include <cstdint>
#include <entt/process/process.hpp>
struct FakeProcess: entt::Process<FakeProcess, int> {
@@ -131,13 +132,13 @@ TEST(Process, AbortImmediately) {
TEST(ProcessAdaptor, Resolved) {
bool updated = false;
auto lambda = [&updated](uint64_t, auto resolve, auto) {
auto lambda = [&updated](std::uint64_t, 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,13 +148,13 @@ TEST(ProcessAdaptor, Resolved) {
TEST(ProcessAdaptor, Rejected) {
bool updated = false;
auto lambda = [&updated](uint64_t, auto, auto rejected) {
auto lambda = [&updated](std::uint64_t, 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);

3672
test/mod/duk_config.h Normal file

File diff suppressed because it is too large Load Diff

95118
test/mod/duktape.c Normal file

File diff suppressed because it is too large Load Diff

1349
test/mod/duktape.h Normal file

File diff suppressed because it is too large Load Diff

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