mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
Improve safety for the multi-threaded executor using UnsafeWorldCell
(#8292)
# Objective Fix #7833. Safety comments in the multi-threaded executor don't really talk about system world accesses, which makes it unclear if the code is actually valid. ## Solution Update the `System` trait to use `UnsafeWorldCell`. This type's API is written in a way that makes it much easier to cleanly maintain safety invariants. Use this type throughout the multi-threaded executor, with a liberal use of safety comments. --- ## Migration Guide The `System` trait now uses `UnsafeWorldCell` instead of `&World`. This type provides a robust API for interior mutable world access. - The method `run_unsafe` uses this type to manage world mutations across multiple threads. - The method `update_archetype_component_access` uses this type to ensure that only world metadata can be used. ```rust let mut system = IntoSystem::into_system(my_system); system.initialize(&mut world); // Before: system.update_archetype_component_access(&world); unsafe { system.run_unsafe(&world) } // After: system.update_archetype_component_access(world.as_unsafe_world_cell_readonly()); unsafe { system.run_unsafe(world.as_unsafe_world_cell()) } ``` --------- Co-authored-by: James Liu <contact@jamessliu.com>
This commit is contained in:
parent
4465f256eb
commit
85a918a8dd
12 changed files with 103 additions and 69 deletions
|
@ -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));
|
||||||
});
|
});
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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));
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,6 +5,7 @@ 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 = Box<dyn ReadOnlySystem<In = (), Out = bool>>;
|
||||||
|
@ -990,7 +991,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 +1008,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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>> {
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue