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 oncewait
: Actions that continue to execute every frame according to specified conditionsdelay
: Actions that perform delay processing- Action chaining with
then
,pipe
, andthrough
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 Name | Description | Default |
---|---|---|
audio | Audio actions | false |
record | Undo/redo actions and events | false |
side-effect | Thread/async side effects | false |
state | State actions | false |
tokio | Use tokio's runtime directly in the reactor | false |
std | Enable features that depend on the standard library | false |
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.
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.
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.
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.
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.
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 usingIntoSystem
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 onswitch_is_off<M>()
- Returns true if the switch is offswitch_just_turned_on<M>()
- Returns true only the first time the switch is detected as onswitch_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 anOption<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 isLeft
is_right()
- Returns true if the value isRight
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:
- The
Then
trait: Allows actions to be combined using thethen
method - 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:
- The actions are executed in sequence
- Each action starts as soon as the previous one completes
- If multiple actions complete in the same frame, they will all execute in that frame
- 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:
- The first action is executed until completion
- The output of the first action is passed as input to the second action
- The second action is then executed with this input
- 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:
- The
through
function: Creates an action that executes a provided action but preserves the input value as the output - 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:
- The original input value is stored
- The provided action is executed until completion
- 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:
Omit
: Omits both input and output types from an actionOmitOutput
: Omits only the output type from an actionOmitInput
: 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:
map
: Transforms the output of an action by applying a function to itoverwrite
: 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:
- The original action is executed until completion
- The output of the action is transformed using the provided function (for
map
) or replaced with the specified value (foroverwrite
) - 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:
- 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 - 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:
- The input value is cloned
- One clone is passed to the auxiliary action for processing
- The original input is forwarded as the output without modification
- 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:
- The original action's
Runner
andOutput
are captured - A new
Runner
is created using the provided function, which receives:- The original
Runner
- The original
Output
- A new
Output
for the transformed action
- The original
- 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 onswitch_is_off<M>()
- Returns true if the switch is offswitch_just_turned_on<M>()
- Returns true only the first time the switch is detected as onswitch_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
- once::switch::on - Turn a switch on
- once::switch::off - Turn a switch off
wait::switch
- wait::switch::on - Wait until a switch is turned on
- wait::switch::off - Wait until a switch is turned off
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 recordedrollback
: 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 actionundo()
: Creates a rollback that doesn't create a redo actionundo_redo()
: Creates a rollback that always creates a redo actionparts()
: Declares undo and redo separately withUndo
andRedo
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 implementsSend + Sync + 'static
.rollback
: The process called when a rollback is requested. This is created using one of theRollback
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 withUndo
andRedo
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 torecord::undo::once
IndexTo(usize)
: Corresponds torecord::undo::index_to
To(Act)
: Corresponds torecord::undo::to
All
: Corresponds torecord::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 torecord::redo::once
IndexTo(usize)
: Corresponds torecord::redo::index_to
To(Act)
: Corresponds torecord::redo::to
All
: Corresponds torecord::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 upRequestUndo
andRequestRedo
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:
- The
Record<Act>
resource is initialized - Event types for
RequestUndo<Act>
andRequestRedo<Act>
are registered - Systems are added to handle these events and triggers
When a RequestUndo
or RequestRedo
event is sent or triggered:
- The corresponding system creates a new Reactor
- The Reactor executes the appropriate undo or redo action
- 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 { ... }
- A function that takes input and returns a future:
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 { ... }
- A function that takes input and returns a future:
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:
|| { ... }
- A function that takes 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 { ... }
- A function that takes input and returns a future:
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"] }