lambda/runtimes/
application.rs

1//! The application runtime is the default runtime for Lambda applications. It
2//! provides a window and a render context which can be used to render
3//! both 2D and 3D graphics to the screen.
4
5use std::time::Instant;
6
7use lambda_platform::winit::{
8  winit_exports::{
9    ElementState,
10    Event as WinitEvent,
11    MouseButton,
12    WindowEvent as WinitWindowEvent,
13  },
14  Loop,
15  LoopBuilder,
16};
17use logging;
18
19use crate::{
20  component::Component,
21  events::{
22    Button,
23    ComponentEvent,
24    Events,
25    Key,
26    Mouse,
27    RuntimeEvent,
28    WindowEvent,
29  },
30  render::{
31    window::{
32      Window,
33      WindowBuilder,
34    },
35    RenderContext,
36    RenderContextBuilder,
37  },
38  runtime::Runtime,
39};
40
41#[derive(Clone, Debug)]
42pub enum ComponentResult {
43  Success,
44  Failure,
45}
46
47pub struct ApplicationRuntimeBuilder {
48  app_name: String,
49  render_context_builder: RenderContextBuilder,
50  window_builder: WindowBuilder,
51  components: Vec<Box<dyn Component<ComponentResult, String>>>,
52}
53
54impl ApplicationRuntimeBuilder {
55  pub fn new(app_name: &str) -> Self {
56    return Self {
57      app_name: app_name.to_string(),
58      render_context_builder: RenderContextBuilder::new(app_name),
59      window_builder: WindowBuilder::new(),
60      components: Vec::new(),
61    };
62  }
63
64  /// Update the name of the LambdaKernel.
65  pub fn with_app_name(mut self, name: &str) -> Self {
66    self.app_name = name.to_string();
67    return self;
68  }
69
70  /// Configures the `RenderAPIBuilder` before the `RenderContext` is built
71  /// using a callback provided by the user. The renderer in it's default
72  /// state will be good enough for most applications, but if you need to
73  /// customize the renderer you can do so here.
74  pub fn with_renderer_configured_as(
75    mut self,
76    configuration: impl FnOnce(RenderContextBuilder) -> RenderContextBuilder,
77  ) -> Self {
78    self.render_context_builder = configuration(self.render_context_builder);
79    return self;
80  }
81
82  /// Configures the WindowBuilder before the Window is built using a callback
83  /// provided by the user. If you need to customize the window you can do so
84  /// here.
85  pub fn with_window_configured_as(
86    mut self,
87    configuration: impl FnOnce(WindowBuilder) -> WindowBuilder,
88  ) -> Self {
89    self.window_builder = configuration(self.window_builder);
90    return self;
91  }
92
93  /// Attach a component to the current runnable.
94  pub fn with_component<
95    T: Default + Component<ComponentResult, String> + 'static,
96  >(
97    self,
98    configure_component: impl FnOnce(Self, T) -> (Self, T),
99  ) -> Self {
100    let (mut kernel_builder, component) =
101      configure_component(self, T::default());
102    kernel_builder.components.push(Box::new(component));
103    return kernel_builder;
104  }
105
106  /// Builds an `ApplicationRuntime` equipped with windowing, an event loop, and a
107  /// component stack that allows components to be dynamically pushed into the
108  /// Kernel to receive events & render access.
109  pub fn build(self) -> ApplicationRuntime {
110    let name = self.app_name;
111    let mut event_loop = LoopBuilder::new().build();
112    let window = self.window_builder.build(&mut event_loop);
113
114    let component_stack = self.components;
115    let render_context = self.render_context_builder.build(&window);
116
117    return ApplicationRuntime {
118      name,
119      event_loop,
120      window,
121      render_context,
122      component_stack,
123    };
124  }
125}
126
127/// A windowed and event-driven runtime that can be used to render a
128/// scene on the primary GPU across Windows, MacOS, and Linux.
129pub struct ApplicationRuntime {
130  name: String,
131  event_loop: Loop<Events>,
132  window: Window,
133  component_stack: Vec<Box<dyn Component<ComponentResult, String>>>,
134  render_context: RenderContext,
135}
136
137impl ApplicationRuntime {}
138
139impl Runtime<(), String> for ApplicationRuntime {
140  type Component = Box<dyn Component<ComponentResult, String>>;
141  /// Runs the event loop for the Application Runtime which takes ownership
142  /// of all components, the windowing the render context, and anything
143  /// else relevant to the runtime.
144  fn run(self) -> Result<(), String> {
145    // Decompose Runtime components to transfer ownership from the runtime to
146    // the event loop closure which will run until the app is closed.
147    let ApplicationRuntime {
148      window,
149      mut event_loop,
150      mut component_stack,
151      name,
152      render_context,
153    } = self;
154
155    let mut active_render_context = Some(render_context);
156
157    let publisher = event_loop.create_event_publisher();
158    publisher.publish_event(Events::Runtime {
159      event: RuntimeEvent::Initialized,
160      issued_at: Instant::now(),
161    });
162
163    let mut current_frame = Instant::now();
164    let mut runtime_result: Box<Result<(), String>> = Box::new(Ok(()));
165
166    event_loop.run_forever(move |event, _, control_flow| {
167      let mapped_event: Option<Events> = match event {
168        WinitEvent::WindowEvent { event, .. } => match event {
169          WinitWindowEvent::CloseRequested => {
170            // Issue a Shutdown event to deallocate resources and clean up.
171            control_flow.set_exit();
172            Some(Events::Runtime {
173              event: RuntimeEvent::Shutdown,
174              issued_at: Instant::now(),
175            })
176          }
177          WinitWindowEvent::Resized(dims) => {
178            active_render_context
179              .as_mut()
180              .unwrap()
181              .resize(dims.width, dims.height);
182
183            Some(Events::Window {
184              event: WindowEvent::Resize {
185                width: dims.width,
186                height: dims.height,
187              },
188              issued_at: Instant::now(),
189            })
190          }
191          WinitWindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
192            active_render_context
193              .as_mut()
194              .unwrap()
195              .resize(new_inner_size.width, new_inner_size.height);
196
197            Some(Events::Window {
198              event: WindowEvent::Resize {
199                width: new_inner_size.width,
200                height: new_inner_size.height,
201              },
202              issued_at: Instant::now(),
203            })
204          }
205          WinitWindowEvent::Moved(_) => None,
206          WinitWindowEvent::Destroyed => None,
207          WinitWindowEvent::DroppedFile(_) => None,
208          WinitWindowEvent::HoveredFile(_) => None,
209          WinitWindowEvent::HoveredFileCancelled => None,
210          WinitWindowEvent::ReceivedCharacter(_) => None,
211          WinitWindowEvent::Focused(_) => None,
212          WinitWindowEvent::KeyboardInput {
213            device_id: _,
214            input,
215            is_synthetic,
216          } => match (input.state, is_synthetic) {
217            (ElementState::Pressed, false) => Some(Events::Keyboard {
218              event: Key::Pressed {
219                scan_code: input.scancode,
220                virtual_key: input.virtual_keycode,
221              },
222              issued_at: Instant::now(),
223            }),
224            (ElementState::Released, false) => Some(Events::Keyboard {
225              event: Key::Released {
226                scan_code: input.scancode,
227                virtual_key: input.virtual_keycode,
228              },
229              issued_at: Instant::now(),
230            }),
231            _ => {
232              logging::warn!("Unhandled synthetic keyboard event: {:?}", input);
233              None
234            }
235          },
236          WinitWindowEvent::ModifiersChanged(_) => None,
237          WinitWindowEvent::CursorMoved {
238            device_id,
239            position,
240            modifiers,
241          } => Some(Events::Mouse {
242            event: Mouse::Moved {
243              x: position.x,
244              y: position.y,
245              dx: 0.0,
246              dy: 0.0,
247              device_id: 0,
248            },
249            issued_at: Instant::now(),
250          }),
251          WinitWindowEvent::CursorEntered { device_id } => {
252            Some(Events::Mouse {
253              event: Mouse::EnteredWindow { device_id: 0 },
254              issued_at: Instant::now(),
255            })
256          }
257          WinitWindowEvent::CursorLeft { device_id } => Some(Events::Mouse {
258            event: Mouse::LeftWindow { device_id: 0 },
259            issued_at: Instant::now(),
260          }),
261          WinitWindowEvent::MouseWheel {
262            device_id,
263            delta,
264            phase,
265            modifiers,
266          } => Some(Events::Mouse {
267            event: Mouse::Scrolled { device_id: 0 },
268            issued_at: Instant::now(),
269          }),
270          WinitWindowEvent::MouseInput {
271            device_id,
272            state,
273            button,
274            modifiers,
275          } => {
276            // Map winit button to our button type
277            let button = match button {
278              MouseButton::Left => Button::Left,
279              MouseButton::Right => Button::Right,
280              MouseButton::Middle => Button::Middle,
281              MouseButton::Other(other) => Button::Other(other),
282            };
283
284            let event = match state {
285              ElementState::Pressed => Mouse::Pressed {
286                button,
287                x: 0.0,
288                y: 0.0,
289                device_id: 0,
290              },
291              ElementState::Released => Mouse::Released {
292                button,
293                x: 0.0,
294                y: 0.0,
295                device_id: 0,
296              },
297            };
298
299            Some(Events::Mouse {
300              event,
301              issued_at: Instant::now(),
302            })
303          }
304          WinitWindowEvent::TouchpadPressure {
305            device_id,
306            pressure,
307            stage,
308          } => None,
309          WinitWindowEvent::AxisMotion {
310            device_id,
311            axis,
312            value,
313          } => None,
314          WinitWindowEvent::Touch(_) => None,
315          WinitWindowEvent::ThemeChanged(_) => None,
316          _ => None,
317        },
318        WinitEvent::MainEventsCleared => {
319          let last_frame = current_frame.clone();
320          current_frame = Instant::now();
321          let duration = &current_frame.duration_since(last_frame);
322
323          let active_render_context = active_render_context
324            .as_mut()
325            .expect("Couldn't get the active render context. ");
326          for component in &mut component_stack {
327            component.on_update(duration);
328            let commands = component.on_render(active_render_context);
329            active_render_context.render(commands);
330          }
331
332          // Warn if frames dropped below 32 ms (30 fps).
333          match duration.as_millis() > 32 {
334            true => {
335              logging::warn!(
336                "Frame took too long to render: {:?} ms",
337                duration.as_millis()
338              );
339            }
340            false => {
341              // Disable until frametimes can be determined via monitor
342              // std::thread::sleep(std::time::Duration::from_millis(16 - duration.as_millis() as u64));
343            }
344          }
345
346          None
347        }
348        WinitEvent::RedrawRequested(_) => None,
349        WinitEvent::NewEvents(_) => None,
350        WinitEvent::DeviceEvent { device_id, event } => None,
351        WinitEvent::UserEvent(lambda_event) => match lambda_event {
352          Events::Runtime { event, issued_at } => match event {
353            RuntimeEvent::Initialized => {
354              logging::debug!(
355                "Initializing all of the components for the runtime: {}",
356                name
357              );
358              for component in &mut component_stack {
359                component.on_attach(active_render_context.as_mut().unwrap());
360              }
361              None
362            }
363            RuntimeEvent::Shutdown => {
364              for component in &mut component_stack {
365                component.on_detach(active_render_context.as_mut().unwrap());
366              }
367              *runtime_result = Ok(());
368              None
369            }
370            RuntimeEvent::ComponentPanic { message } => {
371              *runtime_result = Err(message);
372              None
373            }
374          },
375          _ => None,
376        },
377        WinitEvent::Suspended => None,
378        WinitEvent::Resumed => None,
379        WinitEvent::RedrawEventsCleared => None,
380        WinitEvent::LoopDestroyed => {
381          active_render_context
382            .take()
383            .expect("[ERROR] The render API has been already taken.")
384            .destroy();
385
386          logging::info!("All resources were successfully deleted.");
387          None
388        }
389      };
390
391      match mapped_event {
392        Some(event) => {
393          logging::trace!("Sending event: {:?} to all components", event);
394
395          for component in &mut component_stack {
396            let event_result = component.on_event(event.clone());
397            match event_result {
398              Ok(_) => {}
399              Err(e) => {
400                let error = format!(
401                  "A component has panicked while handling an event. {:?}",
402                  e
403                );
404                logging::error!(
405                  "A component has panicked while handling an event. {:?}",
406                  e
407                );
408                publisher.publish_event(Events::Runtime {
409                  event: RuntimeEvent::ComponentPanic { message: error },
410                  issued_at: Instant::now(),
411                });
412              }
413            }
414          }
415        }
416        None => {}
417      }
418    });
419    return Ok(());
420  }
421
422  /// When an application runtime starts, it will attach all of the components that
423  /// have been added during the construction phase in the users code.
424  fn on_start(&mut self) {
425    logging::info!("Starting the runtime: {}", self.name);
426  }
427
428  fn on_stop(&mut self) {
429    logging::info!("Stopping the runtime: {}", self.name);
430  }
431}