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... Args>
158 auto emplace_or_replace(const entity_type entity, Args&&... args) -> decltype(auto) {
159 auto& pool = _assure<Type>();
160
161 utility::assert_that(is_valid(entity), "Invalid entity");
162
163 return pool.contains(entity) ? pool.patch(entity, [&args...](auto&... current) { ((current = Type{std::forward<Args>(args)...}), ...); }) : pool.emplace(entity, std::forward<Args>(args)...);
164 }
165
166 template<typename Type, typename... Other>
167 auto remove(const entity_type entity) -> size_type {
168 return (_assure<Type>().remove(entity) + ... + _assure<Other>().remove(entity));
169 }
170
171 template<typename... Type>
172 [[nodiscard]] auto all_of([[maybe_unused]] const entity_type entity) const -> bool {
173 if constexpr(sizeof...(Type) == 1u) {
174 auto* pool = _assure<std::remove_const_t<Type>...>();
175 return pool && pool->contains(entity);
176 } else {
177 return (all_of<Type>(entity) && ...);
178 }
179 }
180
181 template<typename... Type>
182 [[nodiscard]] auto any_of([[maybe_unused]] const entity_type entity) const -> bool {
183 return (all_of<Type>(entity) || ...);
184 }
185
186 template<typename... Type>
187 [[nodiscard]] auto get([[maybe_unused]] const entity_type entity) const -> decltype(auto) {
188 if constexpr (sizeof...(Type) == 1u) {
189 return (_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>
196 [[nodiscard]] auto get([[maybe_unused]] const entity_type entity) -> decltype(auto) {
197 if constexpr (sizeof...(Type) == 1u) {
198 return (static_cast<storage_for_type<Type>&>(_assure<std::remove_const_t<Type>>()).get(entity), ...);
199 } else {
200 return std::forward_as_tuple(get<Type>(entity)...);
201 }
202 }
203
204 template<typename Type, typename... Args>
205 requires (std::is_constructible_v<Type, Args...>)
206 [[nodiscard]] auto get_or_emplace(const entity_type entity, Args&&... args) -> decltype(auto) {
207 auto& pool = _assure<Type>();
208 utility::assert_that(is_valid(entity), "Invalid entity");
209 return pool.contains(entity) ? pool.get(entity) : pool.emplace(entity, std::forward<Args>(args)...);
210 }
211
212 template<typename... Type>
213 [[nodiscard]] auto try_get([[maybe_unused]] const entity_type entity) const -> decltype(auto) {
214 if constexpr (sizeof...(Type) == 1u) {
215 const auto* pool = _assure<std::remove_const_t<Type>...>();
216 return (pool && pool->contains(entity)) ? std::addressof(pool->get(entity)) : nullptr;
217 } else {
218 return std::make_tuple(try_get<Type>(entity)...);
219 }
220 }
221
222 template<typename... Type>
223 [[nodiscard]] auto try_get([[maybe_unused]] const entity_type entity) -> decltype(auto) {
224 if constexpr (sizeof...(Type) == 1u) {
225 return (const_cast<Type*>(std::as_const(*this).template try_get<Type>(entity)), ...);
226 } else {
227 return std::make_tuple(try_get<Type>(entity)...);
228 }
229 }
230
231 template<typename... Type>
232 auto clear() -> void {
233 if constexpr (sizeof...(Type) == 0u) {
234 for (auto position = _pools.size(); position; --position) {
235 _pools.begin()[static_cast<typename pool_container_type::difference_type>(position - 1u)].second->clear();
236 }
237
238 const auto element = _entities.each();
239 _entities.erase(element.begin().base(), element.end().base());
240 } else {
241 (_assure<Type>().clear(), ...);
242 }
243 }
244
245 template<typename Type, typename... Other, typename... Exclude>
246 [[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>...>> {
247 auto view = basic_view<get_t<storage_for_type<const Type>, storage_for_type<const Other>...>, exclude_t<storage_for_type<const Exclude>...>>{};
248 [&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>>());
249 return view;
250 }
251
252 template<typename Type, typename... Other, typename... Exclude>
253 [[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>...>> {
254 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>>()...};
255 }
256
257 template<typename Type, typename Compare, typename Sort = utility::std_sort, typename... Args>
258 auto sort(Compare compare, Sort sort = Sort{}, Args&&... args) -> void {
259 // utility::assert_that(!owned<Type>(), "Cannot sort owned storage");
260 auto& pool = _assure<Type>();
261
262 if constexpr(std::is_invocable_v<Compare, decltype(pool.get(std::declval<entity_type>())), decltype(pool.get(std::declval<entity_type>()))>) {
263 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))); };
264 pool.sort(std::move(component_compare), std::move(sort), std::forward<Args>(args)...);
265 } else {
266 pool.sort(std::move(compare), std::move(sort), std::forward<Args>(args)...);
267 }
268 }
269
270 [[nodiscard]] auto storage() noexcept -> iterable {
271 return iterable{_pools.begin(), _pools.end()};
272 }
273
274 [[nodiscard]] auto storage() const noexcept -> const_iterable {
275 return const_iterable{_pools.cbegin(), _pools.cend()};
276 }
277
278 [[nodiscard]] auto begin() const -> decltype(auto) {
279 return _entities.begin();
280 }
281
282 [[nodiscard]] auto end() const -> decltype(auto) {
283 return _entities.end();
284 }
285
286 template<typename Callable>
287 auto invoke(const utility::hashed_string& tag, Callable&& callable) -> void {
288 for (const auto entity : _entities | ranges::views::filter(std::forward<Callable>(callable))) {
289 for (auto&& [type, storage] : storage()) {
290 storage.invoke(tag, entity);
291 }
292 }
293 }
294
295private:
296
297 template<typename Type>
298 requires (std::is_same_v<Type, std::decay_t<Type>>)
299 [[nodiscard]] auto _assure([[maybe_unused]] const std::uint32_t id = type_id<Type>::value()) -> storage_for_type<Type>& {
300 if constexpr(std::is_same_v<Type, entity_type>) {
301 utility::assert_that(id == type_id<Type>::value(), "User entity storage not allowed");
302 return _entities;
303 } else {
304 using storage_type = storage_for_type<Type>;
305
306 if (auto iterator = _pools.find(id); iterator != _pools.cend()) {
307 return static_cast<storage_type&>(*iterator->second);
308 }
309
310 using storage_allocator_type = typename storage_type::allocator_type;
311 using pool_type = typename pool_container_type::mapped_type;
312
313 auto pool = pool_type{};
314
315 if constexpr (std::is_void_v<Type> && !std::is_constructible_v<storage_allocator_type, allocator_type>) {
316 pool = std::allocate_shared<storage_type>(get_allocator(), storage_allocator_type{});
317 } else {
318 pool = std::allocate_shared<storage_type>(get_allocator(), get_allocator());
319 }
320
321 _pools.emplace(id, pool);
322
323 return static_cast<storage_type&>(*pool);
324 }
325 }
326
327 template<typename Type>
328 requires (std::is_same_v<Type, std::decay_t<Type>>)
329 [[nodiscard]] auto _assure([[maybe_unused]] const std::uint32_t id = type_id<Type>::value()) const -> const storage_for_type<Type>* {
330 if constexpr(std::is_same_v<Type, entity_type>) {
331 utility::assert_that(id == type_id<Type>::value(), "User entity storage not allowed");
332 return &_entities;
333 } else {
334 if (const auto iterator = _pools.find(id); iterator != _pools.cend()) {
335 return static_cast<const storage_for_type<Type>*>(iterator->second.get());
336 }
337
338 return static_cast<const storage_for_type<Type>*>(nullptr);
339 }
340 }
341
342 pool_container_type _pools;
343 storage_for_type<entity_type> _entities;
344
345}; // class basic_registry
346
347using registry = basic_registry<entity>;
348
349} // namespace sbx::ecs
350
351#endif // LIBSBX_REGISTRY_HPP_
Definition: dense_map.hpp:233
Definition: registry.hpp:68
Definition: storage.hpp:27
Definition: iterable_adaptor.hpp:11
Sparse set container for ECS entity storage.
Definition: registry.hpp:39
Entity traits.
Definition: entity.hpp:73
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
static auto value() noexcept -> std::uint32_t
Generates a unique ID for the type.
Definition: type_id.hpp:41