-
-
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
Extensible builder syntax for system insertion #1978
Comments
Yep I really like the |
I'm not sure what you mean by "labels of mass description", or how this is going to open the door for anything new anywhere, especially for something like tweaking how systems population is done in a given plugin. We can already modify it, but we have to open up the plugin and do the population ourselves, and this will not improve that story. |
That's a compelling argument; I agree with this even if it is slightly less pretty.
This is the idea about how we could tie other information about the system, like which state or stage it runs in, to the label it's associated with. Like usual, you're probably right that this doesn't make anything any easier though. |
Is it also worth considering dropping the requirement to call App::build()
.add_system(movement_system) // no need to call .system()
.add_system(other_system) IMO it's much cleaner since a lot of the time the call site looks like this: .add_system(movement_system.system()) which is just one too many |
We have consensus on this, but doing so is reasonably hard. Collaborate with @DJMcNab on doing so if you want to tackle it; but split it up into two PRs :) |
@alice-i-cecile I'm also curious as to what we would want the API for a single system to look like. add_system(
my_system
.system()
.label("Hello")
.startup()
.to_stage(my_stage) // note `to_stage` overrides `startup`
) or something more like add_system(
SystemConfig::new(my_system.system())
.label("Hello")
.startup()
.to_stage()
) The first one is more difficult to implement as it will require refactoring with the |
I have a strong preference for the former, and expect Cart will insist on it ;) |
Yup the former is the only one of the two I'm willing to roll with. The second one requires too much typing 😄 I have an alternative implementation proposal (which does have downsides compared to the current impl). Heres a rough sketch: trait System {
/* same functions as before */
fn config(&mut self) -> &mut SystemConfig;
}
trait LabelSystem {
fn label(self, label: impl SystemLabel) -> Self
}
impl<T: System> LabelSystem for T {
fn label(self, label: impl SystemLabel) -> Self {
self.config().add_label(label);
self
}
} Pros:
Cons:
|
That's a neat impl @cart! I think it's better on the whole.
This definitely seems like the biggest drawback. @Ratysz does this look like it'll be compatible with your stageless plans? |
There's probably a way around this, I'll have to do some experimentation :)
stageless plans? |
This is covered in rough, messy detail in #1375, which was written before we had RFCs. The basic idea is that a) stages should no longer own systems b) we should attempt to minimize the number of hard-sync points (where exclusive systems can run and commands can be processed) c) we should ensure that things like States work across hard-sync points. |
Hopefully, eventually. There are language-level obstacles which we haven't found a good way to work around yet; it's hard to tell which of these will come first: us figuring it out, or Rust fixing the relevant bugs and oddities. I would also recommend omitting
The first one. Ease of implementation is always second to ease of use.
I can't tell from the sketch how is that actually going to work, or how would that be less messy than (admittedly) meh coercion traits. But, bundling systems' descriptors with the system itself can be done - we're holding onto most of that data anyway already. I would also like to reiterate that all of this (and more) would be much cleaner if we stopped trying to plumb exclusive systems through the same API as actual systems. I'll be bringing this up in the stageless RFC.
Stageless will require some rework of what we have now, and it will require some rework of what we could have with this implementation. The "mixing of the levels" is about separation of concerns wrt descriptor data storage and is tangential. |
I'm constantly torn on this one. It feels especially redundant when |
Yeah I think its hard to tell if its a win without actually doing it. Fortunately I think thats probably a 20-30 minute project for people familiar with that part of the code. That list is pretty small, so the onus is probably on me to prove its worth doing 😄 |
I think namespacing systems is a better solution. You know that the module contains only systems. Any helper function should go outside the module or at least be inserted into a submodule. EDIT: i actually would love a |
For related discussion on this point, see #1843 :) |
The drawbacks are all those of procedural macros. Compilation times, code clarity, ease of troubleshooting, cognitive load. I don't see any immediate benefits for our case.
I don't think that's related. I think this is about Legion-like system declaration macro. |
@Ratysz would the recommendation be then that we have explicit |
Not sure what you mean by "recommendation"; I don't know what exact form of this I'll settle on for the RFC. Currently leaning towards building the schedule from distinct and strictly separate parallel and sequential blocks of systems, with exclusive systems only being able to be used in the latter. This is, essentially, same as stages (we just can't avoid some form of them if we want to support parallel->exclusive->parallel case) graph-wise, just flatter and with less seams API-wise. |
From #2510 (comment) (@Nilirad)
The API from the snippet would still require hardcoding: something needs to know that stages labelled with |
From #2510, @Nilirad comments:
add_system_set(
my_system_set
.in_stage(StartupStage::PreStartup)
) So, I mostly agree with this. Inserting into a specific stage should cause startup-like behavior if it's a startup stage: no need for another method. However I'd like to keep Of course, with the stageless schedule rework we may need a "run this system only once at the beginning" signifier, or it may just get rolled into the States abstraction. |
I think a nested insertion API would be a good solution: the user just specifies the stage label and then the schedule does the job of finding the stage. It may look as simple as changing |
As discussed on discord, I'm gonna look at this in the coming days, if you have any suggestions, feel free to ping me :) (I'll reference this in a pr when I get a chance so check below) |
I would suggest chaining on the common denominator that one usually groups things around, which is stages! I would feel that having This also makes it easier to add systems to stages, as |
That would be nice with the current stage based API, but since there are discussions of making the scheduler stageless I'm not sure it's the right move to tie everything to stages |
This isn't as much "tying everything to stages" as it is making the app/schedule builder stateful, which is a bad idea more often than it's not. |
Yeah, I'm also not a fan of the stateful part, but I was mostly commenting about the stages being the common denominator part. |
Am I wrong in thinking that a lot of the comments are out of date as we now have a way to do:
rather than
@cart's implementation idea:
is no longer a thing since we're no longer using |
Cart's idea replaces descriptors entirely, the data and API that belonged to the descritor now belong to the system. I'm not yet fully certain reworking system description like this is the way we should take; it seems like it would be pretty neat, but last time I examined it I fould some counterpoints that I cannot recall right now. |
Following up on this, it does seem like the new APIs exposed for stageless mirror some of the goals here. Is this issue still relevant? |
Probably not anymore, since bevyengine/rfcs#31 has been closed. |
What problem does this solve or what need does it fill?
As we create more and more interesting ways to initialize systems, the number of AppBuilder methods we require explodes in an exponential fashion.
add_system
becomesadd_startup_system
becomesadd_startup_system_to_stage
becomeadd_startup_system_set_to_stage
becomes...What solution would you like?
Use two base methods
add_system
andadd_system_set
. Then, use the builder pattern (seeEntityCommands
for an example) to replicate the existing API using method chaining.For example,
add_startup_system_set_to_stage(my_stage, my_system_set)
would becomeadd_system_set(my_system_set).startup().to_stage(my_stage)
.What alternative(s) have you considered?
Continue expanding our API and method names. That's what IDEs are for right? ;)
Additional context
This would help enable the subworlds RFC and similar extensions.
This is a very nice building block for "labels of mass description", hinted at in #1375. It also opens the door to principled modification of systems added by plugins (e.g. to move them into the required States).
The text was updated successfully, but these errors were encountered: