Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Directly run system given world #2427

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ pub mod prelude {
Schedule, Stage, StageLabel, State, SystemLabel, SystemSet, SystemStage,
},
system::{
Commands, ConfigurableSystem, In, IntoChainSystem, IntoExclusiveSystem, IntoSystem,
Local, NonSend, NonSendMut, Query, QuerySet, RemovedComponents, Res, ResMut, System,
Commands, ConfigurableSystem, ExclusiveSystem, In, IntoChainSystem,
IntoExclusiveSystem, IntoSystem, Local, NonSend, NonSendMut, Query, QuerySet,
RemovedComponents, Res, ResMut, System,
},
world::{FromWorld, Mut, World},
};
Expand Down
35 changes: 35 additions & 0 deletions crates/bevy_ecs/src/system/exclusive_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,38 @@ pub trait ExclusiveSystem: Send + Sync + 'static {

fn id(&self) -> SystemId;

/// Runs the exclusive system in the world.
///
/// Use [`run_direct`] instead if you are manually running a system outside of a schedule

fn run(&mut self, world: &mut World);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this imply that run has an additional safety requirement that self is initialised for this world?

If that is so, then it should be unsafe

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not unsafe in the Rust sense; it merely panics with an opaque error message.


/// Runs the exclusive system directly on the world, correctly initializing its state first
///
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For both methods, it may be useful to describe how to persistently store and then access a system, rather than initializing it every frame.

This reduces work by enabling query caching and also allows Local resources to persist correctly.

I'm just not sure that the API docs are the right place to put that; it feels like content that might be better suited to the book.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I suppose that the way to do this is to probably store the system, run .initialize once, and then call the old .run() manually.

The new method is much more convenient, but will probably perform worse for repeated invocations.

Hmm. Maybe we also demonstrate how to store and run a stage in the same section of the book?

/// # Example
///
/// ```rust
/// use bevy_ecs::prelude::*;
///
/// struct Counter(u8);
/// let mut world = World::new();
///
/// fn count_up(world: &mut World){
/// let mut counter = world.get_resource_mut::<Counter>().unwrap();
/// counter.0 += 1;
/// }
///
/// world.insert_resource::<Counter>(Counter(0));
/// count_up.exclusive_system().run_direct(&mut world);
/// let counter = world.get_resource::<Counter>().unwrap();
/// assert_eq!(counter.0, 1);
///```
fn run_direct(&mut self, world: &mut World) {
self.initialize(world);
self.run(world);
}

/// Initialize the World, so that the system can safely run
fn initialize(&mut self, world: &mut World);

fn check_change_tick(&mut self, change_tick: u32);
Expand Down Expand Up @@ -48,6 +78,11 @@ impl ExclusiveSystem for ExclusiveSystemFn {
world.last_change_tick = saved_last_tick;
}

fn run_direct(&mut self, world: &mut World) {
self.initialize(world);
self.run(world);
}

fn initialize(&mut self, _: &mut World) {}

fn check_change_tick(&mut self, change_tick: u32) {
Expand Down
56 changes: 53 additions & 3 deletions crates/bevy_ecs/src/system/function_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,10 +221,10 @@ impl<In, Out, Sys: System<In = In, Out = Out>> IntoSystem<In, Out, AlreadyWasSys
pub struct In<In>(pub In);
pub struct InputMarker;

/// The [`System`] counter part of an ordinary function.
/// The [`System`] counterpart of an ordinary function.
///
/// You get this by calling [`IntoSystem::system`] on a function that only accepts
/// [`SystemParam`]s. The output of the system becomes the functions return type, while the input
/// [`SystemParam`]s. The output of the system becomes the function's return type, while the input
/// becomes the functions [`In`] tagged parameter or `()` if no such paramater exists.
pub struct FunctionSystem<In, Out, Param, Marker, F>
where
Expand Down Expand Up @@ -299,6 +299,52 @@ where
}
}

