3#include <tess/core/shape.h>
4#include <tess/storage/chunk_meta.h>
5#include <tess/storage/residency.h>
6#include <tess/topology/movement_class.h>
16enum class MovementStatus : std::uint8_t {
28static_assert(
sizeof(MovementStatus) ==
sizeof(std::uint8_t));
32 std::optional<std::uint32_t> from_chunk_version;
33 std::optional<std::uint32_t> to_chunk_version;
34 std::optional<std::uint32_t> from_topology_version;
35 std::optional<std::uint32_t> to_topology_version;
47 MovementStatus status = MovementStatus::Moved;
54 std::size_t invalid = 0;
55 std::size_t blocked = 0;
56 std::size_t occupied = 0;
57 std::size_t reserved = 0;
58 std::size_t stale_version = 0;
59 std::size_t stale_topology = 0;
64 MovementStatus status)
noexcept {
66 case MovementStatus::Moved:
68 case MovementStatus::InvalidFrom:
69 case MovementStatus::InvalidTo:
70 case MovementStatus::NotAdjacent:
73 case MovementStatus::BlockedFrom:
74 case MovementStatus::BlockedTo:
77 case MovementStatus::Occupied:
80 case MovementStatus::Reserved:
83 case MovementStatus::StaleVersion:
84 ++counts.stale_version;
86 case MovementStatus::StaleTopology:
87 ++counts.stale_topology;
98[[nodiscard]]
constexpr auto is_transient_movement_failure(
99 MovementStatus status)
noexcept ->
bool {
101 case MovementStatus::BlockedFrom:
102 case MovementStatus::BlockedTo:
103 case MovementStatus::Occupied:
104 case MovementStatus::Reserved:
105 case MovementStatus::StaleVersion:
106 case MovementStatus::StaleTopology:
108 case MovementStatus::Moved:
109 case MovementStatus::InvalidFrom:
110 case MovementStatus::InvalidTo:
111 case MovementStatus::NotAdjacent:
119[[nodiscard]]
inline auto movement_versions_match_meta(
120 const ChunkMeta& from_meta,
const ChunkMeta& to_meta,
121 const MovementVersionCheck& versions)
noexcept -> MovementStatus {
122 if (versions.from_chunk_version.has_value() &&
123 from_meta.version != *versions.from_chunk_version) {
124 return MovementStatus::StaleVersion;
126 if (versions.to_chunk_version.has_value() &&
127 to_meta.version != *versions.to_chunk_version) {
128 return MovementStatus::StaleVersion;
130 if (versions.from_topology_version.has_value() &&
131 from_meta.topology_version != *versions.from_topology_version) {
132 return MovementStatus::StaleTopology;
134 if (versions.to_topology_version.has_value() &&
135 to_meta.topology_version != *versions.to_topology_version) {
136 return MovementStatus::StaleTopology;
138 return MovementStatus::Moved;
141[[nodiscard]]
constexpr auto has_version_expectations(
142 const MovementVersionCheck& versions)
noexcept ->
bool {
143 return versions.from_chunk_version.has_value() ||
144 versions.to_chunk_version.has_value() ||
145 versions.from_topology_version.has_value() ||
146 versions.to_topology_version.has_value();
152template <
typename World>
153[[nodiscard]]
auto movement_versions_match(
const World& world,
156 if constexpr (std::is_same_v<
typename World::residency_type,
162 const auto from = world.resolve(intent.from);
163 const auto to = world.resolve(intent.to);
164 if (!world.is_resident(from.chunk_key) ||
165 !world.is_resident(to.chunk_key)) {
166 return MovementStatus::StaleVersion;
168 if (!detail::has_version_expectations(intent.versions)) {
169 return MovementStatus::Moved;
171 return detail::movement_versions_match_meta(
172 world.meta(from.chunk_key), world.meta(to.chunk_key), intent.versions);
178 if (!detail::has_version_expectations(intent.versions)) {
179 return MovementStatus::Moved;
181 const auto from = world.resolve(intent.from);
182 const auto to = world.resolve(intent.to);
183 return detail::movement_versions_match_meta(
184 world.meta(from.chunk_key), world.meta(to.chunk_key), intent.versions);
201template <
typename World,
typename ClassOrTag,
typename OccupancyTag,
202 typename ReservationTag>
203[[nodiscard]]
auto validate_movement_intent_resolved(
204 const World& world, MovementIntent intent)
noexcept {
205 using Class = movement::movement_class_of<ClassOrTag>;
206 using Resolved = ResolvedTile<typename World::shape_type>;
208 MovementResult result;
212 const auto fail = [&](MovementStatus status) {
213 return Validated{MovementResult{status, intent.from, intent.to}, Resolved{},
216 const auto resolved_from = world.try_resolve(intent.from);
217 if (!resolved_from.has_value()) {
218 return fail(MovementStatus::InvalidFrom);
220 const auto resolved_to = world.try_resolve(intent.to);
221 if (!resolved_to.has_value()) {
222 return fail(MovementStatus::InvalidTo);
224 if constexpr (std::is_same_v<
typename World::residency_type,
237 if (!world.is_resident(resolved_from->chunk_key) ||
238 !world.is_resident(resolved_to->chunk_key)) {
239 return fail(MovementStatus::StaleVersion);
242 if (manhattan_distance(intent.from, intent.to) != 1) {
243 return fail(MovementStatus::NotAdjacent);
245 const auto& from_page = world.chunk(resolved_from->chunk_key);
246 const auto& to_page = world.chunk(resolved_to->chunk_key);
247 if (!Class::passable(from_page, resolved_from->local_tile_id)) {
248 return fail(MovementStatus::BlockedFrom);
250 if (!Class::passable(to_page, resolved_to->local_tile_id)) {
251 return fail(MovementStatus::BlockedTo);
253 if (
static_cast<bool>(
254 to_page.template field<OccupancyTag>(resolved_to->local_tile_id))) {
255 return fail(MovementStatus::Occupied);
257 if (
static_cast<bool>(
258 to_page.template field<ReservationTag>(resolved_to->local_tile_id))) {
259 return fail(MovementStatus::Reserved);
265 if (detail::has_version_expectations(intent.versions)) {
266 const auto version_status = detail::movement_versions_match_meta(
267 world.meta(resolved_from->chunk_key),
268 world.meta(resolved_to->chunk_key), intent.versions);
269 if (version_status != MovementStatus::Moved) {
270 return fail(version_status);
274 MovementResult{MovementStatus::Moved, intent.from, intent.to},
275 *resolved_from, *resolved_to};
280template <
typename World,
typename ClassOrTag,
typename OccupancyTag,
281 typename ReservationTag>
285[[nodiscard]]
auto validate_movement_intent(
const World& world,
288 return detail::validate_movement_intent_resolved<
289 World, ClassOrTag, OccupancyTag, ReservationTag>(world, intent)
293template <
typename World,
typename ClassOrTag,
typename OccupancyTag,
294 typename ReservationTag>
300 std::uint32_t dirty_mask = 0) noexcept
302 const auto validated =
303 detail::validate_movement_intent_resolved<
World, ClassOrTag, OccupancyTag,
304 ReservationTag>(world, intent);
305 if (validated.result.status != MovementStatus::Moved) {
306 return validated.result;
309 auto& from_page = world.chunk(validated.from.chunk_key);
310 auto& to_page = world.chunk(validated.to.chunk_key);
311 from_page.template field<OccupancyTag>(validated.from.local_tile_id) =
false;
312 to_page.template field<OccupancyTag>(validated.to.local_tile_id) =
true;
313 to_page.template field<ReservationTag>(validated.to.local_tile_id) =
false;
314 if (dirty_mask != 0) {
315 world.mark_dirty(validated.from.chunk_key, dirty_mask,
316 Box3{intent.from, Extent3{1, 1, 1}});
317 world.mark_dirty(validated.to.chunk_key, dirty_mask,
320 return validated.result;
Aggregates rejected movement attempts by retry-relevant category.
Definition movement.h:53
Describes an adjacent move and any versions it expects to remain current.
Definition movement.h:39
Reports the movement status together with the requested endpoints.
Definition movement.h:46
Holds optional optimistic-concurrency versions for both movement endpoints.
Definition movement.h:31
Definition residency.h:17