sandbox
Loading...
Searching...
No Matches
registry.hpp
1// SPDX-License-Identifier: MIT
2#ifndef LIBSBX_REGISTRY_HPP_
3#define LIBSBX_REGISTRY_HPP_
4
5#include <vector>
6#include <memory>
7#include <unordered_map>
8#include <unordered_set>
9#include <typeindex>
10#include <optional>
11#include <algorithm>
12#include <tuple>
13#include <ranges>
14
15#include <libsbx/utility/type_id.hpp>
16#include <libsbx/utility/concepts.hpp>
17#include <libsbx/utility/algorithm.hpp>
18#include <libsbx/utility/logger.hpp>
19#include <libsbx/utility/hashed_string.hpp>
20#include <libsbx/utility/assert.hpp>
21
22#include <libsbx/memory/concepts.hpp>
23#include <libsbx/memory/observer_ptr.hpp>
24#include <libsbx/memory/tracking_allocator.hpp>
25
26#include <libsbx/containers/dense_map.hpp>
27
28#include <libsbx/ecs/entity.hpp>
30#include <libsbx/ecs/storage.hpp>
31#include <libsbx/ecs/view.hpp>
32
33#include <libsbx/ecs/detail/registry_storage_iterator.hpp>
34
35namespace sbx::ecs {
36
37namespace detail {
38
40
41} // namespace detail
42
48template<typename Type>
50
51template<typename Type, typename Entity = entity, memory::allocator_for<Type> Allocator = std::allocator<Type>>
54}; // struct storage_type
55
56template<typename... Args>
57using storage_type_t = typename storage_type<Args...>::type;
58
59template<typename Type, typename Entity = entity, memory::allocator_for<std::remove_const_t<Type>> Allocator = std::allocator<std::remove_const_t<Type>>>
61 using type = utility::constness_as_t<storage_type_t<std::remove_const_t<Type>, Entity, Allocator>, Type>;
62}; // struct storage_for
63
64template<typename... Args>
65using storage_for_t = typename storage_for<Args...>::type;
66
67template<typename Entity, memory::allocator_for<Entity> Allocator = std::allocator<Entity>>
69
71 using allocator_traits = std::allocator_traits<Allocator>;
72
73 using pool_container_type = containers::dense_map<std::uint32_t, std::shared_ptr<base_type>, std::identity, std::equal_to<>, memory::rebound_allocator_t<Allocator, std::pair<const std::uint32_t, std::shared_ptr<base_type>>>>;
75
76 template<typename Type>
77 using storage_for_type = storage_for_t<Type, Entity, memory::rebound_allocator_t<Allocator, std::remove_const_t<Type>>>;
78
79public:
80
81 using allocator_type = Allocator;
82 using entity_type = entity_traits::value_type;
83 using version_type = entity_traits::version_type;
84 using size_type = std::size_t;
85 using difference_type = std::ptrdiff_t;
86 using common_type = base_type;
89
90 // template<typename... Get, typename... Exclude>
91 // using view_type = basic_view<get_t<storage_for_type<Get>...>, exclude_t<storage_for_type<Exclude>...>>;
92
93 // template<typename Type, typename... Other, typename... Exclude>
94 // using const_view_type = view_type<const Type, const Other..., const Exclude...>;
95
97 : basic_registry{allocator_type{}} { }
98
99 explicit basic_registry(const allocator_type& allocator)
100 : basic_registry{0u, allocator} { }
101
102 basic_registry(const size_type count, const allocator_type &allocator = allocator_type{})
103 : _pools{allocator},
104 _entities{allocator} {
105 _pools.reserve(count);
106 }
107
108 basic_registry(const basic_registry& other) = delete;
109
110 basic_registry(basic_registry&& other) noexcept
111 : _pools{std::move(other._pools)},
112 _entities{std::move(other._entities)} { }
113
114 ~basic_registry() = default;
115
116 auto operator=(const basic_registry& other) -> basic_registry& = delete;
117
118 auto operator=(basic_registry&& other) noexcept -> basic_registry& {
119 swap(other);
120 return *this;
121 }
122
123 auto swap(basic_registry& other) noexcept -> void {
124 using std::swap;
125 swap(_pools, other._pools);
126 swap(_entities, other._entities);
127 }
128
129 [[nodiscard]] constexpr auto get_allocator() const noexcept -> allocator_type {
130 return _entities.get_allocator();
131 }
132
133 [[nodiscard]] auto is_valid(const entity_type entity) const -> bool {
134 return static_cast<size_type>(_entities.find(entity).index()) < _entities.free_list();
135 }
136
137 auto create() -> entity_type {
138 return _entities.generate();
139 }
140
141 auto destroy(const entity_type entity) -> version_type {
142 for (auto position = _pools.size(); position != 0u; --position) {
143 _pools.begin()[static_cast<typename pool_container_type::difference_type>(position - 1u)].second->remove(entity);
144 }
145
146 _entities.erase(entity);
147 return _entities.current(entity);
148 }
149
150 template<typename Type, typename... Args>
151 requires (std::is_constructible_v<Type, Args...>)
152 auto emplace(const entity_type entity, Args&&... args) -> decltype(auto) {
153 utility::assert_that(is_valid(entity), "Invalid entity");
154 return _assure<Type>().emplace(entity, std::forward<Args>(args)...);
155 }
156
157 template<typename Type, typename... Other>
158 auto remove(const entity_type entity) -> size_type {
159 return (_assure<Type>().remove(entity) + ... + _assure<Other>().remove(entity));
160 }
161
162 template<typename... Type>
163 [[nodiscard]] auto all_of([[maybe_unused]] const entity_type entity) const -> bool {
164 if constexpr(sizeof...(Type) == 1u) {
165 auto* pool = _assure<std::remove_const_t<Type>...>();
166 return pool && pool->contains(entity);
167 } else {
168 return (all_of<Type>(entity) && ...);
169 }
170 }
171
172 template<typename... Type>
173 [[nodiscard]] auto any_of([[maybe_unused]] const entity_type entity) const -> bool {
174 return (all_of<Type>(entity) || ...);
175 }
176
177 template<typename... Type>
178 [[nodiscard]] auto get([[maybe_unused]] const entity_type entity) const -> decltype(auto) {
179 if constexpr (sizeof...(Type) == 1u) {
180 return (_assure<std::remove_const_t<Type>>()->get(entity), ...);
181 } else {
182 return std::forward_as_tuple(get<Type>(entity)...);
183 }
184 }
185
186 template<typename... Type>
187 [[nodiscard]] auto get([[maybe_unused]] const entity_type entity) -> decltype(auto) {
188 if constexpr (sizeof...(Type) == 1u) {
189 return (static_cast<storage_for_type<Type>&>(_assure<std::remove_const_t<Type>>()).get(entity), ...);
190 } else {
191 return std::forward_as_tuple(get<Type>(entity)...);
192 }
193 }
194
195 template<typename Type, typename... Args>
196 requires (std::is_constructible_v<Type, Args...>)
197 [[nodiscard]] auto get_or_emplace(const entity_type entity, Args&&... args) -> decltype(auto) {
198 auto& pool = _assure<Type>();
199 utility::assert_that(is_valid(entity), "Invalid entity");
200 return pool.contains(entity) ? pool.get(entity) : pool.emplace(entity, std::forward<Args>(args)...);
201 }
202
203 template<typename... Type>
204 [[nodiscard]] auto try_get([[maybe_unused]] const entity_type entity) const -> decltype(auto) {
205 if constexpr (sizeof...(Type) == 1u) {
206 const auto* pool = _assure<std::remove_const_t<Type>...>();
207 return (pool && pool->contains(entity)) ? std::addressof(pool->get(entity)) : nullptr;
208 } else {
209 return std::make_tuple(try_get<Type>(entity)...);
210 }
211 }
212
213 template<typename... Type>
214 [[nodiscard]] auto try_get([[maybe_unused]] const entity_type entity) -> decltype(auto) {
215 if constexpr (sizeof...(Type) == 1u) {
216 return (const_cast<Type*>(std::as_const(*this).template try_get<Type>(entity)), ...);
217 } else {
218 return std::make_tuple(try_get<Type>(entity)...);
219 }
220 }
221
222 template<typename... Type>
223 auto clear() -> void {
224 if constexpr (sizeof...(Type) == 0u) {
225 for (auto position = _pools.size(); position; --position) {
226 _pools.begin()[static_cast<typename pool_container_type::difference_type>(position - 1u)].second->clear();
227 }
228
229 const auto element = _entities.each();
230 _entities.erase(element.begin().base(), element.end().base());
231 } else {
232 (_assure<Type>().clear(), ...);
233 }
234 }
235
236 template<typename Type, typename... Other, typename... Exclude>
237 [[nodiscard]] auto view(exclude_t<Exclude...> = exclude_t{}) const -> basic_view<get_t<storage_for_type<const Type>, storage_for_type<const Other>...>, exclude_t<storage_for_type<const Exclude>...>> {
238 auto view = basic_view<get_t<storage_for_type<const Type>, storage_for_type<const Other>...>, exclude_t<storage_for_type<const Exclude>...>>{};
239 [&view](const auto* ...current) { ((current ? view.set_storage(*current) : void()), ...); }(_assure<std::remove_const_t<Exclude>>()..., _assure<std::remove_const_t<Other>>()..., _assure<std::remove_const_t<Type>>());
240 return view;
241 }
242
243 template<typename Type, typename... Other, typename... Exclude>
244 [[nodiscard]] auto view(exclude_t<Exclude...> = exclude_t{}) -> basic_view<get_t<storage_for_type<Type>, storage_for_type<Other>...>, exclude_t<storage_for_type<Exclude>...>> {
245 return basic_view<get_t<storage_for_type<Type>, storage_for_type<Other>...>, exclude_t<storage_for_type<Exclude>...>>{_assure<std::remove_const_t<Type>>(), _assure<std::remove_const_t<Other>>()..., _assure<std::remove_const_t<Exclude>>()...};
246 }
247
248 template<typename Type, typename Compare, typename Sort = utility::std_sort, typename... Args>
249 auto sort(Compare compare, Sort sort = Sort{}, Args&&... args) -> void {
250 // utility::assert_that(!owned<Type>(), "Cannot sort owned storage");
251 auto& pool = _assure<Type>();
252
253 if constexpr(std::is_invocable_v<Compare, decltype(pool.get(std::declval<entity_type>())), decltype(pool.get(std::declval<entity_type>()))>) {
254 auto component_compare = [&pool, compare = std::move(compare)](const auto lhs, const auto rhs) { return compare(std::as_const(pool.get(lhs)), std::as_const(pool.get(rhs))); };
255 pool.sort(std::move(component_compare), std::move(sort), std::forward<Args>(args)...);
256 } else {
257 pool.sort(std::move(compare), std::move(sort), std::forward<Args>(args)...);
258 }
259 }
260
261 [[nodiscard]] auto storage() noexcept -> iterable {
262 return iterable{_pools.begin(), _pools.end()};
263 }
264
265 [[nodiscard]] auto storage() const noexcept -> const_iterable {
266 return const_iterable{_pools.cbegin(), _pools.cend()};
267 }
268
269 [[nodiscard]] auto begin() const -> decltype(auto) {
270 return _entities.begin();
271 }
272
273 [[nodiscard]] auto end() const -> decltype(auto) {
274 return _entities.end();
275 }
276
277 template<typename Callable>
278 auto invoke(const utility::hashed_string& tag, Callable&& callable) -> void {
279 for (const auto entity : _entities | ranges::views::filter(std::forward<Callable>(callable))) {
280 for (auto&& [type, storage] : storage()) {
281 storage.invoke(tag, entity);
282 }
283 }
284 }
285
286private:
287
288 template<typename Type>
289 requires (std::is_same_v<Type, std::decay_t<Type>>)
290 [[nodiscard]] auto _assure([[maybe_unused]] const std::uint32_t id = type_id<Type>::value()) -> storage_for_type<Type>& {
291 if constexpr(std::is_same_v<Type, entity_type>) {
292 utility::assert_that(id == type_id<Type>::value(), "User entity storage not allowed");
293 return _entities;
294 } else {
295 using storage_type = storage_for_type<Type>;
296
297 if (auto iterator = _pools.find(id); iterator != _pools.cend()) {
298 return static_cast<storage_type&>(*iterator->second);
299 }
300
301 using storage_allocator_type = typename storage_type::allocator_type;
302 using pool_type = typename pool_container_type::mapped_type;
303
304 auto pool = pool_type{};
305
306 if constexpr (std::is_void_v<Type> && !std::is_constructible_v<storage_allocator_type, allocator_type>) {
307 pool = std::allocate_shared<storage_type>(get_allocator(), storage_allocator_type{});
308 } else {
309 pool = std::allocate_shared<storage_type>(get_allocator(), get_allocator());
310 }
311
312 _pools.emplace(id, pool);
313
314 return static_cast<storage_type&>(*pool);
315 }
316 }
317
318 template<typename Type>
319 requires (std::is_same_v<Type, std::decay_t<Type>>)
320 [[nodiscard]] auto _assure([[maybe_unused]] const std::uint32_t id = type_id<Type>::value()) const -> const storage_for_type<Type>* {
321 if constexpr(std::is_same_v<Type, entity_type>) {
322 utility::assert_that(id == type_id<Type>::value(), "User entity storage not allowed");
323 return &_entities;
324 } else {
325 if (const auto iterator = _pools.find(id); iterator != _pools.cend()) {
326 return static_cast<const storage_for_type<Type>*>(iterator->second.get());
327 }
328
329 return static_cast<const storage_for_type<Type>*>(nullptr);
330 }
331 }
332
333 pool_container_type _pools;
334 storage_for_type<entity_type> _entities;
335
336}; // class basic_registry
337
339
340} // namespace sbx::ecs
341
342#endif // LIBSBX_REGISTRY_HPP_
Definition: dense_map.hpp:233
Definition: registry.hpp:68
Definition: storage.hpp:27
Definition: view.hpp:318
Definition: iterable_adaptor.hpp:11
Definition: hashed_string.hpp:17
Sparse set container for ECS entity storage.
Definition: registry.hpp:39
Entity traits.
Definition: entity.hpp:73
Definition: view.hpp:310
Definition: view.hpp:302
Definition: registry.hpp:60
Definition: registry.hpp:52
A scoped type ID generator. Allows for generating unique IDs for types within a specific scope.
Definition: type_id.hpp:31
Definition: algorithm.hpp:13