# Objective This PR adds an example that shows how to use `apply_system_buffers` and how to order it with respect to the relevant systems. It also shows how not ordering the systems can lead to unexpected behaviours. ## Solution Add the example.
		
			
				
	
	
		
			171 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			171 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
//! This example illustrates how to use the `apply_system_buffers` system
 | 
						|
//! to flush commands added by systems that have already run,
 | 
						|
//! but have not had their buffers applied yet.
 | 
						|
//!
 | 
						|
//! This is useful when you don't want to wait until the next flush set
 | 
						|
//! automatically added by Bevy (usually `CoreSet::UpdateFlush`, for systems
 | 
						|
//! added to `CoreSet::Update`) but want to flush commands immediately.
 | 
						|
//!
 | 
						|
//! It is important that systems are ordered correctly with respect to
 | 
						|
//! `apply_system_buffers`, to avoid surprising non-deterministic system execution order.
 | 
						|
 | 
						|
use bevy::prelude::*;
 | 
						|
 | 
						|
fn main() {
 | 
						|
    App::new()
 | 
						|
        .add_plugins(DefaultPlugins)
 | 
						|
        .init_resource::<Timers>()
 | 
						|
        .add_startup_system(setup)
 | 
						|
        .add_system(despawn_old_and_spawn_new_fruits.before(CustomFlush))
 | 
						|
        .add_system(apply_system_buffers.in_set(CustomFlush))
 | 
						|
        .add_system(count_apple.after(CustomFlush))
 | 
						|
        .add_system(count_orange)
 | 
						|
        .add_system(bevy::window::close_on_esc)
 | 
						|
        .run();
 | 
						|
}
 | 
						|
 | 
						|
#[derive(Resource)]
 | 
						|
struct Timers {
 | 
						|
    repeating: Timer,
 | 
						|
}
 | 
						|
 | 
						|
