stellarlib 0.1.0
Loading...
Searching...
No Matches
world.hpp
1/* clang-format off */
2
3/*
4 stellarlib
5 Copyright (C) 2025-2026 Domán Zana
6
7 This software is provided 'as-is', without any express or implied
8 warranty. In no event will the authors be held liable for any damages
9 arising from the use of this software.
10
11 Permission is granted to anyone to use this software for any purpose,
12 including commercial applications, and to alter it and redistribute it
13 freely, subject to the following restrictions:
14
15 1. The origin of this software must not be misrepresented; you must not
16 claim that you wrote the original software. If you use this software
17 in a product, an acknowledgment in the product documentation would be
18 appreciated but is not required.
19 2. Altered source versions must be plainly marked as such, and must not be
20 misrepresented as being the original software.
21 3. This notice may not be removed or altered from any source distribution.
22*/
23
24#ifndef STELLARLIB_ECS_WORLD_HPP
25#define STELLARLIB_ECS_WORLD_HPP
26
27#include <stellarlib/ecs/archetype.hpp>
28#include <stellarlib/ecs/command_queue.hpp>
29#include <stellarlib/ecs/query.hpp>
30#include <stellarlib/ecs/sparse_map.hpp>
31#include <stellarlib/ecs/sparse_set.hpp>
32#include <stellarlib/ecs/sparse_storage.hpp>
33#include <stellarlib/ecs/stack_vector.hpp>
34#include <stellarlib/ext/type_traits.hpp>
35#include <stellarlib/ext/utility.hpp>
36
37#include <algorithm>
38#include <cstddef>
39#include <cstdint>
40#include <expected>
41#include <functional>
42#include <limits>
43#include <ranges>
44#include <tuple>
45#include <type_traits>
46#include <utility>
47
48/**
49 * @brief Entity component system
50 */
51namespace stellarlib::ecs
52{
53/**
54 * @brief Stores and exposes operations on entities and components
55 */
56class world final
57{
58public:
59 /**
60 * @brief Default constructor
61 */
62 [[nodiscard]]
63 world() noexcept;
64
65 /**
66 * @brief Deleted copy constructor
67 */
68 [[nodiscard]]
69 constexpr world(const world &) noexcept = delete;
70
71 /**
72 * @brief Move constructor
73 * @param other Other instance
74 */
75 [[nodiscard]]
76 world(world &&other) noexcept;
77
78 /**
79 * @brief Deleted copy assignment operator
80 */
81 constexpr auto operator=(const world &) noexcept
82 -> world & = delete;
83
84 /**
85 * @brief Move assignment operator
86 * @param other Other instance
87 * @return Current instance
88 */
89 auto operator=(world &&other) noexcept
90 -> world &;
91
92 /**
93 * @brief Destructor
94 */
95 ~world() noexcept;
96
97 /**
98 * @brief Spawns an entity with the provided components
99 * @tparam T Component types
100 * @param components Component instances
101 * @return ID of the spawned entity
102 * @note This action is deferred during iterations
103 */
104 template <typename ...T>
105 constexpr auto spawn(T &&...components) noexcept
106 {
107 auto entity{_entities.size() + _spawning.size()};
108
109 if (_despawned.size()) {
110 entity = *(_despawned.end() - 1);
111 _despawned.pop();
112 }
113
114 if (_lock) {
115 if (const auto pair{_entities.at(entity)}) {
116 pair->second = false;
117 }
118 else {
119 _spawning.insert(entity);
120 }
121 }
122
123 const auto command{[this] (const auto entity, auto &&...components) noexcept -> void {
124 [this] <std::size_t ...I> (const auto entity, T &&...components, const std::index_sequence<I...>) noexcept -> void {
125 const auto &ids{internal::sparse_storage::ids<T...>()};
126 (_components.at<T>(ids[I]).insert(entity, std::forward<T>(components)), ...);
127 }(entity, std::forward<T>(components)..., std::index_sequence_for<T...>{});
128
129 relocate<const archetype &>(entity, archetype::of<T...>());
130 }};
131
132 if (_lock) {
133 _commands.enqueue([cpt = std::pair{command, std::tuple{entity, std::forward<T>(components)...}}] mutable noexcept -> void {
134 std::apply(cpt.first, std::move(cpt.second));
135 });
136 }
137 else {
138 command(entity, std::forward<T>(components)...);
139 }
140
141 return entity;
142 }
143
144 /**
145 * @brief Adds the provided components to an entity
146 * @tparam T Components types
147 * @param entity ID of the entity (can be invalid)
148 * @param components Component instances
149 * @return Void, or a tuple of the provided component instances if the entity ID is invalid
150 * @note This action is deferred during iterations
151 */
152 template <typename ...T>
153 [[nodiscard]]
154 constexpr auto insert(const std::uint32_t entity, T &&...components) noexcept
155 -> std::expected<void, std::tuple<T...>>
156 requires (static_cast<bool>(sizeof...(T)))
157 {
158 if (const auto pair{_entities.at(entity)}; (!pair || pair->second) && !spawning(entity)) {
159 return std::unexpected{std::tuple{std::forward<T>(components)...}};
160 }
161
162 const auto command{[this] (const auto entity, auto &&...components) noexcept -> void {
163 [this] <std::size_t ...I> (const auto entity, T &&...components, const std::index_sequence<I...>) noexcept -> void {
164 const auto &ids{internal::sparse_storage::ids<T...>()};
165 (_components.at<T>(ids[I]).insert(entity, std::forward<T>(components)), ...);
166 }(entity, std::forward<T>(components)..., std::index_sequence_for<T...>{});
167
168 cache = (*this)[entity];
169
170 if constexpr (1 < sizeof...(T)) {
171 cache.insert(archetype::of<T...>());
172 }
173 else {
174 cache.insert(internal::sparse_storage::ids<T...>().front());
175 }
176
177 relocate(entity, _entities[entity].first);
178 }};
179
180 if (_lock) {
181 _commands.enqueue([cpt = std::pair{command, std::tuple{entity, std::forward<T>(components)...}}] mutable noexcept -> void {
182 std::apply(cpt.first, std::move(cpt.second));
183 });
184 }
185 else {
186 command(entity, std::forward<T>(components)...);
187 }
188
189 return {};
190 }
191
192 /**
193 * @brief Returns the number of entities
194 * @return Number of entities
195 */
196 [[nodiscard]]
197 auto size() const noexcept
198 -> std::size_t;
199
200 /**
201 * @brief Evaluates whether an entity will be spawned
202 * @param entity ID of the entity
203 * @return Whether the entity will be spawned
204 */
205 [[nodiscard]]
206 auto spawning(std::uint32_t entity) const noexcept
207 -> bool;
208
209 /**
210 * @brief Evaluates whether an entity exists
211 * @param entity ID of the entity
212 * @return Whether the entity exists
213 */
214 [[nodiscard]]
215 auto contains(std::uint32_t entity) const noexcept
216 -> bool;
217
218 /**
219 * @brief Evaluates whether an entity will be despawned
220 * @param entity ID of the entity
221 * @return Whether the entity will be despawned
222 */
223 [[nodiscard]]
224 auto despawning(std::uint32_t entity) const noexcept
225 -> bool;
226
227 /**
228 * @brief Evaluates whether an entity has the specified components
229 * @tparam T Component types
230 * @param entity ID of the entity (can be invalid)
231 * @return Tuple of bools indicating the result for each component type
232 */
233 template <typename ...T>
234 [[nodiscard]]
235 constexpr auto contains(const std::uint32_t entity) const noexcept
236 requires (static_cast<bool>(sizeof...(T)))
237 {
238 if (const auto pair{_entities.at(entity)}) {
239 return [] <std::size_t ...I> [[nodiscard]] (const auto &archetype, const std::index_sequence<I...>) noexcept -> auto {
240 const auto &ids{internal::sparse_storage::ids<T...>()};
241 return std::tuple{archetype.contains(ids[I])...};
242 }(_archetypes[pair->first].first, std::index_sequence_for<T...>{});
243 }
244
245 return std::tuple{ext::expand_as_v<T, false>...};
246 }
247
248 /**
249 * @brief Returns a pointer to the archetype of an entity
250 * @param entity ID of the entity (can be invalid)
251 * @return Pointer to the archetype of the entity, or nullptr if the entity ID is invalid
252 */
253 [[nodiscard]]
254 auto at(std::uint32_t entity) const noexcept
255 -> const archetype *;
256
257 /**
258 * @brief Returns a tuple of pointers to the specified components of an entity
259 * @tparam T Component types
260 * @param entity ID of the entity (can be invalid)
261 * @return Tuple of pointers to the specified components of the entity, invalid components are indicated with nullptrs
262 */
263 template <typename ...T>
264 [[nodiscard]]
265 constexpr auto at(const std::uint32_t entity) const noexcept
266 requires (static_cast<bool>(sizeof...(T)))
267 {
268 return [this] <std::size_t ...I> [[nodiscard]] (const auto entity, const std::index_sequence<I...>) noexcept -> auto {
269 const auto &ids{internal::sparse_storage::ids<T...>()};
270 return std::tuple{_components.at<T>(ids[I]).at(entity)...};
271 }(entity, std::index_sequence_for<T...>{});
272 }
273
274 /**
275 * @brief Returns a tuple of pointers to the specified components of an entity
276 * @tparam T Component types
277 * @param entity ID of the entity (can be invalid)
278 * @return Tuple of pointers to the specified components of the entity, invalid components are indicated with nullptrs
279 */
280 template <typename ...T>
281 [[nodiscard]]
282 constexpr auto at(const std::uint32_t entity) noexcept
283 requires (static_cast<bool>(sizeof...(T)))
284 {
285 return [this] <std::size_t ...I> [[nodiscard]] (const auto entity, const std::index_sequence<I...>) noexcept -> auto {
286 const auto &ids{internal::sparse_storage::ids<T...>()};
287 return std::tuple{_components.at<T>(ids[I]).at(entity)...};
288 }(entity, std::index_sequence_for<T...>{});
289 }
290
291 /**
292 * @brief Returns a reference to the archetype of an entity
293 * @param entity ID of the entity
294 * @return Reference to the archetype of the entity
295 * @warning Invalid entity ID can lead to undefined behavior
296 */
297 [[nodiscard]]
298 auto operator[](std::uint32_t entity) const noexcept
299 -> const archetype &;
300
301 /**
302 * @brief Returns a tuple of references to the specified components of an entity
303 * @tparam T Component types
304 * @param entity ID of the entity
305 * @return Tuple of references to the specified components of the entity
306 * @warning Invalid entity ID or component types can lead to undefined behavior
307 */
308 template <typename ...T>
309 [[nodiscard]]
310 constexpr auto operator[](const std::uint32_t entity) const noexcept
311 requires (static_cast<bool>(sizeof...(T)))
312 {
313 return [this] <std::size_t ...I> [[nodiscard]] (const auto entity, const std::index_sequence<I...>) noexcept -> auto {
314 const auto &ids{internal::sparse_storage::ids<T...>()};
315 return std::tuple<const T &...>{_components.operator[]<T>(ids[I])[entity]...};
316 }(entity, std::index_sequence_for<T...>{});
317 }
318
319 /**
320 * @brief Returns a tuple of references to the specified components of an entity
321 * @tparam T Component types
322 * @param entity ID of the entity
323 * @return Tuple of references to the specified components of the entity
324 * @warning Invalid entity ID or component types can lead to undefined behavior
325 */
326 template <typename ...T>
327 [[nodiscard]]
328 constexpr auto operator[](const std::uint32_t entity) noexcept
329 requires (static_cast<bool>(sizeof...(T)))
330 {
331 return [this] <std::size_t ...I> [[nodiscard]] (const auto entity, const std::index_sequence<I...>) noexcept -> auto {
332 const auto &ids{internal::sparse_storage::ids<T...>()};
333 return std::tuple<T &...>{_components.operator[]<T>(ids[I])[entity]...};
334 }(entity, std::index_sequence_for<T...>{});
335 }
336
337 /**
338 * @brief Returns a view to all entities with their archetypes
339 * @return View to all entities with their archetypes
340 */
341 [[nodiscard]]
342 constexpr auto query() noexcept
343 {
344 ++_lock;
345 return internal::query{_archetypes | std::views::transform([] [[nodiscard]] (const auto &pair) noexcept -> auto {
346 return pair.second | std::views::transform([&] [[nodiscard]] (const auto entity) noexcept -> auto {
347 return std::tuple<std::uint32_t, const archetype &>{entity, pair.first};
348 });
349 }) | std::views::join, _execute};
350 }
351
352 /**
353 * @brief Returns a view to entities with the specified components
354 * @tparam T Component types
355 * @return View to entities with the specified components
356 */
357 template <typename T>
358 [[nodiscard]]
359 constexpr auto query() noexcept
360 {
361 ++_lock;
362 return internal::query{_components.at<T>(internal::sparse_storage::ids<T>().front()).zip(), _execute};
363 }
364
365 /**
366 * @brief Returns a view to entities with the specified components
367 * @tparam T Component types
368 * @return View to entities with the specified components
369 */
370 template <typename ...T>
371 [[nodiscard]]
372 constexpr auto query() noexcept
373 requires (1 < sizeof...(T))
374 {
375 const auto id{ext::scoped_typeid<world, std::tuple<T...>, std::uint16_t>()};
376
377 if (_queries.extend(id + 1, std::numeric_limits<std::uint16_t>::max()) || _queries[id] == std::numeric_limits<std::uint16_t>::max()) {
378 const auto &archetype{archetype::of<T...>()};
379
380 const auto pair{std::ranges::find_if(_indices, [&] [[nodiscard]] (const auto &pair) noexcept -> auto {
381 return pair.first == archetype;
382 })};
383
384 if (pair == _indices.end()) {
385 _queries[id] = _indices.size();
386 _indices.push(archetype, internal::stack_vector<std::uint16_t>{});
387
388 for (const auto index : std::views::iota(std::uint16_t{}, _archetypes.size())) {
389 if (archetype <= _archetypes[index].first) {
390 (_indices.end() - 1)->second.push(index);
391 }
392 }
393 }
394 else {
395 _queries[id] = static_cast<std::uint16_t>(pair - _indices.begin());
396 }
397 }
398
399 ++_lock;
400 return internal::query{std::ranges::subrange{_indices[_queries[id]].second} | std::views::transform([this] [[nodiscard]] (const auto index) noexcept -> const auto & {
401 return _archetypes[index].second;
402 }) | std::views::join | std::views::transform([components = [this] <std::size_t ...I> [[nodiscard]] (const std::index_sequence<I...>) noexcept -> auto {
403 const auto &ids{internal::sparse_storage::ids<T...>()};
404 return std::tuple<internal::sparse_map<std::uint32_t, T> &...>{_components.operator[]<T>(ids[I])...};
405 }(std::index_sequence_for<T...>{})] [[nodiscard]] (const auto entity) noexcept -> auto {
406 return [] <std::size_t ...I> [[nodiscard]] (const auto entity, const auto &components, const std::index_sequence<I...>) noexcept -> auto {
407 return std::tuple<std::uint32_t, T &...>{entity, std::get<I>(components)[entity]...};
408 }(entity, components, std::index_sequence_for<T...>{});
409 }), _execute};
410 }
411
412 /**
413 * @brief Removes the specified components from an entity
414 * @tparam T Components types
415 * @param entity ID of the entity (can be invalid)
416 * @note This action is deferred during iterations
417 */
418 template <typename ...T>
419 constexpr void erase(const std::uint32_t entity) noexcept
420 requires (static_cast<bool>(sizeof...(T)))
421 {
422 if (const auto pair{_entities.at(entity)}; (!pair || pair->second) && !spawning(entity)) {
423 return;
424 }
425
426 const auto command{[this] (const auto entity) noexcept -> void {
427 [this] <std::size_t ...I> (const auto entity, const std::index_sequence<I...>) noexcept -> void {
428 const auto &ids{internal::sparse_storage::ids<T...>()};
429 (_components.at<T>(ids[I]).erase(entity), ...);
430 }(entity, std::index_sequence_for<T...>{});
431
432 cache = (*this)[entity];
433
434 if constexpr (1 < sizeof...(T)) {
435 cache.erase(archetype::of<T...>());
436 }
437 else {
438 cache.erase(internal::sparse_storage::ids<T...>().front());
439 }
440
441 relocate(entity, _entities[entity].first);
442 }};
443
444 if (_lock) {
445 _commands.enqueue([cpt = std::pair{command, entity}] noexcept -> void {
446 cpt.first(cpt.second);
447 });
448 }
449 else {
450 command(entity);
451 }
452 }
453
454 /**
455 * @brief Despawns an entity
456 * @param entity ID of the entity (can be invalid)
457 * @note This action is deferred during iterations
458 */
459 void despawn(std::uint32_t entity) noexcept;
460
461 /**
462 * @brief Despawns all entities
463 * @note This action is deferred during iterations
464 */
465 void clear() noexcept;
466
467private:
468 static thread_local archetype cache;
469 internal::sparse_storage _components;
470 internal::sparse_set _spawning;
471 internal::stack_vector<std::uint32_t, std::uint32_t> _despawned;
472 internal::sparse_map<std::uint32_t, std::pair<std::uint16_t, bool>> _entities;
473 internal::stack_vector<std::pair<archetype, internal::sparse_set>, std::uint16_t> _archetypes;
474 internal::stack_vector<std::uint16_t, std::uint16_t> _queries;
475 internal::stack_vector<std::pair<archetype, internal::stack_vector<std::uint16_t>>, std::uint16_t> _indices;
476 std::size_t _lock{};
477 internal::command_queue _commands;
478
479 std::function<void ()> _execute{[this] noexcept -> void {
480 if (!--_lock) {
481 _commands.execute();
482 _spawning.clear();
483 }
484 }};
485
486 template <typename T>
487 constexpr void relocate(const std::uint32_t entity, T arg) noexcept
488 {
489 if constexpr (std::is_same_v<T, std::uint16_t>) {
490 _archetypes[arg].second.erase(entity);
491 }
492
493 const auto pair{std::ranges::find_if(_archetypes, [&] [[nodiscard]] (const auto &pair) noexcept -> auto {
494 if constexpr (std::is_same_v<T, const archetype &>) {
495 return pair.first == arg;
496 }
497 else if constexpr (std::is_same_v<T, std::uint16_t>) {
498 return pair.first == cache;
499 }
500 })};
501
502 if (pair == _archetypes.end()) {
503 if constexpr (std::is_same_v<T, const archetype &>) {
504 _entities.insert(entity, _archetypes.size(), false);
505 _archetypes.push(arg, internal::sparse_set{});
506 }
507 else if constexpr (std::is_same_v<T, std::uint16_t>) {
508 _entities[entity].first = _archetypes.size();
509 _archetypes.push(cache, internal::sparse_set{});
510 }
511
512 (_archetypes.end() - 1)->second.insert(entity);
513
514 for (auto &pair : _indices) {
515 if (pair.first <= (_archetypes.end() - 1)->first) {
516 pair.second.push(_archetypes.size() - 1);
517 }
518 }
519 }
520 else {
521 if constexpr (std::is_same_v<T, const archetype &>) {
522 _entities.insert(entity, pair - _archetypes.begin(), false);
523 }
524 else if constexpr (std::is_same_v<T, std::uint16_t>) {
525 _entities[entity].first = static_cast<std::uint16_t>(pair - _archetypes.begin());
526 }
527
528 pair->second.insert(entity);
529 }
530 }
531};
532}
533
534#endif
Represents the archetype of an entity.
Definition archetype.hpp:42
static constexpr auto of() noexcept -> std::conditional_t< static_cast< bool >(sizeof...(T)), const archetype &, archetype >
Retrieves an archetype for a set of component types.
Definition archetype.hpp:51
auto contains(std::uintmax_t id) const noexcept -> bool
Evaluates whether the archetype contains a component ID.
auto at(std::uint32_t entity) const noexcept -> const archetype *
Returns a pointer to the archetype of an entity.
auto despawning(std::uint32_t entity) const noexcept -> bool
Evaluates whether an entity will be despawned.
auto spawning(std::uint32_t entity) const noexcept -> bool
Evaluates whether an entity will be spawned.
auto operator[](std::uint32_t entity) const noexcept -> const archetype &
Returns a reference to the archetype of an entity.
constexpr auto query() noexcept
Returns a view to entities with the specified components.
Definition world.hpp:372
constexpr auto insert(const std::uint32_t entity, T &&...components) noexcept -> std::expected< void, std::tuple< T... > >
Adds the provided components to an entity.
Definition world.hpp:154
world() noexcept
Default constructor.
constexpr auto spawn(T &&...components) noexcept
Spawns an entity with the provided components.
Definition world.hpp:105
constexpr auto at(const std::uint32_t entity) const noexcept
Returns a tuple of pointers to the specified components of an entity.
Definition world.hpp:265
constexpr auto at(const std::uint32_t entity) noexcept
Returns a tuple of pointers to the specified components of an entity.
Definition world.hpp:282
constexpr auto query() noexcept
Returns a view to entities with the specified components.
Definition world.hpp:359
constexpr auto operator[](const std::uint32_t entity) noexcept
Returns a tuple of references to the specified components of an entity.
Definition world.hpp:328
auto contains(std::uint32_t entity) const noexcept -> bool
Evaluates whether an entity exists.
constexpr auto query() noexcept
Returns a view to all entities with their archetypes.
Definition world.hpp:342
constexpr auto operator[](const std::uint32_t entity) const noexcept
Returns a tuple of references to the specified components of an entity.
Definition world.hpp:310
void clear() noexcept
Despawns all entities.
void despawn(std::uint32_t entity) noexcept
Despawns an entity.
auto size() const noexcept -> std::size_t
Returns the number of entities.
constexpr void erase(const std::uint32_t entity) noexcept
Removes the specified components from an entity.
Definition world.hpp:419
Entity component system.
Definition any_set.hpp:28
constexpr auto scoped_typeid() noexcept
Generates a unique ID per type within a scope.
Definition utility.hpp:59