moonshine-behavior 0.1.3

Minimalistic state machine for Bevy game engine.
Documentation
# 🎚️ Moonshine Behavior

Minimalistic state machine for [Bevy](https://github.com/bevyengine/bevy) game engine.

## Overview


This crates is designed to provide a simple, stack-based, lightweight implementation of state machines for Bevy entities.

### Features

- Simple. Minimal overhead for defining and setting up behaviors.
- Behaviors can be started, paused, resumed, and stopped.
- Event driven API which allows systems to react to behavior changes on entities.
- Multiple behaviors with different types may exist on the same entity to define complex state machines.

## Usage


A behavior, typically implemented as an `enum`, is a `Component` which represents some state of its entity. Each behavior is associated with a stack.
When the next behavior is started, the current one is pushed onto the stack (if resumable) and paused.

### Setup


#### 1. Define your behavior data as an `enum`:

```rust
use bevy::prelude::*;
use moonshine_behavior::prelude::*;

#[derive(Component, Default, Debug, Reflect, FromReflect)]

#[reflect(Component)]

enum Bird {
    #[default]
    Idle,
    Fly,
    Sleep,
    Chirp,
}
```

#### 2. Implement the `Behavior` trait:

```rust
impl Behavior for Bird {
    fn allows_next(&self, next: &Self) -> bool {
        use Bird::*;
        match self {
            Idle => matches!(next, Sleep | Fly | Chirp),
            Fly => matches!(next, Chirp),
            Sleep | Chirp => false,
        }
    }
}
```
This trait defines the possible transitions for your behavior.
In this example:
  - a bird may sleep, fly, or chirp when idle
  - a bird may chirp when flying
  - a bird may not fly or chirp when sleeping

#### 3. Register the `Behavior` and its transition:

Add a `BehaviorPlugin<T>` to your `App` to register the behavior events and types.
Use `transition()` system to trigger behavior transitions whenever you want.
```rust
app.add_plugins(BehaviorPlugin::<Bird>::default())
    .add_systems(Update, transition::<Bird>);
```

You can define your systems before or after the `transition` system.
Usually, systems that cause behavior change should run before transition while systems that handle behavior logic should run after transition.
  
#### 4. Spawn a `BehaviorBundle`:

For behavior system to work, you must insert your behavior using a `BehaviorBundle`.
This bundle also inserts an instance of your behavior. This is referred to as the initial behavior.
```rust
fn spawn_bird(mut commands: Commands) {
    commands.spawn(BehaviorBundle::<Bird>::default());
}
```

### Transitions


An entity spawned with a `BehaviorBundle` may be queried using `BehaviorRef` and `BehaviorMut` world queries.

- `BehaviorRef` may be used to read the current/previous behaviors.
- `BehaviorMut` may be used to read the current/previous behaviors and request behavior transitions.

To access current behavior, use `Deref` on either `BehaviorRef` or `BehaviorMut`.<br/>
To access previous behavior, use `.previous()`:
```rust
fn is_chirping_while_flying(bird: Query<BehaviorRef<Bird>>) -> bool {
    let behavior = bird.single();
    matches!(*behavior, Chirp) && matches!(behavior.previous(), Some(Fly))
}
```

To start some next behavior, use `.try_start()`:
```rust
fn chirp(mut bird: Query<BehaviorMut<Bird>>) {
    bird.single_mut().try_start(Chirp);
}
```

To stop current behavior and resume the previous behavior, use `.stop()`:
```rust
fn stop(mut bird: Query<BehaviorMut<Bird>>) {
    bird.single_mut().stop();
}
```

To stop current behavior and resume the initial behavior, use `.reset()`:
```rust
fn reset(mut bird: Query<BehaviorMut<Bird>>) {
    bird.single_mut().reset();
}
```

When a transition is requested, it is not invoked immediately. Instead, it is invoked whenever the registered `transition()` system is run.
You may register your systems before or after `transition()` to perform any logic as required.

> **Warning**<br/>
> Be mindful that only one transition may be invoked per application update, per entity. This is an intentional design choice.<br/>
> If multiple transitions are requested on the same entity within the same update cycle, only the last one is invoked, and a warning is logged.

### Events


Any time a transition is invoked, an associated event is dispatched. These events may be used by other systems to react to behavior changes.

Each event (except `StoppedEvent`) carries only the entity ID for which the behavior was started, paused, or resumed.

For `StartedEvent` and `ResumedEvent`, the behavior exists on the entity itself.
You may access it either using a normal query (e.g. `Query<&Bird>`), or using `BehaviorRef`.
```rust
fn on_chirp_started(mut events: Started<Bird>, query: Query<BehaviorRef<Bird>>) {
    for event in events.iter() {
        let entity = event.entity();
        let behavior = query.get(entity).unwrap();
        if let Chirp = *behavior {
            info!("{entity:?} has started chirping!");
        }
  }
}

fn on_chirp_resumed(mut events: Resumed<Bird>, query: Query<BehaviorRef<Bird>>) {
    for event in events.iter() {
        let entity = event.entity();
        let behavior = query.get(entity).unwrap();
        if let Chirp = *behavior {
            info!("{entity:?} is chirping again!");
        }
  }
}
```
For `PausedEvent`, the paused behavior is the previous behavior on the data, which is accessible using `.previous()`:
```rust
fn on_chirp_paused(mut events: Paused<Bird>, query: Query<BehaviorRef<Bird>>) {
    for event in events.iter() {
        let entity = event.entity();
        let behavior = query.get(entity).unwrap();
        if let Chirp = behavior.previous() {
            info!("{entity:?} is no longer chirping.");
        }
  }
}
```
For `StoppedEvent`, the stopped behavior is accessible through the event itself:
```rust
fn on_chirp_stopped(mut events: Stopped<Bird>) {
    for event in events.iter() {
        let entity = event.entity();
        let behavior = event.behavior();
        if let Chirp = *behavior {
            info!("{entity:?} has stopped chirping.");
        }
  }
}
```
### Activation/Suspension


In some cases, it may be necessary to run some logic if a behavior is paused OR stopped (suspension), or started OR resumed (activation).<br/>
To handle activation and suspension, you may use a simple `Changed` query:
```rust
fn on_chirp_activated(query: Query<BehaviorRef<Bird>, Changed<Bird>>) {
    if let Ok(behavior) = query.get_single() {
        if let Chirp = *behavior {
            info!("{entity:?} is chirping!");
        }        
    }
}

fn on_chirp_suspended(query: Query<BehaviorRef<Bird>, Changed<Bird>>) {
    if let Ok(behavior) = query.get_single() {
        if let Chirp = behavior.previous() {
            info!("{entity:?} is not chirping.");
        }        
    }
}
```

## Examples


See [bird.rs](examples/bird.rs) for a complete implementation of the `Bird` behavior.

## Support


Find me on Bevy Discord server, or post an issue.