1//! [`Renderer`] implementation that delegates to another thread to perform the actual rendering
2//!
3//! When [`ThreadedRenderer::render_frame`] is called, blocks until the other thread acknowledges
4//! that it has completed rendering
5
6use jgenesis_common::frontend::{Color, FrameSize, RenderFrameOptions, Renderer};
7use std::slice;
8use std::sync::mpsc;
9use std::sync::mpsc::{Receiver, RecvTimeoutError, SyncSender};
10use std::time::Duration;
11use thiserror::Error;
12
13struct FrameMessage {
14    frame_buffer: *const Color,
15    frame_buffer_len: usize,
16    frame_size: FrameSize,
17    target_fps: f64,
18    options: RenderFrameOptions,
19}
20
21// SAFETY: This pointer-containing struct is only sent across threads in one place in this module,
22// and the struct is private so it cannot be used outside of this module
23unsafe impl Send for FrameMessage {}
24
25pub type DoneMessage = Result<(), ()>;
26
27#[derive(Debug, Error)]
28pub enum ThreadedRendererError {
29    #[error("Invalid frame buffer length {len} for size {width}x{height}")]
30    InvalidFrameBufferLen { len: usize, width: u32, height: u32 },
31    #[error("Lost connection to main thread")]
32    LostConnection,
33    #[error("Error from underlying renderer")]
34    Render,
35}
36
37pub struct ThreadedRenderer {
38    frame_sender: SyncSender<FrameMessage>,
39    done_receiver: Receiver<DoneMessage>,
40}
41
42pub struct ThreadedRendererHandle {
43    frame_receiver: Receiver<FrameMessage>,
44    done_sender: SyncSender<DoneMessage>,
45}
46
47impl ThreadedRenderer {
48    pub fn new() -> (Self, ThreadedRendererHandle) {
49        let (frame_sender, frame_receiver) = mpsc::sync_channel(1);
50        let (done_sender, done_receiver) = mpsc::sync_channel(1);
51
52        let renderer = Self { frame_sender, done_receiver };
53
54        let handle = ThreadedRendererHandle { frame_receiver, done_sender };
55
56        (renderer, handle)
57    }
58}
59
60impl Renderer for ThreadedRenderer {
61    type Err = ThreadedRendererError;
62
63    fn render_frame(
64        &mut self,
65        frame_buffer: &[Color],
66        frame_size: FrameSize,
67        target_fps: f64,
68        options: RenderFrameOptions,
69    ) -> Result<(), Self::Err> {
70        let frame_len = frame_size.len() as usize;
71        if frame_len > frame_buffer.len() {
72            return Err(ThreadedRendererError::InvalidFrameBufferLen {
73                len: frame_len,
74                width: frame_size.width,
75                height: frame_size.height,
76            });
77        }
78
79        // SAFETY: This sends a frame buffer raw pointer to the main thread. This function must not
80        // return before the main thread has signaled that it is no longer using the frame buffer
81        let frame_message = FrameMessage {
82            frame_buffer: frame_buffer.as_ptr(),
83            frame_buffer_len: frame_buffer.len(),
84            frame_size,
85            target_fps,
86            options,
87        };
88        if self.frame_sender.send(frame_message).is_err() {
89            return Err(ThreadedRendererError::LostConnection);
90        }
91
92        match self.done_receiver.recv() {
93            Ok(Ok(())) => {}
94            Ok(Err(())) => return Err(ThreadedRendererError::Render),
95            Err(_) => return Err(ThreadedRendererError::LostConnection),
96        }
97
98        Ok(())
99    }
100}
101
102#[derive(Debug, Error)]
103pub enum RecvFrameError<RErr> {
104    #[error("recv error: {0}")]
105    Recv(#[from] RecvTimeoutError),
106    #[error("lost connection to other thread")]
107    LostConnection,
108    #[error("renderer error: {0}")]
109    Render(RErr),
110}
111
112impl ThreadedRendererHandle {
113    pub fn try_recv_frame<R: Renderer>(
114        &self,
115        renderer: &mut R,
116        timeout: Duration,
117    ) -> Result<(), RecvFrameError<R::Err>> {
118        let frame_message = self.frame_receiver.recv_timeout(timeout)?;
119
120        // SAFETY: The slice is reconstructed from raw parts sent by the runner thread. The main
121        // thread must not use the slice after it sends the done signal to the runner thread
122        unsafe {
123            let frame_buffer =
124                slice::from_raw_parts(frame_message.frame_buffer, frame_message.frame_buffer_len);
125
126            match renderer.render_frame(
127                frame_buffer,
128                frame_message.frame_size,
129                frame_message.target_fps,
130                frame_message.options,
131            ) {
132                Ok(()) => {
133                    self.done_sender.send(Ok(())).map_err(|_| RecvFrameError::LostConnection)?;
134                }
135                Err(err) => {
136                    let _ = self.done_sender.send(Err(()));
137                    return Err(RecvFrameError::Render(err));
138                }
139            }
140        }
141
142        Ok(())
143    }
144}