Skip to content

Commit 419359b

Browse files
authored
SystemParamBuilder - Enable type inference of closure parameter when building dynamic systems (#14820)
# Objective When building a system from `SystemParamBuilder`s and defining the system as a closure, the compiler should be able to infer the parameter types from the builder types. ## Solution Create methods for each arity that take an argument that implements both `SystemParamFunction` as well as `FnMut(SystemParamItem<P>,...)`. The explicit `FnMut` constraint will allow the compiler to infer the necessary higher-ranked lifetimes along with the parameter types. I wanted to show that this was possible, but I can't tell whether it's worth the complexity. It requires a separate method for each arity, which pollutes the docs a bit: ![SystemState build_system docs](https://github.com/user-attachments/assets/5069b749-7ec7-47e3-a5e4-1a4c78129f78) ## Example ```rust let system = (LocalBuilder(0u64), ParamBuilder::local::<u64>()) .build_state(&mut world) .build_system(|a, b| *a + *b + 1); ```
1 parent 8895113 commit 419359b

File tree

2 files changed

+64
-1
lines changed

2 files changed

+64
-1
lines changed

crates/bevy_ecs/src/system/builder.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,21 @@ mod tests {
457457
assert_eq!(result, 3);
458458
}
459459

460+
#[test]
461+
fn multi_param_builder_inference() {
462+
let mut world = World::new();
463+
464+
world.spawn(A);
465+
world.spawn_empty();
466+
467+
let system = (LocalBuilder(0u64), ParamBuilder::local::<u64>())
468+
.build_state(&mut world)
469+
.build_system(|a, b| *a + *b + 1);
470+
471+
let result = world.run_system_once(system);
472+
assert_eq!(result, 1);
473+
}
474+
460475
#[test]
461476
fn param_set_builder() {
462477
let mut world = World::new();

crates/bevy_ecs/src/system/function_system.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,52 @@ pub struct SystemState<Param: SystemParam + 'static> {
208208
archetype_generation: ArchetypeGeneration,
209209
}
210210

211+
// Allow closure arguments to be inferred.
212+
// For a closure to be used as a `SystemParamFunction`, it needs to be generic in any `'w` or `'s` lifetimes.
213+
// Rust will only infer a closure to be generic over lifetimes if it's passed to a function with a Fn constraint.
214+
// So, generate a function for each arity with an explicit `FnMut` constraint to enable higher-order lifetimes,
215+
// along with a regular `SystemParamFunction` constraint to allow the system to be built.
216+
macro_rules! impl_build_system {
217+
($($param: ident),*) => {
218+
impl<$($param: SystemParam),*> SystemState<($($param,)*)> {
219+
/// Create a [`FunctionSystem`] from a [`SystemState`].
220+
/// This method signature allows type inference of closure parameters for a system with no input.
221+
/// You can use [`SystemState::build_system_with_input()`] if you have input, or [`SystemState::build_any_system()`] if you don't need type inference.
222+
pub fn build_system<
223+
Out: 'static,
224+
Marker,
225+
F: FnMut($(SystemParamItem<$param>),*) -> Out
226+
+ SystemParamFunction<Marker, Param = ($($param,)*), In = (), Out = Out>
227+
>
228+
(
229+
self,
230+
func: F,
231+
) -> FunctionSystem<Marker, F>
232+
{
233+
self.build_any_system(func)
234+
}
235+
236+
/// Create a [`FunctionSystem`] from a [`SystemState`].
237+
/// This method signature allows type inference of closure parameters for a system with input.
238+
/// You can use [`SystemState::build_system()`] if you have no input, or [`SystemState::build_any_system()`] if you don't need type inference.
239+
pub fn build_system_with_input<
240+
Input,
241+
Out: 'static,
242+
Marker,
243+
F: FnMut(In<Input>, $(SystemParamItem<$param>),*) -> Out
244+
+ SystemParamFunction<Marker, Param = ($($param,)*), In = Input, Out = Out>,
245+
>(
246+
self,
247+
func: F,
248+
) -> FunctionSystem<Marker, F> {
249+
self.build_any_system(func)
250+
}
251+
}
252+
}
253+
}
254+
255+
all_tuples!(impl_build_system, 0, 16, P);
256+
211257
impl<Param: SystemParam> SystemState<Param> {
212258
/// Creates a new [`SystemState`] with default state.
213259
///
@@ -242,7 +288,9 @@ impl<Param: SystemParam> SystemState<Param> {
242288
}
243289

244290
/// Create a [`FunctionSystem`] from a [`SystemState`].
245-
pub fn build_system<Marker, F: SystemParamFunction<Marker, Param = Param>>(
291+
/// This method signature allows any system function, but the compiler will not perform type inference on closure parameters.
292+
/// You can use [`SystemState::build_system()`] or [`SystemState::build_system_with_input()`] to get type inference on parameters.
293+
pub fn build_any_system<Marker, F: SystemParamFunction<Marker, Param = Param>>(
246294
self,
247295
func: F,
248296
) -> FunctionSystem<Marker, F> {

0 commit comments

Comments
 (0)