Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

View File

@@ -0,0 +1,106 @@
//! This example illustrates how to react to component and resource changes.
use bevy::prelude::*;
use rand::Rng;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(
Update,
(
change_component,
change_component_2,
change_resource,
change_detection,
),
)
.run();
}
#[derive(Component, PartialEq, Debug)]
struct MyComponent(f32);
#[derive(Resource, PartialEq, Debug)]
struct MyResource(f32);
fn setup(mut commands: Commands) {
// Note the first change detection log correctly points to this line because the component is
// added. Although commands are deferred, they are able to track the original calling location.
commands.spawn(MyComponent(0.0));
commands.insert_resource(MyResource(0.0));
}
fn change_component(time: Res<Time>, mut query: Query<(Entity, &mut MyComponent)>) {
for (entity, mut component) in &mut query {
if rand::thread_rng().gen_bool(0.1) {
let new_component = MyComponent(time.elapsed_secs().round());
info!("New value: {new_component:?} {entity}");
// Change detection occurs on mutable dereference, and does not consider whether or not
// a value is actually equal. To avoid triggering change detection when nothing has
// actually changed, you can use the `set_if_neq` method on any component or resource
// that implements PartialEq.
component.set_if_neq(new_component);
}
}
}
/// This is a duplicate of the `change_component` system, added to show that change tracking can
/// help you find *where* your component is being changed, when there are multiple possible
/// locations.
fn change_component_2(time: Res<Time>, mut query: Query<(Entity, &mut MyComponent)>) {
for (entity, mut component) in &mut query {
if rand::thread_rng().gen_bool(0.1) {
let new_component = MyComponent(time.elapsed_secs().round());
info!("New value: {new_component:?} {entity}");
component.set_if_neq(new_component);
}
}
}
/// Change detection concepts for components apply similarly to resources.
fn change_resource(time: Res<Time>, mut my_resource: ResMut<MyResource>) {
if rand::thread_rng().gen_bool(0.1) {
let new_resource = MyResource(time.elapsed_secs().round());
info!("New value: {new_resource:?}");
my_resource.set_if_neq(new_resource);
}
}
/// Query filters like [`Changed<T>`] and [`Added<T>`] ensure only entities matching these filters
/// will be returned by the query.
///
/// Using the [`Ref<T>`] system param allows you to access change detection information, but does
/// not filter the query.
fn change_detection(
changed_components: Query<Ref<MyComponent>, Changed<MyComponent>>,
my_resource: Res<MyResource>,
) {
for component in &changed_components {
// By default, you can only tell that a component was changed.
//
// This is useful, but what if you have multiple systems modifying the same component, how
// will you know which system is causing the component to change?
warn!(
"Change detected!\n\t-> value: {:?}\n\t-> added: {}\n\t-> changed: {}\n\t-> changed by: {}",
component,
component.is_added(),
component.is_changed(),
// If you enable the `track_location` feature, you can unlock the `changed_by()`
// method. It returns the file and line number that the component or resource was
// changed in. It's not recommended for released games, but great for debugging!
component.changed_by()
);
}
if my_resource.is_changed() {
warn!(
"Change detected!\n\t-> value: {:?}\n\t-> added: {}\n\t-> changed: {}\n\t-> changed by: {}",
my_resource,
my_resource.is_added(),
my_resource.is_changed(),
my_resource.changed_by() // Like components, requires `track_location` feature.
);
}
}

View File

@@ -0,0 +1,148 @@
//! This example illustrates the different ways you can employ component lifecycle hooks.
//!
//! Whenever possible, prefer using Bevy's change detection or Events for reacting to component changes.
//! Events generally offer better performance and more flexible integration into Bevy's systems.
//! Hooks are useful to enforce correctness but have limitations (only one hook per component,
//! less ergonomic than events).
//!
//! Here are some cases where components hooks might be necessary:
//!
//! - Maintaining indexes: If you need to keep custom data structures (like a spatial index) in
//! sync with the addition/removal of components.
//!
//! - Enforcing structural rules: When you have systems that depend on specific relationships
//! between components (like hierarchies or parent-child links) and need to maintain correctness.
use bevy::{
ecs::component::{ComponentHook, HookContext, Mutable, StorageType},
prelude::*,
};
use std::collections::HashMap;
#[derive(Debug)]
/// Hooks can also be registered during component initialization by
/// using [`Component`] derive macro:
/// ```no_run
/// #[derive(Component)]
/// #[component(on_add = ..., on_insert = ..., on_replace = ..., on_remove = ...)]
/// ```
struct MyComponent(KeyCode);
impl Component for MyComponent {
const STORAGE_TYPE: StorageType = StorageType::Table;
type Mutability = Mutable;
/// Hooks can also be registered during component initialization by
/// implementing the associated method
fn on_add() -> Option<ComponentHook> {
// We don't have an `on_add` hook so we'll just return None.
// Note that this is the default behavior when not implementing a hook.
None
}
}
#[derive(Resource, Default, Debug, Deref, DerefMut)]
struct MyComponentIndex(HashMap<KeyCode, Entity>);
#[derive(Event)]
struct MyEvent;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, trigger_hooks)
.init_resource::<MyComponentIndex>()
.add_event::<MyEvent>()
.run();
}
fn setup(world: &mut World) {
// In order to register component hooks the component must:
// - not be currently in use by any entities in the world
// - not already have a hook of that kind registered
// This is to prevent overriding hooks defined in plugins and other crates as well as keeping things fast
world
.register_component_hooks::<MyComponent>()
// There are 4 component lifecycle hooks: `on_add`, `on_insert`, `on_replace` and `on_remove`
// A hook has 2 arguments:
// - a `DeferredWorld`, this allows access to resource and component data as well as `Commands`
// - a `HookContext`, this provides access to the following contextual information:
// - the entity that triggered the hook
// - the component id of the triggering component, this is mostly used for dynamic components
// - the location of the code that caused the hook to trigger
//
// `on_add` will trigger when a component is inserted onto an entity without it
.on_add(
|mut world,
HookContext {
entity,
component_id,
caller,
..
}| {
// You can access component data from within the hook
let value = world.get::<MyComponent>(entity).unwrap().0;
println!(
"{component_id:?} added to {entity} with value {value:?}{}",
caller
.map(|location| format!("due to {location}"))
.unwrap_or_default()
);
// Or access resources
world
.resource_mut::<MyComponentIndex>()
.insert(value, entity);
// Or send events
world.send_event(MyEvent);
},
)
// `on_insert` will trigger when a component is inserted onto an entity,
// regardless of whether or not it already had it and after `on_add` if it ran
.on_insert(|world, _| {
println!("Current Index: {:?}", world.resource::<MyComponentIndex>());
})
// `on_replace` will trigger when a component is inserted onto an entity that already had it,
// and runs before the value is replaced.
// Also triggers when a component is removed from an entity, and runs before `on_remove`
.on_replace(|mut world, context| {
let value = world.get::<MyComponent>(context.entity).unwrap().0;
world.resource_mut::<MyComponentIndex>().remove(&value);
})
// `on_remove` will trigger when a component is removed from an entity,
// since it runs before the component is removed you can still access the component data
.on_remove(
|mut world,
HookContext {
entity,
component_id,
caller,
..
}| {
let value = world.get::<MyComponent>(entity).unwrap().0;
println!(
"{component_id:?} removed from {entity} with value {value:?}{}",
caller
.map(|location| format!("due to {location}"))
.unwrap_or_default()
);
// You can also issue commands through `.commands()`
world.commands().entity(entity).despawn();
},
);
}
fn trigger_hooks(
mut commands: Commands,
keys: Res<ButtonInput<KeyCode>>,
index: Res<MyComponentIndex>,
) {
for (key, entity) in index.iter() {
if !keys.pressed(*key) {
commands.entity(*entity).remove::<MyComponent>();
}
}
for key in keys.get_just_pressed() {
commands.spawn(MyComponent(*key));
}
}

View File

