This commit is contained in:
Michele Caini
2017-03-24 17:20:36 +01:00
commit b0b8ee7aea
16 changed files with 1571 additions and 0 deletions

2
.gitignore vendored Normal file
View File

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

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "deps/googletest"]
path = deps/googletest
url = https://github.com/google/googletest.git

7
AUTHORS Normal file
View File

@@ -0,0 +1,7 @@
# Author
Michele Caini aka skypjack
# Contributors
Paolo Monteverde aka morbo84

116
CMakeLists.txt Normal file
View 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
View 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
View 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
View File

@@ -0,0 +1,2 @@
*
!.gitignore

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

Submodule deps/googletest added at aa148eb2b7

194
src/component_pool.hpp Normal file
View 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
View 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
View 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
View 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
View 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
View 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(&registry.get<int>(e1), &registry.get<int>(e3));
ASSERT_NE(&registry.get<char>(e1), &registry.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(&registry.get<int>(e1), &registry.get<int>(e2));
ASSERT_NE(&registry.get<char>(e1), &registry.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(&registry.get<int>(e1), &registry.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()));
}