From b0b8ee7aea3d9f5cfe6f406036ad30287c4a9f17 Mon Sep 17 00:00:00 2001 From: Michele Caini Date: Fri, 24 Mar 2017 17:20:36 +0100 Subject: [PATCH] init --- .gitignore | 2 + .gitmodules | 3 + AUTHORS | 7 + CMakeLists.txt | 116 +++++++++ LICENSE | 21 ++ README.md | 284 ++++++++++++++++++++++ build/.gitignore | 2 + cmake/modules/FindGoogleTest.cmake | 96 ++++++++ deps.sh | 41 ++++ deps/googletest | 1 + src/component_pool.hpp | 194 ++++++++++++++++ src/registry.hpp | 362 +++++++++++++++++++++++++++++ test/CMakeLists.txt | 31 +++ test/benchmark.cpp | 134 +++++++++++ test/component_pool.cpp | 152 ++++++++++++ test/registry.cpp | 125 ++++++++++ 16 files changed, 1571 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 AUTHORS create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 README.md create mode 100644 build/.gitignore create mode 100644 cmake/modules/FindGoogleTest.cmake create mode 100755 deps.sh create mode 160000 deps/googletest create mode 100644 src/component_pool.hpp create mode 100644 src/registry.hpp create mode 100644 test/CMakeLists.txt create mode 100644 test/benchmark.cpp create mode 100644 test/component_pool.cpp create mode 100644 test/registry.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..cb578b3e9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# QtCreator +*.user diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..6561d8ebb --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "deps/googletest"] + path = deps/googletest + url = https://github.com/google/googletest.git diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000..801d5e458 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,7 @@ +# Author + +Michele Caini aka skypjack + +# Contributors + +Paolo Monteverde aka morbo84 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..677b137e7 --- /dev/null +++ b/CMakeLists.txt @@ -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} +) diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..4a7b3ea33 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 000000000..ec140a271 --- /dev/null +++ b/README.md @@ -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++.
+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 +#include + +struct Position { + float x; + float y; +}; + +struct Velocity { + float dx; + float dy; +}; + +using ECS = entt::DefaultRegistry; + +int main() { + ECS ecs; + + for(auto i = 0; i < 10; ++i) { + auto entity = ecs.create(); + ecs.assign(entity, i * 1.f, i * 1.f); + if(i % 2 == 0) { ecs.assign(entity, i * .1f, i * .1f); } + } + + std::cout << "single component view" << std::endl; + + for(auto entity: ecs.view()) { + auto &position = ecs.get(entity); + std::cout << position.x << "," << position.y << std::endl; + } + + std::cout << "multi component view" << std::endl; + + for(auto entity: ecs.view()) { + auto &position = ecs.get(entity); + auto &velocity = ecs.get(entity); + std::cout << position.x << "," << position.y << " - " << velocity.dx << "," << velocity.dy << std::endl; + if(entity % 4) { ecs.remove(entity); } + else { ecs.destroy(entity); } + } + + std::cout << "single component view" << std::endl; + + for(auto entity: ecs.view()) { + auto &position = ecs.get(entity); + std::cout << position.x << "," << position.y << std::endl; + } + + std::cout << "multi component view" << std::endl; + + for(auto entity: ecs.view()) { + auto &position = ecs.get(entity); + auto &velocity = ecs.get(entity); + std::cout << position.x << "," << position.y << " - " << velocity.dx << "," << velocity.dy << std::endl; + if(entity % 4) { ecs.remove(entity); } + else { ecs.destroy(entity); } + } + + ecs.reset(); +} +``` + +## Motivation + +I started using another well known Entity Component System named [entityx](https://github.com/alecthomas/entityx).
+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.
+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.
+Of course, I'll try to get out of it more features and better performance anyway in the future, mainly for fun.
+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.
+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.
+This means that including the `registry.hpp` header is enough to use it.
+It's a matter of adding the following line at the top of a file: + + #include + +Then pass the proper `-I` argument to the compiler to add the `src` directory to the include paths.
+ +## Documentation + +### API Reference + +*EnTT* contains three main actors: the *registry*, the *view* and the *pool*.
+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{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{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.
+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`: creates a new entity and assigns it the given components, then returns the entity. +* `destroy`: destroys the entity and all its components. +* `assign(entity, args...)`: assigns the given component to the entity and uses `args...` to initialize it. +* `remove(entity)`: removes the given component from the entity. +* `has(entity)`: returns `true` if the entity has the given component, `false` otherwise. +* `get(entity)`: returns a reference to the given component for the entity (undefined behaviour if the entity has not the component). +* `replace(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(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()`: 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.
+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(); + +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.
+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.
+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.
+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*.
+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.
+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.
+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.
+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`.
+In particular: + +``` +template<> +struct ComponentPool 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 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.
+It's a mattrer of including the given specialization along with the registry, so that it can find it during the instantiation.
+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.
+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>{}; +``` + +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.
+A generic pool should expose at least the following memeber functions: + +* `template bool empty() const noexcept;` +* `template size_type capacity() const noexcept;` +* `template size_type size() const noexcept;` +* `template const entity_type * entities() const noexcept;` +* `template bool has(entity_type entity) const noexcept;` +* `template const Comp & get(entity_type entity) const noexcept;` +* `template Comp & get(entity_type entity) noexcept;` +* `template Comp & construct(entity_type entity, Args&&... args);` +* `template 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*.
+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.
+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.
+Code released under [the MIT license](https://github.com/skypjack/entt/blob/master/LICENSE).
diff --git a/build/.gitignore b/build/.gitignore new file mode 100644 index 000000000..d6b7ef32c --- /dev/null +++ b/build/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/cmake/modules/FindGoogleTest.cmake b/cmake/modules/FindGoogleTest.cmake new file mode 100644 index 000000000..b122fbed3 --- /dev/null +++ b/cmake/modules/FindGoogleTest.cmake @@ -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 +) diff --git a/deps.sh b/deps.sh new file mode 100755 index 000000000..522eb672e --- /dev/null +++ b/deps.sh @@ -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" diff --git a/deps/googletest b/deps/googletest new file mode 160000 index 000000000..aa148eb2b --- /dev/null +++ b/deps/googletest @@ -0,0 +1 @@ +Subproject commit aa148eb2b7f70ede0eb10de34b6254826bfb34f4 diff --git a/src/component_pool.hpp b/src/component_pool.hpp new file mode 100644 index 000000000..05c78daba --- /dev/null +++ b/src/component_pool.hpp @@ -0,0 +1,194 @@ +#ifndef ENTT_COMPONENT_POOL_HPP +#define ENTT_COMPONENT_POOL_HPP + + +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace entt { + + +template +struct ComponentPool; + + +template +struct ComponentPool 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(const_cast(this)->get(entity)); + } + + template + 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)... }); + + 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 data; + std::vector reverse; + std::vector direct; +}; + + +template +struct ComponentPool final { + using size_type = typename ComponentPool::size_type; + using entity_type = typename ComponentPool::entity_type; + + explicit ComponentPool(size_type dim = 4098) noexcept + : pools{ComponentPool{dim}, ComponentPool{dim}...} + { + assert(!(dim < 0)); + } + + ComponentPool(const ComponentPool &) = delete; + ComponentPool(ComponentPool &&) = delete; + + ComponentPool & operator=(const ComponentPool &) = delete; + ComponentPool & operator=(ComponentPool &&) = delete; + + template + bool empty() const noexcept { + return std::get>(pools).empty(); + } + + template + size_type capacity() const noexcept { + return std::get>(pools).capacity(); + } + + template + size_type size() const noexcept { + return std::get>(pools).size(); + } + + template + const entity_type * entities() const noexcept { + return std::get>(pools).entities(); + } + + template + bool has(entity_type entity) const noexcept { + return std::get>(pools).has(entity); + } + + template + const Comp & get(entity_type entity) const noexcept { + return std::get>(pools).get(entity); + } + + template + Comp & get(entity_type entity) noexcept { + return const_cast(const_cast(this)->get(entity)); + } + + template + Comp & construct(entity_type entity, Args&&... args) { + return std::get>(pools).construct(entity, std::forward(args)...); + } + + template + void destroy(entity_type entity) { + std::get>(pools).destroy(entity); + } + + void reset() { + using accumulator_type = int[]; + std::get>(pools).reset(); + accumulator_type accumulator = { (std::get>(pools).reset(), 0)... }; + (void)accumulator; + } + +private: + std::tuple, ComponentPool...> pools; +}; + + +} + + +#endif // ENTT_COMPONENT_POOL_HPP diff --git a/src/registry.hpp b/src/registry.hpp new file mode 100644 index 000000000..6438597c1 --- /dev/null +++ b/src/registry.hpp @@ -0,0 +1,362 @@ +#ifndef ENTT_REGISTRY_HPP +#define ENTT_REGISTRY_HPP + + +#include +#include +#include +#include +#include +#include +#include +#include "component_pool.hpp" + + +namespace entt { + + +template +class View; + + +template class Pool, typename... Components, typename Type, typename... Types> +class View, Type, Types...> final { + using pool_type = Pool; + using entity_type = typename pool_type::entity_type; + + class ViewIterator { + bool valid() const noexcept { + using accumulator_type = bool[]; + bool check = pool.template has(entities[pos-1]); + accumulator_type accumulator = { true, (check = check && pool.template has(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 + void prefer() noexcept { + auto sz = pool.template size(); + + if(sz < size) { + entities = pool.template entities(); + 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()}, + size{pool.template size()}, + pool{pool} + { + using accumulator_type = int[]; + accumulator_type accumulator = { 0, (prefer(), 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(); + size = pool.template size(); + accumulator_type accumulator = { 0, (prefer(), 0)... }; + (void)accumulator; + } + +private: + const entity_type *entities; + size_type size; + pool_type &pool; +}; + + +template class Pool, typename... Components, typename Type> +class View, Type> final { + using pool_type = Pool; + 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(), pool.template size()}; + } + + iterator_type end() const noexcept { + return ViewIterator{pool.template entities(), 0}; + } + + size_type size() const noexcept { + return pool.template size(); + } + +private: + pool_type &pool; +}; + + +template +struct Registry; + + +template class Pool, typename... Components> +struct Registry> final { + static_assert(sizeof...(Components) > 1, "!"); + + using entity_type = typename Pool::entity_type; + using size_type = std::size_t; + +private: + using pool_type = Pool; + + template + void destroy(entity_type entity) { + if(pool.template has(entity)) { + pool.template destroy(entity); + } + } + + template + void clone(entity_type from, entity_type to) { + if(pool.template has(from)) { + pool.template construct(to, pool.template get(from)); + } + } + + template + void sync(entity_type from, entity_type to) { + bool src = pool.template has(from); + bool dst = pool.template has(to); + + if(src && dst) { + copy(from, to); + } else if(src) { + clone(from, to); + } else if(dst) { + destroy(to); + } + } + +public: + template + using view_type = View; + + template + Registry(Args&&... args) + : count{0}, pool{std::forward(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 + entity_type create() noexcept { + using accumulator_type = int[]; + auto entity = create(); + accumulator_type accumulator = { 0, (assign(entity), 0)... }; + (void)accumulator; + return entity; + } + + void destroy(entity_type entity) { + using accumulator_type = int[]; + accumulator_type accumulator = { 0, (destroy(entity), 0)... }; + (void)accumulator; + available.push_back(entity); + } + + template + Comp & assign(entity_type entity, Args&&... args) { + return pool.template construct(entity, std::forward(args)...); + } + + template + void remove(entity_type entity) { + pool.template destroy(entity); + } + + template + bool has(entity_type entity) const noexcept { + return pool.template has(entity); + } + + template + const Comp & get(entity_type entity) const noexcept { + return pool.template get(entity); + } + + template + Comp & get(entity_type entity) noexcept { + return pool.template get(entity); + } + + template + void replace(entity_type entity, Args&&... args) { + pool.template get(entity) = Comp{std::forward(args)...}; + } + + entity_type clone(entity_type from) { + auto to = create(); + using accumulator_type = int[]; + accumulator_type accumulator = { 0, (clone(from, to), 0)... }; + (void)accumulator; + return to; + } + + template + void copy(entity_type from, entity_type to) { + pool.template get(to) = pool.template get(from); + } + + void copy(entity_type from, entity_type to) { + using accumulator_type = int[]; + accumulator_type accumulator = { 0, (sync(from, to), 0)... }; + (void)accumulator; + } + + void reset() { + available.clear(); + count = 0; + pool.reset(); + } + + template + view_type view() { + return view_type{pool}; + } + +private: + std::vector available; + entity_type count; + pool_type pool; +}; + + +template +using DefaultRegistry = Registry>; + + +} + + +#endif // ENTT_REGISTRY_HPP diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 000000000..12c615554 --- /dev/null +++ b/test/CMakeLists.txt @@ -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) diff --git a/test/benchmark.cpp b/test/benchmark.cpp new file mode 100644 index 000000000..7fcc4422f --- /dev/null +++ b/test/benchmark.cpp @@ -0,0 +1,134 @@ +#include +#include +#include +#include +#include +#include + +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(now - start).count() << " seconds" << std::endl; + } + +private: + std::chrono::time_point start; +}; + +using registry_type = entt::DefaultRegistry; + +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 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(); + } + + Timer timer; + + auto view = registry.view(); + + for(auto entity: view) { + auto &position = registry.get(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(); + } + + auto view = registry.view(); + + 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(); + } + + Timer timer; + + auto view = registry.view(); + + for(auto entity: view) { + auto &position = registry.get(entity); + auto &velocity = registry.get(entity); + (void)position; + (void)velocity; + } + + timer.elapsed(); + registry.reset(); +} diff --git a/test/component_pool.cpp b/test/component_pool.cpp new file mode 100644 index 000000000..bb233280c --- /dev/null +++ b/test/component_pool.cpp @@ -0,0 +1,152 @@ +#include +#include + +TEST(ComponentPool, Functionalities) { + using pool_type = entt::ComponentPool; + + pool_type pool{0}; + + ASSERT_TRUE(pool.empty()); + ASSERT_TRUE(pool.empty()); + ASSERT_EQ(pool.capacity(), pool_type::size_type{0}); + ASSERT_EQ(pool.capacity(), pool_type::size_type{0}); + ASSERT_EQ(pool.size(), pool_type::size_type{0}); + ASSERT_EQ(pool.size(), pool_type::size_type{0}); + ASSERT_EQ(pool.entities(), pool.entities() + pool.size()); + ASSERT_EQ(pool.entities(), pool.entities() + pool.size()); + ASSERT_FALSE(pool.has(0)); + ASSERT_FALSE(pool.has(0)); +} + +TEST(ComponentPool, ConstructDestroy) { + using pool_type = entt::ComponentPool; + + pool_type pool{4}; + + ASSERT_EQ(pool.construct(0, 42), 42); + ASSERT_FALSE(pool.empty()); + ASSERT_TRUE(pool.empty()); + ASSERT_EQ(pool.capacity(), pool_type::size_type{4}); + ASSERT_EQ(pool.capacity(), pool_type::size_type{4}); + ASSERT_EQ(pool.size(), pool_type::size_type{1}); + ASSERT_EQ(pool.size(), pool_type::size_type{0}); + ASSERT_TRUE(pool.has(0)); + ASSERT_FALSE(pool.has(0)); + ASSERT_FALSE(pool.has(1)); + ASSERT_FALSE(pool.has(1)); + + ASSERT_EQ(pool.construct(1), 0); + ASSERT_FALSE(pool.empty()); + ASSERT_TRUE(pool.empty()); + ASSERT_EQ(pool.capacity(), pool_type::size_type{4}); + ASSERT_EQ(pool.capacity(), pool_type::size_type{4}); + ASSERT_EQ(pool.size(), pool_type::size_type{2}); + ASSERT_EQ(pool.size(), pool_type::size_type{0}); + ASSERT_TRUE(pool.has(0)); + ASSERT_FALSE(pool.has(0)); + ASSERT_TRUE(pool.has(1)); + ASSERT_FALSE(pool.has(1)); + ASSERT_NE(pool.get(0), pool.get(1)); + ASSERT_NE(&pool.get(0), &pool.get(1)); + + ASSERT_NO_THROW(pool.destroy(0)); + ASSERT_FALSE(pool.empty()); + ASSERT_TRUE(pool.empty()); + ASSERT_EQ(pool.capacity(), pool_type::size_type{4}); + ASSERT_EQ(pool.capacity(), pool_type::size_type{4}); + ASSERT_EQ(pool.size(), pool_type::size_type{1}); + ASSERT_EQ(pool.size(), pool_type::size_type{0}); + ASSERT_FALSE(pool.has(0)); + ASSERT_FALSE(pool.has(0)); + ASSERT_TRUE(pool.has(1)); + ASSERT_FALSE(pool.has(1)); + + ASSERT_NO_THROW(pool.destroy(1)); + ASSERT_TRUE(pool.empty()); + ASSERT_TRUE(pool.empty()); + ASSERT_EQ(pool.capacity(), pool_type::size_type{4}); + ASSERT_EQ(pool.capacity(), pool_type::size_type{4}); + ASSERT_EQ(pool.size(), pool_type::size_type{0}); + ASSERT_EQ(pool.size(), pool_type::size_type{0}); + ASSERT_FALSE(pool.has(0)); + ASSERT_FALSE(pool.has(0)); + ASSERT_FALSE(pool.has(1)); + ASSERT_FALSE(pool.has(1)); + + int *comp[] = { + &pool.construct(0, 0), + &pool.construct(1, 1), + nullptr, + &pool.construct(3, 3) + }; + + ASSERT_FALSE(pool.empty()); + ASSERT_TRUE(pool.empty()); + ASSERT_EQ(pool.capacity(), pool_type::size_type{4}); + ASSERT_EQ(pool.capacity(), pool_type::size_type{4}); + ASSERT_EQ(pool.size(), pool_type::size_type{3}); + ASSERT_EQ(pool.size(), pool_type::size_type{0}); + ASSERT_TRUE(pool.has(0)); + ASSERT_FALSE(pool.has(0)); + ASSERT_TRUE(pool.has(1)); + ASSERT_FALSE(pool.has(1)); + ASSERT_FALSE(pool.has(2)); + ASSERT_FALSE(pool.has(2)); + ASSERT_TRUE(pool.has(3)); + ASSERT_FALSE(pool.has(3)); + ASSERT_EQ(&pool.get(0), comp[0]); + ASSERT_EQ(&pool.get(1), comp[1]); + ASSERT_EQ(&pool.get(3), comp[3]); + ASSERT_EQ(pool.get(0), 0); + ASSERT_EQ(pool.get(1), 1); + ASSERT_EQ(pool.get(3), 3); + + ASSERT_NO_THROW(pool.destroy(0)); + ASSERT_NO_THROW(pool.destroy(1)); + ASSERT_NO_THROW(pool.destroy(3)); +} + +TEST(ComponentPool, HasGet) { + using pool_type = entt::ComponentPool; + + pool_type pool; + const pool_type &cpool = pool; + + int &comp = pool.construct(0, 42); + + ASSERT_EQ(pool.get(0), comp); + ASSERT_EQ(pool.get(0), 42); + ASSERT_TRUE(pool.has(0)); + + ASSERT_EQ(cpool.get(0), comp); + ASSERT_EQ(cpool.get(0), 42); + ASSERT_TRUE(cpool.has(0)); + + ASSERT_NO_THROW(pool.destroy(0)); +} + +TEST(ComponentPool, EntitiesReset) { + using pool_type = entt::ComponentPool; + + pool_type pool{2}; + + ASSERT_EQ(pool.construct(0, 0), 0); + ASSERT_EQ(pool.construct(2, 2), 2); + ASSERT_EQ(pool.construct(3, 3), 3); + ASSERT_EQ(pool.construct(1, 1), 1); + + ASSERT_EQ(pool.size(), decltype(pool.size()){4}); + ASSERT_EQ(pool.entities()[0], typename pool_type::entity_type{0}); + ASSERT_EQ(pool.entities()[1], typename pool_type::entity_type{2}); + ASSERT_EQ(pool.entities()[2], typename pool_type::entity_type{3}); + ASSERT_EQ(pool.entities()[3], typename pool_type::entity_type{1}); + + pool.destroy(2); + + ASSERT_EQ(pool.size(), decltype(pool.size()){3}); + ASSERT_EQ(pool.entities()[0], typename pool_type::entity_type{0}); + ASSERT_EQ(pool.entities()[1], typename pool_type::entity_type{1}); + ASSERT_EQ(pool.entities()[2], typename pool_type::entity_type{3}); + + ASSERT_NO_THROW(pool.reset()); +} diff --git a/test/registry.cpp b/test/registry.cpp new file mode 100644 index 000000000..53694cb1f --- /dev/null +++ b/test/registry.cpp @@ -0,0 +1,125 @@ +#include +#include + +TEST(DefaultRegistry, Functionalities) { + using registry_type = entt::DefaultRegistry; + + 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(); + + ASSERT_NE(e1, e2); + + ASSERT_FALSE(registry.has(e1)); + ASSERT_TRUE(registry.has(e2)); + ASSERT_FALSE(registry.has(e1)); + ASSERT_TRUE(registry.has(e2)); + + ASSERT_EQ(registry.assign(e1, 42), 42); + ASSERT_EQ(registry.assign(e1, 'c'), 'c'); + ASSERT_NO_THROW(registry.remove(e2)); + ASSERT_NO_THROW(registry.remove(e2)); + + ASSERT_TRUE(registry.has(e1)); + ASSERT_FALSE(registry.has(e2)); + ASSERT_TRUE(registry.has(e1)); + ASSERT_FALSE(registry.has(e2)); + + registry_type::entity_type e3 = registry.clone(e1); + + ASSERT_TRUE(registry.has(e3)); + ASSERT_TRUE(registry.has(e3)); + ASSERT_EQ(registry.get(e1), 42); + ASSERT_EQ(registry.get(e1), 'c'); + ASSERT_EQ(registry.get(e1), registry.get(e3)); + ASSERT_EQ(registry.get(e1), registry.get(e3)); + ASSERT_NE(®istry.get(e1), ®istry.get(e3)); + ASSERT_NE(®istry.get(e1), ®istry.get(e3)); + + ASSERT_NO_THROW(registry.copy(e1, e2)); + ASSERT_TRUE(registry.has(e2)); + ASSERT_TRUE(registry.has(e2)); + ASSERT_EQ(registry.get(e1), 42); + ASSERT_EQ(registry.get(e1), 'c'); + ASSERT_EQ(registry.get(e1), registry.get(e2)); + ASSERT_EQ(registry.get(e1), registry.get(e2)); + ASSERT_NE(®istry.get(e1), ®istry.get(e2)); + ASSERT_NE(®istry.get(e1), ®istry.get(e2)); + + ASSERT_NO_THROW(registry.replace(e1, 0)); + ASSERT_EQ(registry.get(e1), 0); + ASSERT_NO_THROW(registry.copy(e1, e2)); + ASSERT_EQ(registry.get(e2), 0); + ASSERT_NE(®istry.get(e1), ®istry.get(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; + + registry_type registry; + + registry_type::entity_type e1 = registry.create(); + registry_type::entity_type e2 = registry.create(); + + auto view = registry.view(); + + ASSERT_NE(view.begin(), view.end()); + ASSERT_EQ(view.size(), typename registry_type::view_type::size_type{1}); + + registry.assign(e1); + + ASSERT_EQ(view.size(), typename registry_type::view_type::size_type{2}); + + registry.remove(e1); + registry.remove(e2); + + ASSERT_EQ(view.begin(), view.end()); + ASSERT_NO_THROW(registry.reset()); + + ASSERT_NO_THROW(registry.view().begin()++); + ASSERT_NO_THROW(++registry.view().begin()); +} + +TEST(DefaultRegistry, ViewMultipleComponent) { + using registry_type = entt::DefaultRegistry; + + registry_type registry; + + registry_type::entity_type e1 = registry.create(); + registry_type::entity_type e2 = registry.create(); + + auto view = registry.view(); + + ASSERT_NE(view.begin(), view.end()); + + registry.remove(e1); + registry.remove(e2); + view.reset(); + + ASSERT_EQ(view.begin(), view.end()); + ASSERT_NO_THROW(registry.reset()); + + ASSERT_NO_THROW((registry.view().begin()++)); + ASSERT_NO_THROW((++registry.view().begin())); +}