SystemRunner param and macro syntax
Objective
NOTE: blocked on #21917 and #21923
resolves #16680
Add a new system param for running systems inside other systems. Also, I've included some macros for nice syntax on top.
I'm pretty proud of how nice I was able to make this, but there's still a bit of work to do, especially around generic code :)
Testing
- Ran examples
- Tried to migrate existing
pipeandmapimpls, but failed. This PR is pretty robust for most use cases but isn't fully ready for generic code yet. In particular, it's difficult to make sure the input types match (often have to wrap withStaticSystemInput) andReadOnlySystembound is inferred correctly when using for run conditions. These only really matter for combinators likepipeandmapthough, since otherwise they're run exactly the same.
Showcase
Click to view showcase
fn count_a(a: Query<&A>) -> u32 {
a.count()
}
fn count_b(b: Query<&B>) -> u32 {
b.count()
}
let get_sum = (
ParamBuilder::system(count_a),
ParamBuilder::system(count_b)
)
.build_system(
|mut run_a: SystemRunner<(), u32>, mut run_b: SystemRunner<(), u32>| -> Result<u32, RunSystemError> {
let a = run_a.run()?;
let b = run_b.run()?;
Ok(a + b)
}
);
let get_sum = compose! {
|| -> Result<u32, RunSystemError> {
let a = run!(count_a)?;
let b = run!(count_b)?;
Ok(a + b)
}
}
- Tried to migrate existing
pipeandmapimpls, but failed. This PR is pretty robust for most use cases but isn't fully ready for generic code yet. In particular, it's difficult to make sure the input types match (often have to wrap withStaticSystemInput) andReadOnlySystembound is inferred correctly when using for run conditions. These only really matter for combinators likepipeandmapthough, since otherwise they're run exactly the same.
I think I got pipe and and mostly working on this branch! I had to add lots of 'static annotations, but I think those are mostly harmless.
As you mentioned, the big problem is that the resulting systems take StaticSystemInput<In> instead of In, which means they can't be used in the schedule because that expects In = () and not In = StaticSystemInput<()>. Maybe we could create a wrapper type that converts a System<In = StaticSystemInput<In>> to a System<In = In>?
pub trait IntoSystem<In: SystemInput, Out, Marker>: Sized {
// ...
fn pipe2<B, BIn, BOut, MarkerB>(
self,
system: B,
) -> impl System<In = StaticSystemInput<'static, In>, Out = BOut>
where
Out: 'static,
B: IntoSystem<BIn, BOut, MarkerB> + 'static,
for<'a> BIn: SystemInput<Inner<'a> = Out> + 'static,
In: 'static,
BOut: 'static,
Marker: 'static,
MarkerB: 'static,
Self: 'static,
{
compose_with!(
|StaticSystemInput(input): StaticSystemInput<In>| -> Result<BOut, RunSystemError> {
let value = run!(self, input)?;
run!(system, value)
}
)
}
pub trait SystemCondition<Marker, In: SystemInput = ()>:
// ...
fn and2<M: 'static, C: SystemCondition<M, In> + 'static>(
self,
and: C,
) -> impl ReadOnlySystem<In = StaticSystemInput<'static, In>, Out = bool>
where
for<'a> In: SystemInput<Inner<'a>: Copy> + 'static,
Self: 'static,
Marker: 'static,
{
compose_with!(
|StaticSystemInput(input): StaticSystemInput<In>| -> Result<bool, RunSystemError> {
Ok(run!(self, input)? && run!(and, input)?)
}
)
}
Thanks for the input! I'll spin up a few PRs for BuilderSystem and RemapInputSystem (or whatever we call it) and start cleaning things up tonight or tomorrow
Also I realized SystemInput::unwrap is probably unnecessary since you can just destructure StaticSystemInput :P