Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

bevy_flurx Overview

Note: This document is generated by AI. If you find any mistakes or unnatural parts, please report them in the issue.

bevy_flurx is a library that provides functionality similar to coroutines for the Bevy game engine, allowing you to write sequential processing for delays, user input, animations, and more.

What is bevy_flurx?

bevy_flurx allows you to write asynchronous code in a sequential manner, making it easier to implement complex behaviors that involve waiting for events, delays, or other conditions. The library is designed to be used incrementally, meaning there's no need to rewrite existing applications to incorporate it.

The main component of bevy_flurx is the Reactor, which schedules and executes tasks. Each task can use various actions to perform operations or wait for conditions.

Key Features

  • Sequential Processing: Write code that looks sequential but executes asynchronously
  • Incremental Adoption: Use alongside existing Bevy systems without rewriting your application
  • Rich Action Library: Various actions for different use cases:
    • once: Actions that run only once
    • wait: Actions that continue to execute every frame according to specified conditions
    • delay: Actions that perform delay processing
    • Action chaining with then, pipe, and through

Basic Example

use bevy::prelude::*;
use bevy_flurx::prelude::*;
use core::time::Duration;

fn main() {
    App::new()
        .insert_resource(Count(0))
        .add_plugins((
            DefaultPlugins,
            FlurxPlugin,
        ))
        .add_systems(Startup, spawn_reactor)
        .run();
}

#[derive(Resource)]
struct Count(usize);

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Run a system once
        let current_count: usize = task.will(Update, once::run(|mut count: ResMut<Count>| {
            count.0 += 1;
            count.0
        })).await;
        
        // Wait until a condition is met
        task.will(Update, wait::until(|mut count: ResMut<Count>| {
            count.0 += 1;
            count.0 == 4
        })).await;
        
        // Delay for a specific time
        task.will(Update, delay::time().with(Duration::from_secs(1))).await;
        
        // Chain actions together
        let message = task.will(Update, {
            delay::frames().with(30)
                .then(once::run(|count: Res<Count>| {
                    count.0
                }))
                .pipe(once::run(|In(count): In<usize>| {
                    format!("count is {count}")
                }))
        }).await;
        
        info!("Done!");
    }));
}

Feature Flags

bevy_flurx provides several feature flags to enable additional functionality:

Flag NameDescriptionDefault
audioAudio actionsfalse
recordUndo/redo actions and eventsfalse
side-effectThread/async side effectsfalse
stateState actionsfalse
tokioUse tokio's runtime directly in the reactorfalse
stdEnable features that depend on the standard libraryfalse

Next Steps

Check out the Actions section to learn more about the different types of actions available in bevy_flurx.

Actions

Actions are the core building blocks of bevy_flurx. They define how tasks behave and interact with the Bevy ECS. This section provides an overview of the different types of actions available in bevy_flurx.

Action Types

bevy_flurx provides several types of actions for different use cases:

once

The once module defines actions that run only once. These are useful for performing one-time operations, such as initializing resources, spawning entities, or sending events.

Learn more about once actions

wait

The wait module defines actions that continue to execute every frame according to specified conditions. These are useful for waiting for user input, events, or other conditions to be met.

Learn more about wait actions

delay

The delay module defines actions that perform delay processing. These are useful for waiting for a specific amount of time or a specific number of frames.

Learn more about delay actions

omit

The omit module provides mechanisms to omit input and/or output types from an action. This is particularly useful for defining groups of actions by simplifying their type signatures.

Learn more about omit actions

sequence

The sequence module provides mechanisms for sequentially combining actions. This is particularly useful for creating complex action flows by chaining multiple actions together.

Learn more about sequence actions

pipe

The pipe module provides a mechanism to pipe actions together, where the output of one action is used as the input for another action. This is particularly useful for creating data processing pipelines.

Learn more about pipe actions

through

The through module provides a mechanism to execute an action while preserving the output of the previous action. This is particularly useful for inserting actions like delays into a pipeline without affecting the data flow.

Learn more about through actions

map

The map module provides mechanisms to transform the output of an action using a mapping function. This is particularly useful for data transformation and type conversion between actions.

Learn more about map actions

inspect

The inspect module provides mechanisms to clone and inspect input values via auxiliary actions without disrupting their primary flow. This is particularly useful for debugging, logging, or performing side-effects on input values.

Learn more about inspect actions

remake

The remake module provides a mechanism to create a new action based on an existing action's Runner and Output. This is particularly useful for transforming actions while preserving their input type but changing their output type.

Learn more about remake actions

Action Chaining

bevy_flurx provides several ways to chain actions together:

then

The then method allows you to execute one action after another. The output of the first action is discarded, and the output of the second action is returned.

pipe

The pipe method allows you to pass the output of one action as the input to another action.

through

The through method allows you to execute an action while preserving the output of the previous action.

Feature-Gated Actions

bevy_flurx provides additional actions through feature flags:

audio

The audio feature provides actions for audio playback and waiting.

record

The record feature provides actions for undo/redo functionality.

side-effect

The side-effect feature provides actions for handling side effects like asynchronous runtime or threads.

state

The state feature provides actions for state management.

tokio

The tokio feature allows you to use tokio's runtime directly in the reactor.

Next Steps

Explore the specific action types to learn more about their capabilities and how to use them:

Once Actions

Once actions are actions that run a system exactly once and then complete. They are useful for one-time operations like sending events, modifying resources, or performing state transitions.

Available Once Actions

  • run - Run a system once
  • event - Send an event once
  • res - Modify a resource once
  • non_send - Modify a non-send resource once
  • switch - Switch between actions based on a condition
  • state - Transition to a new state once
  • audio - Play audio once

Each action is designed to be used with the Reactor::schedule or task.will methods to schedule a one-time operation.

Basic Usage

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

// Run a system once during the Update schedule
Reactor::schedule(|task| async move {
    task.will(Update, once::run(|world: &mut World| {
        // Do something once
    })).await;
});
}

The once actions are part of the core functionality of bevy_flurx, providing simple building blocks for more complex action sequences.

once::run

The once::run action executes a system exactly once and then completes. This is the most basic and versatile once action, allowing you to run any Bevy system as a one-time operation.

Usage

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

Reactor::schedule(|task| async move {
    task.will(Update, once::run(|world: &mut World| {
        // Your one-time logic here
    })).await;
});
}

Parameters

  • system: Any valid Bevy system that can be converted using IntoSystem

Return Value

The action returns the value returned by the system.

Example: Sending an App Exit Event

#![allow(unused)]
fn main() {
use bevy::app::AppExit;
use bevy::prelude::*;
use bevy_flurx::prelude::*;

Reactor::schedule(|task| async move {
    task.will(Update, once::run(|mut ew: EventWriter<AppExit>| {
        ew.write(AppExit::Success);
    })).await;
});
}

Example: Modifying a Resource

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

#[derive(Resource, Default)]
struct Score(u32);

Reactor::schedule(|task| async move {
    task.will(Update, once::run(|mut score: ResMut<Score>| {
        score.0 += 10;
    })).await;
});
}

When to Use

Use once::run when you need to:

  • Execute arbitrary logic once
  • Access multiple system parameters in a single action
  • Return a custom value from your action

For more specific use cases, consider the specialized once actions like once::event or once::res.

once::event

The once::event module provides actions for sending Bevy events exactly once. These actions are specialized versions of once::run that focus specifically on event sending operations.

Functions

send

#![allow(unused)]
fn main() {
once::event::send<E>() -> ActionSeed<E, ()>
}

Creates an action that sends a specified event once. The event must be provided using the .with() method.

Example

#![allow(unused)]
fn main() {
use bevy::app::AppExit;
use bevy::prelude::*;
use bevy_flurx::prelude::*;

Reactor::schedule(|task| async move {
    task.will(Update, once::event::send().with(AppExit::Success)).await;
});
}

send_default

#![allow(unused)]
fn main() {
once::event::send_default<E>() -> ActionSeed
}

Creates an action that sends a default-constructed event once. The event type must implement the Default trait.

Example

#![allow(unused)]
fn main() {
use bevy::app::AppExit;
use bevy::prelude::*;
use bevy_flurx::prelude::*;

Reactor::schedule(|task| async move {
    task.will(Update, once::event::send_default::<AppExit>()).await;
});
}

app_exit_success

#![allow(unused)]
fn main() {
once::event::app_exit_success() -> Action<AppExit, ()>
}

A convenience function that creates an action to send the AppExit::Success event once, which will exit the application successfully.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

Reactor::schedule(|task| async move {
    task.will(Update, once::event::app_exit_success()).await;
});
}

When to Use

Use once::event actions when you need to:

  • Send a specific event exactly once
  • Send a default-constructed event exactly once
  • Exit the application with a success status

For more complex event handling or when you need to access other system parameters, consider using the more general once::run action.

once::res

The once::res module provides actions for managing Bevy resources exactly once. These actions are specialized versions of once::run that focus specifically on resource operations.

Functions

init

#![allow(unused)]
fn main() {
once::res::init<R>() -> ActionSeed
}

Creates an action that initializes a resource using its Default implementation. The resource will only be initialized if it doesn't already exist.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

#[derive(Resource, Default)]
struct GameScore(u32);

Reactor::schedule(|task| async move {
    task.will(Update, once::res::init::<GameScore>()).await;
});
}

insert

#![allow(unused)]
fn main() {
once::res::insert<R>() -> ActionSeed<R>
}

Creates an action that inserts a provided resource. If the resource already exists, it will be replaced.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

#[derive(Resource)]
struct GameScore(u32);

Reactor::schedule(|task| async move {
    task.will(Update, once::res::insert().with(GameScore(100))).await;
});
}

remove

#![allow(unused)]
fn main() {
once::res::remove<R>() -> ActionSeed
}

Creates an action that removes a resource if it exists.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

#[derive(Resource)]
struct TemporaryResource;

Reactor::schedule(|task| async move {
    task.will(Update, once::res::remove::<TemporaryResource>()).await;
});
}

When to Use

Use once::res actions when you need to:

  • Initialize a resource with its default value
  • Insert or replace a resource with a specific value
  • Remove a resource that's no longer needed

For more complex resource operations or when you need to access other system parameters, consider using the more general once::run action with ResMut or Res parameters.

once::non_send

The once::non_send module provides actions for managing Bevy non-send resources exactly once. Non-send resources are resources that are not thread-safe and can only be accessed from the main thread.

Functions

init

#![allow(unused)]
fn main() {
once::non_send::init<R>() -> ActionSeed
}

Creates an action that initializes a non-send resource using its Default implementation. The resource will only be initialized if it doesn't already exist.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

#[derive(Default)]
struct WindowHandle(/* some non-Send type */);

Reactor::schedule(|task| async move {
    task.will(Update, once::non_send::init::<WindowHandle>()).await;
});
}

insert

#![allow(unused)]
fn main() {
once::non_send::insert<R>() -> ActionSeed<R>
}

Creates an action that inserts a provided non-send resource. If the resource already exists, it will be replaced.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

struct WindowHandle(/* some non-Send type */);

Reactor::schedule(|task| async move {
    task.will(Update, once::non_send::insert().with(WindowHandle(/* ... */)))
        .await;
});
}

remove

#![allow(unused)]
fn main() {
once::non_send::remove<R>() -> ActionSeed
}

Creates an action that removes a non-send resource if it exists.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

struct WindowHandle(/* some non-Send type */);

Reactor::schedule(|task| async move {
    task.will(Update, once::non_send::remove::<WindowHandle>()).await;
});
}

When to Use

Use once::non_send actions when you need to:

  • Initialize a non-send resource with its default value
  • Insert or replace a non-send resource with a specific value
  • Remove a non-send resource that's no longer needed

Non-send resources are typically used for resources that contain types that cannot be sent between threads, such as:

  • Raw pointers
  • File handles
  • Window handles
  • Other platform-specific resources

