Skip to content
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

Triggered systems #1273

Closed
alice-i-cecile opened this issue Jan 20, 2021 · 9 comments
Closed

Triggered systems #1273

alice-i-cecile opened this issue Jan 20, 2021 · 9 comments
Labels
A-ECS Entities, components, systems, and events C-Enhancement A new feature

Comments

@alice-i-cecile
Copy link
Member

alice-i-cecile commented Jan 20, 2021

What problem does this solve or what need does it fill?

Some behavior may require immediate handling at the next available opportunity, rather than waiting until the next frame comes around. This may be useful for handling input events, or chaining systems for things like error recovery.

What solution would you like?

When a certain behavior occurs (such as an event of a specific type being generated like for input), a run-once handling system is dynamically inserted into the pool of systems that are set to be run, possibly with increased priority.

It's unclear if the triggering behavior can or should be limited to Events in the Bevy sense.

What alternative(s) have you considered?

Systems can be run once per tick in the standard fashion and consume events. This is often too slow, especially when chained.

A copy of the appropriate system can be manually inserted immediately after every possible system that could trigger it, and then return early. This is error-prone and tedious.

Additional context

Implementing this would require changes to the scheduler, to allow for dynamic insertion of systems on a one-time basis mid-stage. As is, systems are not at added at runtime in the existing code base, and the scheduler constructs a complete list of systems at the beginning of the stage.

This pattern relates to but is distinct from event-dependent run criteria, discussed in #1272.

This pattern may be helpful for refreshing indexes, as discussed in #1205. This is particularly true if systems could be inserted dynamically immediately before a given system was scheduled to run.

This pattern would be useful for handling UI chains in a timely fashion, although the ergonomics might be painful.

@TheRawMeatball
Copy link
Member

Pinging @Ratysz as this would need executor changes

@Ratysz
Copy link
Contributor

Ratysz commented Jan 20, 2021

Naively inserting a system into the parallel executor mid-run would most likely worsen performance much more than ether suggested alternative. The executor relies on the list of systems being constant for the entirety of its runtime - inserting or removing a system means rebuilding all of its cached data. This cannot be worked around: the check that determines if a system can run or not requires the cached data to be current.

Alternative: pause the executor's logic, let currently running systems finish, run the "inserted" system, resume logic, apply the "inserted" system's command buffer when the rest of the parallel systems do so.

Better alternative: run the "inserted" system as if it were an exclusive system, at the end of stage (or before applying parallel systems' command buffers).

Even better alternative: use chain systems; the main post mentions "chaining", but it also claims it is slow, while it's anything but, so I doubt this is about the IntoChainSystem. Two chained systems are, for all intents and purposes (other than the API), are one single system - the executor doesn't care if there's twelve of them welded end to end, it just sees one big system with lots of stuff it needs access to. The chain runs immediately one after another, without any interruptions.

@alice-i-cecile
Copy link
Member Author

Thanks for the clarification; I hadn't examined the details of how IntoChainSystem worked. The OP was discussing chaining in a naive fashion using events.

So let me think about using IntoChainSystem to solve this problem. As I understand it, constructing these dynamically is impossible, so you'd want an API to automatically stitch these triggered systems onto every possible system that could emit a triggering event (or the like), with an early exit if there's no work to be done.

This handles the immediacy very nicely, without having to hack the scheduler. This is a particularly nice feature, because it means that it would work across any stage type, including those with custom executors.

This will increase the read-write footprint of the modified systems, but that's not too bad.

I'm not sure that a solution of this sort couldn't be used to solve the "deep UI" issue though. Two potential issues arise:

  1. Circular dependencies are needed. This doesn't complicate resource allocation, but it looks like we'd need to add a way to return early at the scheduling level, otherwise the update method will continue to run forever.
  2. Non-linear chains are needed, as UI / input events could easily trigger multiple. Rather than a simple linear chain of systems, I think a UI solution would depend on full DAGs. As far as I can tell, this would require a change to the IntoSystemChain API to accept multiple systems, then its own mini scheduler to ensure that systems are run in the right order. At which point, something like a nested stage that can be executed in parallel with other systems is probably just the right call.

(Disclaimer: I'm not at all convinced that this functionality is a great idea, or particularly useful for UI resolution, but I wanted to collect the discussion in a convenient place.)

@Ratysz
Copy link
Contributor

Ratysz commented Jan 21, 2021

As I understand it, constructing these dynamically is impossible, so you'd want an API to automatically stitch these triggered systems onto every possible system that could emit a triggering event (or the like), with an early exit if there's no work to be done.

I'm not sure we need anything special for this, the chaining API is as minimal as it can be. Doing things for users implicitly is still not a good idea.

Circular dependencies, in the sense "this system must run after that system", are simply impossible: any graph with a dependency cycle cannot be executed, because running any system from a cycle breaks the rule the dependency relation encodes. The intended way is to use run criteria that return ShouldRun::YesAndLoop or ShouldRun::NoAndLoop - this makes the executor re-evaluate those criteria at the end of stage and run those system sets again if needed, before handing control back to the schedule.

Non-linear chains are not what system chaining is for; this would vastly overcomplicate them and quite likely drag their performance down to that of event-using systems anyway (not trying to imply this is in any way slower than it should be).

@alice-i-cecile
Copy link
Member Author

I agree with your comment, and don't think this is a good idea.

@Moxinilian Moxinilian added A-ECS Entities, components, systems, and events C-Enhancement A new feature labels Jan 25, 2021
@alice-i-cecile
Copy link
Member Author

Closing this; we can reopen it if we have a good use case.

@alice-i-cecile
Copy link
Member Author

Reopening this due to interest in using this approach for UI.

@Ratysz
Copy link
Contributor

Ratysz commented Apr 27, 2021

Nothing has changed. We will not be inserting systems dynamically during a tick. The "triggerable" system will need to already exist in the schedule.

We could have syntactic sugar that just constructs systems like "poll events, run this on each one" that would probably save like a line or two, but I'm not sure it's worth its implementation complexity.

@alice-i-cecile
Copy link
Member Author

Closing in favor of #2192.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Enhancement A new feature
Projects
None yet
Development

No branches or pull requests

4 participants