@@ -0,0 +1,195 @@
//! This example illustrates the usage of the [`QueryData`] derive macro, which allows
//! defining custom query and filter types.
//!
//! While regular tuple queries work great in most of simple scenarios, using custom queries
//! declared as named structs can bring the following advantages:
//! - They help to avoid destructuring or using `q.0, q.1, ...` access pattern.
//! - Adding, removing components or changing items order with structs greatly reduces maintenance
//! burden, as you don't need to update statements that destructure tuples, care about order
//! of elements, etc. Instead, you can just add or remove places where a certain element is used.
//! - Named structs enable the composition pattern, that makes query types easier to re-use.
//! - You can bypass the limit of 15 components that exists for query tuples.
//!
//! For more details on the [`QueryData`] derive macro, see the trait documentation.
use bevy::{
ecs::query::{QueryData, QueryFilter},
prelude::*,
};
use std::fmt::Debug;
fn main() {
App::new()
.add_systems(Startup, spawn)
.add_systems(
Update,
(
print_components_read_only,
print_components_iter_mut,
print_components_iter,
print_components_tuple,
)
.chain(),
)
.run();
}
#[derive(Component, Debug)]
struct ComponentA;
#[derive(Component, Debug)]
struct ComponentB;
#[derive(Component, Debug)]
struct ComponentC;
#[derive(Component, Debug)]
struct ComponentD;
#[derive(Component, Debug)]
struct ComponentZ;
#[derive(QueryData)]
#[query_data(derive(Debug))]
struct ReadOnlyCustomQuery<T: Component + Debug, P: Component + Debug> {
entity: Entity,
a: &'static ComponentA,
b: Option<&'static ComponentB>,
nested: NestedQuery,
optional_nested: Option<NestedQuery>,
optional_tuple: Option<(&'static ComponentB, &'static ComponentZ)>,
generic: GenericQuery<T, P>,
empty: EmptyQuery,
}
fn print_components_read_only(
query: Query<
ReadOnlyCustomQuery<ComponentC, ComponentD>,
CustomQueryFilter<ComponentC, ComponentD>,
>,
) {
println!("Print components (read_only):");
for e in &query {
println!("Entity: {}", e.entity);
println!("A: {:?}", e.a);
println!("B: {:?}", e.b);
println!("Nested: {:?}", e.nested);
println!("Optional nested: {:?}", e.optional_nested);
println!("Optional tuple: {:?}", e.optional_tuple);
println!("Generic: {:?}", e.generic);
}
println!();
}
/// If you are going to mutate the data in a query, you must mark it with the `mutable` attribute.
///
/// The [`QueryData`] derive macro will still create a read-only version, which will be have `ReadOnly`
/// suffix.
/// Note: if you want to use derive macros with read-only query variants, you need to pass them with
/// using the `derive` attribute.
#[derive(QueryData)]
#[query_data(mutable, derive(Debug))]
struct CustomQuery<T: Component + Debug, P: Component + Debug> {
entity: Entity,
a: &'static mut ComponentA,
b: Option<&'static mut ComponentB>,
nested: NestedQuery,
optional_nested: Option<NestedQuery>,
optional_tuple: Option<(NestedQuery, &'static mut ComponentZ)>,
generic: GenericQuery<T, P>,
empty: EmptyQuery,
}
// This is a valid query as well, which would iterate over every entity.
#[derive(QueryData)]
#[query_data(derive(Debug))]
struct EmptyQuery {
empty: (),
}
#[derive(QueryData)]
#[query_data(derive(Debug))]
struct NestedQuery {
c: &'static ComponentC,
d: Option<&'static ComponentD>,
}
#[derive(QueryData)]
#[query_data(derive(Debug))]
struct GenericQuery<T: Component, P: Component> {
generic: (&'static T, &'static P),
}
#[derive(QueryFilter)]
struct CustomQueryFilter<T: Component, P: Component> {
_c: With<ComponentC>,
_d: With<ComponentD>,
_or: Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>,
_generic_tuple: (With<T>, With<P>),
}
fn spawn(mut commands: Commands) {
commands.spawn((ComponentA, ComponentB, ComponentC, ComponentD));
}
fn print_components_iter_mut(
mut query: Query<
CustomQuery<ComponentC, ComponentD>,
CustomQueryFilter<ComponentC, ComponentD>,
>,
) {
println!("Print components (iter_mut):");
for e in &mut query {
// Re-declaring the variable to illustrate the type of the actual iterator item.
let e: CustomQueryItem<'_, _, _> = e;
println!("Entity: {}", e.entity);
println!("A: {:?}", e.a);
println!("B: {:?}", e.b);
println!("Optional nested: {:?}", e.optional_nested);
println!("Optional tuple: {:?}", e.optional_tuple);
println!("Nested: {:?}", e.nested);
println!("Generic: {:?}", e.generic);
}
println!();
}
fn print_components_iter(
query: Query<CustomQuery<ComponentC, ComponentD>, CustomQueryFilter<ComponentC, ComponentD>>,
) {
println!("Print components (iter):");
for e in &query {
// Re-declaring the variable to illustrate the type of the actual iterator item.
let e: CustomQueryReadOnlyItem<'_, _, _> = e;
println!("Entity: {}", e.entity);
println!("A: {:?}", e.a);
println!("B: {:?}", e.b);
println!("Nested: {:?}", e.nested);
println!("Generic: {:?}", e.generic);
}
println!();
}
type NestedTupleQuery<'w> = (&'w ComponentC, &'w ComponentD);
type GenericTupleQuery<'w, T, P> = (&'w T, &'w P);
fn print_components_tuple(
query: Query<
(
Entity,
&ComponentA,
&ComponentB,
NestedTupleQuery,
GenericTupleQuery<ComponentC, ComponentD>,
),
(
With<ComponentC>,
With<ComponentD>,
Or<(Added<ComponentC>, Changed<ComponentD>, Without<ComponentZ>)>,
),
>,
) {
println!("Print components (tuple):");
for (entity, a, b, nested, (generic_c, generic_d)) in &query {
println!("Entity: {entity}");
println!("A: {a:?}");
println!("B: {b:?}");
println!("Nested: {:?} {:?}", nested.0, nested.1);
println!("Generic: {generic_c:?} {generic_d:?}");
}
}

View File

@@ -0,0 +1,84 @@
//! Demonstrates how to add custom schedules that run in Bevy's `Main` schedule, ordered relative to Bevy's built-in
//! schedules such as `Update` or `Last`.
use bevy::{
app::MainScheduleOrder,
ecs::schedule::{ExecutorKind, ScheduleLabel},
prelude::*,
};
#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone)]
struct SingleThreadedUpdate;
#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone)]
struct CustomStartup;
fn main() {
let mut app = App::new();
// Create a new [`Schedule`]. For demonstration purposes, we configure it to use a single threaded executor so that
// systems in this schedule are never run in parallel. However, this is not a requirement for custom schedules in
// general.
let mut custom_update_schedule = Schedule::new(SingleThreadedUpdate);
custom_update_schedule.set_executor_kind(ExecutorKind::SingleThreaded);
// Adding the schedule to the app does not automatically run the schedule. This merely registers the schedule so
// that systems can look it up using the `Schedules` resource.
app.add_schedule(custom_update_schedule);
// Bevy `App`s have a `main_schedule_label` field that configures which schedule is run by the App's `runner`.
// By default, this is `Main`. The `Main` schedule is responsible for running Bevy's main schedules such as
// `Update`, `Startup` or `Last`.
//
// We can configure the `Main` schedule to run our custom update schedule relative to the existing ones by modifying
// the `MainScheduleOrder` resource.
//
// Note that we modify `MainScheduleOrder` directly in `main` and not in a startup system. The reason for this is
// that the `MainScheduleOrder` cannot be modified from systems that are run as part of the `Main` schedule.
let mut main_schedule_order = app.world_mut().resource_mut::<MainScheduleOrder>();
main_schedule_order.insert_after(Update, SingleThreadedUpdate);
// Adding a custom startup schedule works similarly, but needs to use `insert_startup_after`
// instead of `insert_after`.
app.add_schedule(Schedule::new(CustomStartup));
let mut main_schedule_order = app.world_mut().resource_mut::<MainScheduleOrder>();
main_schedule_order.insert_startup_after(PreStartup, CustomStartup);
app.add_systems(SingleThreadedUpdate, single_threaded_update_system)
.add_systems(CustomStartup, custom_startup_system)
.add_systems(PreStartup, pre_startup_system)
.add_systems(Startup, startup_system)
.add_systems(First, first_system)
.add_systems(Update, update_system)
.add_systems(Last, last_system)
.run();
}
fn pre_startup_system() {
println!("Pre Startup");
}
fn startup_system() {
println!("Startup");
}
fn custom_startup_system() {
println!("Custom Startup");
}
fn first_system() {
println!("First");
}
fn update_system() {
println!("Update");
}
fn single_threaded_update_system() {
println!("Single Threaded Update");
}
fn last_system() {
println!("Last");
}

278
vendor/bevy/examples/ecs/dynamic.rs vendored Normal file
View File

@@ -0,0 +1,278 @@
#![expect(
unsafe_code,
reason = "Unsafe code is needed to work with dynamic components"
)]
//! This example show how you can create components dynamically, spawn entities with those components
//! as well as query for entities with those components.
use std::{alloc::Layout, collections::HashMap, io::Write, ptr::NonNull};
use bevy::{
ecs::{
component::{
ComponentCloneBehavior, ComponentDescriptor, ComponentId, ComponentInfo, StorageType,
},
query::{ComponentAccessKind, QueryData},
world::FilteredEntityMut,
},
prelude::*,
ptr::{Aligned, OwningPtr},
};
const PROMPT: &str = "
Commands:
comp, c Create new components
spawn, s Spawn entities
query, q Query for entities
Enter a command with no parameters for usage.";
const COMPONENT_PROMPT: &str = "
comp, c Create new components
Enter a comma separated list of type names optionally followed by a size in u64s.
e.g. CompA 3, CompB, CompC 2";
const ENTITY_PROMPT: &str = "
spawn, s Spawn entities
Enter a comma separated list of components optionally followed by values.
e.g. CompA 0 1 0, CompB, CompC 1";
const QUERY_PROMPT: &str = "
query, q Query for entities
Enter a query to fetch and update entities
Components with read or write access will be displayed with their values
Components with write access will have their fields incremented by one
Accesses: 'A' with, '&A' read, '&mut A' write
Operators: '||' or, ',' and, '?' optional
e.g. &A || &B, &mut C, D, ?E";
fn main() {
let mut world = World::new();
let mut lines = std::io::stdin().lines();
let mut component_names = HashMap::<String, ComponentId>::new();
let mut component_info = HashMap::<ComponentId, ComponentInfo>::new();
println!("{PROMPT}");
loop {
print!("\n> ");
let _ = std::io::stdout().flush();
let Some(Ok(line)) = lines.next() else {
return;
};
if line.is_empty() {
return;
};
let Some((first, rest)) = line.trim().split_once(|c: char| c.is_whitespace()) else {
match &line.chars().next() {
Some('c') => println!("{COMPONENT_PROMPT}"),
Some('s') => println!("{ENTITY_PROMPT}"),
Some('q') => println!("{QUERY_PROMPT}"),
_ => println!("{PROMPT}"),
}
continue;
};
match &first[0..1] {
"c" => {
rest.split(',').for_each(|component| {
let mut component = component.split_whitespace();
let Some(name) = component.next() else {
return;
};
let size = match component.next().map(str::parse) {
Some(Ok(size)) => size,
_ => 0,
};
// Register our new component to the world with a layout specified by it's size
// SAFETY: [u64] is Send + Sync
let id = world.register_component_with_descriptor(unsafe {
ComponentDescriptor::new_with_layout(
name.to_string(),
StorageType::Table,
Layout::array::<u64>(size).unwrap(),
None,
true,
ComponentCloneBehavior::Default,
)
});
let Some(info) = world.components().get_info(id) else {
return;
};
component_names.insert(name.to_string(), id);
component_info.insert(id, info.clone());
println!("Component {} created with id: {}", name, id.index());
});
}
"s" => {
let mut to_insert_ids = Vec::new();
let mut to_insert_data = Vec::new();
rest.split(',').for_each(|component| {
let mut component = component.split_whitespace();
let Some(name) = component.next() else {
return;
};
// Get the id for the component with the given name
let Some(&id) = component_names.get(name) else {
println!("Component {name} does not exist");
return;
};
// Calculate the length for the array based on the layout created for this component id
let info = world.components().get_info(id).unwrap();
let len = info.layout().size() / size_of::<u64>();
let mut values: Vec<u64> = component
.take(len)
.filter_map(|value| value.parse::<u64>().ok())
.collect();
values.resize(len, 0);
// Collect the id and array to be inserted onto our entity
to_insert_ids.push(id);
to_insert_data.push(values);
});
let mut entity = world.spawn_empty();
// Construct an `OwningPtr` for each component in `to_insert_data`
let to_insert_ptr = to_owning_ptrs(&mut to_insert_data);
// SAFETY:
// - Component ids have been taken from the same world
// - Each array is created to the layout specified in the world
unsafe {
entity.insert_by_ids(&to_insert_ids, to_insert_ptr.into_iter());
}
println!("Entity spawned with id: {}", entity.id());
}
"q" => {
let mut builder = QueryBuilder::<FilteredEntityMut>::new(&mut world);
parse_query(rest, &mut builder, &component_names);
let mut query = builder.build();
query.iter_mut(&mut world).for_each(|filtered_entity| {
let terms = filtered_entity
.access()
.try_iter_component_access()
.unwrap()
.map(|component_access| {
let id = *component_access.index();
let ptr = filtered_entity.get_by_id(id).unwrap();
let info = component_info.get(&id).unwrap();
let len = info.layout().size() / size_of::<u64>();
// SAFETY:
// - All components are created with layout [u64]
// - len is calculated from the component descriptor
let data = unsafe {
std::slice::from_raw_parts_mut(
ptr.assert_unique().as_ptr().cast::<u64>(),
len,
)
};
// If we have write access, increment each value once
if matches!(component_access, ComponentAccessKind::Exclusive(_)) {
data.iter_mut().for_each(|data| {
*data += 1;
});
}
format!("{}: {:?}", info.name(), data[0..len].to_vec())
})
.collect::<Vec<_>>()
.join(", ");
println!("{}: {}", filtered_entity.id(), terms);
});
}
_ => continue,
}
}
}
// Constructs `OwningPtr` for each item in `components`
// By sharing the lifetime of `components` with the resulting ptrs we ensure we don't drop the data before use
fn to_owning_ptrs(components: &mut [Vec<u64>]) -> Vec<OwningPtr<Aligned>> {
components
.iter_mut()
.map(|data| {
let ptr = data.as_mut_ptr();
// SAFETY:
// - Pointers are guaranteed to be non-null
// - Memory pointed to won't be dropped until `components` is dropped
unsafe {
let non_null = NonNull::new_unchecked(ptr.cast());
OwningPtr::new(non_null)
}
})
.collect()
}
fn parse_term<Q: QueryData>(
str: &str,
builder: &mut QueryBuilder<Q>,
components: &HashMap<String, ComponentId>,
) {
let mut matched = false;
let str = str.trim();
match str.chars().next() {
// Optional term
Some('?') => {
builder.optional(|b| parse_term(&str[1..], b, components));
matched = true;
}
// Reference term
Some('&') => {
let mut parts = str.split_whitespace();
let first = parts.next().unwrap();
if first == "&mut" {
if let Some(str) = parts.next() {
if let Some(&id) = components.get(str) {
builder.mut_id(id);
matched = true;
}
};
} else if let Some(&id) = components.get(&first[1..]) {
builder.ref_id(id);
matched = true;
}
}
// With term
Some(_) => {
if let Some(&id) = components.get(str) {
builder.with_id(id);
matched = true;
}
}
None => {}
};
if !matched {
println!("Unable to find component: {str}");
}
}
fn parse_query<Q: QueryData>(
str: &str,
builder: &mut QueryBuilder<Q>,
components: &HashMap<String, ComponentId>,
) {
let str = str.split(',');
str.for_each(|term| {
let sub_terms: Vec<_> = term.split("||").collect();
if sub_terms.len() == 1 {
parse_term(sub_terms[0], builder, components);
} else {
builder.or(|b| {
sub_terms
.iter()
.for_each(|term| parse_term(term, b, components));
});
}
});
}

361
vendor/bevy/examples/ecs/ecs_guide.rs vendored Normal file
View File

@@ -0,0 +1,361 @@
//! This is a guided introduction to Bevy's "Entity Component System" (ECS)
//! All Bevy app logic is built using the ECS pattern, so definitely pay attention!
//!
//! Why ECS?
//! * Data oriented: Functionality is driven by data
//! * Clean Architecture: Loose coupling of functionality / prevents deeply nested inheritance
//! * High Performance: Massively parallel and cache friendly
//!
//! ECS Definitions:
//!
//! Component: just a normal Rust data type. generally scoped to a single piece of functionality
//! Examples: position, velocity, health, color, name
//!
//! Entity: a collection of components with a unique id
//! Examples: Entity1 { Name("Alice"), Position(0, 0) },
//! Entity2 { Name("Bill"), Position(10, 5) }
//!
//! Resource: a shared global piece of data
//! Examples: asset storage, events, system state
//!
//! System: runs logic on entities, components, and resources
//! Examples: move system, damage system
//!
//! Now that you know a little bit about ECS, lets look at some Bevy code!
//! We will now make a simple "game" to illustrate what Bevy's ECS looks like in practice.
use bevy::{
app::{AppExit, ScheduleRunnerPlugin},
prelude::*,
};
use core::time::Duration;
use rand::random;
use std::fmt;
// COMPONENTS: Pieces of functionality we add to entities. These are just normal Rust data types
//
// Our game will have a number of "players". Each player has a name that identifies them
#[derive(Component)]
struct Player {
name: String,
}
// Each player also has a score. This component holds on to that score
#[derive(Component)]
struct Score {
value: usize,
}
// Enums can also be used as components.
// This component tracks how many consecutive rounds a player has/hasn't scored in.
#[derive(Component)]
enum PlayerStreak {
Hot(usize),
None,
Cold(usize),
}
impl fmt::Display for PlayerStreak {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PlayerStreak::Hot(n) => write!(f, "{n} round hot streak"),
PlayerStreak::None => write!(f, "0 round streak"),
PlayerStreak::Cold(n) => write!(f, "{n} round cold streak"),
}
}
}
// RESOURCES: "Global" state accessible by systems. These are also just normal Rust data types!
//
// This resource holds information about the game:
#[derive(Resource, Default)]
struct GameState {
current_round: usize,
total_players: usize,
winning_player: Option<String>,
}
// This resource provides rules for our "game".
#[derive(Resource)]
struct GameRules {
winning_score: usize,
max_rounds: usize,
max_players: usize,
}
// SYSTEMS: Logic that runs on entities, components, and resources. These generally run once each
// time the app updates.
//
// This is the simplest type of system. It just prints "This game is fun!" on each run:
fn print_message_system() {
println!("This game is fun!");
}
// Systems can also read and modify resources. This system starts a new "round" on each update:
// NOTE: "mut" denotes that the resource is "mutable"
// Res<GameRules> is read-only. ResMut<GameState> can modify the resource
fn new_round_system(game_rules: Res<GameRules>, mut game_state: ResMut<GameState>) {
game_state.current_round += 1;
println!(
"Begin round {} of {}",
game_state.current_round, game_rules.max_rounds
);
}
// This system updates the score for each entity with the `Player`, `Score` and `PlayerStreak` components.
fn score_system(mut query: Query<(&Player, &mut Score, &mut PlayerStreak)>) {
for (player, mut score, mut streak) in &mut query {
let scored_a_point = random::<bool>();
if scored_a_point {
// Accessing components immutably is done via a regular reference - `player`
// has type `&Player`.
//
// Accessing components mutably is performed via type `Mut<T>` - `score`
// has type `Mut<Score>` and `streak` has type `Mut<PlayerStreak>`.
//
// `Mut<T>` implements `Deref<T>`, so struct fields can be updated using
// standard field update syntax ...
score.value += 1;
// ... and matching against enums requires dereferencing them
*streak = match *streak {
PlayerStreak::Hot(n) => PlayerStreak::Hot(n + 1),
PlayerStreak::Cold(_) | PlayerStreak::None => PlayerStreak::Hot(1),
};
println!(
"{} scored a point! Their score is: {} ({})",
player.name, score.value, *streak
);
} else {
*streak = match *streak {
PlayerStreak::Hot(_) | PlayerStreak::None => PlayerStreak::Cold(1),
PlayerStreak::Cold(n) => PlayerStreak::Cold(n + 1),
};
println!(
"{} did not score a point! Their score is: {} ({})",
player.name, score.value, *streak
);
}
}
// this game isn't very fun is it :)
}
// This system runs on all entities with the `Player` and `Score` components, but it also
// accesses the `GameRules` resource to determine if a player has won.
fn score_check_system(
game_rules: Res<GameRules>,
mut game_state: ResMut<GameState>,
query: Query<(&Player, &Score)>,
) {
for (player, score) in &query {
if score.value == game_rules.winning_score {
game_state.winning_player = Some(player.name.clone());
}
}
}
// This system ends the game if we meet the right conditions. This fires an AppExit event, which
// tells our App to quit. Check out the "event.rs" example if you want to learn more about using
// events.
fn game_over_system(
game_rules: Res<GameRules>,
game_state: Res<GameState>,
mut app_exit_events: EventWriter<AppExit>,
) {
if let Some(ref player) = game_state.winning_player {
println!("{player} won the game!");
app_exit_events.write(AppExit::Success);
} else if game_state.current_round == game_rules.max_rounds {
println!("Ran out of rounds. Nobody wins!");
app_exit_events.write(AppExit::Success);
}
}
// This is a "startup" system that runs exactly once when the app starts up. Startup systems are
// generally used to create the initial "state" of our game. The only thing that distinguishes a
// "startup" system from a "normal" system is how it is registered:
// Startup: app.add_systems(Startup, startup_system)
// Normal: app.add_systems(Update, normal_system)
fn startup_system(mut commands: Commands, mut game_state: ResMut<GameState>) {
// Create our game rules resource
commands.insert_resource(GameRules {
max_rounds: 10,
winning_score: 4,
max_players: 4,
});
// Add some players to our world. Players start with a score of 0 ... we want our game to be
// fair!
commands.spawn_batch(vec![
(
Player {
name: "Alice".to_string(),
},
Score { value: 0 },
PlayerStreak::None,
),
(
Player {
name: "Bob".to_string(),
},
Score { value: 0 },
PlayerStreak::None,
),
]);
// set the total players to "2"
game_state.total_players = 2;
}
// This system uses a command buffer to (potentially) add a new player to our game on each
// iteration. Normal systems cannot safely access the World instance directly because they run in
// parallel. Our World contains all of our components, so mutating arbitrary parts of it in parallel
// is not thread safe. Command buffers give us the ability to queue up changes to our World without
// directly accessing it
fn new_player_system(
mut commands: Commands,
game_rules: Res<GameRules>,
mut game_state: ResMut<GameState>,
) {
// Randomly add a new player
let add_new_player = random::<bool>();
if add_new_player && game_state.total_players < game_rules.max_players {
game_state.total_players += 1;
commands.spawn((
Player {
name: format!("Player {}", game_state.total_players),
},
Score { value: 0 },
PlayerStreak::None,
));
println!("Player {} joined the game!", game_state.total_players);
}
}
// If you really need full, immediate read/write access to the world or resources, you can use an
// "exclusive system".
// WARNING: These will block all parallel execution of other systems until they finish, so they
// should generally be avoided if you want to maximize parallelism.
fn exclusive_player_system(world: &mut World) {
// this does the same thing as "new_player_system"
let total_players = world.resource_mut::<GameState>().total_players;
let should_add_player = {
let game_rules = world.resource::<GameRules>();
let add_new_player = random::<bool>();
add_new_player && total_players < game_rules.max_players
};
// Randomly add a new player
if should_add_player {
println!("Player {} has joined the game!", total_players + 1);
world.spawn((
Player {
name: format!("Player {}", total_players + 1),
},
Score { value: 0 },
PlayerStreak::None,
));
let mut game_state = world.resource_mut::<GameState>();
game_state.total_players += 1;
}
}
// Sometimes systems need to be stateful. Bevy's ECS provides the `Local` system parameter
// for this case. A `Local<T>` refers to a value of type `T` that is owned by the system.
// This value is automatically initialized using `T`'s `FromWorld`* implementation upon the system's initialization.
// In this system's `Local` (`counter`), `T` is `u32`.
// Therefore, on the first turn, `counter` has a value of 0.
//
// *: `FromWorld` is a trait which creates a value using the contents of the `World`.
// For any type which is `Default`, like `u32` in this example, `FromWorld` creates the default value.
fn print_at_end_round(mut counter: Local<u32>) {
*counter += 1;
println!("In set 'Last' for the {}th time", *counter);
// Print an empty line between rounds
println!();
}
/// A group of related system sets, used for controlling the order of systems. Systems can be
/// added to any number of sets.
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
enum MySet {
BeforeRound,
Round,
AfterRound,
}
// Our Bevy app's entry point
fn main() {
// Bevy apps are created using the builder pattern. We use the builder to add systems,
// resources, and plugins to our app
App::new()
// Resources that implement the Default or FromWorld trait can be added like this:
.init_resource::<GameState>()
// Plugins are just a grouped set of app builder calls (just like we're doing here).
// We could easily turn our game into a plugin, but you can check out the plugin example for
// that :) The plugin below runs our app's "system schedule" once every 5 seconds.
.add_plugins(ScheduleRunnerPlugin::run_loop(Duration::from_secs(5)))
// `Startup` systems run exactly once BEFORE all other systems. These are generally used for
// app initialization code (ex: adding entities and resources)
.add_systems(Startup, startup_system)
// `Update` systems run once every update. These are generally used for "real-time app logic"
.add_systems(Update, print_message_system)
// SYSTEM EXECUTION ORDER
//
// Each system belongs to a `Schedule`, which controls the execution strategy and broad order
// of the systems within each tick. The `Startup` schedule holds
// startup systems, which are run a single time before `Update` runs. `Update` runs once per app update,
// which is generally one "frame" or one "tick".
//
// By default, all systems in a `Schedule` run in parallel, except when they require mutable access to a
// piece of data. This is efficient, but sometimes order matters.
// For example, we want our "game over" system to execute after all other systems to ensure
// we don't accidentally run the game for an extra round.
//
// You can force an explicit ordering between systems using the `.before` or `.after` methods.
// Systems will not be scheduled until all of the systems that they have an "ordering dependency" on have
// completed.
// There are other schedules, such as `Last` which runs at the very end of each run.
.add_systems(Last, print_at_end_round)
// We can also create new system sets, and order them relative to other system sets.
// Here is what our games execution order will look like:
// "before_round": new_player_system, new_round_system
// "round": print_message_system, score_system
// "after_round": score_check_system, game_over_system
.configure_sets(
Update,
// chain() will ensure sets run in the order they are listed
(MySet::BeforeRound, MySet::Round, MySet::AfterRound).chain(),
)
// The add_systems function is powerful. You can define complex system configurations with ease!
.add_systems(
Update,
(
// These `BeforeRound` systems will run before `Round` systems, thanks to the chained set configuration
(
// You can also chain systems! new_round_system will run first, followed by new_player_system
(new_round_system, new_player_system).chain(),
exclusive_player_system,
)
// All of the systems in the tuple above will be added to this set
.in_set(MySet::BeforeRound),
// This `Round` system will run after the `BeforeRound` systems thanks to the chained set configuration
score_system.in_set(MySet::Round),
// These `AfterRound` systems will run after the `Round` systems thanks to the chained set configuration
(
score_check_system,
// In addition to chain(), you can also use `before(system)` and `after(system)`. This also works
// with sets!
game_over_system.after(score_check_system),
)
.in_set(MySet::AfterRound),
),
)
// This call to run() starts the app we just built!
.run();
}

