-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
bevy_reflect: Store functions as DynamicClosure<'static> in FunctionRegistry
#14704
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
bevy_reflect: Store functions as DynamicClosure<'static> in FunctionRegistry
#14704
Conversation
06759fa to
803d5fa
Compare
|
In my mind, the changes here to |
Yeah I think it's really unfortunate we can't get any additional benefits out of In fact, I think that's the only major issue wrt improving But I think we should save the possible removal/refactor of |
|
CI is failing on merge: https://github.com/bevyengine/bevy/actions/runs/10405781995/job/28817325133 |
e4a471c to
567058e
Compare
|
As an update, once this is merged, I'm going to refactor function reflection to get rid of the |
# Objective One of the changes in #14704 made `DynamicFunction` effectively the same as `DynamicClosure<'static>`. This change meant that the de facto function type would likely be `DynamicClosure<'static>` instead of the intended `DynamicFunction`, since the former is much more flexible. We _could_ explore ways of making `DynamicFunction` implement `Copy` using some unsafe code, but it likely wouldn't be worth it. And users would likely still reach for the convenience of `DynamicClosure<'static>` over the copy-ability of `DynamicFunction`. The goal of this PR is to fix this confusion between the two types. ## Solution Firstly, the `DynamicFunction` type was removed. Again, it was no different than `DynamicClosure<'static>` so it wasn't a huge deal to remove. Secondly, `DynamicClosure<'env>` and `DynamicClosureMut<'env>` were renamed to `DynamicFunction<'env>` and `DynamicFunctionMut<'env>`, respectively. Yes, we still ultimately kept the naming of `DynamicFunction`, but changed its behavior to that of `DynamicClosure<'env>`. We need a term to refer to both functions and closures, and "function" was the best option. [Originally](https://discord.com/channels/691052431525675048/1002362493634629796/1274091992162242710), I was going to go with "callable" as the replacement term to encompass both functions and closures (e.g. `DynamciCallable<'env>`). However, it was [suggested](https://discord.com/channels/691052431525675048/1002362493634629796/1274653581777047625) by @SkiFire13 that the simpler "function" term could be used instead. While "callable" is perhaps the better umbrella term—being truly ambiguous over functions and closures— "function" is more familiar, used more often, easier to discover, and is subjectively just "better-sounding". ## Testing Most changes are purely swapping type names or updating documentation, but you can verify everything still works by running the following command: ``` cargo test --package bevy_reflect ```
Objective
#14098 added the
FunctionRegistryfor registering functions such that they can be retrieved by name and used dynamically. One thing we chose to leave out in that initial PR is support for closures.Why support closures? Mainly, we don't want to prohibit users from injecting environmental data into their registered functions. This allows these functions to not leak their internals to the public API.
For example, let's say we're writing a library crate that allows users to register callbacks for certain actions. We want to perform some actions before invoking the user's callback so we can't just call it directly. We need a closure for this:
We could have made our callback take a reference to the user's callback. This would remove the need for the closure, but it would change our desired API to place the burden of fetching the correct callback on the caller.
Solution
Modify the
FunctionRegistryto store registered functions asDynamicClosure<'static>instead ofDynamicFunction(now usingIntoClosureinstead ofIntoFunction).Due to limitations in Rust and how function reflection works,
DynamicClosure<'static>is functionally equivalent toDynamicFunction. And a normal function is considered a subset of closures (it's a closure that doesn't capture anything), so there shouldn't be any difference in usage: all functions that satisfyIntoFunctionshould satisfyIntoClosure.This means that the registration API introduced in #14098 should require little-to-no changes on anyone following
main.Closures vs Functions
One consideration here is whether we should keep closures and functions separate.
This PR unifies them into
DynamicClosure<'static>, but we can consider splitting them up. The reasons we might want to do so are:DynamicFunctionthat make it useful to separate the twoThe reasons behind this PR's unification approach are:
FunctionRegistryget_functionor elseget_closure")DynamicClosure<'static>as the de facto dynamic callable (similar to how most APIs in Rust code tend to preferimpl Fn() -> Stringoverfn() -> String)I'd love to hear feedback on this matter, and whether we should continue with this PR's approach or switch to a split model.
Testing
You can test locally by running:
Showcase
Closures can now be registered into the
FunctionRegistry: