Michele Caini преди 9 години
ревизия
b0b8ee7aea
променени са 16 файла, в които са добавени 1571 реда и са изтрити 0 реда
  1. 2 0
      .gitignore
  2. 3 0
      .gitmodules
  3. 7 0
      AUTHORS
  4. 116 0
      CMakeLists.txt
  5. 21 0
      LICENSE
  6. 284 0
      README.md
  7. 2 0
      build/.gitignore
  8. 96 0
      cmake/modules/FindGoogleTest.cmake
  9. 41 0
      deps.sh
  10. 1 0
      deps/googletest
  11. 194 0
      src/component_pool.hpp
  12. 362 0
      src/registry.hpp
  13. 31 0
      test/CMakeLists.txt
  14. 134 0
      test/benchmark.cpp
  15. 152 0
      test/component_pool.cpp
  16. 125 0
      test/registry.cpp

+ 2 - 0
.gitignore

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

+ 3 - 0
.gitmodules

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

+ 7 - 0
AUTHORS

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

+ 116 - 0
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}
+)

+ 21 - 0
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.

+ 284 - 0
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++.<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 - 0
build/.gitignore

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

+ 96 - 0
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
+)

+ 41 - 0
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"

+ 1 - 0
deps/googletest

@@ -0,0 +1 @@
+Subproject commit aa148eb2b7f70ede0eb10de34b6254826bfb34f4

+ 194 - 0
src/component_pool.hpp

@@ -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 - 0
src/registry.hpp

@@ -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 - 0
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)

+ 134 - 0
test/benchmark.cpp

@@ -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 - 0
test/component_pool.cpp

@@ -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 - 0
test/registry.cpp

@@ -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()));
+}