View File

@@ -0,0 +1,153 @@
//! Disabling entities is a powerful feature that allows you to hide entities from the ECS without deleting them.
//!
//! This can be useful for implementing features like "sleeping" objects that are offscreen
//! or managing networked entities.
//!
//! While disabling entities *will* make them invisible,
//! that's not its primary purpose!
//! [`Visibility`](bevy::prelude::Visibility) should be used to hide entities;
//! disabled entities are skipped entirely, which can lead to subtle bugs.
//!
//! # Default query filters
//!
//! Under the hood, Bevy uses a "default query filter" that skips entities with the
//! the [`Disabled`] component.
//! These filters act as a by-default exclusion list for all queries,
//! and can be bypassed by explicitly including these components in your queries.
//! For example, `Query<&A, With<Disabled>`, `Query<(Entity, Has<Disabled>>)` or
//! `Query<&A, Or<(With<Disabled>, With<B>)>>` will include disabled entities.
use bevy::ecs::entity_disabling::Disabled;
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins((DefaultPlugins, MeshPickingPlugin))
.add_observer(disable_entities_on_click)
.add_systems(
Update,
(list_all_named_entities, reenable_entities_on_space),
)
.add_systems(Startup, (setup_scene, display_instructions))
.run();
}
#[derive(Component)]
struct DisableOnClick;
fn disable_entities_on_click(
trigger: Trigger<Pointer<Click>>,
valid_query: Query<&DisableOnClick>,
mut commands: Commands,
) {
let clicked_entity = trigger.target();
// Windows and text are entities and can be clicked!
// We definitely don't want to disable the window itself,
// because that would cause the app to close!
if valid_query.contains(clicked_entity) {
// Just add the `Disabled` component to the entity to disable it.
// Note that the `Disabled` component is *only* added to the entity,
// its children are not affected.
commands.entity(clicked_entity).insert(Disabled);
}
}
#[derive(Component)]
struct EntityNameText;
// The query here will not find entities with the `Disabled` component,
// because it does not explicitly include it.
fn list_all_named_entities(
query: Query<&Name>,
mut name_text_query: Query<&mut Text, With<EntityNameText>>,
mut commands: Commands,
) {
let mut text_string = String::from("Named entities found:\n");
// Query iteration order is not guaranteed, so we sort the names
// to ensure the output is consistent.
for name in query.iter().sort::<&Name>() {
text_string.push_str(&format!("{:?}\n", name));
}
if let Ok(mut text) = name_text_query.single_mut() {
*text = Text::new(text_string);
} else {
commands.spawn((
EntityNameText,
Text::default(),
Node {
position_type: PositionType::Absolute,
top: Val::Px(12.0),
right: Val::Px(12.0),
..default()
},
));
}
}
fn reenable_entities_on_space(
mut commands: Commands,
// This query can find disabled entities,
// because it explicitly includes the `Disabled` component.
disabled_entities: Query<Entity, With<Disabled>>,
input: Res<ButtonInput<KeyCode>>,
) {
if input.just_pressed(KeyCode::Space) {
for entity in disabled_entities.iter() {
// To re-enable an entity, just remove the `Disabled` component.
commands.entity(entity).remove::<Disabled>();
}
}
}
const X_EXTENT: f32 = 900.;
fn setup_scene(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
commands.spawn(Camera2d);
let named_shapes = [
(Name::new("Annulus"), meshes.add(Annulus::new(25.0, 50.0))),
(
Name::new("Bestagon"),
meshes.add(RegularPolygon::new(50.0, 6)),
),
(Name::new("Rhombus"), meshes.add(Rhombus::new(75.0, 100.0))),
];
let num_shapes = named_shapes.len();
for (i, (name, shape)) in named_shapes.into_iter().enumerate() {
// Distribute colors evenly across the rainbow.
let color = Color::hsl(360. * i as f32 / num_shapes as f32, 0.95, 0.7);
commands.spawn((
name,
DisableOnClick,
Mesh2d(shape),
MeshMaterial2d(materials.add(color)),
Transform::from_xyz(
// Distribute shapes from -X_EXTENT/2 to +X_EXTENT/2.
-X_EXTENT / 2. + i as f32 / (num_shapes - 1) as f32 * X_EXTENT,
0.0,
0.0,
),
));
}
}
fn display_instructions(mut commands: Commands) {
commands.spawn((
Text::new(
"Click an entity to disable it.\n\nPress Space to re-enable all disabled entities.",
),
Node {
position_type: PositionType::Absolute,
top: Val::Px(12.0),
left: Val::Px(12.0),
..default()
},
));
}

View File

