Merge branch 'main' into transmission

This commit is contained in:
Marco Buono 2023-05-31 22:26:38 -03:00
commit 96c36ef780
54 changed files with 455 additions and 222 deletions

View file

@ -726,7 +726,7 @@ path = "examples/3d/wireframe.rs"
name = "Wireframe" name = "Wireframe"
description = "Showcases wireframe rendering" description = "Showcases wireframe rendering"
category = "3D Rendering" category = "3D Rendering"
wasm = true wasm = false
[[example]] [[example]]
name = "no_prepass" name = "no_prepass"

View file

@ -15,9 +15,14 @@ var<uniform> settings: ShowPrepassSettings;
@fragment @fragment
fn fragment( fn fragment(
@builtin(position) frag_coord: vec4<f32>, @builtin(position) frag_coord: vec4<f32>,
#ifdef MULTISAMPLED
@builtin(sample_index) sample_index: u32, @builtin(sample_index) sample_index: u32,
#endif
#import bevy_pbr::mesh_vertex_output #import bevy_pbr::mesh_vertex_output
) -> @location(0) vec4<f32> { ) -> @location(0) vec4<f32> {
#ifndef MULTISAMPLED
let sample_index = 0u;
#endif
if settings.show_depth == 1u { if settings.show_depth == 1u {
let depth = prepass_depth(frag_coord, sample_index); let depth = prepass_depth(frag_coord, sample_index);
return vec4(depth, depth, depth, 1.0); return vec4(depth, depth, depth, 1.0);

View file

@ -45,7 +45,7 @@ pub fn heavy_compute(c: &mut Criterion) {
let mut system = IntoSystem::into_system(sys); let mut system = IntoSystem::into_system(sys);
system.initialize(&mut world); system.initialize(&mut world);
system.update_archetype_component_access(&world); system.update_archetype_component_access(world.as_unsafe_world_cell());
b.iter(move || system.run((), &mut world)); b.iter(move || system.run((), &mut world));
}); });

View file

@ -37,7 +37,7 @@ impl Benchmark {
let mut system = IntoSystem::into_system(query_system); let mut system = IntoSystem::into_system(query_system);
system.initialize(&mut world); system.initialize(&mut world);
system.update_archetype_component_access(&world); system.update_archetype_component_access(world.as_unsafe_world_cell());
Self(world, Box::new(system)) Self(world, Box::new(system))
} }

View file

@ -291,7 +291,7 @@ pub fn query_get_component_simple(criterion: &mut Criterion) {
let mut system = IntoSystem::into_system(query_system); let mut system = IntoSystem::into_system(query_system);
system.initialize(&mut world); system.initialize(&mut world);
system.update_archetype_component_access(&world); system.update_archetype_component_access(world.as_unsafe_world_cell());
bencher.iter(|| system.run(entity, &mut world)); bencher.iter(|| system.run(entity, &mut world));
}); });

View file

@ -134,9 +134,7 @@ pub enum Tonemapping {
/// Suffers from lots hue shifting, brights don't desaturate naturally. /// Suffers from lots hue shifting, brights don't desaturate naturally.
/// Bright primaries and secondaries don't desaturate at all. /// Bright primaries and secondaries don't desaturate at all.
Reinhard, Reinhard,
/// Current bevy default. Likely to change in the future.
/// Suffers from hue shifting. Brights don't desaturate much at all across the spectrum. /// Suffers from hue shifting. Brights don't desaturate much at all across the spectrum.
#[default]
ReinhardLuminance, ReinhardLuminance,
/// Same base implementation that Godot 4.0 uses for Tonemap ACES. /// Same base implementation that Godot 4.0 uses for Tonemap ACES.
/// <https://github.com/TheRealMJP/BakingLab/blob/master/BakingLab/ACES.hlsl> /// <https://github.com/TheRealMJP/BakingLab/blob/master/BakingLab/ACES.hlsl>
@ -156,6 +154,7 @@ pub enum Tonemapping {
/// Designed as a compromise if you want e.g. decent skin tones in low light, but can't afford to re-do your /// Designed as a compromise if you want e.g. decent skin tones in low light, but can't afford to re-do your
/// VFX to look good without hue shifting. /// VFX to look good without hue shifting.
SomewhatBoringDisplayTransform, SomewhatBoringDisplayTransform,
/// Current Bevy default.
/// By Tomasz Stachowiak /// By Tomasz Stachowiak
/// <https://github.com/h3r2tic/tony-mc-mapface> /// <https://github.com/h3r2tic/tony-mc-mapface>
/// Very neutral. Subtle but intentional hue shifting. Brights desaturate across the spectrum. /// Very neutral. Subtle but intentional hue shifting. Brights desaturate across the spectrum.
@ -167,6 +166,7 @@ pub enum Tonemapping {
/// Color hues are preserved during compression, except for a deliberate [BezoldBrücke shift](https://en.wikipedia.org/wiki/Bezold%E2%80%93Br%C3%BCcke_shift). /// Color hues are preserved during compression, except for a deliberate [BezoldBrücke shift](https://en.wikipedia.org/wiki/Bezold%E2%80%93Br%C3%BCcke_shift).
/// To avoid posterization, selective desaturation is employed, with care to avoid the [Abney effect](https://en.wikipedia.org/wiki/Abney_effect). /// To avoid posterization, selective desaturation is employed, with care to avoid the [Abney effect](https://en.wikipedia.org/wiki/Abney_effect).
/// NOTE: Requires the `tonemapping_luts` cargo feature. /// NOTE: Requires the `tonemapping_luts` cargo feature.
#[default]
TonyMcMapface, TonyMcMapface,
/// Default Filmic Display Transform from blender. /// Default Filmic Display Transform from blender.
/// Somewhat neutral. Suffers from hue shifting. Brights desaturate across the spectrum. /// Somewhat neutral. Suffers from hue shifting. Brights desaturate across the spectrum.
@ -328,7 +328,7 @@ pub fn get_lut_bindings<'a>(
bindings: [u32; 2], bindings: [u32; 2],
) -> [BindGroupEntry<'a>; 2] { ) -> [BindGroupEntry<'a>; 2] {
let image = match tonemapping { let image = match tonemapping {
//AgX lut texture used when tonemapping doesn't need a texture since it's very small (32x32x32) // AgX lut texture used when tonemapping doesn't need a texture since it's very small (32x32x32)
Tonemapping::None Tonemapping::None
| Tonemapping::Reinhard | Tonemapping::Reinhard
| Tonemapping::ReinhardLuminance | Tonemapping::ReinhardLuminance

View file

@ -1162,12 +1162,19 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
} }
} }
/// An error that occurs when retrieving a specific [`Entity`]'s query result. /// An error that occurs when retrieving a specific [`Entity`]'s query result from [`Query`](crate::system::Query) or [`QueryState`].
// TODO: return the type_name as part of this error // TODO: return the type_name as part of this error
#[derive(Debug, PartialEq, Eq, Clone, Copy)] #[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum QueryEntityError { pub enum QueryEntityError {
/// The given [`Entity`]'s components do not match the query.
///
/// Either it does not have a requested component, or it has a component which the query filters out.
QueryDoesNotMatch(Entity), QueryDoesNotMatch(Entity),
/// The given [`Entity`] does not exist.
NoSuchEntity(Entity), NoSuchEntity(Entity),
/// The [`Entity`] was requested mutably more than once.
///
/// See [`QueryState::get_many_mut`] for an example.
AliasedMutability(Entity), AliasedMutability(Entity),
} }
@ -1177,7 +1184,7 @@ impl fmt::Display for QueryEntityError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
QueryEntityError::QueryDoesNotMatch(_) => { QueryEntityError::QueryDoesNotMatch(_) => {
write!(f, "The given entity does not have the requested component.") write!(f, "The given entity's components do not match the query.")
} }
QueryEntityError::NoSuchEntity(_) => write!(f, "The requested entity does not exist."), QueryEntityError::NoSuchEntity(_) => write!(f, "The requested entity does not exist."),
QueryEntityError::AliasedMutability(_) => { QueryEntityError::AliasedMutability(_) => {
@ -1296,11 +1303,13 @@ mod tests {
} }
} }
/// An error that occurs when evaluating a [`QueryState`] as a single expected resulted via /// An error that occurs when evaluating a [`Query`](crate::system::Query) or [`QueryState`] as a single expected result via
/// [`QueryState::single`] or [`QueryState::single_mut`]. /// [`get_single`](crate::system::Query::get_single) or [`get_single_mut`](crate::system::Query::get_single_mut).
#[derive(Debug)] #[derive(Debug)]
pub enum QuerySingleError { pub enum QuerySingleError {
/// No entity fits the query.
NoEntities(&'static str), NoEntities(&'static str),
/// Multiple entities fit the query.
MultipleEntities(&'static str), MultipleEntities(&'static str),
} }

View file

