Replies: 10 comments 35 replies
-
Links to relevant conversations, for context. Any ideas are in no way final. In no particular order; feel free to add your own. |
Beta Was this translation helpful? Give feedback.
-
Problem-scoping questionsThese questions are intended to scope and define the questions we want to answer and think carefully about in these designs. If you have more (or less!) questions that think should be tackled as part of this Gordian knot, please chime in.
|
Beta Was this translation helpful? Give feedback.
-
Current answers to problem-scoping questionsThese replies are mostly written working out of memory; if there's an important detail that I've missed / gotten confused about, please let me know and I can correct this post. Many thanks to @TheRawMeatball for corrections and feedback. How are systems collected and organized?Systems are converted into system descriptors, then stored in stages, which are in turn owned by a schedule. Systems can be grouped into system sets, which can be described en-masse but do not persist past initialization. Functions which modify the world during a hard sync point are conceptualized as exclusive systems (or, when queued from ordinary systems, commands). How do we add systems from other sources to our app?Plugins take the main app, and mutate it. Plugins can add systems and new hard sync points. Plugins cannot directly configure the order of existing systems in any way, and cannot set timing relative to systems from other plugins (except via stages) unless those labels are deliberately made public. Some exceptions to this ordering exist, due to the transitive nature of ordering. Suppose A and B are publicly labeled, and have no order specified between them. By inserting C which runs after A but before B, a new order is induced. How do we control the relative order of systems?There are two approaches:
How do we define hard sync points?Hard sync points are defined globally as a side-effect of inserting a stage. Plugins can insert their own hard-sync points into the app by adding their own stages. Looping run criteria cause hard sync points to repeat. The full stage execution model in Bevy 0.5 is:
How can we enable and disable systems?Systems are enabled and disabled via run criteria, which are attached to the system as a descriptor. Run criteria are themselves systems that return States are widely used for cohesively manipulating which systems should run en-masse. States cannot span hard sync points and are limited to a single stage. How can we opt-in to complex control flows?There are several tools for this:
|
Beta Was this translation helpful? Give feedback.
-
Design goals
System scheduling
Control flow
Plugins
|
Beta Was this translation helpful? Give feedback.
-
System control flow patternsRendering-coupled loopDescription: The game loops in real time. Rendering is part of the game loop, and so one frame is equivalent to one tick. Examples: Factory builders (as we can't make computations cheaper, we slow down the frame rate instead), simple traditional arcade games Current tools: Single schedule Potential changes: None Strict turn-basedDescription: The game loop only advances when new input is provided. Examples: Play-by-mail chess, choose-your-own-adventure terminal games Current tools: Custom runner Potential changes: None Hybrid turn-basedDescription: Much of the game loop only advances when new input is provided, but animations, sound and so on advance in real-time. Examples: Turn-based RPGs, digital board games Current tools: Run criteria and game-state storage in resources (or Potential changes: Improve run criteria combination and interactions with states. Fixed timestep, decoupled renderingDescription: Much of the game loop (typically physics) only advances when a certain fixed amount of time has passed. Note that this control flow pattern and the hybrid turn-based pattern above can be unified into a decoupled game loop control flow, where the criteria differs. Examples: FPS, tower defense Current tools: Fixed timestep run-criteria, pipelined rendering Potential changes: Improve run criteria combination and interactions with states. Stack processingDescription: Some parts of the game loop must be processed repeatedly until a stack is empty. Examples: Magic the Gathering, complex turn-based game effects Current tools: Hierarchical schedules with run criteria, exclusive systems that manually run a schedule stored in a resource Potential changes: Clarify and promote nested schedule design. Store multiple schedules in the Parallel simulationDescription: Many copies of the same app should be run completely independently without user input but with slightly varied hyperparameters which control the simulation details Examples: Scientific simulation Current tools: Custom runner Potential changes: Allow multiple worlds to be stored in the Many-worlds branching AIDescription: Game AI repeatedly simulates a game state and then evaluates it before proceeding within a world Examples: Chess engine, Sudoku solver Current tools: Exclusive systems that manually run a schedule stored in a resource plus sub-worlds stored in a resource Potential changes: Allow multiple worlds to be stored in the Long-running tasksDescription: Some tasks are too long-lived or computationally expensive to be completed in a given framing Examples: Waiting for input in a hybrid turn-based game, AI pathfinding Current tools: Async tasks and manual polling each time a system is run Potential changes: None at the moment Streaming dataflowDescription: The app listens for events and only runs when there is new data to process. Examples: Extract-transform-load data processing Current tools: Custom runners which batch data and then execute a schedule when enough time has elapsed or a batch is full. Potential changes: Multiple worlds would make separating unprocessed and processed data more ergonomic. |
Beta Was this translation helpful? Give feedback.
-
Here's my best attempt at diagramming out all of the dependencies of these problems. I've attached the diagrams.io diagram for editing as may be needed. |
Beta Was this translation helpful? Give feedback.
-
I tried to group those dependencies together a bit, with my own assumptions about what's likely/important. |
Beta Was this translation helpful? Give feedback.
-
wrote up my thoughts on a potential implementation for stageless bevyengine/rfcs#34 |
Beta Was this translation helpful? Give feedback.
-
Useful Discord discussion yesterday where Sander talked about how flecs handles system ordering and sync points. I think I'm on board with "stageless" now. Feels clearer to me now. Summarizing my takeaways. 1. Remove stage and just expose its individual parts
To get rid of
I think we can (and should) order exclusive systems following the topological order instead of special insertion points (they're unnecessary). (Edit: Example if we stored all systems inside // systems sorted in a topological order (assume sync points have also been inserted as exclusive systems)
let remaining = &mut self.systems[..];
loop {
// Find the next exclusive system.
if let Some(index) = remaining.iter().position(|&sys| sys.is_exclusive()) {
let (concurrent, remaining) = remaining.split_at_mut(index);
let (exclusive, remaining) = remaining.split_first_mut().unwrap();
self.executor.run_systems(concurrent, world);
exclusive.system_mut().run(world);
} else {
// No exclusive systems remaining.
self.executor.run_systems(remaining, world);
break;
}
} 2. Make run criteria return boolWe can have this if loops and nested schedules are black-boxed inside exclusive systems, then each schedule remains a DAG. fn fixed_update(world: &mut World) {
world.resource_scope(|world, mut fixed_timestep: Mut<FixedTimestep>| {
while fixed_timestep.sub_step().is_some() {
simulation_schedule.run(&mut world);
}
}
} Would also be cool to have a way to mark systems as exclusive (nothing else running) without the 3. Define states betterStates don't really have a solid definition, but we need to nail one down to proceed. How exactly do we want the app-level state machine to interact with a schedule? From my POV, there are two mutually exclusive options:
Def. 1 reflects the current behavior (an app's I don't like states being literal stages/schedules. It's counter-intuitive that you have to clear the stack and return to the default state to actually end a frame (although that is something we could change). I prefer Def. 2 where states are passive and persistent but transitions can still trigger on-demand behavior. It gives an individual schedule more flexibility while being less open-ended. Either way, I think the on-demand execution means state transitions are only provably safe during sync points, so "flush pending commands" would really be "flush pending commands and then flush pending transitions." Related Future Work
|
Beta Was this translation helpful? Give feedback.
-
More BenchmarksI think one of the first things we need to do before rewriting the scheduler is to add some more benchmarks. There are already some for parallel systems, but we should probably add some more.
Maybe add:
Please suggest any more that you can think of. |
Beta Was this translation helpful? Give feedback.
-
reserved
Beta Was this translation helpful? Give feedback.
All reactions