use std::{ collections::{HashMap, hash_map::Entry}, ops::Deref, sync::{Arc, Mutex, Weak}, thread, time::Duration, }; use egui::{ Color32, ColorImage, TextureHandle, epaint::ImageDelta, generate_loader_id, load::{LoadError, SizedTexture, TextureLoader, TexturePoll}, }; use tokio::{sync::mpsc, time::timeout}; use crate::emulator::SimId; pub struct ImageTextureLoader { cache: Mutex>, sender: mpsc::UnboundedSender>, } impl ImageTextureLoader { pub fn new() -> Self { let (sender, receiver) = mpsc::unbounded_channel(); thread::spawn(move || { tokio::runtime::Builder::new_current_thread() .enable_all() .build() .unwrap() .block_on(async move { let mut worker = ImageTextureLoaderWorker { receiver, renderers: vec![], }; worker.run().await }) }); Self { cache: Mutex::new(HashMap::new()), sender, } } pub fn add + 'static>( &self, sim_id: SimId, renderer: R, params: R::Params, ) -> ImageParams { let states = renderer.sizes().map(ImageState::new); let images = renderer .sizes() .map(|[width, height]| ImageBuffer::new(width, height)); { let mut cache = self.cache.lock().unwrap(); for (name, state) in renderer.names().into_iter().zip(&states) { let url = format!("vip://{sim_id}/{name}"); let entry = ImageEntry { size: state.size.map(|i| i as f32), data: Arc::downgrade(&state.sink), texture: None, }; match cache.entry(url) { Entry::Vacant(e) => { e.insert(entry); } Entry::Occupied(mut e) => { // Only overwrite an old entry if that entry is dead. // Otherwise, we clear out the reference to a texture actually in use. if e.get().data.strong_count() == 0 { e.insert(entry); } } } } } let sink = Arc::new(Mutex::new(params.clone())); let _ = self.sender.send(Box::new(ImageRendererWrapper { renderer, params: Arc::downgrade(&sink), images, states, })); ImageParams { value: params, sink, } } } impl TextureLoader for ImageTextureLoader { fn id(&self) -> &str { generate_loader_id!(ImageTextureLoader2) } fn load( &self, ctx: &egui::Context, uri: &str, texture_options: egui::TextureOptions, _size_hint: egui::SizeHint, ) -> Result { let mut cache = self.cache.lock().unwrap(); let Some(entry) = cache.get_mut(uri) else { return Err(LoadError::NotSupported); }; if texture_options != egui::TextureOptions::NEAREST { return Err(LoadError::Loading( "Only TextureOptions::NEAREST are supported".into(), )); } let Some(data) = entry.data.upgrade() else { cache.remove(uri); return Err(LoadError::Loading("Backing loader no longer exists".into())); }; match (data.lock().unwrap().take(), entry.texture.as_ref()) { (Some(update), Some(handle)) => { let delta = ImageDelta::full(update, texture_options); ctx.tex_manager().write().set(handle.id(), delta); let texture = SizedTexture::new(handle, entry.size); Ok(TexturePoll::Ready { texture }) } (Some(update), None) => { let handle = ctx.load_texture(uri, update, texture_options); let texture = SizedTexture::new(&handle, entry.size); entry.texture.replace(handle); Ok(TexturePoll::Ready { texture }) } (None, Some(handle)) => { let texture = SizedTexture::new(handle, entry.size); Ok(TexturePoll::Ready { texture }) } (None, None) => { let size = entry.size.into(); Ok(TexturePoll::Pending { size: Some(size) }) } } } fn forget(&self, uri: &str) { let _ = uri; } fn forget_all(&self) {} fn byte_size(&self) -> usize { self.cache .lock() .unwrap() .values() .map(|entry| { let [width, height] = entry.size; width as usize * height as usize * 4 }) .sum() } } struct ImageTextureLoaderWorker { receiver: mpsc::UnboundedReceiver>, renderers: Vec>, } impl ImageTextureLoaderWorker { async fn run(&mut self) { loop { if self.renderers.is_empty() { // if we have nothing to do, block until we have something to do if self.receiver.recv_many(&mut self.renderers, 64).await == 0 { // shutdown return; } while let Ok(renderer) = self.receiver.try_recv() { self.renderers.push(renderer); } } self.renderers .retain_mut(|renderer| renderer.try_update().is_ok()); // wait up to 10 ms for more renderers if timeout( Duration::from_millis(10), self.receiver.recv_many(&mut self.renderers, 64), ) .await .is_ok() { while let Ok(renderer) = self.receiver.try_recv() { self.renderers.push(renderer); } } } } } pub struct ImageBuffer { pub size: [usize; 2], pub pixels: Vec, } impl ImageBuffer { pub fn new(width: usize, height: usize) -> Self { Self { size: [width, height], pixels: vec![Color32::BLACK; width * height], } } pub fn clear(&mut self) { for pixel in self.pixels.iter_mut() { *pixel = Color32::BLACK; } } pub fn write(&mut self, coords: (usize, usize), pixel: Color32) { self.pixels[coords.1 * self.size[0] + coords.0] = pixel; } pub fn add(&mut self, coords: (usize, usize), pixel: Color32) { let index = coords.1 * self.size[0] + coords.0; let old = self.pixels[index]; self.pixels[index] = Color32::from_rgb( old.r() + pixel.r(), old.g() + pixel.g(), old.b() + pixel.b(), ); } pub fn changed(&self, image: &ColorImage) -> bool { image.pixels.iter().zip(&self.pixels).any(|(a, b)| a != b) } pub fn read(&self, image: &mut ColorImage) { image.pixels.copy_from_slice(&self.pixels); } } struct ImageEntry { size: [f32; 2], data: Weak>>>, texture: Option, } pub struct ImageParams { value: T, sink: Arc>, } impl Deref for ImageParams { type Target = T; fn deref(&self) -> &Self::Target { &self.value } } impl ImageParams { pub fn write(&mut self, value: T) { if self.value != value { self.value = value.clone(); *self.sink.lock().unwrap() = value; } } } pub trait ImageRenderer: Send { type Params: Clone + Send; fn names(&self) -> [&str; N]; fn sizes(&self) -> [[usize; 2]; N]; fn render(&mut self, params: &Self::Params, images: &mut [ImageBuffer; N]); } #[derive(Clone)] struct ImageState { size: [usize; 2], buffers: [Arc; 2], last_buffer: usize, sink: Arc>>>, } impl ImageState { fn new(size: [usize; 2]) -> Self { let buffers = [ Arc::new(ColorImage::filled(size, Color32::BLACK)), Arc::new(ColorImage::filled(size, Color32::BLACK)), ]; let sink = buffers[0].clone(); Self { size, buffers, last_buffer: 0, sink: Arc::new(Mutex::new(Some(sink))), } } fn try_send_update(&mut self, image: &ImageBuffer) { let last = &self.buffers[self.last_buffer]; if !image.changed(last) { return; } let next_buffer = (self.last_buffer + 1) % self.buffers.len(); let next = &mut self.buffers[next_buffer]; image.read(Arc::make_mut(next)); self.last_buffer = next_buffer; self.sink.lock().unwrap().replace(next.clone()); } } struct ImageRendererWrapper> { renderer: R, params: Weak>, images: [ImageBuffer; N], states: [ImageState; N], } trait ImageRendererImpl: Send { fn try_update(&mut self) -> Result<(), ()>; } impl + Send> ImageRendererImpl for ImageRendererWrapper { fn try_update(&mut self) -> Result<(), ()> { let params = match self.params.upgrade() { Some(params) => params.lock().unwrap().clone(), None => { // the UI isn't using this anymore return Err(()); } }; self.renderer.render(¶ms, &mut self.images); for (state, image) in self.states.iter_mut().zip(&self.images) { state.try_send_update(image); } Ok(()) } }