For more complex non-send resource operations or when you need to access other system parameters, consider using the more general once::run action with NonSendMut or NonSend parameters.

once::switch

The once::switch module provides actions for controlling Bevy switches exactly once. Switches are a mechanism in bevy_flurx that represent two states (on and off) and can be used to coordinate between Reactors and regular Bevy systems.

Functions

on

#![allow(unused)]
fn main() {
once::switch::on<M>() -> ActionSeed
}

Creates an action that turns a switch on. If the switch doesn't exist, it will be created.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

// Define a marker type for our switch
struct PlayerAnimation;

Reactor::schedule(|task| async move {
    // Turn on the PlayerAnimation switch
    task.will(Update, once::switch::on::<PlayerAnimation>()).await;
});
}

off

once::switch::off<M>() -> ActionSeed

Creates an action that turns a switch off. If the switch doesn't exist, it will be created in the off state.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

// Define a marker type for our switch
struct PlayerAnimation;

Reactor::schedule(|task| async move {
    // Turn off the PlayerAnimation switch
    task.will(Update, once::switch::off::<PlayerAnimation>()).await;
});
}

Using Switches with Systems

Switches are designed to be used with Bevy's run_if condition system to control when systems run:

use bevy::prelude::*;
use bevy_flurx::prelude::*;

struct HeavyTask;

fn main() {
    App::new()
        // This system only runs when the HeavyTask switch is on
        .add_systems(Update, (|mut switch: ResMut<Switch<HeavyTask>>| {
            // Do heavy work...

            // Turn off the switch when done
            switch.off();
        }).run_if(switch_is_on::<HeavyTask>))

        // Spawn a reactor that turns the switch on and waits for it to be turned off
        .add_systems(Startup, |mut commands: Commands| {
            commands.spawn(Reactor::schedule(|task| async move {
                task.will(Update, once::switch::on::<HeavyTask>()).await;
                task.will(Update, wait::switch::off::<HeavyTask>()).await;
                // Continue after the heavy task is complete...
            }));
        });
}

Available Conditions

The following conditions can be used with run_if:

  • switch_is_on<M>() - Returns true if the switch is on
  • switch_is_off<M>() - Returns true if the switch is off
  • switch_just_turned_on<M>() - Returns true only the first time the switch is detected as on
  • switch_just_turned_off<M>() - Returns true only the first time the switch is detected as off

When to Use

Use once::switch actions when you need to:

  • Coordinate between Reactors and regular Bevy systems
  • Control when certain systems should run
  • Signal the completion of asynchronous tasks
  • Create state machines with clear on/off states

Switches are particularly useful for tasks that need to be performed on the main thread but need to be coordinated with asynchronous Reactor tasks.

once::state

The once::state module provides actions for managing Bevy state transitions exactly once. This module is designed to work with Bevy's States system.

Functions

set

#![allow(unused)]
fn main() {
once::state::set<S>() -> ActionSeed<S>
}

Creates an action that sets the next state for a Bevy state machine. The state value must be provided using the .with() method.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

#[derive(States, Copy, Clone, Hash, Eq, PartialEq, Default, Debug)]
enum GameState {
    #[default]
    MainMenu,
    Playing,
    Paused,
    GameOver,
}

Reactor::schedule(|task| async move {
    // Wait for some condition...
    
    // Transition to the Playing state
    task.will(Update, once::state::set().with(GameState::Playing)).await;
});
}

State Machines in Bevy

Bevy's state system allows you to organize your game into distinct states, with systems that run only during specific states. The once::state::set() action provides a way to trigger state transitions from within a Reactor.

When to Use

Use once::state actions when you need to:

  • Trigger state transitions from within a Reactor
  • Create game flow sequences that involve state changes
  • Coordinate asynchronous operations with state-based systems

For more complex state management or when you need to access other system parameters, consider using the more general once::run action with ResMut<NextState<S>> parameter.

once::audio

The once::audio module provides actions for playing audio in Bevy exactly once. This module is designed to work with Bevy's audio system.

Functions

play

#![allow(unused)]
fn main() {
once::audio::play<Path>() -> ActionSeed<Path, Entity>
}

Creates an action that plays an audio file once. The path to the audio file must be provided using the .with() method. The action returns the Entity that the AudioBundle is attached to.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

Reactor::schedule(|task| async move {
    // Play a sound effect once
    let entity = task.will(Update, once::audio::play().with("sounds/explosion.ogg")).await;

    // You can use the returned entity to control the audio later if needed
    println!("Audio playing on entity: {:?}", entity);
});
}

Audio Paths

The path provided to once::audio::play() is loaded using Bevy's AssetServer, so it should be relative to your project's asset directory. The following audio formats are supported by default in Bevy:

  • .ogg (Vorbis)
  • .mp3
  • .wav
  • .flac

When to Use

Use once::audio actions when you need to:

  • Play sound effects in response to game events
  • Start background music
  • Create audio sequences as part of game flow

For more complex audio control or when you need to access other system parameters, consider using the more general once::run action with the appropriate audio components.

Wait Actions

Wait actions are tasks that continue to execute every frame according to specified conditions. They are useful for creating reactive behaviors that respond to events, input, state changes, or other conditions.

Available Wait Actions

  • until - Wait until a condition is true
  • output - Wait until a system returns Some value
  • event - Wait for events
  • switch - Wait for switch state changes
  • state - Wait for state transitions
  • audio - Wait for audio playback to finish
  • input - Wait for input events
  • all - Wait for all actions to complete
  • any - Wait for any action to complete
  • both - Wait for two actions to complete
  • either - Wait for either of two actions to complete

Each action is designed to be used with the Reactor::schedule or task.will methods to create tasks that wait for specific conditions before continuing.

Basic Usage

use bevy::prelude::*;
use bevy_flurx::prelude::*;

// Wait until a condition is met
Reactor::schedule(|task| async move {
    task.will(Update, wait::until(|mut count: Local<usize>| {
        *count += 1;
        *count == 4  // Wait until this condition is true
    })).await;

    // This code runs after the condition is met
    println!("Condition met!");
});

The wait actions are part of the core functionality of bevy_flurx, providing powerful tools for creating reactive and event-driven behaviors in your Bevy applications.

wait::until

The wait::until action continues to execute a system every frame until it returns true. This is one of the most basic and versatile wait actions, allowing you to wait for any condition to be met.

Usage

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

Reactor::schedule(|task| async move {
    task.will(Update, wait::until(|world: &mut World| {
        // Your condition here
        true // Replace with your actual condition
    })).await;
});
}

Parameters

  • system: Any valid Bevy system that returns a boolean value

Return Value

The action returns () (unit) when the condition becomes true.

Example: Waiting for a Counter

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

Reactor::schedule(|task| async move {
    task.will(Update, wait::until(|mut count: Local<usize>| {
        *count += 1;
        *count == 4  // Wait until the counter reaches 4
    })).await;
    
    // This code runs after the counter reaches 4
    println!("Counter reached 4!");
});
}

Example: Waiting for a Resource Value

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

#[derive(Resource, Default)]
struct Score(u32);

Reactor::schedule(|task| async move {
    task.will(Update, wait::until(|score: Res<Score>| {
        score.0 >= 100  // Wait until the score is at least 100
    })).await;
    
    // This code runs after the score reaches 100
    println!("Score reached 100!");
});
}

When to Use

Use wait::until when you need to:

  • Wait for a specific condition to be met
  • Poll a value until it reaches a threshold
  • Create a delay based on a custom condition
  • Implement custom waiting logic that isn't covered by other wait actions

For more specific waiting scenarios, consider using the specialized wait actions like wait::event or wait::input.

wait::output

The wait::output action continues to execute a system every frame until it returns Option::Some. The contents of Some will be the return value of the task. This action provides more flexibility than wait::until by allowing you to return a value when the waiting condition is met.

Usage

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

Reactor::schedule(|task| async move {
    let result = task.will(Update, wait::output(|world: &mut World| {
        // Your logic here
        Some("Result value") // Return Some when condition is met
    })).await;
    
    println!("Got result: {}", result);
});
}

Parameters

  • system: Any valid Bevy system that returns an Option<T>

Return Value

The action returns the value inside the Some variant when the system returns Some.

Example: Waiting for a Counter with Result

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

Reactor::schedule(|task| async move {
    let count = task.will(Update, wait::output(|mut count: Local<usize>| {
        *count += 1;
        if *count == 4 {
            Some(*count)  // Return the count when it reaches 4
        } else {
            None
        }
    })).await;
    
    // This code runs after the counter reaches 4
    println!("Counter reached: {}", count);
});
}

Example: Finding an Entity

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

#[derive(Component)]
struct Target;

Reactor::schedule(|task| async move {
    let entity = task.will(Update, wait::output(|query: Query<Entity, With<Target>>| {
        query.iter().next().map(|e| e)  // Return the first entity with Target component
    })).await;
    
    // This code runs after a Target entity is found
    println!("Found target entity: {:?}", entity);
});
}

When to Use

Use wait::output when you need to:

  • Wait for a condition and get a value when the condition is met
  • Find an entity or resource that meets certain criteria
  • Collect data that becomes available at an unpredictable time
  • Implement custom waiting logic that needs to return a value

For simpler cases where you just need to wait for a condition without returning a value, consider using wait::until instead.

wait::event

The wait::event module provides actions for waiting to receive Bevy events. These actions are useful for creating tasks that respond to events in your game or application.

Functions

comes

wait::event::comes<E>() -> ActionSeed

Creates an action that waits until an event of type E is received. The action completes when the event is received but does not return the event itself.

Example

#![allow(unused)]
fn main() {
use bevy::app::AppExit;
use bevy::prelude::*;
use bevy_flurx::prelude::*;

Reactor::schedule(|task| async move {
    // Wait for an AppExit event
    task.will(Update, wait::event::comes::<AppExit>()).await;
    
    // This code runs after an AppExit event is received
    println!("App is exiting!");
});
}

comes_and

wait::event::comes_and<E>(predicate: impl Fn(&E) -> bool + Send + Sync + 'static) -> ActionSeed

Creates an action that waits until an event of type E is received and the event matches the given predicate. The action completes when a matching event is received but does not return the event itself.

Example

#![allow(unused)]
fn main() {
use bevy::app::AppExit;
use bevy::prelude::*;
use bevy_flurx::prelude::*;

Reactor::schedule(|task| async move {
    // Wait for a successful AppExit event
    task.will(Update, wait::event::comes_and::<AppExit>(|e| {
        e.is_success()
    })).await;
    
    // This code runs after a successful AppExit event is received
    println!("App is exiting successfully!");
});
}

read

wait::event::read<E>() -> ActionSeed<(), E>

Creates an action that waits until an event of type E is received and returns a clone of the event. This is similar to comes, but it returns the event itself.

Example

#![allow(unused)]
fn main() {
use bevy::app::AppExit;
use bevy::prelude::*;
use bevy_flurx::prelude::*;

Reactor::schedule(|task| async move {
    // Wait for an AppExit event and get the event
    let exit_event = task.will(Update, wait::event::read::<AppExit>()).await;
    
    // This code runs after an AppExit event is received
    println!("App is exiting with status: {:?}", exit_event);
});
}

read_and

#![allow(unused)]
fn main() {
wait::event::read_and<E>(predicate: impl Fn(&E) -> bool + Send + Sync + 'static) -> ActionSeed<(), E>
}

Creates an action that waits until an event of type E is received, the event matches the given predicate, and returns a clone of the event. This is similar to comes_and, but it returns the event itself.

Example

#![allow(unused)]
fn main() {
use bevy::app::AppExit;
use bevy::prelude::*;
use bevy_flurx::prelude::*;

Reactor::schedule(|task| async move {
    // Wait for a successful AppExit event and get the event
    let exit_event = task.will(Update, wait::event::read_and::<AppExit>(|e| {
        e.is_success()
    })).await;
    
    // This code runs after a successful AppExit event is received
    println!("App is exiting successfully with event: {:?}", exit_event);
});
}

When to Use

Use wait::event actions when you need to:

  • Wait for specific events to occur before continuing execution
  • React to events in an asynchronous manner
  • Filter events based on their content using predicates
  • Retrieve event data for further processing

For more complex event handling scenarios, consider combining wait::event with other wait actions like wait::either or wait::any to wait for multiple different event types.

wait::switch

The wait::switch module provides actions for waiting for Switch state changes. These actions are useful for coordinating between different parts of your application, allowing tasks to wait for specific switch state changes before continuing execution.

Functions

on

#![allow(unused)]
fn main() {
wait::switch::on<M>() -> ActionSeed
}

Creates an action that waits until a switch of type M is turned on. The action completes when the switch is detected as on.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

// Define a marker type for our switch
struct Animation;

Reactor::schedule(|task| async move {
    // Wait until the Animation switch is turned on
    task.will(Update, wait::switch::on::<Animation>()).await;
    
    // This code runs after the Animation switch is turned on
    println!("Animation switch is now on!");
});
}

off

wait::switch::off<M>() -> ActionSeed

Creates an action that waits until a switch of type M is turned off. The action completes when the switch is detected as off.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

// Define a marker type for our switch
struct Animation;

Reactor::schedule(|task| async move {
    // First turn on the Animation switch
    task.will(Update, once::switch::on::<Animation>()).await;
    
    // Then wait until the Animation switch is turned off
    task.will(Update, wait::switch::off::<Animation>()).await;
    
    // This code runs after the Animation switch is turned off
    println!("Animation switch is now off!");
});
}

