-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
Expressively define plugins using functions #11080
Conversation
Great idea! Wouldn't |
It's worth calling out prior art to demonstrate the usefulness and desire for this pattern: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be a little weird having mixed-case plugins haha but overall I like the simplicity of this new approach— especially for generic plugins.
As a side note, if we continue with this approach, I think we should make a distinction of when it should be used style-wise. As in, engine code should use the current method for plugins so as to avoid breakages and examples (or "user code") should use this new method for plugins. |
I can never tell when to use either tag. However I chose |
If I understood correctly |
Co-authored-by: Federico Rinaldi <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This works for plugins that only have build
function and no configuration. In my code that's almost all of them.
I agree with this, it seems to simplify things a lot.
Private plugins that are only created for better modularity would be functions. Top-level plugins in crates and other public API in general would be structs (they most likely have some configuration anyway). Although making almost everything functions is possible via closures, it's too early to make any judgement on that. |
As an incremental step: I really like this! It's a simple improvement that reduces boilerplate and fully captures everything it means to be a plugin. However, I'm quite nervous about how this would interact with other plans for plugins. For example, automatically labelling systems from a plugin with the plugin type for #2160, or a more structured approach with multiple methods to tackle #1255. From my perspective, this code is likely to break or complicate those efforts. If you can convince me that it's either worth it in the interim, or that this flavor of approach can coexist with those plans (or if there's an alternate solution to those serious longstanding problems in our plugin architecture!), then I'd be happy to approve this and reduce boilerplate! |
TBH, automatically labelling systems from a plugin seems like a kind of strange thing to want. However it could still be done, using the same trick we use for
About plugins with build order. This is an important feature, however I don't think it makes much sense for build dependencies to be specified internally to a plugin. If we want graph-like plugin build order, we should specify dependencies externally similar to how systems are scheduled. If I want plugin Whatever form that feature ends up taking, I feel like this PR will still be applicable. Even if we have powerful new tools for ordering plugins, that only matters for a small number of plugins. Most plugins are just containers of configuration added to an app, which can be easily expressed with a single function. If we end up adding more methods to the Merry Christmas BTW 🎄 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm convinced: I think this is a nice step forward then.
Agreed. It's easy to see how we could implement |
I feel like |
I was more thinking as an extension to #11228. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a great improvement in terms of ergonomics and generally am OK with merging as is.
However, I do think some things need to be considered here. Namely the mental separation and impact on documentation discoverablity. Functions have historically been only systems or actual functions in Bevy-related projects. Likewise plugins have been discoverable as structs or enums, but that consistency won't be maintained going forward. These aren't hard blocker issues, but we may need to think on what impact this will have on the ecosystem as a whole.
@@ -60,6 +94,12 @@ pub trait Plugin: Downcast + Any + Send + Sync { | |||
|
|||
impl_downcast!(Plugin); | |||
|
|||
impl<T: Fn(&mut App) + Send + Sync + 'static> Plugin for T { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This prevents us from making any other auto-impl, which is not an issue onto itself, but I think this is worth calling out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not necessarily. Even if we can't add more impls for Plugin
directly, the Plugins<M>
trait has a marker generic, which means we could add auto-impls that allow us to call .add_plugins(...)
for additional types
|
Just adding my two cents here: the lack of something removes the discoverability and expressiveness of things. And while I really would like the ability to have functions as plugins, I would also want it quite clear that a function is a plugin somehow by just looking at the function. One example is that we now enforce the I have noticed that since sometimes, we declare functions that are basically only helper functions inside a system. The mental overhead of keeping track of what is a system, and what is a regular function is a bit taxing, especially so if you are new to the codebase, new to bevy, or going back to fix or change something old. ideally without i would like it if the functions that are systems was declared something like:
and likewise I would prefer if we now would add plugins as a system:
The issue is mainly when you have a plugin, a helper function and a system, and they look like this:
Please tell me what is a plugin and what is a system? Because how I add them to app will surely change things, and most likely result in a runtime crash |
I think that whenever we use or advertise this feature, we have to be really strict about the |
I like the idea of marking systems and plugins with attributes, even more than adding a suffix to the identifier, as it is statically checked by the compiler. Unfortunately, Cart have always been determined to not follow this style, and for the time being, it has been a reasonable decision that kept system definition very simple. |
Merging with @JoJoJet as SME-ECS. |
# Objective Plugins are an incredible tool for encapsulating functionality. They are low-key one of Bevy's best features. Combined with rust's module and privacy system, it's a match made in heaven. The one downside is that they can be a little too verbose to define. 90% of all plugin definitions look something like this: ```rust pub struct MyPlugin; impl Plugin for MyPlugin { fn build(&self, app: &mut App) { app.init_resource::<CameraAssets>() .add_event::<SetCamera>() .add_systems(Update, (collect_set_camera_events, drive_camera).chain()); } } ``` Every so often it gets a little spicier: ```rust pub struct MyGenericPlugin<T>(PhantomData<T>); impl<T> Default for MyGenericPlugin<T> { fn default() -> Self { ... } } impl<T> Plugin for MyGenericPlugin<T> { ... } ``` This is an annoying amount of boilerplate. Ideally, plugins should be focused and small in scope, which means any app is going to have a *lot* of them. Writing a plugin should be as easy as possible, and the *only* part of this process that carries any meaning is the body of `fn build`. ## Solution Implement `Plugin` for functions that take `&mut App` as a parameter. The two examples above now look like this: ```rust pub fn my_plugin(app: &mut App) { app.init_resource::<CameraAssets>() .add_event::<SetCamera>() .add_systems(Update, (collect_set_camera_events, drive_camera).chain()); } pub fn my_generic_plugin<T>(app: &mut App) { // No need for PhantomData, it just works. } ``` Almost all plugins can be written this way, which I believe will make bevy code much more attractive. Less boilerplate and less meaningless indentation. More plugins with smaller scopes. --- ## Changelog The `Plugin` trait is now implemented for all functions that take `&mut App` as their only parameter. This is an abbreviated way of defining plugins with less boilerplate than manually implementing the trait. --------- Co-authored-by: Federico Rinaldi <[email protected]>
Objective
Plugins are an incredible tool for encapsulating functionality. They are low-key one of Bevy's best features. Combined with rust's module and privacy system, it's a match made in heaven.
The one downside is that they can be a little too verbose to define. 90% of all plugin definitions look something like this:
Every so often it gets a little spicier:
This is an annoying amount of boilerplate. Ideally, plugins should be focused and small in scope, which means any app is going to have a lot of them. Writing a plugin should be as easy as possible, and the only part of this process that carries any meaning is the body of
fn build
.Solution
Implement
Plugin
for functions that take&mut App
as a parameter.The two examples above now look like this:
Almost all plugins can be written this way, which I believe will make bevy code much more attractive. Less boilerplate and less meaningless indentation. More plugins with smaller scopes.
Changelog
The
Plugin
trait is now implemented for all functions that take&mut App
as their only parameter. This is an abbreviated way of defining plugins with less boilerplate than manually implementing the trait.