tess 1.0.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#include <tess/storage/metadata_types.h>
5
6#include <bit>
7#include <cstdint>
8#include <limits>
9
10namespace tess {
11
13enum class ChunkActivity : std::uint8_t {
14 Sleeping,
15 Active,
16};
17static_assert(sizeof(ChunkActivity) == sizeof(std::uint8_t));
18
27struct ChunkMeta {
28 ContentVersion content_version{};
29 TopologyVersion topology_version{};
30 std::uint32_t entity_count = 0;
31};
32
47 DirtyMask mask{};
48 Box3 bounds{};
49 ContentVersion content_version{};
50 ResidencyGeneration residency_generation{};
51};
52
53namespace detail {
54
55[[nodiscard]] constexpr std::uint32_t popcount(ActiveMask mask) noexcept {
56 // Single POPCNT/CNT instruction instead of the old 32-iteration bit
57 // loop; runs on every occupancy/state edit.
58 return static_cast<std::uint32_t>(std::popcount(mask.value));
59}
60
61// An extent >= 2^63 would flip the int64 cast negative (and a large origin
62// plus extent would overflow), corrupting dirty-bounds unions; saturate the
63// axis end at the int64 maximum instead.
64[[nodiscard]] constexpr std::int64_t box_axis_end(
65 std::int64_t origin, std::uint64_t extent) noexcept {
66 constexpr auto max = std::numeric_limits<std::int64_t>::max();
67 if (extent > static_cast<std::uint64_t>(max)) {
68 return max;
69 }
70 const auto delta = static_cast<std::int64_t>(extent);
71 return origin > max - delta ? max : origin + delta;
72}
73
74[[nodiscard]] constexpr std::int64_t box_min(std::int64_t lhs,
75 std::int64_t rhs) noexcept {
76 return lhs < rhs ? lhs : rhs;
77}
78
79[[nodiscard]] constexpr std::int64_t box_max(std::int64_t lhs,
80 std::int64_t rhs) noexcept {
81 return lhs < rhs ? rhs : lhs;
82}
83
84[[nodiscard]] constexpr Box3 union_box(Box3 lhs, Box3 rhs) noexcept {
85 const auto min_x = box_min(lhs.origin.x, rhs.origin.x);
86 const auto min_y = box_min(lhs.origin.y, rhs.origin.y);
87 const auto min_z = box_min(lhs.origin.z, rhs.origin.z);
88 const auto max_x = box_max(box_axis_end(lhs.origin.x, lhs.extent.x),
89 box_axis_end(rhs.origin.x, rhs.extent.x));
90 const auto max_y = box_max(box_axis_end(lhs.origin.y, lhs.extent.y),
91 box_axis_end(rhs.origin.y, rhs.extent.y));
92 const auto max_z = box_max(box_axis_end(lhs.origin.z, lhs.extent.z),
93 box_axis_end(rhs.origin.z, rhs.extent.z));
94 return Box3{
95 Coord3{min_x, min_y, min_z},
96 // max >= min on every axis; abs_delta subtracts in unsigned space, so a
97 // saturated end paired with a negative origin cannot overflow int64.
98 Extent3{
99 abs_delta(max_x, min_x),
100 abs_delta(max_y, min_y),
101 abs_delta(max_z, min_z),
102 },
103 };
104}
105
106// Mutation helpers shared by the AlwaysResident and SparseResident worlds so
107// both maintain identical dirty-mask, active-mask, and content-version
108// semantics. The mask word and bounds live in the worlds' SoA columns (see
109// ChunkMeta's comment), so the helpers take them by reference alongside the
110// residual struct.
111
112inline void meta_mark_dirty(DirtyMask& dirty_mask, Box3& dirty_bounds,
113 ChunkMeta& meta, DirtyMask mask,
114 Box3 bounds) noexcept {
115 if (mask.empty()) {
116 return;
117 }
118 if (dirty_mask.empty()) {
119 dirty_bounds = bounds;
120 } else {
121 dirty_bounds = union_box(dirty_bounds, bounds);
122 }
123 dirty_mask |= mask;
124 ++meta.content_version;
125}
126
127inline void meta_mark_content_changed(ChunkMeta& meta) noexcept {
128 ++meta.content_version;
129}
130
131inline void meta_clear_dirty(DirtyMask& dirty_mask, Box3& dirty_bounds,
132 DirtyMask mask) noexcept {
133 if (mask.empty()) {
134 return;
135 }
136 dirty_mask &= ~mask;
137 if (dirty_mask.empty()) {
138 dirty_bounds = {};
139 }
140}
141
142[[nodiscard]] inline DirtyObservation meta_observe_dirty(
143 DirtyMask dirty_mask, Box3 dirty_bounds, const ChunkMeta& meta,
144 DirtyMask mask, ResidencyGeneration residency_generation = {}) noexcept {
145 return DirtyObservation{
146 dirty_mask & mask,
147 dirty_bounds,
148 meta.content_version,
149 residency_generation,
150 };
151}
152
153inline bool meta_clear_dirty_observed(
154 DirtyMask& dirty_mask, Box3& dirty_bounds, const ChunkMeta& meta,
155 DirtyObservation observed,
156 ResidencyGeneration residency_generation = {}) noexcept {
157 // A rematerialized sparse chunk restarts its content version at zero, so an
158 // observation from an earlier residency interval can compare equal to a
159 // mark made after rematerialization. Reject it before the content-version
160 // check.
161 if (residency_generation != observed.residency_generation) {
162 return false;
163 }
164 if (meta.content_version != observed.content_version) {
165 return false;
166 }
167 meta_clear_dirty(dirty_mask, dirty_bounds, observed.mask);
168 return true;
169}
170
171inline void meta_mark_active(ActiveMask& active_mask,
172 ActiveMask mask) noexcept {
173 if (mask.empty()) {
174 return;
175 }
176 active_mask |= mask;
177}
178
179inline void meta_clear_active(ActiveMask& active_mask,
180 ActiveMask mask) noexcept {
181 if (mask.empty()) {
182 return;
183 }
184 active_mask &= ~mask;
185}
186
187} // namespace detail
188
189} // namespace tess
Definition metadata_types.h:49
Definition shape.h:94
Definition chunk_meta.h:27
Definition metadata_types.h:86
Definition metadata_types.h:12
Definition chunk_meta.h:46
Definition metadata_types.h:118
Definition metadata_types.h:102