Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SystemParamBuilder - Enable type inference of closure parameter when building dynamic systems #14820

Merged

Conversation

chescock
Copy link
Contributor

Objective

When building a system from SystemParamBuilders 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

Example

let system = (LocalBuilder(0u64), ParamBuilder::local::<u64>())
    .build_state(&mut world)
    .build_system(|a, b| *a + *b + 1);

@TrialDragon TrialDragon added C-Feature A new feature, making something new possible A-ECS Entities, components, systems, and events C-Usability A targeted quality-of-life change that makes Bevy easier to use S-Needs-Review Needs reviewer attention (from anyone!) to move forward D-Macros Code that generates Rust code labels Aug 19, 2024
Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to see that there's a test and excellent internal docs, thank you.

@alice-i-cecile alice-i-cecile removed the C-Feature A new feature, making something new possible label Aug 24, 2024
pub fn build_system<Marker, F: SystemParamFunction<Marker, Param = Param>>(
/// 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<Marker, F: SystemParamFunction<Marker, Param = Param>>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we're implementing build_system for a FnMut<Param1, Param2, ...>
which calls build_any_system, which is implemented for SystemParamFunction
and SystemParamFunction is implemented for all FnMut<Param1, Param2...>

I get your overall logic to add type inference, but would there be a way to get rid of SystemParamFunction altogether and just use macros everywhere to deal directly with FnMut<Param1, Param2...>?
It seems weird to have this extra indirection.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SystemParamFunction is the existing trait that powers FunctionSystem. So I can't get rid of that!

I agree it's weird, but given that we need a SystemParamFunction to make a system, I think this is the simplest way to make parameter type inference work in the current rust compiler.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like someone like @SkiFire13 would have a better opinion than mine

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that getting rid of SystemParamFunction is likely not an option, as it power all the other system trait machinery. The duplicated FnMut bounds on these methods do feel a bit unfortunate given that SystemParamFunction's impls already have them (and twice!). However I don't think you can avoid them, as closure type inference is very finicky and requires a direct FnMut bound on the function that the closure is passed to, which in this case is build_system. So in the end:

  • SystemParamFunction is needed for the existing system machinery (e.g. App::add_systems)
  • the FnMut bounds here are needed because closure inference won't look at them if they are anywhere else

@alice-i-cecile alice-i-cecile added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Aug 27, 2024
@alice-i-cecile
Copy link
Member

Oh BTW, before I merge this, do you want to try the fake_variadic trick here and see if this resolves the docs regression?

@chescock
Copy link
Contributor Author

do you want to try the fake_variadic trick here and see if this resolves the docs regression?

I'm happy to try it! I don't expect it to work, though. I think that only works for trait impls, and this is a bunch of impl blocks.

How do I test that? Just running cargo doc doesn't seem to get me any collapsed impls, even for traits where I see them collapsed on dev-docs.bevyengine.org.

@alice-i-cecile
Copy link
Member

@bash, the author of #14703 may have advice for you there.

@alice-i-cecile
Copy link
Member

Regardless, I'll merge this in as-is once merge conflicts are resolved :)

@bash
Copy link
Contributor

bash commented Aug 27, 2024

How do I test that? Just running cargo doc doesn't seem to get me any collapsed impls, even for traits where I see them collapsed on dev-docs.bevyengine.org.

You can build the docs with fake_variadic enabled by using the nightly compiler and custom RUSTFLAGS / RUSTDOCFLAGS (See https://github.com/bevyengine/bevy/blob/main/.github/workflows/docs.yml#L59).

This should do the trick:

RUSTFLAGS='--cfg docsrs_dep' RUSTDOCFLAGS='--cfg=docsrs' cargo +nightly doc

If you don't want to remember the correct incantation there's a handy cargo-docs-rs tool that pulls the right flags directly from Cargo.toml.

@alice-i-cecile
Copy link
Member

Let me know if you want to experiment more, or if I should merge this as is. If I don't hear back I'll merge this on Monday :)

@chescock
Copy link
Contributor Author

Nope, putting the attribute on the impl block fails with

error: `#[doc(fake_variadic)]` must be used on the first of a set of tuple or fn pointer trait impls with varying arity

so I don't think it will work for this.

(And the reason I had trouble before is that I forgot how to set environment variables in PowerShell :).)

I can make it work for impl<P: SystemParam, B: SystemParamBuilder<P>> SystemParamBuilder<(P₁, P₂, …, Pₙ)> for (B₁, B₂, …, Bₙ), though, which got missed because the SystemParamBuilder and fake_variadic PRs were open at the same time. I'll push up a separate PR for that!

@alice-i-cecile alice-i-cecile added this pull request to the merge queue Aug 28, 2024
Merged via the queue into bevyengine:main with commit 419359b Aug 28, 2024
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Usability A targeted quality-of-life change that makes Bevy easier to use D-Macros Code that generates Rust code S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants