MCPcopy Index your code
hub / github.com/benfrankel/pyri_state

github.com/benfrankel/pyri_state @v0.6.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.6.0 ↗ · + Follow
351 symbols 595 edges 34 files 173 documented · 49%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

pyri_state

Crates.io Docs License

pyri_state is a bevy_state alternative offering flexible change detection & scheduling.

#[derive(State, Clone, PartialEq, Eq)]
struct Level(usize);

app.add_systems(StateFlush, state!(Level(4 | 7 | 10)).on_enter(save_progress));

See the examples and documentation for more information.

Comparison to bevy_state

State pattern-matching

In pyri_state, state pattern-matching is directly supported:

// Save progress when entering level 4, 7, or 10.
app.add_systems(StateFlush, state!(Level(4 | 7 | 10)).on_enter(save_progress));

There are a few ways to do this using bevy_state:

  1. Add a system for every possible matching state.
for x in [4, 7, 10] {
    app.add_systems(OnEnter(Level(x)), save_progress);
}
  1. Use a custom substate.
app.add_systems(OnEnter(SaveProgressLevel), save_progress);

#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
struct SaveProgressLevel;

impl SubStates for SaveProgressLevel {
    type SourceStates = Level;

    fn should_exist(sources: Level) -> Option<Self> {
        matches!(sources, Level(4 | 7 | 10)).then(Self)
    }
}
  1. Use a custom schedule.
app.add_systems(OnSaveProgress, save_progress);

app.add_systems(
    StateTransition,
    last_transition::<Level>
        .pipe(run_save_progress)
        .in_set(EnterSchedules::<Level>::default()),
);

#[derive(ScheduleLabel, Clone, Eq, PartialEq, Hash, Debug)]
struct OnSaveProgress;

fn run_save_progress(transition: In<Option<StateTransitionEvent<S>>>, world: &mut World) {
    if matches!(transition.0, Some(StateTransitionEvent {
        entered: Some(Level(4 | 7 | 10)),
        ..
    })) {
        let _ = world.try_run_schedule(OnSaveProgress);
    }
}

Note that option 1 is prohibitively expensive when the pattern has too many matches, like Level(x) if x % 2 == 0. Options 2 and 3 add a confusing layer of indirection and boilerplate, hiding the actual pattern-matching in the SubStates implementation or the run_my_schedule exclusive system.

Even worse, option 2 is subtly broken: if you transition from state A to B where both states match the pattern, bevy_state will silently discard the substate's transition because it's a same-state transition.

State refreshing

In pyri_state, state refreshing is supported out-of-the-box:

// Restart game on R press.
app.add_systems(Update, Level::refresh.run_if(input_just_pressed(KeyCode::R)));
// Schedule a system for when any level restarts.
app.add_systems(StateFlush, Level::ANY.on_refresh(|| info!("Restarted level")));
// Refreshing a state will also reuse its exit, trans, and enter hooks.
app.add_systems(StateFlush, Level::ANY.on_exit(tear_down_level));
// You can explicitly check whether the state has changed, if you want.
app.add_systems(StateFlush, Level::ANY.on_enter(load_new_level.run_if(Level::will_change)));

The equivalent in bevy_state requires building your own custom schedules (e.g. OnReExit, OnReTransition, OnReEnter, OnChangeExit, OnChangeTransition, OnChangeEnter, etc.) and hooking them into the state transition internals, as in this example. This is a seriously discouraging amount of boilerplate for something that should be a basic feature.

And more

  • Custom storage: In pyri_state, the next state can be stored in any custom data structure. For example, you can store the next state in a stack to implement a "back button" feature for a menu state as easily as Menu::pop. This is currently impossible in bevy_state, which only supports enum NextState.
  • Direct mutation: In pyri_state, systems can mutate the next state value directly (e.g. level.0 += 1). In bevy_state, you have to clone the current state, mutate it, and set that as the next state. As a consequence, if multiple systems mutate the same state on the same frame, they'll completely overwrite each other, leading to rare, confusing bugs that direct mutation would often circumvent entirely.
  • Local states: In pyri_state, states can be components. This is currently impossible in bevy_state, which only supports global states.

Bevy version compatibility

bevy version pyri_state version
0.18 0.6
0.17 0.5
0.16 0.4
0.15 0.3
0.14 0.2
0.13 0.1

License

This crate is available under either of MIT or Apache-2.0 at your choice.

Extension points exported contracts — how you extend this code

NextState (Interface)
A [`Resource`] that determines the next state for [`Self::State`]. Use [`NextRef`](crate::access::NextRef) or [`FlushRe [4 …
src/next_state/mod.rs
StatePattern (Interface)
A type that can match a subset of values of the [`State`] type `S`. If `S` implements `Eq`, it can be used directly as [3 …
src/pattern.rs
State (Interface)
A [`Resource`] that can be used as a state. This trait can be [derived](pyri_state_derive::State) or implemented manual [1 …
src/state.rs
AppExtState (Interface)
An extension trait for [`App`] that provides methods for adding [`State`] types. [1 implementers]
src/setup.rs
StateExtBevy (Interface)
An extension trait for [`State`] types that provides conversion to [`BevyState`]. [1 implementers]
src/extra/bevy_state.rs
NextStatePairMut (Interface)
Define a custom extension trait to attach extra systems and run conditions to `State` types using your `NextState` type. [1 …
examples/next_state.rs
NextStateMut (Interface)
A [`NextState`] type that allows [`Self::State`](NextState::State) to be mutated directly. Use [`NextMut`](crate::acces [3 …
src/next_state/mod.rs
StateTransPattern (Interface)
A type that can match a subset of transitions in the [`State`] type `S`. A tuple of two [`StatePattern`] types can be u [3 …
src/pattern.rs

Core symbols most depended-on inside this repo

concat
called by 26
derive/src/util.rs
enter
called by 23
src/access.rs
push
called by 17
src/next_state/stack.rs
unwrap
called by 16
src/next_state/buffer.rs
get
called by 15
src/access.rs
before
called by 11
src/schedule/resolve_state.rs
on_update
called by 10
src/pattern.rs
on_enter
called by 10
src/pattern.rs

Shape

Method 181
Function 85
Class 51
Interface 23
Enum 11

Languages

Rust100%

Modules by API surface

src/access.rs36 symbols
src/next_state/stack.rs35 symbols
src/pattern.rs30 symbols
src/state.rs25 symbols
src/next_state/sequence.rs23 symbols
src/next_state/buffer.rs21 symbols
src/extra/react.rs20 symbols
src/setup.rs14 symbols
src/debug/log_flush.rs14 symbols
src/schedule/resolve_state.rs12 symbols
examples/pattern_matching.rs12 symbols
examples/next_state.rs12 symbols

For agents

$ claude mcp add pyri_state \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page