Skip to main content

flow_ngin/
flow.rs

1//! Flow control and application event loop.
2//!
3//! This module provides the main event loop and flow abstraction for the game engine.
4//! A "flow" represents a scene or game state that handles user input, updates simulation,
5//! and provides renderable objects each frame. The engine manages multiple active flows
6//! and coordinates rendering, picking, and event distribution.
7//!
8//! # User-facing types
9//!
10//! - [`GraphicsFlow<S, E>`] is the trait for scenes/states that handle events and rendering
11//! - [`Out<S, E>`] is the output type for async event handling and context configuration
12//!
13//! # Lifecycle Flow
14//!
15//! The event loop follows this pattern each frame:
16//! 1. Collect window/device events
17//! 2. Call `on_<device/window/custom>_event` on all flows for event distribution
18//! 3. Update flow state (via `on_update` / `on_tick`)
19//! 4. Call flows' `get_render()` to collect renderable objects
20//! 5. Perform picking if mouse clicked
21//! 6. Render to frame buffer using batched pipelines
22//! 7. Present frame
23
24use std::{collections::HashSet, fmt::Debug, iter, pin::Pin, sync::Arc};
25
26use instant::{Duration, Instant};
27
28use cgmath::Rotation3;
29#[cfg(feature = "integration-tests")]
30use tokio::runtime::Runtime;
31use wgpu::util::{self, DeviceExt};
32use winit::{
33    application::ApplicationHandler,
34    event::{DeviceEvent, DeviceId, MouseButton, WindowEvent},
35    event_loop::{ActiveEventLoop, EventLoop},
36    window::Window,
37};
38
39use crate::{
40    context::{Context, InitContext, MouseButtonState},
41    data_structures::{
42        model::{DrawLight, DrawModel},
43        texture::Texture,
44    },
45    pick::{PickId, draw_to_pick_buffer},
46    render::{Flat, Geometry, Instanced, Render},
47};
48
49#[cfg(target_arch = "wasm32")]
50use wasm_bindgen::prelude::*;
51
52///
53/// This is the Output Type for every lifecycle hook where the user can pass async events that are
54/// handled according to the platform you're running on.
55///
56/// `Out::FutEvent` can be used to resolve a future of an Event that is put in the Event Queue after
57/// being resolved. The caller is responsible for handling the event later on and it will have no
58/// side effects unless handled.
59///
60/// `Out::FutFn` can be used to directly modify the state and the mutation is handled internally with
61/// no further action required by the callee but is blocking on non-wasn environments.
62///
63/// `Out::Configure` can be used to modify the Context during runtime for instance to change the tick
64/// speed or the clear colour.
65///
66/// `Empty` is the default output used when no eventing/futures need to be handled.
67///
68pub enum Out<S, E>
69where
70    E: Send,
71{
72    FutEvent(Vec<Box<dyn Future<Output = E> + Send>>),
73    FutFn(Vec<Box<dyn Future<Output = Box<dyn FnOnce(&mut S)>>>>),
74    Configure(Box<dyn FnOnce(&mut Context)>),
75    Composed(Vec<Out<S, E>>),
76    Empty,
77}
78
79impl<S, E: Send> Default for Out<S, E> {
80    fn default() -> Self {
81        Self::Empty
82    }
83}
84
85#[cfg(feature = "integration-tests")]
86pub enum ImageTestResult {
87    Passed,
88    Waiting,
89    Failed,
90}
91
92/// Trait for implementing a renderable scene or game state.
93///
94/// A `GraphicsFlow` manages a self-contained portion of the application:
95/// rendering, input handling, animations, and state updates. The engine
96/// coordinates multiple flows, passes events to them, and composes their renders.
97///
98/// # Lifecycle
99///
100/// 1. `on_init()` is called once when the flow is created; configure context (camera, clear color, etc.)
101/// 2. `on_window_events()` and `on_device_events()` are called for each winit input event
102/// 3. `on_update()` is called every frame
103/// 4. `on_tick()` is called every `tick_duration_millis`
104/// 5. `on_click()` is called when an object with this flow's ID is clicked
105/// 6. `on_custom_events()` is called for custom application events
106/// 7. `on_render()` is called each frame and specifies how to render `self`
107///
108pub trait GraphicsFlow<S, E: Send> {
109    /// Initialize the flow and configure the context.
110    ///
111    /// This is the only place to modify the Context and configure things such as the default
112    /// background colour or camera start position.
113    fn on_init(&mut self, _ctx: &mut Context, _state: &mut S) -> Out<S, E> {
114        Out::Empty
115    }
116
117    /// Handle a click on an object rendered by this flow.
118    ///
119    ///
120    /// `on_click` is triggered when something on the screen (rendered by `self`) was clicked on.
121    ///
122    /// `id` is the ID that correlates to a specific mesh set via `on_render`.
123    /// It is advised to use a unique u32 id for each element that should be selectable
124    ///
125    /// When the render type `Custom` is used then also picking has to be implemented by the caller.
126    /// See `flow_ngin::pick::draw_to_pick_buffer` for more information about custom picking.
127    ////
128    /// picking; see [`crate::pick::draw_to_pick_buffer`] for details.
129    fn on_click(&mut self, _ctx: &Context, _state: &mut S, _id: PickId) -> Out<S, E> {
130        Out::Empty
131    }
132
133    /// Update state every frame.
134    ///
135    /// Called every frame with the elapsed time `dt`. Use for animations,
136    /// physics updates, and other per-frame logic.
137    fn on_update(&mut self, _ctx: &Context, _state: &mut S, _dt: Duration) -> Out<S, E> {
138        Out::Empty
139    }
140
141    /// Update state periodically.
142    ///
143    /// Called every `tick_duration_millis` milliseconds (configurable via context).
144    /// Use for discrete game logic that doesn't need to run every frame.
145    fn on_tick(&mut self, _ctx: &Context, _state: &mut S) -> Out<S, E> {
146        Out::Empty
147    }
148
149    /// Handle raw device events (keyboard, mouse hardware input).
150    fn on_device_events(
151        &mut self,
152        _ctx: &Context,
153        _state: &mut S,
154        _event: &DeviceEvent,
155    ) -> Out<S, E> {
156        Out::Empty
157    }
158
159    /// Handle window events (keyboard, mouse, window resizing, etc.).
160    fn on_window_events(
161        &mut self,
162        _ctx: &Context,
163        _state: &mut S,
164        _event: &WindowEvent,
165    ) -> Out<S, E> {
166        Out::Empty
167    }
168
169    /// Handle custom application events.
170    ///
171    /// Returns the event if it was not consumed, allowing it to be passed to
172    /// the next flow. Returning `None` means the event was consumed.
173    fn on_custom_events(&mut self, _ctx: &Context, _state: &mut S, event: E) -> Option<E> {
174        Some(event)
175    }
176
177    /// Return renderable objects for this flow.
178    ///
179    /// Called each frame. Collect your objects into a [`Render`] and return it.
180    /// The engine will batch and render all flows' renders in optimal order.
181    fn on_render<'pass>(&self) -> Render<'_, 'pass> {
182        Render::None
183    }
184
185    #[cfg(feature = "integration-tests")]
186    fn render_to_texture(
187        &self,
188        _ctx: &Context,
189        _state: &mut S,
190        _texture: &mut image::ImageBuffer<image::Rgba<u8>, wgpu::BufferView>,
191    ) -> Result<ImageTestResult, anyhow::Error> {
192        Ok(ImageTestResult::Passed)
193    }
194}
195
196// Dummy impl to make wasm work
197impl<State, Event> Debug for dyn GraphicsFlow<State, Event> + 'static {
198    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199        f.write_str("GraphicsFlow")
200    }
201}
202
203/// Type alias for a flow constructor (factory function).
204///
205/// A flow constructor takes an `InitContext` and asynchronously returns a
206/// boxed `GraphicsFlow`. This allows lazy initialization and resource loading.
207pub type FlowConstructor<S, E> =
208    Box<dyn FnOnce(InitContext) -> Pin<Box<dyn Future<Output = Box<dyn GraphicsFlow<S, E>>>>>>;
209
210/// Application state bundle: GPU context, app state, and surface status.
211#[derive(Debug)]
212pub struct AppState<State: 'static> {
213    pub(crate) ctx: Context,
214    state: State,
215    is_surface_configured: bool,
216}
217impl<'a, State: Default> AppState<State> {
218    async fn new(window: Arc<Window>) -> Self {
219        let ctx = Context::new(window).await;
220        let ctx = match ctx {
221            Ok(ctx) => ctx,
222            Err(e) => panic!(
223                "App initialization failed. Cannot create the main context: {}",
224                e
225            ),
226        };
227        let state = State::default();
228        let is_surface_configured = false;
229        Self {
230            ctx,
231            state,
232            is_surface_configured,
233        }
234    }
235
236    fn resize(&mut self, width: u32, height: u32) {
237        if width > 0 && height > 0 {
238            self.ctx.config.width = width;
239            self.ctx.config.height = height;
240            self.is_surface_configured = true;
241            self.ctx.projection.resize(width, height);
242            self.ctx
243                .surface
244                .configure(&self.ctx.device, &self.ctx.config);
245            let sample_count = self.ctx.anti_aliasing.sample_count();
246            self.ctx.depth_texture = Texture::create_depth_texture(
247                &self.ctx.device,
248                [self.ctx.config.width, self.ctx.config.height],
249                "depth_texture",
250                sample_count,
251            );
252            self.ctx.msaa_view = if sample_count > 1 {
253                Some(Texture::create_msaa_texture(
254                    &self.ctx.device,
255                    &self.ctx.config,
256                    sample_count,
257                ))
258            } else {
259                None
260            };
261            let screen_size_data = [width as f32, height as f32];
262            self.ctx.queue.write_buffer(
263                &self.ctx.screen_size.buffer,
264                0,
265                bytemuck::cast_slice(&screen_size_data),
266            );
267        }
268    }
269
270    fn get_surface_texture(&self) -> wgpu::SurfaceTexture {
271        self.ctx
272            .surface
273            .get_current_texture()
274            .expect("Failed to create surface.")
275    }
276
277    #[cfg(feature = "integration-tests")]
278    fn get_test_texture(&self, extent3d: wgpu::Extent3d) -> wgpu::Texture {
279        self.ctx.device.create_texture(&wgpu::TextureDescriptor {
280            label: Some("Golden Image Test Output Texture"),
281            size: extent3d,
282            mip_level_count: 1,
283            sample_count: 1,
284            dimension: wgpu::TextureDimension::D2,
285            format: self.ctx.config.format,
286            usage: wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::RENDER_ATTACHMENT,
287            view_formats: &[],
288        })
289    }
290
291    #[cfg(feature = "integration-tests")]
292    fn get_test_depth_texture(&self, extent3d: wgpu::Extent3d, sample_count: u32) -> wgpu::Texture {
293        self.ctx.device.create_texture(&wgpu::TextureDescriptor {
294            label: Some("Pick depth texture"),
295            size: extent3d,
296            mip_level_count: 1,
297            sample_count,
298            dimension: wgpu::TextureDimension::D2,
299            format: wgpu::TextureFormat::Depth32Float,
300            usage: wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::RENDER_ATTACHMENT,
301            view_formats: &[],
302        })
303    }
304
305    #[cfg(feature = "integration-tests")]
306    fn get_with_height(&self) -> (u32, u32) {
307        // The img lib requires divisibility of 256...
308        let width = self.ctx.config.width;
309        let height = self.ctx.config.height;
310        let width_offset = 256 - (width % 256);
311        let height_offset = 256 - (height % 256);
312        let width = width + width_offset;
313        let height = height + height_offset;
314        (width, height)
315    }
316
317    #[cfg(feature = "integration-tests")]
318    fn get_test_3d_extent(&self) -> wgpu::Extent3d {
319        let (width, height) = self.get_with_height();
320        wgpu::Extent3d {
321            width: width,
322            height: height,
323            depth_or_array_layers: 1,
324        }
325    }
326
327    fn render<Event: Send>(
328        &'a mut self,
329        graphics_flows: &mut Vec<Box<dyn GraphicsFlow<State, Event>>>,
330        #[cfg(feature = "integration-tests")] async_runtime: &Runtime,
331        #[cfg(feature = "integration-tests")] event_loop: &winit::event_loop::EventLoopProxy<
332            FlowEvent<State, Event>,
333        >,
334    ) -> Result<(), wgpu::SurfaceError> {
335        // invoke main render loop
336        self.ctx.window.request_redraw();
337
338        // Rendering requires the surface to be configured
339        if !self.is_surface_configured {
340            return Ok(());
341        }
342
343        let output = self.get_surface_texture();
344        // TODO: different view for golden img testing
345        #[cfg(not(feature = "integration-tests"))]
346        let view = output
347            .texture
348            .create_view(&wgpu::TextureViewDescriptor::default());
349
350        #[cfg(feature = "integration-tests")]
351        let (tex, msaa_tex, depth) = {
352            let extent3d = self.get_test_3d_extent();
353            let sample_count = self.ctx.anti_aliasing.sample_count();
354            let tex = self.get_test_texture(extent3d.clone());
355            let msaa_tex = if sample_count > 1 {
356                Some(self.ctx.device.create_texture(&wgpu::TextureDescriptor {
357                    label: Some("Test MSAA Color Texture"),
358                    size: extent3d.clone(),
359                    mip_level_count: 1,
360                    sample_count,
361                    dimension: wgpu::TextureDimension::D2,
362                    format: self.ctx.config.format,
363                    usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
364                    view_formats: &[],
365                }))
366            } else {
367                None
368            };
369            let depth = self.get_test_depth_texture(extent3d, sample_count);
370            (tex, msaa_tex, depth)
371        };
372
373        // Pre-create views so they live long enough for the render pass
374        #[cfg(feature = "integration-tests")]
375        let tex_view = tex.create_view(&wgpu::TextureViewDescriptor::default());
376        #[cfg(feature = "integration-tests")]
377        let msaa_tex_view = msaa_tex
378            .as_ref()
379            .map(|t| t.create_view(&wgpu::TextureViewDescriptor::default()));
380        #[cfg(feature = "integration-tests")]
381        let depth_view = depth.create_view(&wgpu::TextureViewDescriptor::default());
382
383        let mut encoder: wgpu::CommandEncoder =
384            self.ctx
385                .device
386                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
387                    label: Some("Render Encoder"),
388                });
389        {
390            let mut render_pass: wgpu::RenderPass<'_> =
391                encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
392                    label: Some("Render Pass"),
393                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
394                        #[cfg(feature = "integration-tests")]
395                        view: msaa_tex_view.as_ref().unwrap_or(&tex_view),
396                        #[cfg(not(feature = "integration-tests"))]
397                        view: self.ctx.msaa_view.as_ref().unwrap_or(&view),
398                        #[cfg(feature = "integration-tests")]
399                        resolve_target: if msaa_tex_view.is_some() {
400                            Some(&tex_view)
401                        } else {
402                            None
403                        },
404                        #[cfg(not(feature = "integration-tests"))]
405                        resolve_target: if self.ctx.msaa_view.is_some() {
406                            Some(&view)
407                        } else {
408                            None
409                        },
410                        ops: wgpu::Operations {
411                            load: wgpu::LoadOp::Clear(self.ctx.clear_colour),
412                            store: wgpu::StoreOp::Store,
413                        },
414                        depth_slice: None,
415                    })],
416                    depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
417                        #[cfg(feature = "integration-tests")]
418                        view: &depth_view,
419                        #[cfg(not(feature = "integration-tests"))]
420                        view: &self.ctx.depth_texture.view,
421                        depth_ops: Some(wgpu::Operations {
422                            load: wgpu::LoadOp::Clear(1.0),
423                            store: wgpu::StoreOp::Store,
424                        }),
425                        stencil_ops: None,
426                    }),
427                    occlusion_query_set: None,
428                    timestamp_writes: None,
429                    ..Default::default()
430                });
431
432            // Actual rendering:
433            if self.ctx.light.model.is_some() {
434                render_pass.set_pipeline(&self.ctx.pipelines.light);
435                render_pass.draw_light_model(
436                    self.ctx.light.model.as_ref().unwrap(),
437                    &self.ctx.camera.bind_group,
438                    &self.ctx.light.bind_group,
439                );
440            }
441            let mut basics: Vec<Instanced> = Vec::new();
442            let mut trans: Vec<Instanced> = Vec::new();
443            let mut guis: Vec<Flat> = Vec::new();
444            let mut terrain: Vec<Geometry> = Vec::new();
445            let mut customs = Vec::new();
446            graphics_flows.iter_mut().for_each(|flow| {
447                let render = flow.on_render();
448                render.set_pipelines(
449                    &self.ctx,
450                    &mut render_pass,
451                    &mut basics,
452                    &mut trans,
453                    &mut guis,
454                    &mut terrain,
455                    &mut customs,
456                );
457            });
458
459            render_pass.set_pipeline(&self.ctx.pipelines.basic);
460            for instanced in basics {
461                if instanced.amount == 0 {
462                    log::warn!("you attemted to render instances, nothing drawn to screen.");
463                    continue;
464                }
465                if instanced.instance.size() == 0 {
466                    log::warn!(
467                        "you attemted to draw an empty buffer, remember to call `write_to_buffer()` on your models."
468                    );
469                    continue;
470                }
471                // TODO: introduce as new render category
472                if let wgpu::FrontFace::Cw = instanced.front_face {
473                    render_pass.set_pipeline(&self.ctx.pipelines.basic_cw);
474                    render_pass.set_vertex_buffer(1, instanced.instance.slice(..));
475                    render_pass.draw_model_instanced(
476                        &instanced.model,
477                        0..instanced.amount as u32,
478                        &self.ctx.camera.bind_group,
479                        &self.ctx.light.bind_group,
480                    );
481                    render_pass.set_pipeline(&self.ctx.pipelines.basic);
482                    continue;
483                }
484                render_pass.set_vertex_buffer(1, instanced.instance.slice(..));
485                render_pass.draw_model_instanced(
486                    &instanced.model,
487                    0..instanced.amount as u32,
488                    &self.ctx.camera.bind_group,
489                    &self.ctx.light.bind_group,
490                );
491            }
492
493            render_pass.set_pipeline(&self.ctx.pipelines.terrain);
494            for button in terrain {
495                render_pass.set_vertex_buffer(1, button.instance.slice(..));
496                render_pass.set_bind_group(0, button.group, &[]);
497                render_pass.set_bind_group(1, &self.ctx.camera.bind_group, &[]);
498                render_pass.set_bind_group(2, &self.ctx.light.bind_group, &[]);
499                render_pass.set_vertex_buffer(0, button.vertex.slice(..));
500                render_pass.set_index_buffer(button.index.slice(..), wgpu::IndexFormat::Uint16);
501                render_pass.draw_indexed(0..button.amount as u32, 0, 0..1);
502            }
503
504            render_pass.set_pipeline(&self.ctx.pipelines.transparent);
505            for instanced in trans {
506                if instanced.amount == 0 {
507                    log::warn!("you attemted to render instances, nothing drawn to screen.");
508                    continue;
509                }
510                if instanced.instance.size() == 0 {
511                    log::warn!(
512                        "you attemted to draw an empty buffer, remember to call `write_to_buffer()` on your models."
513                    );
514                    continue;
515                }
516                render_pass.set_vertex_buffer(1, instanced.instance.slice(..));
517                render_pass.draw_model_instanced(
518                    &instanced.model,
519                    0..instanced.amount as u32,
520                    &self.ctx.camera.bind_group,
521                    &self.ctx.light.bind_group,
522                );
523            }
524
525            render_pass.set_pipeline(&self.ctx.pipelines.gui);
526            render_pass.set_bind_group(1, &self.ctx.screen_size.bind_group, &[]);
527            for button in guis {
528                render_pass.set_bind_group(0, button.group, &[]);
529                render_pass.set_vertex_buffer(0, button.vertex.slice(..));
530                render_pass.set_index_buffer(button.index.slice(..), wgpu::IndexFormat::Uint16);
531                render_pass.draw_indexed(0..button.amount as u32, 0, 0..1);
532            }
533
534            for custom in customs {
535                custom(&self.ctx, &mut render_pass);
536            }
537        }
538
539        #[cfg(feature = "integration-tests")]
540        let output_buffer = {
541            let u32_size = std::mem::size_of::<u32>() as u32;
542            let (width, height) = self.get_with_height();
543            let output_buffer_size = (u32_size * (width) * (height)) as wgpu::BufferAddress;
544            let output_buffer_desc = wgpu::BufferDescriptor {
545                size: output_buffer_size,
546                usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
547                label: None,
548                mapped_at_creation: false,
549            };
550            let output_buffer = self.ctx.device.create_buffer(&output_buffer_desc);
551            encoder.copy_texture_to_buffer(
552                wgpu::TexelCopyTextureInfo {
553                    aspect: wgpu::TextureAspect::All,
554                    texture: &tex,
555                    mip_level: 0,
556                    origin: wgpu::Origin3d::ZERO,
557                },
558                wgpu::TexelCopyBufferInfo {
559                    buffer: &output_buffer,
560                    layout: wgpu::TexelCopyBufferLayout {
561                        offset: 0,
562                        bytes_per_row: Some(u32_size * (width)),
563                        rows_per_image: Some(height),
564                    },
565                },
566                self.get_test_3d_extent(),
567            );
568            output_buffer
569        };
570
571        self.ctx.queue.submit(iter::once(encoder.finish()));
572
573        #[cfg(feature = "integration-tests")]
574        let fut_img = async {
575            let (tx, rx) = futures_intrusive::channel::shared::oneshot_channel();
576            let buffer_slice = output_buffer.slice(..);
577            buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
578                tx.send(result).unwrap();
579            });
580            self.ctx
581                .device
582                .poll(wgpu::PollType::Wait {
583                    submission_index: None,
584                    timeout: Some(Duration::from_secs(3)),
585                })
586                .unwrap();
587            rx.receive().await.unwrap().unwrap();
588            let data = buffer_slice.get_mapped_range();
589            let (width, height) = self.get_with_height();
590            let buffer =
591                image::ImageBuffer::<image::Rgba<u8>, _>::from_raw(width, height, data).unwrap();
592            buffer
593        };
594        #[cfg(feature = "integration-tests")]
595        {
596            use std::convert::identity;
597
598            let mut img: image::ImageBuffer<image::Rgba<u8>, wgpu::BufferView> =
599                async_runtime.block_on(fut_img);
600            let state = &mut self.state;
601            let all_passed = graphics_flows
602                .iter_mut()
603                .map(|flow| flow.render_to_texture(&self.ctx, state, &mut img))
604                .map(|res| match res {
605                    Err(e) => panic!("{}", e),
606                    Ok(ImageTestResult::Passed) => true,
607                    Ok(ImageTestResult::Failed) => panic!("Assertion failed"),
608                    Ok(ImageTestResult::Waiting) => false,
609                })
610                .all(identity);
611            if all_passed {
612                event_loop
613                    .send_event(FlowEvent::Exit)
614                    .expect("All assertions passed but the winit event-loop could not safely exit")
615            }
616        }
617
618        output.present();
619        Ok(())
620    }
621}
622
623pub struct App<State: 'static, Event: 'static> {
624    #[cfg(not(target_arch = "wasm32"))]
625    async_runtime: tokio::runtime::Runtime,
626    proxy: winit::event_loop::EventLoopProxy<FlowEvent<State, Event>>,
627    state: Option<AppState<State>>,
628    // This will hold the fully initialized flows once they are ready.
629    graphics_flows: Vec<Box<dyn GraphicsFlow<State, Event>>>,
630    // This holds the constructors at the star.
631    // We use Option to `take()` it after use.
632    constructors: Option<Vec<FlowConstructor<State, Event>>>,
633    last_time: Instant,
634    time_since_tick: Duration,
635}
636
637impl<'a, State, Event> App<State, Event>
638where
639    State: 'static,
640    Event: 'static,
641{
642    fn new(
643        event_loop: &EventLoop<FlowEvent<State, Event>>,
644        constructors: Vec<FlowConstructor<State, Event>>,
645    ) -> Self {
646        let proxy = event_loop.create_proxy();
647        #[cfg(not(target_arch = "wasm32"))]
648        let async_runtime = tokio::runtime::Runtime::new().unwrap();
649        Self {
650            #[cfg(not(target_arch = "wasm32"))]
651            async_runtime,
652            proxy,
653            state: None,
654            graphics_flows: Vec::new(),
655            constructors: Some(constructors),
656            last_time: Instant::now(),
657            time_since_tick: Duration::from_millis(0),
658        }
659    }
660}
661
662pub(crate) enum FlowEvent<State: 'static, Event: 'static> {
663    #[cfg(target_arch = "wasm32")]
664    Initialized {
665        state: AppState<State>,
666        flows: Vec<Box<dyn GraphicsFlow<State, Event>>>,
667    },
668    #[allow(dead_code)]
669    Id((u32, HashSet<usize>)),
670    #[allow(dead_code)]
671    Mut(Box<dyn FnOnce(&mut State) + Send>),
672    #[allow(dead_code)]
673    Custom(Event),
674    #[allow(dead_code)]
675    Exit,
676}
677impl<State, Event> Debug for FlowEvent<State, Event> {
678    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
679        match self {
680            #[cfg(target_arch = "wasm32")]
681            Self::Initialized { state: _, flows } => {
682                f.debug_struct("Initialized").field("flows", flows).finish()
683            }
684            Self::Id(arg0) => f.debug_tuple("Id").field(arg0).finish(),
685            Self::Mut(_) => f.write_str("Mut(|&mut State| -> {...})"),
686            Self::Custom(_) => f.write_str("Custom(E)"),
687            Self::Exit => f.write_str("Exit"),
688        }
689    }
690}
691
692impl<State: 'static + Default, Event: Send + 'static> ApplicationHandler<FlowEvent<State, Event>>
693    for App<State, Event>
694{
695    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
696        #[allow(unused_mut)]
697        let mut window_attributes = Window::default_attributes();
698
699        #[cfg(target_arch = "wasm32")]
700        {
701            use wasm_bindgen::JsCast;
702            use winit::platform::web::WindowAttributesExtWebSys;
703
704            const CANVAS_ID: &str = "canvas";
705
706            let window = wgpu::web_sys::window().unwrap_throw();
707            let document = window.document().unwrap_throw();
708            let canvas = document.get_element_by_id(CANVAS_ID).unwrap_throw();
709            let html_canvas_element = canvas.unchecked_into();
710            window_attributes = window_attributes.with_canvas(Some(html_canvas_element));
711        }
712
713        let window = Arc::new(event_loop.create_window(window_attributes).unwrap());
714
715        let constructors = self.constructors.take().unwrap();
716
717        let init_future = async move {
718            let app_state = AppState::new(window).await;
719
720            let flow_futures: Vec<_> = constructors
721                .into_iter()
722                // The clone in into() leverages the internal Arcs of Device and Queue and thus only clones the ref
723                .map(|constructor| constructor((&app_state.ctx).into()))
724                .collect();
725            let flows: Vec<_> = futures::future::join_all(flow_futures).await;
726            (app_state, flows)
727        };
728
729        #[cfg(not(target_arch = "wasm32"))]
730        {
731            let (mut app_state, flows) = self.async_runtime.block_on(init_future);
732            self.graphics_flows = flows;
733            self.graphics_flows.iter_mut().for_each(|flow| {
734                let events = flow.on_init(&mut app_state.ctx, &mut app_state.state);
735                let proxy = self.proxy.clone();
736                handle_flow_output(
737                    &self.async_runtime,
738                    &mut app_state.state,
739                    &mut app_state.ctx,
740                    proxy,
741                    events,
742                );
743            });
744            self.state = Some(app_state);
745        }
746
747        #[cfg(target_arch = "wasm32")]
748        {
749            let proxy = self.proxy.clone();
750            wasm_bindgen_futures::spawn_local(async move {
751                let (app_state, flows) = init_future.await;
752                assert!(
753                    proxy
754                        .send_event(FlowEvent::Initialized {
755                            state: app_state,
756                            flows,
757                        })
758                        .is_ok()
759                );
760            });
761        }
762    }
763
764    #[allow(unused_mut)]
765    fn user_event(&mut self, event_loop: &ActiveEventLoop, mut event: FlowEvent<State, Event>) {
766        match event {
767            #[cfg(target_arch = "wasm32")]
768            FlowEvent::Initialized { state, flows } => {
769                // This is the message from our wasm `spawn_local`
770                self.state = Some(state);
771                self.graphics_flows = flows;
772
773                // Important: Trigger a resize and redraw now that we are initialized
774                let app_state = self.state.as_mut().unwrap();
775                let size = app_state.ctx.window.inner_size();
776                app_state.resize(size.width, size.height);
777                self.graphics_flows.iter_mut().for_each(|flow| {
778                    let events = flow.on_init(&mut app_state.ctx, &mut app_state.state);
779                    let proxy = self.proxy.clone();
780                    handle_flow_output(
781                        #[cfg(not(target_arch = "wasm32"))]
782                        &self.async_runtime,
783                        &mut app_state.state,
784                        &mut app_state.ctx,
785                        proxy,
786                        events,
787                    );
788                });
789                app_state.ctx.window.request_redraw();
790            }
791            FlowEvent::Id((pick_id, flow_ids)) => {
792                if let Some(state) = &mut self.state {
793                    state.ctx.mouse.toggle(PickId(pick_id));
794                    flow_ids.into_iter().for_each(|flow_id| {
795                        self.graphics_flows
796                            .get_mut(flow_id)
797                            .map(|flow| flow.on_click(&state.ctx, &mut state.state, PickId(pick_id)));
798                    });
799                }
800            }
801            FlowEvent::Custom(custom_event) => {
802                if let Some(state) = &mut self.state {
803                    let result = self
804                        .graphics_flows
805                        .iter_mut()
806                        .fold(Some(custom_event), |event, flow| {
807                            flow.on_custom_events(&state.ctx, &mut state.state, event?)
808                        });
809                    if result.is_some() {
810                        log::warn!("Warning! Custom event was not consumed this cycle");
811                    }
812                }
813            }
814            FlowEvent::Mut(fn_once) => {
815                if let Some(state) = &mut self.state {
816                    fn_once(&mut state.state);
817                }
818            }
819            FlowEvent::Exit => {
820                event_loop.exit();
821            }
822        }
823    }
824
825    fn device_event(
826        &mut self,
827        _event_loop: &ActiveEventLoop,
828        _device_id: DeviceId,
829        event: DeviceEvent,
830    ) {
831        let state = match &mut self.state {
832            Some(state) => state,
833            None => return,
834        };
835        if let DeviceEvent::MouseMotion { delta: (dx, dy) } = event {
836            // TODO: make the below pattern/factor configurable
837            let speed_factor = 5.0;
838            if let MouseButtonState::Right = state.ctx.mouse.pressed {
839                state
840                    .ctx
841                    .camera
842                    .controller
843                    .handle_mouse(dx * speed_factor, dy * speed_factor);
844            }
845        }
846        self.graphics_flows.iter_mut().for_each(|f| {
847            let events = f.on_device_events(&state.ctx, &mut state.state, &event);
848            let proxy = self.proxy.clone();
849            handle_flow_output(
850                #[cfg(not(target_arch = "wasm32"))]
851                &self.async_runtime,
852                &mut state.state,
853                &mut state.ctx,
854                proxy,
855                events,
856            );
857        });
858    }
859
860    fn window_event(
861        &mut self,
862        event_loop: &ActiveEventLoop,
863        _window_id: winit::window::WindowId,
864        event: WindowEvent,
865    ) {
866        let state = match &mut self.state {
867            Some(state) => state,
868            None => return,
869        };
870
871        // general stuff
872        state.ctx.camera.controller.handle_window_events(&event);
873
874        if let WindowEvent::CursorMoved {
875            device_id: _,
876            position,
877        } = event
878        {
879            state.ctx.mouse.coords = position;
880        };
881
882        // Update config before dispatching so components see current dimensions.
883        if let WindowEvent::Resized(size) = event {
884            state.resize(size.width, size.height);
885        }
886
887        self.graphics_flows.iter_mut().for_each(|f| {
888            let events = f.on_window_events(&state.ctx, &mut state.state, &event);
889            let proxy = self.proxy.clone();
890            handle_flow_output(
891                #[cfg(not(target_arch = "wasm32"))]
892                &self.async_runtime,
893                &mut state.state,
894                &mut state.ctx,
895                proxy,
896                events,
897            );
898        });
899
900        match event {
901            WindowEvent::CloseRequested => event_loop.exit(),
902            WindowEvent::RedrawRequested => {
903                let dt = self.last_time.elapsed();
904                self.last_time = Instant::now();
905                self.time_since_tick += dt;
906
907                match state.render(
908                    &mut self.graphics_flows,
909                    #[cfg(feature = "integration-tests")]
910                    &self.async_runtime,
911                    #[cfg(feature = "integration-tests")]
912                    &self.proxy,
913                ) {
914                    Ok(_) => {
915                        if self.time_since_tick
916                            >= Duration::from_millis(state.ctx.tick_duration_millis)
917                        {
918                            self.graphics_flows.iter_mut().for_each(|f| {
919                                let events = f.on_tick(&state.ctx, &mut state.state);
920                                let proxy = self.proxy.clone();
921                                handle_flow_output(
922                                    #[cfg(not(target_arch = "wasm32"))]
923                                    &self.async_runtime,
924                                    &mut state.state,
925                                    &mut state.ctx,
926                                    proxy,
927                                    events,
928                                );
929                            });
930                            self.time_since_tick = Duration::from_millis(0);
931                        }
932                        // Update the camera
933                        state
934                            .ctx
935                            .camera
936                            .controller
937                            .update(&mut state.ctx.camera.camera, dt);
938                        state
939                            .ctx
940                            .camera
941                            .uniform
942                            .update_view_proj(&state.ctx.camera.camera, &state.ctx.projection);
943                        state.ctx.queue.write_buffer(
944                            &state.ctx.camera.buffer,
945                            0,
946                            bytemuck::cast_slice(&[state.ctx.camera.uniform]),
947                        );
948                        // Update the light
949                        let old_position: cgmath::Vector3<_> =
950                            state.ctx.light.uniform.position.into();
951                        state.ctx.light.uniform.position = (cgmath::Quaternion::from_axis_angle(
952                            (0.0, 1.0, 0.0).into(),
953                            cgmath::Deg(2.0 * dt.as_secs_f32()),
954                        ) * old_position)
955                            .into();
956                        // Update custom stuff
957                        self.graphics_flows.iter_mut().for_each(|f| {
958                            let events = f.on_update(&state.ctx, &mut state.state, dt);
959                            let proxy = self.proxy.clone();
960                            handle_flow_output(
961                                #[cfg(not(target_arch = "wasm32"))]
962                                &self.async_runtime,
963                                &mut state.state,
964                                &mut state.ctx,
965                                proxy,
966                                events,
967                            );
968                        });
969                    }
970                    // Reconfigure the surface if it's lost or outdated
971                    Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
972                        let size = state.ctx.window.inner_size();
973                        state.resize(size.width, size.height);
974                    }
975                    Err(e) => {
976                        log::error!("Unable to render {}", e);
977                    }
978                }
979            }
980            WindowEvent::MouseInput {
981                state: button_state,
982                button,
983                ..
984            } => {
985                if let Some(state) = &mut self.state {
986                    match (button, button_state.is_pressed()) {
987                        (MouseButton::Left, true) => {
988                            state.ctx.mouse.pressed = MouseButtonState::Left;
989                            if let Some((pick_id, flow_ids)) = draw_to_pick_buffer::<State, Event>(
990                                #[cfg(not(target_arch = "wasm32"))]
991                                &self.async_runtime,
992                                &mut self.graphics_flows,
993                                &state.ctx,
994                                &state.ctx.mouse,
995                                #[cfg(target_arch = "wasm32")]
996                                self.proxy.clone(),
997                            ) {
998                                flow_ids.clone().into_iter().for_each(|flow_id| {
999                                    self.graphics_flows.get_mut(flow_id).map(|flow| {
1000                                        let events =
1001                                            flow.on_click(&state.ctx, &mut state.state, PickId(pick_id));
1002                                        let proxy = self.proxy.clone();
1003                                        handle_flow_output(
1004                                            #[cfg(not(target_arch = "wasm32"))]
1005                                            &self.async_runtime,
1006                                            &mut state.state,
1007                                            &mut state.ctx,
1008                                            proxy,
1009                                            events,
1010                                        );
1011                                    });
1012                                });
1013                                state.ctx.mouse.toggle(PickId(pick_id));
1014                                if flow_ids.len() > 1 {
1015                                    log::warn!(
1016                                        "Multiple flows (incides {:?}) want to react to the render ID {}.",
1017                                        flow_ids,
1018                                        pick_id
1019                                    );
1020                                }
1021                            }
1022                        }
1023                        (MouseButton::Right, true) => {
1024                            state.ctx.mouse.pressed = MouseButtonState::Right;
1025                        }
1026                        (_, false) => state.ctx.mouse.pressed = MouseButtonState::None,
1027                        _ => (),
1028                    }
1029                }
1030            }
1031            _ => {}
1032        }
1033    }
1034}
1035
1036fn handle_flow_output<State, Event: Send>(
1037    #[cfg(not(target_arch = "wasm32"))] async_runtime: &tokio::runtime::Runtime,
1038    state: &mut State,
1039    ctx: &mut Context,
1040    proxy: winit::event_loop::EventLoopProxy<FlowEvent<State, Event>>,
1041    out: Out<State, Event>,
1042) {
1043    match out {
1044        // Send the events passed by the user to winit
1045        Out::FutEvent(futures) => {
1046            let fut =
1047                async move { futures::future::join_all(futures.into_iter().map(Pin::from)).await };
1048            #[cfg(not(target_arch = "wasm32"))]
1049            {
1050                async_runtime.spawn(async move {
1051                    let resolved = fut.await;
1052                    resolved.into_iter().for_each(|event| {
1053                        let err = proxy.send_event(FlowEvent::Custom(event));
1054                        if let Err(err) = err {
1055                            log::error!("{}", err);
1056                            panic!("Event loop was cloesed before all events could be processed.")
1057                        }
1058                    });
1059                });
1060            }
1061
1062            #[cfg(target_arch = "wasm32")]
1063            {
1064                wasm_bindgen_futures::spawn_local(async move {
1065                    let resolved = fut.await;
1066                    for event in resolved {
1067                        assert!(proxy.send_event(FlowEvent::Custom(event)).is_ok());
1068                    }
1069                });
1070            }
1071        }
1072        // Mutate the state if the arch supports async, create an event otherwise
1073        Out::FutFn(futures) => {
1074            let events: Vec<Pin<Box<dyn Future<Output = Box<dyn FnOnce(&mut State)>>>>> =
1075                futures.into_iter().map(Pin::from).collect();
1076            let fut = async move { futures::future::join_all(events.into_iter()).await };
1077            #[cfg(not(target_arch = "wasm32"))]
1078            {
1079                let resolved: Vec<Box<dyn FnOnce(&mut State)>> = async_runtime.block_on(fut);
1080                resolved.into_iter().for_each(|mutation| {
1081                    mutation(state);
1082                });
1083            }
1084
1085            #[cfg(target_arch = "wasm32")]
1086            {
1087                wasm_bindgen_futures::spawn_local(async move {
1088                    let resolved = fut.await;
1089                    for mutation in resolved {
1090                        assert!(proxy.send_event(FlowEvent::Mut(mutation)).is_ok());
1091                    }
1092                });
1093            }
1094        }
1095        Out::Configure(f) => f(ctx),
1096        Out::Composed(outs) => {
1097            for out in outs {
1098                handle_flow_output(
1099                    #[cfg(not(target_arch = "wasm32"))]
1100                    async_runtime,
1101                    state,
1102                    ctx,
1103                    proxy.clone(),
1104                    out,
1105                );
1106            }
1107        }
1108        Out::Empty => (),
1109    }
1110}
1111
1112pub fn run<State: 'static + Default, Event: Send + 'static>(
1113    constructors: Vec<FlowConstructor<State, Event>>,
1114) -> anyhow::Result<()> {
1115    #[cfg(not(target_arch = "wasm32"))]
1116    {
1117        if let Err(e) = env_logger::try_init() {
1118            println!("Warning: Could not initialize logger: {}", e);
1119        };
1120    }
1121
1122    #[cfg(target_arch = "wasm32")]
1123    {
1124        console_log::init_with_level(log::Level::Info).unwrap_throw();
1125    }
1126
1127    #[cfg(all(feature = "integration-tests", target_os = "linux"))]
1128    let event_loop: EventLoop<FlowEvent<State, Event>> = {
1129        use winit::platform::wayland::EventLoopBuilderExtWayland;
1130
1131        winit::event_loop::EventLoop::with_user_event()
1132            .with_any_thread(true)
1133            .build()
1134            .expect("Failed to create an event loop")
1135    };
1136
1137    #[cfg(all(feature = "integration-tests", target_os = "windows"))]
1138    let event_loop: EventLoop<FlowEvent<State, Event>> = {
1139        use winit::platform::windows::EventLoopBuilderExtWindows;
1140
1141        winit::event_loop::EventLoop::with_user_event()
1142            .with_any_thread(true)
1143            .build()
1144            .expect("Failed to create an event loop")
1145    };
1146
1147    #[cfg(not(feature = "integration-tests"))]
1148    let event_loop: EventLoop<FlowEvent<State, Event>> = EventLoop::with_user_event().build()?;
1149
1150    let mut app: App<State, Event> = App::new(&event_loop, constructors);
1151
1152    event_loop.run_app(&mut app)?;
1153
1154    Ok(())
1155}