1use 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
52pub 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
92pub trait GraphicsFlow<S, E: Send> {
109 fn on_init(&mut self, _ctx: &mut Context, _state: &mut S) -> Out<S, E> {
114 Out::Empty
115 }
116
117 fn on_click(&mut self, _ctx: &Context, _state: &mut S, _id: PickId) -> Out<S, E> {
130 Out::Empty
131 }
132
133 fn on_update(&mut self, _ctx: &Context, _state: &mut S, _dt: Duration) -> Out<S, E> {
138 Out::Empty
139 }
140
141 fn on_tick(&mut self, _ctx: &Context, _state: &mut S) -> Out<S, E> {
146 Out::Empty
147 }
148
149 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 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 fn on_custom_events(&mut self, _ctx: &Context, _state: &mut S, event: E) -> Option<E> {
174 Some(event)
175 }
176
177 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
196impl<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
203pub type FlowConstructor<S, E> =
208 Box<dyn FnOnce(InitContext) -> Pin<Box<dyn Future<Output = Box<dyn GraphicsFlow<S, E>>>>>>;
209
210#[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 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 self.ctx.window.request_redraw();
337
338 if !self.is_surface_configured {
340 return Ok(());
341 }
342
343 let output = self.get_surface_texture();
344 #[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 #[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 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 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 graphics_flows: Vec<Box<dyn GraphicsFlow<State, Event>>>,
630 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 .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 self.state = Some(state);
771 self.graphics_flows = flows;
772
773 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 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 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 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 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 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 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 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 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 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}