Trait mode::Mode[][src]

pub trait Mode {
    type Family: Family + ?Sized;
}
Expand description

Trait that defines a state within some Family, and can be made active in an Automaton.

Every Automaton contains a single Mode instance that represents the active state of the state machine. An Automaton<F> can only switch between Modes with the same Family type F. The Automaton only allows the active Mode to be accessed as a F::Base reference, so only functions exposed on the Base type are callable on the Mode from outside the Automaton.

See Automaton for more details.

Usage

use mode::*;
 
struct MyFamily;
impl Family for MyFamily {
    type Base = dyn MyMode;
    type Mode = Box<dyn MyMode>;
}
 
trait MyMode : Mode<Family = MyFamily> {
    // TODO: Define some common interface for ModeA and ModeB.
    fn is_mode_a(&self) -> bool;
    fn is_mode_b(&self) -> bool;
 
    // This function will be used to delegate the responsibility for swapping to the active Mode in the Automaton.
    fn swap(self : Box<Self>) -> Box<dyn MyMode>;
}
 
struct ModeA; // TODO: Add fields.
impl Mode for ModeA { type Family = MyFamily; }
impl MyMode for ModeA {
    fn is_mode_a(&self) -> bool { true }
    fn is_mode_b(&self) -> bool { false }
    fn swap(self : Box<Self>) -> Box<dyn MyMode> {
        // Transition to ModeB. ModeA can swap to ModeB because both share the same Family.
        Box::new(ModeB)
    }
}
 
struct ModeB; // TODO: Add fields.
impl Mode for ModeB { type Family = MyFamily; }
impl MyMode for ModeB {
    fn is_mode_a(&self) -> bool { false }
    fn is_mode_b(&self) -> bool { true }
    fn swap(self : Box<Self>) -> Box<dyn MyMode> { self } // Returning self means don't transition.
}
 
fn main() {
    // Create an Automaton, starting in ModeA.
    let mut automaton = MyFamily::automaton_with_mode(Box::new(ModeA));
 
    // Allow Modes to swap between each other. (Call this whenever Modes should be allowed to transition.)
    Automaton::next(&mut automaton, |current_mode| current_mode.swap());
 
    // MyMode functions can be called on the Automaton to dispatch them to the current Mode, via Deref coercion.
    assert!(!automaton.is_mode_a() && automaton.is_mode_b());
}

Tying Modes together with the Family parameter

In the example above, ModeA and ModeB both implement Mode with a Family type of MyFamily. Conceptually, this means that both ModeA and ModeB are part of the same state machine, and therefore an Automaton of type Automaton<MyFamily> is only allowed to switch between Modes of the same Family type when Automaton::next() is called. See Automaton::next() for more details.

Blanket Mode implementations for pointer types (e.g. Box, Rc, Arc)

When storing Modes with a large amount of data or that should be accessed through some dyn Trait reference, it is desirable to have the Automaton store a pointer to a Mode, as opposed to storing the current Mode in place. This is possible by setting the Family::Mode type to a pointer type wrapping a Family::Base, e.g.

use mode::{Family, Mode};
 
struct FamilyWithPointerMode;
impl Family for FamilyWithPointerMode {
    type Base = dyn SomeTrait;
    type Mode = Box<dyn SomeTrait>; // All Modes in this Family will be stored as a Box<dyn SomeTrait> internally.
}

When doing so, it’s usually a good idea to delegate the responsibility for swapping in the next Mode to the type stored in the pointer, not the pointer itself. Hence, this module defines some blanket Mode implementations for various pointer types wrapping T, where T implements Mode. These are given the same Family type as T, and allow the pointer type, e.g. Box<T>, to be used as the Mode type for the Family. The main advantage of doing this is that the callback passed into the Automaton::next() function will accept the current Mode by pointer instead of by value, and it can simply return a pointer to itself if it wishes to remain active. Moving a pointer into and out of the function like this can be much cheaper than moving around the current Mode by value, particularly for Modes that store large amounts of data. (See example below.)

use mode::{Family, Mode};
use std::sync::Arc;
 
trait SomeTrait : Mode<Family = FamilyWithArcMode> {
    // NOTE: This function will delegate the responsibility for swapping in a new Arc<dyn SomeTrait> to the current,
    // concrete Mode type. This is advantageous because it allows the current Mode to move large amounts of state
    // out of itself into the new Mode being swapped in, if it wants to.
    fn swap(self : Arc<Self>) -> Arc<dyn SomeTrait>;
}
 
struct FamilyWithArcMode;
impl Family for FamilyWithArcMode {
    type Base = dyn SomeTrait;
    type Mode = Arc<dyn SomeTrait>; // All Modes in this Family will be stored as an Arc<dyn SomeTrait> internally.
}
 
struct SomeMode;
impl SomeTrait for SomeMode {
    // TODO: Switch Modes by returning a different Arc<dyn SomeTrait> from this function.
    fn swap(self : Arc<Self>) -> Arc<dyn SomeTrait> { self }
}
 
// NOTE: We ONLY impl Mode for SomeMode. There is an auto-impl of Mode for Arc<T : Mode>, so we don't need to
// implement Mode for Arc<dyn SomeTrait>.
//
impl Mode for SomeMode {
    type Family = FamilyWithArcMode;
}

Associated Types

The Family type to which this Mode implementation belongs. An Automaton can only switch between Modes of the exact same Family type, matching the Family type of the Automaton. Swapping between Modes with different Family types is not allowed, even if the Base and Mode associated types of both Family implementations are identical. This is because Modes with the same Family type represent finite states that are part of the same state machine.

See Family for more details.

Implementations on Foreign Types

Blanket impl that allows a Box<T : Mode> to be used as the Mode associated type for a Family.

Blanket impl that allows an Rc<T : Mode> to be used as the Mode associated type for a Family.

Blanket impl that allows an Arc<T : Mode> to be used as the Mode associated type for a Family.

Implementors