From 7c121563db8003e9245d76cf23f768ea7f0847a2 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Mon, 30 Mar 2020 11:52:33 -0700 Subject: [PATCH] headless apps --- examples/headless.rs | 31 +++++ examples/setup_system.rs | 4 +- src/app/app.rs | 6 +- src/app/app_builder.rs | 34 +++-- src/app/mod.rs | 1 + src/app/schedule_runner.rs | 46 +++++++ src/core/window/winit/mod.rs | 123 +++++++++--------- .../renderers/wgpu_renderer/wgpu_renderer.rs | 9 +- 8 files changed, 173 insertions(+), 81 deletions(-) create mode 100644 examples/headless.rs create mode 100644 src/app/schedule_runner.rs diff --git a/examples/headless.rs b/examples/headless.rs new file mode 100644 index 0000000000..7fd24475f9 --- /dev/null +++ b/examples/headless.rs @@ -0,0 +1,31 @@ +use bevy::{ + app::schedule_runner::{RunMode, ScheduleRunner}, + prelude::*, +}; +use std::time::Duration; + +fn main() { + println!("This app runs once:"); + App::build() + .add_plugin(ScheduleRunner { + run_mode: RunMode::Once, + }) + .add_system(hello_world_system()) + .run(); + + println!("This app loops forever at 60 fps:"); + App::build() + .add_plugin(ScheduleRunner { + run_mode: RunMode::Loop { + wait: Some(Duration::from_secs_f64(1.0 / 60.0)), + }, + }) + .add_system(hello_world_system()) + .run(); +} + +pub fn hello_world_system() -> Box { + SystemBuilder::new("hello_world").build(move |_, _, _, _| { + println!("hello world"); + }) +} diff --git a/examples/setup_system.rs b/examples/setup_system.rs index 4acf3690f0..52a029f658 100644 --- a/examples/setup_system.rs +++ b/examples/setup_system.rs @@ -1,10 +1,10 @@ use bevy::prelude::*; fn main() { - App::build().add_defaults().add_setup_system(setup()).run(); + App::build().add_defaults().add_setup_system(setup_system()).run(); } -pub fn setup() -> Box { +pub fn setup_system() -> Box { SystemBuilder::new("setup") .write_resource::>() .write_resource::>() diff --git a/src/app/app.rs b/src/app/app.rs index 86737abda6..79be524c4a 100644 --- a/src/app/app.rs +++ b/src/app/app.rs @@ -5,7 +5,7 @@ pub struct App { pub universe: Universe, pub world: World, pub resources: Resources, - pub run: Option>, + pub runner: Option>, pub schedule: Schedule, } @@ -21,7 +21,7 @@ impl App { universe, world, schedule, - run, + runner: run, resources, } } @@ -42,7 +42,7 @@ impl App { } pub fn run(mut self) { - if let Some(run) = self.run.take() { + if let Some(run) = self.runner.take() { run(self) } } diff --git a/src/app/app_builder.rs b/src/app/app_builder.rs index 5f75999356..088acd1d06 100644 --- a/src/app/app_builder.rs +++ b/src/app/app_builder.rs @@ -3,12 +3,9 @@ use crate::{ plugin::{load_plugin, AppPlugin}, system_stage, App, }, - core::{winit::WinitPlugin, CorePlugin, Event}, + core::{CorePlugin, Event}, legion::prelude::{Resources, Runnable, Schedulable, Schedule, Universe, World}, - render::{ - renderer::{renderers::wgpu_renderer::WgpuRendererPlugin}, - RenderPlugin, - }, + render::RenderPlugin, ui::UiPlugin, }; @@ -119,13 +116,16 @@ impl AppBuilder { pub fn add_system(self, system: Box) -> Self { self.add_system_to_stage(system_stage::UPDATE, system) } - + pub fn add_setup_system(mut self, system: Box) -> Self { self.setup_systems.push(system); self } - pub fn build_system(mut self, build: F) -> Self where F: Fn(&mut Resources) -> Box{ + pub fn build_system(mut self, build: F) -> Self + where + F: Fn(&mut Resources) -> Box, + { let system = build(&mut self.resources); self.add_system(system) } @@ -173,7 +173,10 @@ impl AppBuilder { self } - pub fn add_event(self) -> Self where T: Send + Sync + 'static { + pub fn add_event(self) -> Self + where + T: Send + Sync + 'static, + { self.add_resource(Event::::default()) .add_system_to_stage(system_stage::EVENT_UPDATE, Event::::update_system()) } @@ -186,6 +189,11 @@ impl AppBuilder { self } + pub fn set_runner(mut self, run_fn: impl Fn(App) + 'static) -> Self { + self.run = Some(Box::new(run_fn)); + self + } + pub fn add_defaults(mut self) -> Self { self = self .add_plugin(CorePlugin::default()) @@ -194,12 +202,18 @@ impl AppBuilder { #[cfg(feature = "winit")] { - self = self.add_plugin(WinitPlugin::default()) + self = self.add_plugin(crate::core::window::winit::WinitPlugin::default()) + } + #[cfg(not(feature = "winit"))] + { + self = self.add_plugin(crate::app::schedule_run::ScheduleRunner::default()); } #[cfg(feature = "wgpu")] { - self = self.add_plugin(WgpuRendererPlugin::default()); + self = self.add_plugin( + crate::render::renderer::renderers::wgpu_renderer::WgpuRendererPlugin::default(), + ); } self } diff --git a/src/app/mod.rs b/src/app/mod.rs index 21f907bdab..a493f8b04a 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -2,6 +2,7 @@ mod app; mod app_builder; pub mod system_stage; pub mod plugin; +pub mod schedule_runner; pub use app::App; pub use app_builder::AppBuilder; diff --git a/src/app/schedule_runner.rs b/src/app/schedule_runner.rs new file mode 100644 index 0000000000..b4498c385b --- /dev/null +++ b/src/app/schedule_runner.rs @@ -0,0 +1,46 @@ +use crate::{ + app::{App, AppBuilder}, + prelude::AppPlugin, +}; +use std::{thread, time::Duration}; + +#[derive(Copy, Clone, Debug)] +pub enum RunMode { + Loop { + wait: Option, + }, + Once, +} + +impl Default for RunMode { + fn default() -> Self { + RunMode::Loop { + wait: None, + } + } +} + +#[derive(Default)] +pub struct ScheduleRunner { + pub run_mode: RunMode, +} + +impl AppPlugin for ScheduleRunner { + fn build(&self, app: AppBuilder) -> AppBuilder { + let run_mode = self.run_mode; + app.set_runner(move |mut app: App| match run_mode { + RunMode::Once => { + app.schedule.execute(&mut app.world, &mut app.resources); + } + RunMode::Loop { wait }=> loop { + app.schedule.execute(&mut app.world, &mut app.resources); + if let Some(wait) = wait { + thread::sleep(wait); + } + }, + }) + } + fn name(&self) -> &'static str { + "ScheduleRun" + } +} diff --git a/src/core/window/winit/mod.rs b/src/core/window/winit/mod.rs index d6e64311f7..1715576ee3 100644 --- a/src/core/window/winit/mod.rs +++ b/src/core/window/winit/mod.rs @@ -1,4 +1,7 @@ -use crate::{core::Event, app::{plugin::AppPlugin, App, AppBuilder}}; +use crate::{ + app::{plugin::AppPlugin, App, AppBuilder}, + core::Event, +}; use super::{Window, WindowResize}; use winit::{ @@ -11,12 +14,8 @@ use winit::{ pub struct WinitPlugin; impl AppPlugin for WinitPlugin { - fn build(&self, mut app: AppBuilder) -> AppBuilder { - { - app.run = Some(get_winit_run()); - } - - app + fn build(&self, app: AppBuilder) -> AppBuilder { + app.set_runner(winit_runner) } fn name(&self) -> &'static str { @@ -24,63 +23,61 @@ impl AppPlugin for WinitPlugin { } } -pub fn get_winit_run() -> Box { - Box::new(|mut app: App| { - env_logger::init(); - let event_loop = EventLoop::new(); - let winit_window = { - let window = app.resources.get::().unwrap(); - let winit_window = winit::window::Window::new(&event_loop).unwrap(); - winit_window.set_title(&window.title); - winit_window.set_inner_size(winit::dpi::PhysicalSize::new(window.width, window.height)); - winit_window +pub fn winit_runner(mut app: App) { + env_logger::init(); + let event_loop = EventLoop::new(); + let winit_window = { + let window = app.resources.get::().unwrap(); + let winit_window = winit::window::Window::new(&event_loop).unwrap(); + winit_window.set_title(&window.title); + winit_window.set_inner_size(winit::dpi::PhysicalSize::new(window.width, window.height)); + winit_window + }; + + app.resources.insert(winit_window); + + log::debug!("Entering render loop"); + event_loop.run(move |event, _, control_flow| { + *control_flow = if cfg!(feature = "metal-auto-capture") { + ControlFlow::Exit + } else { + ControlFlow::Poll }; + match event { + event::Event::WindowEvent { + event: WindowEvent::Resized(size), + .. + } => { + let mut window = app.resources.get_mut::().unwrap(); + window.width = size.width; + window.height = size.height; - app.resources.insert(winit_window); - - log::debug!("Entering render loop"); - event_loop.run(move |event, _, control_flow| { - *control_flow = if cfg!(feature = "metal-auto-capture") { - ControlFlow::Exit - } else { - ControlFlow::Poll - }; - match event { - event::Event::WindowEvent { - event: WindowEvent::Resized(size), - .. - } => { - let mut window = app.resources.get_mut::().unwrap(); - window.width = size.width; - window.height = size.height; - - let mut resize_event = app.resources.get_mut::>().unwrap(); - resize_event.raise(WindowResize { - id: window.id, - height: window.height, - width: window.width, - }); - } - event::Event::WindowEvent { event, .. } => match event { - WindowEvent::KeyboardInput { - input: - event::KeyboardInput { - virtual_keycode: Some(event::VirtualKeyCode::Escape), - state: event::ElementState::Pressed, - .. - }, - .. - } - | WindowEvent::CloseRequested => { - *control_flow = ControlFlow::Exit; - } - _ => {} - }, - event::Event::MainEventsCleared => { - app.update(); - } - _ => (), + let mut resize_event = app.resources.get_mut::>().unwrap(); + resize_event.raise(WindowResize { + id: window.id, + height: window.height, + width: window.width, + }); } - }); - }) + event::Event::WindowEvent { event, .. } => match event { + WindowEvent::KeyboardInput { + input: + event::KeyboardInput { + virtual_keycode: Some(event::VirtualKeyCode::Escape), + state: event::ElementState::Pressed, + .. + }, + .. + } + | WindowEvent::CloseRequested => { + *control_flow = ControlFlow::Exit; + } + _ => {} + }, + event::Event::MainEventsCleared => { + app.update(); + } + _ => (), + } + }); } diff --git a/src/render/renderer/renderers/wgpu_renderer/wgpu_renderer.rs b/src/render/renderer/renderers/wgpu_renderer/wgpu_renderer.rs index 13b028ab93..72073b6569 100644 --- a/src/render/renderer/renderers/wgpu_renderer/wgpu_renderer.rs +++ b/src/render/renderer/renderers/wgpu_renderer/wgpu_renderer.rs @@ -343,9 +343,12 @@ impl WgpuRenderer { } pub fn create_surface(&mut self, resources: &Resources) { - let window = resources.get::().unwrap(); - let surface = wgpu::Surface::create(window.deref()); - self.surface = Some(surface); + #[cfg(feature = "winit")] + { + let window = resources.get::().unwrap(); + let surface = wgpu::Surface::create(window.deref()); + self.surface = Some(surface); + } } }