3#include <tess/core/assert.h>
4#include <tess/core/shape.h>
5#include <tess/core/tag_identity.h>
6#include <tess/storage/residency.h>
7#include <tess/storage/world.h>
8#include <tess/topology/movement_class.h>
9#include <tess/topology/transition_provider.h>
25template <
typename Res
idency>
34 std::uint32_t value = 0;
46inline constexpr std::uint32_t invalid_region_index =
47 std::numeric_limits<std::uint32_t>::max();
50enum class BoundaryFace : std::uint8_t {
60enum class TopologyStatus : std::uint8_t {
69 std::size_t tile_count = 0;
71 std::size_t boundary_exit_count = 0;
73 friend constexpr bool operator==(
const LocalRegion& lhs,
82 BoundaryFace face = BoundaryFace::NegativeX;
92 TopologyStatus status = TopologyStatus::Built;
93 std::size_t region_count = 0;
94 std::size_t passable_tile_count = 0;
95 std::size_t boundary_exit_count = 0;
96 std::uint32_t version = 0;
104 friend constexpr bool operator==(
RegionRef lhs,
114 BoundaryFace face = BoundaryFace::NegativeX;
116 friend constexpr bool operator==(
const RegionPortal& lhs,
121enum class ReachabilityStatus : std::uint8_t {
137 ReachabilityStatus status = ReachabilityStatus::Unreachable;
138 std::size_t visited_regions = 0;
144 void reserve_tiles(std::size_t count) { stack_.reserve(count); }
146 [[nodiscard]]
auto capacity()
const noexcept -> std::size_t {
147 return stack_.capacity();
151 template <
typename World,
typename PassableTag>
157 std::vector<LocalTileId> stack_;
163 void reserve_regions(std::size_t count) {
164 frontier_.reserve(count);
165 visited_epoch_.reserve(count);
168 [[nodiscard]]
auto capacity()
const noexcept -> std::size_t {
169 return frontier_.capacity();
173 template <
typename Shape,
typename Res
idency>
181 void begin_traversal(std::size_t region_count) {
183 if (visited_epoch_.size() < region_count) {
184 visited_epoch_.resize(region_count, 0);
188 std::fill(visited_epoch_.begin(), visited_epoch_.end(), 0);
193 [[nodiscard]]
auto is_visited(std::uint32_t region_index)
const noexcept
195 return visited_epoch_[
static_cast<std::size_t
>(region_index)] == epoch_;
198 void visit(std::uint32_t region_index)
noexcept {
199 visited_epoch_[
static_cast<std::size_t
>(region_index)] = epoch_;
202 std::vector<std::uint32_t> frontier_;
203 std::vector<std::uint32_t> visited_epoch_;
204 std::uint32_t epoch_ = 0;
210 void clear()
noexcept {
216 boundary_exits_.clear();
219 [[nodiscard]]
auto chunk()
const noexcept ->
ChunkKey {
return chunk_; }
221 [[nodiscard]]
auto chunk_coord()
const noexcept ->
ChunkCoord3 {
225 [[nodiscard]]
auto version()
const noexcept -> std::uint32_t {
229 [[nodiscard]]
auto region_ids()
const noexcept
230 -> std::span<const LocalRegionId> {
231 return {region_ids_.data(), region_ids_.size()};
234 [[nodiscard]]
auto regions()
const noexcept -> std::span<const LocalRegion> {
235 return {regions_.data(), regions_.size()};
243 if (
id.value == 0 ||
id.value > regions_.size()) {
246 return ®ions_[
static_cast<std::size_t
>(
id.value) - 1];
249 [[nodiscard]]
auto boundary_exits()
const noexcept
250 -> std::span<const LocalBoundaryExit> {
251 return {boundary_exits_.data(), boundary_exits_.size()};
254 [[nodiscard]]
auto region_at(
LocalTileId tile)
const noexcept
256 if (tile.value >= region_ids_.size()) {
257 return invalid_local_region;
259 return region_ids_[
static_cast<std::size_t
>(tile.value)];
262 template <
typename Shape>
263 [[nodiscard]]
auto region_at(
LocalCoord3 coord)
const noexcept
265 return region_at(local_tile_id<Shape>(coord));
269 template <
typename World,
typename PassableTag>
277 std::uint32_t version_ = 0;
278 std::vector<LocalRegionId> region_ids_;
279 std::vector<LocalRegion> regions_;
280 std::vector<LocalBoundaryExit> boundary_exits_;
288template <
typename Res
idency>
289struct RegionGraphSparseData {
293 std::vector<ChunkKey> topology_keys_;
297 std::vector<std::uint8_t> region_reaches_missing_;
300 std::vector<std::uint64_t> frozen_generations_;
304struct RegionGraphSparseData<AlwaysResident> {};
308template <
typename Res
idency>
311 void clear()
noexcept {
312 local_topologies_.clear();
314 region_offsets_.clear();
315 adjacency_starts_.clear();
316 adjacency_targets_.clear();
317 built_chunk_grid_ =
Extent3{0, 0, 0};
318 built_chunk_extent_ =
Extent3{0, 0, 0};
321 built_provider_revision_ = 0;
322 if constexpr (!std::is_same_v<Residency, AlwaysResident>) {
323 sparse_.topology_keys_.clear();
324 sparse_.region_reaches_missing_.clear();
325 sparse_.frozen_generations_.clear();
329 [[nodiscard]]
auto local_topologies()
const noexcept
330 -> std::span<const LocalChunkTopology> {
331 return {local_topologies_.data(), local_topologies_.size()};
334 [[nodiscard]]
auto portals()
const noexcept -> std::span<const RegionPortal> {
335 return {portals_.data(), portals_.size()};
338 [[nodiscard]]
auto local_topology(
ChunkKey chunk)
const noexcept
340 if constexpr (std::is_same_v<Residency, AlwaysResident>) {
341 if (chunk.value >= local_topologies_.size()) {
344 return &local_topologies_[
static_cast<std::size_t
>(chunk.value)];
346 const auto idx = local_index(chunk);
350 return &local_topologies_[idx];
354 template <
typename Shape>
355 [[nodiscard]]
auto region_of(
Coord3 coord)
const noexcept ->
RegionRef {
356 if (!contains<Shape>(coord)) {
358 invalid_local_region};
360 const auto key = chunk_key<Shape>(chunk_coord<Shape>(coord));
361 const auto* local = local_topology(key);
362 if (local ==
nullptr) {
363 return RegionRef{key, invalid_local_region};
366 key, local->region_at(local_tile_id<Shape>(local_coord<Shape>(coord)))};
370 [[nodiscard]]
auto region_count()
const noexcept -> std::uint32_t {
371 return region_offsets_.empty() ? 0U : region_offsets_.back();
380 [[nodiscard]]
auto region_index(
RegionRef ref)
const noexcept
382 if constexpr (std::is_same_v<Residency, AlwaysResident>) {
383 if (ref.region == invalid_local_region ||
384 ref.chunk.value >= local_topologies_.size()) {
385 return invalid_region_index;
387 const auto chunk =
static_cast<std::size_t
>(ref.chunk.value);
388 const auto index =
static_cast<std::uint64_t
>(region_offsets_[chunk]) +
389 ref.region.value - 1;
390 if (index >= region_offsets_[chunk + 1]) {
391 return invalid_region_index;
393 return static_cast<std::uint32_t
>(index);
395 if (ref.region == invalid_local_region) {
396 return invalid_region_index;
398 const auto li = local_index(ref.chunk);
399 if (li == npos || li + 1 >= region_offsets_.size()) {
400 return invalid_region_index;
402 const auto index =
static_cast<std::uint64_t
>(region_offsets_[li]) +
403 ref.region.value - 1;
404 if (index >= region_offsets_[li + 1]) {
405 return invalid_region_index;
407 return static_cast<std::uint32_t
>(index);
417 template <
typename ClassOrTag>
418 [[nodiscard]]
auto matches_class()
const noexcept ->
bool {
419 return built_class_ ==
420 detail::tag_identity<movement::movement_class_of<ClassOrTag>>();
428 template <
typename Prov
ider>
429 [[nodiscard]]
auto matches_provider()
const noexcept ->
bool {
430 return built_provider_ == detail::tag_identity<Provider>();
436 template <
typename Prov
ider>
437 [[nodiscard]]
auto matches_provider(
const Provider& provider)
const noexcept
439 return matches_provider<Provider>() &&
440 built_provider_revision_ ==
441 detail::transition_provider_revision(provider);
445 template <
typename World,
typename ClassOrTag,
typename Prov
ider>
451 template <
typename World,
typename ClassOrTag,
typename Prov
ider>
455 std::span<const ChunkKey> dirty_chunks,
const Provider& provider)
458 template <
typename Shape,
typename OtherRes
idency>
463 template <
typename OtherWorld>
464 friend auto is_region_graph_fresh(
465 const OtherWorld& world,
472 void rebuild_region_index() {
473 region_offsets_.assign(local_topologies_.size() + 1, 0);
474 for (std::size_t i = 0; i < local_topologies_.size(); ++i) {
475 region_offsets_[i + 1] =
477 static_cast<std::uint32_t
>(local_topologies_[i].regions().size());
480 adjacency_starts_.assign(
static_cast<std::size_t
>(region_count()) + 1, 0);
481 for (
const auto& portal : portals_) {
482 ++adjacency_starts_[
static_cast<std::size_t
>(region_index(portal.from)) +
485 for (std::size_t i = 1; i < adjacency_starts_.size(); ++i) {
486 adjacency_starts_[i] += adjacency_starts_[i - 1];
489 adjacency_targets_.resize(portals_.size());
490 auto cursor = adjacency_starts_;
491 for (
const auto& portal : portals_) {
492 const auto from =
static_cast<std::size_t
>(region_index(portal.from));
493 adjacency_targets_[
static_cast<std::size_t
>(cursor[from]++)] =
494 region_index(portal.to);
497 if constexpr (!std::is_same_v<Residency, AlwaysResident>) {
503 sparse_.region_reaches_missing_.assign(
504 static_cast<std::size_t
>(region_count()), 0);
505 for (
const auto& topology : local_topologies_) {
506 for (
const auto& exit : topology.boundary_exits()) {
507 if (has_chunk(exit.target_chunk)) {
511 region_index(
RegionRef{topology.chunk(), exit.region});
512 if (idx != invalid_region_index) {
513 sparse_.region_reaches_missing_[
static_cast<std::size_t
>(idx)] = 1;
525 template <
typename Shape>
526 [[nodiscard]]
auto matches_shape()
const noexcept ->
bool {
528 return built_chunk_grid_ ==
Extent3{Traits::chunk_count_x,
529 Traits::chunk_count_y,
530 Traits::chunk_count_z} &&
531 built_chunk_extent_ == Traits::chunk;
534 template <
typename Shape>
535 void bind_shape()
noexcept {
537 built_chunk_grid_ =
Extent3{Traits::chunk_count_x, Traits::chunk_count_y,
538 Traits::chunk_count_z};
539 built_chunk_extent_ = Traits::chunk;
544 template <
typename ClassOrTag>
545 void bind_class()
noexcept {
547 detail::tag_identity<movement::movement_class_of<ClassOrTag>>();
550 template <
typename Prov
ider>
551 void bind_provider(
const Provider& provider)
noexcept {
552 built_provider_ = detail::tag_identity<Provider>();
553 built_provider_revision_ = detail::transition_provider_revision(provider);
561 template <
typename Shape,
typename World,
typename Prov
ider>
562 void mark_provider_missing_reaches(
563 [[maybe_unused]]
const World& world,
564 [[maybe_unused]]
const Provider& provider) {
565 if constexpr (!std::is_same_v<Residency, AlwaysResident>) {
566 for (
const auto& topology : local_topologies_) {
567 provider.for_each_transition(
569 if (!contains<Shape>(to) ||
570 has_chunk(chunk_key<Shape>(chunk_coord<Shape>(to)))) {
573 const auto source = this->template region_of<Shape>(from);
574 if (source.region == invalid_local_region) {
577 const auto idx = region_index(source);
578 if (idx != invalid_region_index) {
579 sparse_.region_reaches_missing_[
static_cast<std::size_t
>(idx)] =
587 static constexpr std::size_t npos =
static_cast<std::size_t
>(-1);
591 [[nodiscard]]
auto local_index(
ChunkKey chunk)
const noexcept -> std::size_t {
592 const auto& keys = sparse_.topology_keys_;
593 const auto it = std::lower_bound(
594 keys.begin(), keys.end(), chunk,
596 if (it == keys.end() || it->value != chunk.value) {
599 return static_cast<std::size_t
>(it - keys.begin());
602 [[nodiscard]]
auto has_chunk(
ChunkKey chunk)
const noexcept ->
bool {
603 return local_index(chunk) != npos;
606 std::vector<LocalChunkTopology> local_topologies_;
607 std::vector<RegionPortal> portals_;
608 std::vector<std::uint32_t> region_offsets_;
609 std::vector<std::uint32_t> adjacency_starts_;
610 std::vector<std::uint32_t> adjacency_targets_;
613 Extent3 built_chunk_grid_{0, 0, 0};
614 Extent3 built_chunk_extent_{0, 0, 0};
615 std::uintptr_t built_class_ = 0;
616 std::uintptr_t built_provider_ = 0;
617 std::uint64_t built_provider_revision_ = 0;
618 [[no_unique_address]] detail::RegionGraphSparseData<Residency> sparse_;
622using RegionGraph = RegionGraphT<AlwaysResident>;
624using SparseRegionGraph = RegionGraphT<SparseResident>;
628template <
typename Shape>
629[[nodiscard]]
constexpr auto local_tile_coord(LocalTileId
id)
noexcept
631 const auto chunk = ShapeTraits<Shape>::chunk;
632 const auto xy = chunk.x * chunk.y;
633 const auto z =
id.value / xy;
634 const auto remainder =
id.value % xy;
642template <
typename Shape>
643constexpr void add_boundary_exit(std::vector<LocalBoundaryExit>& exits,
644 LocalRegion& region, LocalTileId local_tile,
645 Coord3 coord, BoundaryFace face,
646 ChunkCoord3 target_chunk) {
647 exits.push_back(LocalBoundaryExit{
652 chunk_key<Shape>(target_chunk),
654 ++region.boundary_exit_count;
657template <
typename Shape>
658constexpr void add_boundary_exits(std::vector<LocalBoundaryExit>& exits,
659 ChunkCoord3 chunk_coord, LocalRegion& region,
660 LocalTileId local_tile, LocalCoord3 local,
662 const auto chunk = ShapeTraits<Shape>::chunk;
664 if (local.x == 0 && chunk_coord.x > 0) {
665 auto target = chunk_coord;
667 add_boundary_exit<Shape>(exits, region, local_tile, coord,
668 BoundaryFace::NegativeX, target);
670 if (local.x + 1 == chunk.x &&
671 chunk_coord.x + 1 < ShapeTraits<Shape>::chunk_count_x) {
672 auto target = chunk_coord;
674 add_boundary_exit<Shape>(exits, region, local_tile, coord,
675 BoundaryFace::PositiveX, target);
677 if (local.y == 0 && chunk_coord.y > 0) {
678 auto target = chunk_coord;
680 add_boundary_exit<Shape>(exits, region, local_tile, coord,
681 BoundaryFace::NegativeY, target);
683 if (local.y + 1 == chunk.y &&
684 chunk_coord.y + 1 < ShapeTraits<Shape>::chunk_count_y) {
685 auto target = chunk_coord;
687 add_boundary_exit<Shape>(exits, region, local_tile, coord,
688 BoundaryFace::PositiveY, target);
690 if (local.z == 0 && chunk_coord.z > 0) {
691 auto target = chunk_coord;
693 add_boundary_exit<Shape>(exits, region, local_tile, coord,
694 BoundaryFace::NegativeZ, target);
696 if (local.z + 1 == chunk.z &&
697 chunk_coord.z + 1 < ShapeTraits<Shape>::chunk_count_z) {
698 auto target = chunk_coord;
700 add_boundary_exit<Shape>(exits, region, local_tile, coord,
701 BoundaryFace::PositiveZ, target);
705template <
typename Shape,
typename Fn>
706constexpr void for_each_local_axis_neighbor(LocalCoord3 coord, Fn&& fn) {
707 const auto chunk = ShapeTraits<Shape>::chunk;
708 if (coord.x + 1 < chunk.x) {
709 fn(LocalCoord3{coord.x + 1, coord.y, coord.z});
712 fn(LocalCoord3{coord.x - 1, coord.y, coord.z});
714 if (coord.y + 1 < chunk.y) {
715 fn(LocalCoord3{coord.x, coord.y + 1, coord.z});
718 fn(LocalCoord3{coord.x, coord.y - 1, coord.z});
720 if (coord.z + 1 < chunk.z) {
721 fn(LocalCoord3{coord.x, coord.y, coord.z + 1});
724 fn(LocalCoord3{coord.x, coord.y, coord.z - 1});
728constexpr void include_coord_in_bounds(LocalRegion& region,
729 Coord3 coord)
noexcept {
730 if (region.tile_count == 0) {
731 region.bounds = Box3{coord, Extent3{1, 1, 1}};
735 const auto end = [](std::int64_t origin, std::uint64_t extent) {
738 constexpr auto max = std::numeric_limits<std::int64_t>::max();
739 if (extent >
static_cast<std::uint64_t
>(max)) {
742 const auto delta =
static_cast<std::int64_t
>(extent);
743 return origin > max - delta ? max : origin + delta;
745 const auto min = [](std::int64_t lhs, std::int64_t rhs) {
746 return lhs < rhs ? lhs : rhs;
748 const auto max = [](std::int64_t lhs, std::int64_t rhs) {
749 return lhs < rhs ? rhs : lhs;
751 const auto min_x = min(region.bounds.origin.x, coord.x);
752 const auto min_y = min(region.bounds.origin.y, coord.y);
753 const auto min_z = min(region.bounds.origin.z, coord.z);
755 max(end(region.bounds.origin.x, region.bounds.extent.x), coord.x + 1);
757 max(end(region.bounds.origin.y, region.bounds.extent.y), coord.y + 1);
759 max(end(region.bounds.origin.z, region.bounds.extent.z), coord.z + 1);
761 region.bounds = Box3{
762 Coord3{min_x, min_y, min_z},
766 abs_delta(max_x, min_x),
767 abs_delta(max_y, min_y),
768 abs_delta(max_z, min_z),
773[[nodiscard]]
constexpr auto neighbor_coord(Coord3 coord,
774 BoundaryFace face)
noexcept
777 case BoundaryFace::NegativeX:
778 return Coord3{coord.x - 1, coord.y, coord.z};
779 case BoundaryFace::PositiveX:
780 return Coord3{coord.x + 1, coord.y, coord.z};
781 case BoundaryFace::NegativeY:
782 return Coord3{coord.x, coord.y - 1, coord.z};
783 case BoundaryFace::PositiveY:
784 return Coord3{coord.x, coord.y + 1, coord.z};
785 case BoundaryFace::NegativeZ:
786 return Coord3{coord.x, coord.y, coord.z - 1};
787 case BoundaryFace::PositiveZ:
788 return Coord3{coord.x, coord.y, coord.z + 1};
793template <
typename Shape,
typename Fn>
794constexpr void for_each_face_neighbor_chunk(ChunkCoord3 coord, Fn&& fn) {
795 using Traits = ShapeTraits<Shape>;
801 if (coord.x + 1 < Traits::chunk_count_x) {
811 if (coord.y + 1 < Traits::chunk_count_y) {
821 if (coord.z + 1 < Traits::chunk_count_z) {
831[[nodiscard]]
inline auto transition_face(Coord3 from, Coord3 to)
noexcept
833 const auto dx = to.x - from.x;
834 const auto dy = to.y - from.y;
835 const auto dz = to.z - from.z;
836 const auto ax = dx < 0 ? -dx : dx;
837 const auto ay = dy < 0 ? -dy : dy;
838 const auto az = dz < 0 ? -dz : dz;
839 if (ax >= ay && ax >= az) {
840 return dx < 0 ? BoundaryFace::NegativeX : BoundaryFace::PositiveX;
843 return dy < 0 ? BoundaryFace::NegativeY : BoundaryFace::PositiveY;
845 return dz < 0 ? BoundaryFace::NegativeZ : BoundaryFace::PositiveZ;
851template <
typename Shape>
852[[nodiscard]]
auto same_or_face_neighbor_chunk(Coord3 from, Coord3 to)
noexcept
854 const auto a = chunk_coord<Shape>(from);
855 const auto b = chunk_coord<Shape>(to);
856 const auto dx = a.x < b.x ? b.x - a.x : a.x - b.x;
857 const auto dy = a.y < b.y ? b.y - a.y : a.y - b.y;
858 const auto dz = a.z < b.z ? b.z - a.z : a.z - b.z;
859 return dx + dy + dz <= 1;
866template <
typename Shape,
typename World,
typename Res
idency,
typename Prov
ider>
867void append_provider_portals(
const World& world,
868 const RegionGraphT<Residency>& graph,
869 const LocalChunkTopology& topology,
870 const Provider& provider,
871 std::vector<RegionPortal>& portals) {
872 provider.for_each_transition(
873 world, topology.chunk(), [&](Coord3 from, Coord3 to) {
874 TESS_ASSERT(contains<Shape>(from));
875 TESS_ASSERT(chunk_key<Shape>(chunk_coord<Shape>(from)).value ==
876 topology.chunk().value);
877 if (!contains<Shape>(to)) {
880 TESS_ASSERT((same_or_face_neighbor_chunk<Shape>(from, to)));
881 const auto source = graph.template region_of<Shape>(from);
882 if (source.region == invalid_local_region) {
885 const auto target = graph.template region_of<Shape>(to);
886 if (target.region == invalid_local_region) {
890 RegionPortal{source, target, from, to, transition_face(from, to)});
897template <
typename Shape,
typename Res
idency>
898void append_chunk_portals(
const RegionGraphT<Residency>& graph,
899 const LocalChunkTopology& topology,
900 std::vector<RegionPortal>& portals) {
901 for (
const auto& exit : topology.boundary_exits()) {
902 const auto to_coord = neighbor_coord(exit.coord, exit.face);
903 const auto target = graph.template region_of<Shape>(to_coord);
904 if (target.region == invalid_local_region) {
907 portals.push_back(RegionPortal{
908 RegionRef{topology.chunk(), exit.region},
920template <
typename World,
typename ClassOrTag>
925 using Shape =
typename World::shape_type;
927 using Class = movement::movement_class_of<ClassOrTag>;
930 if (chunk.value >= Traits::chunk_count) {
933 if constexpr (std::is_same_v<
typename World::residency_type,
935 if (!world.is_resident(chunk)) {
940 topology.chunk_ = chunk;
941 topology.chunk_coord_ = chunk_coord<Shape>(chunk);
942 topology.version_ = world.meta(chunk).topology_version;
943 topology.region_ids_.assign(
944 static_cast<std::size_t
>(Traits::local_tile_count), invalid_local_region);
945 scratch.stack_.clear();
950 const auto& page = world.chunk(chunk);
951 [[maybe_unused]]
const auto passable = [&] {
953 return Class::passable_span(page);
958 const auto tile_passable = [&](
LocalTileId id) ->
bool {
960 return static_cast<bool>(passable[
static_cast<std::size_t
>(
id.value)]);
962 return Class::passable(page,
id);
965 std::size_t passable_tiles = 0;
967 for (std::uint64_t raw_id = 0; raw_id < Traits::local_tile_count; ++raw_id) {
969 const auto offset =
static_cast<std::size_t
>(raw_id);
970 if (!tile_passable(tile) ||
971 topology.region_ids_[offset] != invalid_local_region) {
975 const auto region_id =
976 LocalRegionId{
static_cast<std::uint32_t
>(topology.regions_.size() + 1)};
977 topology.regions_.push_back(
LocalRegion{region_id});
978 scratch.stack_.push_back(tile);
979 topology.region_ids_[offset] = region_id;
981 while (!scratch.stack_.empty()) {
982 const auto current = scratch.stack_.back();
983 scratch.stack_.pop_back();
984 const auto local = detail::local_tile_coord<Shape>(current);
985 const auto coord = tess::coord<Shape>(topology.chunk_coord_, current);
986 auto& region = topology.regions_.back();
987 detail::include_coord_in_bounds(region, coord);
990 detail::add_boundary_exits<Shape>(topology.boundary_exits_,
991 topology.chunk_coord_, region, current,
994 detail::for_each_local_axis_neighbor<Shape>(
996 const auto neighbor = local_tile_id<Shape>(neighbor_coord);
997 const auto neighbor_offset =
998 static_cast<std::size_t
>(neighbor.value);
999 if (!tile_passable(neighbor) ||
1000 topology.region_ids_[neighbor_offset] != invalid_local_region) {
1003 topology.region_ids_[neighbor_offset] = region_id;
1004 scratch.stack_.push_back(neighbor);
1010 TopologyStatus::Built, topology.regions_.size(), passable_tiles,
1011 topology.boundary_exits_.size(), topology.version_,
1015template <
typename World,
typename ClassOrTag,
1022 "build_region_graph's provider must satisfy "
1023 "TransitionProviderFor (see transition_provider.h).");
1024 using Shape =
typename World::shape_type;
1026 using Class = movement::movement_class_of<ClassOrTag>;
1029 graph.template bind_shape<Shape>();
1030 graph.template bind_class<Class>();
1031 graph.bind_provider(provider);
1034 if constexpr (std::is_same_v<
typename World::residency_type,
1036 graph.local_topologies_.resize(
1037 static_cast<std::size_t
>(Traits::chunk_count));
1038 for (std::uint64_t raw_chunk = 0; raw_chunk < Traits::chunk_count;
1041 graph.local_topologies_[
static_cast<std::size_t
>(raw_chunk)];
1042 const auto local_result = build_local_chunk_topology<World, Class>(
1043 world, ChunkKey{raw_chunk}, scratch, topology);
1044 if (local_result.status != TopologyStatus::Built) {
1045 result.status = local_result.status;
1046 graph.rebuild_region_index();
1049 result.region_count += local_result.region_count;
1050 result.passable_tile_count += local_result.passable_tile_count;
1051 result.boundary_exit_count += local_result.boundary_exit_count;
1052 result.version += local_result.version;
1059 auto& keys = graph.sparse_.topology_keys_;
1060 const auto resident = world.resident_chunk_keys();
1061 keys.assign(resident.begin(), resident.end());
1062 std::sort(keys.begin(), keys.end(),
1063 [](ChunkKey lhs, ChunkKey rhs) { return lhs.value < rhs.value; });
1064 const auto count = keys.size();
1065 graph.local_topologies_.resize(count);
1066 graph.sparse_.frozen_generations_.resize(count);
1067 for (std::size_t i = 0; i < count; ++i) {
1068 const auto local_result = build_local_chunk_topology<World, Class>(
1069 world, keys[i], scratch, graph.local_topologies_[i]);
1072 if (local_result.status != TopologyStatus::Built) {
1073 result.status = local_result.status;
1074 graph.rebuild_region_index();
1077 result.region_count += local_result.region_count;
1078 result.passable_tile_count += local_result.passable_tile_count;
1079 result.boundary_exit_count += local_result.boundary_exit_count;
1080 result.version += local_result.version;
1081 graph.sparse_.frozen_generations_[i] =
1082 world.residency_generation(keys[i]);
1086 for (
const auto& topology : graph.local_topologies_) {
1087 detail::append_chunk_portals<Shape>(graph, topology, graph.portals_);
1088 detail::append_provider_portals<Shape>(world, graph, topology, provider,
1091 graph.rebuild_region_index();
1092 graph.template mark_provider_missing_reaches<Shape>(world, provider);
1105template <
typename World,
typename ClassOrTag,
1106 typename Provider = AdjacentTransitions>
1110 std::span<const ChunkKey> dirty_chunks,
1113 "update_region_graph's provider must satisfy "
1114 "TransitionProviderFor (see transition_provider.h).");
1115 using Shape =
typename World::shape_type;
1117 using Class = movement::movement_class_of<ClassOrTag>;
1119 if constexpr (std::is_same_v<
typename World::residency_type,
1121 const auto chunk_count =
static_cast<std::size_t
>(Traits::chunk_count);
1122 if (graph.local_topologies_.size() != chunk_count ||
1123 !graph.template matches_shape<Shape>() ||
1124 !graph.template matches_class<Class>() ||
1125 !graph.matches_provider(provider)) {
1126 return build_region_graph<World, Class>(world, scratch, graph, provider);
1128 for (
const auto chunk : dirty_chunks) {
1129 if (chunk.value >= Traits::chunk_count) {
1130 return LocalTopologyResult{TopologyStatus::InvalidChunk, 0, 0, 0, 0};
1134 if (!dirty_chunks.empty()) {
1137 std::vector<std::uint8_t> dirty(chunk_count, 0);
1138 std::vector<std::uint8_t> affected(chunk_count, 0);
1139 for (
const auto chunk : dirty_chunks) {
1140 const auto offset =
static_cast<std::size_t
>(chunk.value);
1142 affected[offset] = 1;
1144 for (std::size_t raw_chunk = 0; raw_chunk < chunk_count; ++raw_chunk) {
1145 if (dirty[raw_chunk] == 0) {
1148 detail::for_each_face_neighbor_chunk<Shape>(
1149 chunk_coord<Shape>(ChunkKey{raw_chunk}), [&](ChunkCoord3 neighbor) {
1150 const auto key = chunk_key<Shape>(neighbor);
1151 affected[
static_cast<std::size_t
>(key.value)] = 1;
1155 for (std::size_t raw_chunk = 0; raw_chunk < chunk_count; ++raw_chunk) {
1156 if (dirty[raw_chunk] == 0) {
1159 build_local_chunk_topology<World, Class>(
1160 world, ChunkKey{raw_chunk}, scratch,
1161 graph.local_topologies_[raw_chunk]);
1168 std::erase_if(graph.portals_, [&](
const RegionPortal& portal) {
1169 return affected[static_cast<std::size_t>(portal.from.chunk.value)] != 0;
1171 for (std::size_t raw_chunk = 0; raw_chunk < chunk_count; ++raw_chunk) {
1172 if (affected[raw_chunk] == 0) {
1175 detail::append_chunk_portals<Shape>(
1176 graph, graph.local_topologies_[raw_chunk], graph.portals_);
1177 detail::append_provider_portals<Shape>(
1178 world, graph, graph.local_topologies_[raw_chunk], provider,
1181 std::stable_sort(graph.portals_.begin(), graph.portals_.end(),
1182 [](
const RegionPortal& lhs,
const RegionPortal& rhs) {
1183 return lhs.from.chunk.value < rhs.from.chunk.value;
1185 graph.rebuild_region_index();
1195 const auto count = graph.local_topologies_.size();
1196 if (count != world.resident_count() ||
1197 graph.sparse_.frozen_generations_.size() != world.resident_count() ||
1198 !graph.template matches_shape<Shape>() ||
1199 !graph.template matches_class<Class>() ||
1200 !graph.matches_provider(provider)) {
1201 return build_region_graph<World, Class>(world, scratch, graph, provider);
1203 for (std::size_t i = 0; i < count; ++i) {
1204 if (world.residency_generation(graph.sparse_.topology_keys_[i]) !=
1205 graph.sparse_.frozen_generations_[i]) {
1206 return build_region_graph<World, Class>(world, scratch, graph,
1210 for (
const auto chunk : dirty_chunks) {
1211 if (chunk.value >= Traits::chunk_count) {
1212 return LocalTopologyResult{TopologyStatus::InvalidChunk, 0, 0, 0, 0};
1216 if (!dirty_chunks.empty()) {
1220 std::vector<std::uint8_t> dirty(count, 0);
1221 std::vector<std::uint8_t> affected(count, 0);
1222 for (
const auto chunk : dirty_chunks) {
1223 const auto li = graph.local_index(chunk);
1224 if (li == graph.npos) {
1230 for (std::size_t i = 0; i < count; ++i) {
1231 if (dirty[i] == 0) {
1234 detail::for_each_face_neighbor_chunk<Shape>(
1235 chunk_coord<Shape>(graph.sparse_.topology_keys_[i]),
1236 [&](ChunkCoord3 neighbor) {
1237 const auto li = graph.local_index(chunk_key<Shape>(neighbor));
1238 if (li != graph.npos) {
1244 for (std::size_t i = 0; i < count; ++i) {
1245 if (dirty[i] == 0) {
1248 build_local_chunk_topology<World, Class>(
1249 world, graph.sparse_.topology_keys_[i], scratch,
1250 graph.local_topologies_[i]);
1253 std::erase_if(graph.portals_, [&](
const RegionPortal& portal) {
1254 const auto li = graph.local_index(portal.from.chunk);
1255 return li != graph.npos && affected[li] != 0;
1257 for (std::size_t i = 0; i < count; ++i) {
1258 if (affected[i] == 0) {
1261 detail::append_chunk_portals<Shape>(graph, graph.local_topologies_[i],
1263 detail::append_provider_portals<Shape>(
1264 world, graph, graph.local_topologies_[i], provider, graph.portals_);
1266 std::stable_sort(graph.portals_.begin(), graph.portals_.end(),
1267 [](
const RegionPortal& lhs,
const RegionPortal& rhs) {
1268 return lhs.from.chunk.value < rhs.from.chunk.value;
1270 graph.rebuild_region_index();
1271 graph.template mark_provider_missing_reaches<Shape>(world, provider);
1275 auto result = LocalTopologyResult{};
1276 for (
const auto& topology : graph.local_topologies_) {
1277 result.region_count += topology.regions().size();
1278 for (
const auto& region : topology.regions()) {
1279 result.passable_tile_count += region.tile_count;
1281 result.boundary_exit_count += topology.boundary_exits().size();
1282 result.version += topology.version();
1288template <
typename Shape,
typename Res
idency>
1291 if (!contains<Shape>(start)) {
1294 if (!contains<Shape>(goal)) {
1298 const auto start_region = graph.template region_of<Shape>(start);
1299 if (start_region.region == invalid_local_region) {
1300 if constexpr (!std::is_same_v<Residency, AlwaysResident>) {
1303 if (!graph.has_chunk(chunk_key<Shape>(chunk_coord<Shape>(start)))) {
1309 const auto goal_region = graph.template region_of<Shape>(goal);
1310 if (goal_region.region == invalid_local_region) {
1311 if constexpr (!std::is_same_v<Residency, AlwaysResident>) {
1312 if (!graph.has_chunk(chunk_key<Shape>(chunk_coord<Shape>(goal)))) {
1318 if (start_region == goal_region) {
1322 const auto start_index = graph.region_index(start_region);
1323 if (start_index == invalid_region_index) {
1326 const auto goal_index = graph.region_index(goal_region);
1327 if (goal_index == invalid_region_index) {
1331 scratch.begin_traversal(
static_cast<std::size_t
>(graph.region_count()));
1332 scratch.visit(start_index);
1333 std::size_t visited_count = 1;
1334 scratch.frontier_.push_back(start_index);
1339 [[maybe_unused]]
bool touched_missing =
false;
1340 if constexpr (!std::is_same_v<Residency, AlwaysResident>) {
1343 .region_reaches_missing_[
static_cast<std::size_t
>(start_index)] !=
1347 while (!scratch.frontier_.empty()) {
1348 const auto current = scratch.frontier_.back();
1349 scratch.frontier_.pop_back();
1351 const auto begin =
static_cast<std::size_t
>(
1352 graph.adjacency_starts_[
static_cast<std::size_t
>(current)]);
1353 const auto end =
static_cast<std::size_t
>(
1354 graph.adjacency_starts_[
static_cast<std::size_t
>(current) + 1]);
1355 for (std::size_t edge = begin; edge < end; ++edge) {
1356 const auto target = graph.adjacency_targets_[edge];
1357 if (scratch.is_visited(target)) {
1360 if (target == goal_index) {
1364 scratch.visit(target);
1366 scratch.frontier_.push_back(target);
1367 if constexpr (!std::is_same_v<Residency, AlwaysResident>) {
1370 graph.sparse_.region_reaches_missing_[
static_cast<std::size_t
>(
1376 if constexpr (!std::is_same_v<Residency, AlwaysResident>) {
1377 if (touched_missing) {
1400template <
typename World>
1401[[nodiscard]]
auto is_region_graph_fresh(
1405 using Residency =
typename World::residency_type;
1406 using Shape =
typename World::shape_type;
1407 if constexpr (std::is_same_v<Residency, AlwaysResident>) {
1411 if (graph.local_topologies_.size() != World::chunk_count ||
1412 !graph.template matches_shape<Shape>()) {
1415 for (std::uint64_t c = 0; c < World::chunk_count; ++c) {
1416 if (graph.local_topologies_[
static_cast<std::size_t
>(c)].version() !=
1417 world.meta(ChunkKey{c}).topology_version) {
1428 const auto count = graph.local_topologies_.size();
1429 if (count != world.resident_count() ||
1430 graph.sparse_.frozen_generations_.size() != world.resident_count() ||
1431 !graph.template matches_shape<Shape>()) {
1434 for (std::size_t i = 0; i < count; ++i) {
1435 const auto key = graph.sparse_.topology_keys_[i];
1440 const auto ref = world.resident_ref(key);
1441 if (ref.generation != graph.sparse_.frozen_generations_[i]) {
1444 if (graph.local_topologies_[i].version() != ref.meta->topology_version) {
1459template <
typename ClassOrTag,
typename World>
1460[[nodiscard]]
auto is_region_graph_fresh_for(
1462 const RegionGraphT<typename World::residency_type>& graph)
noexcept
1464 return graph.template matches_class<ClassOrTag>() &&
1465 is_region_graph_fresh(world, graph);
Connected-region labels and boundary exits for one world chunk.
Definition topology.h:208
friend auto build_local_chunk_topology(const World &world, ChunkKey chunk, LocalTopologyScratch &scratch, LocalChunkTopology &topology) -> LocalTopologyResult
Builds connected-region labels for one valid, resident world chunk.
Definition topology.h:921
Reusable flood-fill storage for local topology construction.
Definition topology.h:142
friend auto build_local_chunk_topology(const World &world, ChunkKey chunk, LocalTopologyScratch &scratch, class LocalChunkTopology &topology) -> LocalTopologyResult
Builds connected-region labels for one valid, resident world chunk.
Definition topology.h:921
Reusable frontier and visitation storage for graph reachability queries.
Definition topology.h:161
friend auto reachable(const RegionGraphT< Residency > &graph, Coord3 start, Coord3 goal, RegionGraphScratch &scratch) -> ReachabilityResult
Queries graph reachability between two world coordinates.
Definition topology.h:1289
Region graph storage specialized by dense or sparse residency policy.
Definition topology.h:309
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) -> LocalTopologyResult
Incrementally updates dirty chunks, rebuilding fully when stamps differ.
Definition topology.h:1108
friend auto build_region_graph(const World &world, LocalTopologyScratch &scratch, RegionGraphT< typename World::residency_type > &graph, const Provider &provider) -> LocalTopologyResult
Rebuilds a complete region graph for the world's current resident set.
Definition topology.h:1018
Constrains deterministic special-transition providers for a world type.
Definition transition_provider.h:43
Checks whether a movement class advertises the exact field-span fast path.
Definition movement_class.h:236
Supplies no special transitions beyond ordinary face adjacency.
Definition transition_provider.h:75
Passable boundary tile that points into an adjacent chunk.
Definition topology.h:78
One-based identifier of a connected region within a chunk.
Definition topology.h:33
Summary and bounds of one connected local region.
Definition topology.h:67
Counts and status returned by local or whole-graph topology builds.
Definition topology.h:91
Reachability verdict plus the number of graph regions visited.
Definition topology.h:136
Directed connection between regions, including its endpoint tiles.
Definition topology.h:109
Stable reference to a local region within a specific chunk.
Definition topology.h:100
Definition residency.h:17