co_ecs 0.9.0
Cobalt ECS
Loading...
Searching...
No Matches
archetype.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <co_ecs/chunk.hpp>
6#include <co_ecs/entity.hpp>
8
9#include <bit>
10
11namespace co_ecs {
12
15class archetype {
16public:
18 using chunks_storage_t = std::vector<chunk>;
19
21 archetype() = default;
22
27 _max_size = get_max_size(_components);
28 init_blocks(_components);
29 _chunks.emplace_back(_blocks, _max_size);
30 }
31
35 [[nodiscard]] auto components() const noexcept -> const component_meta_set& {
36 return _components;
37 }
38
42 [[nodiscard]] auto chunks() noexcept -> chunks_storage_t& {
43 return _chunks;
44 }
45
49 [[nodiscard]] auto chunks() const noexcept -> const chunks_storage_t& {
50 return _chunks;
51 }
52
59 template<component... Components>
60 auto emplace(entity ent, Components&&... components) -> entity_location {
61 auto& free_chunk = ensure_free_chunk();
62 auto entry_index = free_chunk.size();
63 auto chunk_index = _chunks.size() - 1;
64 free_chunk.emplace_back(ent, std::forward<Components>(components)...);
65 return entity_location{
66 this,
67 chunk_index,
68 entry_index,
69 };
70 }
71
77 auto swap_erase(const entity_location& location) noexcept -> std::optional<entity> {
78 auto& chunk = get_chunk(location);
79 auto& last_chunk = _chunks.back();
80 auto maybe_ent = chunk.swap_erase(location.entry_index, last_chunk);
81 if (last_chunk.empty() && _chunks.size() > 1) {
82 _chunks.pop_back();
83 }
84 return maybe_ent;
85 }
86
94 auto move(const entity_location& location, archetype& other) -> std::pair<entity_location, std::optional<entity>> {
95 auto& free_chunk = other.ensure_free_chunk();
96 auto& chunk = get_chunk(location);
97
98 assert((location.entry_index < chunk.size()) && "Entity location index exceeds chunk size");
99
100 auto entry_index = chunk.move(location.entry_index, free_chunk);
101 auto moved = swap_erase(location);
102 auto new_location = entity_location{
103 &other,
104 other._chunks.size() - 1,
105 entry_index,
106 };
107
108 return std::make_pair(new_location, moved);
109 }
110
116 auto copy(const entity_location& location, archetype& other) -> entity_location {
117 auto& free_chunk = other.ensure_free_chunk();
118 auto& chunk = get_chunk(location);
119
120 assert((location.entry_index < chunk.size()) && "Entity location index exceeds chunk size");
121
122 auto entry_index = chunk.copy(location.entry_index, free_chunk);
123 auto new_location = entity_location{
124 &other,
125 other._chunks.size() - 1,
126 entry_index,
127 };
128
129 return new_location;
130 }
131
135 void visit(const entity_location& location, auto&& func) {
136 get_chunk(location).visit(location.entry_index, std::forward<decltype(func)>(func));
137 }
138
142 void visit(const entity_location& location, auto&& func) const {
143 get_chunk(location).visit(location.entry_index, std::forward<decltype(func)>(func));
144 }
145
151 template<component C>
152 auto get(entity_location location) -> C& {
153 return read_impl<C&>(*this, location);
154 }
155
161 template<component C>
162 auto get(entity_location location) const -> const C& {
163 return read_impl<const C&>(*this, location);
164 }
165
171 template<component C>
172 [[nodiscard]] auto contains() const noexcept -> bool {
173 return components().contains<C>();
174 }
175
176private:
177 void init_blocks(const component_meta_set& components_meta) {
178 // make space for entity
179 auto offset = add_block(0, component_meta::of<entity>());
180
181 // space for all components
182 for (const auto& meta : components_meta) {
183 offset = add_block(offset, meta);
184 }
185 }
186
187 auto add_block(std::size_t offset, const component_meta& meta) -> std::size_t {
188 const std::size_t size_in_bytes = _max_size * meta.type->size;
189 const std::size_t align = meta.type->align;
190
191 _blocks.emplace(meta.id, offset, meta);
192
193 offset += detail::mod_2n(offset, align) + size_in_bytes;
194
195 return offset;
196 }
197
198 // Calculates the maximum size of individual components this chunk buffer can hold
199 static auto get_max_size(auto&& components_meta) -> std::size_t {
200 // Calculate size of the following structure:
201 //
202 // struct {
203 // component_a_t a;
204 // component_b_t b;
205 // component_c_t c;
206 // ...
207 // };
208 //
209 auto aligned_size = aligned_components_size(components_meta);
210
211 // handle subtraction overflow - chunk size is insufficient to hold at least one such entity
212 if (aligned_size > chunk::chunk_bytes) [[unlikely]] {
213 throw insufficient_chunk_size{ aligned_size, chunk::chunk_bytes };
214 }
215
216 // Remaining size for packed components
217 auto remaining_space = chunk::chunk_bytes - aligned_size;
218
219 // Calculate how much components we can pack into remaining space
220 auto remaining_elements_count = remaining_space / packed_components_size(components_meta);
221
222 // The maximum amount of entities we can hold is grater by 1 for which we calculated aligned_size
223 return remaining_elements_count + 1;
224 }
225
226 // Calculate size of packed structure of components
227 static auto packed_components_size(auto&& components_meta) noexcept -> std::size_t {
228 return std::accumulate(components_meta.begin(),
229 components_meta.end(),
230 component_meta::of<entity>().type->size,
231 [](const auto& res, const auto& meta) { return res + meta.type->size; });
232 }
233
234 // Calculate size of properly aligned structure of components
235 static auto aligned_components_size(auto&& components_meta) noexcept -> std::size_t {
236 auto begin = chunk::alloc_alignment;
237 auto end = begin;
238
239 // Add single component element size accounting for its alignment
240 auto add_elements = [&end](const component_meta& meta) {
241 end += detail::mod_2n(std::bit_cast<std::size_t>(end), meta.type->align);
242 end += meta.type->size;
243 };
244
245 add_elements(component_meta::of<entity>());
246 for (const auto& meta : components_meta) {
247 add_elements(meta);
248 }
249
250 return end - begin;
251 }
252
253 template<component_reference ComponentRef>
254 static auto read_impl(auto&& self, entity_location location) -> ComponentRef {
255 auto& chunk = self.get_chunk(location);
256 assert((location.entry_index < chunk.size()) && "Entity location index exceeds chunk size");
257 return *component_fetch::fetch_pointer<ComponentRef>(chunk, location.entry_index);
258 }
259
260 static auto get_chunk_impl(auto&& self, entity_location location) noexcept -> decltype(auto) {
261 assert((location.archetype == std::addressof(self))
262 && "Location archetype pointer does not point to this archetype");
263 assert((location.chunk_index < self._chunks.size()) && "Location chunk index exceeds the chunks vector size");
264 return self._chunks[location.chunk_index];
265 }
266
267 auto get_chunk(entity_location location) noexcept -> chunk& {
268 return get_chunk_impl(*this, location);
269 }
270
271 auto get_chunk(entity_location location) const noexcept -> const chunk& {
272 return get_chunk_impl(*this, location);
273 }
274
275 auto ensure_free_chunk() -> chunk& {
276 auto& chunk = _chunks.back();
277 if (!chunk.full()) {
278 return chunk;
279 }
280 _chunks.emplace_back(_blocks, _max_size);
281 return _chunks.back();
282 }
283
284private:
285 std::size_t _max_size{};
286 blocks_type _blocks{};
287 component_meta_set _components{};
288 chunks_storage_t _chunks{};
289};
290
293public:
296 using iterator = storage_type::iterator;
297 using const_iterator = storage_type::const_iterator;
298 using value_type = storage_type::value_type;
299 using key_type = storage_type::key_type;
300 using mapped_type = storage_type::mapped_type;
301
302public:
306 auto ensure_archetype(const component_meta_set& components) -> archetype* {
307 auto& archetype = _archetypes[components.ids()];
308 if (!archetype) {
309 archetype = create_archetype(components);
310 }
311 assert((archetype->components().ids() == components.ids())
312 && "Archetype components do not match the search request");
313 return archetype.get();
314 }
315
320 template<component... Components>
322 _search_component_set.clear();
323 (..., _search_component_set.insert<Components>());
324
325 auto& archetype = _archetypes[_search_component_set];
326 if (!archetype) {
327 archetype = create_archetype(component_meta_set::create<Components...>());
328 }
329 assert((archetype->components().ids() == _search_component_set)
330 && "Archetype components do not match the search request");
331 return archetype.get();
332 }
333
339 template<component... Components>
340 auto ensure_archetype_added(const archetype* anchor_archetype) -> archetype* {
341 _search_component_set = anchor_archetype->components().ids();
342 (..., _search_component_set.insert<Components>());
343
344 auto& archetype = _archetypes[_search_component_set];
345 if (!archetype) {
346 archetype = create_archetype_added<Components...>(anchor_archetype);
347 }
348 assert((archetype->components().ids() == _search_component_set)
349 && "Archetype components do not match the search request");
350 return archetype.get();
351 }
352
358 template<component... Components>
359 auto ensure_archetype_removed(const archetype* anchor_archetype) -> archetype* {
360 _search_component_set = anchor_archetype->components().ids();
361 (..., _search_component_set.erase<Components>());
362
363 auto& archetype = _archetypes[_search_component_set];
364 if (!archetype) {
365 archetype = create_archetype_removed<Components...>(anchor_archetype);
366 }
367 assert((archetype->components().ids() == _search_component_set)
368 && "Archetype components do not match the search request");
369 return archetype.get();
370 }
371
375 auto begin() noexcept -> iterator {
376 return _archetypes.begin();
377 }
378
382 auto end() noexcept -> iterator {
383 return _archetypes.end();
384 }
385
389 [[nodiscard]] auto begin() const noexcept -> const_iterator {
390 return _archetypes.begin();
391 }
392
396 [[nodiscard]] auto end() const noexcept -> const_iterator {
397 return _archetypes.end();
398 }
399
403 [[nodiscard]] auto size() const noexcept -> std::size_t {
404 return _archetypes.size();
405 }
406
407private:
408 static auto create_archetype(auto&& components) -> std::unique_ptr<archetype> {
409 return std::make_unique<archetype>(std::forward<decltype(components)>(components));
410 }
411
412 template<component... Components>
413 static auto create_archetype_added(const archetype* anchor_archetype) -> std::unique_ptr<archetype> {
414 auto components_meta = anchor_archetype->components();
415 (..., components_meta.insert<Components>());
416 return std::make_unique<archetype>(std::move(components_meta));
417 }
418
419 template<component... Components>
420 static auto create_archetype_removed(const archetype* anchor_archetype) -> std::unique_ptr<archetype> {
421 auto components_meta = anchor_archetype->components();
422 (..., components_meta.erase<Components>());
423 return std::make_unique<archetype>(std::move(components_meta));
424 }
425
426 // Member component set is here to speed up archetype lookup.
427 // If we create component_set every time we need to search for an archetype
428 // that requires memory allocation for underlying data structure of the component_set.
429 // The downside is that we need to NOT FORGET to clean the _search_component_set before use.
430 // The other solution may require a custom allocator but create way more problems when I tried it.
431 // In-ability to simply find() in the hash map due to a type mismatch is one of them.
432 component_set _search_component_set{};
433
434 storage_type _archetypes{};
435};
436
437} // namespace co_ecs
Archetype groups entities that share the same types of components. Archetype has a list of fixed size...
Definition archetype.hpp:15
auto copy(const entity_location &location, archetype &other) -> entity_location
Copy entity and its components to a different archetype.
Definition archetype.hpp:116
std::vector< chunk > chunks_storage_t
Chunks storage type.
Definition archetype.hpp:18
auto contains() const noexcept -> bool
Check if archetype has component C.
Definition archetype.hpp:172
void visit(const entity_location &location, auto &&func) const
Visit all components of an entity (const variant).
Definition archetype.hpp:142
auto get(entity_location location) const -> const C &
Get component data, const variant.
Definition archetype.hpp:162
archetype()=default
Construct a new archetype object without components.
auto emplace(entity ent, Components &&... components) -> entity_location
Emplace new entity and assign given components to it, return entities location.
Definition archetype.hpp:60
auto move(const entity_location &location, archetype &other) -> std::pair< entity_location, std::optional< entity > >
Move entity and its components to a different archetype and returns a pair where the first element is...
Definition archetype.hpp:94
auto components() const noexcept -> const component_meta_set &
Return components set.
Definition archetype.hpp:35
void visit(const entity_location &location, auto &&func)
Visit all components of an entity.
Definition archetype.hpp:135
archetype(component_meta_set components)
Construct a new archetype object.
Definition archetype.hpp:26
auto swap_erase(const entity_location &location) noexcept -> std::optional< entity >
Swap erase an entity at given location, returns an entity that has been moved as a result of this ope...
Definition archetype.hpp:77
auto chunks() noexcept -> chunks_storage_t &
Return reference to chunks vector.
Definition archetype.hpp:42
auto chunks() const noexcept -> const chunks_storage_t &
Return const reference to chunks vector.
Definition archetype.hpp:49
auto get(entity_location location) -> C &
Get component data.
Definition archetype.hpp:152
Container for archetypes, holds a map from component set to archetype.
Definition archetype.hpp:292
storage_type::value_type value_type
Definition archetype.hpp:298
auto ensure_archetype() -> archetype *
Get or create an archetype matching the passed Components types.
Definition archetype.hpp:321
auto begin() const noexcept -> const_iterator
Returns iterator to the beginning of archetypes container.
Definition archetype.hpp:389
storage_type::mapped_type mapped_type
Definition archetype.hpp:300
storage_type::key_type key_type
Definition archetype.hpp:299
auto ensure_archetype_removed(const archetype *anchor_archetype) -> archetype *
Get or create an archetype by removing Components from an anchor archetype.
Definition archetype.hpp:359
auto begin() noexcept -> iterator
Returns iterator to the beginning of archetypes container.
Definition archetype.hpp:375
auto ensure_archetype_added(const archetype *anchor_archetype) -> archetype *
Get or create an archetype by adding new Components to an anchor archetype.
Definition archetype.hpp:340
auto size() const noexcept -> std::size_t
Returns the number of archetypes.
Definition archetype.hpp:403
auto ensure_archetype(const component_meta_set &components) -> archetype *
Get or create an archetype given the component meta set.
Definition archetype.hpp:306
auto end() const noexcept -> const_iterator
Returns an iterator to the end of archetypes container.
Definition archetype.hpp:396
storage_type::const_iterator const_iterator
Definition archetype.hpp:297
storage_type::iterator iterator
Definition archetype.hpp:296
detail::hash_map< component_set, std::unique_ptr< archetype >, component_set_hasher > storage_type
Underlying container storage type.
Definition archetype.hpp:295
auto end() noexcept -> iterator
Returns an iterator to the end of archetypes container.
Definition archetype.hpp:382
Chunk holds a 16 Kb block of memory that holds components in blocks: |A1|A2|A3|......
Definition chunk.hpp:37
auto copy(std::size_t index, chunk &other_chunk) -> std::size_t
Copy components in blocks at position index into other_chunk.
Definition chunk.hpp:172
static constexpr std::size_t chunk_bytes
Chunk size in bytes.
Definition chunk.hpp:40
static constexpr std::size_t alloc_alignment
Block allocation alignment.
Definition chunk.hpp:43
auto move(std::size_t index, chunk &other_chunk) -> std::size_t
Move components in blocks at position index into other_chunk.
Definition chunk.hpp:151
constexpr auto size() const noexcept -> std::size_t
Return size.
Definition chunk.hpp:235
auto swap_erase(std::size_t index, chunk &other) noexcept -> std::optional< entity >
Swap end removes a components in blocks at position index and swaps it with the last element from oth...
Definition chunk.hpp:127
Component set holds a set of components metadata.
Definition component.hpp:276
auto end() const noexcept -> const_iterator
Return const iterator to the end of the set.
Definition component.hpp:369
constexpr void insert()
Insert component of type T.
Definition component.hpp:299
constexpr void erase()
Erase component of type T.
Definition component.hpp:307
auto begin() const noexcept -> const_iterator
Return const iterator to beginning of the set.
Definition component.hpp:362
Component set hasher.
Definition component.hpp:264
void clear() noexcept
Definition component.hpp:245
void insert()
Insert component of type T.
Definition component.hpp:200
void erase()
Erase component of type T.
Definition component.hpp:208
Entity location.
Definition entity_location.hpp:12
std::size_t entry_index
Definition entity_location.hpp:21
Component concept. The component must be a struct/class that can be move constructed and move assigna...
Definition component.hpp:86
constexpr auto mod_2n(auto value, auto divisor) noexcept -> decltype(auto)
Calculate the value % b=2^n.
Definition bits.hpp:10
hash_table< K, T, true, Hash, KeyEqual, Allocator > hash_map
Hash map.
Definition hash_map.hpp:19
Definition archetype.hpp:11
detail::handle< struct entity_tag_t > entity
Represents an entity, consisting of an ID and generation.
Definition entity.hpp:13
detail::sparse_map< component_id_t, block_metadata > blocks_type
Definition chunk.hpp:32
STL namespace.