init
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# QtCreator
|
||||
*.user
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "deps/googletest"]
|
||||
path = deps/googletest
|
||||
url = https://github.com/google/googletest.git
|
||||
7
AUTHORS
Normal file
7
AUTHORS
Normal file
@@ -0,0 +1,7 @@
|
||||
# Author
|
||||
|
||||
Michele Caini aka skypjack
|
||||
|
||||
# Contributors
|
||||
|
||||
Paolo Monteverde aka morbo84
|
||||
116
CMakeLists.txt
Normal file
116
CMakeLists.txt
Normal file
@@ -0,0 +1,116 @@
|
||||
#
|
||||
# EnTT
|
||||
#
|
||||
|
||||
#
|
||||
# Building in-tree is not allowed (we take care of your craziness).
|
||||
#
|
||||
|
||||
if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR})
|
||||
message(FATAL_ERROR "Prevented in-tree built. Please create a build directory outside of the source code and call cmake from there. Thank you.")
|
||||
endif()
|
||||
|
||||
#
|
||||
# Project configuration
|
||||
#
|
||||
|
||||
project(entt)
|
||||
cmake_minimum_required(VERSION 3.4)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
endif(NOT CMAKE_BUILD_TYPE)
|
||||
|
||||
set(PROJECT_NAME "entt")
|
||||
set(PROJECT_VERSION_MAJOR 1)
|
||||
set(PROJECT_VERSION_MINOR 0)
|
||||
set(PROJECT_VERSION_PATCH 0)
|
||||
set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH})
|
||||
set(SETTINGS_ORGANIZATION "Michele Caini")
|
||||
set(SETTINGS_APPLICATION ${PROJECT_NAME})
|
||||
set(PROJECT_AUTHOR "Michele Caini")
|
||||
set(PROJECT_YEAR_COPYRIGHT "2017")
|
||||
set(PROJECT_AUTHOR_EMAIL "michele.caini@gmail.com")
|
||||
|
||||
set(PROJECT_BUILD_MESSAGE ${PROJECT_NAME} " v" ${PROJECT_VERSION} " (" ${CMAKE_BUILD_TYPE} ")")
|
||||
set(COPYRIGHT_BUILD_MESSAGE "Copyright (c) " ${PROJECT_YEAR_COPYRIGHT} " " ${PROJECT_AUTHOR} " <" ${PROJECT_AUTHOR_EMAIL} ">")
|
||||
|
||||
message("*")
|
||||
message("* " ${PROJECT_BUILD_MESSAGE})
|
||||
message("* " ${COPYRIGHT_BUILD_MESSAGE})
|
||||
message("*")
|
||||
|
||||
#
|
||||
# Compile stuff
|
||||
#
|
||||
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic -Wall")
|
||||
# set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-Wextra -Weffc++")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g -DDEBUG")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -DRELEASE")
|
||||
|
||||
add_definitions(
|
||||
-DPROJECT_NAME=${PROJECT_NAME}
|
||||
-DPROJECT_VERSION=${PROJECT_VERSION}
|
||||
)
|
||||
|
||||
#
|
||||
# CMake configuration
|
||||
#
|
||||
|
||||
set(PROJECT_CMAKE_MODULES cmake/modules)
|
||||
set(PROJECT_BUILD_DIR build)
|
||||
set(PROJECT_DEPS_DIR deps)
|
||||
set(PROJECT_SRC_DIR src)
|
||||
set(PROJECT_TEST_DIR test)
|
||||
|
||||
set(PROJECT_RUNTIME_OUTPUT_DIRECTORY bin)
|
||||
|
||||
set(
|
||||
CMAKE_MODULE_PATH
|
||||
${CMAKE_MODULE_PATH}
|
||||
${CMAKE_SOURCE_DIR}/${PROJECT_CMAKE_MODULES}
|
||||
)
|
||||
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_RUNTIME_OUTPUT_DIRECTORY})
|
||||
|
||||
#
|
||||
# Enable test support using ctest
|
||||
#
|
||||
|
||||
include(CTest)
|
||||
|
||||
#
|
||||
# Referenced packages
|
||||
#
|
||||
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(GoogleTest)
|
||||
|
||||
#
|
||||
# Referenced directories and targets
|
||||
#
|
||||
|
||||
if(${GOOGLETEST_FOUND})
|
||||
add_subdirectory(${PROJECT_TEST_DIR})
|
||||
endif(${GOOGLETEST_FOUND})
|
||||
|
||||
#
|
||||
# I use QtCreator and I need the lines below, so do not ask. :-)
|
||||
#
|
||||
|
||||
file(
|
||||
GLOB_RECURSE PROJECT_FILES FOLLOW_SYMLINKS
|
||||
*.txt *.c *.cpp *.hpp *.h *.in *.cmake *.sh *.md AUTHORS LICENSE
|
||||
)
|
||||
|
||||
add_custom_target(
|
||||
QTCREATOR_FALLBACK ALL
|
||||
COMMENT "Feel free to ignore this target, but please do not remove it."
|
||||
SOURCES ${PROJECT_FILES}
|
||||
)
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Michele Caini
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copy of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copy or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
284
README.md
Normal file
284
README.md
Normal file
@@ -0,0 +1,284 @@
|
||||
# EnTT - entity-component system in modern C++
|
||||
|
||||
# Introduction
|
||||
|
||||
`EnTT` is a header-only, tiny and easy to use entity-component system in modern C++.<br/>
|
||||
ECS is an architectural pattern used mostly in game development. For further details:
|
||||
|
||||
* [Entity Systems Wiki](http://entity-systems.wikidot.com/)
|
||||
* [Evolve Your Hierarchy](http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/)
|
||||
* [ECS on Wikipedia](https://en.wikipedia.org/wiki/Entity%E2%80%93component%E2%80%93system)
|
||||
|
||||
## Code Example
|
||||
|
||||
```
|
||||
#include <iostream>
|
||||
#include <registry.hpp>
|
||||
|
||||
struct Position {
|
||||
float x;
|
||||
float y;
|
||||
};
|
||||
|
||||
struct Velocity {
|
||||
float dx;
|
||||
float dy;
|
||||
};
|
||||
|
||||
using ECS = entt::DefaultRegistry<Position, Velocity>;
|
||||
|
||||
int main() {
|
||||
ECS ecs;
|
||||
|
||||
for(auto i = 0; i < 10; ++i) {
|
||||
auto entity = ecs.create();
|
||||
ecs.assign<Position>(entity, i * 1.f, i * 1.f);
|
||||
if(i % 2 == 0) { ecs.assign<Velocity>(entity, i * .1f, i * .1f); }
|
||||
}
|
||||
|
||||
std::cout << "single component view" << std::endl;
|
||||
|
||||
for(auto entity: ecs.view<Position>()) {
|
||||
auto &position = ecs.get<Position>(entity);
|
||||
std::cout << position.x << "," << position.y << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "multi component view" << std::endl;
|
||||
|
||||
for(auto entity: ecs.view<Position, Velocity>()) {
|
||||
auto &position = ecs.get<Position>(entity);
|
||||
auto &velocity = ecs.get<Velocity>(entity);
|
||||
std::cout << position.x << "," << position.y << " - " << velocity.dx << "," << velocity.dy << std::endl;
|
||||
if(entity % 4) { ecs.remove<Velocity>(entity); }
|
||||
else { ecs.destroy(entity); }
|
||||
}
|
||||
|
||||
std::cout << "single component view" << std::endl;
|
||||
|
||||
for(auto entity: ecs.view<Position>()) {
|
||||
auto &position = ecs.get<Position>(entity);
|
||||
std::cout << position.x << "," << position.y << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "multi component view" << std::endl;
|
||||
|
||||
for(auto entity: ecs.view<Position, Velocity>()) {
|
||||
auto &position = ecs.get<Position>(entity);
|
||||
auto &velocity = ecs.get<Velocity>(entity);
|
||||
std::cout << position.x << "," << position.y << " - " << velocity.dx << "," << velocity.dy << std::endl;
|
||||
if(entity % 4) { ecs.remove<Velocity>(entity); }
|
||||
else { ecs.destroy(entity); }
|
||||
}
|
||||
|
||||
ecs.reset();
|
||||
}
|
||||
```
|
||||
|
||||
## Motivation
|
||||
|
||||
I started using another well known Entity Component System named [entityx](https://github.com/alecthomas/entityx).<br/>
|
||||
While I was playing with it, I found that I didn't like that much the way it manages its memory.
|
||||
Moreover, I was pretty sure that one could achieve better performance with a slightly modified pool under the hood.<br/>
|
||||
That's also the reason for which the interface is quite similar to the one of _entityx_, so that *EnTT* can be used as a
|
||||
drop-in replacement for it with a minimal effort.
|
||||
|
||||
### 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):
|
||||
|
||||
| Benchmark | EntityX (master) | EntityX (experimental/compile_time) | EnTT |
|
||||
|-----------|-------------|-------------|-------------|
|
||||
| Creating 10M entities | 0.281481 | 0.213988s | 0.00542235s |
|
||||
| Destroying 10M entities | 0.166156 | 0.0673857s | 0.0582367s |
|
||||
| Iterating over 10M entities, unpacking one component | 0.047039 | 0.0297941s | 9.3e-08s |
|
||||
| Iterating over 10M entities, unpacking two components | 0.0701693 | 0.0412988 | 0.0206747s |
|
||||
|
||||
See [benchmark.cpp](https://github.com/skypjack/entt/blob/master/test/benchmark.cpp) for further details.<br/>
|
||||
Of course, I'll try to get out of it more features and better performance anyway in the future, mainly for fun.<br/>
|
||||
If you want to contribute and have any suggestion, feel free to make a PR or open an issue to discuss them.
|
||||
|
||||
# Build Instructions
|
||||
|
||||
## Requirements
|
||||
|
||||
To be able to use `EnTT`, users must provide a full-featured compiler that supports at least C++14.<br/>
|
||||
CMake version 3.4 or later is mandatory to compile the tests, you don't have to install it otherwise.
|
||||
|
||||
## Library
|
||||
|
||||
`EnTT` is a header-only library.<br/>
|
||||
This means that including the `registry.hpp` header is enough to use it.<br/>
|
||||
It's a matter of adding the following line at the top of a file:
|
||||
|
||||
#include <registry.hpp>
|
||||
|
||||
Then pass the proper `-I` argument to the compiler to add the `src` directory to the include paths.<br/>
|
||||
|
||||
## Documentation
|
||||
|
||||
### API Reference
|
||||
|
||||
*EnTT* contains three main actors: the *registry*, the *view* and the *pool*.<br/>
|
||||
Unless you have specific requirements of memory management, the default registry (that used the pool provided with
|
||||
*EnTT*) should be good enough for any use. Customization is an option anyway, so that you can use your own pool as
|
||||
long as it offers the expected interface.
|
||||
|
||||
#### The Registry
|
||||
|
||||
There are two options to instantiate your own registry:
|
||||
|
||||
* By using the default one:
|
||||
|
||||
```
|
||||
auto registry = entt::DefaultRegistry<Components...>{args...};
|
||||
```
|
||||
|
||||
That is, you must provide the whole list of components to be registered with the default registry.
|
||||
|
||||
* By using your own pool:
|
||||
|
||||
```
|
||||
auto registry = entt::Registry<YourOwnPool<Components...>{args...};
|
||||
```
|
||||
|
||||
Note that the registry expects a class template where the template parameters are the components to be managed.
|
||||
|
||||
In both cases, `args...` parameters are forwarded to the underlying pool during the construction.<br/>
|
||||
There are no requirements for the components but to be moveable, therefore POD types are just fine.
|
||||
|
||||
Once you have created a registry, the followings are the exposed member functions:
|
||||
|
||||
* `size`: returns the number of entities still alive.
|
||||
* `capacity`: returns the maximum number of entities created till now.
|
||||
* `empty`: returns `true` if all the entities have been destroyed, `false` otherwise.
|
||||
* `create`: creates a new entity and returns it, no components assigned.
|
||||
* `create<Components...>`: creates a new entity and assigns it the given components, then returns the entity.
|
||||
* `destroy`: destroys the entity and all its components.
|
||||
* `assign<Component>(entity, args...)`: assigns the given component to the entity and uses `args...` to initialize it.
|
||||
* `remove<Component>(entity)`: removes the given component from the entity.
|
||||
* `has<Component>(entity)`: returns `true` if the entity has the given component, `false` otherwise.
|
||||
* `get<Component>(entity)`: returns a reference to the given component for the entity (undefined behaviour if the entity has not the component).
|
||||
* `replace<Component>(entity, args...)`: replaces the given component for the entity, using `args...` to create the new component.
|
||||
* `clone(entity)`: clones an entity and all its components, then returns the new entity identifier.
|
||||
* `copy<Component>(from, to)`: copies a component from an entity to another one (both the entities must already have been assigned the component, undefined behaviour otherwise).
|
||||
* `copy(from, to)`: copies all the components and their contents from an entity to another one (comoonents are created or destroyed if needed).
|
||||
* `reset()`: resets the pool and destroys all the entities and their components.
|
||||
* `view<Components...>()`: gets a view of the entities that have the given components (see below for further details).
|
||||
|
||||
Note that entities are numbers and nothing more. They are not classes and they have no memeber functions at all.
|
||||
|
||||
#### The View
|
||||
|
||||
There are two different kinds of view, each one with a slighlty different interface: the single component view and the multi component view.<br/>
|
||||
Both of them are iterable, that is both of them have `begin` and `end` member functions that are suitable for a range-based for loop:
|
||||
|
||||
```
|
||||
auto view = registry.view<Position, Velocity>();
|
||||
|
||||
for(auto entity: view) {
|
||||
// do whatever you want with your entities
|
||||
}
|
||||
```
|
||||
|
||||
Iterators are extremely poor, they are meant exclusively to be used to iterate over a set of entities.<br/>
|
||||
Exposed member functions are: `operator++()`, `operator++(int)`, `operator==()`, `operator!=()` and `operator*()`.
|
||||
|
||||
The single component view has also a member function named `size` that returns the exact number of expected entities.<br/>
|
||||
The multi component view has also a member function named `reset` that reorganizes internal data to create optimized iterators.
|
||||
Use it whenever the data within the registry are known to be changed.<br/>
|
||||
Both the views can be used more than once, for they return newly created and correctly initialized iterators whenever
|
||||
`begin` or `end` is invoked. Anyway views and iterators are tiny objects and the time to construct them can be safely ignored.
|
||||
I'd suggest not to store them anywhere and to invoke the `Registry::view` member function at each iteration to get a properly
|
||||
initialized view over which to iterate.
|
||||
|
||||
*Note*.<br/>
|
||||
An important feature (usually missed by other well known ECS) is that users can create and destroy entities, as
|
||||
well as assign or remove components while iterating and neither the views nor the iterators will be invalidated.<br/>
|
||||
Therefore, unless one tries to access a destroyed entity through an iterator that hasn't been advanced (in this case, of course,
|
||||
it's an undefined behaviour), users can freely interact with the registry and keep views and iterators consistent.<br/>
|
||||
On the other side, iterators aren't thread safe, thus do no try to concurrently iterate over and modify a set of components at
|
||||
the same time. That being said, as long as a thread iterates over the entities that have the component `X` or assign and removes
|
||||
that component from a set of entities and another thread does something similar with components `Y` and `Z`, it shouldn't be a
|
||||
problem at all.<br/>
|
||||
As an example, that means that users can freely run the render system over the renderable entities and update the physics
|
||||
concurrently on a separate thread if needed.
|
||||
|
||||
#### The Pool
|
||||
|
||||
Custom pools for a given component can be defined as a specialization of the class template `ComponentPool`.<br/>
|
||||
In particular:
|
||||
|
||||
```
|
||||
template<>
|
||||
struct ComponentPool<MyComponent> final {
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
A custom pool should expose at least the following member functions:
|
||||
|
||||
* `bool empty() const noexcept;`
|
||||
* `size_type capacity() const noexcept;`
|
||||
* `size_type size() const noexcept;`
|
||||
* `const entity_type * entities() const noexcept;`
|
||||
* `bool has(entity_type entity) const noexcept;`
|
||||
* `const component_type & get(entity_type entity) const noexcept;`
|
||||
* `component_type & get(entity_type entity) noexcept;`
|
||||
* template<typename... Args> component_type & construct(entity_type entity, Args&&... args);`
|
||||
* `void destroy(entity_type entity);`
|
||||
* `void reset();`
|
||||
|
||||
This is a fast and easy way to define a custom pool specialization for a given component (as an example, if the
|
||||
component `X` requires to be ordered internally somehow during construction or destruction operations) and to use the
|
||||
default pool for all the other components.<br/>
|
||||
It's a mattrer of including the given specialization along with the registry, so that it can find it during the instantiation.<br/>
|
||||
In this case, users are not required to use the more explicit `Registry` class. Instead, they can still use `entt::DefaultRegistry`.
|
||||
|
||||
In cases when the per-component pools are not good enough, the registry can be initialized with a custom pool.<br/>
|
||||
In other terms, `entt::Registry` has a template template parameter that can be used to provide both the pool and the list of
|
||||
components:
|
||||
|
||||
```
|
||||
auto registry = entt::Registry<MyCustomPool<Component1, Component2>>{};
|
||||
```
|
||||
|
||||
Even thoug the underlying pool doesn't store the components separately, the registry must know them to be able to do
|
||||
specific actions (like `destroy` or `copy`). That's why they must be explicitly specified.<br/>
|
||||
A generic pool should expose at least the following memeber functions:
|
||||
|
||||
* `template<typename Comp> bool empty() const noexcept;`
|
||||
* `template<typename Comp> size_type capacity() const noexcept;`
|
||||
* `template<typename Comp> size_type size() const noexcept;`
|
||||
* `template<typename Comp> const entity_type * entities() const noexcept;`
|
||||
* `template<typename Comp> bool has(entity_type entity) const noexcept;`
|
||||
* `template<typename Comp> const Comp & get(entity_type entity) const noexcept;`
|
||||
* `template<typename Comp> Comp & get(entity_type entity) noexcept;`
|
||||
* `template<typename Comp, typename... Args> Comp & construct(entity_type entity, Args&&... args);`
|
||||
* `template<typename Comp> void destroy(entity_type entity);`
|
||||
* `void reset();`
|
||||
|
||||
Good luck. If you come out with a more performant components pool, do not forget to make a PR so that I can add it to
|
||||
the list of available ones. I would be glad to have such a contribution to the project!!
|
||||
|
||||
## Tests
|
||||
|
||||
To compile and run the tests, `EnTT` requires *googletest*.<br/>
|
||||
Run the script `deps.sh` to download it. It is good practice to do it every time one pull the project.
|
||||
|
||||
Then, to build the tests:
|
||||
|
||||
* `$ cd build`
|
||||
* `$ cmake ..`
|
||||
* `$ make`
|
||||
* `$ make test`
|
||||
|
||||
# Contributors
|
||||
|
||||
If you want to contribute, please send patches as pull requests against the branch master.<br/>
|
||||
Check the [contributors list](https://github.com/skypjack/entt/blob/master/AUTHORS) to see who has partecipated so far.
|
||||
|
||||
# License
|
||||
|
||||
Code and documentation Copyright (c) 2017 Michele Caini.<br/>
|
||||
Code released under [the MIT license](https://github.com/skypjack/entt/blob/master/LICENSE).<br/>
|
||||
2
build/.gitignore
vendored
Normal file
2
build/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
96
cmake/modules/FindGoogleTest.cmake
Normal file
96
cmake/modules/FindGoogleTest.cmake
Normal file
@@ -0,0 +1,96 @@
|
||||
# FindGoogleTest
|
||||
# ---------
|
||||
#
|
||||
# Locate Google Test Framework
|
||||
#
|
||||
# This module defines:
|
||||
#
|
||||
# ::
|
||||
#
|
||||
# GOOGLETEST_INCLUDE_DIRS, where to find the headers
|
||||
# GOOGLETEST_LIBRARIES, the libraries against which to link
|
||||
# GOOGLETEST_FOUND, if false, do not try to use the above mentioned vars
|
||||
#
|
||||
|
||||
set(BUILD_DEPS_DIR ${CMAKE_SOURCE_DIR}/${PROJECT_DEPS_DIR})
|
||||
set(GOOGLETEST_DEPS_DIR googletest)
|
||||
|
||||
find_path(
|
||||
GOOGLETEST_INCLUDE_DIR NAMES gtest/gtest.h
|
||||
PATHS ${BUILD_DEPS_DIR}/${GOOGLETEST_DEPS_DIR}/googletest/include/
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
|
||||
find_path(
|
||||
GOOGLEMOCK_INCLUDE_DIR NAMES gmock/gmock.h
|
||||
PATHS ${BUILD_DEPS_DIR}/${GOOGLETEST_DEPS_DIR}/googlemock/include/
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
|
||||
find_library(
|
||||
GOOGLETEST_LIBRARY NAMES gtest
|
||||
PATHS ${BUILD_DEPS_DIR}/${GOOGLETEST_DEPS_DIR}/build/googlemock/gtest/
|
||||
PATH_SUFFIXES Release
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
|
||||
find_library(
|
||||
GOOGLETEST_MAIN_LIBRARY NAMES gtest_main
|
||||
PATHS ${BUILD_DEPS_DIR}/${GOOGLETEST_DEPS_DIR}/build/googlemock/gtest/
|
||||
PATH_SUFFIXES Release
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
|
||||
find_library(
|
||||
GOOGLEMOCK_LIBRARY NAMES gmock
|
||||
PATHS ${BUILD_DEPS_DIR}/${GOOGLETEST_DEPS_DIR}/build/googlemock/
|
||||
PATH_SUFFIXES Release
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
|
||||
find_library(
|
||||
GOOGLEMOCK_MAIN_LIBRARY NAMES gmock_main
|
||||
PATHS ${BUILD_DEPS_DIR}/${GOOGLETEST_DEPS_DIR}/build/googlemock/
|
||||
PATH_SUFFIXES Release
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
find_package_handle_standard_args(
|
||||
GOOGLETEST
|
||||
FOUND_VAR GOOGLETEST_FOUND
|
||||
REQUIRED_VARS
|
||||
GOOGLETEST_LIBRARY
|
||||
GOOGLETEST_MAIN_LIBRARY
|
||||
GOOGLEMOCK_LIBRARY
|
||||
GOOGLEMOCK_MAIN_LIBRARY
|
||||
GOOGLETEST_INCLUDE_DIR
|
||||
GOOGLEMOCK_INCLUDE_DIR
|
||||
)
|
||||
|
||||
if(GOOGLETEST_FOUND)
|
||||
set(
|
||||
GOOGLETEST_LIBRARIES
|
||||
${GOOGLETEST_LIBRARY}
|
||||
${GOOGLETEST_MAIN_LIBRARY}
|
||||
${GOOGLEMOCK_LIBRARY}
|
||||
${GOOGLEMOCK_MAIN_LIBRARY}
|
||||
)
|
||||
|
||||
set(
|
||||
GOOGLETEST_INCLUDE_DIRS
|
||||
${GOOGLETEST_INCLUDE_DIR}
|
||||
${GOOGLEMOCK_INCLUDE_DIR}
|
||||
)
|
||||
endif(GOOGLETEST_FOUND)
|
||||
|
||||
|
||||
mark_as_advanced(
|
||||
GOOGLETEST_INCLUDE_DIR
|
||||
GOOGLEMOCK_INCLUDE_DIR
|
||||
GOOGLETEST_LIBRARY
|
||||
GOOGLETEST_MAIN_LIBRARY
|
||||
GOOGLEMOCK_LIBRARY
|
||||
GOOGLEMOCK_MAIN_LIBRARY
|
||||
)
|
||||
41
deps.sh
Executable file
41
deps.sh
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#
|
||||
# google docet - jump within the directory which contains this script
|
||||
#
|
||||
|
||||
SOURCE="${BASH_SOURCE[0]}"
|
||||
|
||||
while [ -h "$SOURCE" ]; do
|
||||
DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
|
||||
SOURCE="$(readlink "$SOURCE")"
|
||||
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
|
||||
done
|
||||
|
||||
#
|
||||
# set aside the base dir for future references
|
||||
#
|
||||
|
||||
DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
|
||||
|
||||
#
|
||||
# init/update submodules
|
||||
#
|
||||
|
||||
cd "$DIR"
|
||||
git submodule update --init # --recursive
|
||||
|
||||
#
|
||||
# compile dependencies
|
||||
#
|
||||
|
||||
rm -rf "$DIR"/deps/googletest/build
|
||||
mkdir "$DIR"/deps/googletest/build
|
||||
cd "$DIR"/deps/googletest/build
|
||||
cmake .. && make -j4
|
||||
|
||||
#
|
||||
# go back home
|
||||
#
|
||||
|
||||
cd "$DIR"
|
||||
1
deps/googletest
vendored
Submodule
1
deps/googletest
vendored
Submodule
Submodule deps/googletest added at aa148eb2b7
194
src/component_pool.hpp
Normal file
194
src/component_pool.hpp
Normal file
@@ -0,0 +1,194 @@
|
||||
#ifndef ENTT_COMPONENT_POOL_HPP
|
||||
#define ENTT_COMPONENT_POOL_HPP
|
||||
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
#include <memory>
|
||||
#include <cstddef>
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
template<typename, typename...>
|
||||
struct ComponentPool;
|
||||
|
||||
|
||||
template<typename Component>
|
||||
struct ComponentPool<Component> final {
|
||||
using component_type = Component;
|
||||
using size_type = std::uint32_t;
|
||||
using entity_type = std::uint32_t;
|
||||
|
||||
private:
|
||||
bool valid(entity_type entity) const noexcept {
|
||||
return entity < reverse.size() && reverse[entity] < direct.size() && direct[reverse[entity]] == entity;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit ComponentPool(size_type dim = 4098) noexcept {
|
||||
assert(!(dim < 0));
|
||||
data.reserve(dim);
|
||||
}
|
||||
|
||||
ComponentPool(ComponentPool &&) = default;
|
||||
|
||||
~ComponentPool() noexcept {
|
||||
assert(empty());
|
||||
}
|
||||
|
||||
ComponentPool & operator=(ComponentPool &&) = default;
|
||||
|
||||
bool empty() const noexcept {
|
||||
return data.empty();
|
||||
}
|
||||
|
||||
size_type capacity() const noexcept {
|
||||
return data.capacity();
|
||||
}
|
||||
|
||||
size_type size() const noexcept {
|
||||
return data.size();
|
||||
}
|
||||
|
||||
const entity_type * entities() const noexcept {
|
||||
return direct.data();
|
||||
}
|
||||
|
||||
bool has(entity_type entity) const noexcept {
|
||||
return valid(entity);
|
||||
}
|
||||
|
||||
const component_type & get(entity_type entity) const noexcept {
|
||||
assert(valid(entity));
|
||||
return data[reverse[entity]];
|
||||
}
|
||||
|
||||
component_type & get(entity_type entity) noexcept {
|
||||
return const_cast<component_type &>(const_cast<const ComponentPool *>(this)->get(entity));
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
component_type & construct(entity_type entity, Args&&... args) {
|
||||
assert(!valid(entity));
|
||||
|
||||
if(!(entity < reverse.size())) {
|
||||
reverse.resize(entity+1);
|
||||
}
|
||||
|
||||
reverse[entity] = direct.size();
|
||||
direct.emplace_back(entity);
|
||||
data.push_back({ std::forward<Args>(args)... });
|
||||
|
||||
return data.back();
|
||||
}
|
||||
|
||||
void destroy(entity_type entity) {
|
||||
assert(valid(entity));
|
||||
|
||||
auto last = direct.size() - 1;
|
||||
|
||||
reverse[direct[last]] = reverse[entity];
|
||||
direct[reverse[entity]] = direct[last];
|
||||
data[reverse[entity]] = std::move(data[last]);
|
||||
|
||||
direct.pop_back();
|
||||
data.pop_back();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
data.clear();
|
||||
reverse.resize(0);
|
||||
direct.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<component_type> data;
|
||||
std::vector<size_type> reverse;
|
||||
std::vector<entity_type> direct;
|
||||
};
|
||||
|
||||
|
||||
template<typename Component, typename... Components>
|
||||
struct ComponentPool final {
|
||||
using size_type = typename ComponentPool<Component>::size_type;
|
||||
using entity_type = typename ComponentPool<Component>::entity_type;
|
||||
|
||||
explicit ComponentPool(size_type dim = 4098) noexcept
|
||||
: pools{ComponentPool<Component>{dim}, ComponentPool<Components>{dim}...}
|
||||
{
|
||||
assert(!(dim < 0));
|
||||
}
|
||||
|
||||
ComponentPool(const ComponentPool &) = delete;
|
||||
ComponentPool(ComponentPool &&) = delete;
|
||||
|
||||
ComponentPool & operator=(const ComponentPool &) = delete;
|
||||
ComponentPool & operator=(ComponentPool &&) = delete;
|
||||
|
||||
template<typename Comp>
|
||||
bool empty() const noexcept {
|
||||
return std::get<ComponentPool<Comp>>(pools).empty();
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
size_type capacity() const noexcept {
|
||||
return std::get<ComponentPool<Comp>>(pools).capacity();
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
size_type size() const noexcept {
|
||||
return std::get<ComponentPool<Comp>>(pools).size();
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
const entity_type * entities() const noexcept {
|
||||
return std::get<ComponentPool<Comp>>(pools).entities();
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
bool has(entity_type entity) const noexcept {
|
||||
return std::get<ComponentPool<Comp>>(pools).has(entity);
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
const Comp & get(entity_type entity) const noexcept {
|
||||
return std::get<ComponentPool<Comp>>(pools).get(entity);
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
Comp & get(entity_type entity) noexcept {
|
||||
return const_cast<Comp &>(const_cast<const ComponentPool *>(this)->get<Comp>(entity));
|
||||
}
|
||||
|
||||
template<typename Comp, typename... Args>
|
||||
Comp & construct(entity_type entity, Args&&... args) {
|
||||
return std::get<ComponentPool<Comp>>(pools).construct(entity, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
void destroy(entity_type entity) {
|
||||
std::get<ComponentPool<Comp>>(pools).destroy(entity);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
using accumulator_type = int[];
|
||||
std::get<ComponentPool<Component>>(pools).reset();
|
||||
accumulator_type accumulator = { (std::get<ComponentPool<Components>>(pools).reset(), 0)... };
|
||||
(void)accumulator;
|
||||
}
|
||||
|
||||
private:
|
||||
std::tuple<ComponentPool<Component>, ComponentPool<Components>...> pools;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_COMPONENT_POOL_HPP
|
||||
362
src/registry.hpp
Normal file
362
src/registry.hpp
Normal file
@@ -0,0 +1,362 @@
|
||||
#ifndef ENTT_REGISTRY_HPP
|
||||
#define ENTT_REGISTRY_HPP
|
||||
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
#include "component_pool.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
template<typename...>
|
||||
class View;
|
||||
|
||||
|
||||
template<template<typename...> class Pool, typename... Components, typename Type, typename... Types>
|
||||
class View<Pool<Components...>, Type, Types...> final {
|
||||
using pool_type = Pool<Components...>;
|
||||
using entity_type = typename pool_type::entity_type;
|
||||
|
||||
class ViewIterator {
|
||||
bool valid() const noexcept {
|
||||
using accumulator_type = bool[];
|
||||
bool check = pool.template has<Type>(entities[pos-1]);
|
||||
accumulator_type accumulator = { true, (check = check && pool.template has<Types>(entities[pos-1]))... };
|
||||
(void)accumulator;
|
||||
return check;
|
||||
}
|
||||
|
||||
public:
|
||||
using value_type = entity_type;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using reference = entity_type &;
|
||||
using pointer = entity_type *;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
|
||||
ViewIterator(pool_type &pool, const entity_type *entities, typename pool_type::size_type pos) noexcept
|
||||
: pool{pool}, entities{entities}, pos{pos}
|
||||
{
|
||||
if(pos) { while(!valid() && --pos); }
|
||||
}
|
||||
|
||||
ViewIterator & operator++() noexcept {
|
||||
if(pos) { while(--pos && !valid()); }
|
||||
return *this;
|
||||
}
|
||||
|
||||
ViewIterator operator++(int) noexcept {
|
||||
ViewIterator orig = *this;
|
||||
return this->operator++(), orig;
|
||||
}
|
||||
|
||||
bool operator==(const ViewIterator &other) const noexcept {
|
||||
return other.entities == entities && other.pos == pos;
|
||||
}
|
||||
|
||||
bool operator!=(const ViewIterator &other) const noexcept {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
value_type operator*() const noexcept {
|
||||
return *(entities+pos-1);
|
||||
}
|
||||
|
||||
private:
|
||||
pool_type &pool;
|
||||
const entity_type *entities;
|
||||
std::uint32_t pos;
|
||||
};
|
||||
|
||||
template<typename Comp>
|
||||
void prefer() noexcept {
|
||||
auto sz = pool.template size<Comp>();
|
||||
|
||||
if(sz < size) {
|
||||
entities = pool.template entities<Comp>();
|
||||
size = sz;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
using iterator_type = ViewIterator;
|
||||
using size_type = typename pool_type::size_type;
|
||||
|
||||
explicit View(pool_type &pool) noexcept
|
||||
: entities{pool.template entities<Type>()},
|
||||
size{pool.template size<Type>()},
|
||||
pool{pool}
|
||||
{
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (prefer<Types>(), 0)... };
|
||||
(void)accumulator;
|
||||
}
|
||||
|
||||
iterator_type begin() const noexcept {
|
||||
return ViewIterator{pool, entities, size};
|
||||
}
|
||||
|
||||
iterator_type end() const noexcept {
|
||||
return ViewIterator{pool, entities, 0};
|
||||
}
|
||||
|
||||
void reset() noexcept {
|
||||
using accumulator_type = int[];
|
||||
entities = pool.template entities<Type>();
|
||||
size = pool.template size<Type>();
|
||||
accumulator_type accumulator = { 0, (prefer<Types>(), 0)... };
|
||||
(void)accumulator;
|
||||
}
|
||||
|
||||
private:
|
||||
const entity_type *entities;
|
||||
size_type size;
|
||||
pool_type &pool;
|
||||
};
|
||||
|
||||
|
||||
template<template<typename...> class Pool, typename... Components, typename Type>
|
||||
class View<Pool<Components...>, Type> final {
|
||||
using pool_type = Pool<Components...>;
|
||||
using entity_type = typename pool_type::entity_type;
|
||||
|
||||
struct ViewIterator {
|
||||
using value_type = entity_type;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using reference = entity_type &;
|
||||
using pointer = entity_type *;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
|
||||
ViewIterator(const entity_type *entities, typename pool_type::size_type pos) noexcept
|
||||
: entities{entities}, pos{pos}
|
||||
{}
|
||||
|
||||
ViewIterator & operator++() noexcept {
|
||||
--pos;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ViewIterator operator++(int) noexcept {
|
||||
ViewIterator orig = *this;
|
||||
return this->operator++(), orig;
|
||||
}
|
||||
|
||||
bool operator==(const ViewIterator &other) const noexcept {
|
||||
return other.entities == entities && other.pos == pos;
|
||||
}
|
||||
|
||||
bool operator!=(const ViewIterator &other) const noexcept {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
value_type operator*() const noexcept {
|
||||
return *(entities+pos-1);
|
||||
}
|
||||
|
||||
private:
|
||||
const entity_type *entities;
|
||||
typename pool_type::size_type pos;
|
||||
};
|
||||
|
||||
public:
|
||||
using iterator_type = ViewIterator;
|
||||
using size_type = typename pool_type::size_type;
|
||||
|
||||
explicit View(pool_type &pool) noexcept: pool{pool} {}
|
||||
|
||||
iterator_type begin() const noexcept {
|
||||
return ViewIterator{pool.template entities<Type>(), pool.template size<Type>()};
|
||||
}
|
||||
|
||||
iterator_type end() const noexcept {
|
||||
return ViewIterator{pool.template entities<Type>(), 0};
|
||||
}
|
||||
|
||||
size_type size() const noexcept {
|
||||
return pool.template size<Type>();
|
||||
}
|
||||
|
||||
private:
|
||||
pool_type &pool;
|
||||
};
|
||||
|
||||
|
||||
template<typename>
|
||||
struct Registry;
|
||||
|
||||
|
||||
template<template<typename...> class Pool, typename... Components>
|
||||
struct Registry<Pool<Components...>> final {
|
||||
static_assert(sizeof...(Components) > 1, "!");
|
||||
|
||||
using entity_type = typename Pool<Components...>::entity_type;
|
||||
using size_type = std::size_t;
|
||||
|
||||
private:
|
||||
using pool_type = Pool<Components...>;
|
||||
|
||||
template<typename Comp>
|
||||
void destroy(entity_type entity) {
|
||||
if(pool.template has<Comp>(entity)) {
|
||||
pool.template destroy<Comp>(entity);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
void clone(entity_type from, entity_type to) {
|
||||
if(pool.template has<Comp>(from)) {
|
||||
pool.template construct<Comp>(to, pool.template get<Comp>(from));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
void sync(entity_type from, entity_type to) {
|
||||
bool src = pool.template has<Comp>(from);
|
||||
bool dst = pool.template has<Comp>(to);
|
||||
|
||||
if(src && dst) {
|
||||
copy<Comp>(from, to);
|
||||
} else if(src) {
|
||||
clone<Comp>(from, to);
|
||||
} else if(dst) {
|
||||
destroy(to);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
template<typename... Comp>
|
||||
using view_type = View<pool_type, Comp...>;
|
||||
|
||||
template<typename... Args>
|
||||
Registry(Args&&... args)
|
||||
: count{0}, pool{std::forward<Args>(args)...}
|
||||
{}
|
||||
|
||||
Registry(const Registry &) = delete;
|
||||
Registry(Registry &&) = delete;
|
||||
|
||||
Registry & operator=(const Registry &) = delete;
|
||||
Registry & operator=(Registry &&) = delete;
|
||||
|
||||
size_type size() const noexcept {
|
||||
return count - available.size();
|
||||
}
|
||||
|
||||
size_type capacity() const noexcept {
|
||||
return count;
|
||||
}
|
||||
|
||||
bool empty() const noexcept {
|
||||
return available.size() == count;
|
||||
}
|
||||
|
||||
entity_type create() noexcept {
|
||||
entity_type entity;
|
||||
|
||||
if(available.empty()) {
|
||||
entity = count++;
|
||||
} else {
|
||||
entity = available.back();
|
||||
available.pop_back();
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
template<typename... Comp>
|
||||
entity_type create() noexcept {
|
||||
using accumulator_type = int[];
|
||||
auto entity = create();
|
||||
accumulator_type accumulator = { 0, (assign<Comp>(entity), 0)... };
|
||||
(void)accumulator;
|
||||
return entity;
|
||||
}
|
||||
|
||||
void destroy(entity_type entity) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (destroy<Components>(entity), 0)... };
|
||||
(void)accumulator;
|
||||
available.push_back(entity);
|
||||
}
|
||||
|
||||
template<typename Comp, typename... Args>
|
||||
Comp & assign(entity_type entity, Args&&... args) {
|
||||
return pool.template construct<Comp>(entity, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
void remove(entity_type entity) {
|
||||
pool.template destroy<Comp>(entity);
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
bool has(entity_type entity) const noexcept {
|
||||
return pool.template has<Comp>(entity);
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
const Comp & get(entity_type entity) const noexcept {
|
||||
return pool.template get<Comp>(entity);
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
Comp & get(entity_type entity) noexcept {
|
||||
return pool.template get<Comp>(entity);
|
||||
}
|
||||
|
||||
template<typename Comp, typename... Args>
|
||||
void replace(entity_type entity, Args&&... args) {
|
||||
pool.template get<Comp>(entity) = Comp{std::forward<Args>(args)...};
|
||||
}
|
||||
|
||||
entity_type clone(entity_type from) {
|
||||
auto to = create();
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (clone<Components>(from, to), 0)... };
|
||||
(void)accumulator;
|
||||
return to;
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
void copy(entity_type from, entity_type to) {
|
||||
pool.template get<Comp>(to) = pool.template get<Comp>(from);
|
||||
}
|
||||
|
||||
void copy(entity_type from, entity_type to) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (sync<Components>(from, to), 0)... };
|
||||
(void)accumulator;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
available.clear();
|
||||
count = 0;
|
||||
pool.reset();
|
||||
}
|
||||
|
||||
template<typename... Comp>
|
||||
view_type<Comp...> view() {
|
||||
return view_type<Comp...>{pool};
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<entity_type> available;
|
||||
entity_type count;
|
||||
pool_type pool;
|
||||
};
|
||||
|
||||
|
||||
template<typename... Components>
|
||||
using DefaultRegistry = Registry<ComponentPool<Components...>>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_REGISTRY_HPP
|
||||
31
test/CMakeLists.txt
Normal file
31
test/CMakeLists.txt
Normal file
@@ -0,0 +1,31 @@
|
||||
#
|
||||
# Tests configuration
|
||||
#
|
||||
|
||||
set(ENTT_SRC_DIR ${CMAKE_SOURCE_DIR}/${PROJECT_SRC_DIR})
|
||||
|
||||
set(COMMON_INCLUDE_DIRS ${ENTT_SRC_DIR} ${GOOGLETEST_INCLUDE_DIRS})
|
||||
set(COMMON_LINK_LIBS ${GOOGLETEST_LIBRARIES} Threads::Threads)
|
||||
|
||||
# List of available targets
|
||||
|
||||
set(TARGET_ENTT entt)
|
||||
set(TARGET_BENCHMARK benchmark)
|
||||
|
||||
# Test TARGET_ENTT
|
||||
|
||||
set(TARGET_ENTT_SOURCES component_pool.cpp registry.cpp)
|
||||
add_executable(${TARGET_ENTT} ${TARGET_ENTT_SOURCES})
|
||||
target_include_directories(${TARGET_ENTT} PRIVATE ${COMMON_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_ENTT} PRIVATE ${COMMON_LINK_LIBS})
|
||||
add_test(NAME ${TARGET_ENTT} COMMAND ${TARGET_ENTT})
|
||||
|
||||
# Test TARGET_BENCHMARK
|
||||
|
||||
IF(CMAKE_BUILD_TYPE MATCHES Release)
|
||||
set(TARGET_BENCHMARK_SOURCES benchmark.cpp)
|
||||
add_executable(${TARGET_BENCHMARK} ${TARGET_BENCHMARK_SOURCES})
|
||||
target_include_directories(${TARGET_BENCHMARK} PRIVATE ${COMMON_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_BENCHMARK} PRIVATE ${COMMON_LINK_LIBS})
|
||||
add_test(NAME ${TARGET_BENCHMARK} COMMAND ${TARGET_BENCHMARK})
|
||||
ENDIF(CMAKE_BUILD_TYPE MATCHES Release)
|
||||
134
test/benchmark.cpp
Normal file
134
test/benchmark.cpp
Normal file
@@ -0,0 +1,134 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <registry.hpp>
|
||||
#include <iostream>
|
||||
#include <cstddef>
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
|
||||
struct Position {
|
||||
uint64_t x;
|
||||
uint64_t y;
|
||||
};
|
||||
|
||||
struct Velocity {
|
||||
uint64_t x;
|
||||
uint64_t y;
|
||||
};
|
||||
|
||||
struct Timer final {
|
||||
Timer(): start{std::chrono::system_clock::now()} {}
|
||||
|
||||
void elapsed() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
std::cout << std::chrono::duration<double>(now - start).count() << " seconds" << std::endl;
|
||||
}
|
||||
|
||||
private:
|
||||
std::chrono::time_point<std::chrono::system_clock> start;
|
||||
};
|
||||
|
||||
using registry_type = entt::DefaultRegistry<Position, Velocity>;
|
||||
|
||||
TEST(DefaultRegistry, Construct) {
|
||||
registry_type registry;
|
||||
|
||||
std::cout << "Constructing 10000000 entities" << std::endl;
|
||||
|
||||
Timer timer;
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create();
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, Destroy) {
|
||||
registry_type registry;
|
||||
std::vector<registry_type::entity_type> entities{};
|
||||
|
||||
std::cout << "Destroying 10000000 entities" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
entities.push_back(registry.create());
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
for (auto entity: entities) {
|
||||
registry.destroy(entity);
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateSingleComponent) {
|
||||
registry_type registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, one component" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = registry.get<Position>(entity);
|
||||
(void)position;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateCreateDeleteSingleComponent) {
|
||||
registry_type registry;
|
||||
|
||||
std::cout << "Looping 10000 times creating and deleting a random number of entities" << std::endl;
|
||||
|
||||
Timer timer;
|
||||
|
||||
for(int i = 0; i < 10000; i++) {
|
||||
for(int j = 0; j < 10000; j++) {
|
||||
registry.create<Position>();
|
||||
}
|
||||
|
||||
auto view = registry.view<Position>();
|
||||
|
||||
for(auto entity: view) {
|
||||
if(rand() % 2 == 0) {
|
||||
registry.destroy(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTwoComponents) {
|
||||
registry_type registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position, Velocity>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = registry.get<Position>(entity);
|
||||
auto &velocity = registry.get<Velocity>(entity);
|
||||
(void)position;
|
||||
(void)velocity;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
152
test/component_pool.cpp
Normal file
152
test/component_pool.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <component_pool.hpp>
|
||||
|
||||
TEST(ComponentPool, Functionalities) {
|
||||
using pool_type = entt::ComponentPool<int, double>;
|
||||
|
||||
pool_type pool{0};
|
||||
|
||||
ASSERT_TRUE(pool.empty<int>());
|
||||
ASSERT_TRUE(pool.empty<double>());
|
||||
ASSERT_EQ(pool.capacity<int>(), pool_type::size_type{0});
|
||||
ASSERT_EQ(pool.capacity<double>(), pool_type::size_type{0});
|
||||
ASSERT_EQ(pool.size<int>(), pool_type::size_type{0});
|
||||
ASSERT_EQ(pool.size<double>(), pool_type::size_type{0});
|
||||
ASSERT_EQ(pool.entities<int>(), pool.entities<int>() + pool.size<int>());
|
||||
ASSERT_EQ(pool.entities<double>(), pool.entities<double>() + pool.size<double>());
|
||||
ASSERT_FALSE(pool.has<int>(0));
|
||||
ASSERT_FALSE(pool.has<double>(0));
|
||||
}
|
||||
|
||||
TEST(ComponentPool, ConstructDestroy) {
|
||||
using pool_type = entt::ComponentPool<double, int>;
|
||||
|
||||
pool_type pool{4};
|
||||
|
||||
ASSERT_EQ(pool.construct<int>(0, 42), 42);
|
||||
ASSERT_FALSE(pool.empty<int>());
|
||||
ASSERT_TRUE(pool.empty<double>());
|
||||
ASSERT_EQ(pool.capacity<int>(), pool_type::size_type{4});
|
||||
ASSERT_EQ(pool.capacity<double>(), pool_type::size_type{4});
|
||||
ASSERT_EQ(pool.size<int>(), pool_type::size_type{1});
|
||||
ASSERT_EQ(pool.size<double>(), pool_type::size_type{0});
|
||||
ASSERT_TRUE(pool.has<int>(0));
|
||||
ASSERT_FALSE(pool.has<double>(0));
|
||||
ASSERT_FALSE(pool.has<int>(1));
|
||||
ASSERT_FALSE(pool.has<double>(1));
|
||||
|
||||
ASSERT_EQ(pool.construct<int>(1), 0);
|
||||
ASSERT_FALSE(pool.empty<int>());
|
||||
ASSERT_TRUE(pool.empty<double>());
|
||||
ASSERT_EQ(pool.capacity<int>(), pool_type::size_type{4});
|
||||
ASSERT_EQ(pool.capacity<double>(), pool_type::size_type{4});
|
||||
ASSERT_EQ(pool.size<int>(), pool_type::size_type{2});
|
||||
ASSERT_EQ(pool.size<double>(), pool_type::size_type{0});
|
||||
ASSERT_TRUE(pool.has<int>(0));
|
||||
ASSERT_FALSE(pool.has<double>(0));
|
||||
ASSERT_TRUE(pool.has<int>(1));
|
||||
ASSERT_FALSE(pool.has<double>(1));
|
||||
ASSERT_NE(pool.get<int>(0), pool.get<int>(1));
|
||||
ASSERT_NE(&pool.get<int>(0), &pool.get<int>(1));
|
||||
|
||||
ASSERT_NO_THROW(pool.destroy<int>(0));
|
||||
ASSERT_FALSE(pool.empty<int>());
|
||||
ASSERT_TRUE(pool.empty<double>());
|
||||
ASSERT_EQ(pool.capacity<int>(), pool_type::size_type{4});
|
||||
ASSERT_EQ(pool.capacity<double>(), pool_type::size_type{4});
|
||||
ASSERT_EQ(pool.size<int>(), pool_type::size_type{1});
|
||||
ASSERT_EQ(pool.size<double>(), pool_type::size_type{0});
|
||||
ASSERT_FALSE(pool.has<int>(0));
|
||||
ASSERT_FALSE(pool.has<double>(0));
|
||||
ASSERT_TRUE(pool.has<int>(1));
|
||||
ASSERT_FALSE(pool.has<double>(1));
|
||||
|
||||
ASSERT_NO_THROW(pool.destroy<int>(1));
|
||||
ASSERT_TRUE(pool.empty<int>());
|
||||
ASSERT_TRUE(pool.empty<double>());
|
||||
ASSERT_EQ(pool.capacity<int>(), pool_type::size_type{4});
|
||||
ASSERT_EQ(pool.capacity<double>(), pool_type::size_type{4});
|
||||
ASSERT_EQ(pool.size<int>(), pool_type::size_type{0});
|
||||
ASSERT_EQ(pool.size<int>(), pool_type::size_type{0});
|
||||
ASSERT_FALSE(pool.has<int>(0));
|
||||
ASSERT_FALSE(pool.has<double>(0));
|
||||
ASSERT_FALSE(pool.has<int>(1));
|
||||
ASSERT_FALSE(pool.has<double>(1));
|
||||
|
||||
int *comp[] = {
|
||||
&pool.construct<int>(0, 0),
|
||||
&pool.construct<int>(1, 1),
|
||||
nullptr,
|
||||
&pool.construct<int>(3, 3)
|
||||
};
|
||||
|
||||
ASSERT_FALSE(pool.empty<int>());
|
||||
ASSERT_TRUE(pool.empty<double>());
|
||||
ASSERT_EQ(pool.capacity<int>(), pool_type::size_type{4});
|
||||
ASSERT_EQ(pool.capacity<double>(), pool_type::size_type{4});
|
||||
ASSERT_EQ(pool.size<int>(), pool_type::size_type{3});
|
||||
ASSERT_EQ(pool.size<double>(), pool_type::size_type{0});
|
||||
ASSERT_TRUE(pool.has<int>(0));
|
||||
ASSERT_FALSE(pool.has<double>(0));
|
||||
ASSERT_TRUE(pool.has<int>(1));
|
||||
ASSERT_FALSE(pool.has<double>(1));
|
||||
ASSERT_FALSE(pool.has<int>(2));
|
||||
ASSERT_FALSE(pool.has<double>(2));
|
||||
ASSERT_TRUE(pool.has<int>(3));
|
||||
ASSERT_FALSE(pool.has<double>(3));
|
||||
ASSERT_EQ(&pool.get<int>(0), comp[0]);
|
||||
ASSERT_EQ(&pool.get<int>(1), comp[1]);
|
||||
ASSERT_EQ(&pool.get<int>(3), comp[3]);
|
||||
ASSERT_EQ(pool.get<int>(0), 0);
|
||||
ASSERT_EQ(pool.get<int>(1), 1);
|
||||
ASSERT_EQ(pool.get<int>(3), 3);
|
||||
|
||||
ASSERT_NO_THROW(pool.destroy<int>(0));
|
||||
ASSERT_NO_THROW(pool.destroy<int>(1));
|
||||
ASSERT_NO_THROW(pool.destroy<int>(3));
|
||||
}
|
||||
|
||||
TEST(ComponentPool, HasGet) {
|
||||
using pool_type = entt::ComponentPool<int, char>;
|
||||
|
||||
pool_type pool;
|
||||
const pool_type &cpool = pool;
|
||||
|
||||
int &comp = pool.construct<int>(0, 42);
|
||||
|
||||
ASSERT_EQ(pool.get<int>(0), comp);
|
||||
ASSERT_EQ(pool.get<int>(0), 42);
|
||||
ASSERT_TRUE(pool.has<int>(0));
|
||||
|
||||
ASSERT_EQ(cpool.get<int>(0), comp);
|
||||
ASSERT_EQ(cpool.get<int>(0), 42);
|
||||
ASSERT_TRUE(cpool.has<int>(0));
|
||||
|
||||
ASSERT_NO_THROW(pool.destroy<int>(0));
|
||||
}
|
||||
|
||||
TEST(ComponentPool, EntitiesReset) {
|
||||
using pool_type = entt::ComponentPool<int, char>;
|
||||
|
||||
pool_type pool{2};
|
||||
|
||||
ASSERT_EQ(pool.construct<int>(0, 0), 0);
|
||||
ASSERT_EQ(pool.construct<int>(2, 2), 2);
|
||||
ASSERT_EQ(pool.construct<int>(3, 3), 3);
|
||||
ASSERT_EQ(pool.construct<int>(1, 1), 1);
|
||||
|
||||
ASSERT_EQ(pool.size<int>(), decltype(pool.size<int>()){4});
|
||||
ASSERT_EQ(pool.entities<int>()[0], typename pool_type::entity_type{0});
|
||||
ASSERT_EQ(pool.entities<int>()[1], typename pool_type::entity_type{2});
|
||||
ASSERT_EQ(pool.entities<int>()[2], typename pool_type::entity_type{3});
|
||||
ASSERT_EQ(pool.entities<int>()[3], typename pool_type::entity_type{1});
|
||||
|
||||
pool.destroy<int>(2);
|
||||
|
||||
ASSERT_EQ(pool.size<int>(), decltype(pool.size<int>()){3});
|
||||
ASSERT_EQ(pool.entities<int>()[0], typename pool_type::entity_type{0});
|
||||
ASSERT_EQ(pool.entities<int>()[1], typename pool_type::entity_type{1});
|
||||
ASSERT_EQ(pool.entities<int>()[2], typename pool_type::entity_type{3});
|
||||
|
||||
ASSERT_NO_THROW(pool.reset());
|
||||
}
|
||||
125
test/registry.cpp
Normal file
125
test/registry.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <registry.hpp>
|
||||
|
||||
TEST(DefaultRegistry, Functionalities) {
|
||||
using registry_type = entt::DefaultRegistry<int, char>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
ASSERT_EQ(registry.size(), registry_type::size_type{0});
|
||||
ASSERT_EQ(registry.capacity(), registry_type::size_type{0});
|
||||
ASSERT_TRUE(registry.empty());
|
||||
|
||||
registry_type::entity_type e1 = registry.create();
|
||||
registry_type::entity_type e2 = registry.create<int, char>();
|
||||
|
||||
ASSERT_NE(e1, e2);
|
||||
|
||||
ASSERT_FALSE(registry.has<int>(e1));
|
||||
ASSERT_TRUE(registry.has<int>(e2));
|
||||
ASSERT_FALSE(registry.has<char>(e1));
|
||||
ASSERT_TRUE(registry.has<char>(e2));
|
||||
|
||||
ASSERT_EQ(registry.assign<int>(e1, 42), 42);
|
||||
ASSERT_EQ(registry.assign<char>(e1, 'c'), 'c');
|
||||
ASSERT_NO_THROW(registry.remove<int>(e2));
|
||||
ASSERT_NO_THROW(registry.remove<char>(e2));
|
||||
|
||||
ASSERT_TRUE(registry.has<int>(e1));
|
||||
ASSERT_FALSE(registry.has<int>(e2));
|
||||
ASSERT_TRUE(registry.has<char>(e1));
|
||||
ASSERT_FALSE(registry.has<char>(e2));
|
||||
|
||||
registry_type::entity_type e3 = registry.clone(e1);
|
||||
|
||||
ASSERT_TRUE(registry.has<int>(e3));
|
||||
ASSERT_TRUE(registry.has<char>(e3));
|
||||
ASSERT_EQ(registry.get<int>(e1), 42);
|
||||
ASSERT_EQ(registry.get<char>(e1), 'c');
|
||||
ASSERT_EQ(registry.get<int>(e1), registry.get<int>(e3));
|
||||
ASSERT_EQ(registry.get<char>(e1), registry.get<char>(e3));
|
||||
ASSERT_NE(®istry.get<int>(e1), ®istry.get<int>(e3));
|
||||
ASSERT_NE(®istry.get<char>(e1), ®istry.get<char>(e3));
|
||||
|
||||
ASSERT_NO_THROW(registry.copy(e1, e2));
|
||||
ASSERT_TRUE(registry.has<int>(e2));
|
||||
ASSERT_TRUE(registry.has<char>(e2));
|
||||
ASSERT_EQ(registry.get<int>(e1), 42);
|
||||
ASSERT_EQ(registry.get<char>(e1), 'c');
|
||||
ASSERT_EQ(registry.get<int>(e1), registry.get<int>(e2));
|
||||
ASSERT_EQ(registry.get<char>(e1), registry.get<char>(e2));
|
||||
ASSERT_NE(®istry.get<int>(e1), ®istry.get<int>(e2));
|
||||
ASSERT_NE(®istry.get<char>(e1), ®istry.get<char>(e2));
|
||||
|
||||
ASSERT_NO_THROW(registry.replace<int>(e1, 0));
|
||||
ASSERT_EQ(registry.get<int>(e1), 0);
|
||||
ASSERT_NO_THROW(registry.copy<int>(e1, e2));
|
||||
ASSERT_EQ(registry.get<int>(e2), 0);
|
||||
ASSERT_NE(®istry.get<int>(e1), ®istry.get<int>(e2));
|
||||
|
||||
ASSERT_EQ(registry.size(), registry_type::size_type{3});
|
||||
ASSERT_EQ(registry.capacity(), registry_type::size_type{3});
|
||||
ASSERT_FALSE(registry.empty());
|
||||
|
||||
ASSERT_NO_THROW(registry.destroy(e3));
|
||||
|
||||
ASSERT_EQ(registry.size(), registry_type::size_type{2});
|
||||
ASSERT_EQ(registry.capacity(), registry_type::size_type{3});
|
||||
ASSERT_FALSE(registry.empty());
|
||||
|
||||
ASSERT_NO_THROW(registry.reset());
|
||||
|
||||
ASSERT_EQ(registry.size(), registry_type::size_type{0});
|
||||
ASSERT_EQ(registry.capacity(), registry_type::size_type{0});
|
||||
ASSERT_TRUE(registry.empty());
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, ViewSingleComponent) {
|
||||
using registry_type = entt::DefaultRegistry<int, char>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
registry_type::entity_type e1 = registry.create();
|
||||
registry_type::entity_type e2 = registry.create<int, char>();
|
||||
|
||||
auto view = registry.view<char>();
|
||||
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
ASSERT_EQ(view.size(), typename registry_type::view_type<char>::size_type{1});
|
||||
|
||||
registry.assign<char>(e1);
|
||||
|
||||
ASSERT_EQ(view.size(), typename registry_type::view_type<char>::size_type{2});
|
||||
|
||||
registry.remove<char>(e1);
|
||||
registry.remove<char>(e2);
|
||||
|
||||
ASSERT_EQ(view.begin(), view.end());
|
||||
ASSERT_NO_THROW(registry.reset());
|
||||
|
||||
ASSERT_NO_THROW(registry.view<char>().begin()++);
|
||||
ASSERT_NO_THROW(++registry.view<char>().begin());
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, ViewMultipleComponent) {
|
||||
using registry_type = entt::DefaultRegistry<int, char>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
registry_type::entity_type e1 = registry.create<char>();
|
||||
registry_type::entity_type e2 = registry.create<int, char>();
|
||||
|
||||
auto view = registry.view<int, char>();
|
||||
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
|
||||
registry.remove<char>(e1);
|
||||
registry.remove<char>(e2);
|
||||
view.reset();
|
||||
|
||||
ASSERT_EQ(view.begin(), view.end());
|
||||
ASSERT_NO_THROW(registry.reset());
|
||||
|
||||
ASSERT_NO_THROW((registry.view<int, char>().begin()++));
|
||||
ASSERT_NO_THROW((++registry.view<int, char>().begin()));
|
||||
}
|
||||
Reference in New Issue
Block a user