Skip to content

Commit d872173

Browse files
cartostwilkens
authored andcommitted
Add new SystemState and rename old SystemState to SystemMeta (bevyengine#2283)
This enables `SystemParams` to be used outside of function systems. Anything can create and store `SystemState`, which enables efficient "param state cached" access to `SystemParams`. It adds a `ReadOnlySystemParamFetch` trait, which enables safe `SystemState::get` calls without unique world access. I renamed the old `SystemState` to `SystemMeta` to enable us to mirror the `QueryState` naming convention (but I'm happy to discuss alternative names if people have other ideas). I initially pitched this as `ParamState`, but given that it needs to include full system metadata, that doesn't feel like a particularly accurate name. ```rust #[derive(Eq, PartialEq, Debug)] struct A(usize); #[derive(Eq, PartialEq, Debug)] struct B(usize); let mut world = World::default(); world.insert_resource(A(42)); world.spawn().insert(B(7)); // we get nice lifetime elision when declaring the type on the left hand side let mut system_state: SystemState<(Res<A>, Query<&B>)> = SystemState::new(&mut world); let (a, query) = system_state.get(&world); assert_eq!(*a, A(42), "returned resource matches initial value"); assert_eq!( *query.single().unwrap(), B(7), "returned component matches initial value" ); // mutable system params require unique world access let mut system_state: SystemState<(ResMut<A>, Query<&mut B>)> = SystemState::new(&mut world); let (a, query) = system_state.get_mut(&mut world); // static lifetimes are required when declaring inside of structs struct SomeContainer { state: SystemState<(Res<'static, A>, Res<'static, B>)> } // this can be shortened using type aliases, which will be useful for complex param tuples type MyParams<'a> = (Res<'a, A>, Res<'a, B>); struct SomeContainer { state: SystemState<MyParams<'static>> } // It is the user's responsibility to call SystemState::apply(world) for parameters that queue up work let mut system_state: SystemState<(Commands, Query<&B>)> = SystemState::new(&mut world); { let (mut commands, query) = system_state.get(&world); commands.insert_resource(3.14); } system_state.apply(&mut world); ``` ## Future Work * Actually use SystemState inside FunctionSystem. This would be trivial, but it requires FunctionSystem to wrap SystemState in Option in its current form (which complicates system metadata lookup). I'd prefer to hold off until we adopt something like the later designs linked in bevyengine#1364, which enable us to contruct Systems using a World reference (and also remove the need for `.system`). * Consider a "scoped" approach to automatically call SystemState::apply when systems params are no longer being used (either a container type with a Drop impl, or a function that takes a closure for user logic operating on params).
1 parent d61500b commit d872173

File tree

4 files changed

+418
-136
lines changed

4 files changed

+418
-136
lines changed

crates/bevy_ecs/macros/src/lib.rs

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -221,40 +221,45 @@ pub fn impl_query_set(_input: TokenStream) -> TokenStream {
221221
type Fetch = QuerySetState<(#(QueryState<#query, #filter>,)*)>;
222222
}
223223

224-
// SAFE: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemState. If any QueryState conflicts
224+
// SAFE: All Queries are constrained to ReadOnlyFetch, so World is only read
225+
unsafe impl<#(#query: WorldQuery + 'static,)* #(#filter: WorldQuery + 'static,)*> ReadOnlySystemParamFetch for QuerySetState<(#(QueryState<#query, #filter>,)*)>
226+
where #(#query::Fetch: ReadOnlyFetch,)* #(#filter::Fetch: FilterFetch,)*
227+
{ }
228+
229+
// SAFE: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemMeta. If any QueryState conflicts
225230
// with any prior access, a panic will occur.
226231
unsafe impl<#(#query: WorldQuery + 'static,)* #(#filter: WorldQuery + 'static,)*> SystemParamState for QuerySetState<(#(QueryState<#query, #filter>,)*)>
227232
where #(#filter::Fetch: FilterFetch,)*
228233
{
229234
type Config = ();
230-
fn init(world: &mut World, system_state: &mut SystemState, config: Self::Config) -> Self {
235+
fn init(world: &mut World, system_meta: &mut SystemMeta, config: Self::Config) -> Self {
231236
#(
232237
let mut #query = QueryState::<#query, #filter>::new(world);
233238
assert_component_access_compatibility(
234-
&system_state.name,
239+
&system_meta.name,
235240
std::any::type_name::<#query>(),
236241
std::any::type_name::<#filter>(),
237-
&system_state.component_access_set,
242+
&system_meta.component_access_set,
238243
&#query.component_access,
239244
world,
240245
);
241246
)*
242247
#(
243-
system_state
248+
system_meta
244249
.component_access_set
245250
.add(#query.component_access.clone());
246-
system_state
251+
system_meta
247252
.archetype_component_access
248253
.extend(&#query.archetype_component_access);
249254
)*
250255
QuerySetState((#(#query,)*))
251256
}
252257

253-
fn new_archetype(&mut self, archetype: &Archetype, system_state: &mut SystemState) {
258+
fn new_archetype(&mut self, archetype: &Archetype, system_meta: &mut SystemMeta) {
254259
let (#(#query,)*) = &mut self.0;
255260
#(
256261
#query.new_archetype(archetype);
257-
system_state
262+
system_meta
258263
.archetype_component_access
259264
.extend(&#query.archetype_component_access);
260265
)*
@@ -271,12 +276,12 @@ pub fn impl_query_set(_input: TokenStream) -> TokenStream {
271276
#[inline]
272277
unsafe fn get_param(
273278
state: &'a mut Self,
274-
system_state: &'a SystemState,
279+
system_meta: &SystemMeta,
275280
world: &'a World,
276281
change_tick: u32,
277282
) -> Self::Item {
278283
let (#(#query,)*) = &state.0;
279-
QuerySet((#(Query::new(world, #query, system_state.last_change_tick, change_tick),)*))
284+
QuerySet((#(Query::new(world, #query, system_meta.last_change_tick, change_tick),)*))
280285
}
281286
}
282287

@@ -388,15 +393,15 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
388393

389394
unsafe impl<TSystemParamState: #path::system::SystemParamState, #punctuated_generics> #path::system::SystemParamState for #fetch_struct_name<TSystemParamState, #punctuated_generic_idents> {
390395
type Config = TSystemParamState::Config;
391-
fn init(world: &mut #path::world::World, system_state: &mut #path::system::SystemState, config: Self::Config) -> Self {
396+
fn init(world: &mut #path::world::World, system_meta: &mut #path::system::SystemMeta, config: Self::Config) -> Self {
392397
Self {
393-
state: TSystemParamState::init(world, system_state, config),
398+
state: TSystemParamState::init(world, system_meta, config),
394399
marker: std::marker::PhantomData,
395400
}
396401
}
397402

398-
fn new_archetype(&mut self, archetype: &#path::archetype::Archetype, system_state: &mut #path::system::SystemState) {
399-
self.state.new_archetype(archetype, system_state)
403+
fn new_archetype(&mut self, archetype: &#path::archetype::Archetype, system_meta: &mut #path::system::SystemMeta) {
404+
self.state.new_archetype(archetype, system_meta)
400405
}
401406

402407
fn default_config() -> TSystemParamState::Config {
@@ -412,12 +417,12 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
412417
type Item = #struct_name#ty_generics;
413418
unsafe fn get_param(
414419
state: &'a mut Self,
415-
system_state: &'a #path::system::SystemState,
420+
system_meta: &#path::system::SystemMeta,
416421
world: &'a #path::world::World,
417422
change_tick: u32,
418423
) -> Self::Item {
419424
#struct_name {
420-
#(#fields: <<#field_types as SystemParam>::Fetch as #path::system::SystemParamFetch>::get_param(&mut state.state.#field_indices, system_state, world, change_tick),)*
425+
#(#fields: <<#field_types as SystemParam>::Fetch as #path::system::SystemParamFetch>::get_param(&mut state.state.#field_indices, system_meta, world, change_tick),)*
421426
#(#ignored_fields: <#ignored_field_types>::default(),)*
422427
}
423428
}

crates/bevy_ecs/src/system/into_system.rs renamed to crates/bevy_ecs/src/system/function_system.rs

Lines changed: 141 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,29 @@
11
use crate::{
2-
archetype::{Archetype, ArchetypeComponentId},
2+
archetype::{Archetype, ArchetypeComponentId, ArchetypeGeneration, ArchetypeId},
33
component::ComponentId,
44
query::{Access, FilteredAccessSet},
55
system::{
6-
check_system_change_tick, System, SystemId, SystemParam, SystemParamFetch, SystemParamState,
6+
check_system_change_tick, ReadOnlySystemParamFetch, System, SystemId, SystemParam,
7+
SystemParamFetch, SystemParamState,
78
},
8-
world::World,
9+
world::{World, WorldId},
910
};
1011
use bevy_ecs_macros::all_tuples;
1112
use std::{borrow::Cow, marker::PhantomData};
1213

13-
/// The state of a [`System`].
14-
pub struct SystemState {
14+
/// The metadata of a [`System`].
15+
pub struct SystemMeta {
1516
pub(crate) id: SystemId,
1617
pub(crate) name: Cow<'static, str>,
1718
pub(crate) component_access_set: FilteredAccessSet<ComponentId>,
1819
pub(crate) archetype_component_access: Access<ArchetypeComponentId>,
19-
// NOTE: this must be kept private. making a SystemState non-send is irreversible to prevent
20+
// NOTE: this must be kept private. making a SystemMeta non-send is irreversible to prevent
2021
// SystemParams from overriding each other
2122
is_send: bool,
2223
pub(crate) last_change_tick: u32,
2324
}
2425

25-
impl SystemState {
26+
impl SystemMeta {
2627
fn new<T>() -> Self {
2728
Self {
2829
name: std::any::type_name::<T>().into(),
@@ -49,6 +50,114 @@ impl SystemState {
4950
}
5051
}
5152

53+
// TODO: Actually use this in FunctionSystem. We should probably only do this once Systems are constructed using a World reference
54+
// (to avoid the need for unwrapping to retrieve SystemMeta)
55+
/// Holds on to persistent state required to drive [`SystemParam`] for a [`System`].
56+
pub struct SystemState<Param: SystemParam> {
57+
meta: SystemMeta,
58+
param_state: <Param as SystemParam>::Fetch,
59+
world_id: WorldId,
60+
archetype_generation: ArchetypeGeneration,
61+
}
62+
63+
impl<Param: SystemParam> SystemState<Param> {
64+
pub fn new(world: &mut World) -> Self {
65+
let config = <Param::Fetch as SystemParamState>::default_config();
66+
Self::with_config(world, config)
67+
}
68+
69+
pub fn with_config(
70+
world: &mut World,
71+
config: <Param::Fetch as SystemParamState>::Config,
72+
) -> Self {
73+
let mut meta = SystemMeta::new::<Param>();
74+
let param_state = <Param::Fetch as SystemParamState>::init(world, &mut meta, config);
75+
Self {
76+
meta,
77+
param_state,
78+
world_id: world.id(),
79+
archetype_generation: ArchetypeGeneration::initial(),
80+
}
81+
}
82+
83+
#[inline]
84+
pub fn meta(&self) -> &SystemMeta {
85+
&self.meta
86+
}
87+
88+
/// Retrieve the [`SystemParam`] values. This can only be called when all parameters are read-only.
89+
#[inline]
90+
pub fn get<'a>(&'a mut self, world: &'a World) -> <Param::Fetch as SystemParamFetch<'a>>::Item
91+
where
92+
Param::Fetch: ReadOnlySystemParamFetch,
93+
{
94+
self.validate_world_and_update_archetypes(world);
95+
// SAFE: Param is read-only and doesn't allow mutable access to World. It also matches the World this SystemState was created with.
96+
unsafe { self.get_unchecked_manual(world) }
97+
}
98+
99+
/// Retrieve the mutable [`SystemParam`] values.
100+
#[inline]
101+
pub fn get_mut<'a>(
102+
&'a mut self,
103+
world: &'a mut World,
104+
) -> <Param::Fetch as SystemParamFetch<'a>>::Item {
105+
self.validate_world_and_update_archetypes(world);
106+
// SAFE: World is uniquely borrowed and matches the World this SystemState was created with.
107+
unsafe { self.get_unchecked_manual(world) }
108+
}
109+
110+
/// Applies all state queued up for [`SystemParam`] values. For example, this will apply commands queued up
111+
/// by a [`Commands`](`super::Commands`) parameter to the given [`World`].
112+
/// This function should be called manually after the values returned by [`SystemState::get`] and [`SystemState::get_mut`]
113+
/// are finished being used.
114+
pub fn apply(&mut self, world: &mut World) {
115+
self.param_state.apply(world);
116+
}
117+
118+
#[inline]
119+
pub fn matches_world(&self, world: &World) -> bool {
120+
self.world_id == world.id()
121+
}
122+
123+
fn validate_world_and_update_archetypes(&mut self, world: &World) {
124+
assert!(self.matches_world(world), "Encountered a mismatched World. A SystemState cannot be used with Worlds other than the one it was created with.");
125+
let archetypes = world.archetypes();
126+
let new_generation = archetypes.generation();
127+
let old_generation = std::mem::replace(&mut self.archetype_generation, new_generation);
128+
let archetype_index_range = old_generation.value()..new_generation.value();
129+
130+
for archetype_index in archetype_index_range {
131+
self.param_state.new_archetype(
132+
&archetypes[ArchetypeId::new(archetype_index)],
133+
&mut self.meta,
134+
);
135+
}
136+
}
137+
138+
/// Retrieve the [`SystemParam`] values. This will not update archetypes automatically.
139+
///
140+
/// # Safety
141+
/// This call might access any of the input parameters in a way that violates Rust's mutability rules. Make sure the data
142+
/// access is safe in the context of global [`World`] access. The passed-in [`World`] _must_ be the [`World`] the [`SystemState`] was
143+
/// created with.
144+
#[inline]
145+
pub unsafe fn get_unchecked_manual<'a>(
146+
&'a mut self,
147+
world: &'a World,
148+
) -> <Param::Fetch as SystemParamFetch<'a>>::Item {
149+
let change_tick = world.increment_change_tick();
150+
let param = <Param::Fetch as SystemParamFetch>::get_param(
151+
&mut self.param_state,
152+
&self.meta,
153+
world,
154+
change_tick,
155+
);
156+
self.meta.last_change_tick = change_tick;
157+
param
158+
}
159+
}
160+
52161
/// Conversion trait to turn something into a [`System`].
53162
///
54163
/// Use this to get a system from a function. Also note that every system implements this trait as
@@ -116,7 +225,7 @@ where
116225
{
117226
func: F,
118227
param_state: Option<Param::Fetch>,
119-
system_state: SystemState,
228+
system_meta: SystemMeta,
120229
config: Option<<Param::Fetch as SystemParamState>::Config>,
121230
// NOTE: PhantomData<fn()-> T> gives this safe Send/Sync impls
122231
#[allow(clippy::type_complexity)]
@@ -161,7 +270,7 @@ where
161270
func: self,
162271
param_state: None,
163272
config: Some(<Param::Fetch as SystemParamState>::default_config()),
164-
system_state: SystemState::new::<F>(),
273+
system_meta: SystemMeta::new::<F>(),
165274
marker: PhantomData,
166275
}
167276
}
@@ -180,33 +289,33 @@ where
180289

181290
#[inline]
182291
fn name(&self) -> Cow<'static, str> {
183-
self.system_state.name.clone()
292+
self.system_meta.name.clone()
184293
}
185294

186295
#[inline]
187296
fn id(&self) -> SystemId {
188-
self.system_state.id
297+
self.system_meta.id
189298
}
190299

191300
#[inline]
192301
fn new_archetype(&mut self, archetype: &Archetype) {
193302
let param_state = self.param_state.as_mut().unwrap();
194-
param_state.new_archetype(archetype, &mut self.system_state);
303+
param_state.new_archetype(archetype, &mut self.system_meta);
195304
}
196305

197306
#[inline]
198307
fn component_access(&self) -> &Access<ComponentId> {
199-
&self.system_state.component_access_set.combined_access()
308+
&self.system_meta.component_access_set.combined_access()
200309
}
201310

202311
#[inline]
203312
fn archetype_component_access(&self) -> &Access<ArchetypeComponentId> {
204-
&self.system_state.archetype_component_access
313+
&self.system_meta.archetype_component_access
205314
}
206315

207316
#[inline]
208317
fn is_send(&self) -> bool {
209-
self.system_state.is_send
318+
self.system_meta.is_send
210319
}
211320

212321
#[inline]
@@ -215,11 +324,11 @@ where
215324
let out = self.func.run(
216325
input,
217326
self.param_state.as_mut().unwrap(),
218-
&self.system_state,
327+
&self.system_meta,
219328
world,
220329
change_tick,
221330
);
222-
self.system_state.last_change_tick = change_tick;
331+
self.system_meta.last_change_tick = change_tick;
223332
out
224333
}
225334

@@ -233,28 +342,32 @@ where
233342
fn initialize(&mut self, world: &mut World) {
234343
self.param_state = Some(<Param::Fetch as SystemParamState>::init(
235344
world,
236-
&mut self.system_state,
345+
&mut self.system_meta,
237346
self.config.take().unwrap(),
238347
));
239348
}
240349

241350
#[inline]
242351
fn check_change_tick(&mut self, change_tick: u32) {
243352
check_system_change_tick(
244-
&mut self.system_state.last_change_tick,
353+
&mut self.system_meta.last_change_tick,
245354
change_tick,
246-
self.system_state.name.as_ref(),
355+
self.system_meta.name.as_ref(),
247356
);
248357
}
249358
}
250359

251360
/// A trait implemented for all functions that can be used as [`System`]s.
252361
pub trait SystemParamFunction<In, Out, Param: SystemParam, Marker>: Send + Sync + 'static {
253-
fn run(
362+
/// # Safety
363+
///
364+
/// This call might access any of the input parameters in an unsafe way. Make sure the data
365+
/// access is safe in the context of the system scheduler.
366+
unsafe fn run(
254367
&mut self,
255368
input: In,
256369
state: &mut Param::Fetch,
257-
system_state: &SystemState,
370+
system_meta: &SystemMeta,
258371
world: &World,
259372
change_tick: u32,
260373
) -> Out;
@@ -270,11 +383,9 @@ macro_rules! impl_system_function {
270383
FnMut($(<<$param as SystemParam>::Fetch as SystemParamFetch>::Item),*) -> Out + Send + Sync + 'static, Out: 'static
271384
{
272385
#[inline]
273-
fn run(&mut self, _input: (), state: &mut <($($param,)*) as SystemParam>::Fetch, system_state: &SystemState, world: &World, change_tick: u32) -> Out {
274-
unsafe {
275-
let ($($param,)*) = <<($($param,)*) as SystemParam>::Fetch as SystemParamFetch>::get_param(state, system_state, world, change_tick);
276-
self($($param),*)
277-
}
386+
unsafe fn run(&mut self, _input: (), state: &mut <($($param,)*) as SystemParam>::Fetch, system_meta: &SystemMeta, world: &World, change_tick: u32) -> Out {
387+
let ($($param,)*) = <<($($param,)*) as SystemParam>::Fetch as SystemParamFetch>::get_param(state, system_meta, world, change_tick);
388+
self($($param),*)
278389
}
279390
}
280391

@@ -286,11 +397,9 @@ macro_rules! impl_system_function {
286397
FnMut(In<Input>, $(<<$param as SystemParam>::Fetch as SystemParamFetch>::Item),*) -> Out + Send + Sync + 'static, Out: 'static
287398
{
288399
#[inline]
289-
fn run(&mut self, input: Input, state: &mut <($($param,)*) as SystemParam>::Fetch, system_state: &SystemState, world: &World, change_tick: u32) -> Out {
290-
unsafe {
291-
let ($($param,)*) = <<($($param,)*) as SystemParam>::Fetch as SystemParamFetch>::get_param(state, system_state, world, change_tick);
292-
self(In(input), $($param),*)
293-
}
400+
unsafe fn run(&mut self, input: Input, state: &mut <($($param,)*) as SystemParam>::Fetch, system_meta: &SystemMeta, world: &World, change_tick: u32) -> Out {
401+
let ($($param,)*) = <<($($param,)*) as SystemParam>::Fetch as SystemParamFetch>::get_param(state, system_meta, world, change_tick);
402+
self(In(input), $($param),*)
294403
}
295404
}
296405
};

0 commit comments

Comments
 (0)