When to Use

Use wait::switch actions when you need to:

  • Coordinate between different systems in your application
  • Wait for a specific state change before continuing execution
  • Create state machines with clear on/off states
  • Implement gameplay sequences that depend on switch states

Switches are particularly useful for tasks that need to be performed on the main thread but need to be coordinated with asynchronous Reactor tasks.

wait::state

The wait::state module provides actions for waiting for Bevy state transitions. These actions are useful for coordinating tasks with Bevy's state system, allowing tasks to wait for specific state changes before continuing execution.

Functions

becomes

#![allow(unused)]
fn main() {
wait::state::becomes<S>() -> ActionSeed<S>
}

Creates an action that waits until the state becomes the specified value. The action completes when the state matches the expected value.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

#[derive(States, Eq, PartialEq, Copy, Clone, Hash, Default, Debug)]
enum GameState {
    #[default]
    MainMenu,
    Playing,
    Paused,
    GameOver,
}

Reactor::schedule(|task| async move {
    // Wait until the game state becomes Playing
    task.will(Update, wait::state::becomes().with(GameState::Playing)).await;
    
    // This code runs after the game state becomes Playing
    println!("Game is now in Playing state!");
});
}

When to Use

Use wait::state actions when you need to:

  • Coordinate tasks with Bevy's state system
  • Wait for specific state transitions before continuing execution
  • Create game flow sequences that depend on state changes
  • Implement multi-stage processes that follow a state machine pattern

State-based waiting is particularly useful for game flow control, menu navigation, and level transitions.

wait::audio

The wait::audio module provides actions for waiting for audio playback to finish. These actions are useful for coordinating tasks with audio playback, allowing tasks to wait for audio to complete before continuing execution.

Functions

finished

#![allow(unused)]
fn main() {
wait::audio::finished() -> ActionSeed<Entity, ()>
}

Creates an action that waits until the audio associated with the passed Entity has finished playing. The action completes when the audio playback is complete.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

Reactor::schedule(|task| async move {
    // Play a sound effect and get the entity
    let entity = task.will(Update, once::audio::play().with("sounds/explosion.ogg")).await;
    
    // Wait for the sound to finish playing
    task.will(Update, wait::audio::finished().with(entity)).await;
    
    // This code runs after the sound has finished playing
    println!("Sound effect has finished playing!");
});
}

When to Use

Use wait::audio actions when you need to:

  • Coordinate tasks with audio playback
  • Create sequences of sounds with precise timing
  • Ensure an action only occurs after a sound has finished playing
  • Implement audio-driven gameplay elements

Audio waiting is particularly useful for creating polished audio experiences in games, such as ensuring dialog lines don't overlap, creating rhythmic sequences, or synchronizing gameplay events with audio cues.

wait::input

The wait::input module provides actions for waiting for input events. These actions are useful for creating interactive tasks that respond to user input, allowing tasks to wait for specific input events before continuing execution.

Functions

just_pressed

wait::input::just_pressed<T>() -> ActionSeed<T>

Creates an action that waits until a button has just been pressed. The action completes when the specified button is pressed.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

Reactor::schedule(|task| async move {
    // Wait until the B key is pressed
    task.will(Update, wait::input::just_pressed().with(KeyCode::KeyB)).await;
    
    // This code runs after the B key is pressed
    println!("B key was pressed!");
});
}

pressed

wait::input::pressed<T>() -> ActionSeed<T>

Creates an action that waits until a button is being pressed. The action completes when the specified button is detected as pressed.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

Reactor::schedule(|task| async move {
    // Wait until the B key is being pressed
    task.will(Update, wait::input::pressed().with(KeyCode::KeyB)).await;
    
    // This code runs while the B key is being pressed
    println!("B key is being pressed!");
});
}

any_pressed

wait::input::any_pressed<T>() -> ActionSeed<Vec<T>>

Creates an action that waits until any button in a list is being pressed. The action completes when any of the specified buttons is detected as pressed.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

Reactor::schedule(|task| async move {
    // Wait until either A or B key is pressed
    task.will(Update, wait::input::any_pressed().with(vec![KeyCode::KeyA, KeyCode::KeyB])).await;
    
    // This code runs when either A or B is pressed
    println!("Either A or B key is being pressed!");
});
}

all_pressed

wait::input::all_pressed<T>() -> ActionSeed<Vec<T>>

Creates an action that waits until all buttons in a list are being pressed. The action completes when all of the specified buttons are detected as pressed.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

Reactor::schedule(|task| async move {
    // Wait until both A and B keys are pressed
    task.will(Update, wait::input::all_pressed().with(vec![KeyCode::KeyA, KeyCode::KeyB])).await;
    
    // This code runs when both A and B are pressed
    println!("Both A and B keys are being pressed!");
});
}

just_released

wait::input::just_released<T>() -> ActionSeed<T>

Creates an action that waits until a button has just been released. The action completes when the specified button is released.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

Reactor::schedule(|task| async move {
    // Wait until the B key is released
    task.will(Update, wait::input::just_released().with(KeyCode::KeyB)).await;
    
    // This code runs after the B key is released
    println!("B key was released!");
});
}

any_just_released

#![allow(unused)]
fn main() {
wait::input::any_just_released<T>() -> ActionSeed<Vec<T>>
}

Creates an action that waits until any button in a list has just been released. The action completes when any of the specified buttons is released.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

Reactor::schedule(|task| async move {
    // Wait until either A or B key is released
    task.will(Update, wait::input::any_just_released().with(vec![KeyCode::KeyA, KeyCode::KeyB])).await;
    
    // This code runs when either A or B is released
    println!("Either A or B key was released!");
});
}

When to Use

Use wait::input actions when you need to:

  • Wait for specific user input before continuing execution
  • Create interactive sequences that respond to player actions
  • Implement combo systems or special move detection
  • Create tutorial sequences that guide the player through specific inputs
  • Build context-sensitive controls that change based on game state

Input waiting is particularly useful for creating responsive and interactive gameplay experiences that react to player input in sophisticated ways.

wait::all

The wait::all module provides actions for waiting for multiple actions to complete. These actions are useful for coordinating complex tasks that require multiple conditions to be met before continuing execution.

Functions

all

#![allow(unused)]
fn main() {
wait::all<Actions>() -> ActionSeed<Actions>
}

Creates an action that waits until all the specified actions are completed. The output value of this function is ().

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;
use bevy_flurx::actions;
use core::time::Duration;

Reactor::schedule(|task| async move {
    // Wait until all three conditions are met
    task.will(Update, wait::all().with(actions![
        once::run(||{}),
        delay::time().with(Duration::from_millis(300)),
        wait::input::just_pressed().with(KeyCode::KeyA)
    ])).await;
    
    // This code runs after all actions are completed
    println!("All actions completed!");
});
}

wait_all! (macro)

wait_all![action1, action2, ...]

A macro that waits until all tasks are done. The return value type is a tuple, with its length equal to the number of passed tasks. This is similar to wait::all(), but it returns the outputs of each action.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;
use bevy_flurx::wait_all;

#[derive(Default, Clone, Event, PartialEq, Debug)]
struct Event1;
#[derive(Default, Clone, Event, PartialEq, Debug)]
struct Event2;

Reactor::schedule(|task| async move {
    // Wait for both events and get their values
    let (event1, event2) = task.will(Update, wait_all![
        wait::event::read::<Event1>(),
        wait::event::read::<Event2>()
    ]).await;
    
    // This code runs after both events are received
    println!("Received events: {:?}, {:?}", event1, event2);
});
}

When to Use

Use wait::all and wait_all! when you need to:

  • Wait for multiple conditions to be met before continuing execution
  • Coordinate complex initialization sequences
  • Gather results from multiple asynchronous operations
  • Create synchronization points in your game flow
  • Implement "AND" logic for multiple waiting conditions

For "OR" logic (waiting for any of multiple conditions), consider using wait::any instead.

wait::any

The wait::any module provides actions for waiting for any of multiple actions to complete. These actions are useful for creating tasks that can proceed when any of several conditions are met, allowing for more flexible control flow.

Functions

any

#![allow(unused)]
fn main() {
wait::any<Actions>() -> ActionSeed<Actions, usize>
}

Creates an action that waits until the execution of one of the actions is completed. The output value is the index of the completed action.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;
use bevy_flurx::actions;
use bevy::app::AppExit;

Reactor::schedule(|task| async move {
    // Wait until either the B key is pressed or an AppExit event is received
    let index = task.will(Update, wait::any().with(actions![
        wait::input::just_pressed().with(KeyCode::KeyB),
        wait::event::comes::<AppExit>()
    ])).await;
    
    // Check which action completed
    match index {
        0 => println!("B key was pressed!"),
        1 => println!("AppExit event received!"),
        _ => unreachable!(),
    }
});
}

When to Use

Use wait::any when you need to:

  • Wait for the first of several conditions to be met
  • Create branching logic based on which condition occurs first
  • Implement timeout patterns with alternative success paths
  • Handle multiple possible user inputs or events
  • Implement "OR" logic for multiple waiting conditions

For "AND" logic (waiting for all conditions to be met), consider using wait::all instead.

wait::both

The wait::both module provides an action for waiting for two specific actions to complete. This action is useful for coordinating tasks that require exactly two conditions to be met before continuing execution.

Functions

both

#![allow(unused)]
fn main() {
wait::both<LI, LO, RI, RO>(lhs: impl Into<Action<LI, LO>> + 'static, rhs: impl Into<Action<RI, RO>> + 'static) -> Action<(LI, RI), (LO, RO)>
}

Creates an action that waits until both tasks are done. The output value is a tuple containing the outputs from both actions.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

Reactor::schedule(|task| async move {
    // Wait for both the R key to be pressed and an AppExit event to be received
    let (_, exit_event) = task.will(Update, wait::both(
        wait::input::just_pressed().with(KeyCode::KeyR),
        wait::event::read::<AppExit>()
    )).await;
    
    // This code runs after both conditions are met
    println!("R key was pressed and AppExit event received: {:?}", exit_event);
});
}

When to Use

Use wait::both when you need to:

  • Wait for exactly two specific conditions to be met
  • Collect the results from two different asynchronous operations
  • Coordinate between two different parts of your game
  • Create synchronization points that depend on two specific events or inputs
  • Implement simple "AND" logic for exactly two waiting conditions

