Skip to content

Commit

Permalink
Run event loop from window state
Browse files Browse the repository at this point in the history
  • Loading branch information
nanoqsh committed Jun 2, 2024
1 parent 57fa677 commit 314b80a
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 150 deletions.
54 changes: 31 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -118,26 +115,37 @@ We don't do anything special here, we just check is <kbd>Esc</kbd> 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))?;
```

<div align="center">
Expand All @@ -157,13 +165,13 @@ cargo run -p <example_name>

To build and run a wasm example:
```sh
cargo xtask build <example_name>
cargo xtask serve <example_name>
cargo x build <example_name>
cargo x serve <example_name>
```

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 <example_name>
cargo x --no-install build <example_name>
```

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.
Expand Down
22 changes: 7 additions & 15 deletions dunge/src/el.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,48 +27,40 @@ pub type SmolStr = keyboard::SmolStr;
/// Describes a button of a mouse controller.
pub type MouseButton = event::MouseButton;

pub fn run<U>(cx: Context, ws: WindowState, upd: U) -> Result<(), LoopError>
pub(crate) fn run<U>(ws: WindowState<U::Event>, 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<U>(cx: Context, ws: WindowState, upd: U) -> Result<(), LoopError>
pub(crate) fn run_local<U>(ws: WindowState<U::Event>, 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<U>(cx: Context, ws: WindowState, upd: U) -> Result<(), LoopError>
fn spawn<U>(ws: WindowState<U::Event>, 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(())
Expand Down
7 changes: 2 additions & 5 deletions dunge/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};
78 changes: 48 additions & 30 deletions dunge/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use {
crate::{
context::{Context, FailedMakeContext},
el::{Loop, LoopError},
el::{self, Loop, LoopError},
element::Element,
format::Format,
state::{State, Target},
Expand All @@ -24,7 +24,7 @@ use {
},
winit::{
error::{EventLoopError, OsError},
event_loop::{ActiveEventLoop, EventLoopClosed, EventLoopProxy},
event_loop::{ActiveEventLoop, EventLoop, EventLoopClosed, EventLoopProxy},
window::{self, WindowAttributes, WindowId},
},
};
Expand Down Expand Up @@ -134,13 +134,7 @@ impl<V> WindowBuilder<V> {
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 })
}
}
Expand Down Expand Up @@ -264,12 +258,16 @@ where
}
}

pub struct WindowState {
pub struct WindowState<V = ()>
where
V: 'static,
{
attrs: WindowAttributes,
el: Element,
lu: EventLoop<V>,
}

impl WindowState {
impl<V> WindowState<V> {
/// Set the title to the window.
pub fn with_title<S>(self, title: S) -> Self
where
Expand Down Expand Up @@ -302,14 +300,38 @@ impl WindowState {
..self
}
}

pub fn run<U>(self, cx: Context, upd: U) -> Result<(), LoopError>
where
U: Update<Event = V> + 'static,
{
el::run(self, cx, upd)
}

#[cfg(not(target_arch = "wasm32"))]
pub fn run_local<U>(self, cx: Context, upd: U) -> Result<(), LoopError>
where
U: Update<Event = V>,
{
el::run_local(self, cx, upd)
}

pub(crate) fn into_view_and_loop(self) -> (View, EventLoop<V>) {
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<V>() -> WindowState<V> {
state(Element(()))
}

#[cfg(all(feature = "winit", target_arch = "wasm32"))]
Expand All @@ -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<V>(el: Element) -> WindowState<V> {
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 {
Expand Down Expand Up @@ -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) => {
Expand Down
2 changes: 1 addition & 1 deletion examples/cube/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
Loading

0 comments on commit 314b80a

Please sign in to comment.