From 683a70d8e7f4c59ecf03e23779fa7a54cfae6cbe Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Mon, 6 Apr 2020 01:57:00 -0700 Subject: [PATCH] add SchedulePlans --- bevy_app/src/app.rs | 2 +- bevy_app/src/app_builder.rs | 186 +++++++++--------- .../src/{system_stage.rs => default_stage.rs} | 3 +- bevy_app/src/lib.rs | 3 +- bevy_app/src/schedule_plan.rs | 163 +++++++++++++++ bevy_core/src/lib.rs | 6 +- bevy_render/src/lib.rs | 5 +- bevy_wgpu/src/lib.rs | 6 +- .../{setup_system.rs => startup_system.rs} | 4 +- 9 files changed, 268 insertions(+), 110 deletions(-) rename bevy_app/src/{system_stage.rs => default_stage.rs} (73%) create mode 100644 bevy_app/src/schedule_plan.rs rename examples/{setup_system.rs => startup_system.rs} (93%) diff --git a/bevy_app/src/app.rs b/bevy_app/src/app.rs index 694728d265..6e686f6671 100644 --- a/bevy_app/src/app.rs +++ b/bevy_app/src/app.rs @@ -26,7 +26,7 @@ impl Default for App { impl App { pub fn build() -> AppBuilder { - AppBuilder::new() + AppBuilder::default() } pub fn update(&mut self) { diff --git a/bevy_app/src/app_builder.rs b/bevy_app/src/app_builder.rs index bca28dc47c..49c2f072bd 100644 --- a/bevy_app/src/app_builder.rs +++ b/bevy_app/src/app_builder.rs @@ -1,33 +1,40 @@ use crate::{ + default_stage, plugin::{load_plugin, AppPlugin}, - system_stage, App, Events, + schedule_plan::SchedulePlan, + App, Events, }; -use legion::prelude::{Resources, Runnable, Schedulable, Schedule, Universe, World}; -use std::collections::HashMap; +use legion::prelude::{Resources, Runnable, Schedulable, Universe, World}; static APP_MISSING_MESSAGE: &str = "This AppBuilder no longer has an App. Check to see if you already called run(). A call to app_builder.run() consumes the AppBuilder's App."; pub struct AppBuilder { app: Option, - pub setup_systems: Vec>, - // TODO: these separate lists will produce incorrect ordering - pub system_stages: HashMap>>, - pub runnable_stages: HashMap>>, - pub thread_local_stages: HashMap>>, - pub stage_order: Vec, + schedule_plan: SchedulePlan, + startup_schedule_plan: SchedulePlan, +} + +impl Default for AppBuilder { + fn default() -> Self { + let mut app_builder = AppBuilder { + app: Some(App::default()), + schedule_plan: SchedulePlan::default(), + startup_schedule_plan: SchedulePlan::default(), + }; + + app_builder.add_default_stages(); + app_builder + } } impl AppBuilder { - pub fn new() -> Self { + pub fn empty() -> AppBuilder { AppBuilder { app: Some(App::default()), - setup_systems: Vec::new(), - system_stages: HashMap::new(), - runnable_stages: HashMap::new(), - thread_local_stages: HashMap::new(), - stage_order: Vec::new(), - } + schedule_plan: SchedulePlan::default(), + startup_schedule_plan: SchedulePlan::default(), + } } pub fn app(&self) -> &App { @@ -62,53 +69,21 @@ impl AppBuilder { &mut self.app_mut().resources } + pub fn build_and_run_startup_schedule(&mut self) -> &mut Self { + let mut startup_schedule = self.startup_schedule_plan.build(); + let app = self.app_mut(); + startup_schedule.execute(&mut app.world, &mut app.resources); + self + } + pub fn build_schedule(&mut self) -> &mut Self { - let mut setup_schedule_builder = Schedule::builder(); - for setup_system in self.setup_systems.drain(..) { - setup_schedule_builder = setup_schedule_builder.add_system(setup_system); - } - - let mut setup_schedule = setup_schedule_builder.build(); - let app = self.app_mut(); - setup_schedule.execute(&mut app.world, &mut app.resources); - - let mut schedule_builder = Schedule::builder(); - for stage_name in self.stage_order.iter() { - if let Some((_name, stage_systems)) = self.system_stages.remove_entry(stage_name) { - for system in stage_systems { - schedule_builder = schedule_builder.add_system(system); - } - - schedule_builder = schedule_builder.flush(); - } - - if let Some((_name, stage_runnables)) = self.runnable_stages.remove_entry(stage_name) { - for system in stage_runnables { - schedule_builder = schedule_builder.add_thread_local(system); - } - - schedule_builder = schedule_builder.flush(); - } - - if let Some((_name, stage_thread_locals)) = - self.thread_local_stages.remove_entry(stage_name) - { - for system in stage_thread_locals { - schedule_builder = schedule_builder.add_thread_local_fn(system); - } - - schedule_builder = schedule_builder.flush(); - } - } - - let app = self.app_mut(); - app.schedule = Some(schedule_builder.build()); - + self.app_mut().schedule = Some(self.schedule_plan.build()); self } pub fn run(&mut self) { self.build_schedule(); + self.build_and_run_startup_schedule(); self.app.take().unwrap().run(); } @@ -123,15 +98,54 @@ impl AppBuilder { self } - pub fn add_system(&mut self, system: Box) -> &mut Self { - self.add_system_to_stage(system_stage::UPDATE, system) + pub fn add_stage(&mut self, stage_name: &str) -> &mut Self { + self.schedule_plan.add_stage(stage_name); + self } - pub fn add_setup_system(&mut self, system: Box) -> &mut Self { - self.setup_systems.push(system); + pub fn add_stage_after(&mut self, target: &str, stage_name: &str) -> &mut Self { + self.schedule_plan.add_stage_after(target, stage_name); self } + pub fn add_stage_before(&mut self, target: &str, stage_name: &str) -> &mut Self { + self.schedule_plan.add_stage_before(target, stage_name); + self + } + + pub fn add_startup_stage(&mut self, stage_name: &str) -> &mut Self { + self.startup_schedule_plan.add_stage(stage_name); + self + } + + pub fn add_system(&mut self, system: Box) -> &mut Self { + self.add_system_to_stage(default_stage::UPDATE, system) + } + + pub fn add_startup_system_to_stage( + &mut self, + stage_name: &str, + system: Box, + ) -> &mut Self { + self.startup_schedule_plan + .add_system_to_stage(stage_name, system); + self + } + + pub fn add_startup_system(&mut self, system: Box) -> &mut Self { + self.startup_schedule_plan + .add_system_to_stage(default_stage::STARTUP, system); + self + } + + pub fn add_default_stages(&mut self) -> &mut Self { + self.add_startup_stage(default_stage::STARTUP) + .add_stage(default_stage::FIRST) + .add_stage(default_stage::EVENT_UPDATE) + .add_stage(default_stage::UPDATE) + .add_stage(default_stage::LAST) + } + pub fn build_system(&mut self, build: F) -> &mut Self where F: Fn(&mut Resources) -> Box, @@ -145,49 +159,27 @@ impl AppBuilder { stage_name: &str, system: Box, ) -> &mut Self { - if let None = self.system_stages.get(stage_name) { - self.system_stages - .insert(stage_name.to_string(), Vec::new()); - self.stage_order.push(stage_name.to_string()); - } - - let stages = self.system_stages.get_mut(stage_name).unwrap(); - stages.push(system); - - self - } - - pub fn add_runnable_to_stage( - &mut self, - stage_name: &str, - system: Box, - ) -> &mut Self { - if let None = self.runnable_stages.get(stage_name) { - self.runnable_stages - .insert(stage_name.to_string(), Vec::new()); - self.stage_order.push(stage_name.to_string()); - } - - let stages = self.runnable_stages.get_mut(stage_name).unwrap(); - stages.push(system); - + self.schedule_plan.add_system_to_stage(stage_name, system); self } pub fn add_thread_local_to_stage( + &mut self, + stage_name: &str, + system: Box, + ) -> &mut Self { + self.schedule_plan + .add_thread_local_to_stage(stage_name, system); + self + } + + pub fn add_thread_local_fn_to_stage( &mut self, stage_name: &str, f: impl FnMut(&mut World, &mut Resources) + 'static, ) -> &mut Self { - if let None = self.thread_local_stages.get(stage_name) { - self.thread_local_stages - .insert(stage_name.to_string(), Vec::new()); - // TODO: this is so broken - self.stage_order.push(stage_name.to_string()); - } - - let thread_local_stages = self.thread_local_stages.get_mut(stage_name).unwrap(); - thread_local_stages.push(Box::new(f)); + self.schedule_plan + .add_thread_local_fn_to_stage(stage_name, f); self } @@ -197,7 +189,7 @@ impl AppBuilder { { self.add_resource(Events::::default()) .add_system_to_stage( - system_stage::EVENT_UPDATE, + default_stage::EVENT_UPDATE, Events::::build_update_system(), ) } diff --git a/bevy_app/src/system_stage.rs b/bevy_app/src/default_stage.rs similarity index 73% rename from bevy_app/src/system_stage.rs rename to bevy_app/src/default_stage.rs index 3ed3d7ff5b..2c78c93396 100644 --- a/bevy_app/src/system_stage.rs +++ b/bevy_app/src/default_stage.rs @@ -1,7 +1,6 @@ -// TODO: move me +pub const STARTUP: &str = "startup"; pub const FIRST: &str = "first"; pub const EVENT_UPDATE: &str = "event_update"; pub const UPDATE: &str = "update"; -pub const RENDER: &str = "render"; pub const LAST: &str = "last"; diff --git a/bevy_app/src/lib.rs b/bevy_app/src/lib.rs index 2c56c8dcf3..1e743c82e4 100644 --- a/bevy_app/src/lib.rs +++ b/bevy_app/src/lib.rs @@ -2,8 +2,9 @@ mod app; mod app_builder; mod event; mod plugin; +pub mod schedule_plan; pub mod schedule_runner; -pub mod system_stage; +pub mod default_stage; pub use app::*; pub use app_builder::*; diff --git a/bevy_app/src/schedule_plan.rs b/bevy_app/src/schedule_plan.rs new file mode 100644 index 0000000000..076a51f789 --- /dev/null +++ b/bevy_app/src/schedule_plan.rs @@ -0,0 +1,163 @@ +use legion::prelude::*; +use std::{cmp::Ordering, collections::HashMap}; + +enum System { + Schedulable(Box), + ThreadLocal(Box), + ThreadLocalFn(Box), +} + +#[derive(Default)] +pub struct SchedulePlan { + stages: HashMap>, + stage_order: Vec, +} + +impl SchedulePlan { + pub fn build(&mut self) -> Schedule { + let mut schedule_builder = Schedule::builder(); + + for stage in self.stage_order.drain(..) { + if let Some((_, mut systems)) = self.stages.remove_entry(&stage) { + let system_count = systems.len(); + for system in systems.drain(..) { + match system { + System::Schedulable(schedulable) => { + schedule_builder = schedule_builder.add_system(schedulable); + } + System::ThreadLocal(runnable) => { + schedule_builder = schedule_builder.add_thread_local(runnable); + } + System::ThreadLocalFn(thread_local) => { + schedule_builder = schedule_builder.add_thread_local_fn(thread_local); + } + } + } + + if system_count > 0 { + schedule_builder = schedule_builder.flush(); + } + } + } + + schedule_builder.build() + } + + pub fn add_stage(&mut self, stage: &str) { + if let Some(_) = self.stages.get(stage) { + panic!("Stage already exists: {}", stage); + } else { + self.stages.insert(stage.to_string(), Vec::new()); + self.stage_order.push(stage.to_string()); + } + } + + pub fn add_stage_after(&mut self, target: &str, stage: &str) { + if let Some(_) = self.stages.get(stage) { + panic!("Stage already exists: {}", stage); + } + + let target_index = self + .stage_order + .iter() + .enumerate() + .find(|(_i, stage)| stage.as_str() == target) + .map(|(i, _)| i) + .unwrap_or_else(|| panic!("Target stage does not exist: {}", target)); + + self.stages.insert(stage.to_string(), Vec::new()); + self.stage_order.insert(target_index + 1, stage.to_string()); + } + + pub fn add_stage_before(&mut self, target: &str, stage: &str) { + if let Some(_) = self.stages.get(stage) { + panic!("Stage already exists: {}", stage); + } + + let target_index = self + .stage_order + .iter() + .enumerate() + .find(|(_i, stage)| stage.as_str() == target) + .map(|(i, _)| i) + .unwrap_or_else(|| panic!("Target stage does not exist: {}", target)); + + self.stages.insert(stage.to_string(), Vec::new()); + self.stage_order.insert(target_index, stage.to_string()); + } + + pub fn add_system_to_stage( + &mut self, + stage_name: &str, + system: Box, + ) -> &mut Self { + let system = System::Schedulable(system); + let systems = self + .stages + .get_mut(stage_name) + .unwrap_or_else(|| panic!("Stage does not exist: {}", stage_name)); + systems.push(system); + + self + } + + pub fn add_thread_local_to_stage( + &mut self, + stage_name: &str, + runnable: Box, + ) -> &mut Self { + let system = System::ThreadLocal(runnable); + let systems = self + .stages + .get_mut(stage_name) + .unwrap_or_else(|| panic!("Stage does not exist: {}", stage_name)); + systems.push(system); + + self + } + + pub fn add_thread_local_fn_to_stage( + &mut self, + stage_name: &str, + f: impl FnMut(&mut World, &mut Resources) + 'static, + ) -> &mut Self { + let system = System::ThreadLocalFn(Box::new(f)); + let systems = self + .stages + .get_mut(stage_name) + .unwrap_or_else(|| panic!("Stage does not exist: {}", stage_name)); + systems.push(system); + + self + } +} + +// working around the famous "rust float ordering" problem +#[derive(PartialOrd)] +struct FloatOrd(f32); + +impl Ord for FloatOrd { + fn cmp(&self, other: &Self) -> Ordering { + self.0.partial_cmp(&other.0).unwrap_or_else(|| { + if self.0.is_nan() && !other.0.is_nan() { + Ordering::Less + } else if !self.0.is_nan() && other.0.is_nan() { + Ordering::Greater + } else { + Ordering::Equal + } + }) + } +} + +impl PartialEq for FloatOrd { + fn eq(&self, other: &Self) -> bool { + if self.0.is_nan() && other.0.is_nan() { + true + } else { + self.0 == other.0 + } + } +} + +impl Eq for FloatOrd {} diff --git a/bevy_core/src/lib.rs b/bevy_core/src/lib.rs index 9d6eb21922..cd43e6d97e 100644 --- a/bevy_core/src/lib.rs +++ b/bevy_core/src/lib.rs @@ -1,7 +1,7 @@ pub mod bytes; pub mod time; -use bevy_app::{system_stage, AppBuilder, AppPlugin}; +use bevy_app::{default_stage, AppBuilder, AppPlugin}; use bevy_transform::transform_system_bundle; use time::{start_timer_system, stop_timer_system}; @@ -15,7 +15,7 @@ impl AppPlugin for CorePlugin { } app.add_resource(time::Time::new()) - .add_system_to_stage(system_stage::FIRST, start_timer_system()) - .add_system_to_stage(system_stage::LAST, stop_timer_system()); + .add_system_to_stage(default_stage::FIRST, start_timer_system()) + .add_system_to_stage(default_stage::LAST, stop_timer_system()); } } diff --git a/bevy_render/src/lib.rs b/bevy_render/src/lib.rs index 8f6ae7756a..2ac803287a 100644 --- a/bevy_render/src/lib.rs +++ b/bevy_render/src/lib.rs @@ -48,11 +48,13 @@ use self::{ }, }; -use bevy_app::{AppBuilder, AppPlugin, GetEventReader}; +use bevy_app::{AppBuilder, AppPlugin, GetEventReader, default_stage}; use bevy_transform::prelude::LocalToWorld; use bevy_asset::AssetStorage; use bevy_window::WindowResized; +pub static RENDER_STAGE: &str = "render"; + #[derive(Default)] pub struct RenderPlugin; @@ -94,6 +96,7 @@ impl AppPlugin for RenderPlugin { asset_batchers.batch_types2::(); app .add_system(build_entity_render_resource_assignments_system()) + .add_stage_after(default_stage::UPDATE, RENDER_STAGE) .add_resource(RenderGraph::default()) .add_resource(AssetStorage::::new()) .add_resource(AssetStorage::::new()) diff --git a/bevy_wgpu/src/lib.rs b/bevy_wgpu/src/lib.rs index 2dca8bc578..c0c3647d52 100644 --- a/bevy_wgpu/src/lib.rs +++ b/bevy_wgpu/src/lib.rs @@ -7,8 +7,8 @@ pub use wgpu_render_pass::*; pub use wgpu_renderer::*; pub use wgpu_resources::*; -use bevy_app::{AppPlugin, system_stage, AppBuilder, Events}; -use bevy_render::renderer::Renderer; +use bevy_app::{AppPlugin, AppBuilder, Events}; +use bevy_render::{renderer::Renderer, RENDER_STAGE}; use bevy_window::{WindowCreated, WindowResized}; use legion::prelude::*; @@ -18,7 +18,7 @@ pub struct WgpuRendererPlugin; impl AppPlugin for WgpuRendererPlugin { fn build(&self, app: &mut AppBuilder) { let render_system = wgpu_render_system(app.resources()); - app.add_thread_local_to_stage(system_stage::RENDER, render_system); + app.add_thread_local_fn_to_stage(RENDER_STAGE, render_system); } } diff --git a/examples/setup_system.rs b/examples/startup_system.rs similarity index 93% rename from examples/setup_system.rs rename to examples/startup_system.rs index b0fd9bdacd..9e94ff236a 100644 --- a/examples/setup_system.rs +++ b/examples/startup_system.rs @@ -3,11 +3,11 @@ use bevy::prelude::*; fn main() { App::build() .add_default_plugins() - .add_setup_system(setup_system()) + .add_startup_system(startup_system()) .run(); } -pub fn setup_system() -> Box { +pub fn startup_system() -> Box { SystemBuilder::new("setup") .write_resource::>() .write_resource::>()