Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50069d3743 | ||
|
|
1e03f27f23 | ||
|
|
36bb55a9ce | ||
|
|
451e4050db | ||
|
|
367fd3e87f | ||
|
|
a67a2e12fd | ||
|
|
292978daf0 | ||
|
|
85a4a76a14 | ||
|
|
9d0ab7ed70 | ||
|
|
3d5b6a5e0b | ||
|
|
ab20372093 | ||
|
|
ab887f30e4 | ||
|
|
6cb6a8c25f | ||
|
|
9d1d2aca0a | ||
|
|
75cb2cd1f7 |
@@ -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
|
||||
)
|
||||
|
||||
76
README.md
76
README.md
@@ -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.
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
// Fowler–Noll–Vo hash function v. 1a - the good
|
||||
static constexpr std::uint64_t helper(std::uint64_t partial, const char *str) noexcept {
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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] ||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
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
95118
test/mod/duktape.c
Normal file
File diff suppressed because it is too large
Load Diff
1349
test/mod/duktape.h
Normal file
1349
test/mod/duktape.h
Normal file
File diff suppressed because it is too large
Load Diff
426
test/mod/mod.cpp
Normal file
426
test/mod/mod.cpp
Normal file
@@ -0,0 +1,426 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <cassert>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <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 ®istry) {
|
||||
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 ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
const auto x = duk_require_number(ctx, 2);
|
||||
const auto y = duk_require_number(ctx, 3);
|
||||
registry.accomodate<Position>(entity, x, y);
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<>
|
||||
duk_ret_t set<DuktapeRuntime>(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
const auto type = duk_require_uint(ctx, 1);
|
||||
|
||||
duk_dup(ctx, 2);
|
||||
|
||||
if(!registry.has<DuktapeRuntime>(entity)) {
|
||||
registry.assign<DuktapeRuntime>(entity).components[type] = duk_json_encode(ctx, -1);
|
||||
} else {
|
||||
registry.get<DuktapeRuntime>(entity).components[type] = duk_json_encode(ctx, -1);
|
||||
}
|
||||
|
||||
duk_pop(ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
duk_ret_t unset(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
registry.remove<Comp>(entity);
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<>
|
||||
duk_ret_t unset<DuktapeRuntime>(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
const auto type = duk_require_uint(ctx, 1);
|
||||
|
||||
auto &components = registry.get<DuktapeRuntime>(entity).components;
|
||||
assert(components.find(type) != components.cend());
|
||||
components.erase(type);
|
||||
|
||||
if(components.empty()) {
|
||||
registry.remove<DuktapeRuntime>(entity);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
duk_ret_t has(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
duk_push_boolean(ctx, registry.has<Comp>(entity));
|
||||
return 1;
|
||||
}
|
||||
|
||||
template<>
|
||||
duk_ret_t has<DuktapeRuntime>(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
duk_push_boolean(ctx, registry.has<DuktapeRuntime>(entity));
|
||||
|
||||
if(registry.has<DuktapeRuntime>(entity)) {
|
||||
const auto type = duk_require_uint(ctx, 1);
|
||||
const auto &components = registry.get<DuktapeRuntime>(entity).components;
|
||||
duk_push_boolean(ctx, components.find(type) != components.cend());
|
||||
} else {
|
||||
duk_push_false(ctx);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
duk_ret_t get(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
assert(registry.has<Comp>(duk_require_uint(ctx, 0)));
|
||||
duk_push_object(ctx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
template<>
|
||||
duk_ret_t get<Position>(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
const auto &position = registry.get<Position>(entity);
|
||||
|
||||
const auto idx = duk_push_object(ctx);
|
||||
|
||||
duk_push_string(ctx, "x");
|
||||
duk_push_number(ctx, position.x);
|
||||
duk_def_prop(ctx, idx, DUK_DEFPROP_HAVE_VALUE);
|
||||
|
||||
duk_push_string(ctx, "y");
|
||||
duk_push_number(ctx, position.y);
|
||||
duk_def_prop(ctx, idx, DUK_DEFPROP_HAVE_VALUE);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
template<>
|
||||
duk_ret_t get<DuktapeRuntime>(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
const auto type = duk_require_uint(ctx, 1);
|
||||
|
||||
auto &runtime = registry.get<DuktapeRuntime>(entity);
|
||||
assert(runtime.components.find(type) != runtime.components.cend());
|
||||
|
||||
duk_push_string(ctx, runtime.components[type].c_str());
|
||||
duk_json_decode(ctx, -1);
|
||||
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
class DuktapeRegistry {
|
||||
// I'm pretty sure I won't have more than 99 components in the example
|
||||
static constexpr entt::DefaultRegistry::component_type udef = 100;
|
||||
|
||||
struct Func {
|
||||
using func_type = duk_ret_t(*)(duk_context *, entt::DefaultRegistry &);
|
||||
using test_type = bool(entt::DefaultRegistry:: *)(entt::DefaultRegistry::entity_type) const;
|
||||
|
||||
func_type set;
|
||||
func_type unset;
|
||||
func_type has;
|
||||
func_type get;
|
||||
test_type test;
|
||||
};
|
||||
|
||||
template<typename... Comp>
|
||||
void reg() {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type acc = { (func[registry.component<Comp>()] = {
|
||||
&::set<Comp>,
|
||||
&::unset<Comp>,
|
||||
&::has<Comp>,
|
||||
&::get<Comp>,
|
||||
&entt::DefaultRegistry::has<Comp>
|
||||
}, 0)... };
|
||||
(void)acc;
|
||||
}
|
||||
|
||||
static DuktapeRegistry & instance(duk_context *ctx) {
|
||||
duk_push_this(ctx);
|
||||
|
||||
duk_push_string(ctx, DUK_HIDDEN_SYMBOL("dreg"));
|
||||
duk_get_prop(ctx, -2);
|
||||
auto &dreg = *static_cast<DuktapeRegistry *>(duk_require_pointer(ctx, -1));
|
||||
duk_pop_2(ctx);
|
||||
|
||||
return dreg;
|
||||
}
|
||||
|
||||
template<Func::func_type Func::*Op>
|
||||
static duk_ret_t invoke(duk_context *ctx) {
|
||||
auto &dreg = instance(ctx);
|
||||
auto &func = dreg.func;
|
||||
auto ®istry = dreg.registry;
|
||||
auto type = duk_require_uint(ctx, 1);
|
||||
|
||||
if(type >= udef) {
|
||||
type = registry.component<DuktapeRuntime>();
|
||||
}
|
||||
|
||||
assert(func.find(type) != func.cend());
|
||||
|
||||
return (func[type].*Op)(ctx, registry);
|
||||
}
|
||||
|
||||
public:
|
||||
DuktapeRegistry(entt::DefaultRegistry ®istry)
|
||||
: registry{registry}
|
||||
{
|
||||
reg<Position, Renderable, DuktapeRuntime>();
|
||||
}
|
||||
|
||||
static duk_ret_t identifier(duk_context *ctx) {
|
||||
static auto next = udef;
|
||||
duk_push_uint(ctx, next++);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static duk_ret_t create(duk_context *ctx) {
|
||||
auto &dreg = instance(ctx);
|
||||
duk_push_uint(ctx, dreg.registry.create());
|
||||
return 1;
|
||||
}
|
||||
|
||||
static duk_ret_t set(duk_context *ctx) {
|
||||
return invoke<&Func::set>(ctx);
|
||||
}
|
||||
|
||||
static duk_ret_t unset(duk_context *ctx) {
|
||||
return invoke<&Func::unset>(ctx);
|
||||
}
|
||||
|
||||
static duk_ret_t has(duk_context *ctx) {
|
||||
return invoke<&Func::has>(ctx);
|
||||
}
|
||||
|
||||
static duk_ret_t get(duk_context *ctx) {
|
||||
return invoke<&Func::get>(ctx);
|
||||
}
|
||||
|
||||
static duk_ret_t entities(duk_context *ctx) {
|
||||
const duk_idx_t nargs = duk_get_top(ctx);
|
||||
auto &dreg = instance(ctx);
|
||||
duk_uarridx_t pos = 0;
|
||||
|
||||
duk_push_array(ctx);
|
||||
|
||||
dreg.registry.each([ctx, nargs, &pos, &dreg](auto entity) {
|
||||
auto ®istry = dreg.registry;
|
||||
auto &func = dreg.func;
|
||||
bool match = true;
|
||||
|
||||
for (duk_idx_t arg = 0; match && arg < nargs; arg++) {
|
||||
auto type = duk_require_uint(ctx, arg);
|
||||
|
||||
if(type < udef) {
|
||||
assert(func.find(type) != func.cend());
|
||||
match = (registry.*func[type].test)(entity);
|
||||
} else {
|
||||
const auto ctype = registry.component<DuktapeRuntime>();
|
||||
assert(func.find(ctype) != func.cend());
|
||||
match = (registry.*func[ctype].test)(entity);
|
||||
|
||||
if(match) {
|
||||
auto &components = registry.get<DuktapeRuntime>(entity).components;
|
||||
match = (components.find(type) != components.cend());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(match) {
|
||||
duk_push_uint(ctx, entity);
|
||||
duk_put_prop_index(ctx, -2, pos++);
|
||||
}
|
||||
});
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<duk_uint_t, Func> func;
|
||||
entt::DefaultRegistry ®istry;
|
||||
};
|
||||
|
||||
const duk_function_list_entry js_DuktapeRegistry_methods[] = {
|
||||
{ "identifier", &DuktapeRegistry::identifier, 0 },
|
||||
{ "create", &DuktapeRegistry::create, 0 },
|
||||
{ "set", &DuktapeRegistry::set, DUK_VARARGS },
|
||||
{ "unset", &DuktapeRegistry::unset, 2 },
|
||||
{ "has", &DuktapeRegistry::has, 2 },
|
||||
{ "get", &DuktapeRegistry::get, 2 },
|
||||
{ "entities", &DuktapeRegistry::entities, DUK_VARARGS },
|
||||
{ nullptr, nullptr, 0 }
|
||||
};
|
||||
|
||||
void exportTypes(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
auto exportType = [](auto *ctx, auto ®istry, auto idx, auto type, const auto *name) {
|
||||
duk_push_string(ctx, name);
|
||||
duk_push_uint(ctx, registry.template component<typename decltype(type)::type>());
|
||||
duk_def_prop(ctx, idx, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_CLEAR_WRITABLE);
|
||||
};
|
||||
|
||||
auto idx = duk_push_object(ctx);
|
||||
|
||||
exportType(ctx, registry, idx, tag<Position>{}, "POSITION");
|
||||
exportType(ctx, registry, idx, tag<Renderable>{}, "RENDERABLE");
|
||||
|
||||
duk_put_global_string(ctx, "Types");
|
||||
}
|
||||
|
||||
void exportDuktapeRegistry(duk_context *ctx, DuktapeRegistry &dreg) {
|
||||
auto idx = duk_push_object(ctx);
|
||||
|
||||
duk_push_string(ctx, DUK_HIDDEN_SYMBOL("dreg"));
|
||||
duk_push_pointer(ctx, &dreg);
|
||||
duk_put_prop(ctx, idx);
|
||||
|
||||
duk_put_function_list(ctx, idx, js_DuktapeRegistry_methods);
|
||||
duk_put_global_string(ctx, "Registry");
|
||||
}
|
||||
|
||||
TEST(Mod, Duktape) {
|
||||
entt::DefaultRegistry registry;
|
||||
DuktapeRegistry dreg{registry};
|
||||
duk_context *ctx = duk_create_heap_default();
|
||||
|
||||
if(!ctx) {
|
||||
FAIL();
|
||||
}
|
||||
|
||||
exportTypes(ctx, registry);
|
||||
exportDuktapeRegistry(ctx, dreg);
|
||||
|
||||
const char *s0 = ""
|
||||
"Types[\"PLAYING_CHARACTER\"] = Registry.identifier();"
|
||||
"Types[\"VELOCITY\"] = Registry.identifier();"
|
||||
"";
|
||||
|
||||
if(duk_peval_string(ctx, s0)) {
|
||||
FAIL();
|
||||
}
|
||||
|
||||
registry.create(Position{ 0., 0. }, Renderable{});
|
||||
registry.create(Position{ 0., 0. });
|
||||
|
||||
const char *s1 = ""
|
||||
"Registry.entities(Types.POSITION, Types.RENDERABLE).forEach(function(entity) {"
|
||||
"Registry.set(entity, Types.POSITION, 100., 100.);"
|
||||
"});"
|
||||
"var entity = Registry.create();"
|
||||
"Registry.set(entity, Types.POSITION, 100., 100.);"
|
||||
"Registry.set(entity, Types.RENDERABLE);"
|
||||
"";
|
||||
|
||||
if(duk_peval_string(ctx, s1)) {
|
||||
FAIL();
|
||||
}
|
||||
|
||||
ASSERT_EQ(registry.view<DuktapeRuntime>().size(), 0u);
|
||||
ASSERT_EQ(registry.view<Position>().size(), 3u);
|
||||
ASSERT_EQ(registry.view<Renderable>().size(), 2u);
|
||||
|
||||
registry.view<Position>().each([®istry](auto entity, const auto &position) {
|
||||
ASSERT_FALSE(registry.has<DuktapeRuntime>(entity));
|
||||
|
||||
if(registry.has<Renderable>(entity)) {
|
||||
ASSERT_EQ(position.x, 100.);
|
||||
ASSERT_EQ(position.y, 100.);
|
||||
} else {
|
||||
ASSERT_EQ(position.x, 0.);
|
||||
ASSERT_EQ(position.y, 0.);
|
||||
}
|
||||
});
|
||||
|
||||
const char *s2 = ""
|
||||
"Registry.entities(Types.POSITION).forEach(function(entity) {"
|
||||
"if(!Registry.has(entity, Types.RENDERABLE)) {"
|
||||
"Registry.set(entity, Types.VELOCITY, { \"dx\": -100., \"dy\": -100. });"
|
||||
"Registry.set(entity, Types.PLAYING_CHARACTER, {});"
|
||||
"}"
|
||||
"});"
|
||||
"";
|
||||
|
||||
if(duk_peval_string(ctx, s2)) {
|
||||
FAIL();
|
||||
}
|
||||
|
||||
ASSERT_EQ(registry.view<DuktapeRuntime>().size(), 1u);
|
||||
ASSERT_EQ(registry.view<Position>().size(), 3u);
|
||||
ASSERT_EQ(registry.view<Renderable>().size(), 2u);
|
||||
|
||||
registry.view<DuktapeRuntime>().each([](auto, const DuktapeRuntime &runtime) {
|
||||
ASSERT_EQ(runtime.components.size(), 2u);
|
||||
});
|
||||
|
||||
const char *s3 = ""
|
||||
"Registry.entities(Types.POSITION, Types.RENDERABLE, Types.VELOCITY, Types.PLAYING_CHARACTER).forEach(function(entity) {"
|
||||
"var velocity = Registry.get(entity, Types.VELOCITY);"
|
||||
"Registry.set(entity, Types.POSITION, velocity.dx, velocity.dy)"
|
||||
"});"
|
||||
"";
|
||||
|
||||
if(duk_peval_string(ctx, s3)) {
|
||||
FAIL();
|
||||
}
|
||||
|
||||
ASSERT_EQ(registry.view<DuktapeRuntime>().size(), 1u);
|
||||
ASSERT_EQ(registry.view<Position>().size(), 3u);
|
||||
ASSERT_EQ(registry.view<Renderable>().size(), 2u);
|
||||
|
||||
registry.view<Position, Renderable, DuktapeRuntime>().each([](auto, const Position &position, const auto &...) {
|
||||
ASSERT_EQ(position.x, -100.);
|
||||
ASSERT_EQ(position.y, -100.);
|
||||
});
|
||||
|
||||
const char *s4 = ""
|
||||
"Registry.entities(Types.VELOCITY, Types.PLAYING_CHARACTER).forEach(function(entity) {"
|
||||
"Registry.unset(entity, Types.VELOCITY);"
|
||||
"Registry.unset(entity, Types.PLAYING_CHARACTER);"
|
||||
"});"
|
||||
"Registry.entities(Types.POSITION).forEach(function(entity) {"
|
||||
"Registry.unset(entity, Types.POSITION);"
|
||||
"});"
|
||||
"";
|
||||
|
||||
if(duk_peval_string(ctx, s4)) {
|
||||
FAIL();
|
||||
}
|
||||
|
||||
ASSERT_EQ(registry.view<DuktapeRuntime>().size(), 0u);
|
||||
ASSERT_EQ(registry.view<Position>().size(), 0u);
|
||||
ASSERT_EQ(registry.view<Renderable>().size(), 2u);
|
||||
|
||||
duk_destroy_heap(ctx);
|
||||
}
|
||||
Reference in New Issue
Block a user