tess 1.0.0
Performance-first tile and path simulation substrate
Loading...
Searching...
No Matches
world.h
1#pragma once
2
3#include <tess/core/assert.h>
4#include <tess/core/shape.h>
5#include <tess/storage/chunk_meta.h>
6#include <tess/storage/chunk_page.h>
7
8#include <cstddef>
9#include <cstdint>
10#include <limits>
11#include <optional>
12#include <span>
13#include <vector>
14
15namespace tess {
16
19
21template <typename Shape, typename Schema, typename Residency>
22class World;
23
35template <typename Shape, typename Schema>
36class World<Shape, Schema, AlwaysResident> {
37 public:
38 using shape_type = Shape;
39 using schema_type = Schema;
40 using residency_type = AlwaysResident;
41 using page_type = ChunkPage<Shape, Schema>;
42
43 static constexpr std::uint64_t chunk_count = ShapeTraits<Shape>::chunk_count;
44 static constexpr std::uint64_t local_tile_count =
45 ShapeTraits<Shape>::local_tile_count;
46 static constexpr std::size_t field_count = Schema::field_count;
47 // Tess-owned inline page storage; field-managed dynamic or referenced
48 // storage is outside these byte counts.
49 static constexpr std::size_t page_byte_size = page_type::byte_size;
50 static constexpr std::size_t storage_byte_size =
51 static_cast<std::size_t>(chunk_count) * page_byte_size;
52
53 static_assert(chunk_count <= static_cast<std::uint64_t>(
54 std::numeric_limits<std::size_t>::max()),
55 "AlwaysResident World chunk count must fit std::size_t.");
56 static_assert(page_byte_size <= std::numeric_limits<std::size_t>::max() /
57 static_cast<std::size_t>(chunk_count),
58 "AlwaysResident World storage bytes must fit std::size_t.");
59
62 pages_.reserve(static_cast<std::size_t>(chunk_count));
63 metadata_.reserve(static_cast<std::size_t>(chunk_count));
64 for (std::uint64_t key = 0; key < chunk_count; ++key) {
65 const auto chunk = ChunkKey{key};
66 pages_.emplace_back(chunk, chunk_coord<Shape>(chunk));
67 metadata_.emplace_back();
68 }
69 dirty_masks_.assign(static_cast<std::size_t>(chunk_count), DirtyMask{});
70 active_masks_.assign(static_cast<std::size_t>(chunk_count), ActiveMask{});
71 dirty_bounds_.assign(static_cast<std::size_t>(chunk_count), Box3{});
72 }
73
75 [[nodiscard]] auto chunks() noexcept -> std::span<page_type> {
76 return {pages_.data(), pages_.size()};
77 }
78
80 [[nodiscard]] auto chunks() const noexcept -> std::span<const page_type> {
81 return {pages_.data(), pages_.size()};
82 }
83
91 template <typename Tag>
92 void fill_field(const Schema::template value_type<Tag>& value) noexcept {
93 for (auto& page : pages_) {
94 for (auto& tile : page.template field_span<Tag>()) {
95 tile = value;
96 }
97 }
98 }
99
104 [[nodiscard]] auto chunk(ChunkKey key) noexcept -> page_type& {
105 TESS_ASSERT(key.value < chunk_count);
106 return pages_[static_cast<std::size_t>(key.value)];
107 }
108
110 [[nodiscard]] auto chunk(ChunkKey key) const noexcept -> const page_type& {
111 TESS_ASSERT(key.value < chunk_count);
112 return pages_[static_cast<std::size_t>(key.value)];
113 }
114
119 [[nodiscard]] auto chunk(ChunkCoord3 coord) noexcept -> page_type& {
120 return chunk(chunk_key<Shape>(coord));
121 }
122
124 [[nodiscard]] auto chunk(ChunkCoord3 coord) const noexcept
125 -> const page_type& {
126 return chunk(chunk_key<Shape>(coord));
127 }
128
130 [[nodiscard]] auto try_chunk(ChunkKey key) noexcept -> page_type* {
131 if (key.value >= chunk_count) {
132 return nullptr;
133 }
134 return &chunk(key);
135 }
136
138 [[nodiscard]] auto try_chunk(ChunkKey key) const noexcept
139 -> const page_type* {
140 if (key.value >= chunk_count) {
141 return nullptr;
142 }
143 return &chunk(key);
144 }
145
147 [[nodiscard]] auto try_chunk(ChunkCoord3 coord) noexcept -> page_type* {
148 if (!contains_chunk(coord)) {
149 return nullptr;
150 }
151 return &chunk(coord);
152 }
153
155 [[nodiscard]] auto try_chunk(ChunkCoord3 coord) const noexcept
156 -> const page_type* {
157 if (!contains_chunk(coord)) {
158 return nullptr;
159 }
160 return &chunk(coord);
161 }
162
167 [[nodiscard]] auto meta(ChunkKey key) noexcept -> ChunkMeta& {
168 TESS_ASSERT(key.value < chunk_count);
169 return metadata_[static_cast<std::size_t>(key.value)];
170 }
171
172 [[nodiscard]] auto meta(ChunkKey key) const noexcept -> const ChunkMeta& {
173 TESS_ASSERT(key.value < chunk_count);
174 return metadata_[static_cast<std::size_t>(key.value)];
175 }
176
177 [[nodiscard]] auto meta(ChunkCoord3 coord) noexcept -> ChunkMeta& {
178 return meta(chunk_key<Shape>(coord));
179 }
180
181 [[nodiscard]] auto meta(ChunkCoord3 coord) const noexcept
182 -> const ChunkMeta& {
183 return meta(chunk_key<Shape>(coord));
184 }
185
187 [[nodiscard]] auto try_meta(ChunkKey key) noexcept -> ChunkMeta* {
188 if (key.value >= chunk_count) {
189 return nullptr;
190 }
191 return &meta(key);
192 }
193
194 [[nodiscard]] auto try_meta(ChunkKey key) const noexcept -> const ChunkMeta* {
195 if (key.value >= chunk_count) {
196 return nullptr;
197 }
198 return &meta(key);
199 }
200
201 [[nodiscard]] auto try_meta(ChunkCoord3 coord) noexcept -> ChunkMeta* {
202 if (!contains_chunk(coord)) {
203 return nullptr;
204 }
205 return &meta(coord);
206 }
207
208 [[nodiscard]] auto try_meta(ChunkCoord3 coord) const noexcept
209 -> const ChunkMeta* {
210 if (!contains_chunk(coord)) {
211 return nullptr;
212 }
213 return &meta(coord);
214 }
215
216 [[nodiscard]] auto chunk_activity(ChunkKey key) const noexcept
217 -> ChunkActivity {
218 return active_mask(key).empty() ? ChunkActivity::Sleeping
219 : ChunkActivity::Active;
220 }
221
222 [[nodiscard]] auto chunk_activity(ChunkCoord3 coord) const noexcept
223 -> ChunkActivity {
224 return chunk_activity(chunk_key<Shape>(coord));
225 }
226
227 [[nodiscard]] auto active_category_count(ChunkKey key) const noexcept
228 -> std::uint32_t {
229 return detail::popcount(active_mask(key));
230 }
231
232 // Hot-scan SoA columns split out of ChunkMeta; read-only -- mutate through
233 // mark_/clear_/observe_.
234 [[nodiscard]] auto dirty_mask(ChunkKey key) const noexcept -> DirtyMask {
235 TESS_ASSERT(key.value < chunk_count);
236 return dirty_masks_[static_cast<std::size_t>(key.value)];
237 }
238
239 [[nodiscard]] auto active_mask(ChunkKey key) const noexcept -> ActiveMask {
240 TESS_ASSERT(key.value < chunk_count);
241 return active_masks_[static_cast<std::size_t>(key.value)];
242 }
243
244 [[nodiscard]] auto dirty_bounds(ChunkKey key) const noexcept -> Box3 {
245 TESS_ASSERT(key.value < chunk_count);
246 return dirty_bounds_[static_cast<std::size_t>(key.value)];
247 }
248
249 void mark_dirty(ChunkKey key, DirtyMask mask, Box3 bounds) noexcept {
250 TESS_ASSERT(key.value < chunk_count);
251 const auto slot = static_cast<std::size_t>(key.value);
252 detail::meta_mark_dirty(dirty_masks_[slot], dirty_bounds_[slot], meta(key),
253 mask, bounds);
254 }
255
268 void mark_content_changed(ChunkKey key) noexcept {
269 TESS_ASSERT(key.value < chunk_count);
270 detail::meta_mark_content_changed(meta(key));
271 }
272
273 void mark_topology_dirty(ChunkKey key, DirtyMask mask, Box3 bounds) noexcept {
274 if (mask.empty()) {
275 return;
276 }
277 mark_dirty(key, mask, bounds);
278 ++meta(key).topology_version;
279 }
280
281 void mark_topology_rebuilt(ChunkKey key) noexcept {
282 ++meta(key).topology_version;
283 }
284
285 void clear_dirty(ChunkKey key, DirtyMask mask) noexcept {
286 TESS_ASSERT(key.value < chunk_count);
287 const auto slot = static_cast<std::size_t>(key.value);
288 detail::meta_clear_dirty(dirty_masks_[slot], dirty_bounds_[slot], mask);
289 }
290
291 [[nodiscard]] auto observe_dirty(ChunkKey key, DirtyMask mask) const noexcept
292 -> DirtyObservation {
293 TESS_ASSERT(key.value < chunk_count);
294 const auto slot = static_cast<std::size_t>(key.value);
295 return detail::meta_observe_dirty(dirty_masks_[slot], dirty_bounds_[slot],
296 meta(key), mask);
297 }
298
299 // Clears exactly the observed mask iff the chunk's content version still
300 // matches the observation. Any mark_dirty after the observation advances
301 // that version, so a stale clear leaves every mask bit and bound in place
302 // and returns false; the caller re-observes and rebuilds.
303 bool clear_dirty_observed(ChunkKey key, DirtyObservation observed) noexcept {
304 TESS_ASSERT(key.value < chunk_count);
305 const auto slot = static_cast<std::size_t>(key.value);
306 return detail::meta_clear_dirty_observed(
307 dirty_masks_[slot], dirty_bounds_[slot], meta(key), observed);
308 }
309
310 void mark_active(ChunkKey key, ActiveMask mask) noexcept {
311 TESS_ASSERT(key.value < chunk_count);
312 const auto slot = static_cast<std::size_t>(key.value);
313 detail::meta_mark_active(active_masks_[slot], mask);
314 }
315
316 void clear_active(ChunkKey key, ActiveMask mask) noexcept {
317 TESS_ASSERT(key.value < chunk_count);
318 const auto slot = static_cast<std::size_t>(key.value);
319 detail::meta_clear_active(active_masks_[slot], mask);
320 }
321
328 void collect_dirty_chunks(DirtyMask mask, std::vector<ChunkKey>& out) const {
329 collect_matching_chunks(mask, dirty_masks_, out);
330 }
331
338 std::vector<ChunkKey>& out) const {
339 collect_matching_chunks(mask, active_masks_, out);
340 }
341
343 [[nodiscard]] auto dirty_chunks(DirtyMask mask) const
344 -> std::vector<ChunkKey> {
345 std::vector<ChunkKey> chunks;
347 return chunks;
348 }
349
351 [[nodiscard]] auto active_chunks(ActiveMask mask) const
352 -> std::vector<ChunkKey> {
353 std::vector<ChunkKey> chunks;
355 return chunks;
356 }
357
362 [[nodiscard]] auto resolve(Coord3 coord) const noexcept
364 TESS_ASSERT(contains<Shape>(coord));
365 const auto chunk_coord_value = chunk_coord<Shape>(coord);
366 return ResolvedTile<Shape>{
367 chunk_key<Shape>(chunk_coord_value),
368 local_tile_id<Shape>(local_coord<Shape>(coord)),
369 };
370 }
371
373 [[nodiscard]] auto try_resolve(Coord3 coord) const noexcept
374 -> std::optional<ResolvedTile<Shape>> {
375 if (!contains<Shape>(coord)) {
376 return std::nullopt;
377 }
378 return resolve(coord);
379 }
380
385 template <typename Tag>
386 [[nodiscard]] auto field(Coord3 coord) noexcept
387 -> Schema::template value_type<Tag>& {
388 const auto resolved = resolve(coord);
389 return chunk(resolved.chunk_key)
390 .template field<Tag>(resolved.local_tile_id);
391 }
392
394 template <typename Tag>
395 [[nodiscard]] auto field(Coord3 coord) const noexcept
396 -> const Schema::template value_type<Tag>& {
397 const auto resolved = resolve(coord);
398 return chunk(resolved.chunk_key)
399 .template field<Tag>(resolved.local_tile_id);
400 }
401
406 template <typename Tag>
407 [[nodiscard]] auto try_field(Coord3 coord) noexcept
408 -> Schema::template value_type<Tag>* {
409 const auto resolved = try_resolve(coord);
410 if (!resolved.has_value()) {
411 return nullptr;
412 }
413 return &chunk(resolved->chunk_key)
414 .template field<Tag>(resolved->local_tile_id);
415 }
416
418 template <typename Tag>
419 [[nodiscard]] auto try_field(Coord3 coord) const noexcept
420 -> const Schema::template value_type<Tag>* {
421 const auto resolved = try_resolve(coord);
422 if (!resolved.has_value()) {
423 return nullptr;
424 }
425 return &chunk(resolved->chunk_key)
426 .template field<Tag>(resolved->local_tile_id);
427 }
428
433 template <typename Tag>
434 [[nodiscard]] auto field_span(ChunkKey key) noexcept {
435 return chunk(key).template field_span<Tag>();
436 }
437
439 template <typename Tag>
440 [[nodiscard]] auto field_span(ChunkKey key) const noexcept {
441 return chunk(key).template field_span<Tag>();
442 }
443
444 private:
445 static constexpr bool contains_chunk(ChunkCoord3 coord) noexcept {
446 using Traits = ShapeTraits<Shape>;
447 return coord.x < Traits::chunk_count_x && coord.y < Traits::chunk_count_y &&
448 coord.z < Traits::chunk_count_z;
449 }
450
451 // Scans a dense 4-byte mask column (16 chunks per cache line) instead of
452 // streaming ChunkMeta structs.
453 template <typename Mask>
454 void collect_matching_chunks(Mask mask, const std::vector<Mask>& column,
455 std::vector<ChunkKey>& out) const {
456 for (std::uint64_t key = 0; key < chunk_count; ++key) {
457 if (static_cast<bool>(column[static_cast<std::size_t>(key)] & mask)) {
458 out.push_back(ChunkKey{key});
459 }
460 }
461 }
462
463 std::vector<page_type> pages_;
464 std::vector<ChunkMeta> metadata_;
465 std::vector<DirtyMask> dirty_masks_;
466 std::vector<ActiveMask> active_masks_;
467 std::vector<Box3> dirty_bounds_;
468};
469
471template <typename Shape, typename Schema>
472using AlwaysResidentWorld = World<Shape, Schema, AlwaysResident>;
473
474} // namespace tess
Definition chunk_page.h:123
auto field_span(ChunkKey key) const noexcept
Definition world.h:440
auto try_meta(ChunkKey key) noexcept -> ChunkMeta *
Definition world.h:187
auto field(Coord3 coord) const noexcept -> const Schema::template value_type< Tag > &
Definition world.h:395
auto resolve(Coord3 coord) const noexcept -> ResolvedTile< Shape >
Definition world.h:362
void collect_active_chunks(ActiveMask mask, std::vector< ChunkKey > &out) const
Definition world.h:337
auto dirty_chunks(DirtyMask mask) const -> std::vector< ChunkKey >
Definition world.h:343
auto try_resolve(Coord3 coord) const noexcept -> std::optional< ResolvedTile< Shape > >
Definition world.h:373
auto field(Coord3 coord) noexcept -> Schema::template value_type< Tag > &
Definition world.h:386
auto chunk(ChunkKey key) const noexcept -> const page_type &
Definition world.h:110
auto chunks() noexcept -> std::span< page_type >
Definition world.h:75
auto meta(ChunkKey key) noexcept -> ChunkMeta &
Definition world.h:167
void fill_field(const Schema::template value_type< Tag > &value) noexcept
Definition world.h:92
auto try_chunk(ChunkCoord3 coord) const noexcept -> const page_type *
Definition world.h:155
auto try_chunk(ChunkCoord3 coord) noexcept -> page_type *
Definition world.h:147
auto try_field(Coord3 coord) noexcept -> Schema::template value_type< Tag > *
Definition world.h:407
auto try_field(Coord3 coord) const noexcept -> const Schema::template value_type< Tag > *
Definition world.h:419
auto try_chunk(ChunkKey key) noexcept -> page_type *
Definition world.h:130
auto active_chunks(ActiveMask mask) const -> std::vector< ChunkKey >
Definition world.h:351
void collect_dirty_chunks(DirtyMask mask, std::vector< ChunkKey > &out) const
Definition world.h:328
auto try_chunk(ChunkKey key) const noexcept -> const page_type *
Definition world.h:138
auto chunk(ChunkCoord3 coord) const noexcept -> const page_type &
Definition world.h:124
auto field_span(ChunkKey key) noexcept
Definition world.h:434
auto chunk(ChunkCoord3 coord) noexcept -> page_type &
Definition world.h:119
auto chunks() const noexcept -> std::span< const page_type >
Definition world.h:80
void mark_content_changed(ChunkKey key) noexcept
Definition world.h:268
auto chunk(ChunkKey key) noexcept -> page_type &
Definition world.h:104
Definition world.h:22
Definition metadata_types.h:49
Definition world.h:18
Definition shape.h:94
Definition shape.h:58
Definition shape.h:86
Definition chunk_meta.h:27
Definition shape.h:46
Definition metadata_types.h:12
Definition shape.h:107
Definition shape.h:320
Definition shape.h:296