For waiting on more than two conditions, consider using wait::all or the wait_all! macro instead.

wait::either

The wait::either module provides an action for waiting for either of two actions to complete. This action is useful for creating tasks that can proceed when either of two conditions is met, allowing for more flexible control flow.

Types

Either

#![allow(unused)]
fn main() {
enum Either<L, R> {
    Left(L),
    Right(R),
}
}

This enum represents the result of wait::either. It contains either the result of the first action (Left) or the result of the second action (Right).

Methods

  • is_left() - Returns true if the value is Left
  • is_right() - Returns true if the value is Right

Functions

either

#![allow(unused)]
fn main() {
wait::either<LI, LO, RI, RO>(lhs: impl Into<Action<LI, LO>> + 'static, rhs: impl Into<Action<RI, RO>> + 'static) -> Action<(LI, RI), Either<LO, RO>>
}

Creates an action that waits until either of the two tasks is completed. The output value is an Either<LO, RO> enum that indicates which action completed and contains its output.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

Reactor::schedule(|task| async move {
    // Wait until either a system returns false or an AppExit event is received
    let result = task.will(Update, wait::either(
        wait::until(|| false),
        wait::event::read::<AppExit>()
    )).await;
    
    match result {
        Either::Left(_) => println!("System returned false"),
        Either::Right(exit_event) => println!("AppExit event received: {:?}", exit_event),
    }
});
}

When to Use

Use wait::either when you need to:

  • Wait for the first of two specific conditions to be met
  • Create branching logic based on which of two conditions occurs first
  • Implement timeout patterns with a success path
  • Handle two possible user inputs or events
  • Implement simple "OR" logic for exactly two waiting conditions

For waiting on more than two conditions, consider using wait::any instead. For waiting on both conditions, use wait::both.

delay

The delay module defines actions that perform delay processing. These are useful for waiting for a specific amount of time or a specific number of frames.

Basic Usage

The delay module provides two main functions:

time

The time function delays the execution for a specified amount of time.

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;
use core::time::Duration;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Delay for 1 second
        task.will(Update, delay::time().with(Duration::from_secs(1))).await;
        
        // Delay for 500 milliseconds
        task.will(Update, delay::time().with(Duration::from_millis(500))).await;
    }));
}
}

Under the hood, delay::time() uses a Timer with TimerMode::Once to track the elapsed time. The action completes when the timer finishes.

frames

The frames function delays the execution for a specified number of frames.

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Delay for 30 frames
        task.will(Update, delay::frames().with(30)).await;
        
        // Delay for 5 frames
        task.will(Update, delay::frames().with(5)).await;
    }));
}
}

Under the hood, delay::frames() uses a Local<usize> to track the number of frames that have passed. The action completes when the specified number of frames have been processed.

Combining with Other Actions

The delay actions can be combined with other actions using the then, pipe, and through methods.

then

The then method allows you to execute an action after a delay.

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;
use core::time::Duration;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Delay for 1 second, then send an event
        task.will(Update, delay::time().with(Duration::from_secs(1))
            .then(once::event::app_exit_success())
        ).await;
    }));
}
}

through

The through method allows you to delay the output of a previous action.

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;
use core::time::Duration;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Get a value, then delay for 1 second before returning it
        let value = task.will(Update, once::run(|count: Res<Count>| {
            count.0
        }).through(delay::time().with(Duration::from_secs(1)))).await;
    }));
}
}

Examples

Delayed Animation

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;
use core::time::Duration;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Spawn an entity
        let entity = task.will(Update, once::run(|mut commands: Commands| {
            commands.spawn(SpriteBundle {
                sprite: Sprite {
                    color: Color::RED,
                    custom_size: Some(Vec2::new(50.0, 50.0)),
                    ..default()
                },
                transform: Transform::from_translation(Vec3::new(-100.0, 0.0, 0.0)),
                ..default()
            }).id()
        })).await;
        
        // Move the entity to the right over time
        for i in 0..10 {
            task.will(Update, once::run(move |mut transforms: Query<&mut Transform>| {
                let mut transform = transforms.get_mut(entity).unwrap();
                transform.translation.x += 20.0;
            })).await;
            
            // Delay between movements
            task.will(Update, delay::time().with(Duration::from_millis(100))).await;
        }
    }));
}
}

Frame-Based Animation

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Spawn an entity
        let entity = task.will(Update, once::run(|mut commands: Commands| {
            commands.spawn(SpriteBundle {
                sprite: Sprite {
                    color: Color::BLUE,
                    custom_size: Some(Vec2::new(50.0, 50.0)),
                    ..default()
                },
                transform: Transform::from_translation(Vec3::new(0.0, -100.0, 0.0)),
                ..default()
            }).id()
        })).await;
        
        // Move the entity upward over frames
        for i in 0..10 {
            task.will(Update, once::run(move |mut transforms: Query<&mut Transform>| {
                let mut transform = transforms.get_mut(entity).unwrap();
                transform.translation.y += 20.0;
            })).await;
            
            // Delay between movements
            task.will(Update, delay::frames().with(5)).await;
        }
    }));
}
}

sequence

The sequence module provides mechanisms for sequentially combining actions. This is particularly useful for creating complex action flows by chaining multiple actions together.

Basic Usage

The sequence module provides two main ways to combine actions:

  1. The Then trait: Allows actions to be combined using the then method
  2. The sequence! macro: Provides an alternative syntax for combining actions

Using the then Method

Use the then() method to chain actions together:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Chain actions using the then method
        let result = task.will(Update, 
            once::run(|| {})
                .then(once::run(|| "Hello"))
                .then(once::run(|In(text): In<&str>| format!("{}, World!", text)))
        ).await;

        println!("{}", result); // Prints "Hello, World!"
    }));
}
}

Using the sequence! Macro

Use the sequence! macro to combine actions with a more declarative syntax:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;
use bevy_flurx::sequence;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Chain actions using the sequence! macro
        let result = task.will(Update, 
            sequence![
                once::run(|| {}),
                once::run(|| "Hello"),
                once::run(|In(text): In<&str>| format!("{}, World!", text)),
            ]
        ).await;

        println!("{}", result); // Prints "Hello, World!"
    }));
}
}

How It Works

When actions are combined using then or the sequence! macro:

  1. The actions are executed in sequence
  2. Each action starts as soon as the previous one completes
  3. If multiple actions complete in the same frame, they will all execute in that frame
  4. The output of the combined action will be that of the last action in the sequence

Practical Examples

Creating a Multi-step Process

The sequence module is particularly useful for creating multi-step processes:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Create a multi-step process
        task.will(Update, 
            once::run(|| println!("Step 1: Initializing..."))
                .then(once::run(|| println!("Step 2: Processing...")))
                .then(once::run(|| println!("Step 3: Finalizing...")))
                .then(once::run(|| println!("Process completed!")))
        ).await;
    }));
}
}

Combining Different Types of Actions

The sequence module can be used to combine different types of actions:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Combine different types of actions
        task.will(Update, 
            once::run(|| println!("Waiting for key press..."))
                .then(wait::input::key_pressed(KeyCode::Space))
                .then(once::run(|| println!("Key pressed! Waiting 2 seconds...")))
                .then(delay::seconds(2.0))
                .then(once::run(|| println!("Sequence completed!")))
        ).await;
    }));
}
}

pipe

The pipe module provides a mechanism to pipe actions together, where the output of one action is used as the input for another action. This is particularly useful for creating data processing pipelines.

Basic Usage

The pipe module provides the Pipe trait, which adds the pipe method to all actions. This method allows you to connect the output of one action to the input of another.

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Pipe the output of one action as the input to another
        let result = task.will(Update, 
            once::run(|| "Hello")
                .pipe(once::run(|In(text): In<&str>| format!("{}, World!", text)))
        ).await;

        println!("{}", result); // Prints "Hello, World!"
    }));
}
}

How It Works

When actions are combined using the pipe method:

  1. The first action is executed until completion
  2. The output of the first action is passed as input to the second action
  3. The second action is then executed with this input
  4. The output of the combined action will be that of the second action

This creates a data flow where information is processed in stages, with each stage building on the results of the previous one.

Practical Examples

Data Processing Pipeline

The pipe module is particularly useful for creating data processing pipelines:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Create a data processing pipeline
        let result = task.will(Update, 
            once::run(|| 5) // Generate a number
                .pipe(once::run(|In(num): In<i32>| num * 2)) // Double it
                .pipe(once::run(|In(num): In<i32>| format!("The result is: {}", num))) // Format it
        ).await;

        println!("{}", result); // Prints "The result is: 10"
    }));
}
}

Event Handling

The pipe module can be used to process events:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

#[derive(Component)]
struct Hp(u8);

#[derive(Event, Clone)]
struct PlayerHit(Entity);

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Wait for a PlayerHit event and then process it
        task.will(Update, 
            wait::event::read::<PlayerHit>()
                .pipe(once::run(|In(PlayerHit(entity)): In<PlayerHit>, mut players: Query<&mut Hp>| {
                    players.get_mut(entity).unwrap().0 -= 10;
                    println!("Player hit! HP reduced to {}", players.get(entity).unwrap().0);
                }))
        ).await;
    }));
}
}

Combining with Other Action Types

The pipe method can be combined with other action types for more complex behaviors:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Combine pipe with other action types
        task.will(Update, 
            wait::input::key_pressed(KeyCode::Space) // Wait for space key
                .pipe(once::run(|_| "Space pressed!")) // Process the event
                .then(delay::seconds(1.0)) // Wait for 1 second
                .then(once::run(|| println!("Ready for next input"))) // Print a message
        ).await;
    }));
}
}

through

The through module provides a mechanism to execute an action while preserving the output of the previous action. This is particularly useful for inserting actions like delays into a pipeline without affecting the data flow.

Basic Usage

The through module provides two main ways to use the functionality:

  1. The through function: Creates an action that executes a provided action but preserves the input value as the output
  2. The Through trait: Adds a convenient .through() method to actions, simplifying the chaining of actions

Using the through Function

Use the through() function to create an action that preserves its input:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;
use core::time::Duration;

#[derive(Event, Clone)]
struct Damage(usize);

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Use through to insert a delay without affecting the data flow
        task.will(Update, wait::event::read::<Damage>()
            .pipe(through(delay::time().with(Duration::from_millis(500))))
            .pipe(once::run(|In(Damage(damage)): In<Damage>| {
                println!("Player takes {damage} points of damage.");
            }))
        ).await;
    }));
}
}

Using the Through Trait

Use the .through() method for a more concise syntax:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;
use core::time::Duration;

#[derive(Event, Clone)]
struct Damage(usize);

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Use the through method for a more concise syntax
        task.will(Update, wait::event::read::<Damage>()
            .through(delay::time().with(Duration::from_millis(500)))
            .pipe(once::run(|In(Damage(damage)): In<Damage>| {
                println!("Player takes {damage} points of damage.");
            }))
        ).await;
    }));
}
}

How It Works

When using the through function or the Through trait:

  1. The original input value is stored
  2. The provided action is executed until completion
  3. The original input value is then forwarded as the output, regardless of the output of the executed action

This allows you to insert actions into a pipeline without affecting the data flow, which is particularly useful for actions like delays or logging.

Practical Examples

Adding Delays

The through module is particularly useful for adding delays without affecting the data flow:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;
use core::time::Duration;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Add a delay between steps without affecting the data
        let result = task.will(Update, 
            once::run(|| "Processing...")
                .through(delay::seconds(1.0)) // Wait for 1 second
                .pipe(once::run(|In(text): In<&str>| {
                    format!("{} Completed!", text)
                }))
        ).await;

        println!("{}", result); // Prints "Processing... Completed!"
    }));
}
}

Logging Without Affecting Data Flow