@@ -0,0 +1,192 @@
//! Showcases how fallible systems and observers can make use of Rust's powerful result handling
//! syntax.
//!
//! Important note: to set the global error handler, the `configurable_error_handler` feature must be
//! enabled. This feature is disabled by default, as it may introduce runtime overhead, especially for commands.
use bevy::ecs::{
error::{warn, GLOBAL_ERROR_HANDLER},
world::DeferredWorld,
};
use bevy::math::sampling::UniformMeshSampler;
use bevy::prelude::*;
use rand::distributions::Distribution;
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;
fn main() {
// By default, fallible systems that return an error will panic.
//
// We can change this by setting a custom error handler, which applies globally.
// Here we set the global error handler using one of the built-in
// error handlers. Bevy provides built-in handlers for `panic`, `error`, `warn`, `info`,
// `debug`, `trace` and `ignore`.
GLOBAL_ERROR_HANDLER
.set(warn)
.expect("The error handler can only be set once, globally.");
let mut app = App::new();
app.add_plugins(DefaultPlugins);
#[cfg(feature = "bevy_mesh_picking_backend")]
app.add_plugins(MeshPickingPlugin);
// Fallible systems can be used the same way as regular systems. The only difference is they
// return a `Result<(), BevyError>` instead of a `()` (unit) type. Bevy will handle both
// types of systems the same way, except for the error handling.
app.add_systems(Startup, setup);
// Commands can also return `Result`s, which are automatically handled by the global error handler
// if not explicitly handled by the user.
app.add_systems(Startup, failing_commands);
// Individual systems can also be handled by piping the output result:
app.add_systems(
PostStartup,
failing_system.pipe(|result: In<Result>| {
let _ = result.0.inspect_err(|err| info!("captured error: {err}"));
}),
);
// Fallible observers are also supported.
app.add_observer(fallible_observer);
// If we run the app, we'll see the following output at startup:
//
// WARN Encountered an error in system `fallible_systems::failing_system`: Resource not initialized
// ERROR fallible_systems::failing_system failed: Resource not initialized
// INFO captured error: Resource not initialized
app.run();
}
/// An example of a system that calls several fallible functions with the question mark operator.
///
/// See: <https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator>
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) -> Result {
let mut seeded_rng = ChaCha8Rng::seed_from_u64(19878367467712);
// Make a plane for establishing space.
commands.spawn((
Mesh3d(meshes.add(Plane3d::default().mesh().size(12.0, 12.0))),
MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
Transform::from_xyz(0.0, -2.5, 0.0),
));
// Spawn a light:
commands.spawn((
PointLight {
shadows_enabled: true,
..default()
},
Transform::from_xyz(4.0, 8.0, 4.0),
));
// Spawn a camera:
commands.spawn((
Camera3d::default(),
Transform::from_xyz(-2.0, 3.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
));
// Create a new sphere mesh:
let mut sphere_mesh = Sphere::new(1.0).mesh().ico(7)?;
sphere_mesh.generate_tangents()?;
// Spawn the mesh into the scene:
let mut sphere = commands.spawn((
Mesh3d(meshes.add(sphere_mesh.clone())),
MeshMaterial3d(materials.add(StandardMaterial::default())),
Transform::from_xyz(-1.0, 1.0, 0.0),
));
// Generate random sample points:
let triangles = sphere_mesh.triangles()?;
let distribution = UniformMeshSampler::try_new(triangles)?;
// Setup sample points:
let point_mesh = meshes.add(Sphere::new(0.01).mesh().ico(3)?);
let point_material = materials.add(StandardMaterial {
base_color: Srgba::RED.into(),
emissive: LinearRgba::rgb(1.0, 0.0, 0.0),
..default()
});
// Add sample points as children of the sphere:
for point in distribution.sample_iter(&mut seeded_rng).take(10000) {
sphere.with_child((
Mesh3d(point_mesh.clone()),
MeshMaterial3d(point_material.clone()),
Transform::from_translation(point),
));
}
// Indicate the system completed successfully:
Ok(())
}
// Observer systems can also return a `Result`.
fn fallible_observer(
trigger: Trigger<Pointer<Move>>,
mut world: DeferredWorld,
mut step: Local<f32>,
) -> Result {
let mut transform = world
.get_mut::<Transform>(trigger.target)
.ok_or("No transform found.")?;
*step = if transform.translation.x > 3. {
-0.1
} else if transform.translation.x < -3. || *step == 0. {
0.1
} else {
*step
};
transform.translation.x += *step;
Ok(())
}
#[derive(Resource)]
struct UninitializedResource;
fn failing_system(world: &mut World) -> Result {
world
// `get_resource` returns an `Option<T>`, so we use `ok_or` to convert it to a `Result` on
// which we can call `?` to propagate the error.
.get_resource::<UninitializedResource>()
// We can provide a `str` here because `BevyError` implements `From<&str>`.
.ok_or("Resource not initialized")?;
Ok(())
}
fn failing_commands(mut commands: Commands) {
commands
// This entity doesn't exist!
.entity(Entity::from_raw(12345678))
// Normally, this failed command would panic,
// but since we've set the global error handler to `warn`
// it will log a warning instead.
.insert(Transform::default());
// The error handlers for commands can be set individually as well,
// by using the queue_handled method.
commands.queue_handled(
|world: &mut World| -> Result {
world
.get_resource::<UninitializedResource>()
.ok_or("Resource not initialized when accessed in a command")?;
Ok(())
},
|error, context| {
error!("{error}, {context}");
},
);
}

144
vendor/bevy/examples/ecs/event.rs vendored Normal file
View File

@@ -0,0 +1,144 @@
//! This example shows how to send, mutate, and receive, events. As well as showing
//! how to you might control system ordering so that events are processed in a specific order.
//! It does this by simulating a damage over time effect that you might find in a game.
use bevy::prelude::*;
// In order to send or receive events first you must define them
// This event should be sent when something attempts to deal damage to another entity.
#[derive(Event, Debug)]
struct DealDamage {
pub amount: i32,
}
// This event should be sent when an entity receives damage.
#[derive(Event, Debug, Default)]
struct DamageReceived;
// This event should be sent when an entity blocks damage with armor.
#[derive(Event, Debug, Default)]
struct ArmorBlockedDamage;
// This resource represents a timer used to determine when to deal damage
// By default it repeats once per second
#[derive(Resource, Deref, DerefMut)]
struct DamageTimer(pub Timer);
impl Default for DamageTimer {
fn default() -> Self {
DamageTimer(Timer::from_seconds(1.0, TimerMode::Repeating))
}
}
// Next we define systems that send, mutate, and receive events
// This system reads 'DamageTimer', updates it, then sends a 'DealDamage' event
// if the timer has finished.
//
// Events are sent using an 'EventWriter<T>' by calling 'write' or 'write_default'.
// The 'write_default' method will send the event with the default value if the event
// has a 'Default' implementation.
fn deal_damage_over_time(
time: Res<Time>,
mut state: ResMut<DamageTimer>,
mut events: EventWriter<DealDamage>,
) {
if state.tick(time.delta()).finished() {
// Events can be sent with 'write' and constructed just like any other object.
events.write(DealDamage { amount: 10 });
}
}
// This system mutates the 'DealDamage' events to apply some armor value
// It also sends an 'ArmorBlockedDamage' event if the value of 'DealDamage' is zero
//
// Events are mutated using an 'EventMutator<T>' by calling 'read'. This returns an iterator
// over all the &mut T that this system has not read yet. Note, you can have multiple
// 'EventReader', 'EventWriter', and 'EventMutator' in a given system, as long as the types (T) are different.
fn apply_armor_to_damage(
mut dmg_events: EventMutator<DealDamage>,
mut armor_events: EventWriter<ArmorBlockedDamage>,
) {
for event in dmg_events.read() {
event.amount -= 1;
if event.amount <= 0 {
// Zero-sized events can also be sent with 'send'
armor_events.write(ArmorBlockedDamage);
}
}
}
// This system reads 'DealDamage' events and sends 'DamageReceived' if the amount is non-zero
//
// Events are read using an 'EventReader<T>' by calling 'read'. This returns an iterator over all the &T
// that this system has not read yet, and must be 'mut' in order to track which events have been read.
// Again, note you can have multiple 'EventReader', 'EventWriter', and 'EventMutator' in a given system,
// as long as the types (T) are different.
fn apply_damage_to_health(
mut dmg_events: EventReader<DealDamage>,
mut rcvd_events: EventWriter<DamageReceived>,
) {
for event in dmg_events.read() {
info!("Applying {} damage", event.amount);
if event.amount > 0 {
// Events with a 'Default' implementation can be written with 'write_default'
rcvd_events.write_default();
}
}
}
// Finally these two systems read 'DamageReceived' events.
//
// The first system will play a sound.
// The second system will spawn a particle effect.
//
// As before, events are read using an 'EventReader' by calling 'read'. This returns an iterator over all the &T
// that this system has not read yet.
fn play_damage_received_sound(mut dmg_events: EventReader<DamageReceived>) {
for _ in dmg_events.read() {
info!("Playing a sound.");
}
}
// Note that both systems receive the same 'DamageReceived' events. Any number of systems can
// receive the same event type.
fn play_damage_received_particle_effect(mut dmg_events: EventReader<DamageReceived>) {
for _ in dmg_events.read() {
info!("Playing particle effect.");
}
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
// Events must be added to the app before they can be used
// using the 'add_event' method
.add_event::<DealDamage>()
.add_event::<ArmorBlockedDamage>()
.add_event::<DamageReceived>()
.init_resource::<DamageTimer>()
// As always we must add our systems to the apps schedule.
// Here we add our systems to the schedule using 'chain()' so that they run in order
// This ensures that 'apply_armor_to_damage' runs before 'apply_damage_to_health'
// It also ensures that 'EventWriters' are used before the associated 'EventReaders'
.add_systems(
Update,
(
deal_damage_over_time,
apply_armor_to_damage,
apply_damage_to_health,
)
.chain(),
)
// These two systems are not guaranteed to run in order, nor are they guaranteed to run
// after the above chain. They may even run in parallel with each other.
// This means they may have a one frame delay in processing events compared to the above chain
// In some instances this is fine. In other cases it can be an issue. See the docs for more information
.add_systems(
Update,
(
play_damage_received_sound,
play_damage_received_particle_effect,
),
)
.run();
}

View File

@@ -0,0 +1,166 @@
//! This example demonstrates how fallible parameters can prevent their systems
//! from running if their acquiry conditions aren't met.
//!
//! Fallible system parameters include:
//! - [`Res<R>`], [`ResMut<R>`] - Resource has to exist, and the [`GLOBAL_ERROR_HANDLER`] will be called if it doesn't.
//! - [`Single<D, F>`] - There must be exactly one matching entity, but the system will be silently skipped otherwise.
//! - [`Option<Single<D, F>>`] - There must be zero or one matching entity. The system will be silently skipped if there are more.
//! - [`Populated<D, F>`] - There must be at least one matching entity, but the system will be silently skipped otherwise.
//!
//! Other system parameters, such as [`Query`], will never fail validation: returning a query with no matching entities is valid.
//!
//! The result of failed system parameter validation is determined by the [`SystemParamValidationError`] returned
//! by [`SystemParam::validate_param`] for each system parameter.
//! Each system will pass if all of its parameters are valid, or else return [`SystemParamValidationError`] for the first failing parameter.
//!
//! To learn more about setting the fallback behavior for [`SystemParamValidationError`] failures,
//! please see the `error_handling.rs` example.
//!
//! [`SystemParamValidationError`]: bevy::ecs::system::SystemParamValidationError
//! [`SystemParam::validate_param`]: bevy::ecs::system::SystemParam::validate_param
use bevy::ecs::error::{warn, GLOBAL_ERROR_HANDLER};
use bevy::prelude::*;
use rand::Rng;
fn main() {
// By default, if a parameter fail to be fetched,
// the `GLOBAL_ERROR_HANDLER` will be used to handle the error,
// which by default is set to panic.
GLOBAL_ERROR_HANDLER
.set(warn)
.expect("The error handler can only be set once, globally.");
println!();
println!("Press 'A' to add enemy ships and 'R' to remove them.");
println!("Player ship will wait for enemy ships and track one if it exists,");
println!("but will stop tracking if there are more than one.");
println!();
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, (user_input, move_targets, track_targets).chain())
// This system will always fail validation, because we never create an entity with both `Player` and `Enemy` components.
.add_systems(Update, do_nothing_fail_validation)
.run();
}
/// Enemy component stores data for movement in a circle.
#[derive(Component, Default)]
struct Enemy {
origin: Vec2,
radius: f32,
rotation: f32,
rotation_speed: f32,
}
/// Player component stores data for going after enemies.
#[derive(Component, Default)]
struct Player {
speed: f32,
rotation_speed: f32,
min_follow_radius: f32,
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// Spawn 2D camera.
commands.spawn(Camera2d);
// Spawn player.
let texture = asset_server.load("textures/simplespace/ship_C.png");
commands.spawn((
Player {
speed: 100.0,
rotation_speed: 2.0,
min_follow_radius: 50.0,
},
Sprite {
image: texture,
color: bevy::color::palettes::tailwind::BLUE_800.into(),
..Default::default()
},
Transform::from_translation(Vec3::ZERO),
));
}
/// System that reads user input.
/// If user presses 'A' we spawn a new random enemy.
/// If user presses 'R' we remove a random enemy (if any exist).
fn user_input(
mut commands: Commands,
enemies: Query<Entity, With<Enemy>>,
keyboard_input: Res<ButtonInput<KeyCode>>,
asset_server: Res<AssetServer>,
) {
let mut rng = rand::thread_rng();
if keyboard_input.just_pressed(KeyCode::KeyA) {
let texture = asset_server.load("textures/simplespace/enemy_A.png");
commands.spawn((
Enemy {
origin: Vec2::new(rng.gen_range(-200.0..200.0), rng.gen_range(-200.0..200.0)),
radius: rng.gen_range(50.0..150.0),
rotation: rng.gen_range(0.0..std::f32::consts::TAU),
rotation_speed: rng.gen_range(0.5..1.5),
},
Sprite {
image: texture,
color: bevy::color::palettes::tailwind::RED_800.into(),
..default()
},
Transform::from_translation(Vec3::ZERO),
));
}
if keyboard_input.just_pressed(KeyCode::KeyR) {
if let Some(entity) = enemies.iter().next() {
commands.entity(entity).despawn();
}
}
}
// System that moves the enemies in a circle.
// Only runs if there are enemies, due to the `Populated` parameter.
fn move_targets(mut enemies: Populated<(&mut Transform, &mut Enemy)>, time: Res<Time>) {
for (mut transform, mut target) in &mut *enemies {
target.rotation += target.rotation_speed * time.delta_secs();
transform.rotation = Quat::from_rotation_z(target.rotation);
let offset = transform.right() * target.radius;
transform.translation = target.origin.extend(0.0) + offset;
}
}
/// System that moves the player, causing them to track a single enemy.
/// The player will search for enemies if there are none.
/// If there is one, player will track it.
/// If there are too many enemies, the player will cease all action (the system will not run).
fn track_targets(
// `Single` ensures the system runs ONLY when exactly one matching entity exists.
mut player: Single<(&mut Transform, &Player)>,
// `Option<Single>` ensures that the system runs ONLY when zero or one matching entity exists.
enemy: Option<Single<&Transform, (With<Enemy>, Without<Player>)>>,
time: Res<Time>,
) {
let (player_transform, player) = &mut *player;
if let Some(enemy_transform) = enemy {
// Enemy found, rotate and move towards it.
let delta = enemy_transform.translation - player_transform.translation;
let distance = delta.length();
let front = delta / distance;
let up = Vec3::Z;
let side = front.cross(up);
player_transform.rotation = Quat::from_mat3(&Mat3::from_cols(side, front, up));
let max_step = distance - player.min_follow_radius;
if 0.0 < max_step {
let velocity = (player.speed * time.delta_secs()).min(max_step);
player_transform.translation += front * velocity;
}
} else {
// 0 or multiple enemies found, keep searching.
player_transform.rotate_axis(Dir3::Z, player.rotation_speed * time.delta_secs());
}
}
/// This system always fails param validation, because we never
/// create an entity with both [`Player`] and [`Enemy`] components.
fn do_nothing_fail_validation(_: Single<(), (With<Player>, With<Enemy>)>) {}

View File

@@ -0,0 +1,40 @@
//! Shows how to create systems that run every fixed timestep, rather than every tick.
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
// this system will run once every update (it should match your screen's refresh rate)
.add_systems(Update, frame_update)
// add our system to the fixed timestep schedule
.add_systems(FixedUpdate, fixed_update)
// configure our fixed timestep schedule to run twice a second
.insert_resource(Time::<Fixed>::from_seconds(0.5))
.run();
}
fn frame_update(mut last_time: Local<f32>, time: Res<Time>) {
// Default `Time` is `Time<Virtual>` here
info!(
"time since last frame_update: {}",
time.elapsed_secs() - *last_time
);
*last_time = time.elapsed_secs();
}
fn fixed_update(mut last_time: Local<f32>, time: Res<Time>, fixed_time: Res<Time<Fixed>>) {
// Default `Time`is `Time<Fixed>` here
info!(
"time since last fixed_update: {}\n",
time.elapsed_secs() - *last_time
);
info!("fixed timestep: {}\n", time.delta_secs());
// If we want to see the overstep, we need to access `Time<Fixed>` specifically
info!(
"time accrued toward next fixed_update: {}\n",
fixed_time.overstep().as_secs_f32()
);
*last_time = time.elapsed_secs();
}

View File

@@ -0,0 +1,89 @@
//! Generic types allow us to reuse logic across many related systems,
//! allowing us to specialize our function's behavior based on which type (or types) are passed in.
//!
//! This is commonly useful for working on related components or resources,
//! where we want to have unique types for querying purposes but want them all to work the same way.
//! This is particularly powerful when combined with user-defined traits to add more functionality to these related types.
//! Remember to insert a specialized copy of the system into the schedule for each type that you want to operate on!
//!
//! For more advice on working with generic types in Rust, check out <https://doc.rust-lang.org/book/ch10-01-syntax.html>
//! or <https://doc.rust-lang.org/rust-by-example/generics.html>
use bevy::prelude::*;
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, States)]
enum AppState {
#[default]
MainMenu,
InGame,
}
#[derive(Component)]
struct TextToPrint(String);
#[derive(Component, Deref, DerefMut)]
struct PrinterTick(Timer);
#[derive(Component)]
struct MenuClose;
#[derive(Component)]
struct LevelUnload;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_state::<AppState>()
.add_systems(Startup, setup_system)
.add_systems(
Update,
(
print_text_system,
transition_to_in_game_system.run_if(in_state(AppState::MainMenu)),
),
)
// Cleanup systems.
// Pass in the types your system should operate on using the ::<T> (turbofish) syntax
.add_systems(OnExit(AppState::MainMenu), cleanup_system::<MenuClose>)
.add_systems(OnExit(AppState::InGame), cleanup_system::<LevelUnload>)
.run();
}
fn setup_system(mut commands: Commands) {
commands.spawn((
PrinterTick(Timer::from_seconds(1.0, TimerMode::Repeating)),
TextToPrint("I will print until you press space.".to_string()),
MenuClose,
));
commands.spawn((
PrinterTick(Timer::from_seconds(1.0, TimerMode::Repeating)),
TextToPrint("I will always print".to_string()),
LevelUnload,
));
}
fn print_text_system(time: Res<Time>, mut query: Query<(&mut PrinterTick, &TextToPrint)>) {
for (mut timer, text) in &mut query {
if timer.tick(time.delta()).just_finished() {
info!("{}", text.0);
}
}
}
fn transition_to_in_game_system(
mut next_state: ResMut<NextState<AppState>>,
keyboard_input: Res<ButtonInput<KeyCode>>,
) {
if keyboard_input.pressed(KeyCode::Space) {
next_state.set(AppState::InGame);
}
}
// Type arguments on functions come after the function name, but before ordinary arguments.
// Here, the `Component` trait is a trait bound on T, our generic type
fn cleanup_system<T: Component>(mut commands: Commands, query: Query<Entity, With<T>>) {
for e in &query {
commands.entity(e).despawn();
}
}

93
vendor/bevy/examples/ecs/hierarchy.rs vendored Normal file
View File

@@ -0,0 +1,93 @@
//! Demonstrates techniques for creating a hierarchy of parent and child entities.
//!
//! When [`DefaultPlugins`] are added to your app, systems are automatically added to propagate
//! [`Transform`] and [`Visibility`] from parents to children down the hierarchy,
//! resulting in a final [`GlobalTransform`] and [`InheritedVisibility`] component for each entity.
use std::f32::consts::*;
use bevy::{color::palettes::css::*, prelude::*};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, rotate)
.run();
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2d);
let texture = asset_server.load("branding/icon.png");
// Spawn a root entity with no parent
let parent = commands
.spawn((
Sprite::from_image(texture.clone()),
Transform::from_scale(Vec3::splat(0.75)),
))
// With that entity as a parent, run a lambda that spawns its children
.with_children(|parent| {
// parent is a ChildSpawnerCommands, which has a similar API to Commands
parent.spawn((
Transform::from_xyz(250.0, 0.0, 0.0).with_scale(Vec3::splat(0.75)),
Sprite {
image: texture.clone(),
color: BLUE.into(),
..default()
},
));
})
// Store parent entity for next sections
.id();
// Another way is to use the add_child function to add children after the parent
// entity has already been spawned.
let child = commands
.spawn((
Sprite {
image: texture,
color: LIME.into(),
..default()
},
Transform::from_xyz(0.0, 250.0, 0.0).with_scale(Vec3::splat(0.75)),
))
.id();
// Add child to the parent.
commands.entity(parent).add_child(child);
}
// A simple system to rotate the root entity, and rotate all its children separately
fn rotate(
mut commands: Commands,
time: Res<Time>,
mut parents_query: Query<(Entity, &Children), With<Sprite>>,
mut transform_query: Query<&mut Transform, With<Sprite>>,
) {
for (parent, children) in &mut parents_query {
if let Ok(mut transform) = transform_query.get_mut(parent) {
transform.rotate_z(-PI / 2. * time.delta_secs());
}
// To iterate through the entities children, just treat the Children component as a Vec
// Alternatively, you could query entities that have a ChildOf component
for child in children {
if let Ok(mut transform) = transform_query.get_mut(*child) {
transform.rotate_z(PI * time.delta_secs());
}
}
// To demonstrate removing children, we'll remove a child after a couple of seconds.
if time.elapsed_secs() >= 2.0 && children.len() == 2 {
let child = children.last().unwrap();
commands.entity(*child).despawn();
}
if time.elapsed_secs() >= 4.0 {
// This will remove the entity from its parent's list of children, as well as despawn
// any children the entity has.
commands.entity(parent).despawn();
}
}
}

