Cheatsheet

A simple guide through the concepts behind solstack.

Core Concepts

There's really only three things you should care about in solstack: States, Trans and the Stack.

A State represents what your program should run at that moment. A MainMenu state does not render the player in a game; it renders options and pushes other relevant states on top of itself if the correct inputs are provided, or even quits the application entirely.

Take a look at the previous chapter Use Case for understanding a typical application flow in solstack.

A Stack is a type that holds a list of States. When the stack's tick method is called, it looks for the topmost state it holds and executes its methods (namely on_tick and on_shadow_tick). This way, only the topmost state in a stack is run at a time; while the others are effectively paused.

In v0.3.0 we introduced the concept of a shadow_tick. The Stack now, when ticked, will execute the method on_shadow_tick of every single state it holds. It can simply be ignored, of course.

A Trans represents a transition between States on a Stack. You can perform this directly through the Stacks methods (namely push, pop, quit, none, etc.). But the most useful way is through returning a Trans on the States on_update and/or on_shadow_update. Returning allows you to easily code conditional transitions, like if the player presses Esc, push the PauseMenu state.

Modeling an Application

  • Bring the important stuff into scope.

// State, Trans and Stack
use solstack::prelude::*;
// optional macros for cleaner code (i'll use both ways so you can choose)
use solstack::macros::*;  
  • Create an application data type that will hold important information. This will be shared mutably by all of your states.

#[derive(Default)] // optional
struct AppData {
    // important data for your app
    name: String,
    age: i32,
}
  • Create some states.

struct Menu;        // the main menu.
struct PromptName;  // prompts the user for their name (stores in AppData).
struct PromptAge;   // prompts the user for their age (stores in AppData).
struct Greet;       // prints a greeting to the user and their age (reads from AppData).
  • Implement State for your states.

impl State<AppData> for Menu {
    fn on_start(&mut self, _data: &mut AppData) {}
    fn on_stop(&mut self, _data: &mut AppData) {}
    fn on_pause(&mut self, _data: &mut AppData) {}
    fn on_resume(&mut self, _data: &mut AppData) {}

    fn on_tick(&mut self, _data: &mut AppData) -> Trans<AppData> { 
        Trans::None
    }

    fn on_shadow_tick(&mut self, _data: &mut AppData) -> Trans<AppData> {
        Trans::None
    }
}

Your IDE might bring the data parameter on the methods as _data. This tells rust it's not currently being used. If you're going to use it, change it do data.

All of the methods are documented below in Callbacks and in the crate's docs.

All of the Transitions are documented below in Transition and in the crate's docs.

  • Instatiate your data and a new stack; set up a loop.

fn main() {
    let data = AppData::default();
    let stack = Stack::<AppData>::new();
    
    // push at least one state directly, otherwise `is_running` will immediately 
    // return false.
    stack.push(&mut data, Box::new(Menu));
    // or stack_push!(stack, data, Menu);
    
    while stack.is_running() { // keeps going until there are no states inside.
        stack.tick(&mut data);
        // or stack_tick!(stack, data);
    }

If you are goin to use is_running, make sure that eventually at least one of your states requests for a Trans::Quit. Otherwise you'll have an infinite loop. If you need your ticks to be called, for instance, 60 times/frames per second, you'd do that logic here. The solstack lib won't opinate on how or how often to tick, though features are planned for that.

Callbacks

METHODDESCRIPTIONTRANS

on_start

Called when this State first enters the stack.

no

on_stop

Called when this State is popped from the stack.

no

on_pause

Called when this State loses its topmost position in the stack to another State that has been pushed on top of it.

no

on_resume

Called when this State regains the first position in the stack. Id est, when other States above it are popped.

no

on_tick

Represents a single tick/update. It’s called when the Stack’s .tick() is called. Your loop logic should call your stack’s tick, not this directly.

yes

on_shadow_tick

Represents a single tick/update independent of this State position in the stack. It’s called when the Stack’s .tick() is called.

NOTE: on_shadow_tick is called BEFORE on_tick.

yes

The methods marked as TRANS requires returning a Trans (see how at the chapter below Transitions.

Transitions

TRANSITIONCALLSDESCRIPTION

Push(Box<State<D>>)

stack.push stack_push! trans_push! Trans::Push

Pushes a new state above the current one. Effectively pauses the current state until everything above it is popped.

Pop

stack.pop stack_pop! trans_pop! Trans::Pop

Pops the current state from the stack.

Replace<Box<State<D>>

stack.replace stack_replace! trans_replace! Trans::Replace

Pops and pushes a new state. Effectively replaces the current state with a new one.

Isolate<Box<State<D>>

stack.isolate stack_isolate! trans_isolate! Trans::Isolate

Pops every state from the stack and pushes a new one. Effectively isolates it as the only state on the stack.

Quit

stack.quit stack_quit! trans_quit! Trans::Quit

Pops everything from the stack. Effectively ends the state stack machine.

None

trans_none! Trans::None

Does nothing to the stack. Effectively keeps the current state and the stack the way it is.

Stack Ticking (Not a Transition)

stack.tick stack_tick!

Ticks the current (topmost at the stack) state once and performs any necessary transitions. Also ticks every state's shadow tick and performs their transitions.

Last updated