co_ecs 0.9.0
Cobalt ECS
Loading...
Searching...
No Matches
chunk.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <cassert>
4#include <cstdint>
5#include <new>
6#include <optional>
7#include <type_traits>
8#include <utility>
9
10#include <co_ecs/component.hpp>
14#include <co_ecs/entity.hpp>
15#include <co_ecs/exceptions.hpp>
16
17
18namespace co_ecs {
19
20// NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast)
21// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
22
25 std::size_t offset{};
27
28 block_metadata(std::size_t offset, const component_meta& meta) noexcept : offset(offset), meta(meta) {
29 }
30};
31
33
37class chunk {
38public:
40 static constexpr std::size_t chunk_bytes = static_cast<const std::size_t>(16U * 1024); // 16 KB
41
43 static constexpr std::size_t alloc_alignment = alignof(entity);
44
46 struct alignas(alloc_alignment) chunk_buffer {
47 std::byte data[chunk_bytes];
48 };
49
54 chunk(const blocks_type& blocks, std::size_t max_size) :
55 _blocks(&blocks), _max_size(max_size), _buffer((new chunk_buffer)->data) {
56 }
57
61 chunk(const chunk& rhs) = delete;
62
66 auto operator=(const chunk& rhs) -> chunk& = delete;
67
71 chunk(chunk&& rhs) noexcept :
72 _buffer(std::exchange(rhs._buffer, nullptr)), _size(rhs._size), _max_size(rhs._max_size), _blocks(rhs._blocks) {
73 }
74
79 auto operator=(chunk&& rhs) noexcept -> chunk& {
80 _buffer = std::exchange(rhs._buffer, _buffer);
81 _size = std::exchange(rhs._size, _size);
82 _max_size = std::exchange(rhs._max_size, _max_size);
83 _blocks = std::exchange(rhs._blocks, _blocks);
84 return *this;
85 }
86
89 if (_buffer == nullptr) {
90 return;
91 }
92 for (const auto& [id, block] : *_blocks) {
93 for (std::size_t i = 0; i < _size; i++) {
94 block.meta.type->destruct(_buffer + block.offset + i * block.meta.type->size);
95 }
96 }
97 delete reinterpret_cast<chunk_buffer*>(_buffer);
98 }
99
105 template<component... Args>
106 void emplace_back(entity ent, Args&&... args) {
107 assert((!full()) && "Chunk is full, cannot add more entities");
108 std::construct_at(ptr_unchecked<entity>(size()), ent);
109 (..., std::construct_at(ptr_mut<Args>(size()), std::forward<Args>(args)));
110 _size++;
111 }
112
114 void pop_back() noexcept {
115 assert((!empty()) && "Chunk is empty, cannot pop out any entity");
116 _size--;
117 destroy_at(_size);
118 }
119
127 auto swap_erase(std::size_t index, chunk& other) noexcept -> std::optional<entity> {
128 assert((index < _size) && "Entity index exceeds chunk size");
129 if (size() == 1 || index == _size - 1) {
130 pop_back();
131 return std::nullopt;
132 }
133 assert((!other.empty()) && "Other chunk is empty, cannot move entity out of it");
134 const std::size_t other_chunk_index = other._size - 1;
135 entity ent = *other.ptr_unchecked<entity>(other_chunk_index);
136 for (const auto& [id, block] : *_blocks) {
137 auto other_block = other._blocks->find(id)->second;
138 const auto* type = block.meta.type;
139 auto* ptr = other._buffer + other_block.offset + other_chunk_index * type->size;
140 type->move_assign(_buffer + block.offset + index * type->size, ptr);
141 }
142 other.pop_back();
143 return ent;
144 }
145
151 auto move(std::size_t index, chunk& other_chunk) -> std::size_t {
152 assert((index < _size) && "Entity index exceeds chunk size");
153 assert((!other_chunk.full()) && "Other chunk is full, cannot move entity to it");
154 const std::size_t other_chunk_index = other_chunk._size;
155 for (const auto& [id, block] : *_blocks) {
156 const auto* type = block.meta.type;
157 if (!other_chunk._blocks->contains(id)) {
158 continue;
159 }
160 auto* ptr = other_chunk._buffer + other_chunk._blocks->at(id).offset + other_chunk_index * type->size;
161 type->move_construct(ptr, _buffer + block.offset + index * type->size);
162 }
163 other_chunk._size++;
164 return other_chunk_index;
165 }
166
172 auto copy(std::size_t index, chunk& other_chunk) -> std::size_t {
173 assert((index < _size) && "Entity index exceeds chunk size");
174 assert((!other_chunk.full()) && "Other chunk is full, cannot move entity to it");
175 const std::size_t other_chunk_index = other_chunk._size;
176 for (const auto& [id, block] : *_blocks) {
177 const auto* type = block.meta.type;
178 if (!other_chunk._blocks->contains(id)) {
179 continue;
180 }
181 auto* ptr = other_chunk._buffer + other_chunk._blocks->at(id).offset + other_chunk_index * type->size;
182 if (type->copy_construct) {
183 type->copy_construct(ptr, _buffer + block.offset + index * type->size);
184 }
185 }
186 other_chunk._size++;
187 return other_chunk_index;
188 }
189
193 constexpr void visit(std::size_t index, auto&& func) noexcept {
194 visit_impl(*this, index, std::forward<decltype(func)>(func));
195 }
196
200 constexpr void visit(std::size_t index, auto&& func) const noexcept {
201 visit_impl(*this, index, std::forward<decltype(func)>(func));
202 }
203
209 template<component T>
210 inline auto ptr_const(std::size_t index) const -> const T* {
211 return ptr_unchecked_impl<const T*>(*this, index);
212 }
213
219 template<component T>
220 inline auto ptr_mut(std::size_t index) -> T* {
221 static_assert(!std::is_same_v<T, entity>, "Cannot give a mutable pointer/reference to the entity");
222 return ptr_unchecked_impl<T*>(*this, index);
223 }
224
228 [[nodiscard]] constexpr auto max_size() const noexcept -> std::size_t {
229 return _max_size;
230 }
231
235 [[nodiscard]] constexpr auto size() const noexcept -> std::size_t {
236 return _size;
237 }
238
243 [[nodiscard]] constexpr auto full() const noexcept -> bool {
244 return size() == max_size();
245 }
246
251 [[nodiscard]] constexpr auto empty() const noexcept -> bool {
252 return size() == 0;
253 }
254
255private:
256 friend class base_registry;
257
258 constexpr static void visit_impl(auto&& self, std::size_t index, auto&& func) noexcept {
259 assert((index < self._size) && "Entity index exceeds chunk size");
260
261 constexpr auto is_const = std::is_const_v<std::remove_reference_t<decltype(self)>>;
262 using ptr_t = std::conditional_t<is_const, const void*, void*>;
263
264 for (const auto& [id, block] :
265 *self._blocks | detail::views::drop(1)) // skip first block - it's an entity handle
266 {
267 const auto* type = block.meta.type;
268 ptr_t ptr = self._buffer + block.offset + index * type->size;
269 func(block.meta, ptr);
270 }
271 }
272
273 template<component T>
274 [[nodiscard]] constexpr auto ptr_unchecked(std::size_t index) -> T* {
275 return ptr_unchecked_impl<T*>(*this, index);
276 }
277
278 template<component T>
279 [[nodiscard]] constexpr auto ptr_unchecked(std::size_t index) const noexcept -> const T* {
280 return ptr_unchecked_impl<const T*>(*this, index);
281 }
282
283 template<typename P>
284 [[nodiscard]] static inline auto ptr_unchecked_impl(auto&& self, std::size_t index) -> P {
285 using component_type = std::remove_const_t<std::remove_pointer_t<P>>;
286 const auto& block = self.get_block(component_id::value<component_type>);
287 return (reinterpret_cast<P>(self._buffer + block.offset) + index);
288 }
289
290 [[nodiscard]] auto get_block(component_id_t id) const -> const block_metadata& {
291 return _blocks->at(id);
292 }
293
294 inline void destroy_at(std::size_t index) noexcept {
295 for (const auto& [id, block] : *_blocks) {
296 block.meta.type->destruct(_buffer + block.offset + index * block.meta.type->size);
297 }
298 }
299
300 std::byte* _buffer{};
301 std::size_t _size{};
302 std::size_t _max_size{};
303 const blocks_type* _blocks;
304};
305
309 // clang-format off
310
314 template<component_reference C>
315 static auto fetch_pointer(auto&& chunk, std::size_t index)
316 -> const decay_component_t<C>* requires(const_component_reference_v<C>) {
317 try {
318 return chunk.template ptr_const<decay_component_t<C>>(index);
319 } catch (const std::out_of_range&) {
320 throw component_not_found{ type_meta::of<decay_component_t<C>>() };
321 }
322 }
323
327 template<component_reference C>
328 static auto fetch_pointer(auto&& chunk, std::size_t index)
329 -> decay_component_t<C>* requires(mutable_component_reference_v<C>) {
330 try {
331 return chunk.template ptr_mut<decay_component_t<C>>(index);
332 } catch (const std::out_of_range&) {
333 throw component_not_found{ type_meta::of<decay_component_t<C>>() };
334 }
335 }
336
337 // clang-format on
338};
339
343template<component_reference... Args>
345public:
347 static constexpr bool is_const = const_component_references_v<Args...>;
348
349 using chunk_type = std::conditional_t<is_const, const chunk&, chunk&>;
350
352 class iterator {
353 public:
354 using iterator_concept = std::forward_iterator_tag;
355 using iterator_category = std::forward_iterator_tag;
356 using difference_type = int;
357 using value_type = std::tuple<Args...>;
358 using reference = std::tuple<Args...>;
360
362 constexpr iterator() = default;
363
365 constexpr ~iterator() = default;
366
371 explicit constexpr iterator(chunk_type c, std::size_t index = 0) :
372 _ptrs(std::make_tuple(component_fetch::fetch_pointer<Args>(c, index)...)) {
373 }
374
378 constexpr iterator(const iterator& rhs) noexcept = default;
379
384 constexpr auto operator=(const iterator& rhs) noexcept -> iterator& = default;
385
389 constexpr iterator(iterator&& rhs) noexcept = default;
390
395 constexpr auto operator=(iterator&& rhs) noexcept -> iterator& = default;
396
400 constexpr auto operator++() noexcept -> iterator& {
401 std::apply([](auto&&... args) { (args++, ...); }, _ptrs);
402 return *this;
403 }
404
408 constexpr auto operator++(int) noexcept -> iterator {
409 iterator tmp(*this);
410 std::apply([](auto&&... args) { (args++, ...); }, _ptrs);
411 return tmp;
412 }
413
417 constexpr auto operator*() const noexcept -> reference {
418 return std::apply([](auto&&... args) { return std::make_tuple(std::ref(*args)...); }, _ptrs);
419 }
420
425 constexpr bool operator==(const iterator& rhs) const noexcept {
426 return std::get<0>(_ptrs) == std::get<0>(rhs._ptrs);
427 }
428
433 constexpr auto operator<=>(const iterator& rhs) const noexcept {
434 return std::get<0>(_ptrs) <=> std::get<0>(rhs._ptrs);
435 }
436
437 private:
438 std::tuple<std::add_pointer_t<std::remove_reference_t<Args>>...> _ptrs;
439 };
440
444 explicit chunk_view(chunk_type c) : _chunk(c) {
445 }
446
450 [[nodiscard]] constexpr auto begin() noexcept -> iterator {
451 return iterator(_chunk, 0);
452 }
453
457 [[nodiscard]] constexpr auto end() noexcept -> iterator {
458 return iterator(_chunk, _chunk.size());
459 }
460
461private:
462 chunk_type _chunk;
463};
464
465// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
466// NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast)
467
468} // namespace co_ecs
Definition base_registry.hpp:35
Chunk view iterator.
Definition chunk.hpp:352
constexpr iterator(const iterator &rhs) noexcept=default
Default copy constructor.
constexpr auto operator++(int) noexcept -> iterator
Post-increment iterator.
Definition chunk.hpp:408
constexpr iterator(chunk_type c, std::size_t index=0)
Create iterator out of chunk pointing to the index.
Definition chunk.hpp:371
std::tuple< Args... > reference
Definition chunk.hpp:358
std::forward_iterator_tag iterator_category
Definition chunk.hpp:355
constexpr iterator(iterator &&rhs) noexcept=default
Default move constructor.
constexpr bool operator==(const iterator &rhs) const noexcept
Equality operator.
Definition chunk.hpp:425
constexpr auto operator=(iterator &&rhs) noexcept -> iterator &=default
Default move assignment operator.
int difference_type
Definition chunk.hpp:356
reference element_type
Definition chunk.hpp:359
constexpr auto operator=(const iterator &rhs) noexcept -> iterator &=default
Default copy assignment operator.
constexpr auto operator<=>(const iterator &rhs) const noexcept
Spaceship operator.
Definition chunk.hpp:433
std::forward_iterator_tag iterator_concept
Definition chunk.hpp:354
constexpr iterator()=default
Default constructor.
constexpr ~iterator()=default
Default destructor.
constexpr auto operator*() const noexcept -> reference
Dereference iterator.
Definition chunk.hpp:417
std::tuple< Args... > value_type
Definition chunk.hpp:357
constexpr auto operator++() noexcept -> iterator &
Pre-increment iterator.
Definition chunk.hpp:400
A type aware view into a chunk components.
Definition chunk.hpp:344
std::conditional_t< is_const, const chunk &, chunk & > chunk_type
Definition chunk.hpp:349
chunk_view(chunk_type c)
Construct a new chunk view object.
Definition chunk.hpp:444
constexpr auto end() noexcept -> iterator
Return iterator to the end of a chunk.
Definition chunk.hpp:457
constexpr auto begin() noexcept -> iterator
Return iterator to the beginning of a chunk.
Definition chunk.hpp:450
Chunk holds a 16 Kb block of memory that holds components in blocks: |A1|A2|A3|......
Definition chunk.hpp:37
~chunk()
Destroy the chunk object.
Definition chunk.hpp:88
constexpr auto empty() const noexcept -> bool
Check if chunk is empty.
Definition chunk.hpp:251
chunk(const chunk &rhs)=delete
Deleted copy constructor.
constexpr auto full() const noexcept -> bool
Check if chunk is full.
Definition chunk.hpp:243
constexpr void visit(std::size_t index, auto &&func) noexcept
Visit components at given index.
Definition chunk.hpp:193
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
auto operator=(const chunk &rhs) -> chunk &=delete
Deleted copy assignment operator.
static constexpr std::size_t alloc_alignment
Block allocation alignment.
Definition chunk.hpp:43
auto operator=(chunk &&rhs) noexcept -> chunk &
Move assignment operator.
Definition chunk.hpp:79
chunk(chunk &&rhs) noexcept
Move constructor.
Definition chunk.hpp:71
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 void visit(std::size_t index, auto &&func) const noexcept
Visit components at given index.
Definition chunk.hpp:200
constexpr auto size() const noexcept -> std::size_t
Return size.
Definition chunk.hpp:235
constexpr auto max_size() const noexcept -> std::size_t
Get max size, how many elements can this chunk hold.
Definition chunk.hpp:228
chunk(const blocks_type &blocks, std::size_t max_size)
Construct a new chunk object.
Definition chunk.hpp:54
void emplace_back(entity ent, Args &&... args)
Emplace back components into blocks.
Definition chunk.hpp:106
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
auto ptr_const(std::size_t index) const -> const T *
Give a const pointer to a component T at index.
Definition chunk.hpp:210
auto ptr_mut(std::size_t index) -> T *
Give a pointer to a component T at index.
Definition chunk.hpp:220
void pop_back() noexcept
Remove back elements from blocks.
Definition chunk.hpp:114
Exception raised when accessing entities component which was not assigned.
Definition exceptions.hpp:36
Component concept. The component must be a struct/class that can be move constructed and move assigna...
Definition component.hpp:86
sparse_table< K, T, true, Allocator > sparse_map
Sparse map.
Definition sparse_map.hpp:12
Definition archetype.hpp:11
constexpr bool const_component_references_v
Returns true when all Args are const references.
Definition component.hpp:142
std::decay_t< T > decay_component_t
Decay component; converts component_reference to component by removing cv-qualifiers and reference.
Definition component.hpp:100
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.
Block metadata holds pointers where it begins, ends and a component metadata it holds.
Definition chunk.hpp:24
component_meta meta
Definition chunk.hpp:26
std::size_t offset
Definition chunk.hpp:25
block_metadata(std::size_t offset, const component_meta &meta) noexcept
Definition chunk.hpp:28
Chunk buffer type.
Definition chunk.hpp:46
Component fetch is a namespace for routines that figure out based on input component_reference how to...
Definition chunk.hpp:308
static auto fetch_pointer(auto &&chunk, std::size_t index) -> decay_component_t< C > *requires(mutable_component_reference_v< C >)
Fetches pointer for mutable component reference.
Definition chunk.hpp:328
static auto fetch_pointer(auto &&chunk, std::size_t index) -> const decay_component_t< C > *requires(const_component_reference_v< C >)
Fetches pointer for const component reference.
Definition chunk.hpp:315
Component metadata. Stores an ID, size, alignment, destructor, etc.
Definition component.hpp:145