View File

@@ -0,0 +1,204 @@
//! This example demonstrates immutable components.
use bevy::{
ecs::{
component::{
ComponentCloneBehavior, ComponentDescriptor, ComponentId, HookContext, StorageType,
},
world::DeferredWorld,
},
platform::collections::HashMap,
prelude::*,
ptr::OwningPtr,
};
use core::alloc::Layout;
/// This component is mutable, the default case. This is indicated by components
/// implementing [`Component`] where [`Component::Mutability`] is [`Mutable`](bevy::ecs::component::Mutable).
#[derive(Component)]
pub struct MyMutableComponent(bool);
/// This component is immutable. Once inserted into the ECS, it can only be viewed,
/// or removed. Replacement is also permitted, as this is equivalent to removal
/// and insertion.
///
/// Adding the `#[component(immutable)]` attribute prevents the implementation of [`Component<Mutability = Mutable>`]
/// in the derive macro.
#[derive(Component)]
#[component(immutable)]
pub struct MyImmutableComponent(bool);
fn demo_1(world: &mut World) {
// Immutable components can be inserted just like mutable components.
let mut entity = world.spawn((MyMutableComponent(false), MyImmutableComponent(false)));
// But where mutable components can be mutated...
let mut my_mutable_component = entity.get_mut::<MyMutableComponent>().unwrap();
my_mutable_component.0 = true;
// ...immutable ones cannot. The below fails to compile as `MyImmutableComponent`
// is declared as immutable.
// let mut my_immutable_component = entity.get_mut::<MyImmutableComponent>().unwrap();
// Instead, you could take or replace the immutable component to update its value.
let mut my_immutable_component = entity.take::<MyImmutableComponent>().unwrap();
my_immutable_component.0 = true;
entity.insert(my_immutable_component);
}
/// This is an example of a component like [`Name`](bevy::prelude::Name), but immutable.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Component, Reflect)]
#[reflect(Hash, Component)]
#[component(
immutable,
// Since this component is immutable, we can fully capture all mutations through
// these component hooks. This allows for keeping other parts of the ECS synced
// to a component's value at all times.
on_insert = on_insert_name,
on_replace = on_replace_name,
)]
pub struct Name(pub &'static str);
/// This index allows for O(1) lookups of an [`Entity`] by its [`Name`].
#[derive(Resource, Default)]
struct NameIndex {
name_to_entity: HashMap<Name, Entity>,
}
impl NameIndex {
fn get_entity(&self, name: &'static str) -> Option<Entity> {
self.name_to_entity.get(&Name(name)).copied()
}
}
/// When a [`Name`] is inserted, we will add it to our [`NameIndex`].
///
/// Since all mutations to [`Name`] are captured by hooks, we know it is not currently
/// inserted in the index, and its value will not change without triggering a hook.
fn on_insert_name(mut world: DeferredWorld<'_>, HookContext { entity, .. }: HookContext) {
let Some(&name) = world.entity(entity).get::<Name>() else {
unreachable!("OnInsert hook guarantees `Name` is available on entity")
};
let Some(mut index) = world.get_resource_mut::<NameIndex>() else {
return;
};
index.name_to_entity.insert(name, entity);
}
/// When a [`Name`] is removed or replaced, remove it from our [`NameIndex`].
///
/// Since all mutations to [`Name`] are captured by hooks, we know it is currently
/// inserted in the index.
fn on_replace_name(mut world: DeferredWorld<'_>, HookContext { entity, .. }: HookContext) {
let Some(&name) = world.entity(entity).get::<Name>() else {
unreachable!("OnReplace hook guarantees `Name` is available on entity")
};
let Some(mut index) = world.get_resource_mut::<NameIndex>() else {
return;
};
index.name_to_entity.remove(&name);
}
fn demo_2(world: &mut World) {
// Setup our name index
world.init_resource::<NameIndex>();
// Spawn some entities!
let alyssa = world.spawn(Name("Alyssa")).id();
let javier = world.spawn(Name("Javier")).id();
// Check our index
let index = world.resource::<NameIndex>();
assert_eq!(index.get_entity("Alyssa"), Some(alyssa));
assert_eq!(index.get_entity("Javier"), Some(javier));
// Changing the name of an entity is also fully capture by our index
world.entity_mut(javier).insert(Name("Steven"));
// Javier changed their name to Steven
let steven = javier;
// Check our index
let index = world.resource::<NameIndex>();
assert_eq!(index.get_entity("Javier"), None);
assert_eq!(index.get_entity("Steven"), Some(steven));
}
/// This example demonstrates how to work with _dynamic_ immutable components.
#[expect(
unsafe_code,
reason = "Unsafe code is needed to work with dynamic components"
)]
fn demo_3(world: &mut World) {
// This is a list of dynamic components we will create.
// The first item is the name of the component, and the second is the size
// in bytes.
let my_dynamic_components = [("Foo", 1), ("Bar", 2), ("Baz", 4)];
// This pipeline takes our component descriptions, registers them, and gets
// their ComponentId's.
let my_registered_components = my_dynamic_components
.into_iter()
.map(|(name, size)| {
// SAFETY:
// - No drop command is required
// - The component will store [u8; size], which is Send + Sync
let descriptor = unsafe {
ComponentDescriptor::new_with_layout(
name.to_string(),
StorageType::Table,
Layout::array::<u8>(size).unwrap(),
None,
false,
ComponentCloneBehavior::Default,
)
};
(name, size, descriptor)
})
.map(|(name, size, descriptor)| {
let component_id = world.register_component_with_descriptor(descriptor);
(name, size, component_id)
})
.collect::<Vec<(&str, usize, ComponentId)>>();
// Now that our components are registered, let's add them to an entity
let mut entity = world.spawn_empty();
for (_name, size, component_id) in &my_registered_components {
// We're just storing some zeroes for the sake of demonstration.
let data = core::iter::repeat_n(0, *size).collect::<Vec<u8>>();
OwningPtr::make(data, |ptr| {
// SAFETY:
// - ComponentId has been taken from the same world
// - Array is created to the layout specified in the world
unsafe {
entity.insert_by_id(*component_id, ptr);
}
});
}
for (_name, _size, component_id) in &my_registered_components {
// With immutable components, we can read the values...
assert!(entity.get_by_id(*component_id).is_ok());
// ...but we cannot gain a mutable reference.
assert!(entity.get_mut_by_id(*component_id).is_err());
// Instead, you must either remove or replace the value.
}
}
fn main() {
App::new()
.add_systems(Startup, demo_1)
.add_systems(Startup, demo_2)
.add_systems(Startup, demo_3)
.run();
}

View File

@@ -0,0 +1,159 @@
//! Shows how to iterate over combinations of query results.
use bevy::{color::palettes::css::ORANGE_RED, math::FloatPow, prelude::*};
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.insert_resource(ClearColor(Color::BLACK))
.add_systems(Startup, generate_bodies)
.add_systems(FixedUpdate, (interact_bodies, integrate))
.add_systems(Update, look_at_star)
.run();
}
const GRAVITY_CONSTANT: f32 = 0.001;
const NUM_BODIES: usize = 100;
#[derive(Component, Default)]
struct Mass(f32);
#[derive(Component, Default)]
struct Acceleration(Vec3);
#[derive(Component, Default)]
struct LastPos(Vec3);
#[derive(Component)]
struct Star;
#[derive(Bundle, Default)]
struct BodyBundle {
mesh: Mesh3d,
material: MeshMaterial3d<StandardMaterial>,
mass: Mass,
last_pos: LastPos,
acceleration: Acceleration,
}
fn generate_bodies(
time: Res<Time<Fixed>>,
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let mesh = meshes.add(Sphere::new(1.0).mesh().ico(3).unwrap());
let color_range = 0.5..1.0;
let vel_range = -0.5..0.5;
// We're seeding the PRNG here to make this example deterministic for testing purposes.
// This isn't strictly required in practical use unless you need your app to be deterministic.
let mut rng = ChaCha8Rng::seed_from_u64(19878367467713);
for _ in 0..NUM_BODIES {
let radius: f32 = rng.gen_range(0.1..0.7);
let mass_value = FloatPow::cubed(radius) * 10.;
let position = Vec3::new(
rng.gen_range(-1.0..1.0),
rng.gen_range(-1.0..1.0),
rng.gen_range(-1.0..1.0),
)
.normalize()
* ops::cbrt(rng.gen_range(0.2f32..1.0))
* 15.;
commands.spawn((
BodyBundle {
mesh: Mesh3d(mesh.clone()),
material: MeshMaterial3d(materials.add(Color::srgb(
rng.gen_range(color_range.clone()),
rng.gen_range(color_range.clone()),
rng.gen_range(color_range.clone()),
))),
mass: Mass(mass_value),
acceleration: Acceleration(Vec3::ZERO),
last_pos: LastPos(
position
- Vec3::new(
rng.gen_range(vel_range.clone()),
rng.gen_range(vel_range.clone()),
rng.gen_range(vel_range.clone()),
) * time.timestep().as_secs_f32(),
),
},
Transform {
translation: position,
scale: Vec3::splat(radius),
..default()
},
));
}
// add bigger "star" body in the center
let star_radius = 1.;
commands
.spawn((
BodyBundle {
mesh: Mesh3d(meshes.add(Sphere::new(1.0).mesh().ico(5).unwrap())),
material: MeshMaterial3d(materials.add(StandardMaterial {
base_color: ORANGE_RED.into(),
emissive: LinearRgba::from(ORANGE_RED) * 2.,
..default()
})),
mass: Mass(500.0),
..default()
},
Transform::from_scale(Vec3::splat(star_radius)),
Star,
))
.with_child(PointLight {
color: Color::WHITE,
range: 100.0,
radius: star_radius,
..default()
});
commands.spawn((
Camera3d::default(),
Transform::from_xyz(0.0, 10.5, -30.0).looking_at(Vec3::ZERO, Vec3::Y),
));
}
fn interact_bodies(mut query: Query<(&Mass, &GlobalTransform, &mut Acceleration)>) {
let mut iter = query.iter_combinations_mut();
while let Some([(Mass(m1), transform1, mut acc1), (Mass(m2), transform2, mut acc2)]) =
iter.fetch_next()
{
let delta = transform2.translation() - transform1.translation();
let distance_sq: f32 = delta.length_squared();
let f = GRAVITY_CONSTANT / distance_sq;
let force_unit_mass = delta * f;
acc1.0 += force_unit_mass * *m2;
acc2.0 -= force_unit_mass * *m1;
}
}
fn integrate(time: Res<Time>, mut query: Query<(&mut Acceleration, &mut Transform, &mut LastPos)>) {
let dt_sq = time.delta_secs() * time.delta_secs();
for (mut acceleration, mut transform, mut last_pos) in &mut query {
// verlet integration
// x(t+dt) = 2x(t) - x(t-dt) + a(t)dt^2 + O(dt^4)
let new_pos = transform.translation * 2.0 - last_pos.0 + acceleration.0 * dt_sq;
acceleration.0 = Vec3::ZERO;
last_pos.0 = transform.translation;
transform.translation = new_pos;
}
}
fn look_at_star(
mut camera: Single<&mut Transform, (With<Camera>, Without<Star>)>,
star: Single<&Transform, With<Star>>,
) {
let new_rotation = camera
.looking_at(star.translation, Vec3::Y)
.rotation
.lerp(camera.rotation, 0.1);
camera.rotation = new_rotation;
}

View File

@@ -0,0 +1,100 @@
//! By default, Bevy systems run in parallel with each other.
//! Unless the order is explicitly specified, their relative order is nondeterministic.
//!
//! In many cases, this doesn't matter and is in fact desirable!
//! Consider two systems, one which writes to resource A, and the other which writes to resource B.
//! By allowing their order to be arbitrary, we can evaluate them greedily, based on the data that is free.
//! Because their data accesses are **compatible**, there is no **observable** difference created based on the order they are run.
//!
//! But if instead we have two systems mutating the same data, or one reading it and the other mutating,
//! then the actual observed value will vary based on the nondeterministic order of evaluation.
//! These observable conflicts are called **system execution order ambiguities**.
//!
//! This example demonstrates how you might detect and resolve (or silence) these ambiguities.
use bevy::{
ecs::schedule::{LogLevel, ScheduleBuildSettings},
prelude::*,
};
fn main() {
App::new()
// We can modify the reporting strategy for system execution order ambiguities on a per-schedule basis.
// You must do this for each schedule you want to inspect; child schedules executed within an inspected
// schedule do not inherit this modification.
.edit_schedule(Update, |schedule| {
schedule.set_build_settings(ScheduleBuildSettings {
ambiguity_detection: LogLevel::Warn,
..default()
});
})
.init_resource::<A>()
.init_resource::<B>()
.add_systems(
Update,
(
// This pair of systems has an ambiguous order,
// as their data access conflicts, and there's no order between them.
reads_a,
writes_a,
// This pair of systems has conflicting data access,
// but it's resolved with an explicit ordering:
// the .after relationship here means that we will always double after adding.
adds_one_to_b,
doubles_b.after(adds_one_to_b),
// This system isn't ambiguous with adds_one_to_b,
// due to the transitive ordering created by our constraints:
// if A is before B is before C, then A must be before C as well.
reads_b.after(doubles_b),
// This system will conflict with all of our writing systems
// but we've silenced its ambiguity with adds_one_to_b.
// This should only be done in the case of clear false positives:
// leave a comment in your code justifying the decision!
reads_a_and_b.ambiguous_with(adds_one_to_b),
),
)
// Be mindful, internal ambiguities are reported too!
// If there are any ambiguities due solely to DefaultPlugins,
// or between DefaultPlugins and any of your third party plugins,
// please file a bug with the repo responsible!
// Only *you* can prevent nondeterministic bugs due to greedy parallelism.
.add_plugins(DefaultPlugins)
.run();
}
#[derive(Resource, Debug, Default)]
struct A(usize);
#[derive(Resource, Debug, Default)]
struct B(usize);
// Data access is determined solely on the basis of the types of the system's parameters
// Every implementation of the `SystemParam` and `WorldQuery` traits must declare which data is used
// and whether or not it is mutably accessed.
fn reads_a(_a: Res<A>) {}
fn writes_a(mut a: ResMut<A>) {
a.0 += 1;
}
fn adds_one_to_b(mut b: ResMut<B>) {
b.0 = b.0.saturating_add(1);
}
fn doubles_b(mut b: ResMut<B>) {
// This will overflow pretty rapidly otherwise
b.0 = b.0.saturating_mul(2);
}
fn reads_b(b: Res<B>) {
// This invariant is always true,
// because we've fixed the system order so doubling always occurs after adding.
assert!((b.0 % 2 == 0) || (b.0 == usize::MAX));
}
fn reads_a_and_b(a: Res<A>, b: Res<B>) {
// Only display the first few steps to avoid burying the ambiguities in the console
if b.0 < 10 {
info!("{}, {}", a.0, b.0);
}
}

View File

@@ -0,0 +1,125 @@
//! Demonstrates how to propagate events through the hierarchy with observers.
use std::time::Duration;
use bevy::{log::LogPlugin, prelude::*, time::common_conditions::on_timer};
use rand::{seq::IteratorRandom, thread_rng, Rng};
fn main() {
App::new()
.add_plugins((MinimalPlugins, LogPlugin::default()))
.add_systems(Startup, setup)
.add_systems(
Update,
attack_armor.run_if(on_timer(Duration::from_millis(200))),
)
// Add a global observer that will emit a line whenever an attack hits an entity.
.add_observer(attack_hits)
.run();
}
// In this example, we spawn a goblin wearing different pieces of armor. Each piece of armor
// is represented as a child entity, with an `Armor` component.
//
// We're going to model how attack damage can be partially blocked by the goblin's armor using
// event bubbling. Our events will target the armor, and if the armor isn't strong enough to block
// the attack it will continue up and hit the goblin.
fn setup(mut commands: Commands) {
commands
.spawn((Name::new("Goblin"), HitPoints(50)))
.observe(take_damage)
.with_children(|parent| {
parent
.spawn((Name::new("Helmet"), Armor(5)))
.observe(block_attack);
parent
.spawn((Name::new("Socks"), Armor(10)))
.observe(block_attack);
parent
.spawn((Name::new("Shirt"), Armor(15)))
.observe(block_attack);
});
}
// This event represents an attack we want to "bubble" up from the armor to the goblin.
//
// We enable propagation by adding the event attribute and specifying two important pieces of information.
//
// - **traversal:**
// Which component we want to propagate along. In this case, we want to "bubble" (meaning propagate
// from child to parent) so we use the `ChildOf` component for propagation. The component supplied
// must implement the `Traversal` trait.
//
// - **auto_propagate:**
// We can also choose whether or not this event will propagate by default when triggered. If this is
// false, it will only propagate following a call to `Trigger::propagate(true)`.
#[derive(Clone, Component, Event)]
#[event(traversal = &'static ChildOf, auto_propagate)]
struct Attack {
damage: u16,
}
/// An entity that can take damage.
#[derive(Component, Deref, DerefMut)]
struct HitPoints(u16);
/// For damage to reach the wearer, it must exceed the armor.
#[derive(Component, Deref)]
struct Armor(u16);
/// A normal bevy system that attacks a piece of the goblin's armor on a timer.
fn attack_armor(entities: Query<Entity, With<Armor>>, mut commands: Commands) {
let mut rng = thread_rng();
if let Some(target) = entities.iter().choose(&mut rng) {
let damage = rng.gen_range(1..20);
commands.trigger_targets(Attack { damage }, target);
info!("⚔️ Attack for {} damage", damage);
}
}
fn attack_hits(trigger: Trigger<Attack>, name: Query<&Name>) {
if let Ok(name) = name.get(trigger.target()) {
info!("Attack hit {}", name);
}
}
/// A callback placed on [`Armor`], checking if it absorbed all the [`Attack`] damage.
fn block_attack(mut trigger: Trigger<Attack>, armor: Query<(&Armor, &Name)>) {
let (armor, name) = armor.get(trigger.target()).unwrap();
let attack = trigger.event_mut();
let damage = attack.damage.saturating_sub(**armor);
if damage > 0 {
info!("🩸 {} damage passed through {}", damage, name);
// The attack isn't stopped by the armor. We reduce the damage of the attack, and allow
// it to continue on to the goblin.
attack.damage = damage;
} else {
info!("🛡️ {} damage blocked by {}", attack.damage, name);
// Armor stopped the attack, the event stops here.
trigger.propagate(false);
info!("(propagation halted early)\n");
}
}
/// A callback on the armor wearer, triggered when a piece of armor is not able to block an attack,
/// or the wearer is attacked directly.
fn take_damage(
trigger: Trigger<Attack>,
mut hp: Query<(&mut HitPoints, &Name)>,
mut commands: Commands,
mut app_exit: EventWriter<AppExit>,
) {
let attack = trigger.event();
let (mut hp, name) = hp.get_mut(trigger.target()).unwrap();
**hp = hp.saturating_sub(attack.damage);
if **hp > 0 {
info!("{} has {:.1} HP", name, hp.0);
} else {
warn!("💀 {} has died a gruesome death", name);
commands.entity(trigger.target()).despawn();
app_exit.write(AppExit::Success);
}
info!("(propagation reached root)\n");
}

219
vendor/bevy/examples/ecs/observers.rs vendored Normal file
View File

@@ -0,0 +1,219 @@
//! Demonstrates how to observe life-cycle triggers as well as define custom ones.
use bevy::{
platform::collections::{HashMap, HashSet},
prelude::*,
};
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_resource::<SpatialIndex>()
.add_systems(Startup, setup)
.add_systems(Update, (draw_shapes, handle_click))
// Observers are systems that run when an event is "triggered". This observer runs whenever
// `ExplodeMines` is triggered.
.add_observer(
|trigger: Trigger<ExplodeMines>,
mines: Query<&Mine>,
index: Res<SpatialIndex>,
mut commands: Commands| {
// You can access the trigger data via the `Observer`
let event = trigger.event();
// Access resources
for e in index.get_nearby(event.pos) {
// Run queries
let mine = mines.get(e).unwrap();
if mine.pos.distance(event.pos) < mine.size + event.radius {
// And queue commands, including triggering additional events
// Here we trigger the `Explode` event for entity `e`
commands.trigger_targets(Explode, e);
}
}
},
)
// This observer runs whenever the `Mine` component is added to an entity, and places it in a simple spatial index.
.add_observer(on_add_mine)
// This observer runs whenever the `Mine` component is removed from an entity (including despawning it)
// and removes it from the spatial index.
.add_observer(on_remove_mine)
.run();
}
#[derive(Component)]
struct Mine {
pos: Vec2,
size: f32,
}
impl Mine {
fn random(rand: &mut ChaCha8Rng) -> Self {
Mine {
pos: Vec2::new(
(rand.r#gen::<f32>() - 0.5) * 1200.0,
(rand.r#gen::<f32>() - 0.5) * 600.0,
),
size: 4.0 + rand.r#gen::<f32>() * 16.0,
}
}
}
#[derive(Event)]
struct ExplodeMines {
pos: Vec2,
radius: f32,
}
#[derive(Event)]
struct Explode;
fn setup(mut commands: Commands) {
commands.spawn(Camera2d);
commands.spawn((
Text::new(
"Click on a \"Mine\" to trigger it.\n\
When it explodes it will trigger all overlapping mines.",
),
Node {
position_type: PositionType::Absolute,
top: Val::Px(12.),
left: Val::Px(12.),
..default()
},
));
let mut rng = ChaCha8Rng::seed_from_u64(19878367467713);
commands
.spawn(Mine::random(&mut rng))
// Observers can watch for events targeting a specific entity.
// This will create a new observer that runs whenever the Explode event
// is triggered for this spawned entity.
.observe(explode_mine);
// We want to spawn a bunch of mines. We could just call the code above for each of them.
// That would create a new observer instance for every Mine entity. Having duplicate observers
// generally isn't worth worrying about as the overhead is low. But if you want to be maximally efficient,
// you can reuse observers across entities.
//
// First, observers are actually just entities with the Observer component! The `observe()` functions
// you've seen so far in this example are just shorthand for manually spawning an observer.
let mut observer = Observer::new(explode_mine);
// As we spawn entities, we can make this observer watch each of them:
for _ in 0..1000 {
let entity = commands.spawn(Mine::random(&mut rng)).id();
observer.watch_entity(entity);
}
// By spawning the Observer component, it becomes active!
commands.spawn(observer);
}
fn on_add_mine(
trigger: Trigger<OnAdd, Mine>,
query: Query<&Mine>,
mut index: ResMut<SpatialIndex>,
) {
let mine = query.get(trigger.target()).unwrap();
let tile = (
(mine.pos.x / CELL_SIZE).floor() as i32,
(mine.pos.y / CELL_SIZE).floor() as i32,
);
index.map.entry(tile).or_default().insert(trigger.target());
}
// Remove despawned mines from our index
fn on_remove_mine(
trigger: Trigger<OnRemove, Mine>,
query: Query<&Mine>,
mut index: ResMut<SpatialIndex>,
) {
let mine = query.get(trigger.target()).unwrap();
let tile = (
(mine.pos.x / CELL_SIZE).floor() as i32,
(mine.pos.y / CELL_SIZE).floor() as i32,
);
index.map.entry(tile).and_modify(|set| {
set.remove(&trigger.target());
});
}
fn explode_mine(trigger: Trigger<Explode>, query: Query<&Mine>, mut commands: Commands) {
// If a triggered event is targeting a specific entity you can access it with `.target()`
let id = trigger.target();
let Ok(mut entity) = commands.get_entity(id) else {
return;
};
info!("Boom! {} exploded.", id.index());
entity.despawn();
let mine = query.get(id).unwrap();
// Trigger another explosion cascade.
commands.trigger(ExplodeMines {
pos: mine.pos,
radius: mine.size,
});
}
// Draw a circle for each mine using `Gizmos`
fn draw_shapes(mut gizmos: Gizmos, mines: Query<&Mine>) {
for mine in &mines {
gizmos.circle_2d(
mine.pos,
mine.size,
Color::hsl((mine.size - 4.0) / 16.0 * 360.0, 1.0, 0.8),
);
}
}
// Trigger `ExplodeMines` at the position of a given click
fn handle_click(
mouse_button_input: Res<ButtonInput<MouseButton>>,
camera: Single<(&Camera, &GlobalTransform)>,
windows: Query<&Window>,
mut commands: Commands,
) {
let Ok(windows) = windows.single() else {
return;
};
let (camera, camera_transform) = *camera;
if let Some(pos) = windows
.cursor_position()
.and_then(|cursor| camera.viewport_to_world(camera_transform, cursor).ok())
.map(|ray| ray.origin.truncate())
{
if mouse_button_input.just_pressed(MouseButton::Left) {
commands.trigger(ExplodeMines { pos, radius: 1.0 });
}
}
}
#[derive(Resource, Default)]
struct SpatialIndex {
map: HashMap<(i32, i32), HashSet<Entity>>,
}
/// Cell size has to be bigger than any `TriggerMine::radius`
const CELL_SIZE: f32 = 64.0;
impl SpatialIndex {
// Lookup all entities within adjacent cells of our spatial index
fn get_nearby(&self, pos: Vec2) -> Vec<Entity> {
let tile = (
(pos.x / CELL_SIZE).floor() as i32,
(pos.y / CELL_SIZE).floor() as i32,
);
let mut nearby = Vec::new();
for x in -1..2 {
for y in -1..2 {
if let Some(mines) = self.map.get(&(tile.0 + x, tile.1 + y)) {
nearby.extend(mines.iter());
}
}
}
nearby
}
}

View File

@@ -0,0 +1,112 @@
//! Demonstrates the use of "one-shot systems", which run once when triggered.
//!
//! These can be useful to help structure your logic in a push-based fashion,
//! reducing the overhead of running extremely rarely run systems
//! and improving schedule flexibility.
//!
//! See the [`World::run_system`](World::run_system) or
//! [`World::run_system_once`](World#method.run_system_once_with)
//! docs for more details.
use bevy::{
ecs::system::{RunSystemOnce, SystemId},
prelude::*,
};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(
Startup,
(
setup_ui,
setup_with_commands,
setup_with_world.after(setup_ui), // since we run `system_b` once in world it needs to run after `setup_ui`
),
)
.add_systems(Update, (trigger_system, evaluate_callbacks).chain())
.run();
}
#[derive(Component)]
struct Callback(SystemId);
#[derive(Component)]
struct Triggered;
#[derive(Component)]
struct A;
#[derive(Component)]
struct B;
fn setup_with_commands(mut commands: Commands) {
let system_id = commands.register_system(system_a);
commands.spawn((Callback(system_id), A));
}
fn setup_with_world(world: &mut World) {
// We can run it once manually
world.run_system_once(system_b).unwrap();
// Or with a Callback
let system_id = world.register_system(system_b);
world.spawn((Callback(system_id), B));
}
/// Tag entities that have callbacks we want to run with the `Triggered` component.
fn trigger_system(
mut commands: Commands,
query_a: Single<Entity, With<A>>,
query_b: Single<Entity, With<B>>,
input: Res<ButtonInput<KeyCode>>,
) {
if input.just_pressed(KeyCode::KeyA) {
let entity = *query_a;
commands.entity(entity).insert(Triggered);
}
if input.just_pressed(KeyCode::KeyB) {
let entity = *query_b;
commands.entity(entity).insert(Triggered);
}
}
/// Runs the systems associated with each `Callback` component if the entity also has a `Triggered` component.
///
/// This could be done in an exclusive system rather than using `Commands` if preferred.
fn evaluate_callbacks(query: Query<(Entity, &Callback), With<Triggered>>, mut commands: Commands) {
for (entity, callback) in query.iter() {
commands.run_system(callback.0);
commands.entity(entity).remove::<Triggered>();
}
}
fn system_a(entity_a: Single<Entity, With<Text>>, mut writer: TextUiWriter) {
*writer.text(*entity_a, 3) = String::from("A");
info!("A: One shot system registered with Commands was triggered");
}
fn system_b(entity_b: Single<Entity, With<Text>>, mut writer: TextUiWriter) {
*writer.text(*entity_b, 3) = String::from("B");
info!("B: One shot system registered with World was triggered");
}
fn setup_ui(mut commands: Commands) {
commands.spawn(Camera2d);
commands
.spawn((
Text::default(),
TextLayout::new_with_justify(JustifyText::Center),
Node {
align_self: AlignSelf::Center,
justify_self: JustifySelf::Center,
..default()
},
))
.with_children(|p| {
p.spawn(TextSpan::new("Press A or B to trigger a one-shot system\n"));
p.spawn(TextSpan::new("Last Triggered: "));
p.spawn((
TextSpan::new("-"),
TextColor(bevy::color::palettes::css::ORANGE.into()),
));
});
}

View File

@@ -0,0 +1,79 @@
//! Illustrates parallel queries with `ParallelIterator`.
use bevy::{ecs::batching::BatchingStrategy, prelude::*};
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
#[derive(Component, Deref)]
struct Velocity(Vec2);
fn spawn_system(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2d);
let texture = asset_server.load("branding/icon.png");
// We're seeding the PRNG here to make this example deterministic for testing purposes.
// This isn't strictly required in practical use unless you need your app to be deterministic.
let mut rng = ChaCha8Rng::seed_from_u64(19878367467713);
for z in 0..128 {
commands.spawn((
Sprite::from_image(texture.clone()),
Transform::from_scale(Vec3::splat(0.1))
.with_translation(Vec2::splat(0.0).extend(z as f32)),
Velocity(20.0 * Vec2::new(rng.r#gen::<f32>() - 0.5, rng.r#gen::<f32>() - 0.5)),
));
}
}
// Move sprites according to their velocity
fn move_system(mut sprites: Query<(&mut Transform, &Velocity)>) {
// Compute the new location of each sprite in parallel on the
// ComputeTaskPool
//
// This example is only for demonstrative purposes. Using a
// ParallelIterator for an inexpensive operation like addition on only 128
// elements will not typically be faster than just using a normal Iterator.
// See the ParallelIterator documentation for more information on when
// to use or not use ParallelIterator over a normal Iterator.
sprites
.par_iter_mut()
.for_each(|(mut transform, velocity)| {
transform.translation += velocity.extend(0.0);
});
}
// Bounce sprites outside the window
fn bounce_system(window: Query<&Window>, mut sprites: Query<(&Transform, &mut Velocity)>) {
let Ok(window) = window.single() else {
return;
};
let width = window.width();
let height = window.height();
let left = width / -2.0;
let right = width / 2.0;
let bottom = height / -2.0;
let top = height / 2.0;
// The default batch size can also be overridden.
// In this case a batch size of 32 is chosen to limit the overhead of
// ParallelIterator, since negating a vector is very inexpensive.
sprites
.par_iter_mut()
.batching_strategy(BatchingStrategy::fixed(32))
.for_each(|(transform, mut v)| {
if !(left < transform.translation.x
&& transform.translation.x < right
&& bottom < transform.translation.y
&& transform.translation.y < top)
{
// For simplicity, just reverse the velocity; don't use realistic bounces
v.0 = -v.0;
}
});
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, spawn_system)
.add_systems(Update, (move_system, bounce_system))
.run();
}

View File

@@ -0,0 +1,215 @@
//! Entities generally don't exist in isolation. Instead, they are related to other entities in various ways.
//! While Bevy comes with a built-in [`ChildOf`]/[`Children`] relationship
//! (which enables transform and visibility propagation),
//! you can define your own relationships using components.
//!
//! We can define a custom relationship by creating two components:
//! one to store the relationship itself, and another to keep track of the reverse relationship.
//! Bevy's [`ChildOf`] component implements the [`Relationship`] trait, serving as the source of truth,
//! while the [`Children`] component implements the [`RelationshipTarget`] trait and is used to accelerate traversals down the hierarchy.
//!
//! In this example we're creating a [`Targeting`]/[`TargetedBy`] relationship,
//! demonstrating how you might model units which target a single unit in combat.
use bevy::ecs::entity::EntityHashSet;
use bevy::ecs::system::RunSystemOnce;
use bevy::prelude::*;
/// The entity that this entity is targeting.
///
/// This is the source of truth for the relationship,
/// and can be modified directly to change the target.
#[derive(Component, Debug)]
#[relationship(relationship_target = TargetedBy)]
struct Targeting(Entity);
/// All entities that are targeting this entity.
///
/// This component is updated reactively using the component hooks introduced by deriving
/// the [`Relationship`] trait. We should not modify this component directly,
/// but can safely read its field. In a larger project, we could enforce this through the use of
/// private fields and public getters.
#[derive(Component, Debug)]
#[relationship_target(relationship = Targeting)]
struct TargetedBy(Vec<Entity>);
fn main() {
// Operating on a raw `World` and running systems one at a time
// is great for writing tests and teaching abstract concepts!
let mut world = World::new();
// We're going to spawn a few entities and relate them to each other in a complex way.
// To start, Bob will target Alice, Charlie will target Bob,
// and Alice will target Charlie. This creates a loop in the relationship graph.
//
// Then, we'll spawn Devon, who will target Charlie,
// creating a more complex graph with a branching structure.
fn spawning_entities_with_relationships(mut commands: Commands) {
// Calling .id() after spawning an entity will return the `Entity` identifier of the spawned entity,
// even though the entity itself is not yet instantiated in the world.
// This works because Commands will reserve the entity ID before actually spawning the entity,
// through the use of atomic counters.
let alice = commands.spawn(Name::new("Alice")).id();
// Relations are just components, so we can add them into the bundle that we're spawning.
let bob = commands.spawn((Name::new("Bob"), Targeting(alice))).id();
// The `with_related` and `with_relationships` helper methods on `EntityCommands` can be used to add relations in a more ergonomic way.
let charlie = commands
.spawn((Name::new("Charlie"), Targeting(bob)))
// The `with_related` method will spawn a bundle with `Targeting` relationship
.with_related::<Targeting>(Name::new("James"))
// The `with_relationships` method will automatically add the `Targeting` component to any entities spawned within the closure,
// targeting the entity that we're calling `with_related` on.
.with_related_entities::<Targeting>(|related_spawner_commands| {
// We could spawn multiple entities here, and they would all target `charlie`.
related_spawner_commands.spawn(Name::new("Devon"));
})
.id();
// Simply inserting the `Targeting` component will automatically create and update the `TargetedBy` component on the target entity.
// We can do this at any point; not just when the entity is spawned.
commands.entity(alice).insert(Targeting(charlie));
}
world
.run_system_once(spawning_entities_with_relationships)
.unwrap();
fn debug_relationships(
// Not all of our entities are targeted by something, so we use `Option` in our query to handle this case.
relations_query: Query<(&Name, &Targeting, Option<&TargetedBy>)>,
name_query: Query<&Name>,
) {
let mut relationships = String::new();
for (name, targeting, maybe_targeted_by) in relations_query.iter() {
let targeting_name = name_query.get(targeting.0).unwrap();
let targeted_by_string = if let Some(targeted_by) = maybe_targeted_by {
let mut vec_of_names = Vec::<&Name>::new();
for entity in &targeted_by.0 {
let name = name_query.get(*entity).unwrap();
vec_of_names.push(name);
}
// Convert this to a nice string for printing.
let vec_of_str: Vec<&str> = vec_of_names.iter().map(|name| name.as_str()).collect();
vec_of_str.join(", ")
} else {
"nobody".to_string()
};
relationships.push_str(&format!(
"{name} is targeting {targeting_name}, and is targeted by {targeted_by_string}\n",
));
}
println!("{}", relationships);
}
world.run_system_once(debug_relationships).unwrap();
// Demonstrates how to correctly mutate relationships.
// Relationship components are immutable! We can't query for the `Targeting` component mutably and modify it directly,
// but we can insert a new `Targeting` component to replace the old one.
// This allows the hooks on the `Targeting` component to update the `TargetedBy` component correctly.
// The `TargetedBy` component will be updated automatically!
fn mutate_relationships(name_query: Query<(Entity, &Name)>, mut commands: Commands) {
// Let's find Devon by doing a linear scan of the entity names.
let devon = name_query
.iter()
.find(|(_entity, name)| name.as_str() == "Devon")
.unwrap()
.0;
let alice = name_query
.iter()
.find(|(_entity, name)| name.as_str() == "Alice")
.unwrap()
.0;
println!("Making Devon target Alice.\n");
commands.entity(devon).insert(Targeting(alice));
}
world.run_system_once(mutate_relationships).unwrap();
world.run_system_once(debug_relationships).unwrap();
// Systems can return errors,
// which can be used to signal that something went wrong during the system's execution.
#[derive(Debug)]
#[expect(
dead_code,
reason = "Rust considers types that are only used by their debug trait as dead code."
)]
struct TargetingCycle {
initial_entity: Entity,
visited: EntityHashSet,
}
/// Bevy's relationships come with all sorts of useful methods for traversal.
/// Here, we're going to look for cycles using a depth-first search.
fn check_for_cycles(
// We want to check every entity for cycles
query_to_check: Query<Entity, With<Targeting>>,
// Fetch the names for easier debugging.
name_query: Query<&Name>,
// The targeting_query allows us to traverse the relationship graph.
targeting_query: Query<&Targeting>,
) -> Result<(), TargetingCycle> {
for initial_entity in query_to_check.iter() {
let mut visited = EntityHashSet::new();
let mut targeting_name = name_query.get(initial_entity).unwrap().clone();
println!("Checking for cycles starting at {targeting_name}",);
// There's all sorts of methods like this; check the `Query` docs for more!
// This would also be easy to do by just manually checking the `Targeting` component,
// and calling `query.get(targeted_entity)` on the entity that it targets in a loop.
for targeting in targeting_query.iter_ancestors(initial_entity) {
let target_name = name_query.get(targeting).unwrap();
println!("{targeting_name} is targeting {target_name}",);
targeting_name = target_name.clone();
if !visited.insert(targeting) {
return Err(TargetingCycle {
initial_entity,
visited,
});
}
}
}
// If we've checked all the entities and haven't found a cycle, we're good!
Ok(())
}
// Calling `world.run_system_once` on systems which return Results gives us two layers of errors:
// the first checks if running the system failed, and the second checks if the system itself returned an error.
// We're unwrapping the first, but checking the output of the system itself.
let cycle_result = world.run_system_once(check_for_cycles).unwrap();
println!("{cycle_result:?} \n");
// We deliberately introduced a cycle during spawning!
assert!(cycle_result.is_err());
// Now, let's demonstrate removing relationships and break the cycle.
fn untarget(mut commands: Commands, name_query: Query<(Entity, &Name)>) {
// Let's find Charlie by doing a linear scan of the entity names.
let charlie = name_query
.iter()
.find(|(_entity, name)| name.as_str() == "Charlie")
.unwrap()
.0;
// We can remove the `Targeting` component to remove the relationship
// and break the cycle we saw earlier.
println!("Removing Charlie's targeting relationship.\n");
commands.entity(charlie).remove::<Targeting>();
}
world.run_system_once(untarget).unwrap();
world.run_system_once(debug_relationships).unwrap();
// Cycle free!
let cycle_result = world.run_system_once(check_for_cycles).unwrap();
println!("{cycle_result:?} \n");
assert!(cycle_result.is_ok());
}

View File

@@ -0,0 +1,57 @@
//! This example shows how you can know when a [`Component`] has been removed, so you can react to it.
//!
//! When a [`Component`] is removed from an [`Entity`], all [`Observer`] with an [`OnRemove`] trigger for
//! that [`Component`] will be notified. These observers will be called immediately after the
//! [`Component`] is removed. For more info on observers, see the
//! [observers example](https://github.com/bevyengine/bevy/blob/main/examples/ecs/observers.rs).
//!
//! Advanced users may also consider using a lifecycle hook
//! instead of an observer, as it incurs less overhead for a case like this.
//! See the [component hooks example](https://github.com/bevyengine/bevy/blob/main/examples/ecs/component_hooks.rs).
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
// This system will remove a component after two seconds.
.add_systems(Update, remove_component)
// This observer will react to the removal of the component.
.add_observer(react_on_removal)
.run();
}
/// This `struct` is just used for convenience in this example. This is the [`Component`] we'll be
/// giving to the `Entity` so we have a [`Component`] to remove in `remove_component()`.
#[derive(Component)]
struct MyComponent;
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2d);
commands.spawn((
Sprite::from_image(asset_server.load("branding/icon.png")),
// Add the `Component`.
MyComponent,
));
}
fn remove_component(
time: Res<Time>,
mut commands: Commands,
query: Query<Entity, With<MyComponent>>,
) {
// After two seconds have passed the `Component` is removed.
if time.elapsed_secs() > 2.0 {
if let Some(entity) = query.iter().next() {
commands.entity(entity).remove::<MyComponent>();
}
}
}
fn react_on_removal(trigger: Trigger<OnRemove, MyComponent>, mut query: Query<&mut Sprite>) {
// The `OnRemove` trigger was automatically called on the `Entity` that had its `MyComponent` removed.
let entity = trigger.target();
if let Ok(mut sprite) = query.get_mut(entity) {
sprite.color = Color::srgb(0.5, 1., 1.);
}
}

View File

@@ -0,0 +1,107 @@
//! This example demonstrates how to use run conditions to control when systems run.
use bevy::prelude::*;
fn main() {
println!();
println!("For the first 2 seconds you will not be able to increment the counter");
println!("Once that time has passed you can press space, enter, left mouse, right mouse or touch the screen to increment the counter");
println!();
App::new()
.add_plugins(DefaultPlugins)
.init_resource::<InputCounter>()
.add_systems(
Update,
(
increment_input_counter
// The common_conditions module has a few useful run conditions
// for checking resources and states. These are included in the prelude.
.run_if(resource_exists::<InputCounter>)
// `.or()` is a run condition combinator that only evaluates the second condition
// if the first condition returns `false`. This behavior is known as "short-circuiting",
// and is how the `||` operator works in Rust (as well as most C-family languages).
// In this case, the `has_user_input` run condition will be evaluated since the `Unused` resource has not been initialized.
.run_if(resource_exists::<Unused>.or(
// This is a custom run condition, defined using a system that returns
// a `bool` and which has read-only `SystemParam`s.
// Only a single run condition must return `true` in order for the system to run.
has_user_input,
)),
print_input_counter
// `.and()` is a run condition combinator that only evaluates the second condition
// if the first condition returns `true`, analogous to the `&&` operator.
// In this case, the short-circuiting behavior prevents the second run condition from
// panicking if the `InputCounter` resource has not been initialized.
.run_if(resource_exists::<InputCounter>.and(
// This is a custom run condition in the form of a closure.
// This is useful for small, simple run conditions you don't need to reuse.
// All the normal rules still apply: all parameters must be read only except for local parameters.
|counter: Res<InputCounter>| counter.is_changed() && !counter.is_added(),
)),
print_time_message
// This function returns a custom run condition, much like the common conditions module.
// It will only return true once 2 seconds have passed.
.run_if(time_passed(2.0))
// You can use the `not` condition from the common_conditions module
// to inverse a run condition. In this case it will return true if
// less than 2.5 seconds have elapsed since the app started.
.run_if(not(time_passed(2.5))),
),
)
.run();
}
#[derive(Resource, Default)]
struct InputCounter(usize);
#[derive(Resource)]
struct Unused;
/// Return true if any of the defined inputs were just pressed.
///
/// This is a custom run condition, it can take any normal system parameters as long as
/// they are read only (except for local parameters which can be mutable).
/// It returns a bool which determines if the system should run.
fn has_user_input(
keyboard_input: Res<ButtonInput<KeyCode>>,
mouse_button_input: Res<ButtonInput<MouseButton>>,
touch_input: Res<Touches>,
) -> bool {
keyboard_input.just_pressed(KeyCode::Space)
|| keyboard_input.just_pressed(KeyCode::Enter)
|| mouse_button_input.just_pressed(MouseButton::Left)
|| mouse_button_input.just_pressed(MouseButton::Right)
|| touch_input.any_just_pressed()
}
/// This is a function that returns a closure which can be used as a run condition.
///
/// This is useful because you can reuse the same run condition but with different variables.
/// This is how the common conditions module works.
fn time_passed(t: f32) -> impl FnMut(Local<f32>, Res<Time>) -> bool {
move |mut timer: Local<f32>, time: Res<Time>| {
// Tick the timer
*timer += time.delta_secs();
// Return true if the timer has passed the time
*timer >= t
}
}
/// SYSTEM: Increment the input counter
/// Notice how we can take just the `ResMut` and not have to wrap
/// it in an option in case it hasn't been initialized, this is because
/// it has a run condition that checks if the `InputCounter` resource exists
fn increment_input_counter(mut counter: ResMut<InputCounter>) {
counter.0 += 1;
}
/// SYSTEM: Print the input counter
fn print_input_counter(counter: Res<InputCounter>) {
println!("Input counter: {}", counter.0);
}
/// SYSTEM: Adds the input counter resource
fn print_time_message() {
println!("It has been more than 2 seconds since the program started and less than 2.5 seconds");
}

View File