/// Allows end users to call system-running methods from the [`System`](crate::system::System) trait without .system().
pub trait RunnableSystem<In, Out, Param: SystemParam, Marker>:
IntoSystem<In, Out, (IsFunctionSystem, Param, Marker)>
{
fn apply_buffers(self, world: &mut World);

fn initialize(self, _world: &mut World);

fn run(self, input: In, world: &mut World) -> Out;

fn run_direct(self, input: In, world: &mut World) -> Out;
}

impl<In, Out, Param: SystemParam, Marker, F> RunnableSystem<In, Out, Param, Marker> for F
where
In: 'static,
Out: 'static,
Param: SystemParam + 'static,
Marker: 'static,
F: SystemParamFunction<In, Out, Param, Marker>
+ IntoSystem<
In,
Out,
(IsFunctionSystem, Param, Marker),
System = FunctionSystem<In, Out, Param, Marker, F>,
> + Send
+ Sync
+ 'static,
{
fn apply_buffers(self, world: &mut World) {
self.system().apply_buffers(world);
}

fn initialize(self, world: &mut World) {
self.system().initialize(world);
}

fn run(self, input: In, world: &mut World) -> Out {
self.system().run(input, world)
}

fn run_direct(self, input: In, world: &mut World) -> Out {
self.system().run_direct(input, world)
}
}

pub struct IsFunctionSystem;

impl<In, Out, Param, Marker, F> IntoSystem<In, Out, (IsFunctionSystem, Param, Marker)> for F
Expand Down Expand Up @@ -366,13 +412,17 @@ where
#[inline]
unsafe fn run_unsafe(&mut self, input: Self::In, world: &World) -> Self::Out {
let change_tick = world.increment_change_tick();
let out = self.func.run(
// Trait disambiguation required here to disambiguate .run call
// the .run found in the `RunnableSystem` trait
let out = <F as SystemParamFunction<In, Out, Param, Marker>>::run(
&mut self.func,
input,
self.param_state.as_mut().unwrap(),
&self.system_meta,
world,
change_tick,
);

self.system_meta.last_change_tick = change_tick;
out
}
Expand Down
36 changes: 35 additions & 1 deletion crates/bevy_ecs/src/system/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,46 @@ pub trait System: Send + Sync + 'static {
/// [`System::archetype_component_access()`].
unsafe fn run_unsafe(&mut self, input: Self::In, world: &World) -> Self::Out;
/// Runs the system with the given input in the world.
///
/// Use [`run_direct`] instead if you are manually running a system outside of a schedule
fn run(&mut self, input: Self::In, world: &mut World) -> Self::Out {
// SAFE: world and resources are exclusively borrowed
unsafe { self.run_unsafe(input, world) }
}
/// Runs the system directly on the world, initializing the world correctly;
/// immediately applying buffers (such as `Commands`) modified by its system parameters
///
/// Use () as the `input` parameter for systems which do not take any chained input.
///
/// Only one system will run at a time when executed in this way;
/// use a [`Schedule`] (or a custom abstraction created with [`run_unsafe`])
/// when system parallelism is desired.
///
/// # Examples
/// ```rust
/// use bevy_ecs::prelude::*;
///
/// struct Counter(u8);
/// let mut world = World::new();
///
/// fn count_up(mut counter: ResMut<Counter>){
/// counter.0 += 1;
/// }
///
/// world.insert_resource::<Counter>(Counter(0));
/// count_up.run_direct((), &mut world);
/// let counter = world.get_resource::<Counter>().unwrap();
/// assert_eq!(counter.0, 1);
/// ```
fn run_direct(&mut self, input: Self::In, world: &mut World) -> Self::Out {
self.initialize(world);
let output = self.run(input, world);
self.apply_buffers(world);
output
}
/// Applies any buffers (such as `Commands`) created by this system's parameters to the world
fn apply_buffers(&mut self, world: &mut World);
/// Initialize the system.
/// Initializes the system from the world, so that it may be run successfully
fn initialize(&mut self, _world: &mut World);
fn check_change_tick(&mut self, change_tick: u32);
}
Expand Down