The through module can be used for logging without affecting the data flow:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Log data without affecting the pipeline
        let result = task.will(Update, 
            once::run(|| 42)
                .through(once::run(|In(num): In<i32>| {
                    println!("Processing number: {}", num);
                }))
                .pipe(once::run(|In(num): In<i32>| num * 2))
        ).await;

        println!("Result: {}", result); // Prints "Result: 84"
    }));
}
}

Combining with Other Action Types

The through method can be combined with other action types for more complex behaviors:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;
use core::time::Duration;

#[derive(Event, Clone)]
struct PlayerAction {
    action_type: String,
    value: i32,
}

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Create a complex action flow with through
        task.will(Update, 
            wait::event::read::<PlayerAction>()
                .through(once::run(|In(action): In<PlayerAction>| {
                    println!("Received action: {} with value {}", action.action_type, action.value);
                }))
                .through(delay::time().with(Duration::from_millis(500))) // Add a delay
                .pipe(once::run(|In(action): In<PlayerAction>| {
                    // Process the action
                    action.value * 2
                }))
        ).await;
    }));
}
}

omit

The omit module provides mechanisms to omit input and/or output types from an action. This is particularly useful for defining groups of actions by simplifying their type signatures.

Basic Usage

The omit module provides three main traits:

  1. Omit: Omits both input and output types from an action
  2. OmitOutput: Omits only the output type from an action
  3. OmitInput: Omits only the input type from an action

Omitting Both Input and Output

Use the omit() method to omit both input and output types from an action:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        task.will(Update, action()).await;
    }));
}

fn action() -> ActionSeed {
    once::run(|In(num): In<usize>| num)
        .with(1) // ActionSeed<usize, usize>
        .omit() // ActionSeed<(), ()>
}
}

Omitting Only Output

Use the omit_output() method to omit only the output type from an action:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Create an action that omits only the output type
        task.will(Update, once::run(|In(num): In<usize>| {
                format!("Number: {}", num)
            })
            .with(42)
            .omit_output()
            .pipe(once::run(|| {
                println!("Action completed!");
            }))
        ).await;
    }));
}
}

Omitting Only Input

Use the omit_input() method to omit only the input type from an action:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Create an action that omits only the input type
        let result: usize = task.will(Update, once::run(|In(num): In<usize>| num)
            .with(5)
            .omit_input()
        ).await;
        
        println!("Result: {}", result); // Prints "Result: 5"
    }));
}
}

Practical Examples

Creating Reusable Action Groups

The omit module is particularly useful for creating reusable action groups with simplified type signatures:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

// Define a reusable action that doesn't expose its internal input/output types
fn print_message() -> ActionSeed {
    once::run(|In(message): In<String>| {
        println!("{}", message);
    })
    .with("Hello, world!".to_string())
    .omit()
}

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Use the reusable action
        task.will(Update, print_message()).await;
    }));
}
}

Chaining Actions with Different Types

The omit traits can be used to chain actions with different input/output types:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Chain actions with different types
        task.will(Update, once::run(|In(num): In<usize>| num * 2)
            .with(3)
            .omit_output() // Discard the output
            .pipe(once::run(|| "Action completed!"))
        ).await;
    }));
}
}

map

The map module provides mechanisms to transform the output of an action using a mapping function. This is particularly useful for data transformation and type conversion between actions.

Basic Usage

The map module provides the Map trait, which adds two main methods to all actions:

  1. map: Transforms the output of an action by applying a function to it
  2. overwrite: Replaces the output of an action with a specified value

Using the map Method

Use the map() method to transform the output of an action:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Transform the output of an action
        let result = task.will(Update, 
            once::run(|| 5)
                .map(|num| num * 2)
        ).await;

        println!("{}", result); // Prints "10"
    }));
}
}

Using the overwrite Method

Use the overwrite() method to replace the output of an action with a specified value:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Replace the output of an action
        let result = task.will(Update, 
            once::run(|| "Original output")
                .overwrite("Replaced output")
        ).await;

        println!("{}", result); // Prints "Replaced output"
    }));
}
}

How It Works

When using the map or overwrite methods:

  1. The original action is executed until completion
  2. The output of the action is transformed using the provided function (for map) or replaced with the specified value (for overwrite)
  3. The transformed or replaced value becomes the output of the combined action

This allows for flexible data transformation and type conversion between actions.

Practical Examples

Type Conversion

The map module is particularly useful for converting between different types:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Convert a number to a string
        let result = task.will(Update, 
            once::run(|| 42)
                .map(|num| format!("The answer is: {}", num))
        ).await;

        println!("{}", result); // Prints "The answer is: 42"
    }));
}
}

Data Transformation in Pipelines

The map module can be combined with pipe to create powerful data transformation pipelines:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Create a data transformation pipeline
        let result = task.will(Update, 
            once::run(|| 5)
                .map(|num| num * 2) // Transform to 10
                .pipe(once::run(|In(num): In<i32>| {
                    let squared = num * num;
                    squared
                })) // Transform to 100
                .map(|num| format!("Result: {}", num)) // Transform to string
        ).await;

        println!("{}", result); // Prints "Result: 100"
    }));
}
}

Conditional Logic

The map method can be used to implement conditional logic:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Implement conditional logic
        let score = task.will(Update, once::run(|| 85)).await;
        
        let grade = task.will(Update, 
            once::run(|| score)
                .map(|score| {
                    if score >= 90 { "A" }
                    else if score >= 80 { "B" }
                    else if score >= 70 { "C" }
                    else if score >= 60 { "D" }
                    else { "F" }
                })
        ).await;

        println!("Score: {}, Grade: {}", score, grade); // Prints "Score: 85, Grade: B"
    }));
}
}

inspect

The inspect module provides mechanisms to clone and inspect input values via auxiliary actions without disrupting their primary flow. This is particularly useful for debugging, logging, or performing side-effects on input values.

Basic Usage

The inspect module provides two main ways to inspect input values:

  1. The inspect function: Creates an action that clones its input, passing one clone to a provided action for processing while forwarding the original input as output
  2. The Inspect trait: Adds a convenient .inspect() method to actions, simplifying the chaining of actions with auxiliary side-effects

Using the inspect Function

Use the inspect() function to create an action that processes a clone of the input:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

#[derive(Event, Clone)]
struct Damage(u8);

#[derive(Component)]
struct Hp(u8);

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Use inspect to log damage without affecting the main flow
        task.will(Update, wait::event::read::<Damage>()
            .pipe(inspect(once::run(|In(Damage(damage)): In<Damage>| {
                println!("Players take {damage} points of damage.");
            })))
            .pipe(once::run(|In(Damage(damage)): In<Damage>, mut players: Query<&mut Hp>| {
                for mut player in &mut players {            
                    player.0 -= damage;
                }
            }))
        ).await;
    }));
}
}

Using the Inspect Trait

Use the .inspect() method to achieve the same result with a more concise syntax:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

#[derive(Event, Clone)]
struct Damage(u8);

#[derive(Component)]
struct Hp(u8);

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Use the inspect method for a more concise syntax
        task.will(Update, wait::event::read::<Damage>()
            .inspect(once::run(|In(Damage(damage)): In<Damage>| {
                println!("Players take {damage} points of damage.");
            }))
            .pipe(once::run(|In(Damage(damage)): In<Damage>, mut players: Query<&mut Hp>| {
                for mut player in &mut players {            
                    player.0 -= damage;
                }
            }))
        ).await;
    }));
}
}

How It Works

When using the inspect function or the Inspect trait:

  1. The input value is cloned
  2. One clone is passed to the auxiliary action for processing
  3. The original input is forwarded as the output without modification
  4. Any side-effects in the auxiliary action (e.g., logging, external calls) are executed

This ensures that you can perform auxiliary operations (e.g., logging, metrics) while preserving the original input for further use.

Practical Examples

Debugging

The inspect module is particularly useful for debugging:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Debug the value at each step of a pipeline
        let result = task.will(Update, 
            once::run(|| 5)
                .inspect(once::run(|In(num): In<i32>| {
                    println!("Initial value: {}", num);
                }))
                .map(|num| num * 2)
                .inspect(once::run(|In(num): In<i32>| {
                    println!("After multiplication: {}", num);
                }))
                .pipe(once::run(|In(num): In<i32>| num + 3))
                .inspect(once::run(|In(num): In<i32>| {
                    println!("Final value: {}", num);
                }))
        ).await;

        println!("Result: {}", result); // Prints "Result: 13"
    }));
}
}

Metrics Collection

The inspect module can be used for collecting metrics:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

#[derive(Resource, Default)]
struct Metrics {
    damage_dealt: u32,
}

#[derive(Event, Clone)]
struct Damage(u8);

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Collect metrics while processing events
        task.will(Update, wait::event::read::<Damage>()
            .inspect(once::run(|In(Damage(damage)): In<Damage>, mut metrics: ResMut<Metrics>| {
                metrics.damage_dealt += damage as u32;
                println!("Total damage dealt: {}", metrics.damage_dealt);
            }))
            .pipe(once::run(|In(Damage(damage)): In<Damage>, mut players: Query<&mut Hp>| {
                for mut player in &mut players {            
                    player.0 -= damage;
                }
            }))
        ).await;
    }));
}
}

Conditional Side Effects

The inspect module can be used for conditional side effects:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

#[derive(Event, Clone)]
struct PlayerAction {
    action_type: ActionType,
    value: i32,
}

#[derive(Clone, PartialEq)]
enum ActionType {
    Attack,
    Heal,
    Move,
}

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Perform conditional side effects based on input values
        task.will(Update, wait::event::read::<PlayerAction>()
            .inspect(once::run(|In(action): In<PlayerAction>| {
                match action.action_type {
                    ActionType::Attack => println!("Player attacks for {} damage!", action.value),
                    ActionType::Heal => println!("Player heals for {} health!", action.value),
                    ActionType::Move => println!("Player moves {} units!", action.value),
                }
            }))
            .pipe(once::run(|In(action): In<PlayerAction>| {
                // Process the action...
                action.value
            }))
        ).await;
    }));
}
}

remake

The remake module provides a mechanism to create a new action based on an existing action's Runner and Output. This is particularly useful for transforming actions while preserving their input type but changing their output type.

Basic Usage

The remake module provides the Remake trait, which adds the remake method to all actions:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Create a new action with a different output type
        let result = task.will(Update, 
            once::run(|| 5)
                .remake(|runner, original_output, new_output| {
                    // Custom runner that uses the original runner but produces a different output
                    CustomRunner {
                        runner,
                        original_output,
                        new_output,
                    }
                })
        ).await;

        println!("{}", result);
    }));
}

// A custom runner that transforms the output
struct CustomRunner<O1, O2> {
    runner: BoxedRunner,
    original_output: Output<O1>,
    new_output: Output<O2>,
}

impl<O1, O2> Runner for CustomRunner<O1, O2>
where
    O1: 'static,
    O2: 'static,
{
    fn run(&mut self, world: &mut World, token: &mut CancellationHandlers) -> RunnerIs {
        // Run the original runner
        let result = self.runner.run(world, token);
        
        // If the original runner completed, transform its output
        if result == RunnerIs::Completed {
            if let Some(value) = self.original_output.take() {
                // Transform the output (example: convert a number to a string)
                let transformed = format!("Value: {}", value);
                self.new_output.set(transformed);
            }
        }
        
        result
    }
}
}

How It Works

When using the remake method:

  1. The original action's Runner and Output are captured
  2. A new Runner is created using the provided function, which receives:
    • The original Runner
    • The original Output
    • A new Output for the transformed action
  3. The new Runner can use the original runner's behavior while producing a different output type

This allows for advanced customization of action behavior while maintaining type safety.

Practical Examples

Type Conversion

The remake module can be used for complex type conversions that require access to the original runner:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Convert a number action to a string action with custom processing
        let result = task.will(Update, 
            once::run(|| 42)
                .remake(|runner, original_output, new_output| {
                    TypeConverter {
                        runner,
                        original_output,
                        new_output,
                    }
                })
        ).await;

        println!("{}", result); // Prints a string representation of 42
    }));
}

