tess 0.4.0
Performance-first tile and path simulation substrate
Loading...
Searching...
No Matches
chunk_meta.h
1#pragma once
2
3#include <tess/core/shape.h>
4
5#include <bit>
6#include <cstdint>
7#include <limits>
8
9namespace tess {
10
12enum class ChunkState : std::uint8_t {
13 ResidentSleeping,
14 ResidentActive,
15};
16static_assert(sizeof(ChunkState) == sizeof(std::uint8_t));
17
26struct ChunkMeta {
27 ChunkState state = ChunkState::ResidentSleeping;
28 std::uint32_t version = 0;
29 std::uint32_t topology_version = 0;
30 std::uint32_t active_count = 0;
31 std::uint32_t entity_count = 0;
32};
33
42 std::uint32_t flags = 0;
43 Box3 bounds{};
44 std::uint32_t version = 0;
45};
46
47namespace detail {
48
49[[nodiscard]] constexpr std::uint32_t popcount(std::uint32_t flags) noexcept {
50 // Single POPCNT/CNT instruction instead of the old 32-iteration bit
51 // loop; runs on every occupancy/state edit (audit 2026-07-11 low).
52 return static_cast<std::uint32_t>(std::popcount(flags));
53}
54
55// An extent >= 2^63 would flip the int64 cast negative (and a large origin
56// plus extent would overflow), corrupting dirty-bounds unions; saturate the
57// axis end at the int64 maximum instead.
58[[nodiscard]] constexpr std::int64_t box_axis_end(
59 std::int64_t origin, std::uint64_t extent) noexcept {
60 constexpr auto max = std::numeric_limits<std::int64_t>::max();
61 if (extent > static_cast<std::uint64_t>(max)) {
62 return max;
63 }
64 const auto delta = static_cast<std::int64_t>(extent);
65 return origin > max - delta ? max : origin + delta;
66}
67
68[[nodiscard]] constexpr std::int64_t box_min(std::int64_t lhs,
69 std::int64_t rhs) noexcept {
70 return lhs < rhs ? lhs : rhs;
71}
72
73[[nodiscard]] constexpr std::int64_t box_max(std::int64_t lhs,
74 std::int64_t rhs) noexcept {
75 return lhs < rhs ? rhs : lhs;
76}
77
78[[nodiscard]] constexpr Box3 union_box(Box3 lhs, Box3 rhs) noexcept {
79 const auto min_x = box_min(lhs.origin.x, rhs.origin.x);
80 const auto min_y = box_min(lhs.origin.y, rhs.origin.y);
81 const auto min_z = box_min(lhs.origin.z, rhs.origin.z);
82 const auto max_x = box_max(box_axis_end(lhs.origin.x, lhs.extent.x),
83 box_axis_end(rhs.origin.x, rhs.extent.x));
84 const auto max_y = box_max(box_axis_end(lhs.origin.y, lhs.extent.y),
85 box_axis_end(rhs.origin.y, rhs.extent.y));
86 const auto max_z = box_max(box_axis_end(lhs.origin.z, lhs.extent.z),
87 box_axis_end(rhs.origin.z, rhs.extent.z));
88 return Box3{
89 Coord3{min_x, min_y, min_z},
90 // max >= min on every axis; abs_delta subtracts in unsigned space, so a
91 // saturated end paired with a negative origin cannot overflow int64.
92 Extent3{
93 abs_delta(max_x, min_x),
94 abs_delta(max_y, min_y),
95 abs_delta(max_z, min_z),
96 },
97 };
98}
99
100// Mutation helpers shared by the AlwaysResident and SparseResident worlds so
101// both maintain identical dirty/active/version semantics. The flag word and
102// bounds live in the worlds' SoA columns (see ChunkMeta's comment), so the
103// helpers take them by reference alongside the residual struct.
104
105inline void meta_mark_dirty(std::uint32_t& dirty_flags, Box3& dirty_bounds,
106 ChunkMeta& meta, std::uint32_t flags,
107 Box3 bounds) noexcept {
108 if (flags == 0) {
109 return;
110 }
111 if (dirty_flags == 0) {
112 dirty_bounds = bounds;
113 } else {
114 dirty_bounds = union_box(dirty_bounds, bounds);
115 }
116 dirty_flags |= flags;
117 ++meta.version;
118}
119
120inline void meta_clear_dirty(std::uint32_t& dirty_flags, Box3& dirty_bounds,
121 std::uint32_t flags) noexcept {
122 if (flags == 0) {
123 return;
124 }
125 dirty_flags &= ~flags;
126 if (dirty_flags == 0) {
127 dirty_bounds = {};
128 }
129}
130
131[[nodiscard]] inline DirtyObservation meta_observe_dirty(
132 std::uint32_t dirty_flags, Box3 dirty_bounds, const ChunkMeta& meta,
133 std::uint32_t flags) noexcept {
134 return DirtyObservation{
135 dirty_flags & flags,
136 dirty_bounds,
137 meta.version,
138 };
139}
140
141inline bool meta_clear_dirty_observed(std::uint32_t& dirty_flags,
142 Box3& dirty_bounds, const ChunkMeta& meta,
143 DirtyObservation observed) noexcept {
144 if (meta.version != observed.version) {
145 return false;
146 }
147 meta_clear_dirty(dirty_flags, dirty_bounds, observed.flags);
148 return true;
149}
150
151inline void meta_mark_active(std::uint32_t& active_flags, ChunkMeta& meta,
152 std::uint32_t flags) noexcept {
153 if (flags == 0) {
154 return;
155 }
156 const auto before = active_flags;
157 active_flags |= flags;
158 meta.active_count = popcount(active_flags);
159 if (before == 0 && active_flags != 0) {
160 meta.state = ChunkState::ResidentActive;
161 }
162}
163
164inline void meta_clear_active(std::uint32_t& active_flags, ChunkMeta& meta,
165 std::uint32_t flags) noexcept {
166 if (flags == 0) {
167 return;
168 }
169 active_flags &= ~flags;
170 meta.active_count = popcount(active_flags);
171 if (active_flags == 0) {
172 meta.state = ChunkState::ResidentSleeping;
173 }
174}
175
176} // namespace detail
177
178} // namespace tess
Definition shape.h:75
Definition chunk_meta.h:26
Definition chunk_meta.h:41