@@ -0,0 +1,165 @@
//! From time to time, you may find that you want to both send and receive an event of the same type in a single system.
//!
//! Of course, this results in an error: the borrows of [`EventWriter`] and [`EventReader`] overlap,
//! if and only if the [`Event`] type is the same.
//! One system parameter borrows the [`Events`] resource mutably, and another system parameter borrows the [`Events`] resource immutably.
//! If Bevy allowed this, this would violate Rust's rules against aliased mutability.
//! In other words, this would be Undefined Behavior (UB)!
//!
//! There are two ways to solve this problem:
//!
//! 1. Use [`ParamSet`] to check out the [`EventWriter`] and [`EventReader`] one at a time.
//! 2. Use a [`Local`] [`EventCursor`] instead of an [`EventReader`], and use [`ResMut`] to access [`Events`].
//!
//! In the first case, you're being careful to only check out only one of the [`EventWriter`] or [`EventReader`] at a time.
//! By "temporally" separating them, you avoid the overlap.
//!
//! In the second case, you only ever have one access to the underlying [`Events`] resource at a time.
//! But in exchange, you have to manually keep track of which events you've already read.
//!
//! Let's look at an example of each.
use bevy::{diagnostic::FrameCount, ecs::event::EventCursor, prelude::*};
fn main() {
let mut app = App::new();
app.add_plugins(MinimalPlugins)
.add_event::<DebugEvent>()
.add_event::<A>()
.add_event::<B>()
.add_systems(Update, read_and_write_different_event_types)
.add_systems(
Update,
(
send_events,
debug_events,
send_and_receive_param_set,
debug_events,
send_and_receive_manual_event_reader,
debug_events,
)
.chain(),
);
// We're just going to run a few frames, so we can see and understand the output.
app.update();
// By running for longer than one frame, we can see that we're caching our cursor in the event queue properly.
app.update();
}
#[derive(Event)]
struct A;
#[derive(Event)]
struct B;
// This works fine, because the types are different,
// so the borrows of the `EventWriter` and `EventReader` don't overlap.
// Note that these borrowing rules are checked at system initialization time,
// not at compile time, as Bevy uses internal unsafe code to split the `World` into disjoint pieces.
fn read_and_write_different_event_types(mut a: EventWriter<A>, mut b: EventReader<B>) {
for _ in b.read() {}
a.write(A);
}
/// A dummy event type.
#[derive(Debug, Clone, Event)]
struct DebugEvent {
resend_from_param_set: bool,
resend_from_local_event_reader: bool,
times_sent: u8,
}
/// A system that sends all combinations of events.
fn send_events(mut events: EventWriter<DebugEvent>, frame_count: Res<FrameCount>) {
println!("Sending events for frame {}", frame_count.0);
events.write(DebugEvent {
resend_from_param_set: false,
resend_from_local_event_reader: false,
times_sent: 1,
});
events.write(DebugEvent {
resend_from_param_set: true,
resend_from_local_event_reader: false,
times_sent: 1,
});
events.write(DebugEvent {
resend_from_param_set: false,
resend_from_local_event_reader: true,
times_sent: 1,
});
events.write(DebugEvent {
resend_from_param_set: true,
resend_from_local_event_reader: true,
times_sent: 1,
});
}
/// A system that prints all events sent since the last time this system ran.
///
/// Note that some events will be printed twice, because they were sent twice.
fn debug_events(mut events: EventReader<DebugEvent>) {
for event in events.read() {
println!("{event:?}");
}
}
/// A system that both sends and receives events using [`ParamSet`].
fn send_and_receive_param_set(
mut param_set: ParamSet<(EventReader<DebugEvent>, EventWriter<DebugEvent>)>,
frame_count: Res<FrameCount>,
) {
println!(
"Sending and receiving events for frame {} with a `ParamSet`",
frame_count.0
);
// We must collect the events to resend, because we can't access the writer while we're iterating over the reader.
let mut events_to_resend = Vec::new();
// This is p0, as the first parameter in the `ParamSet` is the reader.
for event in param_set.p0().read() {
if event.resend_from_param_set {
events_to_resend.push(event.clone());
}
}
// This is p1, as the second parameter in the `ParamSet` is the writer.
for mut event in events_to_resend {
event.times_sent += 1;
param_set.p1().write(event);
}
}
/// A system that both sends and receives events using a [`Local`] [`EventCursor`].
fn send_and_receive_manual_event_reader(
// The `Local` `SystemParam` stores state inside the system itself, rather than in the world.
// `EventCursor<T>` is the internal state of `EventReader<T>`, which tracks which events have been seen.
mut local_event_reader: Local<EventCursor<DebugEvent>>,
// We can access the `Events` resource mutably, allowing us to both read and write its contents.
mut events: ResMut<Events<DebugEvent>>,
frame_count: Res<FrameCount>,
) {
println!(
"Sending and receiving events for frame {} with a `Local<EventCursor>",
frame_count.0
);
// We must collect the events to resend, because we can't mutate events while we're iterating over the events.
let mut events_to_resend = Vec::new();
for event in local_event_reader.read(&events) {
if event.resend_from_local_event_reader {
// For simplicity, we're cloning the event.
// In this case, since we have mutable access to the `Events` resource,
// we could also just mutate the event in-place,
// or drain the event queue into our `events_to_resend` vector.
events_to_resend.push(event.clone());
}
}
for mut event in events_to_resend {
event.times_sent += 1;
events.send(event);
}
}

View File

@@ -0,0 +1,20 @@
//! Demonstrates a startup system (one that runs once when the app starts up).
use bevy::prelude::*;
fn main() {
App::new()
.add_systems(Startup, startup_system)
.add_systems(Update, normal_system)
.run();
}
/// Startup systems are run exactly once when the app starts up.
/// They run right before "normal" systems run.
fn startup_system() {
println!("startup system ran first");
}
fn normal_system() {
println!("normal system ran second");
}

View File

@@ -0,0 +1,47 @@
//! Shows how anonymous functions / closures can be used as systems.
use bevy::{log::LogPlugin, prelude::*};
fn main() {
// create a simple closure.
let simple_closure = || {
// this is a closure that does nothing.
info!("Hello from a simple closure!");
};
// create a closure, with an 'input' value.
let complex_closure = |mut value: String| {
move || {
info!("Hello from a complex closure! {}", value);
// we can modify the value inside the closure. this will be saved between calls.
value = format!("{value} - updated");
// you could also use an outside variable like presented in the inlined closures
// info!("outside_variable! {}", outside_variable);
}
};
let outside_variable = "bar".to_string();
App::new()
.add_plugins(LogPlugin::default())
// we can use a closure as a system
.add_systems(Update, simple_closure)
// or we can use a more complex closure, and pass an argument to initialize a Local variable.
.add_systems(Update, complex_closure("foo".into()))
// we can also inline a closure
.add_systems(Update, || {
info!("Hello from an inlined closure!");
})
// or use variables outside a closure
.add_systems(Update, move || {
info!(
"Hello from an inlined closure that captured the 'outside_variable'! {}",
outside_variable
);
// you can use outside_variable, or any other variables inside this closure.
// their states will be saved.
})
.run();
}

View File

@@ -0,0 +1,47 @@
//! This example creates a custom [`SystemParam`] struct that counts the number of players.
use bevy::{ecs::system::SystemParam, prelude::*};
fn main() {
App::new()
.insert_resource(PlayerCount(0))
.add_systems(Startup, spawn)
.add_systems(Update, count_players)
.run();
}
#[derive(Component)]
struct Player;
#[derive(Resource)]
struct PlayerCount(usize);
/// The [`SystemParam`] struct can contain any types that can also be included in a
/// system function signature.
///
/// In this example, it includes a query and a mutable resource.
#[derive(SystemParam)]
struct PlayerCounter<'w, 's> {
players: Query<'w, 's, &'static Player>,
count: ResMut<'w, PlayerCount>,
}
impl<'w, 's> PlayerCounter<'w, 's> {
fn count(&mut self) {
self.count.0 = self.players.iter().len();
}
}
/// Spawn some players to count
fn spawn(mut commands: Commands) {
commands.spawn(Player);
commands.spawn(Player);
commands.spawn(Player);
}
/// The [`SystemParam`] can be used directly in a system argument.
fn count_players(mut counter: PlayerCounter) {
counter.count();
println!("{} players in the game", counter.count.0);
}

View File

@@ -0,0 +1,77 @@
//! Illustrates how to make a single system from multiple functions running in sequence,
//! passing the output of the first into the input of the next.
use bevy::prelude::*;
use std::num::ParseIntError;
use bevy::log::{debug, error, info, Level, LogPlugin};
fn main() {
App::new()
.insert_resource(Message("42".to_string()))
.insert_resource(OptionalWarning(Err("Got to rusty?".to_string())))
.add_plugins(LogPlugin {
level: Level::TRACE,
filter: "".to_string(),
..default()
})
.add_systems(
Update,
(
parse_message_system.pipe(handler_system),
data_pipe_system.map(|out| info!("{out}")),
parse_message_system.map(|out| debug!("{out:?}")),
warning_pipe_system.map(|out| {
if let Err(err) = out {
error!("{err}");
}
}),
parse_error_message_system.map(|out| {
if let Err(err) = out {
error!("{err}");
}
}),
parse_message_system.map(drop),
),
)
.run();
}
#[derive(Resource, Deref)]
struct Message(String);
#[derive(Resource, Deref)]
struct OptionalWarning(Result<(), String>);
// This system produces a Result<usize> output by trying to parse the Message resource.
fn parse_message_system(message: Res<Message>) -> Result<usize, ParseIntError> {
message.parse::<usize>()
}
// This system produces a Result<()> output by trying to parse the Message resource.
fn parse_error_message_system(message: Res<Message>) -> Result<(), ParseIntError> {
message.parse::<usize>()?;
Ok(())
}
// This system takes a Result<usize> input and either prints the parsed value or the error message
// Try changing the Message resource to something that isn't an integer. You should see the error
// message printed.
fn handler_system(In(result): In<Result<usize, ParseIntError>>) {
match result {
Ok(value) => println!("parsed message: {value}"),
Err(err) => println!("encountered an error: {err:?}"),
}
}
// This system produces a String output by trying to clone the String from the Message resource.
fn data_pipe_system(message: Res<Message>) -> String {
message.0.clone()
}
// This system produces a Result<String> output by trying to extract a String from the
// OptionalWarning resource. Try changing the OptionalWarning resource to None. You should
// not see the warning message printed.
fn warning_pipe_system(message: Res<OptionalWarning>) -> Result<(), String> {
message.0.clone()
}

View File

@@ -0,0 +1,208 @@
//! Demonstrate stepping through systems in order of execution.
//!
//! To run this example, you must enable the `bevy_debug_stepping` feature.
use bevy::{ecs::schedule::Stepping, log::LogPlugin, prelude::*};
fn main() {
let mut app = App::new();
app
// to display log messages from Stepping resource
.add_plugins(LogPlugin::default())
.add_systems(
Update,
(
update_system_one,
// establish a dependency here to simplify descriptions below
update_system_two.after(update_system_one),
update_system_three.after(update_system_two),
update_system_four,
),
)
.add_systems(PreUpdate, pre_update_system);
// For the simplicity of this example, we directly modify the `Stepping`
// resource here and run the systems with `App::update()`. Each call to
// `App::update()` is the equivalent of a single frame render when using
// `App::run()`.
//
// In a real-world situation, the `Stepping` resource would be modified by
// a system based on input from the user. A full demonstration of this can
// be found in the breakout example.
println!(
r#"
Actions: call app.update()
Result: All systems run normally"#
);
app.update();
println!(
r#"
Actions: Add the Stepping resource then call app.update()
Result: All systems run normally. Stepping has no effect unless explicitly
configured for a Schedule, and Stepping has been enabled."#
);
app.insert_resource(Stepping::new());
app.update();
println!(
r#"
Actions: Add the Update Schedule to Stepping; enable Stepping; call
app.update()
Result: Only the systems in PreUpdate run. When Stepping is enabled,
systems in the configured schedules will not run unless:
* Stepping::step_frame() is called
* Stepping::continue_frame() is called
* System has been configured to always run"#
);
let mut stepping = app.world_mut().resource_mut::<Stepping>();
stepping.add_schedule(Update).enable();
app.update();
println!(
r#"
Actions: call Stepping.step_frame(); call app.update()
Result: The PreUpdate systems run, and one Update system will run. In
Stepping, step means run the next system across all the schedules
that have been added to the Stepping resource."#
);
let mut stepping = app.world_mut().resource_mut::<Stepping>();
stepping.step_frame();
app.update();
println!(
r#"
Actions: call app.update()
Result: Only the PreUpdate systems run. The previous call to
Stepping::step_frame() only applies for the next call to
app.update()/the next frame rendered.
"#
);
app.update();
println!(
r#"
Actions: call Stepping::continue_frame(); call app.update()
Result: PreUpdate system will run, and all remaining Update systems will
run. Stepping::continue_frame() tells stepping to run all systems
starting after the last run system until it hits the end of the
frame, or it encounters a system with a breakpoint set. In this
case, we previously performed a step, running one system in Update.
This continue will cause all remaining systems in Update to run."#
);
let mut stepping = app.world_mut().resource_mut::<Stepping>();
stepping.continue_frame();
app.update();
println!(
r#"
Actions: call Stepping::step_frame() & app.update() four times in a row
Result: PreUpdate system runs every time we call app.update(), along with
one system from the Update schedule each time. This shows what
execution would look like to step through an entire frame of
systems."#
);
for _ in 0..4 {
let mut stepping = app.world_mut().resource_mut::<Stepping>();
stepping.step_frame();
app.update();
}
println!(
r#"
Actions: Stepping::always_run(Update, update_system_two); step through all
systems
Result: PreUpdate system and update_system_two() will run every time we
call app.update(). We'll also only need to step three times to
execute all systems in the frame. Stepping::always_run() allows
us to granularly allow systems to run when stepping is enabled."#
);
let mut stepping = app.world_mut().resource_mut::<Stepping>();
stepping.always_run(Update, update_system_two);
for _ in 0..3 {
let mut stepping = app.world_mut().resource_mut::<Stepping>();
stepping.step_frame();
app.update();
}
println!(
r#"
Actions: Stepping::never_run(Update, update_system_two); continue through
all systems
Result: All systems except update_system_two() will execute.
Stepping::never_run() allows us to disable systems while Stepping
is enabled."#
);
let mut stepping = app.world_mut().resource_mut::<Stepping>();
stepping.never_run(Update, update_system_two);
stepping.continue_frame();
app.update();
println!(
r#"
Actions: Stepping::set_breakpoint(Update, update_system_two); continue,
step, continue
Result: During the first continue, pre_update_system() and
update_system_one() will run. update_system_four() may also run
as it has no dependency on update_system_two() or
update_system_three(). Nether update_system_two() nor
update_system_three() will run in the first app.update() call as
they form a chained dependency on update_system_one() and run
in order of one, two, three. Stepping stops system execution in
the Update schedule when it encounters the breakpoint for
update_system_three().
During the step we run update_system_two() along with the
pre_update_system().
During the final continue pre_update_system() and
update_system_three() run."#
);
let mut stepping = app.world_mut().resource_mut::<Stepping>();
stepping.set_breakpoint(Update, update_system_two);
stepping.continue_frame();
app.update();
let mut stepping = app.world_mut().resource_mut::<Stepping>();
stepping.step_frame();
app.update();
let mut stepping = app.world_mut().resource_mut::<Stepping>();
stepping.continue_frame();
app.update();
println!(
r#"
Actions: Stepping::clear_breakpoint(Update, update_system_two); continue
through all systems
Result: All systems will run"#
);
let mut stepping = app.world_mut().resource_mut::<Stepping>();
stepping.clear_breakpoint(Update, update_system_two);
stepping.continue_frame();
app.update();
println!(
r#"
Actions: Stepping::disable(); app.update()
Result: All systems will run. With Stepping disabled, there's no need to
call Stepping::step_frame() or Stepping::continue_frame() to run
systems in the Update schedule."#
);
let mut stepping = app.world_mut().resource_mut::<Stepping>();
stepping.disable();
app.update();
}
fn pre_update_system() {
println!("▶ pre_update_system");
}
fn update_system_one() {
println!("▶ update_system_one");
}
fn update_system_two() {
println!("▶ update_system_two");
}
fn update_system_three() {
println!("▶ update_system_three");
}
fn update_system_four() {
println!("▶ update_system_four");
}