Skip to content

Commit 9d45353

Browse files
committed
System Param Lifetime Split (#2605)
# Objective Enable using exact World lifetimes during read-only access . This is motivated by the new renderer's need to allow read-only world-only queries to outlive the query itself (but still be constrained by the world lifetime). For example: https://github.com/bevyengine/bevy/blob/115b170d1f11a91146bb6d6e9684dceb8b21f786/pipelined/bevy_pbr2/src/render/mod.rs#L774 ## Solution Split out SystemParam state and world lifetimes and pipe those lifetimes up to read-only Query ops (and add into_inner for Res). According to every safety test I've run so far (except one), this is safe (see the temporary safety test commit). Note that changing the mutable variants to the new lifetimes would allow aliased mutable pointers (try doing that to see how it affects the temporary safety tests). The new state lifetime on SystemParam does make `#[derive(SystemParam)]` more cumbersome (the current impl requires PhantomData if you don't use both lifetimes). We can make this better by detecting whether or not a lifetime is used in the derive and adjusting accordingly, but that should probably be done in its own pr. ## Why is this a draft? The new lifetimes break QuerySet safety in one very specific case (see the query_set system in system_safety_test). We need to solve this before we can use the lifetimes given. This is due to the fact that QuerySet is just a wrapper over Query, which now relies on world lifetimes instead of `&self` lifetimes to prevent aliasing (but in systems, each Query has its own implied lifetime, not a centralized world lifetime). I believe the fix is to rewrite QuerySet to have its own World lifetime (and own the internal reference). This will complicate the impl a bit, but I think it is doable. I'm curious if anyone else has better ideas. Personally, I think these new lifetimes need to happen. We've gotta have a way to directly tie read-only World queries to the World lifetime. The new renderer is the first place this has come up, but I doubt it will be the last. Worst case scenario we can come up with a second `WorldLifetimeQuery<Q, F = ()>` parameter to enable these read-only scenarios, but I'd rather not add another type to the type zoo.
1 parent a89a954 commit 9d45353

File tree

20 files changed

+481
-256
lines changed

20 files changed

+481
-256
lines changed

crates/bevy_ecs/macros/src/lib.rs

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ use syn::{
99
parse_macro_input,
1010
punctuated::Punctuated,
1111
token::Comma,
12-
Data, DataStruct, DeriveInput, Field, Fields, GenericParam, Ident, Index, Lifetime, LitInt,
13-
Path, Result, Token,
12+
Data, DataStruct, DeriveInput, Field, Fields, GenericParam, Ident, Index, LitInt, Path, Result,
13+
Token,
1414
};
1515

1616
struct AllTuples {
@@ -176,48 +176,36 @@ fn get_idents(fmt_string: fn(usize) -> String, count: usize) -> Vec<Ident> {
176176
.collect::<Vec<Ident>>()
177177
}
178178

179-
fn get_lifetimes(fmt_string: fn(usize) -> String, count: usize) -> Vec<Lifetime> {
180-
(0..count)
181-
.map(|i| Lifetime::new(&fmt_string(i), Span::call_site()))
182-
.collect::<Vec<Lifetime>>()
183-
}
184-
185179
#[proc_macro]
186180
pub fn impl_query_set(_input: TokenStream) -> TokenStream {
187181
let mut tokens = TokenStream::new();
188182
let max_queries = 4;
189183
let queries = get_idents(|i| format!("Q{}", i), max_queries);
190184
let filters = get_idents(|i| format!("F{}", i), max_queries);
191-
let lifetimes = get_lifetimes(|i| format!("'q{}", i), max_queries);
192-
let mut query_fns = Vec::new();
193185
let mut query_fn_muts = Vec::new();
194186
for i in 0..max_queries {
195187
let query = &queries[i];
196188
let filter = &filters[i];
197-
let lifetime = &lifetimes[i];
198189
let fn_name = Ident::new(&format!("q{}", i), Span::call_site());
199-
let fn_name_mut = Ident::new(&format!("q{}_mut", i), Span::call_site());
200190
let index = Index::from(i);
201-
query_fns.push(quote! {
202-
pub fn #fn_name(&self) -> &Query<#lifetime, #query, #filter> {
203-
&self.0.#index
204-
}
205-
});
206191
query_fn_muts.push(quote! {
207-
pub fn #fn_name_mut(&mut self) -> &mut Query<#lifetime, #query, #filter> {
208-
&mut self.0.#index
192+
pub fn #fn_name(&mut self) -> Query<'_, '_, #query, #filter> {
193+
// SAFE: systems run without conflicts with other systems.
194+
// Conflicting queries in QuerySet are not accessible at the same time
195+
// QuerySets are guaranteed to not conflict with other SystemParams
196+
unsafe {
197+
Query::new(self.world, &self.query_states.#index, self.last_change_tick, self.change_tick)
198+
}
209199
}
210200
});
211201
}
212202

213203
for query_count in 1..=max_queries {
214204
let query = &queries[0..query_count];
215205
let filter = &filters[0..query_count];
216-
let lifetime = &lifetimes[0..query_count];
217-
let query_fn = &query_fns[0..query_count];
218206
let query_fn_mut = &query_fn_muts[0..query_count];
219207
tokens.extend(TokenStream::from(quote! {
220-
impl<#(#lifetime,)* #(#query: WorldQuery + 'static,)* #(#filter: WorldQuery + 'static,)*> SystemParam for QuerySet<(#(Query<#lifetime, #query, #filter>,)*)>
208+
impl<'w, 's, #(#query: WorldQuery + 'static,)* #(#filter: WorldQuery + 'static,)*> SystemParam for QuerySet<'w, 's, (#(QueryState<#query, #filter>,)*)>
221209
where #(#filter::Fetch: FilterFetch,)*
222210
{
223211
type Fetch = QuerySetState<(#(QueryState<#query, #filter>,)*)>;
@@ -270,27 +258,30 @@ pub fn impl_query_set(_input: TokenStream) -> TokenStream {
270258
fn default_config() {}
271259
}
272260

273-
impl<'a, #(#query: WorldQuery + 'static,)* #(#filter: WorldQuery + 'static,)*> SystemParamFetch<'a> for QuerySetState<(#(QueryState<#query, #filter>,)*)>
261+
impl<'w, 's, #(#query: WorldQuery + 'static,)* #(#filter: WorldQuery + 'static,)*> SystemParamFetch<'w, 's> for QuerySetState<(#(QueryState<#query, #filter>,)*)>
274262
where #(#filter::Fetch: FilterFetch,)*
275263
{
276-
type Item = QuerySet<(#(Query<'a, #query, #filter>,)*)>;
264+
type Item = QuerySet<'w, 's, (#(QueryState<#query, #filter>,)*)>;
277265

278266
#[inline]
279267
unsafe fn get_param(
280-
state: &'a mut Self,
268+
state: &'s mut Self,
281269
system_meta: &SystemMeta,
282-
world: &'a World,
270+
world: &'w World,
283271
change_tick: u32,
284272
) -> Self::Item {
285-
let (#(#query,)*) = &state.0;
286-
QuerySet((#(Query::new(world, #query, system_meta.last_change_tick, change_tick),)*))
273+
QuerySet {
274+
query_states: &state.0,
275+
world,
276+
last_change_tick: system_meta.last_change_tick,
277+
change_tick,
278+
}
287279
}
288280
}
289281

290-
impl<#(#lifetime,)* #(#query: WorldQuery,)* #(#filter: WorldQuery,)*> QuerySet<(#(Query<#lifetime, #query, #filter>,)*)>
282+
impl<'w, 's, #(#query: WorldQuery,)* #(#filter: WorldQuery,)*> QuerySet<'w, 's, (#(QueryState<#query, #filter>,)*)>
291283
where #(#filter::Fetch: FilterFetch,)*
292284
{
293-
#(#query_fn)*
294285
#(#query_fn_mut)*
295286
}
296287
}));
@@ -415,12 +406,12 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
415406
}
416407
}
417408

418-
impl #impl_generics #path::system::SystemParamFetch<'a> for #fetch_struct_name <(#(<#field_types as #path::system::SystemParam>::Fetch,)*), #punctuated_generic_idents> {
409+
impl #impl_generics #path::system::SystemParamFetch<'w, 's> for #fetch_struct_name <(#(<#field_types as #path::system::SystemParam>::Fetch,)*), #punctuated_generic_idents> {
419410
type Item = #struct_name#ty_generics;
420411
unsafe fn get_param(
421-
state: &'a mut Self,
412+
state: &'s mut Self,
422413
system_meta: &#path::system::SystemMeta,
423-
world: &'a #path::world::World,
414+
world: &'w #path::world::World,
424415
change_tick: u32,
425416
) -> Self::Item {
426417
#struct_name {

crates/bevy_ecs/src/event.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -151,18 +151,20 @@ fn map_instance_event<T>(event_instance: &EventInstance<T>) -> &T {
151151

152152
/// Reads events of type `T` in order and tracks which events have already been read.
153153
#[derive(SystemParam)]
154-
pub struct EventReader<'a, T: Component> {
155-
last_event_count: Local<'a, (usize, PhantomData<T>)>,
156-
events: Res<'a, Events<T>>,
154+
pub struct EventReader<'w, 's, T: Component> {
155+
last_event_count: Local<'s, (usize, PhantomData<T>)>,
156+
events: Res<'w, Events<T>>,
157157
}
158158

159159
/// Sends events of type `T`.
160160
#[derive(SystemParam)]
161-
pub struct EventWriter<'a, T: Component> {
162-
events: ResMut<'a, Events<T>>,
161+
pub struct EventWriter<'w, 's, T: Component> {
162+
events: ResMut<'w, Events<T>>,
163+
#[system_param(ignore)]
164+
marker: PhantomData<&'s usize>,
163165
}
164166

165-
impl<'a, T: Component> EventWriter<'a, T> {
167+
impl<'w, 's, T: Component> EventWriter<'w, 's, T> {
166168
pub fn send(&mut self, event: T) {
167169
self.events.send(event);
168170
}
@@ -252,7 +254,7 @@ fn internal_event_reader<'a, T>(
252254
}
253255
}
254256

255-
impl<'a, T: Component> EventReader<'a, T> {
257+
impl<'w, 's, T: Component> EventReader<'w, 's, T> {
256258
/// Iterates over the events this EventReader has not seen yet. This updates the EventReader's
257259
/// event counter, which means subsequent event reads will not include events that happened
258260
/// before now.

crates/bevy_ecs/src/query/state.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,11 @@ where
117117
}
118118

119119
#[inline]
120-
pub fn get<'w>(
121-
&mut self,
120+
pub fn get<'w, 's>(
121+
&'s mut self,
122122
world: &'w World,
123123
entity: Entity,
124-
) -> Result<<Q::Fetch as Fetch<'w, '_>>::Item, QueryEntityError>
124+
) -> Result<<Q::Fetch as Fetch<'w, 's>>::Item, QueryEntityError>
125125
where
126126
Q::Fetch: ReadOnlyFetch,
127127
{
@@ -130,11 +130,11 @@ where
130130
}
131131

132132
#[inline]
133-
pub fn get_mut<'w>(
134-
&mut self,
133+
pub fn get_mut<'w, 's>(
134+
&'s mut self,
135135
world: &'w mut World,
136136
entity: Entity,
137-
) -> Result<<Q::Fetch as Fetch<'w, '_>>::Item, QueryEntityError> {
137+
) -> Result<<Q::Fetch as Fetch<'w, 's>>::Item, QueryEntityError> {
138138
// SAFETY: query has unique world access
139139
unsafe { self.get_unchecked(world, entity) }
140140
}
@@ -144,11 +144,11 @@ where
144144
/// This does not check for mutable query correctness. To be safe, make sure mutable queries
145145
/// have unique access to the components they query.
146146
#[inline]
147-
pub unsafe fn get_unchecked<'w>(
148-
&mut self,
147+
pub unsafe fn get_unchecked<'w, 's>(
148+
&'s mut self,
149149
world: &'w World,
150150
entity: Entity,
151-
) -> Result<<Q::Fetch as Fetch<'w, '_>>::Item, QueryEntityError> {
151+
) -> Result<<Q::Fetch as Fetch<'w, 's>>::Item, QueryEntityError> {
152152
self.validate_world_and_update_archetypes(world);
153153
self.get_unchecked_manual(
154154
world,
@@ -161,13 +161,13 @@ where
161161
/// # Safety
162162
/// This does not check for mutable query correctness. To be safe, make sure mutable queries
163163
/// have unique access to the components they query.
164-
pub unsafe fn get_unchecked_manual<'w>(
165-
&self,
164+
pub unsafe fn get_unchecked_manual<'w, 's>(
165+
&'s self,
166166
world: &'w World,
167167
entity: Entity,
168168
last_change_tick: u32,
169169
change_tick: u32,
170-
) -> Result<<Q::Fetch as Fetch<'w, '_>>::Item, QueryEntityError> {
170+
) -> Result<<Q::Fetch as Fetch<'w, 's>>::Item, QueryEntityError> {
171171
let location = world
172172
.entities
173173
.get(entity)

crates/bevy_ecs/src/system/commands/mod.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ pub trait Command: Send + Sync + 'static {
1616
}
1717

1818
/// A list of commands that will be run to modify a [`World`].
19-
pub struct Commands<'a> {
20-
queue: &'a mut CommandQueue,
21-
entities: &'a Entities,
19+
pub struct Commands<'w, 's> {
20+
queue: &'s mut CommandQueue,
21+
entities: &'w Entities,
2222
}
2323

24-
impl<'a> Commands<'a> {
24+
impl<'w, 's> Commands<'w, 's> {
2525
/// Create a new `Commands` from a queue and a world.
26-
pub fn new(queue: &'a mut CommandQueue, world: &'a World) -> Self {
26+
pub fn new(queue: &'s mut CommandQueue, world: &'w World) -> Self {
2727
Self {
2828
queue,
2929
entities: world.entities(),
@@ -50,7 +50,7 @@ impl<'a> Commands<'a> {
5050
/// }
5151
/// # example_system.system();
5252
/// ```
53-
pub fn spawn(&mut self) -> EntityCommands<'a, '_> {
53+
pub fn spawn<'a>(&'a mut self) -> EntityCommands<'w, 's, 'a> {
5454
let entity = self.entities.reserve_entity();
5555
EntityCommands {
5656
entity,
@@ -98,7 +98,7 @@ impl<'a> Commands<'a> {
9898
/// }
9999
/// # example_system.system();
100100
/// ```
101-
pub fn spawn_bundle<'b, T: Bundle>(&'b mut self, bundle: T) -> EntityCommands<'a, 'b> {
101+
pub fn spawn_bundle<'a, T: Bundle>(&'a mut self, bundle: T) -> EntityCommands<'w, 's, 'a> {
102102
let mut e = self.spawn();
103103
e.insert_bundle(bundle);
104104
e
@@ -123,7 +123,7 @@ impl<'a> Commands<'a> {
123123
/// }
124124
/// # example_system.system();
125125
/// ```
126-
pub fn entity(&mut self, entity: Entity) -> EntityCommands<'a, '_> {
126+
pub fn entity<'a>(&'a mut self, entity: Entity) -> EntityCommands<'w, 's, 'a> {
127127
EntityCommands {
128128
entity,
129129
commands: self,
@@ -159,12 +159,12 @@ impl<'a> Commands<'a> {
159159
}
160160

161161
/// A list of commands that will be run to modify an [`Entity`].
162-
pub struct EntityCommands<'a, 'b> {
162+
pub struct EntityCommands<'w, 's, 'a> {
163163
entity: Entity,
164-
commands: &'b mut Commands<'a>,
164+
commands: &'a mut Commands<'w, 's>,
165165
}
166166

167-
impl<'a, 'b> EntityCommands<'a, 'b> {
167+
impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> {
168168
/// Retrieves the current entity's unique [`Entity`] id.
169169
#[inline]
170170
pub fn id(&self) -> Entity {
@@ -252,7 +252,7 @@ impl<'a, 'b> EntityCommands<'a, 'b> {
252252
}
253253

254254
/// Returns the underlying `[Commands]`.
255-
pub fn commands(&mut self) -> &mut Commands<'a> {
255+
pub fn commands(&mut self) -> &mut Commands<'w, 's> {
256256
self.commands
257257
}
258258
}

crates/bevy_ecs/src/system/function_system.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,10 @@ impl<Param: SystemParam> SystemState<Param> {
8787

8888
/// Retrieve the [`SystemParam`] values. This can only be called when all parameters are read-only.
8989
#[inline]
90-
pub fn get<'a>(&'a mut self, world: &'a World) -> <Param::Fetch as SystemParamFetch<'a>>::Item
90+
pub fn get<'w, 's>(
91+
&'s mut self,
92+
world: &'w World,
93+
) -> <Param::Fetch as SystemParamFetch<'w, 's>>::Item
9194
where
9295
Param::Fetch: ReadOnlySystemParamFetch,
9396
{
@@ -98,10 +101,10 @@ impl<Param: SystemParam> SystemState<Param> {
98101

99102
/// Retrieve the mutable [`SystemParam`] values.
100103
#[inline]
101-
pub fn get_mut<'a>(
102-
&'a mut self,
103-
world: &'a mut World,
104-
) -> <Param::Fetch as SystemParamFetch<'a>>::Item {
104+
pub fn get_mut<'w, 's>(
105+
&'s mut self,
106+
world: &'w mut World,
107+
) -> <Param::Fetch as SystemParamFetch<'w, 's>>::Item {
105108
self.validate_world_and_update_archetypes(world);
106109
// SAFE: World is uniquely borrowed and matches the World this SystemState was created with.
107110
unsafe { self.get_unchecked_manual(world) }
@@ -142,10 +145,10 @@ impl<Param: SystemParam> SystemState<Param> {
142145
/// access is safe in the context of global [`World`] access. The passed-in [`World`] _must_ be the [`World`] the [`SystemState`] was
143146
/// created with.
144147
#[inline]
145-
pub unsafe fn get_unchecked_manual<'a>(
146-
&'a mut self,
147-
world: &'a World,
148-
) -> <Param::Fetch as SystemParamFetch<'a>>::Item {
148+
pub unsafe fn get_unchecked_manual<'w, 's>(
149+
&'s mut self,
150+
world: &'w World,
151+
) -> <Param::Fetch as SystemParamFetch<'w, 's>>::Item {
149152
let change_tick = world.increment_change_tick();
150153
let param = <Param::Fetch as SystemParamFetch>::get_param(
151154
&mut self.param_state,

0 commit comments

Comments
 (0)