-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
RFC: Barriers with Before/After markers #10618
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
Conversation
I'm not a fan of adding this to the system signature. Systems should describe what they do, their configuration should determine when and how they're run. When we blur the barrier, we increase cognitive and implementation complexity, and reduce reusability. I'd much prefer #8595 + system ambiguity detection by default as a way to avoid these problems. |
I think this is terminology issue. We may call this thing "configured system" of something like that instead of just system. Practically current setup is very inconvenient and my largest usability issue with bevy. I'm not not confident any time that my system setup is configured correctly, and I'm getting less confidence after any change. And to have good night sleep I just group the systems into larger systems. Which is not how bevy is supposed to be used. BTW I watched your talk recently, you mentioned dependency injection, and this PR is it! It allows not just linking systems to world objects, but also systems to each other. (Also this can be viewed as an alternative to
|
Explicit ordering of systems is hard: during refactoring you forgot one edge, and your program befomes non-deterministic. This draft PR proposes implementing ordering based on function signature params (`SystemParam`). Basically, the idea is this. We introduce two new system params: ``` Before<T> After<T> ``` where `T` must be a `SystemSet + Default`. And it can be used like this: ``` #[derive(SystemSet)] struct MyBarrier; fn first(_before: Before<MyBarrier>) {} fn second(_after: After<MyBarrier>) {} ``` Now if `first` and `second` are added to the same schedule, they will be ordered like if `first.before(after)` was done explicitly. `SystemParam` are composable, so it is possible to build higher level constructs on top of it. For example, we may want to schedule all event writers before all event writers: ``` #[derive(SystemParam)] struct MyOrderedEventWriter<T> { writer: EventWriter<T>, after: After<MyOrderedEventWriterBarrier<T>>, } #[derive(SystemParam)] struct MyOrderedEventWriter<T> { reader: EventReader<T>, after: Before<MyOrderedEventWriterBarrier<T>>, } ``` Want to get early feedback before spending several more hours on it. WDYT?
Strongly against this proposal. Re #8595
This separation is intentional. Systems are intended to be flexibly re-used in multiple contexts.
This proposed API encodes user ordering logic in the type system, which should not be mistaken for an API where the type system actually helps you order systems correctly. You can just as easily put By contrast, #8595 uses the type system to make meaningful configuration changes based on the actual data the systems are accessing, and not an empty marker value.
#8595 is based on world access, not component access, and event sending is literally the first example the author mentioned. I agree that ordering systems in Bevy could be meaningfully improved. The place to improve it is in the graph that does the ordering, not in the function signatures of the systems themselves. |
This proposal seems like it would need to extend the It seems like your biggest goal is not having to repeat yourself, and for that, I just don't see a practical advantage of doing it like this over writing a plugin with a method to handle any schedule configuration. pub struct ExamplePlugin;
impl ExamplePlugin {
// you only have to get this function right once
pub fn schedule_my_systems(schedule: &mut Schedule) {
schedule.add_systems(
(
foo.in_set(X),
bar.before(Y),
baz.after(Z),
)
.chain()
.run_if(something_happens)
);
/* the rest of the code */
}
}
impl Plugin for ExamplePlugin {
fn build(&self, app: &mut App) {
app.edit_schedule(SomeSchedule, |schedule| {
self.schedule_my_systems(schedule);
});
/* the rest of the code */
}
} Since scheduling happens at runtime and what you propose expresses the same I will also admit that, aesthetically, I'm quite biased against "hard-coding" schedule details in each system's |
It seems like 4 against 1, and I have zero credibility in this project, so feel free to ignore and close as won't fix or whatever. Regardless of that, I feel obligated to answer the comments above. I suspect my proposal is misunderstood given commenters:
I'm fine with being able to configure them additionally to annotations on functions itself. This RFC proposes safe shortcut for simplest cases, which are majority.
Yes, it does. First, it it much easier to not lose the type annotations rather than
Second, as with any
Sorry for repeating it again:
As I said before, that setup does not prevent using it incorrectly, e.g.
By contrast,
At least something we agree upon.
Function signatures describe the graph node inputs and outputs. So using function signature to describe dependencies in one way or another might be reasonable. (This can be done for queries too by the way. Just add something like
For example, I can copy-paste system, or move it to another file, and I'm certain it is going to work properly. The function is the unit here. Being a unit, code block makes it safer for changes. Additionally (not sure why people ignore that argument) higher level safe utilities can be built on top of before/after. Like
Which is not an advantage from my point of view. Because "system configuration" is split in two places. And during refactoring, two places needs to be changed.
Initially I wanted to propose proc macro like
but this only solves the smaller problem and does not allow building higher level utilities on top of it (like event queues, |
This proposal is still more powerful than #8595, in that, for example, you could have several "stages" on a specific data, and order systems based on those stages. But it's true that #8595 occupies the same niche. I have some concerns:
While I'm skeptical about this approach, I have been much more skeptical about other APIs that are now part of the project. |
I think it should be possible to implement the API as a third party crate. @stepancheg I would be open to collaborate on a proof of concept. |
My fundamental objections are to blending system scheduling information into the system params. Any proposal that does this, regardless of other merits, will cause serious architectural issues with how Bevy handles scheduling. To lay it out explicitly:
Generating system sets based on system-param-scheduling would mitigate 3, 7 and perhaps 8, but in exchange would lead to an exposed API that could be misused in the same way you're concerned about for the API in #8595. It seems impossible for a system param based approach to cover all of your ordering needs, and so issue 1 cannot be resolved. The composability is neat! This is something that can be done using system sets as well, but the type-level composition is genuinely appealing. I'm sorry for not commenting on it earlier: engaging with other elements of the design when severe architectural concerns are present felt disrespectful and misleading. |
From what I understand, the driving concerns here are:
Taken together, this makes refactoring and working with complex schedules painful. Is that correct? From my perspective:
|
I think we all agree there are problems with scheduling in bevy as it currently exists, especially for large projects. (I know of some larger projects that have given up and more or less just use one big linear There's been a lot of conjecture in this thread, but before making decisions for or against this rfc, or for or against other competing developments to scheduling in bevy, I think we need answers to the following questions — preferably weighing the perspective of some of the few large projects that currently exist
There are lots of tradeoffs to be made (many pointed out in this thread), and good decisions are difficult to make without knowing the value of what is being traded |
I see the merit of embedding scheduling info in the It's a clever idea, but style-wise, it's a tough sell in my opinion. It's not so bad when it's just one or two markers, but if there are more constraints, it'll look bad unless they're combined into a new, custom param. (I'm not sure if such nested params are immune to the same kinds of issues that nested bundles and nested states have.) It's kinda interesting that you could take that to an extreme, advise writing a unique param per My other issue is this would be another huge breaking change to the scheduling API. The suggested API couldn't just coexist with the existing one (not that anyone was suggesting we mix and match them). UX-wise, we shouldn't have two distinct APIs controlling the same thing. This one is being pitched as a "just better" way to handle Such a drastic change would result in another large effort to refactor everything like 0.10 (but with way less payoff). |
Something I'm curious to know is what these params would even write to (Also, if this is going to turn into a true RFC, I think the discussion should move to a GitHub discussion or to https://github.com/bevyengine/rfcs.) |
A clear example of why this wouldn't work, at least on its own, is what Ordering Params would |
If I understand correctly, I think there would be some kind of composed type which would express each of the different places to instantiate the system. @stepancheg how would the API order multiple instances of the same system? |
Also I want to apologize for being judgmental too quickly; I’m not necessarily sold but I’d be happy to see a full RFC explaining the concept in fuller detail. |
I don't understand how. This implementation requires adding members to Alternatively
This would be duplication of large part of |
Hard to answer without an example of problem to solve. I don't even know why would we need multiple instances of the same system. Universal answer might be:
Generally I'm not 100% sure that ordering with types can replace ordering with method calls completely. I know it would solve all my problems, but my setup is perhaps not complex enough. |
OK, I gave an idea how it can be done, and perhaps bevy maintainers might be OK with it. We implement dynamic provide-like API in bevy. So bevy would require these changes: trait SystemParam {
// Request user metadata from param. Note this is dynamically typed like Rust provide API
fn provide_user_meta(/* no self */ request: &mut Request) {}
}
trait System {
// Request user metadata from all the parameters
fn provide_param_user_meta(&self, request: &mut Request) { ... }
}
impl dyn System {
// Easier to use wrapper for `provide_param_user_meta`
fn user_meta<T>(&self) -> Vec<T> { ... }
} Then a user (or third-party library) could:
instead of
this function should be used:
|
Draft RFC for parameter metadata #10678 which can be used instead of scheduling instructions built into systems. |
Why
Explicit ordering of systems is hard: during refactoring you forgot one edge, and your program befomes non-deterministic.
How
This draft PR proposes implementing ordering based on function signature params (
SystemParam
).Basically, the idea is this. We introduce two new system params:
where
T
must be aSystemSet + Default
.And it can be used like this:
Now if
first
andsecond
are added to the same schedule, they will be ordered like iffirst.before(after)
was done explicitly.Working example for this is in this commit as
test_before_after_from_param
.Composable
SystemParam
are composable, so it is possible to build higher level constructs on top of it. For example, we may want to schedule all event writers before all event writers:Feedback?
Want to get early feedback before spending several more hours on it.
WDYT?