devotee_backend_softbuffer/
lib.rs

1#![deny(missing_docs)]
2
3//! [Softbuffer](https://crates.io/crates/softbuffer)-based backend for the devotee project.
4
5use std::rc::Rc;
6use std::time::{Duration, Instant};
7
8use devotee_backend::Middleware;
9use devotee_backend::middling::EventContext;
10use winit::application::ApplicationHandler;
11use winit::dpi::{PhysicalPosition, PhysicalSize};
12use winit::error::{EventLoopError, OsError};
13use winit::event::{StartCause, WindowEvent};
14use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
15use winit::window::{Window, WindowAttributes, WindowId};
16
17use surface::ScaleMode;
18
19pub use surface::SoftSurface;
20
21mod surface;
22
23/// [Softbuffer](https://crates.io/crates/softbuffer)-based backend implementation for the devotee project.
24pub struct SoftBackend<M> {
25    middleware: M,
26    internal: Option<Internal>,
27    settings: Settings,
28    last: Instant,
29}
30
31impl<M> SoftBackend<M> {
32    /// Create new backend instance.
33    pub fn new(middleware: M) -> Self {
34        let internal = None;
35        let last = Instant::now();
36        let settings = Settings {
37            render_window_size: PhysicalSize::new(32, 32),
38            border_color: 0,
39            scale_mode: ScaleMode::Auto,
40            updates_per_seconds: 60.0,
41        };
42        Self {
43            middleware,
44            internal,
45            settings,
46            last,
47        }
48    }
49}
50
51impl<M> SoftBackend<M>
52where
53    for<'init, 'context, 'surface> M: Middleware<
54            SoftInit<'init>,
55            SoftContext<'context>,
56            SoftSurface<'surface>,
57            SoftEvent,
58            SoftEventContext,
59            (),
60        >,
61{
62    /// Run this backend to completion.
63    pub fn run(&mut self) -> Result<(), Error> {
64        let event_loop = EventLoop::new()?;
65
66        event_loop.set_control_flow(ControlFlow::WaitUntil(
67            Instant::now() + Duration::from_secs_f32(1.0 / self.settings.updates_per_seconds),
68        ));
69        event_loop.run_app(self)?;
70
71        Ok(())
72    }
73
74    fn init(&mut self, event_loop: &ActiveEventLoop) -> Result<(), Error> {
75        let window = Rc::new(event_loop.create_window(WindowAttributes::default())?);
76
77        let mut init = SoftInit {
78            window: &window,
79            settings: &mut self.settings,
80        };
81
82        window.set_visible(true);
83        self.middleware.on_init(&mut init);
84        window.set_min_inner_size(Some(self.settings.render_window_size));
85        let _ = window.request_inner_size(self.settings.render_window_size);
86
87        let context = softbuffer::Context::new(Rc::clone(&window))?;
88        let surface = softbuffer::Surface::new(&context, Rc::clone(&window))?;
89        let surface_size = window.inner_size();
90
91        let mut internal = Internal {
92            window,
93            surface,
94            surface_size,
95        };
96        let _ = internal.on_resize(surface_size);
97
98        self.internal = Some(internal);
99
100        Ok(())
101    }
102
103    fn handle_update(&mut self, event_loop: &ActiveEventLoop) {
104        let now = Instant::now();
105        if let Some(internal) = &self.internal {
106            let delta = now - self.last;
107
108            let mut control = SoftContext {
109                shutdown: false,
110                window: &internal.window,
111                delta,
112            };
113            self.middleware.on_update(&mut control);
114
115            internal.window.request_redraw();
116
117            if control.shutdown {
118                event_loop.exit();
119            }
120        }
121        self.last = now;
122        event_loop.set_control_flow(ControlFlow::WaitUntil(
123            now + Duration::from_secs_f32(1.0 / self.settings.updates_per_seconds),
124        ));
125    }
126}
127
128impl<M> ApplicationHandler for SoftBackend<M>
129where
130    for<'init, 'control, 'surface> M: Middleware<
131            SoftInit<'init>,
132            SoftContext<'control>,
133            SoftSurface<'surface>,
134            SoftEvent,
135            SoftEventContext,
136            (),
137        >,
138{
139    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
140        if self.internal.is_none() && self.init(event_loop).is_err() {
141            event_loop.exit();
142        }
143    }
144
145    fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
146        if let StartCause::ResumeTimeReached { .. } = cause {
147            self.handle_update(event_loop);
148        }
149    }
150
151    fn window_event(
152        &mut self,
153        event_loop: &ActiveEventLoop,
154        _window_id: WindowId,
155        event: WindowEvent,
156    ) {
157        if let Some(internal) = &mut self.internal {
158            let context = SoftEventContext::new(
159                internal.surface_size,
160                self.settings.scale_mode,
161                self.settings.render_window_size,
162            );
163
164            if let Some(event) = self.middleware.on_event(event, &context, &mut ()) {
165                match event {
166                    WindowEvent::Resized(physical_size) => {
167                        if let Some(internal) = &mut self.internal {
168                            internal.on_resize(physical_size);
169                        }
170                    }
171                    WindowEvent::CloseRequested => {
172                        event_loop.exit();
173                    }
174                    WindowEvent::Destroyed => {
175                        event_loop.exit();
176                    }
177                    WindowEvent::RedrawRequested => {
178                        if let Ok(buffer) = internal.surface.buffer_mut() {
179                            let mut surface = SoftSurface::new(
180                                buffer,
181                                internal.surface_size,
182                                self.settings.scale_mode,
183                                self.settings.render_window_size,
184                            );
185                            let _ = surface.clear(self.settings.border_color);
186                            self.middleware.on_render(&mut surface);
187                            let _ = surface.present();
188                        }
189                    }
190                    _ => {}
191                }
192            }
193        }
194    }
195}
196
197struct Settings {
198    render_window_size: PhysicalSize<u32>,
199    border_color: u32,
200    scale_mode: ScaleMode,
201    updates_per_seconds: f32,
202}
203
204impl Settings {
205    fn set_scale(&mut self, scale: u32) {
206        if let Ok(scale) = scale.try_into() {
207            self.scale_mode = ScaleMode::Fixed(scale);
208        } else {
209            self.scale_mode = ScaleMode::Auto;
210        }
211    }
212
213    /// # Panics
214    ///
215    /// Panics if `updates_per_second` is less or equal to `0`.
216    fn set_updated_per_second(&mut self, updates_per_second: f32) {
217        assert!(
218            updates_per_second > 0.0,
219            "Update rate has to be greater than 0"
220        );
221        self.updates_per_seconds = updates_per_second;
222    }
223}
224
225struct Internal {
226    window: Rc<Window>,
227    surface: softbuffer::Surface<Rc<Window>, Rc<Window>>,
228    surface_size: PhysicalSize<u32>,
229}
230
231impl Internal {
232    fn on_resize(&mut self, size: PhysicalSize<u32>) -> Option<()> {
233        self.surface
234            .resize(size.width.try_into().ok()?, size.height.try_into().ok()?)
235            .ok()?;
236
237        self.surface_size = size;
238
239        Some(())
240    }
241}
242
243/// An initialization argument passed to the application.
244pub struct SoftInit<'a> {
245    window: &'a Window,
246    settings: &'a mut Settings,
247}
248
249impl SoftInit<'_> {
250    /// Get internal `winit` window reference.
251    pub fn window(&self) -> &Window {
252        self.window
253    }
254
255    /// Set internal render scale.
256    /// If case of `0` `scale` value automatic scaling is used.
257    pub fn set_scale(&mut self, scale: u32) {
258        self.settings.set_scale(scale);
259    }
260
261    /// Set update framerate in updates per second count.
262    ///
263    /// # Panics
264    ///
265    /// Panics if `updates_per_second` is less or equal to `0`.
266    pub fn set_updates_per_second(&mut self, updates_per_second: f32) {
267        self.settings.set_updated_per_second(updates_per_second);
268    }
269
270    /// Set the internal render window size.
271    pub fn set_render_window_size(&mut self, width: u32, height: u32) {
272        self.settings.render_window_size = PhysicalSize::new(width, height);
273    }
274
275    /// Set the color of the border to be rendered.
276    pub fn set_border_color(&mut self, color: u32) {
277        self.settings.border_color = color;
278    }
279}
280
281/// An update argument passed to the application.
282pub struct SoftContext<'a> {
283    shutdown: bool,
284    window: &'a Window,
285    delta: Duration,
286}
287
288impl SoftContext<'_> {
289    /// Get reference to the underlying `winit` `Window` reference.
290    pub fn window(&self) -> &Window {
291        self.window
292    }
293
294    /// Tell the backend to shut itself down.
295    pub fn shutdown(&mut self) {
296        self.shutdown = true;
297    }
298
299    /// Get delta time estimation.
300    pub fn delta(&self) -> Duration {
301        self.delta
302    }
303}
304
305type SoftEvent = WindowEvent;
306
307/// A context passed to the event handler.
308pub struct SoftEventContext {
309    render_window: (PhysicalPosition<u32>, PhysicalSize<u32>),
310    scale: u32,
311}
312
313impl SoftEventContext {
314    fn new(
315        surface_size: PhysicalSize<u32>,
316        scale: ScaleMode,
317        render_window_size: PhysicalSize<u32>,
318    ) -> Self {
319        let (render_window_position, scale) =
320            surface::estimate_render_window_position_scale(surface_size, scale, render_window_size);
321        let render_window = (render_window_position, render_window_size);
322        Self {
323            render_window,
324            scale,
325        }
326    }
327}
328
329impl EventContext<PhysicalPosition<f64>> for SoftEventContext {
330    type SurfaceSpace = Option<PhysicalPosition<u32>>;
331
332    fn estimate_surface_space(&self, event_space: PhysicalPosition<f64>) -> Self::SurfaceSpace {
333        let PhysicalPosition { x, y } = event_space;
334        let (x, y) = (x as u32, y as u32);
335        if x > self.render_window.0.x && y > self.render_window.0.y {
336            let (x, y) = (
337                (x - self.render_window.0.x) / self.scale,
338                (y - self.render_window.0.y) / self.scale,
339            );
340            if x < self.render_window.1.width && y < self.render_window.1.height {
341                return Some(PhysicalPosition::new(x, y));
342            }
343        }
344        None
345    }
346}
347
348/// An error generalization.
349#[derive(Debug)]
350pub enum Error {
351    /// Winit event loop error.
352    WinitEventLoopError(EventLoopError),
353    /// Os error.
354    OsError(OsError),
355    /// SoftBuffer error.
356    SoftBufferError(softbuffer::SoftBufferError),
357}
358
359impl From<EventLoopError> for Error {
360    fn from(value: EventLoopError) -> Self {
361        Self::WinitEventLoopError(value)
362    }
363}
364
365impl From<OsError> for Error {
366    fn from(value: OsError) -> Self {
367        Self::OsError(value)
368    }
369}
370
371impl From<softbuffer::SoftBufferError> for Error {
372    fn from(value: softbuffer::SoftBufferError) -> Self {
373        Self::SoftBufferError(value)
374    }
375}