tess 1.0.0
Performance-first tile and path simulation substrate
Loading...
Searching...
No Matches
area.h
1#pragma once
2
3#include <tess/topology/topology.h>
4
5#include <algorithm>
6#include <cstddef>
7#include <cstdint>
8#include <functional>
9#include <span>
10#include <vector>
11
12namespace tess {
13
15struct AreaId {
16 std::uint32_t value = 0;
17
18 friend constexpr bool operator==(AreaId lhs, AreaId rhs) noexcept = default;
19};
20
22inline constexpr AreaId invalid_area_id{};
23
26 AreaId id{};
27 std::uint64_t key = 0;
28 std::size_t region_count = 0;
29 std::size_t tile_count = 0;
30 Box3 bounds{};
31};
32
35 AreaId first{};
36 AreaId second{};
37 std::size_t directed_portal_count = 0;
38};
39
41enum class AreaBuildStatus : std::uint8_t {
42 Built,
43 TooManyAreas,
44};
45
48 AreaBuildStatus status = AreaBuildStatus::Built;
49 std::size_t area_count = 0;
50 std::size_t connection_count = 0;
51};
52
55 public:
56 void reserve(std::size_t region_count, std::size_t portal_count) {
57 region_keys_.reserve(region_count);
58 unique_keys_.reserve(region_count);
59 edge_keys_.reserve(portal_count);
60 }
61
62 private:
63 template <typename Residency, typename Grouper>
64 friend auto build_area_index(const RegionGraphT<Residency>& graph,
65 Grouper&& grouper, AreaIndexScratch& scratch,
66 class AreaIndex& index) -> AreaBuildResult;
67
68 std::vector<std::uint64_t> region_keys_;
69 std::vector<std::uint64_t> unique_keys_;
70 std::vector<std::uint64_t> edge_keys_;
71};
72
73namespace detail {
74
75inline void extend_area_bounds(Box3& current, Box3 addition,
76 bool first) noexcept {
77 if (first) {
78 current = addition;
79 return;
80 }
81 const auto current_end = Coord3{
82 current.origin.x + static_cast<std::int64_t>(current.extent.x),
83 current.origin.y + static_cast<std::int64_t>(current.extent.y),
84 current.origin.z + static_cast<std::int64_t>(current.extent.z),
85 };
86 const auto addition_end = Coord3{
87 addition.origin.x + static_cast<std::int64_t>(addition.extent.x),
88 addition.origin.y + static_cast<std::int64_t>(addition.extent.y),
89 addition.origin.z + static_cast<std::int64_t>(addition.extent.z),
90 };
91 const auto origin = Coord3{
92 std::min(current.origin.x, addition.origin.x),
93 std::min(current.origin.y, addition.origin.y),
94 std::min(current.origin.z, addition.origin.z),
95 };
96 const auto end = Coord3{
97 std::max(current_end.x, addition_end.x),
98 std::max(current_end.y, addition_end.y),
99 std::max(current_end.z, addition_end.z),
100 };
101 current = Box3{
102 origin,
103 Extent3{static_cast<std::uint64_t>(end.x - origin.x),
104 static_cast<std::uint64_t>(end.y - origin.y),
105 static_cast<std::uint64_t>(end.z - origin.z)},
106 };
107}
108
109} // namespace detail
110
119 public:
120 void reserve(std::size_t region_count, std::size_t portal_count) {
121 region_refs_.reserve(region_count);
122 region_areas_.reserve(region_count);
123 areas_.reserve(region_count);
124 connections_.reserve(portal_count);
125 }
126
127 void clear() noexcept {
128 region_refs_.clear();
129 region_areas_.clear();
130 areas_.clear();
131 connections_.clear();
132 graph_identity_ = nullptr;
133 graph_revision_ = 0;
134 }
135
136 [[nodiscard]] auto areas() const noexcept -> std::span<const AreaSummary> {
137 return areas_;
138 }
139
140 [[nodiscard]] auto connections() const noexcept
141 -> std::span<const AreaConnection> {
142 return connections_;
143 }
144
145 [[nodiscard]] auto area_of(RegionRef region) const noexcept -> AreaId {
146 const auto it =
147 std::lower_bound(region_refs_.begin(), region_refs_.end(), region,
148 [](RegionRef lhs, RegionRef rhs) {
149 return lhs.chunk.value < rhs.chunk.value ||
150 (lhs.chunk.value == rhs.chunk.value &&
151 lhs.region.value < rhs.region.value);
152 });
153 if (it == region_refs_.end() || *it != region) {
154 return invalid_area_id;
155 }
156 return region_areas_[static_cast<std::size_t>(it - region_refs_.begin())];
157 }
158
159 template <typename Shape, typename Residency>
160 [[nodiscard]] auto area_of(const RegionGraphT<Residency>& graph,
161 Coord3 coord) const noexcept -> AreaId {
162 if (!is_valid(graph)) {
163 return invalid_area_id;
164 }
165 return area_of(graph.template region_of<Shape>(coord));
166 }
167
168 template <typename Residency>
169 [[nodiscard]] auto is_valid(
170 const RegionGraphT<Residency>& graph) const noexcept -> bool {
171 // The address is an object-lifetime identity, not a content hash. The
172 // class contract excludes comparing against a new graph constructed later
173 // in the same storage, where address and revision could both repeat.
174 return graph_identity_ == static_cast<const void*>(&graph) &&
175 graph_revision_ == graph.revision();
176 }
177
178 private:
179 template <typename Residency, typename Grouper>
180 friend auto build_area_index(const RegionGraphT<Residency>& graph,
181 Grouper&& grouper, AreaIndexScratch& scratch,
182 AreaIndex& index) -> AreaBuildResult;
183
184 std::vector<RegionRef> region_refs_;
185 std::vector<AreaId> region_areas_;
186 std::vector<AreaSummary> areas_;
187 std::vector<AreaConnection> connections_;
188 const void* graph_identity_ = nullptr;
189 std::uint64_t graph_revision_ = 0;
190};
191
193template <typename Residency, typename Grouper>
194[[nodiscard]] auto build_area_index(const RegionGraphT<Residency>& graph,
195 Grouper&& grouper,
196 AreaIndexScratch& scratch, AreaIndex& index)
197 -> AreaBuildResult {
198 const auto region_count = static_cast<std::size_t>(graph.region_count());
199 index.clear();
200 scratch.region_keys_.assign(region_count, 0);
201 scratch.unique_keys_.clear();
202 index.region_refs_.resize(region_count);
203 index.region_areas_.assign(region_count, invalid_area_id);
204
205 for (const auto& topology : graph.local_topologies()) {
206 for (const auto& region : topology.regions()) {
207 const auto ref = RegionRef{topology.chunk(), region.id};
208 const auto offset = graph.region_index(ref);
209 if (offset == invalid_region_index) {
210 continue;
211 }
212 const auto key =
213 static_cast<std::uint64_t>(std::invoke(grouper, ref, region));
214 index.region_refs_[offset] = ref;
215 scratch.region_keys_[offset] = key;
216 if (key != 0) {
217 scratch.unique_keys_.push_back(key);
218 }
219 }
220 }
221
222 std::sort(scratch.unique_keys_.begin(), scratch.unique_keys_.end());
223 scratch.unique_keys_.erase(
224 std::unique(scratch.unique_keys_.begin(), scratch.unique_keys_.end()),
225 scratch.unique_keys_.end());
226 if (scratch.unique_keys_.size() >= invalid_region_index) {
227 return {AreaBuildStatus::TooManyAreas, 0, 0};
228 }
229 index.areas_.resize(scratch.unique_keys_.size());
230 for (std::size_t i = 0; i < scratch.unique_keys_.size(); ++i) {
231 index.areas_[i].id = AreaId{static_cast<std::uint32_t>(i + 1)};
232 index.areas_[i].key = scratch.unique_keys_[i];
233 }
234
235 for (const auto& topology : graph.local_topologies()) {
236 for (const auto& region : topology.regions()) {
237 const auto ref = RegionRef{topology.chunk(), region.id};
238 const auto offset = graph.region_index(ref);
239 // Both passes traverse the same immutable graph. Keep the defensive
240 // sentinel guard in each pass so a violated graph invariant cannot turn
241 // into an out-of-bounds index in release builds.
242 if (offset == invalid_region_index) {
243 continue;
244 }
245 const auto key = scratch.region_keys_[offset];
246 if (key == 0) {
247 continue;
248 }
249 const auto key_it = std::lower_bound(scratch.unique_keys_.begin(),
250 scratch.unique_keys_.end(), key);
251 const auto area_offset =
252 static_cast<std::size_t>(key_it - scratch.unique_keys_.begin());
253 const auto id = AreaId{static_cast<std::uint32_t>(area_offset + 1)};
254 index.region_areas_[offset] = id;
255 auto& area = index.areas_[area_offset];
256 detail::extend_area_bounds(area.bounds, region.bounds,
257 area.region_count == 0);
258 ++area.region_count;
259 area.tile_count += region.tile_count;
260 }
261 }
262
263 scratch.edge_keys_.clear();
264 for (const auto& portal : graph.portals()) {
265 const auto from_index = graph.region_index(portal.from);
266 const auto to_index = graph.region_index(portal.to);
267 // A valid graph never carries a dangling portal. Keep malformed derived
268 // state from turning that invariant violation into release-mode OOB.
269 if (from_index == invalid_region_index ||
270 to_index == invalid_region_index) {
271 continue;
272 }
273 const auto from = index.region_areas_[from_index];
274 const auto to = index.region_areas_[to_index];
275 if (from == invalid_area_id || to == invalid_area_id || from == to) {
276 continue;
277 }
278 const auto first = std::min(from.value, to.value);
279 const auto second = std::max(from.value, to.value);
280 scratch.edge_keys_.push_back((static_cast<std::uint64_t>(first) << 32U) |
281 second);
282 }
283 std::sort(scratch.edge_keys_.begin(), scratch.edge_keys_.end());
284 for (std::size_t begin = 0; begin < scratch.edge_keys_.size();) {
285 auto end = begin + 1;
286 while (end < scratch.edge_keys_.size() &&
287 scratch.edge_keys_[end] == scratch.edge_keys_[begin]) {
288 ++end;
289 }
290 const auto edge = scratch.edge_keys_[begin];
291 index.connections_.push_back(
292 AreaConnection{AreaId{static_cast<std::uint32_t>(edge >> 32U)},
293 AreaId{static_cast<std::uint32_t>(edge)}, end - begin});
294 begin = end;
295 }
296
297 index.graph_identity_ = static_cast<const void*>(&graph);
298 index.graph_revision_ = graph.revision();
299 return {AreaBuildStatus::Built, index.areas_.size(),
300 index.connections_.size()};
301}
302
303} // namespace tess
Reusable temporary storage for area grouping and adjacency construction.
Definition area.h:54
friend auto build_area_index(const RegionGraphT< Residency > &graph, Grouper &&grouper, AreaIndexScratch &scratch, class AreaIndex &index) -> AreaBuildResult
Groups graph regions by a caller-supplied nonzero 64-bit semantic key.
Definition area.h:194
Definition area.h:118
friend auto build_area_index(const RegionGraphT< Residency > &graph, Grouper &&grouper, AreaIndexScratch &scratch, AreaIndex &index) -> AreaBuildResult
Groups graph regions by a caller-supplied nonzero 64-bit semantic key.
Definition area.h:194
Region graph storage specialized by dense or sparse residency policy.
Definition topology.h:382
auto revision() const noexcept -> std::uint64_t
Monotonic identity for the graph's current derived contents.
Definition topology.h:419
Counts returned by build_area_index.
Definition area.h:47
Undirected area adjacency summarized from directed region portals.
Definition area.h:34
Identifier scoped to one built AreaIndex; zero means no area.
Definition area.h:15
Aggregates regions that a caller assigned the same nonzero key.
Definition area.h:25
Definition shape.h:94
Definition shape.h:46
Reference scoped to a local region within a specific chunk topology.
Definition topology.h:127