[][src]Trait mode::Transition

pub trait Transition<'a, A> where
    A: Mode<'a>, 
{ fn invoke(
        self: Box<Self>,
        mode: A
    ) -> Box<dyn AnyModeWrapper<'a, Base = A::Base> + 'a>; }

Trait that defines the call signature for a function that can switch an Automaton from one Mode (A) to another.

State machines, by definition, are systems that possess multiple states that can be switched in or out in order to change the behavior of the system. Automatons are no exception. Eventually, the Automaton will need to switch Modes in order to change its behavior.

Rather than requiring the owner of the Automaton to check the status of the current Mode periodically and swap in a new state from outside the object when appropriate, the Automaton instead delegates this responsibility to the current Mode via the perform_transitions() function. Each time this function is called, the Automaton calls get_transition() on the current Mode to determine whether it is ready to transition to another state. To do this, the Mode may return a callback function of the form FnOnce(A) -> B, where A is the type of the currently active Mode and B is another implementation of Mode with the same Base type. The Transition trait is automatically implemented on any closure of this form, so that get_transitions() can return a boxed closure instead of some wrapper type.

NOTE: Although there is rarely a need to do this, it is perfectly valid for a Transition function to return the input Mode as a result. This will result in the currently active Mode remaining active, even after the Transition function has been called.

Usage

use mode::*;
 
impl<'a> Mode<'a> for SomeMode {
    // ...
    fn get_transition(&mut self) -> Option<TransitionBox<'a, Self>> {
        // ...
        if some_condition {
            // Returning a Transition function will cause the Automaton to switch the
            // current Mode to whatever new Mode is produced by the callback.
            Some(Box::new(|previous : Self| { SomeOtherMode }))
        }
        else if some_other_condition {
            // NOTE: The Transition trait allows this function to return closures with
            // completely different return types, so long as the parameter types match.
            Some(Box::new(|previous : Self| { YetAnotherMode }))
        }
        else { None } // Returning None will keep the current Mode active.
    }
}

Why Transition functions?

One of the most powerful features of the Transition system is that, while transitioning, the current Mode is moved into the Transition callback. This allows Mode B, being produced, to steal pointers and other state from Mode A, as opposed to allocating more memory for B, copying over state from A, and then deallocating A. This won't make much of a difference for small Modes, but for Modes that contain a large amount of state (on the order of several megabytes or even gigabytes, as is common in UI applications), this can make a huge difference for both performance and heap memory usage.

Instead of having the get_transition() function return a callback, the transition system could have been implemented as a fn do_transition(self) -> B function on Mode. (This is a little bit of an oversimplification.) However, having a callback allows Transitions to be scheduled in advance and performed later, capturing state from the calling code, as necessary. This is especially convenient in cases where a Mode needs to stay active for a little longer after a Transition is scheduled, e.g. allowing a Mode-driven UI system to finish playing a transition animation or sound effect before swapping in a new Mode.

Example

use mode::*;
 
struct SomeMode<'a> {
    queued_transition : Option<TransitionBox<'a, Self>>
}
 
impl<'a> MyMode for SomeMode<'a> {
    fn update(&mut self) {
        // ...
        if (some_condition) {
            // Queue up a transition to be performed later.
            self.queued_transition = Some(Box::new(|previous| { SomeOtherMode }))
        }
 
        // TODO: Continue updating, animating, etc.
    }
}
 
impl<'a> Mode<'a> for SomeMode<'a> {
    // ...
    fn get_transition(&mut self) -> Option<TransitionBox<'a, Self>> {
        // ...
        if ready_to_transition && self.queued_transition.is_some() {
            // When we're finally finished updating, return the queued transition.
            self.queued_transition.take()
        }
        else { None } // Returning None will keep the current Mode active.
    }
}

Required methods

fn invoke(
    self: Box<Self>,
    mode: A
) -> Box<dyn AnyModeWrapper<'a, Base = A::Base> + 'a>

Calls the Transition function on the specified Mode, consuming the Transition and the mode and returning a wrapper around the new Mode to be swapped in as active.

NOTE: You should not attempt to implement this function yourself, as the return type makes use of a private trait (AnyModeWrapper). Ideally, this function would return a Box<Mode> in order to abstract away all of the implementation details of the Automaton. Unfortunately, that isn't possible, in this case, because the Mode trait cannot be made into an object, and therefore cannot be boxed.

Loading content...

Implementors

impl<'a, T, A, B> Transition<'a, A> for T where
    T: FnOnce(A) -> B,
    A: 'a + Mode<'a>,
    B: 'a + Mode<'a, Base = A::Base>, 
[src]

Loading content...