tess 0.4.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 static constexpr std::size_t page_byte_size = page_type::byte_size;
48 static constexpr std::size_t storage_byte_size =
49 static_cast<std::size_t>(chunk_count) * page_byte_size;
50
51 static_assert(chunk_count <= static_cast<std::uint64_t>(
52 std::numeric_limits<std::size_t>::max()),
53 "AlwaysResident World chunk count must fit std::size_t.");
54 static_assert(page_byte_size <= std::numeric_limits<std::size_t>::max() /
55 static_cast<std::size_t>(chunk_count),
56 "AlwaysResident World storage bytes must fit std::size_t.");
57
60 pages_.reserve(static_cast<std::size_t>(chunk_count));
61 metadata_.reserve(static_cast<std::size_t>(chunk_count));
62 for (std::uint64_t key = 0; key < chunk_count; ++key) {
63 const auto chunk = ChunkKey{key};
64 pages_.emplace_back(chunk, chunk_coord<Shape>(chunk));
65 metadata_.emplace_back();
66 }
67 dirty_flags_.assign(static_cast<std::size_t>(chunk_count), 0u);
68 active_flags_.assign(static_cast<std::size_t>(chunk_count), 0u);
69 dirty_bounds_.assign(static_cast<std::size_t>(chunk_count), Box3{});
70 }
71
73 [[nodiscard]] auto chunks() noexcept -> std::span<page_type> {
74 return {pages_.data(), pages_.size()};
75 }
76
78 [[nodiscard]] auto chunks() const noexcept -> std::span<const page_type> {
79 return {pages_.data(), pages_.size()};
80 }
81
86 [[nodiscard]] auto chunk(ChunkKey key) noexcept -> page_type& {
87 TESS_ASSERT(key.value < chunk_count);
88 return pages_[static_cast<std::size_t>(key.value)];
89 }
90
92 [[nodiscard]] auto chunk(ChunkKey key) const noexcept -> const page_type& {
93 TESS_ASSERT(key.value < chunk_count);
94 return pages_[static_cast<std::size_t>(key.value)];
95 }
96
101 [[nodiscard]] auto chunk(ChunkCoord3 coord) noexcept -> page_type& {
102 return chunk(chunk_key<Shape>(coord));
103 }
104
106 [[nodiscard]] auto chunk(ChunkCoord3 coord) const noexcept
107 -> const page_type& {
108 return chunk(chunk_key<Shape>(coord));
109 }
110
112 [[nodiscard]] auto try_chunk(ChunkKey key) noexcept -> page_type* {
113 if (key.value >= chunk_count) {
114 return nullptr;
115 }
116 return &chunk(key);
117 }
118
120 [[nodiscard]] auto try_chunk(ChunkKey key) const noexcept
121 -> const page_type* {
122 if (key.value >= chunk_count) {
123 return nullptr;
124 }
125 return &chunk(key);
126 }
127
129 [[nodiscard]] auto try_chunk(ChunkCoord3 coord) noexcept -> page_type* {
130 if (!contains_chunk(coord)) {
131 return nullptr;
132 }
133 return &chunk(coord);
134 }
135
137 [[nodiscard]] auto try_chunk(ChunkCoord3 coord) const noexcept
138 -> const page_type* {
139 if (!contains_chunk(coord)) {
140 return nullptr;
141 }
142 return &chunk(coord);
143 }
144
149 [[nodiscard]] auto meta(ChunkKey key) noexcept -> ChunkMeta& {
150 TESS_ASSERT(key.value < chunk_count);
151 return metadata_[static_cast<std::size_t>(key.value)];
152 }
153
154 [[nodiscard]] auto meta(ChunkKey key) const noexcept -> const ChunkMeta& {
155 TESS_ASSERT(key.value < chunk_count);
156 return metadata_[static_cast<std::size_t>(key.value)];
157 }
158
159 [[nodiscard]] auto meta(ChunkCoord3 coord) noexcept -> ChunkMeta& {
160 return meta(chunk_key<Shape>(coord));
161 }
162
163 [[nodiscard]] auto meta(ChunkCoord3 coord) const noexcept
164 -> const ChunkMeta& {
165 return meta(chunk_key<Shape>(coord));
166 }
167
169 [[nodiscard]] auto try_meta(ChunkKey key) noexcept -> ChunkMeta* {
170 if (key.value >= chunk_count) {
171 return nullptr;
172 }
173 return &meta(key);
174 }
175
176 [[nodiscard]] auto try_meta(ChunkKey key) const noexcept -> const ChunkMeta* {
177 if (key.value >= chunk_count) {
178 return nullptr;
179 }
180 return &meta(key);
181 }
182
183 [[nodiscard]] auto try_meta(ChunkCoord3 coord) noexcept -> ChunkMeta* {
184 if (!contains_chunk(coord)) {
185 return nullptr;
186 }
187 return &meta(coord);
188 }
189
190 [[nodiscard]] auto try_meta(ChunkCoord3 coord) const noexcept
191 -> const ChunkMeta* {
192 if (!contains_chunk(coord)) {
193 return nullptr;
194 }
195 return &meta(coord);
196 }
197
198 [[nodiscard]] auto chunk_state(ChunkKey key) const noexcept -> ChunkState {
199 return meta(key).state;
200 }
201
202 [[nodiscard]] auto chunk_state(ChunkCoord3 coord) const noexcept
203 -> ChunkState {
204 return meta(coord).state;
205 }
206
207 void set_chunk_state(ChunkKey key, ChunkState state) noexcept {
208 meta(key).state = state;
209 }
210
211 // Hot-scan SoA columns split out of ChunkMeta (audit 2026-07-11 M5);
212 // read-only -- mutate through mark_/clear_/observe_ as before.
213 [[nodiscard]] auto dirty_flags(ChunkKey key) const noexcept -> std::uint32_t {
214 TESS_ASSERT(key.value < chunk_count);
215 return dirty_flags_[static_cast<std::size_t>(key.value)];
216 }
217
218 [[nodiscard]] auto active_flags(ChunkKey key) const noexcept
219 -> std::uint32_t {
220 TESS_ASSERT(key.value < chunk_count);
221 return active_flags_[static_cast<std::size_t>(key.value)];
222 }
223
224 [[nodiscard]] auto dirty_bounds(ChunkKey key) const noexcept -> Box3 {
225 TESS_ASSERT(key.value < chunk_count);
226 return dirty_bounds_[static_cast<std::size_t>(key.value)];
227 }
228
229 void mark_dirty(ChunkKey key, std::uint32_t flags, Box3 bounds) noexcept {
230 TESS_ASSERT(key.value < chunk_count);
231 const auto slot = static_cast<std::size_t>(key.value);
232 detail::meta_mark_dirty(dirty_flags_[slot], dirty_bounds_[slot], meta(key),
233 flags, bounds);
234 }
235
236 void mark_topology_dirty(ChunkKey key, std::uint32_t flags,
237 Box3 bounds) noexcept {
238 if (flags == 0) {
239 return;
240 }
241 mark_dirty(key, flags, bounds);
242 ++meta(key).topology_version;
243 }
244
245 void mark_topology_rebuilt(ChunkKey key) noexcept {
246 ++meta(key).topology_version;
247 }
248
249 void clear_dirty(ChunkKey key, std::uint32_t flags) noexcept {
250 TESS_ASSERT(key.value < chunk_count);
251 const auto slot = static_cast<std::size_t>(key.value);
252 detail::meta_clear_dirty(dirty_flags_[slot], dirty_bounds_[slot], flags);
253 }
254
255 [[nodiscard]] auto observe_dirty(ChunkKey key,
256 std::uint32_t flags) const noexcept
257 -> DirtyObservation {
258 TESS_ASSERT(key.value < chunk_count);
259 const auto slot = static_cast<std::size_t>(key.value);
260 return detail::meta_observe_dirty(dirty_flags_[slot], dirty_bounds_[slot],
261 meta(key), flags);
262 }
263
264 // Clears exactly the observed flags iff the chunk's dirty generation still
265 // matches the observation. Any mark_dirty after the observation advances
266 // the generation, so a stale clear leaves every flag and bound in place
267 // and returns false; the caller re-observes and rebuilds.
268 bool clear_dirty_observed(ChunkKey key, DirtyObservation observed) noexcept {
269 TESS_ASSERT(key.value < chunk_count);
270 const auto slot = static_cast<std::size_t>(key.value);
271 return detail::meta_clear_dirty_observed(
272 dirty_flags_[slot], dirty_bounds_[slot], meta(key), observed);
273 }
274
275 void mark_active(ChunkKey key, std::uint32_t flags) noexcept {
276 TESS_ASSERT(key.value < chunk_count);
277 const auto slot = static_cast<std::size_t>(key.value);
278 detail::meta_mark_active(active_flags_[slot], meta(key), flags);
279 }
280
281 void clear_active(ChunkKey key, std::uint32_t flags) noexcept {
282 TESS_ASSERT(key.value < chunk_count);
283 const auto slot = static_cast<std::size_t>(key.value);
284 detail::meta_clear_active(active_flags_[slot], meta(key), flags);
285 }
286
293 void collect_dirty_chunks(std::uint32_t flags,
294 std::vector<ChunkKey>& out) const {
295 collect_matching_chunks(flags, dirty_flags_, out);
296 }
297
303 void collect_active_chunks(std::uint32_t flags,
304 std::vector<ChunkKey>& out) const {
305 collect_matching_chunks(flags, active_flags_, out);
306 }
307
309 [[nodiscard]] auto dirty_chunks(std::uint32_t flags) const
310 -> std::vector<ChunkKey> {
311 std::vector<ChunkKey> chunks;
313 return chunks;
314 }
315
317 [[nodiscard]] auto active_chunks(std::uint32_t flags) const
318 -> std::vector<ChunkKey> {
319 std::vector<ChunkKey> chunks;
321 return chunks;
322 }
323
328 [[nodiscard]] auto resolve(Coord3 coord) const noexcept
330 TESS_ASSERT(contains<Shape>(coord));
331 const auto chunk_coord_value = chunk_coord<Shape>(coord);
332 return ResolvedTile<Shape>{
333 chunk_key<Shape>(chunk_coord_value),
334 local_tile_id<Shape>(local_coord<Shape>(coord)),
335 };
336 }
337
339 [[nodiscard]] auto try_resolve(Coord3 coord) const noexcept
340 -> std::optional<ResolvedTile<Shape>> {
341 if (!contains<Shape>(coord)) {
342 return std::nullopt;
343 }
344 return resolve(coord);
345 }
346
351 template <typename Tag>
352 [[nodiscard]] auto field(Coord3 coord) noexcept
353 -> Schema::template value_type<Tag>& {
354 const auto resolved = resolve(coord);
355 return chunk(resolved.chunk_key)
356 .template field<Tag>(resolved.local_tile_id);
357 }
358
360 template <typename Tag>
361 [[nodiscard]] auto field(Coord3 coord) const noexcept
362 -> const Schema::template value_type<Tag>& {
363 const auto resolved = resolve(coord);
364 return chunk(resolved.chunk_key)
365 .template field<Tag>(resolved.local_tile_id);
366 }
367
372 template <typename Tag>
373 [[nodiscard]] auto try_field(Coord3 coord) noexcept
374 -> Schema::template value_type<Tag>* {
375 const auto resolved = try_resolve(coord);
376 if (!resolved.has_value()) {
377 return nullptr;
378 }
379 return &chunk(resolved->chunk_key)
380 .template field<Tag>(resolved->local_tile_id);
381 }
382
384 template <typename Tag>
385 [[nodiscard]] auto try_field(Coord3 coord) const noexcept
386 -> const Schema::template value_type<Tag>* {
387 const auto resolved = try_resolve(coord);
388 if (!resolved.has_value()) {
389 return nullptr;
390 }
391 return &chunk(resolved->chunk_key)
392 .template field<Tag>(resolved->local_tile_id);
393 }
394
399 template <typename Tag>
400 [[nodiscard]] auto field_span(ChunkKey key) noexcept {
401 return chunk(key).template field_span<Tag>();
402 }
403
405 template <typename Tag>
406 [[nodiscard]] auto field_span(ChunkKey key) const noexcept {
407 return chunk(key).template field_span<Tag>();
408 }
409
410 private:
411 static constexpr bool contains_chunk(ChunkCoord3 coord) noexcept {
412 using Traits = ShapeTraits<Shape>;
413 return coord.x < Traits::chunk_count_x && coord.y < Traits::chunk_count_y &&
414 coord.z < Traits::chunk_count_z;
415 }
416
417 // Scans a dense 4-byte flag column (16 chunks per cache line) instead of
418 // streaming ChunkMeta structs (audit 2026-07-11 M5).
419 void collect_matching_chunks(std::uint32_t flags,
420 const std::vector<std::uint32_t>& column,
421 std::vector<ChunkKey>& out) const {
422 for (std::uint64_t key = 0; key < chunk_count; ++key) {
423 if ((column[static_cast<std::size_t>(key)] & flags) != 0) {
424 out.push_back(ChunkKey{key});
425 }
426 }
427 }
428
429 std::vector<page_type> pages_;
430 std::vector<ChunkMeta> metadata_;
431 std::vector<std::uint32_t> dirty_flags_;
432 std::vector<std::uint32_t> active_flags_;
433 std::vector<Box3> dirty_bounds_;
434};
435
437template <typename Shape, typename Schema>
438using AlwaysResidentWorld = World<Shape, Schema, AlwaysResident>;
439
440} // namespace tess
Definition chunk_page.h:108
auto field_span(ChunkKey key) const noexcept
Definition world.h:406
auto try_meta(ChunkKey key) noexcept -> ChunkMeta *
Definition world.h:169
auto field(Coord3 coord) const noexcept -> const Schema::template value_type< Tag > &
Definition world.h:361
auto resolve(Coord3 coord) const noexcept -> ResolvedTile< Shape >
Definition world.h:328
void collect_dirty_chunks(std::uint32_t flags, std::vector< ChunkKey > &out) const
Definition world.h:293
auto try_resolve(Coord3 coord) const noexcept -> std::optional< ResolvedTile< Shape > >
Definition world.h:339
auto field(Coord3 coord) noexcept -> Schema::template value_type< Tag > &
Definition world.h:352
auto chunk(ChunkKey key) const noexcept -> const page_type &
Definition world.h:92
auto dirty_chunks(std::uint32_t flags) const -> std::vector< ChunkKey >
Definition world.h:309
auto chunks() noexcept -> std::span< page_type >
Definition world.h:73
void collect_active_chunks(std::uint32_t flags, std::vector< ChunkKey > &out) const
Definition world.h:303
auto meta(ChunkKey key) noexcept -> ChunkMeta &
Definition world.h:149
auto try_chunk(ChunkCoord3 coord) const noexcept -> const page_type *
Definition world.h:137
auto try_chunk(ChunkCoord3 coord) noexcept -> page_type *
Definition world.h:129
auto try_field(Coord3 coord) noexcept -> Schema::template value_type< Tag > *
Definition world.h:373
auto try_field(Coord3 coord) const noexcept -> const Schema::template value_type< Tag > *
Definition world.h:385
auto try_chunk(ChunkKey key) noexcept -> page_type *
Definition world.h:112
auto try_chunk(ChunkKey key) const noexcept -> const page_type *
Definition world.h:120
auto chunk(ChunkCoord3 coord) const noexcept -> const page_type &
Definition world.h:106
auto field_span(ChunkKey key) noexcept
Definition world.h:400
auto chunk(ChunkCoord3 coord) noexcept -> page_type &
Definition world.h:101
auto chunks() const noexcept -> std::span< const page_type >
Definition world.h:78
auto active_chunks(std::uint32_t flags) const -> std::vector< ChunkKey >
Definition world.h:317
auto chunk(ChunkKey key) noexcept -> page_type &
Definition world.h:86
Definition world.h:22
Definition world.h:18
Definition shape.h:75
Definition shape.h:39
Definition shape.h:67
Definition chunk_meta.h:26
Definition shape.h:30
Definition shape.h:88
Definition shape.h:244
Definition shape.h:225