struct TypeConverter<O1, O2> {
    runner: BoxedRunner,
    original_output: Output<O1>,
    new_output: Output<O2>,
}

impl<O1, O2> Runner for TypeConverter<O1, O2>
where
    O1: std::fmt::Display + 'static,
    O2: From<String> + 'static,
{
    fn run(&mut self, world: &mut World, token: &mut CancellationHandlers) -> RunnerIs {
        let result = self.runner.run(world, token);
        
        if result == RunnerIs::Completed {
            if let Some(value) = self.original_output.take() {
                let string_value = format!("{}", value);
                self.new_output.set(O2::from(string_value));
            }
        }
        
        result
    }
}
}

Advanced Action Composition

The remake module can be used to create complex action compositions:

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Create a composite action that logs its execution time
        let result = task.will(Update, 
            once::run(|| "Processing data...")
                .remake(|runner, original_output, new_output| {
                    TimedRunner {
                        runner,
                        original_output,
                        new_output,
                        start_time: None,
                    }
                })
        ).await;

        println!("{}", result); // Prints the result with timing information
    }));
}

struct TimedRunner<O> {
    runner: BoxedRunner,
    original_output: Output<O>,
    new_output: Output<String>,
    start_time: Option<std::time::Instant>,
}

impl<O> Runner for TimedRunner<O>
where
    O: std::fmt::Display + 'static,
{
    fn run(&mut self, world: &mut World, token: &mut CancellationHandlers) -> RunnerIs {
        if self.start_time.is_none() {
            self.start_time = Some(std::time::Instant::now());
        }
        
        let result = self.runner.run(world, token);
        
        if result == RunnerIs::Completed {
            if let Some(value) = self.original_output.take() {
                let elapsed = self.start_time.unwrap().elapsed();
                let timed_result = format!("Result: {} (took {:?})", value, elapsed);
                self.new_output.set(timed_result);
            }
        }
        
        result
    }
}
}

When to Use

Use the remake module when you need to:

  • Transform an action's output type in ways that can't be achieved with map
  • Access and modify the behavior of an action's runner
  • Create custom runners that build upon existing actions
  • Implement advanced action composition patterns

The remake module is particularly useful for library authors and advanced users who need fine-grained control over action behavior and transformation.

switch

The switch module provides a mechanism for coordinating between Reactors and regular Bevy systems through a binary state (on/off).

Core Concepts

Switch Resource

The Switch<M> resource represents a binary state (on/off) that can be used to coordinate between different parts of your application. The generic type parameter M allows you to define different types of switches for different purposes.

#![allow(unused)]
fn main() {
// Define a marker type for our switch
struct PlayerAnimation;

// Access the switch in a system
fn check_animation_state(switch: Res<Switch<PlayerAnimation>>) {
    if switch.is_on() {
        println!("Player animation is running!");
    } else {
        println!("Player animation is stopped!");
    }
}
}

Condition Systems

The switch module provides several condition systems that can be used with Bevy's run_if functionality to conditionally run systems based on switch states:

  • switch_is_on<M>() - Returns true if the switch is on
  • switch_is_off<M>() - Returns true if the switch is off
  • switch_just_turned_on<M>() - Returns true only the first time the switch is detected as on
  • switch_just_turned_off<M>() - Returns true only the first time the switch is detected as off
use bevy::prelude::*;
use bevy_flurx::prelude::*;

struct HeavyTask;

fn main() {
    App::new()
        // This system only runs when the HeavyTask switch is on
        .add_systems(Update, (|mut switch: ResMut<Switch<HeavyTask>>| {
            // Do heavy work...

            // Turn off the switch when done
            switch.off();
        }).run_if(switch_is_on::<HeavyTask>))
        
        // This system only runs when the HeavyTask switch just turned off
        .add_systems(Update, (|| {
            println!("Heavy task just completed!");
        }).run_if(switch_just_turned_off::<HeavyTask>));
}

Available Actions

The switch module provides actions in both the once and wait modules:

once::switch

wait::switch

Examples

Coordinating Between Reactors and Systems

use bevy::prelude::*;
use bevy_flurx::prelude::*;

struct LoadingTask;

fn main() {
    App::new()
        // This system performs a heavy loading task when the switch is on
        .add_systems(Update, (|mut switch: ResMut<Switch<LoadingTask>>| {
            // Simulate loading...
            println!("Loading assets...");
            
            // Turn off the switch when done
            switch.off();
        }).run_if(switch_is_on::<LoadingTask>))
        
        // Spawn a reactor that coordinates the loading sequence
        .add_systems(Startup, |mut commands: Commands| {
            commands.spawn(Reactor::schedule(|task| async move {
                println!("Starting loading sequence...");
                
                // Turn on the loading switch to start the loading task
                task.will(Update, once::switch::on::<LoadingTask>()).await;
                
                // Wait until the loading task is complete (switch is turned off)
                task.will(Update, wait::switch::off::<LoadingTask>()).await;
                
                println!("Loading sequence complete!");
            }));
        });
}

Creating a State Machine

use bevy::prelude::*;
use bevy_flurx::prelude::*;

// Define switch types for different states
struct Idle;
struct Walking;
struct Running;

fn main() {
    App::new()
        // Idle animation system
        .add_systems(Update, (|mut idle_anim: ResMut<IdleAnimation>| {
            idle_anim.play();
        }).run_if(switch_is_on::<Idle>))
        
        // Walking animation system
        .add_systems(Update, (|mut walk_anim: ResMut<WalkAnimation>| {
            walk_anim.play();
        }).run_if(switch_is_on::<Walking>))
        
        // Running animation system
        .add_systems(Update, (|mut run_anim: ResMut<RunAnimation>| {
            run_anim.play();
        }).run_if(switch_is_on::<Running>))
        
        // State transition system
        .add_systems(Update, |
            keys: Res<Input<KeyCode>>,
            mut commands: Commands,
        | {
            if keys.just_pressed(KeyCode::W) {
                commands.spawn(Reactor::schedule(|task| async move {
                    // Turn off all states
                    task.will(Update, once::switch::off::<Idle>()).await;
                    task.will(Update, once::switch::off::<Running>()).await;
                    
                    // Turn on walking state
                    task.will(Update, once::switch::on::<Walking>()).await;
                }));
            }
            
            if keys.just_pressed(KeyCode::ShiftLeft) {
                commands.spawn(Reactor::schedule(|task| async move {
                    // Turn off all states
                    task.will(Update, once::switch::off::<Idle>()).await;
                    task.will(Update, once::switch::off::<Walking>()).await;
                    
                    // Turn on running state
                    task.will(Update, once::switch::on::<Running>()).await;
                }));
            }
            
            if keys.just_released(KeyCode::W) && keys.just_released(KeyCode::ShiftLeft) {
                commands.spawn(Reactor::schedule(|task| async move {
                    // Turn off all states
                    task.will(Update, once::switch::off::<Walking>()).await;
                    task.will(Update, once::switch::off::<Running>()).await;
                    
                    // Turn on idle state
                    task.will(Update, once::switch::on::<Idle>()).await;
                }));
            }
        });
}

When to Use

Use the switch module when you need to:

  • Coordinate between Reactors and regular Bevy systems
  • Control when certain systems should run
  • Signal the completion of asynchronous tasks
  • Create state machines with clear on/off states
  • Implement gameplay sequences that depend on switch states

Switches are particularly useful for tasks that need to be performed on the main thread but need to be coordinated with asynchronous Reactor tasks.

Record Actions

The record module provides actions for managing operation history with undo and redo capabilities. These actions allow you to track operations, roll them back (undo), and reapply them (redo).

Available Record Actions

  • push - Push an operation onto the record history
  • undo - Undo operations from the record history
  • redo - Redo previously undone operations
  • extension - Extensions for using record actions with events and triggers
  • all_clear - Clear all history of undo and redo operations

Basic Usage

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

// Define an operation type
struct MoveAct;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Push an operation onto the record history
        task.will(Update, record::push()
            .with(Track {
                act: MoveAct,
                rollback: Rollback::undo_redo(|| once::run(|mut player: Query<&mut Transform>| {
                    let pos = player.single_mut().unwrap().translation;
                    player.single_mut().unwrap().translation = Vec3::Z;
                    RedoAction::new(once::run(move |mut player: Query<&mut Transform>| {
                        player.single_mut().unwrap().translation = pos;
                    }))
                }))
            })
        ).await.expect("Failed to push operation");

        // Undo the operation
        task.will(Update, record::undo::once::<MoveAct>())
            .await.expect("Failed to undo operation");

        // Redo the operation
        task.will(Update, record::redo::once::<MoveAct>())
            .await.expect("Failed to redo operation");
    }));
}
}

Core Concepts

Track

The Track struct represents an operation to be recorded, containing:

  • act: The actual operation being recorded
  • rollback: The process called when a rollback is requested

Rollback

The Rollback struct holds the function called when an undo operation is requested. It can be created in several ways:

  • new(): Creates a rollback with a function that may optionally create a redo action
  • undo(): Creates a rollback that doesn't create a redo action
  • undo_redo(): Creates a rollback that always creates a redo action
  • parts(): Declares undo and redo separately with Undo and Redo types

RedoAction

The RedoAction struct represents an action executed when a redo operation is called.

When to Use

Use record actions when you need to:

  • Implement undo/redo functionality in your application
  • Track user operations for later reversal
  • Create a history of operations that can be navigated

The record module is particularly useful for applications like editors, games with rewind mechanics, or any application where users might want to undo their actions.

record::push

The record::push function allows you to add operations to the record history for later undo and redo operations.

Function Signature

#![allow(unused)]
fn main() {
record::push<Act>() -> ActionSeed<Track<Act>, EditRecordResult>
}

Creates an action that pushes a Track onto the Record. The output will be an EditRecordResult, which will be an error (UndoRedoInProgress) if an undo or redo operation is in progress.

Parameters

  • Act: The type of operation being recorded. This is a generic type parameter that allows you to define different types of operations.

Return Value

Returns an ActionSeed<Track<Act>, EditRecordResult> that, when executed, will push the provided Track onto the Record.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

// Define an operation type
struct MoveAct;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Push an operation onto the record history
        task.will(Update, record::push()
            .with(Track {
                act: MoveAct,
                rollback: Rollback::undo_redo(|| once::run(|mut player: Query<&mut Transform>| {
                    let pos = player.single_mut().unwrap().translation;
                    player.single_mut().unwrap().translation = Vec3::Z;
                    RedoAction::new(once::run(move |mut player: Query<&mut Transform>| {
                        player.single_mut().unwrap().translation = pos;
                    }))
                }))
            })
        ).await.expect("An error will be returned if undo or redo is operating.");
    }));
}
}

Creating a Track

The Track struct is used to define an operation and its rollback behavior:

#![allow(unused)]
fn main() {
Track {
    act: YourActType,
    rollback: Rollback::undo_redo(|| /* your undo action */)
}
}

Track Fields

  • act: The operation being recorded. This can be any type that implements Send + Sync + 'static.
  • rollback: The process called when a rollback is requested. This is created using one of the Rollback methods.

Rollback Methods

  • Rollback::new(): Creates a rollback with a function that may optionally create a redo action.
  • Rollback::undo(): Creates a rollback that doesn't create a redo action.
  • Rollback::undo_redo(): Creates a rollback that always creates a redo action.
  • Rollback::parts(): Declares undo and redo separately with Undo and Redo types.

Error Handling

The push function returns an EditRecordResult, which is a Result<(), UndoRedoInProgress>. If an undo or redo operation is in progress, the function will return Err(UndoRedoInProgress).

When to Use

Use record::push when you need to:

  • Add an operation to the record history
  • Define how an operation can be undone and redone
  • Track user actions for later reversal

This function is the foundation of the record system, as it allows you to define operations and their rollback behavior.

