[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
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}