tess 1.0.0
Performance-first tile and path simulation substrate
Loading...
Searching...
No Matches
node_index_space.h
1#pragma once
2
3#include <tess/core/shape.h>
4#include <tess/storage/residency.h>
5#include <tess/storage/world.h>
6
7#include <cstddef>
8#include <cstdint>
9#include <limits>
10
11namespace tess::detail {
12
13// Maps a global tile index (0..tile_count) to a compact node-array offset and
14// reports the node-array capacity a search must allocate. For the dense
15// AlwaysResident world the map is the identity and the capacity is the whole
16// tile count, so A* node storage and indexing are byte-identical to indexing
17// the arrays by the raw tile index. The sparse specialization remaps through
18// the resident chunk directory so node arrays are
19// bounded by the residency budget rather than the global tile count, letting
20// A* run over worlds far too large to index densely.
21template <typename World, typename Residency = typename World::residency_type>
22struct NodeIndexSpace {
23 static_assert(sizeof(World) == 0,
24 "NodeIndexSpace has no specialization for this residency "
25 "policy. AlwaysResident and SparseResident are both "
26 "specialized below; a custom residency policy needs its "
27 "own specialization.");
28};
29
30template <typename World>
31struct NodeIndexSpace<World, AlwaysResident> {
32 // Every chunk of a dense world is resident, so the map is the identity and
33 // A* can index its node arrays by the raw tile index. is_dense lets the
34 // shared search code drop residency guards entirely under `if constexpr`,
35 // keeping the dense path byte-identical.
36 static constexpr bool is_dense = true;
37
38 explicit NodeIndexSpace(const World& /*world*/) noexcept {}
39
40 [[nodiscard]] constexpr bool is_resident_index(
41 std::uint64_t /*index*/) const noexcept {
42 return true;
43 }
44
45 [[nodiscard]] constexpr std::size_t offset(
46 std::uint64_t index) const noexcept {
47 return static_cast<std::size_t>(index);
48 }
49
50 // Dense identity: never npos (mirrors the sparse combined probe).
51 static constexpr std::size_t npos_offset =
52 std::numeric_limits<std::size_t>::max();
53
54 [[nodiscard]] constexpr std::size_t resident_offset(
55 std::uint64_t index) const noexcept {
56 return static_cast<std::size_t>(index);
57 }
58
59 [[nodiscard]] constexpr std::size_t capacity_hint() const noexcept {
60 static_assert(World::chunk_count <=
61 std::numeric_limits<std::size_t>::max() /
62 World::local_tile_count);
63 return static_cast<std::size_t>(World::chunk_count *
64 World::local_tile_count);
65 }
66};
67
68// Sparse specialization: node-array offsets are bounded by the residency
69// budget rather than the global tile count. A tile's offset is its resident
70// chunk slot times the per-chunk tile count plus the tile's chunk-local id, so
71// the node arrays a search allocates are sized to `capacity * local_tile_count`
72// no matter how enormous the shape is. offset must only be called for a
73// resident tile (guard with is_resident_index first); the slot mapping is
74// unchanged for one search because the world is const.
75template <typename World>
76struct NodeIndexSpace<World, SparseResident> {
77 using shape_type = typename World::shape_type;
78 static constexpr bool is_dense = false;
79 static constexpr std::uint64_t local_tile_count = World::local_tile_count;
80
81 explicit NodeIndexSpace(const World& world) noexcept : world_(&world) {}
82
83 [[nodiscard]] bool is_resident_index(std::uint64_t index) const noexcept {
84 return world_->resident_slot(chunk_key_of(index)) != World::npos_slot;
85 }
86
87 [[nodiscard]] std::size_t offset(std::uint64_t index) const noexcept {
88 const auto slot = world_->resident_slot(chunk_key_of(index));
89 return slot * static_cast<std::size_t>(local_tile_count) + local_of(index);
90 }
91
92 static constexpr std::size_t npos_offset =
93 std::numeric_limits<std::size_t>::max();
94
95 // Residency test and offset in ONE directory probe (npos_offset when the
96 // chunk is not resident) -- the split is_resident_index-then-offset pair
97 // paid two probes per neighbor in the hottest search loops.
98 [[nodiscard]] std::size_t resident_offset(
99 std::uint64_t index) const noexcept {
100 const auto slot = world_->resident_slot(chunk_key_of(index));
101 if (slot == World::npos_slot) {
102 return npos_offset;
103 }
104 return slot * static_cast<std::size_t>(local_tile_count) + local_of(index);
105 }
106
107 [[nodiscard]] std::size_t capacity_hint() const noexcept {
108 return world_->capacity() * static_cast<std::size_t>(local_tile_count);
109 }
110
111 private:
112 using Storage = typename ShapeTraits<shape_type>::TileKeyStorage;
113
114 [[nodiscard]] static ChunkKey chunk_key_of(std::uint64_t index) noexcept {
115 return chunk_key<shape_type>(
116 TileKey<shape_type>{static_cast<Storage>(index)});
117 }
118
119 [[nodiscard]] static std::size_t local_of(std::uint64_t index) noexcept {
120 return static_cast<std::size_t>(
121 local_tile_id<shape_type>(
122 TileKey<shape_type>{static_cast<Storage>(index)})
123 .value);
124 }
125
126 const World* world_;
127};
128
129} // namespace tess::detail