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 {