From 419359b9a7af62939e8d4466d9c539b9a7cf4cf3 Mon Sep 17 00:00:00 2001 From: Chris Russell <8494645+chescock@users.noreply.github.com> Date: Tue, 27 Aug 2024 21:37:52 -0400 Subject: [PATCH] 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

,...)`. 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::()) .build_state(&mut world) .build_system(|a, b| *a + *b + 1); ``` --- crates/bevy_ecs/src/system/builder.rs | 15 ++++++ crates/bevy_ecs/src/system/function_system.rs | 50 ++++++++++++++++++- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/system/builder.rs b/crates/bevy_ecs/src/system/builder.rs index ab440ff3042dd..cd532776e5b88 100644 --- a/crates/bevy_ecs/src/system/builder.rs +++ b/crates/bevy_ecs/src/system/builder.rs @@ -457,6 +457,21 @@ mod tests { assert_eq!(result, 3); } + #[test] + fn multi_param_builder_inference() { + let mut world = World::new(); + + world.spawn(A); + world.spawn_empty(); + + let system = (LocalBuilder(0u64), ParamBuilder::local::()) + .build_state(&mut world) + .build_system(|a, b| *a + *b + 1); + + let result = world.run_system_once(system); + assert_eq!(result, 1); + } + #[test] fn param_set_builder() { let mut world = World::new(); diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index fe2420a663b85..aff5da6eb522c 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -208,6 +208,52 @@ pub struct SystemState { archetype_generation: ArchetypeGeneration, } +// Allow closure arguments to be inferred. +// For a closure to be used as a `SystemParamFunction`, it needs to be generic in any `'w` or `'s` lifetimes. +// Rust will only infer a closure to be generic over lifetimes if it's passed to a function with a Fn constraint. +// So, generate a function for each arity with an explicit `FnMut` constraint to enable higher-order lifetimes, +// along with a regular `SystemParamFunction` constraint to allow the system to be built. +macro_rules! impl_build_system { + ($($param: ident),*) => { + impl<$($param: SystemParam),*> SystemState<($($param,)*)> { + /// Create a [`FunctionSystem`] from a [`SystemState`]. + /// This method signature allows type inference of closure parameters for a system with no input. + /// You can use [`SystemState::build_system_with_input()`] if you have input, or [`SystemState::build_any_system()`] if you don't need type inference. + pub fn build_system< + Out: 'static, + Marker, + F: FnMut($(SystemParamItem<$param>),*) -> Out + + SystemParamFunction + > + ( + self, + func: F, + ) -> FunctionSystem + { + self.build_any_system(func) + } + + /// Create a [`FunctionSystem`] from a [`SystemState`]. + /// This method signature allows type inference of closure parameters for a system with input. + /// You can use [`SystemState::build_system()`] if you have no input, or [`SystemState::build_any_system()`] if you don't need type inference. + pub fn build_system_with_input< + Input, + Out: 'static, + Marker, + F: FnMut(In, $(SystemParamItem<$param>),*) -> Out + + SystemParamFunction, + >( + self, + func: F, + ) -> FunctionSystem { + self.build_any_system(func) + } + } + } +} + +all_tuples!(impl_build_system, 0, 16, P); + impl SystemState { /// Creates a new [`SystemState`] with default state. /// @@ -242,7 +288,9 @@ impl SystemState { } /// Create a [`FunctionSystem`] from a [`SystemState`]. - pub fn build_system>( + /// This method signature allows any system function, but the compiler will not perform type inference on closure parameters. + /// You can use [`SystemState::build_system()`] or [`SystemState::build_system_with_input()`] to get type inference on parameters. + pub fn build_any_system>( self, func: F, ) -> FunctionSystem {