impl Default for Timers {
 | 
						|
    fn default() -> Self {
 | 
						|
        Self {
 | 
						|
            repeating: Timer::from_seconds(0.5, TimerMode::Repeating),
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
 | 
						|
struct CustomFlush;
 | 
						|
 | 
						|
#[derive(Component)]
 | 
						|
struct Apple;
 | 
						|
 | 
						|
#[derive(Component)]
 | 
						|
struct Orange;
 | 
						|
 | 
						|
#[derive(Component)]
 | 
						|
struct AppleCount;
 | 
						|
 | 
						|
#[derive(Component)]
 | 
						|
struct OrangeCount;
 | 
						|
 | 
						|
// Setup the counters in the UI.
 | 
						|
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
 | 
						|
    commands.spawn(Camera2dBundle::default());
 | 
						|
 | 
						|
    commands
 | 
						|
        .spawn(NodeBundle {
 | 
						|
            style: Style {
 | 
						|
                size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
 | 
						|
                align_items: AlignItems::Center,
 | 
						|
                justify_content: JustifyContent::Center,
 | 
						|
                flex_direction: FlexDirection::Column,
 | 
						|
                ..default()
 | 
						|
            },
 | 
						|
            ..default()
 | 
						|
        })
 | 
						|
        .with_children(|parent| {
 | 
						|
            parent.spawn((
 | 
						|
                TextBundle::from_section(
 | 
						|
                    "Apple: nothing counted yet".to_string(),
 | 
						|
                    TextStyle {
 | 
						|
                        font: asset_server.load("fonts/FiraSans-Bold.ttf"),
 | 
						|
                        font_size: 80.0,
 | 
						|
                        color: Color::ORANGE,
 | 
						|
                    },
 | 
						|
                ),
 | 
						|
                AppleCount,
 | 
						|
            ));
 | 
						|
            parent.spawn((
 | 
						|
                TextBundle::from_section(
 | 
						|
                    "Orange: nothing counted yet".to_string(),
 | 
						|
                    TextStyle {
 | 
						|
                        font: asset_server.load("fonts/FiraSans-Bold.ttf"),
 | 
						|
                        font_size: 80.0,
 | 
						|
                        color: Color::ORANGE,
 | 
						|
                    },
 | 
						|
                ),
 | 
						|
                OrangeCount,
 | 
						|
            ));
 | 
						|
        });
 | 
						|
}
 | 
						|
 | 
						|
// Every tick, before the CustomFlush we added, we despawn any Apple and Orange
 | 
						|
// we have previously spawned, if any. Then we tick the timer, and if the timer
 | 
						|
// has finished during this tick, we spawn a new Apple and a new Orange.
 | 
						|
//
 | 
						|
// The commands that we have added here will normally be flushed by Bevy
 | 
						|
// as part of the `CoreSet::UpdateFlush` set, but because we have ordered
 | 
						|
// this system to run before `apply_system_buffer.in_set(CustomFlush)`,
 | 
						|
// these commands added here will be flushed during our custom flush.
 | 
						|
fn despawn_old_and_spawn_new_fruits(
 | 
						|
    mut commands: Commands,
 | 
						|
    time: Res<Time>,
 | 
						|
    mut timers: ResMut<Timers>,
 | 
						|
    apple: Query<Entity, With<Apple>>,
 | 
						|
    orange: Query<Entity, With<Orange>>,
 | 
						|
) {
 | 
						|
    if let Ok(apple_entity) = apple.get_single() {
 | 
						|
        commands.entity(apple_entity).despawn();
 | 
						|
    }
 | 
						|
 | 
						|
    if let Ok(orange_entity) = orange.get_single() {
 | 
						|
        commands.entity(orange_entity).despawn();
 | 
						|
    }
 | 
						|
 | 
						|
    timers.repeating.tick(time.delta());
 | 
						|
 | 
						|
    if timers.repeating.just_finished() {
 | 
						|
        commands.spawn(Apple);
 | 
						|
        commands.spawn(Orange);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
// If the timer has finished during this tick, we see if there is an entity
 | 
						|
// with an Apple component or not, and update the UI accordingly.
 | 
						|
//
 | 
						|
// Since this system is ordered `.after(CustomFlush)` it will be guaranteed
 | 
						|
// to run after our CustomFlush set, so the Apple will always be counted.
 | 
						|
//
 | 
						|
// We will see the AppleCount go from "Apple: nothing counted yet" to "Apple: counted"
 | 
						|
fn count_apple(
 | 
						|
    timers: Res<Timers>,
 | 
						|
    apple: Query<&Apple>,
 | 
						|
    mut apple_count: Query<&mut Text, With<AppleCount>>,
 | 
						|
) {
 | 
						|
    if timers.repeating.just_finished() {
 | 
						|
        let mut apples_text = apple_count.single_mut();
 | 
						|
        apples_text.sections[0].value = if apple.is_empty() {
 | 
						|
            "Apple: not counted".to_string()
 | 
						|
        } else {
 | 
						|
            "Apple: counted".to_string()
 | 
						|
        };
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
// If the timer has finished during this tick, we see if there is an entity
 | 
						|
// with an Orange component or not, and update the UI accordingly.
 | 
						|
//
 | 
						|
// Since this system is not ordered `.after(CustomFlush)`, it may or may not run
 | 
						|
// before the custom flush, therefore you will see the UI either show "Orange: counted"
 | 
						|
// or "Orange: not counted" or alternate between the two.
 | 
						|
//
 | 
						|
// Try to re-run the example multiple times as well.
 | 
						|
fn count_orange(
 | 
						|
    timers: Res<Timers>,
 | 
						|
    orange: Query<&Orange>,
 | 
						|
    mut orange_count: Query<&mut Text, With<OrangeCount>>,
 | 
						|
) {
 | 
						|
    if timers.repeating.just_finished() {
 | 
						|
        let mut oranges_text = orange_count.single_mut();
 | 
						|
        oranges_text.sections[0].value = if orange.is_empty() {
 | 
						|
            "Orange: not counted".to_string()
 | 
						|
        } else {
 | 
						|
            "Orange: counted".to_string()
 | 
						|
        };
 | 
						|
    }
 | 
						|
}
 |