Declarative plugins / Graph-based plugins MVP#24920
Conversation
|
The generated |
My take here is mostly "don't do that". I'm very reluctant to support this sort of pattern in general: states and startup systems are better, and there's nothing stopping people from just manually writing code that modifies My initial impressions are that you're being too careful to avoid breakage for weird users here, when 90% of the cases should be handled just fine by declarative patterns, and 90% of the remainder are patterns that should be rethought completely :) WRT rendering it feels like a |
The plan is definitely this way of doing plugins becoming the only way of doing plugins with no escape hatches. What I want to do here is keep scope small, then expand it in follow-up PRs, mark the old API as deprecated, and then replace
Yeah I'm still cooking in my head about how to manage different plugin "contexts" like render plugin / editor plugin. This is a fair shout. |
|
oh neat! |
|
The generated |
|
I can live with a gradual approach :) Proposed migration plan:
My kingdom for trait aliases, but maybe you can make it easier with blanket impls. |
I personally think plugins are too central (and future breaking changes to them too far reaching) to just wing it. Imo this deserves a working group with lots of adversarial design. We should have buy in from as many Bevy devs as possible and high confidence in our path across many scenarios. I do think this PR is a good conversation starter / a solid working group kickoff item, but I don't think the need is so pressing that we need to move quickly rather than surely here. Lots of open questions:
|
I failed to read this in the appropriate context. I agree with your assessment of how the trait rollout should happen. (I do still think this is working group material / high-scrutiny work) |
|
Heya, thanks for taking a look! I am writing these answers down mostly for my sake, but I hope they're helpful 😅
I agree, I've also been stewing on this for 4+ months including a heavily-edited and now mangled design doc (I got frustrated partway through an additional round of editing it and decided to just take a stab at impl). I'm fine with work in this PR being too dubious to merge and in need of more discussion, but I really want to work out the implementation details because it's been weighing on me. I've struggled to convince people outside of alice and a few others. It's been suggested by @MalekiRe this could be a 3rd party crate for now given it doesn't actually cause any bevy-internal changes, and I might move it to there if it makes more sense. But yeah I do want to get the conversation actually going :)
We're building a graph of "plugin output" nodes and dependency edges. We then expand that into a graph of "items that can be added to world" through a solving step which "asks" each plugin for approval wrt resources and plugin configs. What the end user1 sees is methods on a type that is otherwise opaque to them. They have a limited set of operations they can perform, we have an underlying type we're free to change without end user scrutiny. The data model should not be plugin-author-accessible outside of the additive methods on the opaque type, imo. I'm of the opinion that users and plugin authors that care about internals should be registering startup systems, not doing startup logic in plugin registration. I can see points of contention there, but I think there are real benefits to keeping plugin registration about exposing information (including where the initialisation code is) rather than directly running initialisation code. As alice said above, the places where people are currently doing direct world/subapp access could have needs met by a declarative API. It'd just take some more awareness of what the needs are for that use.
I chatted a bit with @ItsDoot today on the discord and I'm on the fence about there being a contradiction between the two efforts. On one hand, being able to use patches on resources (and plugins) is very cool, and using a On the other hand, this might be a data representation question rather than an API design question. At this point in time I think both can live together. I also think that exposing plugins as entities as the entry point for plugin registration might prevent us from being able to make confident assertions about deep, interconnected plugin dependency graphs "just working." Being able to hide Even if
I would hope it's the start of something enabling, as it's a move from plugins being about adding data directly to world towards building a metadata-rich graph to reason about. Hypothetically it's a spot where an API surface can be built, and a space where we can build an bevy-internal model for what to do with dynamic plugins. I do not have the answers for how implementation should look. It was on my mind, but not enough that I have an answer. I'm guessing the answer depends on if we can expose something consistent enough for dynamic plugins to be able to expose the data (function pointers) and metadata (type information etc.) needed to register what they need to. I think an opaque-to-the-end-user data type with a well-defined registration API is still the key here. I could be way off base here (and probably am).
First priority for what I'm doing here is making it smooth for developers to move from Later I want to see the plugin API change after that (because it really needs it) but as I said above I don't want to cause bevy to lose users because there was a sudden massive change to the API everyone's stuff touches that people need to think about for more than a second :) Footnotes
|
|
Copy/pasting this from Discord: I do think we need more explicit stated motivation before moving forward with any plugin changes. Iirc our motivations were:
I'm personally dubious of things like "plugins swapping out other plugin's systems". I've never really been a fan of the "reach in and do arbitrary things to the schedule" stuff. Systems aren't generally a public API surface. They (in combination) produce a set of behaviors. It should be up to the author of the systems to build "configuration levers" for the people that want them (ex: public system sets, settings to disable features in controlled ways, etc). |
|
|
||
| impl DeclarativePlugin for PluginB { | ||
| fn build(&self, output: &mut bevy::app::PluginOutput) { | ||
| output.add_dependency_no_worries::<PluginA>(); |
There was a problem hiding this comment.
This example should show an actual dependency
There was a problem hiding this comment.
We're in drafts still, that's the plan later ^^
Update the example Change up the Approval type a bunch
5d72e1f to
d3e42f5
Compare
Objective
Get an MVP in of the declarative plugins implementation laid out in https://hackmd.io/@fallible/BJ_oByDcbl. Opening this as a draft for my own sake + a bit more visibility, but it's not in a seriously reviewable state just yet.
Principle
Plugin registration adds stuff directly to an
App, which hands stuff directly off toWorld. This is a procedural solution to what is mostly a "build system" problem. Build systems use graphs and solvers to juggle the needs of a program and manage complex constraints.We can do something similar here by moving the API surface of plugin registration away from
Appand towards a "Manifest"-style data structure of preferences and approval/veto functions for configurations of plugins and resources, then find a solution for a whole dependency tree of plugins that everyone in the supply chain agrees on / doesn't veto.Short-term Motivations:
Long-term Motivations:
my_cool_systemruns inLastnotUpdate, and this gets applied beforeWorldis constructed and theAppis run.Solution
DeclarativePlugintrait that is a drop-in replacement for most users needs.AppBefore leaving draft (TODO: Remove)
Plugin/registration-facingAppAPI, but over aPluginOutputtype that stores the systems, observers, resources, messages, and schedule labels instead of directly adding them to an app.bevy_appHashablea supertrait of DeclarativePlugin to prevent infinite dependency expansion (Plugin A --> Plugin B --> Plugin C --> Plugin A)?Not for this MVP
RenderApp's guts to set up rendering systems.DeclarativePluginis clunky, but it should become the mainPlugintrait and that plugins that really need access to an underlyingAppbefore it's run as a program will use an legacy, renamedPlugintrait or lobby for their needs being met by the declarative API.FAQ
Please raise any concerns / suggestions, this is a draft.
Worldin plugin registration, what do I do?"Startupsystem, or stick with whatever the currentPluginwill be. Alternatively, please state the use case here it can be accounted for in design.Worries / Scope
This PR might be trying to do too many things. An actual MVP might just create
DeclarativePluginwith aPluginOutputthat wraps anAppwithout giving an escape hatch for direct access and markPluginas deprecated while opening aFoundationalPluginto maintain the behaviour ofPluginwithAppaccess.Testing
TODO
Showcase
TODO: example of dependencies between plugins
Footnotes
The bevy ecosystem cannot at this time develop crates that have complex plugin dependency needs without asking the user to do some configuration themselves. Plugins that eagerly add 3rd party plugins to a bevy app panic when a plugin is added more than once. ↩