3#include <tess/core/assert.h>
4#include <tess/core/config.h>
5#include <tess/core/fail_fast.h>
6#include <tess/core/shape.h>
7#include <tess/core/tag_identity.h>
8#include <tess/path/request.h>
9#include <tess/storage/residency.h>
10#include <tess/storage/world.h>
11#include <tess/topology/movement_class.h>
12#include <tess/topology/transition_model.h>
13#include <tess/topology/transition_provider.h>
30template <
typename Res
idency>
39 std::uint32_t value = 0;
51inline constexpr std::uint32_t invalid_region_index =
52 std::numeric_limits<std::uint32_t>::max();
55enum class BoundaryFace : std::uint8_t {
67enum class TopologyStatus : std::uint8_t {
76 std::size_t tile_count = 0;
78 std::size_t boundary_exit_count = 0;
80 friend constexpr bool operator==(
const LocalRegion& lhs,
89 BoundaryFace face = BoundaryFace::NegativeX;
111 std::size_t region_count = 0;
112 std::size_t passable_tile_count = 0;
113 std::size_t boundary_exit_count = 0;
114 std::uint64_t topology_version_sum = 0;
119 TopologyStatus status = TopologyStatus::Built;
120 std::size_t region_count = 0;
121 std::size_t passable_tile_count = 0;
122 std::size_t boundary_exit_count = 0;
123 std::uint64_t topology_version_sum = 0;
131 friend constexpr bool operator==(
RegionRef lhs,
141 BoundaryFace face = BoundaryFace::NegativeX;
143 friend constexpr bool operator==(
const RegionPortal& lhs,
148enum class ReachabilityStatus : std::uint8_t {
164 ReachabilityStatus status = ReachabilityStatus::Unreachable;
165 std::size_t visited_regions = 0;
170 ReachabilityStatus status = ReachabilityStatus::Unreachable;
171 std::size_t visited_regions = 0;
172 std::span<const RegionRef> regions;
173 std::span<const RegionPortal> portals;
174 std::span<const ChunkKey> chunks;
181 void reserve_tiles(std::size_t count) { stack_.reserve(count); }
183 [[nodiscard]]
auto capacity()
const noexcept -> std::size_t {
184 return stack_.capacity();
188 template <
typename World,
typename PassableTag>
194 std::vector<LocalTileId> stack_;
200 void reserve_regions(std::size_t count) {
201 frontier_.reserve(count);
202 visited_epoch_.reserve(count);
203 parent_.reserve(count);
204 parent_portal_.reserve(count);
205 path_regions_.reserve(count);
206 path_portals_.reserve(count);
207 corridor_chunks_.reserve(count);
210 [[nodiscard]]
auto capacity()
const noexcept -> std::size_t {
211 return frontier_.capacity();
215 template <
typename Shape,
typename Res
idency>
220 template <
typename Shape,
typename Res
idency>
228 void begin_traversal(std::size_t region_count) {
230 path_regions_.clear();
231 path_portals_.clear();
232 corridor_chunks_.clear();
233 if (visited_epoch_.size() < region_count) {
234 visited_epoch_.resize(region_count, 0);
238 std::fill(visited_epoch_.begin(), visited_epoch_.end(), 0);
243 [[nodiscard]]
auto is_visited(std::uint32_t region_index)
const noexcept
245 return visited_epoch_[
static_cast<std::size_t
>(region_index)] == epoch_;
248 void visit(std::uint32_t region_index)
noexcept {
249 visited_epoch_[
static_cast<std::size_t
>(region_index)] = epoch_;
252 std::vector<std::uint32_t> frontier_;
253 std::vector<std::uint32_t> visited_epoch_;
254 std::vector<std::uint32_t> parent_;
255 std::vector<std::uint32_t> parent_portal_;
256 std::vector<RegionRef> path_regions_;
257 std::vector<RegionPortal> path_portals_;
258 std::vector<ChunkKey> corridor_chunks_;
259 std::uint32_t epoch_ = 0;
265 void clear()
noexcept {
268 topology_version_ = {};
271 boundary_exits_.clear();
274 [[nodiscard]]
auto chunk()
const noexcept ->
ChunkKey {
return chunk_; }
276 [[nodiscard]]
auto chunk_coord()
const noexcept ->
ChunkCoord3 {
280 [[nodiscard]]
auto topology_version()
const noexcept ->
TopologyVersion {
281 return topology_version_;
284 [[nodiscard]]
auto region_ids()
const noexcept
285 -> std::span<const LocalRegionId> {
286 return {region_ids_.data(), region_ids_.size()};
289 [[nodiscard]]
auto regions()
const noexcept -> std::span<const LocalRegion> {
290 return {regions_.data(), regions_.size()};
298 if (
id.value == 0 ||
id.value > regions_.size()) {
301 return ®ions_[
static_cast<std::size_t
>(
id.value) - 1];
304 [[nodiscard]]
auto boundary_exits()
const noexcept
305 -> std::span<const LocalBoundaryExit> {
306 return {boundary_exits_.data(), boundary_exits_.size()};
309 [[nodiscard]]
auto region_at(
LocalTileId tile)
const noexcept
311 if (tile.value >= region_ids_.size()) {
312 return invalid_local_region;
314 return region_ids_[
static_cast<std::size_t
>(tile.value)];
317 template <
typename Shape>
318 [[nodiscard]]
auto region_at(
LocalCoord3 coord)
const noexcept
320 return region_at(local_tile_id<Shape>(coord));
324 template <
typename World,
typename PassableTag>
333 std::vector<LocalRegionId> region_ids_;
334 std::vector<LocalRegion> regions_;
335 std::vector<LocalBoundaryExit> boundary_exits_;
343template <
typename Res
idency>
344struct RegionGraphSparseData {
348 std::vector<ChunkKey> topology_keys_;
352 std::vector<std::uint8_t> region_reaches_missing_;
355 std::vector<ResidencyGeneration> frozen_generations_;
359struct RegionGraphSparseData<AlwaysResident> {};
366template <
typename Shape>
367[[nodiscard]]
constexpr bool provider_source_is_owned(Coord3 from,
368 ChunkKey chunk)
noexcept {
369 return contains<Shape>(from) &&
370 chunk_key<Shape>(chunk_coord<Shape>(from)).value == chunk.value;
381template <
typename Res
idency>
384 void clear()
noexcept {
385 local_topologies_.clear();
387 region_offsets_.clear();
388 adjacency_starts_.clear();
389 adjacency_targets_.clear();
390 adjacency_portals_.clear();
391 built_chunk_grid_ =
Extent3{0, 0, 0};
392 built_chunk_extent_ =
Extent3{0, 0, 0};
393 built_lattice_identity_ = 0;
394 built_lattice_version_ = 0;
396 built_step_policy_identity_ = 0;
397 built_cost_scale_ = 0;
399 built_provider_instance_ =
nullptr;
400 built_provider_revision_ = 0;
401 if constexpr (!std::is_same_v<Residency, AlwaysResident>) {
402 sparse_.topology_keys_.clear();
403 sparse_.region_reaches_missing_.clear();
404 sparse_.frozen_generations_.clear();
409 [[nodiscard]]
auto local_topologies()
const noexcept
410 -> std::span<const LocalChunkTopology> {
411 return {local_topologies_.data(), local_topologies_.size()};
414 [[nodiscard]]
auto portals()
const noexcept -> std::span<const RegionPortal> {
415 return {portals_.data(), portals_.size()};
419 [[nodiscard]]
auto revision() const noexcept -> std::uint64_t {
423 [[nodiscard]]
auto local_topology(
ChunkKey chunk)
const noexcept
425 if constexpr (std::is_same_v<Residency, AlwaysResident>) {
426 if (chunk.value >= local_topologies_.size()) {
429 return &local_topologies_[
static_cast<std::size_t
>(chunk.value)];
431 const auto idx = local_index(chunk);
435 return &local_topologies_[idx];
439 template <
typename Shape>
440 [[nodiscard]]
auto region_of(Coord3 coord)
const noexcept -> RegionRef {
441 if (!contains<Shape>(coord)) {
442 return RegionRef{ChunkKey{std::numeric_limits<std::uint64_t>::max()},
443 invalid_local_region};
445 const auto key = chunk_key<Shape>(chunk_coord<Shape>(coord));
446 const auto* local = local_topology(key);
447 if (local ==
nullptr) {
448 return RegionRef{key, invalid_local_region};
451 key, local->region_at(local_tile_id<Shape>(local_coord<Shape>(coord)))};
455 [[nodiscard]]
auto region_count() const noexcept -> std::uint32_t {
456 return region_offsets_.empty() ? 0U : region_offsets_.back();
465 [[nodiscard]]
auto region_index(RegionRef ref)
const noexcept
467 if constexpr (std::is_same_v<Residency, AlwaysResident>) {
468 if (ref.region == invalid_local_region ||
469 ref.chunk.value >= local_topologies_.size()) {
470 return invalid_region_index;
472 const auto chunk =
static_cast<std::size_t
>(ref.chunk.value);
473 const auto index =
static_cast<std::uint64_t
>(region_offsets_[chunk]) +
474 ref.region.value - 1;
475 if (index >= region_offsets_[chunk + 1]) {
476 return invalid_region_index;
478 return static_cast<std::uint32_t
>(index);
480 if (ref.region == invalid_local_region) {
481 return invalid_region_index;
483 const auto li = local_index(ref.chunk);
484 if (li == npos || li + 1 >= region_offsets_.size()) {
485 return invalid_region_index;
487 const auto index =
static_cast<std::uint64_t
>(region_offsets_[li]) +
488 ref.region.value - 1;
489 if (index >= region_offsets_[li + 1]) {
490 return invalid_region_index;
492 return static_cast<std::uint32_t
>(index);
502 template <
typename ClassOrTag>
503 [[nodiscard]]
auto matches_class() const noexcept ->
bool {
504 using Class = movement::movement_class_of<ClassOrTag>;
505 using Policy = movement::step_policy_of<Class>;
506 return built_class_ == detail::tag_identity<Class>() &&
507 built_step_policy_identity_ ==
508 static_cast<std::uint32_t
>(Policy::identity) &&
509 built_cost_scale_ == Policy::cost_scale;
517 template <
typename Prov
ider>
518 [[nodiscard]]
auto matches_provider() const noexcept ->
bool {
519 return built_provider_ == detail::tag_identity<Provider>();
527 template <
typename Prov
ider>
528 [[nodiscard]]
auto matches_provider(
const Provider& provider)
const noexcept
530 return matches_provider<Provider>() &&
531 built_provider_instance_ ==
532 detail::transition_provider_instance_identity(provider) &&
533 built_provider_revision_ ==
534 detail::transition_provider_revision(provider);
538 template <
typename World,
typename ClassOrTag,
typename Prov
ider>
540 const World& world, LocalTopologyScratch& scratch,
541 RegionGraphT<typename World::residency_type>& graph,
542 const Provider& provider) -> RegionGraphBuildResult;
544 template <
typename World,
typename ClassOrTag,
typename Prov
ider>
546 const World& world, LocalTopologyScratch& scratch,
547 RegionGraphT<typename World::residency_type>& graph,
548 std::span<const ChunkKey> dirty_chunks,
const Provider& provider)
549 -> TopologyBuildResult;
551 template <
typename Shape,
typename OtherRes
idency>
552 friend auto reachable(
const RegionGraphT<OtherResidency>& graph,
553 PathRequest request, RegionGraphScratch& scratch)
554 -> ReachabilityResult;
556 template <
typename Shape,
typename OtherRes
idency>
557 friend auto coarse_path(
const RegionGraphT<OtherResidency>& graph,
558 PathRequest request, RegionGraphScratch& scratch)
561 template <
typename OtherWorld>
562 friend auto is_region_graph_fresh(
563 const OtherWorld& world,
564 const RegionGraphT<typename OtherWorld::residency_type>& graph)
noexcept
570 void rebuild_region_index() {
571 region_offsets_.assign(local_topologies_.size() + 1, 0);
572 for (std::size_t i = 0; i < local_topologies_.size(); ++i) {
573 region_offsets_[i + 1] =
575 static_cast<std::uint32_t
>(local_topologies_[i].regions().size());
578 adjacency_starts_.assign(
static_cast<std::size_t
>(region_count()) + 1, 0);
579 for (
const auto& portal : portals_) {
580 ++adjacency_starts_[
static_cast<std::size_t
>(region_index(portal.from)) +
583 for (std::size_t i = 1; i < adjacency_starts_.size(); ++i) {
584 adjacency_starts_[i] += adjacency_starts_[i - 1];
587 adjacency_targets_.resize(portals_.size());
588 adjacency_portals_.resize(portals_.size());
589 auto cursor = adjacency_starts_;
590 for (std::size_t portal_index = 0; portal_index < portals_.size();
592 const auto& portal = portals_[portal_index];
593 const auto from =
static_cast<std::size_t
>(region_index(portal.from));
594 const auto edge =
static_cast<std::size_t
>(cursor[from]++);
595 adjacency_targets_[edge] = region_index(portal.to);
596 adjacency_portals_[edge] =
static_cast<std::uint32_t
>(portal_index);
599 if constexpr (!std::is_same_v<Residency, AlwaysResident>) {
605 sparse_.region_reaches_missing_.assign(
606 static_cast<std::size_t
>(region_count()), 0);
607 for (
const auto& topology : local_topologies_) {
608 for (
const auto& exit : topology.boundary_exits()) {
609 if (has_chunk(exit.target_chunk)) {
613 region_index(RegionRef{topology.chunk(), exit.region});
614 if (idx != invalid_region_index) {
615 sparse_.region_reaches_missing_[
static_cast<std::size_t
>(idx)] = 1;
627 template <
typename Shape>
628 [[nodiscard]]
auto matches_shape() const noexcept ->
bool {
629 using Traits = ShapeTraits<Shape>;
630 return built_chunk_grid_ == Extent3{Traits::chunk_count_x,
631 Traits::chunk_count_y,
632 Traits::chunk_count_z} &&
633 built_chunk_extent_ == Traits::chunk &&
634 built_lattice_identity_ ==
635 static_cast<std::uint32_t
>(Traits::lattice_identity) &&
636 built_lattice_version_ == Traits::lattice_version;
639 template <
typename Shape>
640 void bind_shape() noexcept {
641 using Traits = ShapeTraits<Shape>;
642 built_chunk_grid_ = Extent3{Traits::chunk_count_x, Traits::chunk_count_y,
643 Traits::chunk_count_z};
644 built_chunk_extent_ = Traits::chunk;
645 built_lattice_identity_ =
646 static_cast<std::uint32_t
>(Traits::lattice_identity);
647 built_lattice_version_ = Traits::lattice_version;
652 template <
typename ClassOrTag>
653 void bind_class() noexcept {
654 using Class = movement::movement_class_of<ClassOrTag>;
655 using Policy = movement::step_policy_of<Class>;
656 built_class_ = detail::tag_identity<Class>();
657 built_step_policy_identity_ =
static_cast<std::uint32_t
>(Policy::identity);
658 built_cost_scale_ = Policy::cost_scale;
661 template <
typename Prov
ider>
662 void bind_provider(
const Provider& provider)
noexcept {
663 built_provider_ = detail::tag_identity<Provider>();
664 built_provider_instance_ =
665 detail::transition_provider_instance_identity(provider);
666 built_provider_revision_ = detail::transition_provider_revision(provider);
669 void bump_revision() noexcept {
671 if (revision_ == 0) {
681 template <
typename Shape,
typename World,
typename Prov
ider>
682 void mark_provider_missing_reaches(
683 [[maybe_unused]]
const World& world,
684 [[maybe_unused]]
const Provider& provider) {
685 if constexpr (!std::is_same_v<Residency, AlwaysResident>) {
686 for (
const auto& topology : local_topologies_) {
687 provider.for_each_transition(
688 world, topology.chunk(), [&](Coord3 from, Coord3 to) {
694 if (!detail::provider_source_is_owned<Shape>(from,
698 if (!contains<Shape>(to) ||
699 has_chunk(chunk_key<Shape>(chunk_coord<Shape>(to)))) {
702 const auto source = this->
template region_of<Shape>(from);
703 if (source.region == invalid_local_region) {
706 const auto idx = region_index(source);
707 if (idx != invalid_region_index) {
708 sparse_.region_reaches_missing_[
static_cast<std::size_t
>(idx)] =
716 static constexpr std::size_t npos =
static_cast<std::size_t
>(-1);
720 [[nodiscard]]
auto local_index(
ChunkKey chunk)
const noexcept -> std::size_t {
721 const auto& keys = sparse_.topology_keys_;
722 const auto it = std::lower_bound(
723 keys.begin(), keys.end(), chunk,
725 if (it == keys.end() || it->value != chunk.value) {
728 return static_cast<std::size_t
>(it - keys.begin());
731 [[nodiscard]]
auto has_chunk(
ChunkKey chunk)
const noexcept ->
bool {
732 return local_index(chunk) != npos;
735 [[nodiscard]]
auto region_ref(std::uint32_t index)
const noexcept
737 if (index >= region_count()) {
739 invalid_local_region};
742 std::upper_bound(region_offsets_.begin(), region_offsets_.end(), index);
743 const auto local =
static_cast<std::size_t
>(
744 std::distance(region_offsets_.begin(), upper) - 1);
745 const auto chunk = [&] {
746 if constexpr (std::is_same_v<Residency, AlwaysResident>) {
747 return ChunkKey{
static_cast<std::uint64_t
>(local)};
749 return sparse_.topology_keys_[local];
756 std::vector<LocalChunkTopology> local_topologies_;
757 std::vector<RegionPortal> portals_;
758 std::vector<std::uint32_t> region_offsets_;
759 std::vector<std::uint32_t> adjacency_starts_;
760 std::vector<std::uint32_t> adjacency_targets_;
761 std::vector<std::uint32_t> adjacency_portals_;
764 Extent3 built_chunk_grid_{0, 0, 0};
765 Extent3 built_chunk_extent_{0, 0, 0};
766 std::uint32_t built_lattice_identity_ = 0;
767 std::uint32_t built_lattice_version_ = 0;
768 std::uintptr_t built_class_ = 0;
769 std::uint32_t built_step_policy_identity_ = 0;
770 std::uint32_t built_cost_scale_ = 0;
771 std::uintptr_t built_provider_ = 0;
772 const void* built_provider_instance_ =
nullptr;
773 std::uint64_t built_provider_revision_ = 0;
774 std::uint64_t revision_ = 0;
775 [[no_unique_address]] detail::RegionGraphSparseData<Residency> sparse_;
779using RegionGraph = RegionGraphT<AlwaysResident>;
781using SparseRegionGraph = RegionGraphT<SparseResident>;
785template <
typename Shape>
786[[nodiscard]]
constexpr auto local_tile_coord(LocalTileId
id)
noexcept
788 const auto chunk = ShapeTraits<Shape>::chunk;
789 const auto xy = chunk.x * chunk.y;
790 const auto z =
id.value / xy;
791 const auto remainder =
id.value % xy;
799template <
typename Shape>
800constexpr void add_boundary_exit(std::vector<LocalBoundaryExit>& exits,
801 LocalRegion& region, LocalTileId local_tile,
802 Coord3 coord, BoundaryFace face,
803 ChunkCoord3 target_chunk) {
804 exits.push_back(LocalBoundaryExit{
809 chunk_key<Shape>(target_chunk),
811 ++region.boundary_exit_count;
814template <
typename Shape>
815constexpr void add_boundary_exits(std::vector<LocalBoundaryExit>& exits,
816 ChunkCoord3 chunk_coord, LocalRegion& region,
817 LocalTileId local_tile, LocalCoord3 local,
819 const auto chunk = ShapeTraits<Shape>::chunk;
821 if constexpr (std::is_same_v<typename ShapeTraits<Shape>::lattice_type,
822 lattice::HexAxial>) {
823 detail::for_each_regular_candidate<Shape, movement::DefaultSteps>(
824 coord, [&](detail::RegularTransitionCandidate candidate) {
825 const auto target = tess::chunk_coord<Shape>(candidate.to);
826 if (target == chunk_coord) {
829 auto face = BoundaryFace::NegativeX;
830 if (candidate.to.x > coord.x && candidate.to.y < coord.y) {
831 face = BoundaryFace::PositiveXNegativeY;
832 }
else if (candidate.to.x < coord.x && candidate.to.y > coord.y) {
833 face = BoundaryFace::NegativeXPositiveY;
834 }
else if (candidate.to.x > coord.x) {
835 face = BoundaryFace::PositiveX;
836 }
else if (candidate.to.x < coord.x) {
837 face = BoundaryFace::NegativeX;
838 }
else if (candidate.to.y > coord.y) {
839 face = BoundaryFace::PositiveY;
841 face = BoundaryFace::NegativeY;
843 add_boundary_exit<Shape>(exits, region, local_tile, coord, face,
849 if (local.x == 0 && chunk_coord.x > 0) {
850 auto target = chunk_coord;
852 add_boundary_exit<Shape>(exits, region, local_tile, coord,
853 BoundaryFace::NegativeX, target);
855 if (local.x + 1 == chunk.x &&
856 chunk_coord.x + 1 < ShapeTraits<Shape>::chunk_count_x) {
857 auto target = chunk_coord;
859 add_boundary_exit<Shape>(exits, region, local_tile, coord,
860 BoundaryFace::PositiveX, target);
862 if (local.y == 0 && chunk_coord.y > 0) {
863 auto target = chunk_coord;
865 add_boundary_exit<Shape>(exits, region, local_tile, coord,
866 BoundaryFace::NegativeY, target);
868 if (local.y + 1 == chunk.y &&
869 chunk_coord.y + 1 < ShapeTraits<Shape>::chunk_count_y) {
870 auto target = chunk_coord;
872 add_boundary_exit<Shape>(exits, region, local_tile, coord,
873 BoundaryFace::PositiveY, target);
875 if (local.z == 0 && chunk_coord.z > 0) {
876 auto target = chunk_coord;
878 add_boundary_exit<Shape>(exits, region, local_tile, coord,
879 BoundaryFace::NegativeZ, target);
881 if (local.z + 1 == chunk.z &&
882 chunk_coord.z + 1 < ShapeTraits<Shape>::chunk_count_z) {
883 auto target = chunk_coord;
885 add_boundary_exit<Shape>(exits, region, local_tile, coord,
886 BoundaryFace::PositiveZ, target);
890template <
typename Shape,
typename Fn>
891constexpr void for_each_local_axis_neighbor(LocalCoord3 coord, Fn&& fn) {
892 const auto chunk = ShapeTraits<Shape>::chunk;
893 if (coord.x + 1 < chunk.x) {
894 fn(LocalCoord3{coord.x + 1, coord.y, coord.z});
897 fn(LocalCoord3{coord.x - 1, coord.y, coord.z});
899 if (coord.y + 1 < chunk.y) {
900 fn(LocalCoord3{coord.x, coord.y + 1, coord.z});
903 fn(LocalCoord3{coord.x, coord.y - 1, coord.z});
905 if constexpr (std::is_same_v<typename ShapeTraits<Shape>::lattice_type,
906 lattice::HexAxial>) {
907 if (coord.x + 1 < chunk.x && coord.y > 0) {
908 fn(LocalCoord3{coord.x + 1, coord.y - 1, 0});
910 if (coord.x > 0 && coord.y + 1 < chunk.y) {
911 fn(LocalCoord3{coord.x - 1, coord.y + 1, 0});
915 if (coord.z + 1 < chunk.z) {
916 fn(LocalCoord3{coord.x, coord.y, coord.z + 1});
919 fn(LocalCoord3{coord.x, coord.y, coord.z - 1});
923constexpr void include_coord_in_bounds(LocalRegion& region,
924 Coord3 coord)
noexcept {
925 if (region.tile_count == 0) {
926 region.bounds = Box3{coord, Extent3{1, 1, 1}};
930 const auto end = [](std::int64_t origin, std::uint64_t extent) {
933 constexpr auto max = std::numeric_limits<std::int64_t>::max();
934 if (extent >
static_cast<std::uint64_t
>(max)) {
937 const auto delta =
static_cast<std::int64_t
>(extent);
938 return origin > max - delta ? max : origin + delta;
940 const auto min = [](std::int64_t lhs, std::int64_t rhs) {
941 return lhs < rhs ? lhs : rhs;
943 const auto max = [](std::int64_t lhs, std::int64_t rhs) {
944 return lhs < rhs ? rhs : lhs;
946 const auto min_x = min(region.bounds.origin.x, coord.x);
947 const auto min_y = min(region.bounds.origin.y, coord.y);
948 const auto min_z = min(region.bounds.origin.z, coord.z);
950 max(end(region.bounds.origin.x, region.bounds.extent.x), coord.x + 1);
952 max(end(region.bounds.origin.y, region.bounds.extent.y), coord.y + 1);
954 max(end(region.bounds.origin.z, region.bounds.extent.z), coord.z + 1);
956 region.bounds = Box3{
957 Coord3{min_x, min_y, min_z},
961 abs_delta(max_x, min_x),
962 abs_delta(max_y, min_y),
963 abs_delta(max_z, min_z),
968[[nodiscard]]
constexpr auto neighbor_coord(Coord3 coord,
969 BoundaryFace face)
noexcept
972 case BoundaryFace::NegativeX:
973 return Coord3{coord.x - 1, coord.y, coord.z};
974 case BoundaryFace::PositiveX:
975 return Coord3{coord.x + 1, coord.y, coord.z};
976 case BoundaryFace::NegativeY:
977 return Coord3{coord.x, coord.y - 1, coord.z};
978 case BoundaryFace::PositiveY:
979 return Coord3{coord.x, coord.y + 1, coord.z};
980 case BoundaryFace::NegativeZ:
981 return Coord3{coord.x, coord.y, coord.z - 1};
982 case BoundaryFace::PositiveZ:
983 return Coord3{coord.x, coord.y, coord.z + 1};
984 case BoundaryFace::PositiveXNegativeY:
985 return Coord3{coord.x + 1, coord.y - 1, coord.z};
986 case BoundaryFace::NegativeXPositiveY:
987 return Coord3{coord.x - 1, coord.y + 1, coord.z};
992template <
typename Shape,
typename Fn>
993constexpr void for_each_face_neighbor_chunk(ChunkCoord3 coord, Fn&& fn) {
994 using Traits = ShapeTraits<Shape>;
1000 if (coord.x + 1 < Traits::chunk_count_x) {
1001 auto target = coord;
1006 auto target = coord;
1010 if (coord.y + 1 < Traits::chunk_count_y) {
1011 auto target = coord;
1016 auto target = coord;
1020 if (coord.z + 1 < Traits::chunk_count_z) {
1021 auto target = coord;
1025 if constexpr (std::is_same_v<
typename Traits::lattice_type,
1026 lattice::HexAxial>) {
1027 if (coord.x + 1 < Traits::chunk_count_x && coord.y > 0) {
1028 auto target = coord;
1033 if (coord.x > 0 && coord.y + 1 < Traits::chunk_count_y) {
1034 auto target = coord;
1045[[nodiscard]]
inline auto transition_face(Coord3 from, Coord3 to)
noexcept
1047 const auto dx = to.x - from.x;
1048 const auto dy = to.y - from.y;
1049 const auto dz = to.z - from.z;
1050 const auto ax = dx < 0 ? -dx : dx;
1051 const auto ay = dy < 0 ? -dy : dy;
1052 const auto az = dz < 0 ? -dz : dz;
1053 if (ax >= ay && ax >= az) {
1054 return dx < 0 ? BoundaryFace::NegativeX : BoundaryFace::PositiveX;
1057 return dy < 0 ? BoundaryFace::NegativeY : BoundaryFace::PositiveY;
1059 return dz < 0 ? BoundaryFace::NegativeZ : BoundaryFace::PositiveZ;
1066template <
typename Shape>
1067[[nodiscard]]
auto same_or_face_neighbor_chunk(Coord3 from, Coord3 to)
noexcept
1069 const auto a = chunk_coord<Shape>(from);
1070 const auto b = chunk_coord<Shape>(to);
1074 auto is_neighbor =
false;
1075 for_each_face_neighbor_chunk<Shape>(a, [&](ChunkCoord3 neighbor) {
1076 is_neighbor = is_neighbor || neighbor == b;
1085template <
typename Shape,
typename World,
typename Res
idency,
typename Prov
ider>
1086void append_provider_portals(
const World& world,
1087 const RegionGraphT<Residency>& graph,
1088 const LocalChunkTopology& topology,
1089 const Provider& provider,
1090 std::vector<RegionPortal>& portals) {
1091 provider.for_each_transition(
1092 world, topology.chunk(), [&](Coord3 from, Coord3 to) {
1103 if (!detail::provider_source_is_owned<Shape>(from, topology.chunk())) {
1106 if (!contains<Shape>(to)) {
1109 TESS_ASSERT((same_or_face_neighbor_chunk<Shape>(from, to)));
1110 const auto source = graph.template region_of<Shape>(from);
1111 if (source.region == invalid_local_region) {
1114 const auto target = graph.template region_of<Shape>(to);
1115 if (target.region == invalid_local_region) {
1119 RegionPortal{source, target, from, to, transition_face(from, to)});
1126template <
typename Shape,
typename Res
idency>
1127void append_chunk_portals(
const RegionGraphT<Residency>& graph,
1128 const LocalChunkTopology& topology,
1129 std::vector<RegionPortal>& portals) {
1130 for (
const auto& exit : topology.boundary_exits()) {
1131 const auto to_coord = neighbor_coord(exit.coord, exit.face);
1132 const auto target = graph.template region_of<Shape>(to_coord);
1133 if (target.region == invalid_local_region) {
1136 portals.push_back(RegionPortal{
1137 RegionRef{topology.chunk(), exit.region},
1149template <
typename World,
typename ClassOrTag>
1155 using Shape =
typename World::shape_type;
1157 using Class = movement::movement_class_of<ClassOrTag>;
1160 if (chunk.value >= Traits::chunk_count) {
1163 if constexpr (std::is_same_v<
typename World::residency_type,
1165 if (!world.is_resident(chunk)) {
1170 topology.chunk_ = chunk;
1171 topology.chunk_coord_ = chunk_coord<Shape>(chunk);
1172 topology.topology_version_ = world.meta(chunk).topology_version;
1173 topology.region_ids_.assign(
1174 static_cast<std::size_t
>(Traits::local_tile_count), invalid_local_region);
1175 scratch.stack_.clear();
1180 const auto& page = world.chunk(chunk);
1181 [[maybe_unused]]
const auto passable = [&] {
1183 return Class::passable_span(page);
1188 const auto tile_passable = [&](
LocalTileId id) ->
bool {
1190 return static_cast<bool>(passable[
static_cast<std::size_t
>(
id.value)]);
1192 return Class::passable(page,
id);
1195 std::size_t passable_tiles = 0;
1197 for (std::uint64_t raw_id = 0; raw_id < Traits::local_tile_count; ++raw_id) {
1199 const auto offset =
static_cast<std::size_t
>(raw_id);
1200 if (!tile_passable(tile) ||
1201 topology.region_ids_[offset] != invalid_local_region) {
1205 const auto region_id =
1206 LocalRegionId{
static_cast<std::uint32_t
>(topology.regions_.size() + 1)};
1207 topology.regions_.push_back(
LocalRegion{region_id});
1208 scratch.stack_.push_back(tile);
1209 topology.region_ids_[offset] = region_id;
1211 while (!scratch.stack_.empty()) {
1212 const auto current = scratch.stack_.back();
1213 scratch.stack_.pop_back();
1214 const auto local = detail::local_tile_coord<Shape>(current);
1215 const auto coord = tess::coord<Shape>(topology.chunk_coord_, current);
1216 auto& region = topology.regions_.back();
1217 detail::include_coord_in_bounds(region, coord);
1218 ++region.tile_count;
1220 detail::add_boundary_exits<Shape>(topology.boundary_exits_,
1221 topology.chunk_coord_, region, current,
1224 detail::for_each_local_axis_neighbor<Shape>(
1226 const auto neighbor = local_tile_id<Shape>(neighbor_coord);
1227 const auto neighbor_offset =
1228 static_cast<std::size_t
>(neighbor.value);
1229 if (!tile_passable(neighbor) ||
1230 topology.region_ids_[neighbor_offset] != invalid_local_region) {
1233 topology.region_ids_[neighbor_offset] = region_id;
1234 scratch.stack_.push_back(neighbor);
1240 TopologyStatus::Built,
1241 topology.regions_.size(),
1243 topology.boundary_exits_.size(),
1244 topology.topology_version_.value,
1249inline void add_topology_version_sum(std::uint64_t& sum,
1250 std::uint64_t value)
noexcept {
1251 if (value > std::numeric_limits<std::uint64_t>::max() - sum) {
1252 fail_fast(
"topology version sum exhausted");
1260[[nodiscard]]
constexpr auto as_topology_build_result(
1261 RegionGraphBuildResult built)
noexcept -> TopologyBuildResult {
1262 return TopologyBuildResult{
1263 TopologyStatus::Built, built.region_count,
1264 built.passable_tile_count, built.boundary_exit_count,
1265 built.topology_version_sum,
1271template <
typename World,
typename ClassOrTag,
1272 typename Provider = AdjacentTransitions>
1285 const Provider& provider = {})
1288 "build_region_graph's provider must satisfy "
1289 "TransitionProviderFor (see transition_provider.h).");
1290 using Shape =
typename World::shape_type;
1292 using Class = movement::movement_class_of<ClassOrTag>;
1295 graph.template bind_shape<Shape>();
1296 graph.template bind_class<Class>();
1297 graph.bind_provider(provider);
1300#if TESS_HAS_EXCEPTIONS
1303 if constexpr (std::is_same_v<
typename World::residency_type,
1305 graph.local_topologies_.resize(
1306 static_cast<std::size_t
>(Traits::chunk_count));
1307 for (std::uint64_t raw_chunk = 0; raw_chunk < Traits::chunk_count;
1310 graph.local_topologies_[
static_cast<std::size_t
>(raw_chunk)];
1311 const auto local_result = build_local_chunk_topology<World, Class>(
1312 world, ChunkKey{raw_chunk}, scratch, topology);
1313 if (local_result.status != TopologyStatus::Built) {
1319 "build_region_graph: dense local build reported a failure");
1321 result.region_count += local_result.region_count;
1322 result.passable_tile_count += local_result.passable_tile_count;
1323 result.boundary_exit_count += local_result.boundary_exit_count;
1324 detail::add_topology_version_sum(result.topology_version_sum,
1325 local_result.topology_version_sum);
1332 auto& keys = graph.sparse_.topology_keys_;
1333 const auto resident = world.resident_chunk_keys();
1334 keys.assign(resident.begin(), resident.end());
1335 std::sort(keys.begin(), keys.end(), [](ChunkKey lhs, ChunkKey rhs) {
1336 return lhs.value < rhs.value;
1338 const auto count = keys.size();
1339 graph.local_topologies_.resize(count);
1340 graph.sparse_.frozen_generations_.resize(count);
1341 for (std::size_t i = 0; i < count; ++i) {
1342 const auto local_result = build_local_chunk_topology<World, Class>(
1343 world, keys[i], scratch, graph.local_topologies_[i]);
1346 if (local_result.status != TopologyStatus::Built) {
1348 "build_region_graph: sparse local build reported a failure");
1350 result.region_count += local_result.region_count;
1351 result.passable_tile_count += local_result.passable_tile_count;
1352 result.boundary_exit_count += local_result.boundary_exit_count;
1353 detail::add_topology_version_sum(result.topology_version_sum,
1354 local_result.topology_version_sum);
1355 graph.sparse_.frozen_generations_[i] =
1356 world.residency_generation(keys[i]);
1360 for (
const auto& topology : graph.local_topologies_) {
1361 detail::append_chunk_portals<Shape>(graph, topology, graph.portals_);
1362 detail::append_provider_portals<Shape>(world, graph, topology, provider,
1365 graph.rebuild_region_index();
1366 graph.template mark_provider_missing_reaches<Shape>(world, provider);
1369#if TESS_HAS_EXCEPTIONS
1388template <
typename World,
typename ClassOrTag,
1389 typename Provider = AdjacentTransitions>
1394 std::span<const ChunkKey> dirty_chunks,
const Provider& provider = {})
1397 "update_region_graph's provider must satisfy "
1398 "TransitionProviderFor (see transition_provider.h).");
1399 using Shape =
typename World::shape_type;
1401 using Class = movement::movement_class_of<ClassOrTag>;
1403 if constexpr (std::is_same_v<
typename World::residency_type,
1405 const auto chunk_count =
static_cast<std::size_t
>(Traits::chunk_count);
1406 if (graph.local_topologies_.size() != chunk_count ||
1407 !graph.template matches_shape<Shape>() ||
1408 !graph.template matches_class<Class>() ||
1409 !graph.matches_provider(provider)) {
1410 return detail::as_topology_build_result(
1411 build_region_graph<World, Class>(world, scratch, graph, provider));
1413 for (
const auto chunk : dirty_chunks) {
1414 if (chunk.value >= Traits::chunk_count) {
1415 return TopologyBuildResult{TopologyStatus::InvalidChunk, 0, 0, 0, 0};
1419 if (!dirty_chunks.empty()) {
1422 std::vector<std::uint8_t> dirty(chunk_count, 0);
1423 std::vector<std::uint8_t> affected(chunk_count, 0);
1424 for (
const auto chunk : dirty_chunks) {
1425 const auto offset =
static_cast<std::size_t
>(chunk.value);
1427 affected[offset] = 1;
1429 for (std::size_t raw_chunk = 0; raw_chunk < chunk_count; ++raw_chunk) {
1430 if (dirty[raw_chunk] == 0) {
1433 detail::for_each_face_neighbor_chunk<Shape>(
1434 chunk_coord<Shape>(ChunkKey{raw_chunk}), [&](ChunkCoord3 neighbor) {
1435 const auto key = chunk_key<Shape>(neighbor);
1436 affected[
static_cast<std::size_t
>(key.value)] = 1;
1440#if TESS_HAS_EXCEPTIONS
1443 for (std::size_t raw_chunk = 0; raw_chunk < chunk_count; ++raw_chunk) {
1444 if (dirty[raw_chunk] == 0) {
1455 static_cast<void>(build_local_chunk_topology<World, Class>(
1456 world, ChunkKey{raw_chunk}, scratch,
1457 graph.local_topologies_[raw_chunk]));
1464 std::erase_if(graph.portals_, [&](
const RegionPortal& portal) {
1465 return affected[static_cast<std::size_t>(portal.from.chunk.value)] !=
1468 for (std::size_t raw_chunk = 0; raw_chunk < chunk_count; ++raw_chunk) {
1469 if (affected[raw_chunk] == 0) {
1472 detail::append_chunk_portals<Shape>(
1473 graph, graph.local_topologies_[raw_chunk], graph.portals_);
1474 detail::append_provider_portals<Shape>(
1475 world, graph, graph.local_topologies_[raw_chunk], provider,
1478 std::stable_sort(graph.portals_.begin(), graph.portals_.end(),
1479 [](
const RegionPortal& lhs,
const RegionPortal& rhs) {
1480 return lhs.from.chunk.value < rhs.from.chunk.value;
1482 graph.rebuild_region_index();
1483 graph.bump_revision();
1484#if TESS_HAS_EXCEPTIONS
1503 const auto count = graph.local_topologies_.size();
1504 if (count != world.resident_count() ||
1505 graph.sparse_.frozen_generations_.size() != world.resident_count() ||
1506 !graph.template matches_shape<Shape>() ||
1507 !graph.template matches_class<Class>() ||
1508 !graph.matches_provider(provider)) {
1509 return detail::as_topology_build_result(
1510 build_region_graph<World, Class>(world, scratch, graph, provider));
1512 for (std::size_t i = 0; i < count; ++i) {
1513 if (world.residency_generation(graph.sparse_.topology_keys_[i]) !=
1514 graph.sparse_.frozen_generations_[i]) {
1515 return detail::as_topology_build_result(
1516 build_region_graph<World, Class>(world, scratch, graph, provider));
1519 for (
const auto chunk : dirty_chunks) {
1520 if (chunk.value >= Traits::chunk_count) {
1521 return TopologyBuildResult{TopologyStatus::InvalidChunk, 0, 0, 0, 0};
1525 if (!dirty_chunks.empty()) {
1529 std::vector<std::uint8_t> dirty(count, 0);
1530 std::vector<std::uint8_t> affected(count, 0);
1531 for (
const auto chunk : dirty_chunks) {
1532 const auto li = graph.local_index(chunk);
1533 if (li == graph.npos) {
1539 for (std::size_t i = 0; i < count; ++i) {
1540 if (dirty[i] == 0) {
1543 detail::for_each_face_neighbor_chunk<Shape>(
1544 chunk_coord<Shape>(graph.sparse_.topology_keys_[i]),
1545 [&](ChunkCoord3 neighbor) {
1546 const auto li = graph.local_index(chunk_key<Shape>(neighbor));
1547 if (li != graph.npos) {
1553#if TESS_HAS_EXCEPTIONS
1556 for (std::size_t i = 0; i < count; ++i) {
1557 if (dirty[i] == 0) {
1569 static_cast<void>(build_local_chunk_topology<World, Class>(
1570 world, graph.sparse_.topology_keys_[i], scratch,
1571 graph.local_topologies_[i]));
1574 std::erase_if(graph.portals_, [&](
const RegionPortal& portal) {
1575 const auto li = graph.local_index(portal.from.chunk);
1576 return li != graph.npos && affected[li] != 0;
1578 for (std::size_t i = 0; i < count; ++i) {
1579 if (affected[i] == 0) {
1582 detail::append_chunk_portals<Shape>(graph, graph.local_topologies_[i],
1584 detail::append_provider_portals<Shape>(world, graph,
1585 graph.local_topologies_[i],
1586 provider, graph.portals_);
1588 std::stable_sort(graph.portals_.begin(), graph.portals_.end(),
1589 [](
const RegionPortal& lhs,
const RegionPortal& rhs) {
1590 return lhs.from.chunk.value < rhs.from.chunk.value;
1592 graph.rebuild_region_index();
1593 graph.template mark_provider_missing_reaches<Shape>(world, provider);
1594 graph.bump_revision();
1595#if TESS_HAS_EXCEPTIONS
1606 auto result = TopologyBuildResult{};
1607 for (
const auto& topology : graph.local_topologies_) {
1608 result.region_count += topology.regions().size();
1609 for (
const auto& region : topology.regions()) {
1610 result.passable_tile_count += region.tile_count;
1612 result.boundary_exit_count += topology.boundary_exits().size();
1613 detail::add_topology_version_sum(result.topology_version_sum,
1614 topology.topology_version().value);
1620template <
typename Shape,
typename Res
idency>
1624 if (!contains<Shape>(request.start)) {
1627 if (!contains<Shape>(request.goal)) {
1631 const auto start_region = graph.template region_of<Shape>(request.start);
1632 if (start_region.region == invalid_local_region) {
1633 if constexpr (!std::is_same_v<Residency, AlwaysResident>) {
1636 if (!graph.has_chunk(
1637 chunk_key<Shape>(chunk_coord<Shape>(request.start)))) {
1643 const auto goal_region = graph.template region_of<Shape>(request.goal);
1644 if (goal_region.region == invalid_local_region) {
1645 if constexpr (!std::is_same_v<Residency, AlwaysResident>) {
1646 if (!graph.has_chunk(
1647 chunk_key<Shape>(chunk_coord<Shape>(request.goal)))) {
1653 if (start_region == goal_region) {
1657 const auto start_index = graph.region_index(start_region);
1658 if (start_index == invalid_region_index) {
1661 const auto goal_index = graph.region_index(goal_region);
1662 if (goal_index == invalid_region_index) {
1666 scratch.begin_traversal(
static_cast<std::size_t
>(graph.region_count()));
1667 scratch.visit(start_index);
1668 std::size_t visited_count = 1;
1669 scratch.frontier_.push_back(start_index);
1674 [[maybe_unused]]
bool touched_missing =
false;
1675 if constexpr (!std::is_same_v<Residency, AlwaysResident>) {
1678 .region_reaches_missing_[
static_cast<std::size_t
>(start_index)] !=
1682 while (!scratch.frontier_.empty()) {
1683 const auto current = scratch.frontier_.back();
1684 scratch.frontier_.pop_back();
1686 const auto begin =
static_cast<std::size_t
>(
1687 graph.adjacency_starts_[
static_cast<std::size_t
>(current)]);
1688 const auto end =
static_cast<std::size_t
>(
1689 graph.adjacency_starts_[
static_cast<std::size_t
>(current) + 1]);
1690 for (std::size_t edge = begin; edge < end; ++edge) {
1691 const auto target = graph.adjacency_targets_[edge];
1692 if (scratch.is_visited(target)) {
1695 if (target == goal_index) {
1699 scratch.visit(target);
1701 scratch.frontier_.push_back(target);
1702 if constexpr (!std::is_same_v<Residency, AlwaysResident>) {
1705 graph.sparse_.region_reaches_missing_[
static_cast<std::size_t
>(
1711 if constexpr (!std::is_same_v<Residency, AlwaysResident>) {
1712 if (touched_missing) {
1724template <
typename Shape,
typename Res
idency>
1728 const auto region_count =
static_cast<std::size_t
>(graph.region_count());
1729 scratch.begin_traversal(region_count);
1730 const auto result = [&](ReachabilityStatus status,
1735 std::span<const RegionRef>{scratch.path_regions_},
1736 std::span<const RegionPortal>{scratch.path_portals_},
1737 std::span<const ChunkKey>{scratch.corridor_chunks_},
1741 if (!contains<Shape>(request.start)) {
1742 return result(ReachabilityStatus::InvalidStart, 0);
1744 if (!contains<Shape>(request.goal)) {
1745 return result(ReachabilityStatus::InvalidGoal, 0);
1748 const auto start_region = graph.template region_of<Shape>(request.start);
1749 if (start_region.region == invalid_local_region) {
1750 if constexpr (!std::is_same_v<Residency, AlwaysResident>) {
1751 if (!graph.has_chunk(
1752 chunk_key<Shape>(chunk_coord<Shape>(request.start)))) {
1753 return result(ReachabilityStatus::Indeterminate, 0);
1756 return result(ReachabilityStatus::InvalidStart, 0);
1758 const auto goal_region = graph.template region_of<Shape>(request.goal);
1759 if (goal_region.region == invalid_local_region) {
1760 if constexpr (!std::is_same_v<Residency, AlwaysResident>) {
1761 if (!graph.has_chunk(
1762 chunk_key<Shape>(chunk_coord<Shape>(request.goal)))) {
1763 return result(ReachabilityStatus::Indeterminate, 0);
1766 return result(ReachabilityStatus::InvalidGoal, 0);
1769 const auto start_index = graph.region_index(start_region);
1770 const auto goal_index = graph.region_index(goal_region);
1771 if (start_index == invalid_region_index) {
1772 return result(ReachabilityStatus::InvalidStart, 0);
1774 if (goal_index == invalid_region_index) {
1775 return result(ReachabilityStatus::InvalidGoal, 0);
1778 scratch.parent_.resize(region_count, invalid_region_index);
1779 scratch.parent_portal_.resize(region_count, invalid_region_index);
1780 scratch.visit(start_index);
1781 scratch.parent_[start_index] = start_index;
1782 scratch.frontier_.push_back(start_index);
1783 auto visited_count = std::size_t{1};
1784 auto found = start_index == goal_index;
1785 [[maybe_unused]]
auto touched_missing =
false;
1786 if constexpr (!std::is_same_v<Residency, AlwaysResident>) {
1787 touched_missing = graph.sparse_.region_reaches_missing_[start_index] != 0;
1790 for (std::size_t head = 0; head < scratch.frontier_.size() && !found;
1792 const auto current = scratch.frontier_[head];
1793 const auto begin =
static_cast<std::size_t
>(
1794 graph.adjacency_starts_[
static_cast<std::size_t
>(current)]);
1795 const auto end =
static_cast<std::size_t
>(
1796 graph.adjacency_starts_[
static_cast<std::size_t
>(current) + 1]);
1797 for (
auto edge = begin; edge < end; ++edge) {
1798 const auto target = graph.adjacency_targets_[edge];
1799 if (scratch.is_visited(target)) {
1802 scratch.visit(target);
1803 scratch.parent_[target] = current;
1804 scratch.parent_portal_[target] = graph.adjacency_portals_[edge];
1805 scratch.frontier_.push_back(target);
1807 if constexpr (!std::is_same_v<Residency, AlwaysResident>) {
1808 touched_missing = touched_missing ||
1809 graph.sparse_.region_reaches_missing_[target] != 0;
1811 if (target == goal_index) {
1819 if constexpr (!std::is_same_v<Residency, AlwaysResident>) {
1820 if (touched_missing) {
1821 return result(ReachabilityStatus::Indeterminate, visited_count);
1824 return result(ReachabilityStatus::Unreachable, visited_count);
1827 auto current = goal_index;
1828 scratch.path_regions_.push_back(graph.region_ref(current));
1829 while (current != start_index) {
1830 const auto portal_index = scratch.parent_portal_[current];
1831 TESS_ASSERT(portal_index < graph.portals_.size());
1832 scratch.path_portals_.push_back(graph.portals_[portal_index]);
1833 current = scratch.parent_[current];
1834 scratch.path_regions_.push_back(graph.region_ref(current));
1836 std::reverse(scratch.path_regions_.begin(), scratch.path_regions_.end());
1837 std::reverse(scratch.path_portals_.begin(), scratch.path_portals_.end());
1838 for (
const auto region : scratch.path_regions_) {
1839 if (std::find(scratch.corridor_chunks_.begin(),
1840 scratch.corridor_chunks_.end(),
1841 region.chunk) == scratch.corridor_chunks_.end()) {
1842 scratch.corridor_chunks_.push_back(region.chunk);
1847 auto minimum =
Coord3{std::numeric_limits<std::int64_t>::max(),
1848 std::numeric_limits<std::int64_t>::max(),
1849 std::numeric_limits<std::int64_t>::max()};
1851 for (
const auto chunk_key_value : scratch.corridor_chunks_) {
1852 const auto chunk = chunk_coord<Shape>(chunk_key_value);
1853 const auto begin =
Coord3{
1854 static_cast<std::int64_t
>(chunk.x * Traits::chunk.x),
1855 static_cast<std::int64_t
>(chunk.y * Traits::chunk.y),
1856 static_cast<std::int64_t
>(chunk.z * Traits::chunk.z),
1859 static_cast<std::int64_t
>(
1860 std::min((chunk.x + 1) * Traits::chunk.x, Traits::size.x)),
1861 static_cast<std::int64_t
>(
1862 std::min((chunk.y + 1) * Traits::chunk.y, Traits::size.y)),
1863 static_cast<std::int64_t
>(
1864 std::min((chunk.z + 1) * Traits::chunk.z, Traits::size.z)),
1866 minimum.x = std::min(minimum.x, begin.x);
1867 minimum.y = std::min(minimum.y, begin.y);
1868 minimum.z = std::min(minimum.z, begin.z);
1869 maximum.x = std::max(maximum.x, end.x);
1870 maximum.y = std::max(maximum.y, end.y);
1871 maximum.z = std::max(maximum.z, end.z);
1874 auto output = result(ReachabilityStatus::Reachable, visited_count);
1875 output.bounds =
Box3{
1877 Extent3{
static_cast<std::uint64_t
>(maximum.x - minimum.x),
1878 static_cast<std::uint64_t
>(maximum.y - minimum.y),
1879 static_cast<std::uint64_t
>(maximum.z - minimum.z)},
1899template <
typename World>
1900[[nodiscard]]
auto is_region_graph_fresh(
1904 using Residency =
typename World::residency_type;
1905 using Shape =
typename World::shape_type;
1906 if constexpr (std::is_same_v<Residency, AlwaysResident>) {
1910 if (graph.local_topologies_.size() != World::chunk_count ||
1911 !graph.template matches_shape<Shape>()) {
1914 for (std::uint64_t c = 0; c < World::chunk_count; ++c) {
1915 if (graph.local_topologies_[
static_cast<std::size_t
>(c)]
1916 .topology_version() != world.meta(ChunkKey{c}).topology_version) {
1928 const auto count = graph.local_topologies_.size();
1929 if (count != world.resident_count() ||
1930 graph.sparse_.frozen_generations_.size() != world.resident_count() ||
1931 !graph.template matches_shape<Shape>()) {
1934 for (std::size_t i = 0; i < count; ++i) {
1935 const auto key = graph.sparse_.topology_keys_[i];
1940 const auto ref = world.resident_ref(key);
1941 if (ref.generation != graph.sparse_.frozen_generations_[i]) {
1944 if (graph.local_topologies_[i].topology_version() !=
1945 ref.meta->topology_version) {
1960template <
typename ClassOrTag,
typename World>
1961[[nodiscard]]
auto is_region_graph_fresh_for(
1963 const RegionGraphT<typename World::residency_type>& graph)
noexcept
1965 return graph.template matches_class<ClassOrTag>() &&
1966 is_region_graph_fresh(world, graph);
Connected-region labels and boundary exits for one world chunk.
Definition topology.h:263
friend auto build_local_chunk_topology(const World &world, ChunkKey chunk, LocalTopologyScratch &scratch, LocalChunkTopology &topology) -> TopologyBuildResult
Builds connected-region labels for one valid, resident world chunk.
Definition topology.h:1150
Reusable flood-fill storage for local topology construction.
Definition topology.h:179
friend auto build_local_chunk_topology(const World &world, ChunkKey chunk, LocalTopologyScratch &scratch, class LocalChunkTopology &topology) -> TopologyBuildResult
Builds connected-region labels for one valid, resident world chunk.
Definition topology.h:1150
Reusable frontier and visitation storage for graph reachability queries.
Definition topology.h:198
friend auto coarse_path(const RegionGraphT< Residency > &graph, PathRequest request, RegionGraphScratch &scratch) -> CoarsePathResult
Definition topology.h:1725
friend auto reachable(const RegionGraphT< Residency > &graph, PathRequest request, RegionGraphScratch &scratch) -> ReachabilityResult
Queries graph reachability between two world coordinates.
Definition topology.h:1621
Region graph storage specialized by dense or sparse residency policy.
Definition topology.h:382
friend auto update_region_graph(const World &world, LocalTopologyScratch &scratch, RegionGraphT< typename World::residency_type > &graph, std::span< const ChunkKey > dirty_chunks, const Provider &provider) -> TopologyBuildResult
Incrementally updates dirty chunks, rebuilding fully when stamps differ.
Definition topology.h:1391
friend auto build_region_graph(const World &world, LocalTopologyScratch &scratch, RegionGraphT< typename World::residency_type > &graph, const Provider &provider) -> RegionGraphBuildResult
Definition topology.h:1283
auto revision() const noexcept -> std::uint64_t
Monotonic identity for the graph's current derived contents.
Definition topology.h:419
Constrains deterministic special-transition providers for a world type.
Definition transition_provider.h:61
Checks whether a movement class advertises the exact field-span fast path.
Definition movement_class.h:271
Scratch-borrowing shortest region path and its chunk corridor.
Definition topology.h:169
Passable boundary tile that points into an adjacent chunk.
Definition topology.h:85
One-based identifier of a connected region within a chunk.
Definition topology.h:38
Summary and bounds of one connected local region.
Definition topology.h:74
Specifies inclusive start and goal coordinates for a path query.
Definition request.h:10
Reachability verdict plus the number of graph regions visited.
Definition topology.h:163
Definition topology.h:110
Directed connection between regions, including its endpoint tiles.
Definition topology.h:136
Reference scoped to a local region within a specific chunk topology.
Definition topology.h:127
Definition residency.h:18
Counts and status returned by a local chunk build or an incremental update.
Definition topology.h:118
Definition metadata_types.h:102