From 314b80ab9dd772fb780a774dff216854b27c30ab Mon Sep 17 00:00:00 2001 From: nanoqsh Date: Sun, 2 Jun 2024 20:03:59 +0500 Subject: [PATCH] Run event loop from window state --- README.md | 54 +++++++++++++----------- dunge/src/el.rs | 22 ++++------ dunge/src/lib.rs | 7 +--- dunge/src/window.rs | 78 +++++++++++++++++++++-------------- examples/cube/src/lib.rs | 2 +- examples/ssaa/src/lib.rs | 73 +++++++++++++++++--------------- examples/ssaa/src/main.rs | 4 +- examples/triangle/src/lib.rs | 39 +++++++++++------- examples/triangle/src/main.rs | 4 +- examples/wasm/src/lib.rs | 4 +- examples/window/src/main.rs | 48 ++++++++++++--------- 11 files changed, 185 insertions(+), 150 deletions(-) diff --git a/README.md b/README.md index 4788504..141b976 100644 --- a/README.md +++ b/README.md @@ -69,17 +69,14 @@ As you can see from the snippet, the shader requires you to provide two things: That's because this function doesn't actually compute anything. It is needed only to describe the method for computing what we need on GPU. During shader instantiation, this function is used to compile an actual shader. However, this saves us from having to write the shader in wgsl and allows to typecheck at compile time. For example, dunge checks that a vertex type in a shader matches with a mesh used during rendering. It also checks types inside the shader itself. -Now let's create the dunge context, window and other necessary things: +Now let's create the dunge context and other necessary things: ```rust -// Create the dunge context with a window -let window = dunge::window().await?; -let cx = window.context(); +// Create the dunge context +let cx = dunge::context().await?; // You can use the context to manage dunge objects. // Create a shader instance let shader = cx.make_shader(triangle); -// And a layer for drawing a mesh on it -let layer = cx.make_layer(&shader, window.format()); ``` You may notice the context creation requires async. This is WGPU specific, so you will have to add your favorite async runtime in the project. @@ -118,26 +115,37 @@ We don't do anything special here, we just check is Esc pressed and e Second `Draw` is used directly to draw something in the final frame: ```rust // Describe the `Draw` handler -let draw = |mut frame: Frame| { - use dunge::color::Rgba; - - // Create a black RGBA background - let bg = Rgba::from_bytes([0, 0, 0, !0]); - - frame - // Select a layer to draw on it - .layer(&layer, bg) - // The shader has no bindings, so call empty bind - .bind_empty() - // And finally draw the mesh - .draw(&mesh); +let draw = { + // Clone the context to closure + let cx = cx.clone(); + + // Create a layer for drawing a mesh on it + let layer = OnceCell::default(); + + move |mut frame: Frame| { + use dunge::color::Rgba; + + // Create a black RGBA background + let bg = Rgba::from_bytes([0, 0, 0, !0]); + + // Lazily create a layer + let layer = layer.get_or_init(|| cx.make_layer(&shader, frame.format())); + + frame + // Select a layer to draw on it + .layer(&layer, bg) + // The shader has no bindings, so call empty bind + .bind_empty() + // And finally draw the mesh + .draw(&mesh); + } }; ``` Now you can run our application and see the window: ```rust // Run the window with handlers -window.run_local(dunge::update(upd, draw))?; +dunge::window_state().run_local(cx, dunge::update(upd, draw))?; ```
@@ -157,13 +165,13 @@ cargo run -p To build and run a wasm example: ```sh -cargo xtask build -cargo xtask serve +cargo x build +cargo x serve ``` If [`wasm-pack`](https://github.com/rustwasm/wasm-pack) is already installed on the system, the build script will find it and use it to compile a wasm artifact. Otherwise, `wasm-pack` will be installed locally. To prevent this behavior add the `no-install` flag: ```sh -cargo xtask --no-install build +cargo x --no-install build ``` Eventually it will start a local server and you can open http://localhost:3000 in your browser to see the application running. Only [WebGPU](https://gpuweb.github.io/gpuweb/) backend is supported for the web platform, so make sure your browser supports it. diff --git a/dunge/src/el.rs b/dunge/src/el.rs index c0ec9c1..05d24e8 100644 --- a/dunge/src/el.rs +++ b/dunge/src/el.rs @@ -27,48 +27,40 @@ pub type SmolStr = keyboard::SmolStr; /// Describes a button of a mouse controller. pub type MouseButton = event::MouseButton; -pub fn run(cx: Context, ws: WindowState, upd: U) -> Result<(), LoopError> +pub(crate) fn run(ws: WindowState, cx: Context, upd: U) -> Result<(), LoopError> where U: Update + 'static, { #[cfg(not(target_arch = "wasm32"))] { - run_local(cx, ws, upd) + run_local(ws, cx, upd) } #[cfg(target_arch = "wasm32")] { - spawn(cx, ws, upd) + spawn(ws, cx, upd) } } #[cfg(not(target_arch = "wasm32"))] -pub fn run_local(cx: Context, ws: WindowState, upd: U) -> Result<(), LoopError> +pub(crate) fn run_local(ws: WindowState, cx: Context, upd: U) -> Result<(), LoopError> where U: Update, { - let lu = EventLoop::with_user_event() - .build() - .map_err(LoopError::EventLoop)?; - - let view = View::new(ws); + let (view, lu) = ws.into_view_and_loop(); let mut handler = Handler::new(cx, view, upd); let out = lu.run_app(&mut handler).map_err(LoopError::EventLoop); out.or(handler.out) } #[cfg(target_arch = "wasm32")] -fn spawn(cx: Context, ws: WindowState, upd: U) -> Result<(), LoopError> +fn spawn(ws: WindowState, cx: Context, upd: U) -> Result<(), LoopError> where U: Update + 'static, { use winit::platform::web::EventLoopExtWebSys; - let lu = EventLoop::with_user_event() - .build() - .map_err(LoopError::EventLoop)?; - - let view = View::new(ws); + let (view, lu) = ws.into_view_and_loop(); let handler = Handler::new(cx, view, upd); lu.spawn_app(handler); Ok(()) diff --git a/dunge/src/lib.rs b/dunge/src/lib.rs index 2b7b419..56c780a 100644 --- a/dunge/src/lib.rs +++ b/dunge/src/lib.rs @@ -49,16 +49,13 @@ pub use { }; #[cfg(all(feature = "winit", not(target_arch = "wasm32")))] -pub use crate::{ - el::run_local, - window::{window, window_state}, -}; +pub use crate::window::{window, window_state}; #[cfg(all(feature = "winit", target_arch = "wasm32"))] pub use crate::window::{from_element, window_state_from_element}; #[cfg(feature = "winit")] pub use crate::{ - el::{run, Buttons, Control, Flow, Key, KeyCode, LoopError, Mouse, MouseButton, SmolStr, Then}, + el::{Buttons, Control, Flow, Key, KeyCode, LoopError, Mouse, MouseButton, SmolStr, Then}, update::{update, update_with_event, update_with_state, Update}, }; diff --git a/dunge/src/window.rs b/dunge/src/window.rs index 8a2338f..e736c33 100644 --- a/dunge/src/window.rs +++ b/dunge/src/window.rs @@ -3,7 +3,7 @@ use { crate::{ context::{Context, FailedMakeContext}, - el::{Loop, LoopError}, + el::{self, Loop, LoopError}, element::Element, format::Format, state::{State, Target}, @@ -24,7 +24,7 @@ use { }, winit::{ error::{EventLoopError, OsError}, - event_loop::{ActiveEventLoop, EventLoopClosed, EventLoopProxy}, + event_loop::{ActiveEventLoop, EventLoop, EventLoopClosed, EventLoopProxy}, window::{self, WindowAttributes, WindowId}, }, }; @@ -134,13 +134,7 @@ impl WindowBuilder { None => attrs.with_fullscreen(Some(Fullscreen::Borderless(None))), }; - let view = { - let el = self.element.take().expect("take the element once"); - // el.set_canvas(&inner); - // el.set_window_size(&inner); - View::new(WindowState { attrs, el }) - }; - + let view = todo!(); Ok(Window { cx, lu, view }) } } @@ -264,12 +258,16 @@ where } } -pub struct WindowState { +pub struct WindowState +where + V: 'static, +{ attrs: WindowAttributes, el: Element, + lu: EventLoop, } -impl WindowState { +impl WindowState { /// Set the title to the window. pub fn with_title(self, title: S) -> Self where @@ -302,14 +300,38 @@ impl WindowState { ..self } } + + pub fn run(self, cx: Context, upd: U) -> Result<(), LoopError> + where + U: Update + 'static, + { + el::run(self, cx, upd) + } + + #[cfg(not(target_arch = "wasm32"))] + pub fn run_local(self, cx: Context, upd: U) -> Result<(), LoopError> + where + U: Update, + { + el::run_local(self, cx, upd) + } + + pub(crate) fn into_view_and_loop(self) -> (View, EventLoop) { + let view = View { + init: Init::Empty(Box::new(self.attrs)), + id: WindowId::from(u64::MAX), + el: self.el, + format: Format::default(), + size: (1, 1), + }; + + (view, self.lu) + } } #[cfg(all(feature = "winit", not(target_arch = "wasm32")))] -pub fn window_state() -> WindowState { - WindowState { - attrs: WindowAttributes::default(), - el: Element(()), - } +pub fn window_state() -> WindowState { + state(Element(())) } #[cfg(all(feature = "winit", target_arch = "wasm32"))] @@ -325,10 +347,16 @@ pub fn window_state_from_element(id: &str) -> WindowState { panic!("an element with id {id:?} not found"); }; - WindowState { - attrs: WindowAttributes::default(), - el: Element(inner), - } + state(Element(inner)) +} + +fn state(el: Element) -> WindowState { + let attrs = WindowAttributes::default(); + let Ok(lu) = EventLoop::with_user_event().build() else { + panic!("attempt to recreate the event loop"); + }; + + WindowState { attrs, el, lu } } enum Init { @@ -365,16 +393,6 @@ pub struct View { } impl View { - pub(crate) fn new(ws: WindowState) -> Self { - Self { - init: Init::Empty(Box::new(ws.attrs)), - id: WindowId::from(u64::MAX), - el: ws.el, - format: Format::default(), - size: (1, 1), - } - } - pub(crate) fn init(&mut self, state: &State, el: &ActiveEventLoop) -> Result<(), Error> { match &mut self.init { Init::Empty(attrs) => { diff --git a/examples/cube/src/lib.rs b/examples/cube/src/lib.rs index b04decf..656f17c 100644 --- a/examples/cube/src/lib.rs +++ b/examples/cube/src/lib.rs @@ -133,6 +133,6 @@ pub async fn run(ws: dunge::window::WindowState) -> Result<(), Error> { } }; - dunge::run(cx, ws, dunge::update(upd, draw))?; + ws.run(cx, dunge::update(upd, draw))?; Ok(()) } diff --git a/examples/ssaa/src/lib.rs b/examples/ssaa/src/lib.rs index c929816..10e9ea3 100644 --- a/examples/ssaa/src/lib.rs +++ b/examples/ssaa/src/lib.rs @@ -1,6 +1,6 @@ type Error = Box; -pub fn run(window: dunge::window::Window) -> Result<(), Error> { +pub async fn run(ws: dunge::window::WindowState) -> Result<(), Error> { use { dunge::{ bind::UniqueBinding, @@ -13,7 +13,7 @@ pub fn run(window: dunge::window::Window) -> Result<(), Error> { uniform::Uniform, Format, }, - std::f32::consts, + std::{cell::OnceCell, f32::consts}, }; const COLOR: Vec4 = Vec4::new(1., 0.4, 0.8, 1.); @@ -61,7 +61,7 @@ pub fn run(window: dunge::window::Window) -> Result<(), Error> { }, }; - let cx = window.context(); + let cx = dunge::context().await?; let triangle_shader = cx.make_shader(triangle); let screen_shader = cx.make_shader(screen); let mut r = 0.; @@ -87,7 +87,7 @@ pub fn run(window: dunge::window::Window) -> Result<(), Error> { cx.make_texture(data) }; - let render_buf = make_render_buf(&cx, window.size()); + let render_buf = make_render_buf(&cx, (1, 1)); let sam = cx.make_sampler(Filter::Nearest); let make_stp = |size| { @@ -110,21 +110,6 @@ pub fn run(window: dunge::window::Window) -> Result<(), Error> { (binder.into_binding(), handler) }; - let screen_mesh = { - const VERTS: [Screen; 4] = [ - Screen([-1., -1.], [0., 1.]), - Screen([1., -1.], [1., 1.]), - Screen([1., 1.], [1., 0.]), - Screen([-1., 1.], [0., 0.]), - ]; - - let data = MeshData::from_quads(&[VERTS])?; - cx.make_mesh(&data) - }; - - let triangle_layer = cx.make_layer(&triangle_shader, Format::SrgbAlpha); - let screen_layer = cx.make_layer(&screen_shader, window.format()); - let upd = move |state: &mut State<_>, ctrl: &Control| { for key in ctrl.pressed_keys() { if key.code == KeyCode::Escape { @@ -150,21 +135,41 @@ pub fn run(window: dunge::window::Window) -> Result<(), Error> { Then::Run }; - let draw = move |state: &State<_>, mut frame: Frame| { - let main = |mut frame: Frame| { - let opts = Rgba::from_standard([0.1, 0.05, 0.15, 1.]); - frame - .layer(&triangle_layer, opts) - .bind(&bind) - .draw_points(3); - }; + let screen_mesh = { + const VERTS: [Screen; 4] = [ + Screen([-1., -1.], [0., 1.]), + Screen([1., -1.], [1., 1.]), + Screen([1., 1.], [1., 0.]), + Screen([-1., 1.], [0., 0.]), + ]; + + let data = MeshData::from_quads(&[VERTS])?; + cx.make_mesh(&data) + }; + + let triangle_layer = cx.make_layer(&triangle_shader, Format::SrgbAlpha); + let screen_layer = OnceCell::default(); + let draw = { + let cx = cx.clone(); + move |state: &State<_>, mut frame: Frame| { + let main = |mut frame: Frame| { + let opts = Rgba::from_standard([0.1, 0.05, 0.15, 1.]); + frame + .layer(&triangle_layer, opts) + .bind(&bind) + .draw_points(3); + }; + + state.cx.draw_to(&state.render_buf, dunge::draw(main)); - state.cx.draw_to(&state.render_buf, dunge::draw(main)); + let screen_layer = + screen_layer.get_or_init(|| cx.make_layer(&screen_shader, frame.format())); - frame - .layer(&screen_layer, Options::default()) - .bind(&state.bind_map) - .draw(&screen_mesh); + frame + .layer(&screen_layer, Options::default()) + .bind(&state.bind_map) + .draw(&screen_mesh); + } }; struct State { @@ -174,11 +179,11 @@ pub fn run(window: dunge::window::Window) -> Result<(), Error> { } let state = State { - cx, + cx: cx.clone(), render_buf, bind_map, }; - window.run(dunge::update_with_state(state, upd, draw))?; + ws.run(cx, dunge::update_with_state(state, upd, draw))?; Ok(()) } diff --git a/examples/ssaa/src/main.rs b/examples/ssaa/src/main.rs index dc01efa..417ac93 100644 --- a/examples/ssaa/src/main.rs +++ b/examples/ssaa/src/main.rs @@ -1,7 +1,7 @@ fn main() { env_logger::init(); - let window = helpers::block_on(dunge::window().with_title("SSAA")); - if let Err(err) = window.map_err(Box::from).and_then(ssaa::run) { + let ws = dunge::window_state().with_title("SSAA"); + if let Err(err) = helpers::block_on(ssaa::run(ws)) { eprintln!("error: {err}"); } } diff --git a/examples/triangle/src/lib.rs b/examples/triangle/src/lib.rs index 8e1973e..b15d623 100644 --- a/examples/triangle/src/lib.rs +++ b/examples/triangle/src/lib.rs @@ -1,6 +1,6 @@ type Error = Box; -pub fn run(window: dunge::window::Window) -> Result<(), Error> { +pub async fn run(ws: dunge::window::WindowState) -> Result<(), Error> { use { dunge::{ color::Rgba, @@ -9,7 +9,7 @@ pub fn run(window: dunge::window::Window) -> Result<(), Error> { sl::{Groups, Index, Out}, uniform::Uniform, }, - std::f32::consts, + std::{cell::OnceCell, f32::consts}, }; const COLOR: Vec4 = Vec4::new(1., 0.4, 0.8, 1.); @@ -26,7 +26,7 @@ pub fn run(window: dunge::window::Window) -> Result<(), Error> { } }; - let cx = window.context(); + let cx = dunge::context().await?; let shader = cx.make_shader(triangle); let mut r = 0.; let uniform = cx.make_uniform(r); @@ -37,24 +37,31 @@ pub fn run(window: dunge::window::Window) -> Result<(), Error> { binder.into_binding() }; - let layer = cx.make_layer(&shader, window.format()); - let upd = move |ctrl: &Control| { - for key in ctrl.pressed_keys() { - if key.code == KeyCode::Escape { - return Then::Close; + let upd = { + let cx = cx.clone(); + move |ctrl: &Control| { + for key in ctrl.pressed_keys() { + if key.code == KeyCode::Escape { + return Then::Close; + } } - } - r += ctrl.delta_time().as_secs_f32() * 0.5; - uniform.update(&cx, r); - Then::Run + r += ctrl.delta_time().as_secs_f32() * 0.5; + uniform.update(&cx, r); + Then::Run + } }; - let draw = move |mut frame: Frame| { - let opts = Rgba::from_standard([0.1, 0.05, 0.15, 1.]); - frame.layer(&layer, opts).bind(&bind).draw_points(3); + let draw = { + let cx = cx.clone(); + let layer = OnceCell::default(); + move |mut frame: Frame| { + let opts = Rgba::from_standard([0.1, 0.05, 0.15, 1.]); + let layer = layer.get_or_init(|| cx.make_layer(&shader, frame.format())); + frame.layer(&layer, opts).bind(&bind).draw_points(3); + } }; - window.run(dunge::update(upd, draw))?; + ws.run(cx, dunge::update(upd, draw))?; Ok(()) } diff --git a/examples/triangle/src/main.rs b/examples/triangle/src/main.rs index cd5bb19..abc5070 100644 --- a/examples/triangle/src/main.rs +++ b/examples/triangle/src/main.rs @@ -1,7 +1,7 @@ fn main() { env_logger::init(); - let window = helpers::block_on(dunge::window().with_title("Triangle")); - if let Err(err) = window.map_err(Box::from).and_then(triangle::run) { + let ws = dunge::window_state().with_title("Triangle"); + if let Err(err) = helpers::block_on(triangle::run(ws)) { eprintln!("error: {err}"); } } diff --git a/examples/wasm/src/lib.rs b/examples/wasm/src/lib.rs index 4e771ab..f370516 100644 --- a/examples/wasm/src/lib.rs +++ b/examples/wasm/src/lib.rs @@ -24,8 +24,8 @@ pub async fn start() { run = triangle::run; } - let window = dunge::from_element("root").await; - if let Err(err) = window.map_err(Box::from).and_then(run) { + let ws = dunge::window_state_from_element("root"); + if let Err(err) = run(ws).await { panic!("error: {err}"); } } diff --git a/examples/window/src/main.rs b/examples/window/src/main.rs index d41c43c..da84f06 100644 --- a/examples/window/src/main.rs +++ b/examples/window/src/main.rs @@ -8,7 +8,7 @@ fn main() { } async fn run() -> Result<(), Error> { - use dunge::prelude::*; + use {dunge::prelude::*, std::cell::OnceCell}; // Create a vertex type #[repr(C)] @@ -37,15 +37,12 @@ async fn run() -> Result<(), Error> { sl::Out { place, color } }; - // Create the dunge context with a window - let window = dunge::window().await?; - let cx = window.context(); + // Create the dunge context + let cx = dunge::context().await?; // You can use the context to manage dunge objects. // Create a shader instance let shader = cx.make_shader(triangle); - // And a layer for drawing a mesh on it - let layer = cx.make_layer(&shader, window.format()); // Create a mesh from vertices let mesh = { @@ -81,22 +78,33 @@ async fn run() -> Result<(), Error> { }; // Describe the `Draw` handler - let draw = |mut frame: Frame| { - use dunge::color::Rgba; - - // Create a black RGBA background - let bg = Rgba::from_bytes([0, 0, 0, !0]); - - frame - // Select a layer to draw on it - .layer(&layer, bg) - // The shader has no bindings, so call empty bind - .bind_empty() - // And finally draw the mesh - .draw(&mesh); + let draw = { + // Clone the context to closure + let cx = cx.clone(); + + // Create a layer for drawing a mesh on it + let layer = OnceCell::default(); + + move |mut frame: Frame| { + use dunge::color::Rgba; + + // Create a black RGBA background + let bg = Rgba::from_bytes([0, 0, 0, !0]); + + // Lazily create a layer + let layer = layer.get_or_init(|| cx.make_layer(&shader, frame.format())); + + frame + // Select a layer to draw on it + .layer(&layer, bg) + // The shader has no bindings, so call empty bind + .bind_empty() + // And finally draw the mesh + .draw(&mesh); + } }; // Run the window with handlers - window.run_local(dunge::update(upd, draw))?; + dunge::window_state().run_local(cx, dunge::update(upd, draw))?; Ok(()) }