[Renderer] implementation that delegates to another thread to perform the actual rendering

When [ThreadedRenderer::render_frame] is called, blocks until the other thread acknowledges that it has completed rendering

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;
13struct FrameMessage {
14    frame_buffer: *const Color,
15    frame_buffer_len: usize,
16    frame_size: FrameSize,
17    target_fps: f64,
18    options: RenderFrameOptions,
19}

SAFETY: This pointer-containing struct is only sent across threads in one place in this module, and the struct is private so it cannot be used outside of this module

23unsafe impl Send for FrameMessage {}
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}