diff --git a/bench/benchmark.h b/bench/benchmark.h index 9c23143..d7fe39f 100644 --- a/bench/benchmark.h +++ b/bench/benchmark.h @@ -1,3 +1,6 @@ +#ifndef _BENCHMARK_HPP_ +#define _BENCHMARK_HPP_ + #include #include #include @@ -10,9 +13,8 @@ using rep_t = double; using elapsed_t = std::chrono::duration; using average_t = std::chrono::duration; -// do_not_optimize works very well for gcc clang and msvc -// however there may be differences in overhead -// Other compilers do not have a do_not_optimize implementation +// do_not_optimize works very well for gcc and clang +// msvc works aswell, however results are not always accurate #if __GNUC__ || __clang__ template @@ -52,3 +54,5 @@ inline void do_not_optimize(T const&) auto __benchmark_iterations = __ITERATIONS * __OPERATIONS_PER_ITERATION; \ std::cout << "[ ELAPSED ] " << (__benchmark_elapsed.count() * 0.001) << " ms (" << __benchmark_iterations << " iterations)" << std::endl; \ std::cout << "[ AVERAGE ] " << (__benchmark_average.count() / __benchmark_iterations) << " ns" << std::endl; + +#endif \ No newline at end of file diff --git a/single/xecs.hpp b/single/xecs.hpp index 9e15cbb..004427e 100644 --- a/single/xecs.hpp +++ b/single/xecs.hpp @@ -1,4 +1,5 @@ -#pragma once +#ifndef _XECS_ARCHETYPE_HPP_ +#define _XECS_ARCHETYPE_HPP_ #include #include @@ -336,6 +337,243 @@ struct archetype_list_builder : internal::archetype_list_builder<> {}; } // namespace xecs +#endif + +#ifndef _XECS_ENTITY_MANAGER_HPP_ +#define _XECS_ENTITY_MANAGER_HPP_ + +#include +#include +#include +#include + +#define ENTITY_MANAGER_STACK_SIZE 16384 // This should not be changed unless you know what your doing + +static_assert((ENTITY_MANAGER_STACK_SIZE & (ENTITY_MANAGER_STACK_SIZE - 1)) == 0, + "ENTITY_MANAGER_STACK_SIZE must be a power of two"); + +namespace xecs +{ +/** + * @brief Manager responsible for distributing entities. + * + * An entity manager is essentially a class responsible for generating and recycling entities. + * Entities are simply just identifiers. + * + * For an enitity to be valid is must be an unsigned integer. + * + * The maximum amount of entities that can exist is equal to the maximum value of the entity. 16 bit identifiers + * can allow for a maximum of 65535 entities. A 32 bit identifier is much larger and can support 4,294,967,295 entities. + * Anything more than 32 bit is probably overkill for the any game or simulation. + * + * The entity manager is just a counter with a stack to be able to recycle entities. + * + * This particular entity manager optimizes to use stack memory (could also be static memory). It contains + * a small stack allocated on the stack and a bigger stack allocated on the heap. The size of the stack memory stack + * is defined by ENTITY_MANAGER_STACK_SIZE. When the size of recycled entities exceed this amount, the entity manager + * will then go to the heap. The manager will always priorize fetching from the stack. It is possible to swap recycled values + * accumulated in the heap memory stack into the stack memory stack + * + * @tparam Entity unsigned integer type to represent entity + */ +template +class entity_manager +{ +public: + using entity_type = Entity; + using size_type = size_t; + + static_assert(std::numeric_limits::is_integer && !std::numeric_limits::is_signed, + "Entity type must be an unsigned integer"); + + /** + * @brief Fixed capacity of entities for stack memory stack. + * + */ + static constexpr size_type stack_capacity = ENTITY_MANAGER_STACK_SIZE / sizeof(entity_type); + + /** + * @brief Minimum capacity on entities for the heap memory stack. + * + * Twice as big as stack. + */ + static constexpr size_type minimum_heap_capacity = stack_capacity * 2; + +public: + using stack_buffer_type = entity_type[stack_capacity]; + using heap_buffer_type = entity_type*; + + /** + * @brief Construct a new entity manager object + * + */ + entity_manager() + : _current(0), _stack_reusable(0), _heap_reusable(0), _heap_capacity(minimum_heap_capacity), _stack_buffer() + { + _heap_buffer = static_cast(std::malloc(minimum_heap_capacity * sizeof(entity_type))); + } + + /** + * @brief Destroy the entity manager object + */ + ~entity_manager() + { + free(_heap_buffer); + } + + entity_manager(const entity_manager&) = delete; + entity_manager(entity_manager&&) = delete; + entity_manager& operator=(const entity_manager&) = delete; + + /** + * @brief Generates a unique entity. + * + * This will try to obtain a recycled entity, but if none are available then the + * internal counter will be incremented. + * + * @return entity_type The entity identifier generated + */ + entity_type generate() + { + if (_stack_reusable) return _stack_buffer[--_stack_reusable]; + else if (_heap_reusable) + return _heap_buffer[--_heap_reusable]; + else + return _current++; + } + + /** + * @brief Allows an entity to be reused. + * + * This will add the entity to pools of reusable entities. + * + * @param entity Entity to release + */ + void release(entity_type entity) + { + if (_stack_reusable < stack_capacity) _stack_buffer[_stack_reusable++] = entity; + else + { + // Heap resizing should not happen very often + if (_heap_reusable == _heap_capacity) + { + // Grow by a factor of 1.25 + // This is ok since we know the heap capacity starts off as a large amount + _heap_capacity = (_heap_capacity * 5) / 3; + _heap_buffer = static_cast(std::realloc(_heap_buffer, _heap_capacity * sizeof(entity_type))); + } + _heap_buffer[_heap_reusable++] = entity; + } + } + + /** + * @brief releases all entities at once. + * + * Resets the internal counter and clears reusable entities. + * + * This is a very cheap O(1) operation. + */ + void release_all() + { + _stack_reusable = 0; + _heap_reusable = 0; + _current = 0; + } + + /** + * @brief moves reusable heap memory entites into stack memory as best as possible. + * + * This can be good to call every once in a while to insure that we use stack memory + * as much as possible by moving the entities accumulated in the heap. + */ + void swap() + { + if (_heap_reusable && _stack_reusable != stack_capacity) + { + const auto stack_space = stack_capacity - _stack_reusable; + const auto swap_amount = _heap_reusable < stack_space ? _heap_reusable : stack_space; + + void* dst_stack = static_cast(static_cast(_stack_buffer) + _stack_reusable); + void* src_heap = _heap_buffer + _heap_reusable - swap_amount; + + std::memcpy(dst_stack, src_heap, swap_amount * sizeof(entity_type)); + + _stack_reusable += swap_amount; + _heap_reusable -= swap_amount; + } + } + + /** + * @brief Resizes the heap memory stack to be as small possible. + * + * The heap memory cannot be smaller than minimum_heap_capacity. + * + * This is good to call every once in a while to optimize memory usage. + */ + void shrink_to_fit() + { + if (_heap_reusable != _heap_capacity && _heap_reusable > minimum_heap_capacity) + { + _heap_capacity = _heap_reusable; + + _heap_buffer = static_cast(std::realloc(_heap_buffer, _heap_capacity * sizeof(entity_type))); + } + } + + /** + * @brief Reveals the current value of the internal counter. + * + * @warning This value is not guaranted to be the next value to be generated. + * + * @return entity_type Next entity for internal counter + */ + [[nodiscard]] entity_type peek() const { return _current; } + + /** + * @brief Returns the amount of reusable entities that are in stack memory. + * + * @return size_type Amount of reusable entites in stack memory + */ + [[nodiscard]] size_type stack_reusable() const { return _stack_reusable; } + + /** + * @brief Returns the amount of reusable entities that are in heap memory. + * + * @return size_type amount of reusable entites in heap memory + */ + [[nodiscard]] size_type heap_reusable() const { return _heap_reusable; } + + /** + * @brief Returns the total amount of reusable entities. + * + * @return size_type Reusable entities + */ + [[nodiscard]] size_type reusable() const { return _stack_reusable + _heap_reusable; } + + /** + * @brief Returns the current capacity of the heap memory stack. + * + * @return size_type Heap memory stack capacity + */ + [[nodiscard]] size_type heap_capacity() const { return _heap_capacity; } + +private: + entity_type _current; + + size_type _stack_reusable; + size_type _heap_reusable; + size_type _heap_capacity; + + heap_buffer_type _heap_buffer; + stack_buffer_type _stack_buffer; +}; +} // namespace xecs + +#endif + +#ifndef _XECS_STORAGE_HPP_ +#define _XECS_STORAGE_HPP_ + #include #include #include @@ -851,15 +1089,15 @@ class storage>::iterator final {} // clang-format off - iterator& operator+=(const size_type value) { _pos += value; return *this; } - iterator& operator-=(const size_type value) { _pos -= value; return *this; } + iterator& operator+=(const size_type value) { _pos -= value; return *this; } + iterator& operator-=(const size_type value) { _pos += value; return *this; } // clang-format on iterator& operator++() { return --_pos, *this; } iterator& operator--() { return ++_pos, *this; } - iterator operator+(const size_type value) const { return { _pos - value }; } - iterator operator-(const size_type value) const { return { _pos + value }; } + iterator operator+(const size_type value) const { return { _ptr, _pos - value }; } + iterator operator-(const size_type value) const { return { _ptr, _pos + value }; } bool operator==(const iterator other) const { return other._pos == _pos; } bool operator!=(const iterator other) const { return other._pos != _pos; } @@ -913,232 +1151,10 @@ class storage>::iterator final }; } // namespace xecs -#include -#include -#include -#include - -#define ENTITY_MANAGER_STACK_SIZE 16384 // This should not be changed unless you know what your doing - -static_assert((ENTITY_MANAGER_STACK_SIZE & (ENTITY_MANAGER_STACK_SIZE - 1)) == 0, - "ENTITY_MANAGER_STACK_SIZE must be a power of two"); - -namespace xecs -{ -/** - * @brief Manager responsible for distributing entities. - * - * An entity manager is essentially a class responsible for generating and recycling entities. - * Entities are simply just identifiers. - * - * For an enitity to be valid is must be an unsigned integer. - * - * The maximum amount of entities that can exist is equal to the maximum value of the entity. 16 bit identifiers - * can allow for a maximum of 65535 entities. A 32 bit identifier is much larger and can support 4,294,967,295 entities. - * Anything more than 32 bit is probably overkill for the any game or simulation. - * - * The entity manager is just a counter with a stack to be able to recycle entities. - * - * This particular entity manager optimizes to use stack memory (could also be static memory). It contains - * a small stack allocated on the stack and a bigger stack allocated on the heap. The size of the stack memory stack - * is defined by ENTITY_MANAGER_STACK_SIZE. When the size of recycled entities exceed this amount, the entity manager - * will then go to the heap. The manager will always priorize fetching from the stack. It is possible to swap recycled values - * accumulated in the heap memory stack into the stack memory stack - * - * @tparam Entity unsigned integer type to represent entity - */ -template -class entity_manager -{ -public: - using entity_type = Entity; - using size_type = size_t; - - static_assert(std::numeric_limits::is_integer && !std::numeric_limits::is_signed, - "Entity type must be an unsigned integer"); - - /** - * @brief Fixed capacity of entities for stack memory stack. - * - */ - static constexpr size_type stack_capacity = ENTITY_MANAGER_STACK_SIZE / sizeof(entity_type); - - /** - * @brief Minimum capacity on entities for the heap memory stack. - * - * Twice as big as stack. - */ - static constexpr size_type minimum_heap_capacity = stack_capacity * 2; - -public: - using stack_buffer_type = entity_type[stack_capacity]; - using heap_buffer_type = entity_type*; - - /** - * @brief Construct a new entity manager object - * - */ - entity_manager() - : _current(0), _stack_reusable(0), _heap_reusable(0), _heap_capacity(minimum_heap_capacity), _stack_buffer() - { - _heap_buffer = static_cast(std::malloc(minimum_heap_capacity * sizeof(entity_type))); - } - - /** - * @brief Destroy the entity manager object - */ - ~entity_manager() - { - free(_heap_buffer); - } - - entity_manager(const entity_manager&) = delete; - entity_manager(entity_manager&&) = delete; - entity_manager& operator=(const entity_manager&) = delete; - - /** - * @brief Generates a unique entity. - * - * This will try to obtain a recycled entity, but if none are available then the - * internal counter will be incremented. - * - * @return entity_type The entity identifier generated - */ - entity_type generate() - { - if (_stack_reusable) return _stack_buffer[--_stack_reusable]; - else if (_heap_reusable) - return _heap_buffer[--_heap_reusable]; - else - return _current++; - } - - /** - * @brief Allows an entity to be reused. - * - * This will add the entity to pools of reusable entities. - * - * @param entity Entity to release - */ - void release(entity_type entity) - { - if (_stack_reusable < stack_capacity) _stack_buffer[_stack_reusable++] = entity; - else - { - // Heap resizing should not happen very often - if (_heap_reusable == _heap_capacity) - { - // Grow by a factor of 1.25 - // This is ok since we know the heap capacity starts off as a large amount - _heap_capacity = (_heap_capacity * 5) / 3; - _heap_buffer = static_cast(std::realloc(_heap_buffer, _heap_capacity * sizeof(entity_type))); - } - _heap_buffer[_heap_reusable++] = entity; - } - } - - /** - * @brief releases all entities at once. - * - * Resets the internal counter and clears reusable entities. - * - * This is a very cheap O(1) operation. - */ - void release_all() - { - _stack_reusable = 0; - _heap_reusable = 0; - _current = 0; - } - - /** - * @brief moves reusable heap memory entites into stack memory as best as possible. - * - * This can be good to call every once in a while to insure that we use stack memory - * as much as possible by moving the entities accumulated in the heap. - */ - void swap() - { - if (_heap_reusable && _stack_reusable != stack_capacity) - { - const auto stack_space = stack_capacity - _stack_reusable; - const auto swap_amount = _heap_reusable < stack_space ? _heap_reusable : stack_space; - - void* dst_stack = static_cast(static_cast(_stack_buffer) + _stack_reusable); - void* src_heap = _heap_buffer + _heap_reusable - swap_amount; - - std::memcpy(dst_stack, src_heap, swap_amount * sizeof(entity_type)); +#endif - _stack_reusable += swap_amount; - _heap_reusable -= swap_amount; - } - } - - /** - * @brief Resizes the heap memory stack to be as small possible. - * - * The heap memory cannot be smaller than minimum_heap_capacity. - * - * This is good to call every once in a while to optimize memory usage. - */ - void shrink_to_fit() - { - if (_heap_reusable != _heap_capacity && _heap_reusable > minimum_heap_capacity) - { - _heap_capacity = _heap_reusable; - - _heap_buffer = static_cast(std::realloc(_heap_buffer, _heap_capacity * sizeof(entity_type))); - } - } - - /** - * @brief Reveals the current value of the internal counter. - * - * @warning This value is not guaranted to be the next value to be generated. - * - * @return entity_type Next entity for internal counter - */ - [[nodiscard]] entity_type peek() const { return _current; } - - /** - * @brief Returns the amount of reusable entities that are in stack memory. - * - * @return size_type Amount of reusable entites in stack memory - */ - [[nodiscard]] size_type stack_reusable() const { return _stack_reusable; } - - /** - * @brief Returns the amount of reusable entities that are in heap memory. - * - * @return size_type amount of reusable entites in heap memory - */ - [[nodiscard]] size_type heap_reusable() const { return _heap_reusable; } - - /** - * @brief Returns the total amount of reusable entities. - * - * @return size_type Reusable entities - */ - [[nodiscard]] size_type reusable() const { return _stack_reusable + _heap_reusable; } - - /** - * @brief Returns the current capacity of the heap memory stack. - * - * @return size_type Heap memory stack capacity - */ - [[nodiscard]] size_type heap_capacity() const { return _heap_capacity; } - -private: - entity_type _current; - - size_type _stack_reusable; - size_type _heap_reusable; - size_type _heap_capacity; - - heap_buffer_type _heap_buffer; - stack_buffer_type _stack_buffer; -}; -} // namespace xecs +#ifndef _XECS_REGISTRY_HPP_ +#define _XECS_REGISTRY_HPP_ #include #include @@ -1574,4 +1590,8 @@ class registry>::basic_view private: registry_type* _registry; }; -} // namespace xecs \ No newline at end of file +} // namespace xecs + +#endif + + diff --git a/src/archetype.hpp b/src/archetype.hpp index 0156512..46f968a 100644 --- a/src/archetype.hpp +++ b/src/archetype.hpp @@ -1,4 +1,5 @@ -#pragma once +#ifndef _XECS_ARCHETYPE_HPP_ +#define _XECS_ARCHETYPE_HPP_ #include #include @@ -335,3 +336,5 @@ namespace internal struct archetype_list_builder : internal::archetype_list_builder<> {}; } // namespace xecs + +#endif \ No newline at end of file diff --git a/src/entity_manager.hpp b/src/entity_manager.hpp index 993600e..856168b 100644 --- a/src/entity_manager.hpp +++ b/src/entity_manager.hpp @@ -1,4 +1,5 @@ -#pragma once +#ifndef _XECS_ENTITY_MANAGER_HPP_ +#define _XECS_ENTITY_MANAGER_HPP_ #include #include @@ -226,3 +227,5 @@ class entity_manager stack_buffer_type _stack_buffer; }; } // namespace xecs + +#endif diff --git a/src/registry.hpp b/src/registry.hpp index 426906f..96d7f49 100644 --- a/src/registry.hpp +++ b/src/registry.hpp @@ -1,4 +1,5 @@ -#pragma once +#ifndef _XECS_REGISTRY_HPP_ +#define _XECS_REGISTRY_HPP_ #include "archetype.hpp" #include "entity_manager.hpp" @@ -439,3 +440,5 @@ class registry>::basic_view registry_type* _registry; }; } // namespace xecs + +#endif diff --git a/src/storage.hpp b/src/storage.hpp index 34b3252..1bbbf19 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -1,4 +1,5 @@ -#pragma once +#ifndef _XECS_STORAGE_HPP_ +#define _XECS_STORAGE_HPP_ #include "archetype.hpp" @@ -577,4 +578,6 @@ class storage>::iterator final storage* const _ptr; size_type _pos; }; -} // namespace xecs \ No newline at end of file +} // namespace xecs + +#endif \ No newline at end of file diff --git a/src/xecs.hpp b/src/xecs.hpp index 98facc0..06b6898 100644 --- a/src/xecs.hpp +++ b/src/xecs.hpp @@ -1,5 +1,3 @@ -#pragma once - #include "archetype.hpp" #include "entity_manager.hpp" #include "registry.hpp"