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>
7#include <tess/topology/transition_model.h>
17enum class MovementStatus : std::uint8_t {
30static_assert(
sizeof(MovementStatus) ==
sizeof(std::uint8_t));
34 std::optional<ContentVersion> from_content_version;
35 std::optional<ContentVersion> to_content_version;
36 std::optional<TopologyVersion> from_topology_version;
37 std::optional<TopologyVersion> to_topology_version;
49 MovementStatus status = MovementStatus::Moved;
56 std::size_t invalid = 0;
57 std::size_t impassable = 0;
58 std::size_t blocked = 0;
59 std::size_t occupied = 0;
60 std::size_t reserved = 0;
61 std::size_t stale_content = 0;
62 std::size_t stale_topology = 0;
67 MovementStatus status)
noexcept {
69 case MovementStatus::Moved:
71 case MovementStatus::InvalidFrom:
72 case MovementStatus::InvalidTo:
73 case MovementStatus::NotAdjacent:
76 case MovementStatus::ImpassableFrom:
77 case MovementStatus::ImpassableTo:
80 case MovementStatus::Blocked:
83 case MovementStatus::Occupied:
86 case MovementStatus::Reserved:
89 case MovementStatus::StaleContent:
90 ++counts.stale_content;
92 case MovementStatus::StaleTopology:
93 ++counts.stale_topology;
108[[nodiscard]]
constexpr auto is_transient_movement_failure(
109 MovementStatus status)
noexcept ->
bool {
111 case MovementStatus::ImpassableFrom:
112 case MovementStatus::ImpassableTo:
113 case MovementStatus::Blocked:
114 case MovementStatus::Occupied:
115 case MovementStatus::Reserved:
116 case MovementStatus::StaleContent:
117 case MovementStatus::StaleTopology:
119 case MovementStatus::Moved:
120 case MovementStatus::InvalidFrom:
121 case MovementStatus::InvalidTo:
122 case MovementStatus::NotAdjacent:
130[[nodiscard]]
inline auto movement_versions_match_meta(
131 const ChunkMeta& from_meta,
const ChunkMeta& to_meta,
132 const MovementVersionCheck& versions)
noexcept -> MovementStatus {
133 if (versions.from_content_version.has_value() &&
134 from_meta.content_version != *versions.from_content_version) {
135 return MovementStatus::StaleContent;
137 if (versions.to_content_version.has_value() &&
138 to_meta.content_version != *versions.to_content_version) {
139 return MovementStatus::StaleContent;
141 if (versions.from_topology_version.has_value() &&
142 from_meta.topology_version != *versions.from_topology_version) {
143 return MovementStatus::StaleTopology;
145 if (versions.to_topology_version.has_value() &&
146 to_meta.topology_version != *versions.to_topology_version) {
147 return MovementStatus::StaleTopology;
149 return MovementStatus::Moved;
152[[nodiscard]]
constexpr auto has_version_expectations(
153 const MovementVersionCheck& versions)
noexcept ->
bool {
154 return versions.from_content_version.has_value() ||
155 versions.to_content_version.has_value() ||
156 versions.from_topology_version.has_value() ||
157 versions.to_topology_version.has_value();
163template <
typename World>
164[[nodiscard]]
auto movement_versions_match(
const World& world,
167 if constexpr (std::is_same_v<
typename World::residency_type,
173 const auto from = world.resolve(intent.from);
174 const auto to = world.resolve(intent.to);
175 if (!world.is_resident(from.chunk_key) ||
176 !world.is_resident(to.chunk_key)) {
177 return MovementStatus::StaleContent;
179 if (!detail::has_version_expectations(intent.versions)) {
180 return MovementStatus::Moved;
182 return detail::movement_versions_match_meta(
183 world.meta(from.chunk_key), world.meta(to.chunk_key), intent.versions);
188 if (!detail::has_version_expectations(intent.versions)) {
189 return MovementStatus::Moved;
191 const auto from = world.resolve(intent.from);
192 const auto to = world.resolve(intent.to);
193 return detail::movement_versions_match_meta(
194 world.meta(from.chunk_key), world.meta(to.chunk_key), intent.versions);
213template <
typename World,
typename ClassOrTag,
typename OccupancyTag,
214 typename ReservationTag,
typename Provider>
215[[nodiscard]]
auto validate_movement_intent_resolved(
const World& world,
216 MovementIntent intent,
217 const Provider& provider) {
218 using Class = movement::movement_class_of<ClassOrTag>;
219 using Model = ResolvedTransitionModel<World, Class, Provider>;
220 using Resolved = ResolvedTile<typename World::shape_type>;
222 MovementResult result;
226 const auto fail = [&](MovementStatus status) {
227 return Validated{MovementResult{status, intent.from, intent.to}, Resolved{},
230 const auto resolved_from = world.try_resolve(intent.from);
231 if (!resolved_from.has_value()) {
232 return fail(MovementStatus::InvalidFrom);
234 const auto resolved_to = world.try_resolve(intent.to);
235 if (!resolved_to.has_value()) {
236 return fail(MovementStatus::InvalidTo);
238 if constexpr (std::is_same_v<
typename World::residency_type,
251 if (!world.is_resident(resolved_from->chunk_key) ||
252 !world.is_resident(resolved_to->chunk_key)) {
253 return fail(MovementStatus::StaleContent);
256 const auto& from_page = world.chunk(resolved_from->chunk_key);
257 const auto& to_page = world.chunk(resolved_to->chunk_key);
258 if (!Class::passable(from_page, resolved_from->local_tile_id)) {
259 return fail(MovementStatus::ImpassableFrom);
261 if (!Class::passable(to_page, resolved_to->local_tile_id)) {
262 return fail(MovementStatus::ImpassableTo);
268 if (Class::entry_cost(to_page, resolved_to->local_tile_id) == 0) {
269 return fail(MovementStatus::ImpassableTo);
271 auto transition_availability = TransitionAvailability::Blocked;
272 auto is_candidate = Model::is_regular_candidate(intent.from, intent.to);
274 transition_availability =
275 Model::regular_availability(world, intent.from, intent.to);
277 if constexpr (Model::has_special_transitions) {
285 if (transition_availability != TransitionAvailability::Legal) {
286 const auto model = Model{provider};
287 model.for_each_forward(
289 detail::transition_index<typename World::shape_type>(intent.from),
291 if (probe.to != intent.to) {
295 if (probe.availability == TransitionAvailability::Legal) {
296 transition_availability = TransitionAvailability::Legal;
297 }
else if (probe.availability ==
298 TransitionAvailability::MissingTopology &&
299 transition_availability !=
300 TransitionAvailability::Legal) {
301 transition_availability = TransitionAvailability::MissingTopology;
307 if constexpr (Model::has_special_transitions) {
308 return fail(MovementStatus::StaleTopology);
310 return fail(MovementStatus::NotAdjacent);
312 if (transition_availability == TransitionAvailability::MissingTopology) {
313 return fail(MovementStatus::StaleTopology);
315 if (transition_availability != TransitionAvailability::Legal) {
316 return fail(MovementStatus::Blocked);
318 if (
static_cast<bool>(
319 to_page.template field<OccupancyTag>(resolved_to->local_tile_id))) {
320 return fail(MovementStatus::Occupied);
322 if (
static_cast<bool>(
323 to_page.template field<ReservationTag>(resolved_to->local_tile_id))) {
324 return fail(MovementStatus::Reserved);
330 if (detail::has_version_expectations(intent.versions)) {
331 const auto version_status = detail::movement_versions_match_meta(
332 world.meta(resolved_from->chunk_key),
333 world.meta(resolved_to->chunk_key), intent.versions);
334 if (version_status != MovementStatus::Moved) {
335 return fail(version_status);
339 MovementResult{MovementStatus::Moved, intent.from, intent.to},
340 *resolved_from, *resolved_to};
345template <
typename World,
typename ClassOrTag,
typename OccupancyTag,
346 typename ReservationTag>
350[[nodiscard]]
auto validate_movement_intent(
const World& world,
351 MovementIntent intent)
noexcept
353 return detail::validate_movement_intent_resolved<World, ClassOrTag,
354 OccupancyTag, ReservationTag,
355 AdjacentTransitions>(
356 world, intent, AdjacentTransitions{})
360template <
typename World,
typename ClassOrTag,
typename OccupancyTag,
361 typename ReservationTag,
typename Provider>
365[[nodiscard]]
auto validate_movement_intent(
const World& world,
366 MovementIntent intent,
367 const Provider& provider)
369 return detail::validate_movement_intent_resolved<
370 World, ClassOrTag, OccupancyTag, ReservationTag, Provider>(
371 world, intent, provider)
375template <
typename World,
typename ClassOrTag,
typename OccupancyTag,
376 typename ReservationTag>
381[[nodiscard]]
auto commit_movement_intent(World& world, MovementIntent intent,
382 DirtyMask dirty_mask = {})
noexcept
384 const auto validated = detail::validate_movement_intent_resolved<
385 World, ClassOrTag, OccupancyTag, ReservationTag, AdjacentTransitions>(
386 world, intent, AdjacentTransitions{});
387 if (validated.result.status != MovementStatus::Moved) {
388 return validated.result;
391 auto& from_page = world.chunk(validated.from.chunk_key);
392 auto& to_page = world.chunk(validated.to.chunk_key);
393 from_page.template field<OccupancyTag>(validated.from.local_tile_id) =
false;
394 to_page.template field<OccupancyTag>(validated.to.local_tile_id) =
true;
395 to_page.template field<ReservationTag>(validated.to.local_tile_id) =
false;
397 world.mark_dirty(validated.from.chunk_key, dirty_mask,
398 Box3{intent.from, Extent3{1, 1, 1}});
399 world.mark_dirty(validated.to.chunk_key, dirty_mask,
400 Box3{intent.to, Extent3{1, 1, 1}});
402 return validated.result;
405template <
typename World,
typename ClassOrTag,
typename OccupancyTag,
406 typename ReservationTag,
typename Provider>
410[[nodiscard]]
auto commit_movement_intent(World& world, MovementIntent intent,
411 DirtyMask dirty_mask,
412 const Provider& provider)
414 const auto validated =
415 detail::validate_movement_intent_resolved<World, ClassOrTag, OccupancyTag,
416 ReservationTag, Provider>(
417 world, intent, provider);
418 if (validated.result.status != MovementStatus::Moved) {
419 return validated.result;
422 auto& from_page = world.chunk(validated.from.chunk_key);
423 auto& to_page = world.chunk(validated.to.chunk_key);
424 from_page.template field<OccupancyTag>(validated.from.local_tile_id) =
false;
425 to_page.template field<OccupancyTag>(validated.to.local_tile_id) =
true;
426 to_page.template field<ReservationTag>(validated.to.local_tile_id) =
false;
428 world.mark_dirty(validated.from.chunk_key, dirty_mask,
429 Box3{intent.from, Extent3{1, 1, 1}});
430 world.mark_dirty(validated.to.chunk_key, dirty_mask,
431 Box3{intent.to, Extent3{1, 1, 1}});
433 return validated.result;
Aggregates rejected movement attempts by retry-relevant category.
Definition movement.h:55
Describes an adjacent move and any versions it expects to remain current.
Definition movement.h:41
Reports the movement status together with the requested endpoints.
Definition movement.h:48
Holds optional optimistic-concurrency versions for both movement endpoints.
Definition movement.h:33
Definition residency.h:18