docs(stepper): Explain how stepper traits and navcord work

Navcord is special because we advance its state in a way that is similar
to how steppers are stepped but it actually is not a stepper.
This commit is contained in:
Mikolaj Wielgus 2025-05-12 02:16:07 +02:00
parent 47371fdf3f
commit 0df68ee80c
2 changed files with 47 additions and 3 deletions

View File

@ -20,13 +20,18 @@ use super::{
navmesh::{BinavvertexNodeIndex, Navmesh, NavvertexIndex},
};
/// The navcord is a structure that holds the movable non-borrowing
/// data of the currently running routing process.
/// The navcord is a data structure that holds the movable non-borrowing data of
/// the currently running routing process.
///
/// Note that this data structure is not a stepper, since steppers always
/// progress linearly, whereas `Navcord` branches out to different states
/// depending on the value of the `to` argument of the `.step_to(...)` method.
///
/// The name "navcord" is a shortening of "navigation cord", by analogy to
/// "navmesh" being a shortening of "navigation mesh".
#[derive(Debug)]
pub struct Navcord {
/// The layout edit which we are currently recording.
pub recorder: LayoutEdit,
/// The currently attempted path.
pub path: Vec<NavvertexIndex>,
@ -37,7 +42,7 @@ pub struct Navcord {
}
impl Navcord {
/// Creates a new navcord.
/// Create a new navcord.
pub fn new(
recorder: LayoutEdit,
source: FixedDotIndex,
@ -52,6 +57,7 @@ impl Navcord {
}
}
/// From the current head `head` wrap a new head around the navvertex `around`.
fn wrap(
&mut self,
layout: &mut Layout<impl AccessRules>,
@ -86,6 +92,8 @@ impl Navcord {
.map_err(NavcorderException::CannotDraw)
}
/// Advance the navcord and the currently routed band by one step to the
/// navvertex `to`.
#[debug_ensures(ret.is_ok() -> matches!(self.head, Head::Cane(..)))]
#[debug_ensures(ret.is_ok() -> self.path.len() == old(self.path.len() + 1))]
#[debug_ensures(ret.is_err() -> self.path.len() == old(self.path.len()))]
@ -96,11 +104,17 @@ impl Navcord {
to: NavvertexIndex,
) -> Result<(), NavcorderException> {
self.head = self.wrap(layout, navmesh, self.head, to)?.into();
// Now that the new head has been created, push the navvertex
// `to` onto the currently attempted path to start from it on
// the next `.step_to(...)` call or retreat from it later using
// `.step_back(...)`.
self.path.push(to);
Ok(())
}
/// Retreat the navcord and the currently routed band by one step.
#[debug_ensures(self.path.len() == old(self.path.len() - 1))]
pub fn step_back<R: AccessRules>(
&mut self,
@ -113,6 +127,9 @@ impl Navcord {
return Err(NavcorderException::CannotWrap);
}
// Now that the last head of the currently routed band was deleted, pop
// the last navvertex from the currently attempted path so that it is up
// to date.
self.path.pop();
Ok(())
}

View File

@ -4,11 +4,31 @@
use std::ops::ControlFlow;
/// This trait represents a linearly advanceable state whose advancement may
/// break or fail with many different return values, and to which part of
/// the information, called the context, has to be supplied on each call as a
/// mutable reference argument.
///
/// An object that implements this trait is called a "stepper".
///
/// Steppers always progress linearly, that is, it is presumed that the context
/// does not change between calls in a way that can affect the stepper's future
/// states. Advanceable data structures designed for uses where the future state
/// intentionally *may* change from the information supplied as arguments are
/// not considered steppers. An example of such an advanceable non-stepper is
/// the [`navcord::Navcord`] struct, as it does not progress linearly because
/// it branches out by on each call taking in a changeable `to` argument that
/// affects the future states.
///
/// Petgraph's counterpart of this trait is its
/// [`petgraph::visit::Walker<Context>`] trait.
pub trait Step<Ctx, B, C = ()> {
type Error;
/// Advance the stepper's state by one step.
fn step(&mut self, context: &mut Ctx) -> Result<ControlFlow<B, C>, Self::Error>;
/// Advance the stepper step-by-step in a loop until it fails or breaks.
fn finish(&mut self, context: &mut Ctx) -> Result<B, Self::Error> {
loop {
if let ControlFlow::Break(outcome) = self.step(context)? {
@ -18,10 +38,17 @@ pub trait Step<Ctx, B, C = ()> {
}
}
/// Steppers that may be stepped backwards implement this trait.
pub trait StepBack<C, S, E> {
/// Retreat the stepper's state by one step.
fn step_back(&mut self, context: &mut C) -> Result<S, E>;
}
/// Steppers that may be aborted implement this trait.
///
/// Aborting a stepper puts it in a state where stepping or stepping back always
/// fails.
pub trait Abort<C> {
/// Abort the stepper.
fn abort(&mut self, context: &mut C);
}