@ -5,15 +5,58 @@ use std::ops::Not;
use crate::component::{self, ComponentId}; use crate::component::{self, ComponentId};
use crate::query::Access; use crate::query::Access;
use crate::system::{CombinatorSystem, Combine, IntoSystem, ReadOnlySystem, System}; use crate::system::{CombinatorSystem, Combine, IntoSystem, ReadOnlySystem, System};
use crate::world::unsafe_world_cell::UnsafeWorldCell;
use crate::world::World; use crate::world::World;
pub type BoxedCondition = Box<dyn ReadOnlySystem<In = (), Out = bool>>; pub type BoxedCondition<In = ()> = Box<dyn ReadOnlySystem<In = In, Out = bool>>;
/// A system that determines if one or more scheduled systems should run. /// A system that determines if one or more scheduled systems should run.
/// ///
/// Implemented for functions and closures that convert into [`System<In=(), Out=bool>`](crate::system::System) /// Implemented for functions and closures that convert into [`System<Out=bool>`](crate::system::System)
/// with [read-only](crate::system::ReadOnlySystemParam) parameters. /// with [read-only](crate::system::ReadOnlySystemParam) parameters.
pub trait Condition<Marker>: sealed::Condition<Marker> { ///
/// # Examples
/// A condition that returns true every other time it's called.
/// ```
/// # use bevy_ecs::prelude::*;
/// fn every_other_time() -> impl Condition<()> {
/// IntoSystem::into_system(|mut flag: Local<bool>| {
/// *flag = !*flag;
/// *flag
/// })
/// }
///
/// # #[derive(Resource)] struct DidRun(bool);
/// # fn my_system(mut did_run: ResMut<DidRun>) { did_run.0 = true; }
/// # let mut schedule = Schedule::new();
/// schedule.add_systems(my_system.run_if(every_other_time()));
/// # let mut world = World::new();
/// # world.insert_resource(DidRun(false));
/// # schedule.run(&mut world);
/// # assert!(world.resource::<DidRun>().0);
/// # world.insert_resource(DidRun(false));
/// # schedule.run(&mut world);
/// # assert!(!world.resource::<DidRun>().0);
/// ```
///
/// A condition that takes a bool as an input and returns it unchanged.
///
/// ```
/// # use bevy_ecs::prelude::*;
/// fn identity() -> impl Condition<(), bool> {
/// IntoSystem::into_system(|In(x)| x)
/// }
///
/// # fn always_true() -> bool { true }
/// # let mut schedule = Schedule::new();
/// # #[derive(Resource)] struct DidRun(bool);
/// # fn my_system(mut did_run: ResMut<DidRun>) { did_run.0 = true; }
/// schedule.add_systems(my_system.run_if(always_true.pipe(identity())));
/// # let mut world = World::new();
/// # world.insert_resource(DidRun(false));
/// # schedule.run(&mut world);
/// # assert!(world.resource::<DidRun>().0);
pub trait Condition<Marker, In = ()>: sealed::Condition<Marker, In> {
/// Returns a new run condition that only returns `true` /// Returns a new run condition that only returns `true`
/// if both this one and the passed `and_then` return `true`. /// if both this one and the passed `and_then` return `true`.
/// ///
@ -58,7 +101,7 @@ pub trait Condition<Marker>: sealed::Condition<Marker> {
/// Note that in this case, it's better to just use the run condition [`resource_exists_and_equals`]. /// Note that in this case, it's better to just use the run condition [`resource_exists_and_equals`].
/// ///
/// [`resource_exists_and_equals`]: common_conditions::resource_exists_and_equals /// [`resource_exists_and_equals`]: common_conditions::resource_exists_and_equals
fn and_then<M, C: Condition<M>>(self, and_then: C) -> AndThen<Self::System, C::System> { fn and_then<M, C: Condition<M, In>>(self, and_then: C) -> AndThen<Self::System, C::System> {
let a = IntoSystem::into_system(self); let a = IntoSystem::into_system(self);
let b = IntoSystem::into_system(and_then); let b = IntoSystem::into_system(and_then);
let name = format!("{} && {}", a.name(), b.name()); let name = format!("{} && {}", a.name(), b.name());
@ -105,7 +148,7 @@ pub trait Condition<Marker>: sealed::Condition<Marker> {
/// # app.run(&mut world); /// # app.run(&mut world);
/// # assert!(world.resource::<C>().0); /// # assert!(world.resource::<C>().0);
/// ``` /// ```
fn or_else<M, C: Condition<M>>(self, or_else: C) -> OrElse<Self::System, C::System> { fn or_else<M, C: Condition<M, In>>(self, or_else: C) -> OrElse<Self::System, C::System> {
let a = IntoSystem::into_system(self); let a = IntoSystem::into_system(self);
let b = IntoSystem::into_system(or_else); let b = IntoSystem::into_system(or_else);
let name = format!("{} || {}", a.name(), b.name()); let name = format!("{} || {}", a.name(), b.name());
@ -113,22 +156,22 @@ pub trait Condition<Marker>: sealed::Condition<Marker> {
} }
} }
impl<Marker, F> Condition<Marker> for F where F: sealed::Condition<Marker> {} impl<Marker, In, F> Condition<Marker, In> for F where F: sealed::Condition<Marker, In> {}
mod sealed { mod sealed {
use crate::system::{IntoSystem, ReadOnlySystem}; use crate::system::{IntoSystem, ReadOnlySystem};
pub trait Condition<Marker>: pub trait Condition<Marker, In>:
IntoSystem<(), bool, Marker, System = Self::ReadOnlySystem> IntoSystem<In, bool, Marker, System = Self::ReadOnlySystem>
{ {
// This associated type is necessary to let the compiler // This associated type is necessary to let the compiler
// know that `Self::System` is `ReadOnlySystem`. // know that `Self::System` is `ReadOnlySystem`.
type ReadOnlySystem: ReadOnlySystem<In = (), Out = bool>; type ReadOnlySystem: ReadOnlySystem<In = In, Out = bool>;
} }
impl<Marker, F> Condition<Marker> for F impl<Marker, In, F> Condition<Marker, In> for F
where where
F: IntoSystem<(), bool, Marker>, F: IntoSystem<In, bool, Marker>,
F::System: ReadOnlySystem, F::System: ReadOnlySystem,
{ {
type ReadOnlySystem = F::System; type ReadOnlySystem = F::System;
@ -990,7 +1033,7 @@ where
self.condition.is_exclusive() self.condition.is_exclusive()
} }
unsafe fn run_unsafe(&mut self, input: Self::In, world: &World) -> Self::Out { unsafe fn run_unsafe(&mut self, input: Self::In, world: UnsafeWorldCell) -> Self::Out {
// SAFETY: The inner condition system asserts its own safety. // SAFETY: The inner condition system asserts its own safety.
!self.condition.run_unsafe(input, world) !self.condition.run_unsafe(input, world)
} }
@ -1007,7 +1050,7 @@ where
self.condition.initialize(world); self.condition.initialize(world);
} }
fn update_archetype_component_access(&mut self, world: &World) { fn update_archetype_component_access(&mut self, world: UnsafeWorldCell) {
self.condition.update_archetype_component_access(world); self.condition.update_archetype_component_access(world);
} }

View file

@ -21,7 +21,7 @@ use crate::{
is_apply_system_buffers, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule, is_apply_system_buffers, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule,
}, },
system::BoxedSystem, system::BoxedSystem,
world::World, world::{unsafe_world_cell::UnsafeWorldCell, World},
}; };
use crate as bevy_ecs; use crate as bevy_ecs;
@ -184,7 +184,6 @@ impl SystemExecutor for MultiThreadedExecutor {
.map(|e| e.0.clone()); .map(|e| e.0.clone());
let thread_executor = thread_executor.as_deref(); let thread_executor = thread_executor.as_deref();
let world = SyncUnsafeCell::from_mut(world);
let SyncUnsafeSchedule { let SyncUnsafeSchedule {
systems, systems,
mut conditions, mut conditions,
@ -197,10 +196,13 @@ impl SystemExecutor for MultiThreadedExecutor {
// the executor itself is a `Send` future so that it can run // the executor itself is a `Send` future so that it can run
// alongside systems that claim the local thread // alongside systems that claim the local thread
let executor = async { let executor = async {
let world_cell = world.as_unsafe_world_cell();
while self.num_completed_systems < self.num_systems { while self.num_completed_systems < self.num_systems {
// SAFETY: self.ready_systems does not contain running systems // SAFETY:
// - self.ready_systems does not contain running systems.
// - `world_cell` has mutable access to the entire world.
unsafe { unsafe {
self.spawn_system_tasks(scope, systems, &mut conditions, world); self.spawn_system_tasks(scope, systems, &mut conditions, world_cell);
} }
if self.num_running_systems > 0 { if self.num_running_systems > 0 {
@ -231,7 +233,7 @@ impl SystemExecutor for MultiThreadedExecutor {
if self.apply_final_buffers { if self.apply_final_buffers {
// Do one final apply buffers after all systems have completed // Do one final apply buffers after all systems have completed
// Commands should be applied while on the scope's thread, not the executor's thread // Commands should be applied while on the scope's thread, not the executor's thread
let res = apply_system_buffers(&self.unapplied_systems, systems, world.get_mut()); let res = apply_system_buffers(&self.unapplied_systems, systems, world);
if let Err(payload) = res { if let Err(payload) = res {
let mut panic_payload = self.panic_payload.lock().unwrap(); let mut panic_payload = self.panic_payload.lock().unwrap();
*panic_payload = Some(payload); *panic_payload = Some(payload);
@ -283,14 +285,16 @@ impl MultiThreadedExecutor {
} }
/// # Safety /// # Safety
/// Caller must ensure that `self.ready_systems` does not contain any systems that /// - Caller must ensure that `self.ready_systems` does not contain any systems that
/// have been mutably borrowed (such as the systems currently running). /// have been mutably borrowed (such as the systems currently running).
/// - `world_cell` must have permission to access all world data (not counting
/// any world data that is claimed by systems currently running on this executor).
unsafe fn spawn_system_tasks<'scope>( unsafe fn spawn_system_tasks<'scope>(
&mut self, &mut self,
scope: &Scope<'_, 'scope, ()>, scope: &Scope<'_, 'scope, ()>,
systems: &'scope [SyncUnsafeCell<BoxedSystem>], systems: &'scope [SyncUnsafeCell<BoxedSystem>],
conditions: &mut Conditions, conditions: &mut Conditions,
cell: &'scope SyncUnsafeCell<World>, world_cell: UnsafeWorldCell<'scope>,
) { ) {
if self.exclusive_running { if self.exclusive_running {
return; return;
@ -307,10 +311,7 @@ impl MultiThreadedExecutor {
// Therefore, no other reference to this system exists and there is no aliasing. // Therefore, no other reference to this system exists and there is no aliasing.
let system = unsafe { &mut *systems[system_index].get() }; let system = unsafe { &mut *systems[system_index].get() };
// SAFETY: No exclusive system is running. if !self.can_run(system_index, system, conditions, world_cell) {
// Therefore, there is no existing mutable reference to the world.
let world = unsafe { &*cell.get() };
if !self.can_run(system_index, system, conditions, world) {
// NOTE: exclusive systems with ambiguities are susceptible to // NOTE: exclusive systems with ambiguities are susceptible to
// being significantly displaced here (compared to single-threaded order) // being significantly displaced here (compared to single-threaded order)
// if systems after them in topological order can run // if systems after them in topological order can run
@ -320,9 +321,10 @@ impl MultiThreadedExecutor {
self.ready_systems.set(system_index, false); self.ready_systems.set(system_index, false);
// SAFETY: Since `self.can_run` returned true earlier, it must have called // SAFETY: `can_run` returned true, which means that:
// `update_archetype_component_access` for each run condition. // - It must have called `update_archetype_component_access` for each run condition.
if !self.should_run(system_index, system, conditions, world) { // - There can be no systems running whose accesses would conflict with any conditions.
if !self.should_run(system_index, system, conditions, world_cell) {
self.skip_system_and_signal_dependents(system_index); self.skip_system_and_signal_dependents(system_index);
continue; continue;
} }
@ -331,10 +333,12 @@ impl MultiThreadedExecutor {
self.num_running_systems += 1; self.num_running_systems += 1;
if self.system_task_metadata[system_index].is_exclusive { if self.system_task_metadata[system_index].is_exclusive {
// SAFETY: `can_run` confirmed that no systems are running. // SAFETY: `can_run` returned true for this system, which means
// Therefore, there is no existing reference to the world. // that no other systems currently have access to the world.
let world = unsafe { world_cell.world_mut() };
// SAFETY: `can_run` returned true for this system,
// which means no systems are currently borrowed.
unsafe { unsafe {
let world = &mut *cell.get();
self.spawn_exclusive_system_task(scope, system_index, systems, world); self.spawn_exclusive_system_task(scope, system_index, systems, world);
} }
break; break;
@ -342,9 +346,10 @@ impl MultiThreadedExecutor {
// SAFETY: // SAFETY:
// - No other reference to this system exists. // - No other reference to this system exists.
// - `self.can_run` has been called, which calls `update_archetype_component_access` with this system. // - `can_run` has been called, which calls `update_archetype_component_access` with this system.
// - `can_run` returned true, so no systems with conflicting world access are running.
unsafe { unsafe {
self.spawn_system_task(scope, system_index, systems, world); self.spawn_system_task(scope, system_index, systems, world_cell);
} }
} }
@ -357,7 +362,7 @@ impl MultiThreadedExecutor {
system_index: usize, system_index: usize,
system: &mut BoxedSystem, system: &mut BoxedSystem,
conditions: &mut Conditions, conditions: &mut Conditions,
world: &World, world: UnsafeWorldCell,
) -> bool { ) -> bool {
let system_meta = &self.system_task_metadata[system_index]; let system_meta = &self.system_task_metadata[system_index];
if system_meta.is_exclusive && self.num_running_systems > 0 { if system_meta.is_exclusive && self.num_running_systems > 0 {
@ -413,15 +418,17 @@ impl MultiThreadedExecutor {
} }
/// # Safety /// # Safety
/// /// * `world` must have permission to read any world data required by
/// `update_archetype_component` must have been called with `world` /// the system's conditions: this includes conditions for the system
/// for each run condition in `conditions`. /// itself, and conditions for any of the system's sets.
/// * `update_archetype_component` must have been called with `world`
/// for each run condition in `conditions`.
unsafe fn should_run( unsafe fn should_run(
&mut self, &mut self,
system_index: usize, system_index: usize,
_system: &BoxedSystem, _system: &BoxedSystem,
conditions: &mut Conditions, conditions: &mut Conditions,
world: &World, world: UnsafeWorldCell,
) -> bool { ) -> bool {
let mut should_run = !self.skipped_systems.contains(system_index); let mut should_run = !self.skipped_systems.contains(system_index);
for set_idx in conditions.sets_with_conditions_of_systems[system_index].ones() { for set_idx in conditions.sets_with_conditions_of_systems[system_index].ones() {
@ -430,7 +437,10 @@ impl MultiThreadedExecutor {
} }
// Evaluate the system set's conditions. // Evaluate the system set's conditions.
// SAFETY: `update_archetype_component_access` has been called for each run condition. // SAFETY:
// - The caller ensures that `world` has permission to read any data
// required by the conditions.
// - `update_archetype_component_access` has been called for each run condition.
let set_conditions_met = let set_conditions_met =
evaluate_and_fold_conditions(&mut conditions.set_conditions[set_idx], world); evaluate_and_fold_conditions(&mut conditions.set_conditions[set_idx], world);
@ -444,7 +454,10 @@ impl MultiThreadedExecutor {
} }
// Evaluate the system's conditions. // Evaluate the system's conditions.
// SAFETY: `update_archetype_component_access` has been called for each run condition. // SAFETY:
// - The caller ensures that `world` has permission to read any data
// required by the conditions.
// - `update_archetype_component_access` has been called for each run condition.
let system_conditions_met = let system_conditions_met =
evaluate_and_fold_conditions(&mut conditions.system_conditions[system_index], world); evaluate_and_fold_conditions(&mut conditions.system_conditions[system_index], world);
@ -459,6 +472,8 @@ impl MultiThreadedExecutor {
/// # Safety /// # Safety
/// - Caller must not alias systems that are running. /// - Caller must not alias systems that are running.
/// - `world` must have permission to access the world data
/// used by the specified system.
/// - `update_archetype_component_access` must have been called with `world` /// - `update_archetype_component_access` must have been called with `world`
/// on the system assocaited with `system_index`. /// on the system assocaited with `system_index`.
unsafe fn spawn_system_task<'scope>( unsafe fn spawn_system_task<'scope>(
@ -466,7 +481,7 @@ impl MultiThreadedExecutor {
scope: &Scope<'_, 'scope, ()>, scope: &Scope<'_, 'scope, ()>,
system_index: usize, system_index: usize,
systems: &'scope [SyncUnsafeCell<BoxedSystem>], systems: &'scope [SyncUnsafeCell<BoxedSystem>],
world: &'scope World, world: UnsafeWorldCell<'scope>,
) { ) {
// SAFETY: this system is not running, no other reference exists // SAFETY: this system is not running, no other reference exists
let system = unsafe { &mut *systems[system_index].get() }; let system = unsafe { &mut *systems[system_index].get() };
@ -483,7 +498,8 @@ impl MultiThreadedExecutor {
let system_guard = system_span.enter(); let system_guard = system_span.enter();
let res = std::panic::catch_unwind(AssertUnwindSafe(|| { let res = std::panic::catch_unwind(AssertUnwindSafe(|| {
// SAFETY: // SAFETY:
// - Access: TODO. // - The caller ensures that we have permission to
// access the world data used by the system.
// - `update_archetype_component_access` has been called. // - `update_archetype_component_access` has been called.
unsafe { system.run_unsafe((), world) }; unsafe { system.run_unsafe((), world) };
})); }));
@ -688,10 +704,14 @@ fn apply_system_buffers(
} }
/// # Safety /// # Safety
/// /// - `world` must have permission to read any world data
/// `update_archetype_component_access` must have been called /// required by `conditions`.
/// with `world` for each condition in `conditions`. /// - `update_archetype_component_access` must have been called
unsafe fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &World) -> bool { /// with `world` for each condition in `conditions`.
unsafe fn evaluate_and_fold_conditions(
conditions: &mut [BoxedCondition],
world: UnsafeWorldCell,
) -> bool {
// not short-circuiting is intentional // not short-circuiting is intentional
#[allow(clippy::unnecessary_fold)] #[allow(clippy::unnecessary_fold)]
conditions conditions
@ -699,7 +719,8 @@ unsafe fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world:
.map(|condition| { .map(|condition| {
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
let _condition_span = info_span!("condition", name = &*condition.name()).entered(); let _condition_span = info_span!("condition", name = &*condition.name()).entered();
// SAFETY: caller ensures system access is compatible // SAFETY: The caller ensures that `world` has permission to
// access any data required by the condition.
unsafe { condition.run_unsafe((), world) } unsafe { condition.run_unsafe((), world) }
}) })
.fold(true, |acc, res| acc && res) .fold(true, |acc, res| acc && res)

View file

@ -7,6 +7,7 @@ use crate::{
component::{ComponentId, Tick}, component::{ComponentId, Tick},
prelude::World, prelude::World,
query::Access, query::Access,
world::unsafe_world_cell::UnsafeWorldCell,
}; };
use super::{ReadOnlySystem, System}; use super::{ReadOnlySystem, System};
@ -157,7 +158,7 @@ where
self.a.is_exclusive() || self.b.is_exclusive() self.a.is_exclusive() || self.b.is_exclusive()
} }
unsafe fn run_unsafe(&mut self, input: Self::In, world: &World) -> Self::Out { unsafe fn run_unsafe(&mut self, input: Self::In, world: UnsafeWorldCell) -> Self::Out {
Func::combine( Func::combine(
input, input,
// SAFETY: The world accesses for both underlying systems have been registered, // SAFETY: The world accesses for both underlying systems have been registered,
@ -198,7 +199,7 @@ where
self.component_access.extend(self.b.component_access()); self.component_access.extend(self.b.component_access());
} }
fn update_archetype_component_access(&mut self, world: &World) { fn update_archetype_component_access(&mut self, world: UnsafeWorldCell) {
self.a.update_archetype_component_access(world); self.a.update_archetype_component_access(world);
self.b.update_archetype_component_access(world); self.b.update_archetype_component_access(world);

View file

@ -200,7 +200,9 @@ impl<'w, 's> Commands<'w, 's> {
/// apps, and only when they have a scheme worked out to share an ID space (which doesn't happen /// apps, and only when they have a scheme worked out to share an ID space (which doesn't happen
/// by default). /// by default).
pub fn get_or_spawn<'a>(&'a mut self, entity: Entity) -> EntityCommands<'w, 's, 'a> { pub fn get_or_spawn<'a>(&'a mut self, entity: Entity) -> EntityCommands<'w, 's, 'a> {
self.add(GetOrSpawn { entity }); self.add(move |world: &mut World| {
world.get_or_spawn(entity);
});
EntityCommands { EntityCommands {
entity, entity,
commands: self, commands: self,
@ -839,8 +841,10 @@ where
} }
} }
/// A [`Command`] that spawns a new entity and adds the components in a [`Bundle`] to it.
#[derive(Debug)] #[derive(Debug)]
pub struct Spawn<T> { pub struct Spawn<T> {
/// The [`Bundle`] of components that will be added to the newly-spawned entity.
pub bundle: T, pub bundle: T,
} }
@ -853,16 +857,6 @@ where
} }
} }
pub struct GetOrSpawn {
entity: Entity,
}
impl Command for GetOrSpawn {
fn write(self, world: &mut World) {
world.get_or_spawn(self.entity);
}
}
pub struct SpawnBatch<I> pub struct SpawnBatch<I>
where where
I: IntoIterator, I: IntoIterator,
@ -907,6 +901,7 @@ where
} }
} }
/// A [`Command`] that despawns a specific entity.
#[derive(Debug)] #[derive(Debug)]
pub struct Despawn { pub struct Despawn {
pub entity: Entity, pub entity: Entity,
@ -918,8 +913,11 @@ impl Command for Despawn {
} }
} }
/// A [`Command`] that adds the components in a [`Bundle`] to an entity.
pub struct Insert<T> { pub struct Insert<T> {
/// The entity to which the components will be added.
pub entity: Entity, pub entity: Entity,
/// The [`Bundle`] containing the components that will be added to the entity.
pub bundle: T, pub bundle: T,
} }
@ -936,10 +934,13 @@ where
} }
} }
/// A [`Command`] that removes components from an entity.
/// For a [`Bundle`] type `T`, this will remove any components in the bundle.
/// Any components in the bundle that aren't found on the entity will be ignored.
#[derive(Debug)] #[derive(Debug)]
pub struct Remove<T> { pub struct Remove<T> {
pub entity: Entity, pub entity: Entity,
pub phantom: PhantomData<T>, _marker: PhantomData<T>,
} }
impl<T> Command for Remove<T> impl<T> Command for Remove<T>
@ -954,17 +955,19 @@ where
} }
impl<T> Remove<T> { impl<T> Remove<T> {
/// Creates a [`Command`] which will remove the specified [`Entity`] when flushed /// Creates a [`Command`] which will remove the specified [`Entity`] when applied.
pub const fn new(entity: Entity) -> Self { pub const fn new(entity: Entity) -> Self {
Self { Self {
entity, entity,
phantom: PhantomData::<T>, _marker: PhantomData,
} }
} }
} }
/// A [`Command`] that inserts a [`Resource`] into the world using a value
/// created with the [`FromWorld`] trait.
pub struct InitResource<R: Resource + FromWorld> { pub struct InitResource<R: Resource + FromWorld> {
_phantom: PhantomData<R>, _marker: PhantomData<R>,
} }
impl<R: Resource + FromWorld> Command for InitResource<R> { impl<R: Resource + FromWorld> Command for InitResource<R> {
@ -977,11 +980,12 @@ impl<R: Resource + FromWorld> InitResource<R> {
/// Creates a [`Command`] which will insert a default created [`Resource`] into the [`World`] /// Creates a [`Command`] which will insert a default created [`Resource`] into the [`World`]
pub const fn new() -> Self { pub const fn new() -> Self {
Self { Self {
_phantom: PhantomData::<R>, _marker: PhantomData,
} }
} }
} }
/// A [`Command`] that inserts a [`Resource`] into the world.
pub struct InsertResource<R: Resource> { pub struct InsertResource<R: Resource> {
pub resource: R, pub resource: R,
} }
@ -992,8 +996,9 @@ impl<R: Resource> Command for InsertResource<R> {
} }
} }
/// A [`Command`] that removes the [resource](Resource) `R` from the world.
pub struct RemoveResource<R: Resource> { pub struct RemoveResource<R: Resource> {
pub phantom: PhantomData<R>, _marker: PhantomData<R>,
} }
impl<R: Resource> Command for RemoveResource<R> { impl<R: Resource> Command for RemoveResource<R> {
@ -1006,7 +1011,7 @@ impl<R: Resource> RemoveResource<R> {
/// Creates a [`Command`] which will remove a [`Resource`] from the [`World`] /// Creates a [`Command`] which will remove a [`Resource`] from the [`World`]
pub const fn new() -> Self { pub const fn new() -> Self {
Self { Self {
phantom: PhantomData::<R>, _marker: PhantomData,
} }
} }
} }

View file

@ -6,7 +6,7 @@ use crate::{
check_system_change_tick, ExclusiveSystemParam, ExclusiveSystemParamItem, In, IntoSystem, check_system_change_tick, ExclusiveSystemParam, ExclusiveSystemParamItem, In, IntoSystem,
System, SystemMeta, System, SystemMeta,
}, },
world::World, world::{unsafe_world_cell::UnsafeWorldCell, World},
}; };
use bevy_utils::all_tuples; use bevy_utils::all_tuples;
@ -86,7 +86,7 @@ where
} }
#[inline] #[inline]
unsafe fn run_unsafe(&mut self, _input: Self::In, _world: &World) -> Self::Out { unsafe fn run_unsafe(&mut self, _input: Self::In, _world: UnsafeWorldCell) -> Self::Out {
panic!("Cannot run exclusive systems with a shared World reference"); panic!("Cannot run exclusive systems with a shared World reference");
} }
@ -134,7 +134,7 @@ where
self.param_state = Some(F::Param::init(world, &mut self.system_meta)); self.param_state = Some(F::Param::init(world, &mut self.system_meta));
} }
fn update_archetype_component_access(&mut self, _world: &World) {} fn update_archetype_component_access(&mut self, _world: UnsafeWorldCell) {}
#[inline] #[inline]
fn check_change_tick(&mut self, change_tick: Tick) { fn check_change_tick(&mut self, change_tick: Tick) {

View file

@ -4,7 +4,7 @@ use crate::{
prelude::FromWorld, prelude::FromWorld,
query::{Access, FilteredAccessSet}, query::{Access, FilteredAccessSet},
system::{check_system_change_tick, ReadOnlySystemParam, System, SystemParam, SystemParamItem}, system::{check_system_change_tick, ReadOnlySystemParam, System, SystemParam, SystemParamItem},
world::{World, WorldId}, world::{unsafe_world_cell::UnsafeWorldCell, World, WorldId},
}; };
use bevy_utils::all_tuples; use bevy_utils::all_tuples;
@ -417,7 +417,7 @@ where
} }
#[inline] #[inline]
unsafe fn run_unsafe(&mut self, input: Self::In, world: &World) -> Self::Out { unsafe fn run_unsafe(&mut self, input: Self::In, world: UnsafeWorldCell) -> Self::Out {
let change_tick = world.increment_change_tick(); let change_tick = world.increment_change_tick();
// SAFETY: // SAFETY:
@ -428,7 +428,7 @@ where
let params = F::Param::get_param( let params = F::Param::get_param(
self.param_state.as_mut().expect(Self::PARAM_MESSAGE), self.param_state.as_mut().expect(Self::PARAM_MESSAGE),
&self.system_meta, &self.system_meta,
world.as_unsafe_world_cell_migration_internal(), world,
change_tick, change_tick,
); );
let out = self.func.run(input, params); let out = self.func.run(input, params);
@ -457,7 +457,7 @@ where
self.param_state = Some(F::Param::init_state(world, &mut self.system_meta)); self.param_state = Some(F::Param::init_state(world, &mut self.system_meta));
} }
fn update_archetype_component_access(&mut self, world: &World) { fn update_archetype_component_access(&mut self, world: UnsafeWorldCell) {
assert!(self.world_id == Some(world.id()), "Encountered a mismatched World. A System cannot be used with Worlds other than the one it was initialized with."); assert!(self.world_id == Some(world.id()), "Encountered a mismatched World. A System cannot be used with Worlds other than the one it was initialized with.");
let archetypes = world.archetypes(); let archetypes = world.archetypes();
let new_generation = archetypes.generation(); let new_generation = archetypes.generation();

View file

@ -1610,7 +1610,7 @@ mod tests {
// set up system and verify its access is empty // set up system and verify its access is empty
system.initialize(&mut world); system.initialize(&mut world);
system.update_archetype_component_access(&world); system.update_archetype_component_access(world.as_unsafe_world_cell());
assert_eq!( assert_eq!(
system system
.archetype_component_access() .archetype_component_access()
@ -1640,7 +1640,7 @@ mod tests {
world.spawn((B, C)); world.spawn((B, C));
// update system and verify its accesses are correct // update system and verify its accesses are correct
system.update_archetype_component_access(&world); system.update_archetype_component_access(world.as_unsafe_world_cell());
assert_eq!( assert_eq!(
system system
.archetype_component_access() .archetype_component_access()
@ -1658,7 +1658,7 @@ mod tests {
.unwrap(), .unwrap(),
); );
world.spawn((A, B, D)); world.spawn((A, B, D));
system.update_archetype_component_access(&world); system.update_archetype_component_access(world.as_unsafe_world_cell());
assert_eq!( assert_eq!(
system system
.archetype_component_access() .archetype_component_access()

View file

@ -1054,7 +1054,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
/// Returns a mutable reference to the component `T` of the given entity. /// Returns a mutable reference to the component `T` of the given entity.
/// ///
/// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is returned instead. /// In case of a nonexisting entity or mismatched component, a [`QueryComponentError`] is returned instead.
/// ///
/// # Example /// # Example
/// ///
@ -1090,7 +1090,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
/// Returns a mutable reference to the component `T` of the given entity. /// Returns a mutable reference to the component `T` of the given entity.
/// ///
/// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is returned instead. /// In case of a nonexisting entity or mismatched component, a [`QueryComponentError`] is returned instead.
/// ///
/// # Safety /// # Safety
/// ///
@ -1357,12 +1357,69 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> IntoIterator for &'w mut Quer
} }
} }
/// An error that occurs when retrieving a specific [`Entity`]'s component from a [`Query`] /// An error that occurs when retrieving a specific [`Entity`]'s component from a [`Query`].
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum QueryComponentError { pub enum QueryComponentError {
/// The [`Query`] does not have read access to the requested component.
///
/// This error occurs when the requested component is not included in the original query.
///
/// # Example
///
/// ```
/// # use bevy_ecs::{prelude::*, system::QueryComponentError};
/// #
/// # #[derive(Component)]
/// # struct OtherComponent;
/// #
/// # #[derive(Component, PartialEq, Debug)]
/// # struct RequestedComponent;
/// #
/// # #[derive(Resource)]
/// # struct SpecificEntity {
/// # entity: Entity,
/// # }
/// #
/// fn get_missing_read_access_error(query: Query<&OtherComponent>, res: Res<SpecificEntity>) {
/// assert_eq!(
/// query.get_component::<RequestedComponent>(res.entity),
/// Err(QueryComponentError::MissingReadAccess),
/// );
/// println!("query doesn't have read access to RequestedComponent because it does not appear in Query<&OtherComponent>");
/// }
/// # bevy_ecs::system::assert_is_system(get_missing_read_access_error);
/// ```
MissingReadAccess, MissingReadAccess,
/// The [`Query`] does not have write access to the requested component.
///
/// This error occurs when the requested component is not included in the original query, or the mutability of the requested component is mismatched with the original query.
///
/// # Example
///
/// ```
/// # use bevy_ecs::{prelude::*, system::QueryComponentError};
/// #
/// # #[derive(Component, PartialEq, Debug)]
/// # struct RequestedComponent;
/// #
/// # #[derive(Resource)]
/// # struct SpecificEntity {
/// # entity: Entity,
/// # }
/// #
/// fn get_missing_write_access_error(mut query: Query<&RequestedComponent>, res: Res<SpecificEntity>) {
/// assert_eq!(
/// query.get_component::<RequestedComponent>(res.entity),
/// Err(QueryComponentError::MissingWriteAccess),
/// );
/// println!("query doesn't have write access to RequestedComponent because it doesn't have &mut in Query<&RequestedComponent>");
/// }
/// # bevy_ecs::system::assert_is_system(get_missing_write_access_error);
/// ```
MissingWriteAccess, MissingWriteAccess,
/// The given [`Entity`] does not have the requested component.
MissingComponent, MissingComponent,
/// The requested [`Entity`] does not exist.
NoSuchEntity, NoSuchEntity,
} }

View file

@ -2,6 +2,7 @@ use bevy_utils::tracing::warn;
use core::fmt::Debug; use core::fmt::Debug;
use crate::component::Tick; use crate::component::Tick;
use crate::world::unsafe_world_cell::UnsafeWorldCell;
use crate::{archetype::ArchetypeComponentId, component::ComponentId, query::Access, world::World}; use crate::{archetype::ArchetypeComponentId, component::ComponentId, query::Access, world::World};
use std::any::TypeId; use std::any::TypeId;
@ -39,26 +40,24 @@ pub trait System: Send + Sync + 'static {
fn is_exclusive(&self) -> bool; fn is_exclusive(&self) -> bool;
/// Runs the system with the given input in the world. Unlike [`System::run`], this function /// Runs the system with the given input in the world. Unlike [`System::run`], this function
/// takes a shared reference to [`World`] and may therefore break Rust's aliasing rules, making /// can be called in parallel with other systems and may break Rust's aliasing rules
/// it unsafe to call. /// if used incorrectly, making it unsafe to call.
/// ///
/// # Safety /// # Safety
/// ///
/// This might access world and resources in an unsafe manner. This should only be called in one /// - The caller must ensure that `world` has permission to access any world data
/// of the following contexts: /// registered in [`Self::archetype_component_access`]. There must be no conflicting
/// 1. This system is the only system running on the given world across all threads. /// simultaneous accesses while the system is running.
/// 2. This system only runs in parallel with other systems that do not conflict with the /// - The method [`Self::update_archetype_component_access`] must be called at some
/// [`System::archetype_component_access()`]. /// point before this one, with the same exact [`World`]. If `update_archetype_component_access`
/// /// panics (or otherwise does not return for any reason), this method must not be called.
/// Additionally, the method [`Self::update_archetype_component_access`] must be called at some unsafe fn run_unsafe(&mut self, input: Self::In, world: UnsafeWorldCell) -> Self::Out;
/// point before this one, with the same exact [`World`]. If `update_archetype_component_access`
/// panics (or otherwise does not return for any reason), this method must not be called.
unsafe fn run_unsafe(&mut self, input: Self::In, world: &World) -> Self::Out;
/// Runs the system with the given input in the world. /// Runs the system with the given input in the world.
fn run(&mut self, input: Self::In, world: &mut World) -> Self::Out { fn run(&mut self, input: Self::In, world: &mut World) -> Self::Out {
let world = world.as_unsafe_world_cell();
self.update_archetype_component_access(world); self.update_archetype_component_access(world);
// SAFETY: // SAFETY:
// - World and resources are exclusively borrowed, which ensures no data access conflicts. // - We have exclusive access to the entire world.
// - `update_archetype_component_access` has been called. // - `update_archetype_component_access` has been called.
unsafe { self.run_unsafe(input, world) } unsafe { self.run_unsafe(input, world) }
} }
@ -66,7 +65,11 @@ pub trait System: Send + Sync + 'static {
/// Initialize the system. /// Initialize the system.
fn initialize(&mut self, _world: &mut World); fn initialize(&mut self, _world: &mut World);
/// Update the system's archetype component [`Access`]. /// Update the system's archetype component [`Access`].
fn update_archetype_component_access(&mut self, world: &World); ///
/// ## Note for implementors
/// `world` may only be used to access metadata. This can be done in safe code
/// via functions such as [`UnsafeWorldCell::archetypes`].
fn update_archetype_component_access(&mut self, world: UnsafeWorldCell);
fn check_change_tick(&mut self, change_tick: Tick); fn check_change_tick(&mut self, change_tick: Tick);
/// Returns the system's default [system sets](crate::schedule::SystemSet). /// Returns the system's default [system sets](crate::schedule::SystemSet).
fn default_system_sets(&self) -> Vec<Box<dyn crate::schedule::SystemSet>> { fn default_system_sets(&self) -> Vec<Box<dyn crate::schedule::SystemSet>> {

View file

@ -44,10 +44,10 @@ impl FromWorld for WorldId {
} }
} }
// SAFETY: Has read-only access to shared World metadata // SAFETY: No world data is accessed.
unsafe impl ReadOnlySystemParam for WorldId {} unsafe impl ReadOnlySystemParam for WorldId {}
// SAFETY: A World's ID is immutable and fetching it from the World does not borrow anything // SAFETY: No world data is accessed.
unsafe impl SystemParam for WorldId { unsafe impl SystemParam for WorldId {
type State = (); type State = ();
@ -61,7 +61,7 @@ unsafe impl SystemParam for WorldId {
world: UnsafeWorldCell<'world>, world: UnsafeWorldCell<'world>,
_: Tick, _: Tick,
) -> Self::Item<'world, 'state> { ) -> Self::Item<'world, 'state> {
world.world_metadata().id() world.id()
} }
} }

View file

@ -1,6 +1,6 @@
#![warn(unsafe_op_in_unsafe_fn)] #![warn(unsafe_op_in_unsafe_fn)]
use super::{Mut, World}; use super::{Mut, World, WorldId};
use crate::{ use crate::{
archetype::{Archetype, ArchetypeComponentId, Archetypes}, archetype::{Archetype, ArchetypeComponentId, Archetypes},
bundle::Bundles, bundle::Bundles,
@ -190,6 +190,14 @@ impl<'w> UnsafeWorldCell<'w> {
unsafe { &*self.0 } unsafe { &*self.0 }
} }
/// Retrieves this world's unique [ID](WorldId).
#[inline]
pub fn id(self) -> WorldId {
// SAFETY:
// - we only access world metadata
unsafe { self.world_metadata() }.id()
}
/// Retrieves this world's [Entities] collection /// Retrieves this world's [Entities] collection
#[inline] #[inline]
pub fn entities(self) -> &'w Entities { pub fn entities(self) -> &'w Entities {

View file

@ -472,22 +472,39 @@ impl<P: Point> CubicCurve<P> {
/// A flexible iterator used to sample curves with arbitrary functions. /// A flexible iterator used to sample curves with arbitrary functions.
/// ///
/// This splits the curve into `subdivisions` of evenly spaced `t` values across the /// This splits the curve into `subdivisions` of evenly spaced `t` values across the
/// length of the curve from start (t = 0) to end (t = 1), returning an iterator that evaluates /// length of the curve from start (t = 0) to end (t = n), where `n = self.segment_count()`,
/// the curve with the supplied `sample_function` at each `t`. /// returning an iterator evaluating the curve with the supplied `sample_function` at each `t`.
/// ///
/// Given `subdivisions = 2`, this will split the curve into two lines, or three points, and /// For `subdivisions = 2`, this will split the curve into two lines, or three points, and
/// return an iterator over those three points, one at the start, middle, and end. /// return an iterator with 3 items, the three points, one at the start, middle, and end.
#[inline] #[inline]
pub fn iter_samples( pub fn iter_samples<'a, 'b: 'a>(
&self, &'b self,
subdivisions: usize, subdivisions: usize,
sample_function: fn(&Self, f32) -> P, mut sample_function: impl FnMut(&Self, f32) -> P + 'a,
) -> impl Iterator<Item = P> + '_ { ) -> impl Iterator<Item = P> + 'a {
(0..=subdivisions).map(move |i| { self.iter_uniformly(subdivisions)
let segments = self.segments.len() as f32; .map(move |t| sample_function(self, t))
let t = i as f32 / subdivisions as f32 * segments; }
sample_function(self, t)
}) /// An iterator that returns values of `t` uniformly spaced over `0..=subdivisions`.
#[inline]
fn iter_uniformly(&self, subdivisions: usize) -> impl Iterator<Item = f32> {
let segments = self.segments.len() as f32;
let step = segments / subdivisions as f32;
(0..=subdivisions).map(move |i| i as f32 * step)
}
/// The list of segments contained in this `CubicCurve`.
///
/// This spline's global `t` value is equal to how many segments it has.
///
/// All method accepting `t` on `CubicCurve` depends on the global `t`.
/// When sampling over the entire curve, you should either use one of the
/// `iter_*` methods or account for the segment count using `curve.segments().len()`.
#[inline]
pub fn segments(&self) -> &[CubicSegment<P>] {
&self.segments
} }
/// Iterate over the curve split into `subdivisions`, sampling the position at each step. /// Iterate over the curve split into `subdivisions`, sampling the position at each step.

View file

@ -491,6 +491,9 @@ pub fn queue_material_meshes<M: Material>(
AlphaMode::Multiply => { AlphaMode::Multiply => {
mesh_key |= MeshPipelineKey::BLEND_MULTIPLY; mesh_key |= MeshPipelineKey::BLEND_MULTIPLY;
} }
AlphaMode::Mask(_) => {
mesh_key |= MeshPipelineKey::MAY_DISCARD;
}
_ => (), _ => (),
} }

View file

@ -364,8 +364,8 @@ where
shader_defs.push("DEPTH_PREPASS".into()); shader_defs.push("DEPTH_PREPASS".into());
} }
if key.mesh_key.contains(MeshPipelineKey::ALPHA_MASK) { if key.mesh_key.contains(MeshPipelineKey::MAY_DISCARD) {
shader_defs.push("ALPHA_MASK".into()); shader_defs.push("MAY_DISCARD".into());
} }
let blend_key = key let blend_key = key
@ -467,9 +467,7 @@ where
// is enabled or the material uses alpha cutoff values and doesn't rely on the standard // is enabled or the material uses alpha cutoff values and doesn't rely on the standard
// prepass shader // prepass shader
let fragment_required = !targets.is_empty() let fragment_required = !targets.is_empty()
|| ((key.mesh_key.contains(MeshPipelineKey::ALPHA_MASK) || (key.mesh_key.contains(MeshPipelineKey::MAY_DISCARD)
|| blend_key == MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA
|| blend_key == MeshPipelineKey::BLEND_ALPHA)
&& self.material_fragment_shader.is_some()); && self.material_fragment_shader.is_some());
let fragment = fragment_required.then(|| { let fragment = fragment_required.then(|| {
@ -967,7 +965,7 @@ pub fn queue_prepass_material_meshes<M: Material>(
let alpha_mode = material.properties.alpha_mode; let alpha_mode = material.properties.alpha_mode;
match alpha_mode { match alpha_mode {
AlphaMode::Opaque => {} AlphaMode::Opaque => {}
AlphaMode::Mask(_) => mesh_key |= MeshPipelineKey::ALPHA_MASK, AlphaMode::Mask(_) => mesh_key |= MeshPipelineKey::MAY_DISCARD,
AlphaMode::Blend AlphaMode::Blend
| AlphaMode::Premultiplied | AlphaMode::Premultiplied
| AlphaMode::Add | AlphaMode::Add

View file

@ -1608,11 +1608,11 @@ pub fn queue_shadows<M: Material>(
} }
let alpha_mode = material.properties.alpha_mode; let alpha_mode = material.properties.alpha_mode;
match alpha_mode { match alpha_mode {
AlphaMode::Mask(_) => { AlphaMode::Mask(_)
mesh_key |= MeshPipelineKey::ALPHA_MASK; | AlphaMode::Blend
} | AlphaMode::Premultiplied
AlphaMode::Blend | AlphaMode::Premultiplied | AlphaMode::Add => { | AlphaMode::Add => {
mesh_key |= MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA; mesh_key |= MeshPipelineKey::MAY_DISCARD;
} }
_ => {} _ => {}
} }

View file

@ -616,7 +616,8 @@ bitflags::bitflags! {
const DEPTH_PREPASS = (1 << 3); const DEPTH_PREPASS = (1 << 3);
const NORMAL_PREPASS = (1 << 4); const NORMAL_PREPASS = (1 << 4);
const MOTION_VECTOR_PREPASS = (1 << 5); const MOTION_VECTOR_PREPASS = (1 << 5);
const ALPHA_MASK = (1 << 6); const MAY_DISCARD = (1 << 6); // Guards shader codepaths that may discard, allowing early depth tests in most cases
// See: https://www.khronos.org/opengl/wiki/Early_Fragment_Test
const ENVIRONMENT_MAP = (1 << 7); const ENVIRONMENT_MAP = (1 << 7);
const DEPTH_CLAMP_ORTHO = (1 << 8); const DEPTH_CLAMP_ORTHO = (1 << 8);
const TAA = (1 << 9); const TAA = (1 << 9);
@ -830,6 +831,10 @@ impl SpecializedMeshPipeline for MeshPipeline {
} }
} }
if key.contains(MeshPipelineKey::MAY_DISCARD) {
shader_defs.push("MAY_DISCARD".into());
}
if key.contains(MeshPipelineKey::ENVIRONMENT_MAP) { if key.contains(MeshPipelineKey::ENVIRONMENT_MAP) {
shader_defs.push("ENVIRONMENT_MAP".into()); shader_defs.push("ENVIRONMENT_MAP".into());
} }

View file

@ -11,16 +11,20 @@ fn alpha_discard(material: StandardMaterial, output_color: vec4<f32>) -> vec4<f3
if alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE { if alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE {
// NOTE: If rendering as opaque, alpha should be ignored so set to 1.0 // NOTE: If rendering as opaque, alpha should be ignored so set to 1.0
color.a = 1.0; color.a = 1.0;
} else if alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK { }
#ifdef MAY_DISCARD
else if alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK {
if color.a >= material.alpha_cutoff { if color.a >= material.alpha_cutoff {
// NOTE: If rendering as masked alpha and >= the cutoff, render as fully opaque // NOTE: If rendering as masked alpha and >= the cutoff, render as fully opaque
color.a = 1.0; color.a = 1.0;
} else { } else {
// NOTE: output_color.a < in.material.alpha_cutoff should not is not rendered // NOTE: output_color.a < in.material.alpha_cutoff should not be rendered
// NOTE: This and any other discards mean that early-z testing cannot be done!
discard; discard;
} }
} }
#endif
return color; return color;
} }

View file

@ -30,19 +30,7 @@ const PREMULTIPLIED_ALPHA_CUTOFF = 0.05;
// We can use a simplified version of alpha_discard() here since we only need to handle the alpha_cutoff // We can use a simplified version of alpha_discard() here since we only need to handle the alpha_cutoff
fn prepass_alpha_discard(in: FragmentInput) { fn prepass_alpha_discard(in: FragmentInput) {
// This is a workaround since the preprocessor does not support #ifdef MAY_DISCARD
// #if defined(ALPHA_MASK) || defined(BLEND_PREMULTIPLIED_ALPHA)
#ifndef ALPHA_MASK
#ifndef BLEND_PREMULTIPLIED_ALPHA
#ifndef BLEND_ALPHA
#define EMPTY_PREPASS_ALPHA_DISCARD
#endif // BLEND_ALPHA
#endif // BLEND_PREMULTIPLIED_ALPHA not defined
#endif // ALPHA_MASK not defined
#ifndef EMPTY_PREPASS_ALPHA_DISCARD
var output_color: vec4<f32> = material.base_color; var output_color: vec4<f32> = material.base_color;
#ifdef VERTEX_UVS #ifdef VERTEX_UVS
@ -51,22 +39,22 @@ fn prepass_alpha_discard(in: FragmentInput) {
} }
#endif // VERTEX_UVS #endif // VERTEX_UVS
#ifdef ALPHA_MASK
if ((material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK) != 0u) && output_color.a < material.alpha_cutoff {
discard;
}
#else // BLEND_PREMULTIPLIED_ALPHA || BLEND_ALPHA
let alpha_mode = material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS; let alpha_mode = material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS;
if (alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND || alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_ADD) if alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK {
&& output_color.a < PREMULTIPLIED_ALPHA_CUTOFF { if output_color.a < material.alpha_cutoff {
discard; discard;
} else if alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_PREMULTIPLIED }
&& all(output_color < vec4(PREMULTIPLIED_ALPHA_CUTOFF)) { } else if (alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND || alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_ADD) {
discard; if output_color.a < PREMULTIPLIED_ALPHA_CUTOFF {
discard;
}
} else if alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_PREMULTIPLIED {
if all(output_color < vec4(PREMULTIPLIED_ALPHA_CUTOFF)) {
discard;
}
} }
#endif // !ALPHA_MASK
#endif // EMPTY_PREPASS_ALPHA_DISCARD not defined #endif // MAY_DISCARD
} }
#ifdef PREPASS_FRAGMENT #ifdef PREPASS_FRAGMENT

View file

@ -1,10 +1,13 @@
use crate::fq_std::FQDefault; use crate::derive_data::StructField;
use crate::field_attributes::DefaultBehavior;
use crate::fq_std::{FQDefault, FQOption};
use crate::{ use crate::{
derive_data::{EnumVariantFields, ReflectEnum}, derive_data::{EnumVariantFields, ReflectEnum},
utility::ident_or_index, utility::ident_or_index,
}; };
use proc_macro2::Ident; use proc_macro2::Ident;
use quote::{quote, ToTokens}; use quote::{quote, ToTokens};
use syn::Member;
/// Contains all data needed to construct all variants within an enum. /// Contains all data needed to construct all variants within an enum.
pub(crate) struct EnumVariantConstructors { pub(crate) struct EnumVariantConstructors {
@ -30,7 +33,7 @@ pub(crate) fn get_variant_constructors(
let name = ident.to_string(); let name = ident.to_string();
let variant_constructor = reflect_enum.get_unit(ident); let variant_constructor = reflect_enum.get_unit(ident);
let fields = match &variant.fields { let fields: &[StructField] = match &variant.fields {
EnumVariantFields::Unit => &[], EnumVariantFields::Unit => &[],
EnumVariantFields::Named(fields) | EnumVariantFields::Unnamed(fields) => { EnumVariantFields::Named(fields) | EnumVariantFields::Unnamed(fields) => {
fields.as_slice() fields.as_slice()
@ -39,35 +42,59 @@ pub(crate) fn get_variant_constructors(
let mut reflect_index: usize = 0; let mut reflect_index: usize = 0;
let constructor_fields = fields.iter().enumerate().map(|(declare_index, field)| { let constructor_fields = fields.iter().enumerate().map(|(declare_index, field)| {
let field_ident = ident_or_index(field.data.ident.as_ref(), declare_index); let field_ident = ident_or_index(field.data.ident.as_ref(), declare_index);
let field_ty = &field.data.ty;
let field_value = if field.attrs.ignore.is_ignored() { let field_value = if field.attrs.ignore.is_ignored() {
quote! { #FQDefault::default() } match &field.attrs.default {
DefaultBehavior::Func(path) => quote! { #path() },
_ => quote! { #FQDefault::default() }
}
} else { } else {
let error_repr = field.data.ident.as_ref().map_or_else( let (resolve_error, resolve_missing) = if can_panic {
|| format!("at index {reflect_index}"), let field_ref_str = match &field_ident {
|name| format!("`{name}`"), Member::Named(ident) => format!("the field `{ident}`"),
); Member::Unnamed(index) => format!("the field at index {}", index.index)
let unwrapper = if can_panic { };
let type_err_message = format!( let ty = field.data.ty.to_token_stream();
"the field {error_repr} should be of type `{}`",
field.data.ty.to_token_stream() let on_error = format!("{field_ref_str} should be of type `{ty}`");
); let on_missing = format!("{field_ref_str} is required but could not be found");
quote!(.expect(#type_err_message))
(quote!(.expect(#on_error)), quote!(.expect(#on_missing)))
} else { } else {
quote!(?) (quote!(?), quote!(?))
}; };
let field_accessor = match &field.data.ident { let field_accessor = match &field.data.ident {
Some(ident) => { Some(ident) => {
let name = ident.to_string(); let name = ident.to_string();
quote!(.field(#name)) quote!(#ref_value.field(#name))
} }
None => quote!(.field_at(#reflect_index)), None => quote!(#ref_value.field_at(#reflect_index)),
}; };
reflect_index += 1; reflect_index += 1;
let missing_field_err_message = format!("the field {error_repr} was not declared");
let accessor = quote!(#field_accessor .expect(#missing_field_err_message)); match &field.attrs.default {
quote! { DefaultBehavior::Func(path) => quote! {
#bevy_reflect_path::FromReflect::from_reflect(#ref_value #accessor) if let #FQOption::Some(field) = #field_accessor {
#unwrapper <#field_ty as #bevy_reflect_path::FromReflect>::from_reflect(field)
#resolve_error
} else {
#path()
}
},
DefaultBehavior::Default => quote! {
if let #FQOption::Some(field) = #field_accessor {
<#field_ty as #bevy_reflect_path::FromReflect>::from_reflect(field)
#resolve_error
} else {
#FQDefault::default()
}
},
DefaultBehavior::Required => quote! {
<#field_ty as #bevy_reflect_path::FromReflect>::from_reflect(#field_accessor #resolve_missing)
#resolve_error
},
} }
}; };
quote! { #field_ident : #field_value } quote! { #field_ident : #field_value }

View file

@ -87,7 +87,8 @@ pub(crate) static REFLECT_VALUE_ATTRIBUTE_NAME: &str = "reflect_value";
/// to improve performance and/or robustness. /// to improve performance and/or robustness.
/// An example of where this is used is in the [`FromReflect`] derive macro, /// An example of where this is used is in the [`FromReflect`] derive macro,
/// where adding this attribute will cause the `FromReflect` implementation to create /// where adding this attribute will cause the `FromReflect` implementation to create
/// a base value using its [`Default`] implementation avoiding issues with ignored fields. /// a base value using its [`Default`] implementation avoiding issues with ignored fields
/// (for structs and tuple structs only).
/// ///
/// ## `#[reflect_value]` /// ## `#[reflect_value]`
/// ///

View file

@ -751,6 +751,39 @@ mod tests {
assert_eq!(Some(expected), my_struct); assert_eq!(Some(expected), my_struct);
} }
#[test]
fn from_reflect_should_use_default_variant_field_attributes() {
#[derive(Reflect, FromReflect, Eq, PartialEq, Debug)]
enum MyEnum {
Foo(#[reflect(default)] String),
Bar {
#[reflect(default = "get_baz_default")]
#[reflect(ignore)]
baz: usize,
},
}
fn get_baz_default() -> usize {
123
}
let expected = MyEnum::Foo(String::default());
let dyn_enum = DynamicEnum::new("Foo", DynamicTuple::default());
let my_enum = <MyEnum as FromReflect>::from_reflect(&dyn_enum);
assert_eq!(Some(expected), my_enum);
let expected = MyEnum::Bar {
baz: get_baz_default(),
};
let dyn_enum = DynamicEnum::new("Bar", DynamicStruct::default());
let my_enum = <MyEnum as FromReflect>::from_reflect(&dyn_enum);
assert_eq!(Some(expected), my_enum);
}
#[test] #[test]
fn from_reflect_should_use_default_container_attribute() { fn from_reflect_should_use_default_container_attribute() {
#[derive(Reflect, FromReflect, Eq, PartialEq, Debug)] #[derive(Reflect, FromReflect, Eq, PartialEq, Debug)]

View file

@ -199,7 +199,7 @@ impl Image {
.map(DynamicImage::ImageRgba8), .map(DynamicImage::ImageRgba8),
// This format is commonly used as the format for the swapchain texture // This format is commonly used as the format for the swapchain texture
// This conversion is added here to support screenshots // This conversion is added here to support screenshots
TextureFormat::Bgra8UnormSrgb => ImageBuffer::from_raw( TextureFormat::Bgra8UnormSrgb | TextureFormat::Bgra8Unorm => ImageBuffer::from_raw(
self.texture_descriptor.size.width, self.texture_descriptor.size.width,
self.texture_descriptor.size.height, self.texture_descriptor.size.height,
{ {

View file

@ -377,7 +377,7 @@ pub fn prepare_windows(
mip_level_count: 1, mip_level_count: 1,
sample_count: 1, sample_count: 1,
dimension: wgpu::TextureDimension::D2, dimension: wgpu::TextureDimension::D2,
format: surface_configuration.format, format: surface_configuration.format.add_srgb_suffix(),
usage: TextureUsages::RENDER_ATTACHMENT usage: TextureUsages::RENDER_ATTACHMENT
| TextureUsages::COPY_SRC | TextureUsages::COPY_SRC
| TextureUsages::TEXTURE_BINDING, | TextureUsages::TEXTURE_BINDING,

View file

@ -234,16 +234,11 @@ impl SpecializedRenderPipeline for ScreenshotToScreenPipeline {
shader: SCREENSHOT_SHADER_HANDLE.typed(), shader: SCREENSHOT_SHADER_HANDLE.typed(),
}, },
primitive: wgpu::PrimitiveState { primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back), cull_mode: Some(wgpu::Face::Back),
polygon_mode: wgpu::PolygonMode::Fill, ..Default::default()
conservative: false,
unclipped_depth: false,
}, },
depth_stencil: None, depth_stencil: None,
multisample: wgpu::MultisampleState::default(), multisample: Default::default(),
fragment: Some(FragmentState { fragment: Some(FragmentState {
shader: SCREENSHOT_SHADER_HANDLE.typed(), shader: SCREENSHOT_SHADER_HANDLE.typed(),
entry_point: Cow::Borrowed("fs_main"), entry_point: Cow::Borrowed("fs_main"),

View file

@ -10,7 +10,7 @@ keywords = ["bevy"]
[features] [features]
trace = [] trace = []
wayland = ["winit/wayland"] wayland = ["winit/wayland", "winit/wayland-csd-adwaita"]
x11 = ["winit/x11"] x11 = ["winit/x11"]
accesskit_unix = ["accesskit_winit/accesskit_unix"] accesskit_unix = ["accesskit_winit/accesskit_unix"]

View file

@ -99,7 +99,7 @@ fn star(
// We can now spawn the entities for the star and the camera // We can now spawn the entities for the star and the camera
commands.spawn(( commands.spawn((
// We use a marker component to identify the custom colored meshes // We use a marker component to identify the custom colored meshes
ColoredMesh2d::default(), ColoredMesh2d,
// The `Handle<Mesh>` needs to be wrapped in a `Mesh2dHandle` to use 2d rendering instead of 3d // The `Handle<Mesh>` needs to be wrapped in a `Mesh2dHandle` to use 2d rendering instead of 3d
Mesh2dHandle(meshes.add(star)), Mesh2dHandle(meshes.add(star)),
// This bundle's components are needed for something to be rendered // This bundle's components are needed for something to be rendered

View file

@ -302,7 +302,7 @@ fn example_control_system(
let randomize_colors = input.just_pressed(KeyCode::C); let randomize_colors = input.just_pressed(KeyCode::C);
for (material_handle, controls) in &controllable { for (material_handle, controls) in &controllable {
let mut material = materials.get_mut(material_handle).unwrap(); let material = materials.get_mut(material_handle).unwrap();
material.base_color.set_a(state.alpha); material.base_color.set_a(state.alpha);
if controls.color && randomize_colors { if controls.color && randomize_colors {

View file

@ -378,7 +378,7 @@ fn update_normal(
return; return;
} }
if let Some(normal) = normal.0.as_ref() { if let Some(normal) = normal.0.as_ref() {
if let Some(mut image) = images.get_mut(normal) { if let Some(image) = images.get_mut(normal) {
image.texture_descriptor.format = TextureFormat::Rgba8Unorm; image.texture_descriptor.format = TextureFormat::Rgba8Unorm;
*already_ran = true; *already_ran = true;
} }

View file

@ -145,7 +145,7 @@ fn asset_loaded(
&& asset_server.get_load_state(cubemap.image_handle.clone_weak()) == LoadState::Loaded && asset_server.get_load_state(cubemap.image_handle.clone_weak()) == LoadState::Loaded
{ {
info!("Swapping to {}...", CUBEMAPS[cubemap.index].0); info!("Swapping to {}...", CUBEMAPS[cubemap.index].0);
let mut image = images.get_mut(&cubemap.image_handle).unwrap(); let image = images.get_mut(&cubemap.image_handle).unwrap();
// NOTE: PNGs do not have any metadata that could indicate they contain a cubemap texture, // NOTE: PNGs do not have any metadata that could indicate they contain a cubemap texture,
// so they appear as one texture. The following code reconfigures the texture as necessary. // so they appear as one texture. The following code reconfigures the texture as necessary.
if image.texture_descriptor.array_layer_count() == 1 { if image.texture_descriptor.array_layer_count() == 1 {

View file

@ -10,7 +10,7 @@ use rand::{thread_rng, Rng};
fn main() { fn main() {
App::new() App::new()
.add_plugins(DefaultPlugins) .add_plugins(DefaultPlugins)
.add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(FrameTimeDiagnosticsPlugin)
.add_plugin(LogDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default())
.add_systems(Startup, setup) .add_systems(Startup, setup)
.add_systems(Update, (light_sway, movement)) .add_systems(Update, (light_sway, movement))

View file

@ -430,7 +430,7 @@ fn update_color_grading_settings(
mut selected_parameter: ResMut<SelectedParameter>, mut selected_parameter: ResMut<SelectedParameter>,
) { ) {
let method = tonemapping.single(); let method = tonemapping.single();
let mut color_grading = per_method_settings.settings.get_mut(method).unwrap(); let color_grading = per_method_settings.settings.get_mut(method).unwrap();
let mut dt = time.delta_seconds() * 0.25; let mut dt = time.delta_seconds() * 0.25;
if keys.pressed(KeyCode::Left) { if keys.pressed(KeyCode::Left) {
dt = -dt; dt = -dt;

View file

@ -9,7 +9,7 @@ fn main() {
App::new() App::new()
.add_plugins(DefaultPlugins) .add_plugins(DefaultPlugins)
// Adds frame time diagnostics // Adds frame time diagnostics
.add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(FrameTimeDiagnosticsPlugin)
// Adds a system that prints diagnostics to the console // Adds a system that prints diagnostics to the console
.add_plugin(LogDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default())
// Any plugin can register diagnostics // Any plugin can register diagnostics

View file

@ -18,15 +18,20 @@ fn main() {
// The common_conditions module has a few useful run conditions // The common_conditions module has a few useful run conditions
// for checking resources and states. These are included in the prelude. // for checking resources and states. These are included in the prelude.
.run_if(resource_exists::<InputCounter>()) .run_if(resource_exists::<InputCounter>())
// This is a custom run condition, defined using a system that returns // `.or_else()` is a run condition combinator that only evaluates the second condition
// a `bool` and which has read-only `SystemParam`s. // if the first condition returns `false`. This behavior is known as "short-circuiting",
// Both run conditions must return `true` in order for the system to run. // and is how the `||` operator works in Rust (as well as most C-family languages).
// Note that this second run condition will be evaluated even if the first returns `false`. // In this case, the `has_user_input` run condition will be evaluated since the `Unused` resource has not been initialized.
.run_if(has_user_input), .run_if(resource_exists::<Unused>().or_else(
// This is a custom run condition, defined using a system that returns
// a `bool` and which has read-only `SystemParam`s.
// Both run conditions must return `true` in order for the system to run.
// Note that this second run condition will be evaluated even if the first returns `false`.
has_user_input,
)),
print_input_counter print_input_counter
// `.and_then()` is a run condition combinator that only evaluates the second condition // `.and_then()` is a run condition combinator that only evaluates the second condition
// if the first condition returns `true`. This behavior is known as "short-circuiting", // if the first condition returns `true`, analogous to the `&&` operator.
// and is how the `&&` operator works in Rust (as well as most C-family languages).
// In this case, the short-circuiting behavior prevents the second run condition from // In this case, the short-circuiting behavior prevents the second run condition from
// panicking if the `InputCounter` resource has not been initialized. // panicking if the `InputCounter` resource has not been initialized.
.run_if(resource_exists::<InputCounter>().and_then( .run_if(resource_exists::<InputCounter>().and_then(
@ -51,6 +56,9 @@ fn main() {
#[derive(Resource, Default)] #[derive(Resource, Default)]
struct InputCounter(usize); struct InputCounter(usize);
#[derive(Resource)]
struct Unused;
/// Return true if any of the defined inputs were just pressed. /// 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 /// 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). /// they are read only (except for local parameters which can be mutable).

View file

@ -334,7 +334,7 @@ fn contributors() -> Result<Contributors, LoadContributorsError> {
let contributors = BufReader::new(stdout) let contributors = BufReader::new(stdout)
.lines() .lines()
.filter_map(|x| x.ok()) .map_while(|x| x.ok())
.collect(); .collect();
Ok(contributors) Ok(contributors)

View file

@ -28,6 +28,8 @@ fn main() {
}) })
.add_systems(Startup, setup) .add_systems(Startup, setup)
.add_systems(Update, (rotate, toggle_prepass_view)) .add_systems(Update, (rotate, toggle_prepass_view))
// Disabling MSAA for maximum compatibility. Shader prepass with MSAA needs GPU capability MULTISAMPLED_SHADING
.insert_resource(Msaa::Off)
.run(); .run();
} }

View file

@ -37,7 +37,7 @@ fn main() {
}), }),
..default() ..default()
})) }))
.add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(FrameTimeDiagnosticsPlugin)
.add_plugin(LogDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default())
.insert_resource(BevyCounter { .insert_resource(BevyCounter {
count: 0, count: 0,

View file

@ -21,7 +21,7 @@ fn main() {
App::new() App::new()
// Since this is also used as a benchmark, we want it to display performance data. // Since this is also used as a benchmark, we want it to display performance data.
.add_plugin(LogDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default())
.add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(FrameTimeDiagnosticsPlugin)
.add_plugins(DefaultPlugins.set(WindowPlugin { .add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window { primary_window: Some(Window {
present_mode: PresentMode::AutoNoVsync, present_mode: PresentMode::AutoNoVsync,

View file

@ -30,7 +30,7 @@ fn main() {
}), }),
..default() ..default()
})) }))
.add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(FrameTimeDiagnosticsPlugin)
.add_plugin(LogDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default())
.add_systems(Startup, setup) .add_systems(Startup, setup)
.add_systems(Update, button_system); .add_systems(Update, button_system);

View file

@ -28,7 +28,7 @@ fn main() {
}), }),
..default() ..default()
})) }))
.add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(FrameTimeDiagnosticsPlugin)
.add_plugin(LogDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default())
.add_systems(Startup, setup) .add_systems(Startup, setup)
.add_systems(Update, (move_camera, print_mesh_count)) .add_systems(Update, (move_camera, print_mesh_count))

View file

@ -18,7 +18,7 @@ fn main() {
}), }),
..default() ..default()
})) }))
.add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(FrameTimeDiagnosticsPlugin)
.insert_resource(Config { .insert_resource(Config {
line_count: 50_000, line_count: 50_000,
fancy: false, fancy: false,

View file

@ -21,7 +21,7 @@ fn main() {
}), }),
..default() ..default()
})) }))
.add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(FrameTimeDiagnosticsPlugin)
.add_plugin(LogDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default())
.add_systems(Startup, setup); .add_systems(Startup, setup);

View file

@ -24,7 +24,7 @@ fn main() {
}), }),
..default() ..default()
})) }))
.add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(FrameTimeDiagnosticsPlugin)
.add_plugin(LogDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default())
.add_systems(Startup, setup) .add_systems(Startup, setup)
.add_systems(Update, (move_camera, print_light_count)) .add_systems(Update, (move_camera, print_light_count))

View file

@ -29,7 +29,7 @@ fn main() {
)) ))
// Since this is also used as a benchmark, we want it to display performance data. // Since this is also used as a benchmark, we want it to display performance data.
.add_plugin(LogDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default())
.add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(FrameTimeDiagnosticsPlugin)
.add_plugins(DefaultPlugins.set(WindowPlugin { .add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window { primary_window: Some(Window {
present_mode: PresentMode::AutoNoVsync, present_mode: PresentMode::AutoNoVsync,

View file

@ -18,7 +18,7 @@ fn main() {
}), }),
..default() ..default()
})) }))
.add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(FrameTimeDiagnosticsPlugin)
.add_plugin(LogDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default())
.add_systems(Startup, spawn) .add_systems(Startup, spawn)
.add_systems(Update, update_text_bounds) .add_systems(Update, update_text_bounds)

View file

@ -184,7 +184,7 @@ fn main() {
App::new() App::new()
.insert_resource(cfg) .insert_resource(cfg)
.add_plugins(MinimalPlugins) .add_plugins(MinimalPlugins)
.add_plugin(TransformPlugin::default()) .add_plugin(TransformPlugin)
.add_systems(Startup, setup) .add_systems(Startup, setup)
// Updating transforms *must* be done before `CoreSet::PostUpdate` // Updating transforms *must* be done before `CoreSet::PostUpdate`
// or the hierarchy will momentarily be in an invalid state. // or the hierarchy will momentarily be in an invalid state.

View file

@ -11,7 +11,7 @@ use bevy::{
fn main() { fn main() {
App::new() App::new()
.add_plugins(DefaultPlugins) .add_plugins(DefaultPlugins)
.add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(FrameTimeDiagnosticsPlugin)
.add_systems(Startup, setup) .add_systems(Startup, setup)
.add_systems(Update, (text_update_system, text_color_system)) .add_systems(Update, (text_update_system, text_color_system))
.run(); .run();

View file

@ -1,4 +1,4 @@
///! This example illustrates how to resize windows, and how to respond to a window being resized. //! This example illustrates how to resize windows, and how to respond to a window being resized.
use bevy::{prelude::*, window::WindowResized}; use bevy::{prelude::*, window::WindowResized};
fn main() { fn main() {