Make an unsafe accessor for SystemSchedule::systems (#23443)

# Objective

- #23414 made `SystemSchedule::systems` pub, but this can lead to
breaking invariants that `Schedule` expects. For example this allows
mutating the access which is used to prevent race conditions in the
multithreaded executor. This could also allow replacing systems, but
without initializing the access as the `Schedule` is meant to keep track
of which systems are unitialized.

## Solution

- Make the fields of `SystemWithAccess` private to make it harder to
modify the access. This is potentially a breaking change as
`SystemWithAccess` is pub, but the type is not exposed in our public
api's for `Schedule` in 0.18.
- Make an unsafe accessor for the `systems` field and make the field
private again.

## Testing

- Only checked that this compiles
This commit is contained in:
Mike
2026-03-22 12:04:45 -07:00
committed by GitHub
parent 1d4be6eb4d
commit 984f7203e3
3 changed files with 16 additions and 5 deletions
+10 -1
View File
@@ -75,7 +75,7 @@ pub struct SystemSchedule {
/// List of system node ids.
pub(super) system_ids: Vec<SystemKey>,
/// Indexed by system node id.
pub systems: Vec<SystemWithAccess>,
pub(super) systems: Vec<SystemWithAccess>,
/// Indexed by system node id.
pub(super) system_conditions: Vec<Vec<ConditionWithAccess>>,
/// Indexed by system node id.
@@ -121,6 +121,15 @@ impl SystemSchedule {
systems_in_sets_with_conditions: Vec::new(),
}
}
/// Accessor to allow running systems from a custom executor
///
/// # Safety
/// - The only allowed mutations are from calling methods on the [`System`] trait. Replacing
/// systems in the returned [`Vec`] should be considered undefined behavior.
pub unsafe fn systems_mut(&mut self) -> &mut Vec<SystemWithAccess> {
&mut self.systems
}
}
/// A special [`System`] that instructs the executor to call
+2 -2
View File
@@ -35,10 +35,10 @@ pub(crate) struct SystemNode {
/// A [`ScheduleSystem`] stored alongside the access returned from [`System::initialize`].
pub struct SystemWithAccess {
/// The system itself.
pub system: ScheduleSystem,
pub(crate) system: ScheduleSystem,
/// The access returned by [`System::initialize`].
/// This will be empty if the system has not been initialized yet.
pub access: FilteredAccessSet,
pub(crate) access: FilteredAccessSet,
}
impl SystemWithAccess {
+4 -2
View File
@@ -21,8 +21,10 @@ impl SystemExecutor for CustomExecutor {
_skip_systems: Option<&FixedBitSet>,
_error_handler: fn(BevyError, ErrorContext),
) {
for entry in schedule.systems.iter_mut() {
let _ = entry.system.run((), world);
#[expect(unsafe_code, reason = "CustomExecutor's require unsafe")]
// SAFETY: `run` is a trait method on `System`
for entry in unsafe { schedule.systems_mut().iter_mut() } {
let _ = entry.run((), world);
}
}