record::undo

The record::undo module provides actions for undoing operations that have been pushed onto the record history.

Functions

once

#![allow(unused)]
fn main() {
record::undo::once<Act>() -> ActionSeed<(), EditRecordResult>
}

Pops the last pushed undo action, and then executes it. The output will be an EditRecordResult, which will be an error (UndoRedoInProgress) if an undo or redo operation is in progress.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

struct Act;

Reactor::schedule(|task| async move {
    task.will(Update, record::push().with(Track {
        act: Act,
        rollback: Rollback::undo(|| once::run(||{}))
    }))
        .await
        .expect("An error will be returned if undo or redo is operating.");
    
    task.will(Update, record::undo::once::<Act>())
        .await
        .expect("An error will be returned if undo or redo is operating.");
});
}

index_to

#![allow(unused)]
fn main() {
record::undo::index_to<Act>() -> ActionSeed<usize, EditRecordResult>
}

Pops undo actions up to the specified index. The output will be an EditRecordResult, which will be an error (UndoRedoInProgress) if an undo or redo operation is in progress.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

struct Act;

Reactor::schedule(|task| async move {
    // Push multiple operations
    task.will(Update, record::push().with(Track {
        act: Act,
        rollback: Rollback::undo(|| once::run(||{}))
    })).await.unwrap();
    
    task.will(Update, record::push().with(Track {
        act: Act,
        rollback: Rollback::undo(|| once::run(||{}))
    })).await.unwrap();
    
    task.will(Update, record::push().with(Track {
        act: Act,
        rollback: Rollback::undo(|| once::run(||{}))
    })).await.unwrap();
    
    // Undo operations up to index 1 (keeping the first operation)
    task.will(Update, record::undo::index_to::<Act>().with(1))
        .await
        .expect("An error will be returned if undo or redo is operating.");
});
}

to

#![allow(unused)]
fn main() {
record::undo::to<Act>() -> ActionSeed<Act, EditRecordResult>
}

Pops undo actions until the specified operation is reached. The output will be an EditRecordResult, which will be an error (UndoRedoInProgress) if an undo or redo operation is in progress.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

#[derive(PartialEq)]
enum Act {
    Move,
    Rotate,
    Scale,
}

Reactor::schedule(|task| async move {
    // Push multiple operations
    task.will(Update, record::push().with(Track {
        act: Act::Move,
        rollback: Rollback::undo(|| once::run(||{}))
    })).await.unwrap();
    
    task.will(Update, record::push().with(Track {
        act: Act::Rotate,
        rollback: Rollback::undo(|| once::run(||{}))
    })).await.unwrap();
    
    task.will(Update, record::push().with(Track {
        act: Act::Scale,
        rollback: Rollback::undo(|| once::run(||{}))
    })).await.unwrap();
    
    // Undo operations until Act::Move is reached
    task.will(Update, record::undo::to().with(Act::Move))
        .await
        .expect("An error will be returned if undo or redo is operating.");
});
}

all

#![allow(unused)]
fn main() {
record::undo::all<Act>() -> ActionSeed<(), EditRecordResult>
}

Pops all the undo actions from the Record. The output will be an EditRecordResult, which will be an error (UndoRedoInProgress) if an undo or redo operation is in progress.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

struct Act;

Reactor::schedule(|task| async move {
    // Push multiple operations
    task.will(Update, record::push().with(Track {
        act: Act,
        rollback: Rollback::undo(|| once::run(||{}))
    })).await.unwrap();
    
    task.will(Update, record::push().with(Track {
        act: Act,
        rollback: Rollback::undo(|| once::run(||{}))
    })).await.unwrap();
    
    // Undo all operations
    task.will(Update, record::undo::all::<Act>())
        .await
        .expect("An error will be returned if undo or redo is operating.");
});
}

Error Handling

All undo functions return an EditRecordResult, which is a Result<(), UndoRedoInProgress>. If an undo or redo operation is already in progress, the function will return Err(UndoRedoInProgress).

When to Use

Use record::undo actions when you need to:

  • Undo the last operation
  • Undo operations up to a specific point
  • Undo all operations

These actions are particularly useful for implementing undo functionality in applications like editors, games with rewind mechanics, or any application where users might want to undo their actions.

record::redo

The record::redo module provides actions for redoing operations that have been previously undone.

Functions

once

#![allow(unused)]
fn main() {
record::redo::once<Act>() -> ActionSeed<(), EditRecordResult>
}

Pops the last pushed redo action and executes it. After the redo action is executed, the undo action that created it is pushed into the Record again. If the redo stack in the Record is empty, nothing happens.

The output will be an EditRecordResult, which will be an error (UndoRedoInProgress) if an undo or redo operation is in progress.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

struct Act;

Reactor::schedule(|task| async move {
    // Push an operation
    task.will(Update, record::push().with(
        Track {
            act: Act,
            rollback: Rollback::undo(|| once::run(||{})),
        }))
        .await
        .expect("An error will be returned if undo or redo is operating.");
    
    // Undo the operation
    task.will(Update, record::undo::once::<Act>())
        .await
        .expect("An error will be returned if undo or redo is operating.");
    
    // Redo the operation
    task.will(Update, record::redo::once::<Act>())
        .await
        .expect("An error will be returned if undo or redo is operating.");
});
}

index_to

#![allow(unused)]
fn main() {
record::redo::index_to<Act>() -> ActionSeed<usize, EditRecordResult>
}

Pops and executes the redo actions up to the specified index. If the redo stack in the Record is empty, nothing happens.

The output will be an EditRecordResult, which will be an error (UndoRedoInProgress) if an undo or redo operation is in progress.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

struct Act;

Reactor::schedule(|task| async move {
    // Push multiple operations
    task.will(Update, record::push().with(Track {
        act: Act,
        rollback: Rollback::undo(|| once::run(||{}))
    })).await.unwrap();
    
    task.will(Update, record::push().with(Track {
        act: Act,
        rollback: Rollback::undo(|| once::run(||{}))
    })).await.unwrap();
    
    task.will(Update, record::push().with(Track {
        act: Act,
        rollback: Rollback::undo(|| once::run(||{}))
    })).await.unwrap();
    
    // Undo all operations
    task.will(Update, record::undo::all::<Act>())
        .await
        .expect("An error will be returned if undo or redo is operating.");
    
    // Redo operations up to index 1 (redoing the first two operations)
    task.will(Update, record::redo::index_to::<Act>().with(1))
        .await
        .expect("An error will be returned if undo or redo is operating.");
});
}

to

#![allow(unused)]
fn main() {
record::redo::to<Act>() -> ActionSeed<Act, EditRecordResult>
}

Pops and executes the redo actions until the specified operation is reached. If the redo stack in the Record is empty, nothing happens.

The output will be an EditRecordResult, which will be an error (UndoRedoInProgress) if an undo or redo operation is in progress.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

#[derive(PartialEq)]
enum Act {
    Move,
    Rotate,
    Scale,
}

Reactor::schedule(|task| async move {
    // Push multiple operations
    task.will(Update, record::push().with(Track {
        act: Act::Move,
        rollback: Rollback::undo(|| once::run(||{}))
    })).await.unwrap();
    
    task.will(Update, record::push().with(Track {
        act: Act::Rotate,
        rollback: Rollback::undo(|| once::run(||{}))
    })).await.unwrap();
    
    task.will(Update, record::push().with(Track {
        act: Act::Scale,
        rollback: Rollback::undo(|| once::run(||{}))
    })).await.unwrap();
    
    // Undo all operations
    task.will(Update, record::undo::all::<Act>())
        .await
        .expect("An error will be returned if undo or redo is operating.");
    
    // Redo operations until Act::Rotate is reached
    task.will(Update, record::redo::to().with(Act::Rotate))
        .await
        .expect("An error will be returned if undo or redo is operating.");
});
}

all

#![allow(unused)]
fn main() {
record::redo::all<Act>() -> ActionSeed<(), EditRecordResult>
}

Pops and executes all the redo actions from the Record. If the redo stack in the Record is empty, nothing happens.

The output will be an EditRecordResult, which will be an error (UndoRedoInProgress) if an undo or redo operation is in progress.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

struct Act;

Reactor::schedule(|task| async move {
    // Push multiple operations
    task.will(Update, record::push().with(Track {
        act: Act,
        rollback: Rollback::undo(|| once::run(||{}))
    })).await.unwrap();
    
    task.will(Update, record::push().with(Track {
        act: Act,
        rollback: Rollback::undo(|| once::run(||{}))
    })).await.unwrap();
    
    // Undo all operations
    task.will(Update, record::undo::all::<Act>())
        .await
        .expect("An error will be returned if undo or redo is operating.");
    
    // Redo all operations
    task.will(Update, record::redo::all::<Act>())
        .await
        .expect("An error will be returned if undo or redo is operating.");
});
}

Error Handling

All redo functions return an EditRecordResult, which is a Result<(), UndoRedoInProgress>. If an undo or redo operation is already in progress, the function will return Err(UndoRedoInProgress).

When to Use

Use record::redo actions when you need to:

  • Redo the last undone operation
  • Redo operations up to a specific point
  • Redo all undone operations

These actions are particularly useful for implementing redo functionality in applications like editors, games with rewind mechanics, or any application where users might want to redo their previously undone actions.

record::all_clear

The record::all_clear function clears all history of undo and redo operations from the Record.

Function Signature

#![allow(unused)]
fn main() {
record::all_clear<M: 'static>() -> ActionSeed<(), Result<(), UndoRedoInProgress>>
}

Creates an action that clears all history of undo and redo operations from the Record. The output will be an error (UndoRedoInProgress) if an undo or redo operation is in progress.

Parameters

  • M: The type of operation being recorded. This is a generic type parameter that allows you to define different types of operations.

Return Value

Returns an ActionSeed<(), Result<(), UndoRedoInProgress>> that, when executed, will clear all history of undo and redo operations from the Record.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

struct MyOperation;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Push some operations
        task.will(Update, record::push().with(Track {
            act: MyOperation,
            rollback: Rollback::undo(|| once::run(||{}))
        })).await.unwrap();
        
        task.will(Update, record::push().with(Track {
            act: MyOperation,
            rollback: Rollback::undo(|| once::run(||{}))
        })).await.unwrap();
        
        // Clear all history
        task.will(Update, record::all_clear::<MyOperation>())
            .await
            .expect("An error will be returned if undo or redo is operating.");
    }));
}
}

Error Handling

The all_clear function returns a Result<(), UndoRedoInProgress>. If an undo or redo operation is in progress, the function will return Err(UndoRedoInProgress).

When to Use

Use record::all_clear when you need to:

  • Reset the history of operations
  • Clear all undo and redo stacks
  • Start fresh with a new history

This function is particularly useful when:

  • Starting a new document or project
  • After a major operation that makes previous history irrelevant
  • When you want to free up memory used by a large history

record::extension

The record::extension module provides functionality for making undo and redo requests from outside the Reactor using events and triggers.

Types

RequestUndo

#![allow(unused)]
fn main() {
enum RequestUndo<Act> {
    Once,
    IndexTo(usize),
    To(Act),
    All,
}
}

Represents a request to undo operations. If an undo or redo is already in progress, the request will be ignored.

  • Once: Corresponds to record::undo::once
  • IndexTo(usize): Corresponds to record::undo::index_to
  • To(Act): Corresponds to record::undo::to
  • All: Corresponds to record::undo::all

RequestRedo

#![allow(unused)]
fn main() {
enum RequestRedo<Act> {
    Once,
    IndexTo(usize),
    To(Act),
    All,
}
}

Represents a request to redo operations. If an undo or redo is already in progress, the request will be ignored.

  • Once: Corresponds to record::redo::once
  • IndexTo(usize): Corresponds to record::redo::index_to
  • To(Act): Corresponds to record::redo::to
  • All: Corresponds to record::redo::all

Traits

RecordExtension

#![allow(unused)]
fn main() {
trait RecordExtension {
    fn add_record<Act>(&mut self) -> &mut Self
    where
        Act: Clone + PartialEq + Send + Sync + 'static;
}
}

Allows undo and redo requests to be made using RequestUndo and RequestRedo from outside the Reactor.

Methods

  • add_record<Act>: Sets up RequestUndo and RequestRedo and their associated systems.

Examples

Using Events

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

#[derive(Resource, Default)]
struct UndoRedoState {
    can_undo: bool,
    can_redo: bool,
}

fn setup_app(app: &mut App) {
    // Add record functionality for your operation type
    app.add_record::<MyOperation>();
}

fn update_ui_system(
    mut ui_state: ResMut<UndoRedoState>,
    record: Res<Record<MyOperation>>,
) {
    ui_state.can_undo = !record.tracks.is_empty();
    ui_state.can_redo = !record.redo.is_empty();
}

fn handle_input(
    keys: Res<Input<KeyCode>>,
    mut undo_events: EventWriter<RequestUndo<MyOperation>>,
    mut redo_events: EventWriter<RequestRedo<MyOperation>>,
) {
    // Handle Ctrl+Z for undo
    if keys.pressed(KeyCode::ControlLeft) && keys.just_pressed(KeyCode::Z) {
        undo_events.send(RequestUndo::Once);
    }
    
    // Handle Ctrl+Y for redo
    if keys.pressed(KeyCode::ControlLeft) && keys.just_pressed(KeyCode::Y) {
        redo_events.send(RequestRedo::Once);
    }
}
}

Using Triggers

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn handle_button_click(
    interaction_query: Query<&Interaction, (Changed<Interaction>, With<UndoButton>)>,
    world: &mut World,
) {
    for interaction in interaction_query.iter() {
        if *interaction == Interaction::Clicked {
            // Trigger an undo operation
            world.trigger(RequestUndo::<MyOperation>::Once);
        }
    }
}
}

How It Works

When you call app.add_record::<Act>(), the following happens:

  1. The Record<Act> resource is initialized
  2. Event types for RequestUndo<Act> and RequestRedo<Act> are registered
  3. Systems are added to handle these events and triggers

When a RequestUndo or RequestRedo event is sent or triggered:

  1. The corresponding system creates a new Reactor
  2. The Reactor executes the appropriate undo or redo action
  3. If the action fails (e.g., because an undo or redo is already in progress), the error is ignored

When to Use

Use the extension module when you need to:

  • Trigger undo/redo operations from UI elements
  • Handle keyboard shortcuts for undo/redo
  • Integrate undo/redo functionality with other systems in your application

This module is particularly useful for applications with complex UI that need to provide undo/redo functionality through various means (buttons, keyboard shortcuts, etc.).

Side Effect Actions

The side_effect module provides actions for handling operations with side effects such as asynchronous runtime or threads. These actions allow you to execute code outside the main Bevy ECS system while maintaining the Action-based flow.

Available Side Effect Actions

  • bevy_task - Spawn tasks using Bevy's task system
  • thread - Spawn OS threads
  • tokio - Spawn tasks using Tokio's runtime

Basic Usage

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Execute a CPU-intensive operation on a separate thread
        let result = task.will(Update, 
            once::run(|| 2)
                .pipe(side_effect::thread::spawn(|num| {
                    // This runs on a separate thread
                    num * 10
                }))
        ).await;
        
        println!("Result: {}", result); // Prints "Result: 20"
        
        // Execute an asynchronous operation using Tokio
        task.will(Update, 
            side_effect::tokio::spawn(async move {
                // This runs on Tokio's runtime
                tokio::time::sleep(std::time::Duration::from_secs(1)).await;
                "Operation completed"
            })
        ).await;
    }));
}
}

Core Concepts

AsyncFunctor

The AsyncFunctor trait is implemented for functions that return futures. It allows you to:

  • Pass a function that takes input and returns a future: spawn(|input| async move { ... })
  • Pass a future directly: spawn(async move { ... })

Functor

The Functor trait is used for functions that need to be executed with side effects. It allows you to:

  • Pass a function that takes input: spawn(|input| { ... })
  • Pass a function without input: spawn(|| { ... })

When to Use

Use side_effect actions when you need to:

  • Execute CPU-intensive operations without blocking the main thread
  • Perform asynchronous operations like network requests or file I/O
  • Integrate with external asynchronous APIs
  • Execute code that would otherwise block or slow down the main Bevy ECS system

The side_effect module is particularly useful for applications that need to perform operations outside the main game loop, such as loading assets, making network requests, or performing complex calculations.

side_effect::bevy_task

The side_effect::bevy_task module provides actions for spawning tasks using Bevy's task system. These actions allow you to execute asynchronous code while maintaining the Action-based flow.

Functions

spawn

#![allow(unused)]
fn main() {
side_effect::bevy_task::spawn<I, Out, Functor, M>(f: Functor) -> ActionSeed<I, Out>
}

Spawns a future onto the Bevy thread pool and waits until it's completed. The task is started when the Runner is executed for the first time.

Parameters

  • f: A function that returns a future, or a future itself. This can be either:
    • A function that takes input and returns a future: |input| async move { ... }
    • A future directly: async move { ... }

Return Value

Returns an ActionSeed<I, Out> that, when executed, will spawn the future onto the Bevy thread pool and wait for it to complete.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Spawn a future directly
        task.will(Update, side_effect::bevy_task::spawn(async move {
            // This runs on the Bevy thread pool
            "Hello from Bevy task!"
        })).await;
        
        // Spawn a function that takes input and returns a future
        let result = task.will(Update, 
            once::run(|| 5)
                .pipe(side_effect::bevy_task::spawn(|num| async move {
                    // This runs on the Bevy thread pool
                    num * 2
                }))
        ).await;
        
        println!("Result: {}", result); // Prints "Result: 10"
    }));
}
}

spawn_detached

#![allow(unused)]
fn main() {
side_effect::bevy_task::spawn_detached<I, Out, Functor, M>(functor: Functor) -> ActionSeed<I, Out>
}

Spawns a future onto the Bevy thread pool and waits until it's completed. Unlike spawn, the spawned task is detached and continues to run in the background, even if the Reactor is canceled.

Parameters

  • functor: A function that returns a future, or a future itself. This can be either:
    • A function that takes input and returns a future: |input| async move { ... }
    • A future directly: async move { ... }

Return Value

Returns an ActionSeed<I, Out> that, when executed, will spawn the future onto the Bevy thread pool as a detached task and wait for it to complete.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Spawn a detached future
        task.will(Update, side_effect::bevy_task::spawn_detached(async move {
            // This runs on the Bevy thread pool and continues even if the Reactor is canceled
            "Hello from detached Bevy task!"
        })).await;
        
        // Spawn a function that takes input and returns a future
        let result = task.will(Update, 
            once::run(|| 5)
                .pipe(side_effect::bevy_task::spawn_detached(|num| async move {
                    // This runs on the Bevy thread pool and continues even if the Reactor is canceled
                    num * 2
                }))
        ).await;
        
        println!("Result: {}", result); // Prints "Result: 10"
    }));
}
}

When to Use

Use side_effect::bevy_task actions when you need to:

  • Execute asynchronous code that would block the main thread
  • Perform operations that can benefit from Bevy's task system
  • Execute code that should continue even if the Reactor is canceled (using spawn_detached)

The bevy_task module is particularly useful for operations that need to be executed asynchronously but don't require the full power of Tokio's runtime.

side_effect::thread

The side_effect::thread module provides actions for spawning OS threads. These actions allow you to execute CPU-intensive code without blocking the main thread while maintaining the Action-based flow.

Functions

spawn

#![allow(unused)]
fn main() {
side_effect::thread::spawn<I, O, M>(f: impl Functor<I, O, M> + Send + Sync + 'static) -> ActionSeed<I, O>
}

Spawns a new OS thread and waits for its output. The thread is started when the Runner is executed for the first time. Note that the thread created from this function will continue to run even if the Reactor is canceled.

Parameters

  • f: A function to be executed on a separate thread. This can be either:
    • A function that takes input: |input| { ... }
    • A function without input: || { ... }

Return Value

Returns an ActionSeed<I, O> that, when executed, will spawn a new OS thread to execute the function and wait for it to complete.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Execute a function with input on a separate thread
        let result = task.will(Update, 
            once::run(|| 5)
                .pipe(side_effect::thread::spawn(|num| {
                    // This runs on a separate OS thread
                    num * 2
                }))
        ).await;
        
        println!("Result: {}", result); // Prints "Result: 10"
        
        // Execute a function without input on a separate thread
        let result = task.will(Update, 
            side_effect::thread::spawn(|| {
                // This runs on a separate OS thread
                "Hello from thread!"
            })
        ).await;
        
        println!("Result: {}", result); // Prints "Result: Hello from thread!"
    }));
}
}

When to Use

Use side_effect::thread actions when you need to:

  • Execute CPU-intensive operations without blocking the main thread
  • Perform operations that would otherwise slow down the main game loop
  • Execute code that should continue even if the Reactor is canceled

The thread module is particularly useful for operations that are CPU-bound rather than I/O-bound. For I/O-bound operations, consider using the tokio module instead.

Feature Requirements

The thread module requires both the side-effect and std feature flags to be enabled. It is not available on WebAssembly targets.

[dependencies]
bevy_flurx = { version = "0.1", features = ["side-effect", "std"] }

side_effect::tokio

The side_effect::tokio module provides actions for spawning tasks using Tokio's runtime. These actions allow you to execute asynchronous code with Tokio while maintaining the Action-based flow.

Functions

spawn

#![allow(unused)]
fn main() {
side_effect::tokio::spawn<I, Out, Functor, M>(f: Functor) -> ActionSeed<I, Out>
}

Spawns a new Tokio task and waits for its output. The task is started when the Runner is executed for the first time.

Parameters

  • f: A function that returns a future, or a future itself. This can be either:
    • A function that takes input and returns a future: |input| async move { ... }
    • A future directly: async move { ... }

Return Value

Returns an ActionSeed<I, Out> that, when executed, will spawn a new Tokio task to execute the future and wait for it to complete.

Example

#![allow(unused)]
fn main() {
use bevy::prelude::*;
use bevy_flurx::prelude::*;

fn spawn_reactor(mut commands: Commands) {
    commands.spawn(Reactor::schedule(|task| async move {
        // Execute an asynchronous operation using Tokio
        let result = task.will(Update, 
            side_effect::tokio::spawn(async move {
                // This runs on Tokio's runtime
                tokio::time::sleep(std::time::Duration::from_secs(1)).await;
                "Operation completed"
            })
        ).await;
        
        println!("Result: {}", result); // Prints "Result: Operation completed"
        
        // Execute a function that takes input and returns a future
        let result = task.will(Update, 
            once::run(|| 5)
                .pipe(side_effect::tokio::spawn(|num| async move {
                    // This runs on Tokio's runtime
                    tokio::time::sleep(std::time::Duration::from_millis(100)).await;
                    num * 2
                }))
        ).await;
        
        println!("Result: {}", result); // Prints "Result: 10"
    }));
}
}

Cancellation Behavior

Unlike bevy_task::spawn_detached and thread::spawn, Tokio tasks spawned with tokio::spawn are aborted when the Runner is dropped. This means that if the Reactor is canceled, the Tokio task will also be canceled.

When to Use

Use side_effect::tokio actions when you need to:

  • Perform I/O-bound operations like network requests or file operations
  • Execute asynchronous code that benefits from Tokio's runtime features
  • Integrate with other libraries that use Tokio

The tokio module is particularly useful for operations that are I/O-bound rather than CPU-bound. For CPU-bound operations, consider using the thread module instead.

Feature Requirements

The tokio module requires the tokio feature flag to be enabled.

[dependencies]
bevy_flurx = { version = "0.1", features = ["tokio"] }