From e0e64f7e24acb9c90ce4b77a1baa046580f2c982 Mon Sep 17 00:00:00 2001 From: liquidev Date: Wed, 23 Oct 2024 20:46:42 +0200 Subject: [PATCH] implement auto-saving under reticles --- crates/rkgk/src/api/wall.rs | 117 +++++++++++++++++------------- crates/rkgk/src/wall/auto_save.rs | 13 ++-- 2 files changed, 75 insertions(+), 55 deletions(-) diff --git a/crates/rkgk/src/api/wall.rs b/crates/rkgk/src/api/wall.rs index d3aeb72..d36682d 100644 --- a/crates/rkgk/src/api/wall.rs +++ b/crates/rkgk/src/api/wall.rs @@ -324,34 +324,49 @@ impl SessionLoop { wall::EventKind::Interact { interactions } => { let (done_tx, done_rx) = oneshot::channel(); - if interactions - .iter() - .any(|i| matches!(i, Interaction::SetBrush { .. })) - { - // SetBrush is an important event, so we wait for the render thread - // to unload. - _ = self - .render_commands_tx - .send(RenderCommand::Interact { - interactions: interactions.clone(), - done: done_tx, - }) - .await; - } else { - // If there is no SetBrush, there's no need to wait, so we fire events - // blindly. If the thread's not okay with that... well, whatever. - // That's your issue for making a really slow brush. - let send_result = - self.render_commands_tx.try_send(RenderCommand::Interact { - interactions: interactions.clone(), - done: done_tx, + let chunks_to_modify: Vec<_> = + chunks_to_modify(self.wall.settings(), interactions) + .into_iter() + .collect(); + match self.chunk_images.load(chunks_to_modify.clone()).await { + Ok(_) => { + if interactions + .iter() + .any(|i| matches!(i, Interaction::SetBrush { .. })) + { + // SetBrush is an important event, so we wait for the render thread + // to unload. + _ = self + .render_commands_tx + .send(RenderCommand::Interact { + interactions: interactions.clone(), + done: done_tx, + }) + .await; + } else { + // If there is no SetBrush, there's no need to wait, so we fire events + // blindly. If the thread's not okay with that... well, whatever. + // That's your issue for making a really slow brush. + let send_result = + self.render_commands_tx.try_send(RenderCommand::Interact { + interactions: interactions.clone(), + done: done_tx, + }); + if send_result.is_err() { + info!( + ?interactions, + "render thread is overloaded, dropping interaction request" + ); + } + } + + let auto_save = Arc::clone(&self.auto_save); + tokio::spawn(async move { + _ = done_rx.await; + auto_save.request(chunks_to_modify).await; }); - if send_result.is_err() { - info!( - ?interactions, - "render thread is overloaded, dropping interaction request" - ); } + Err(err) => error!(?err, "while loading chunks for render command"), } // TODO: Auto save. This'll need us to compute which chunks will be affected @@ -501,12 +516,10 @@ impl SessionLoop { Interaction::Dotter { from, to, num } => { if brush_ok { - if let Some(trampoline) = - jumpstart_trampoline(&mut haku, &mut trampoline) - { - let cont = haku.cont(trampoline); + if let Some(tramp) = jumpstart_trampoline(&mut haku, &mut trampoline) { + let cont = haku.cont(tramp); if cont == Cont::Dotter { - _ = haku.dotter(trampoline, from, to, num); + _ = haku.dotter(tramp, from, to, num); } else { error!("received Dotter interaction when a {cont:?} continuation was next"); } @@ -562,6 +575,29 @@ fn render_area(wall_settings: &wall::Settings, interaction: &Interaction) -> Opt } } +fn chunks_to_modify( + wall_settings: &wall::Settings, + interactions: &[Interaction], +) -> HashSet { + let mut chunks = HashSet::new(); + + for interaction in interactions { + // NOTE: This is mostly a tentative overestimation, and can result in more chunks being + // marked as needing autosave than will be touched in reality. + // It's better to play safe in this case than lose data. + if let Some(render_area) = render_area(wall_settings, interaction) { + let top_left_chunk = wall_settings.chunk_at(render_area.top_left); + let bottom_right_chunk = wall_settings.chunk_at_ceil(render_area.bottom_right); + for chunk_y in top_left_chunk.y..bottom_right_chunk.y { + for chunk_x in top_left_chunk.x..bottom_right_chunk.x { + chunks.insert(ChunkPosition::new(chunk_x, chunk_y)); + } + } + } + } + + chunks +} fn jumpstart_trampoline<'a>( haku: &mut Haku, trampoline: &'a mut Option, @@ -572,25 +608,6 @@ fn jumpstart_trampoline<'a>( trampoline.as_mut() } -fn chunks_to_modify(wall: &Wall, points: &[Vec2]) -> HashSet { - let mut chunks = HashSet::new(); - for point in points { - let paint_area = wall.settings().paint_area as f32; - let left = point.x - paint_area / 2.0; - let top = point.y - paint_area / 2.0; - let top_left_chunk = wall.settings().chunk_at(Vec2::new(left, top)); - let bottom_right_chunk = wall - .settings() - .chunk_at_ceil(Vec2::new(left + paint_area, top + paint_area)); - for chunk_y in top_left_chunk.y..bottom_right_chunk.y { - for chunk_x in top_left_chunk.x..bottom_right_chunk.x { - chunks.insert(ChunkPosition::new(chunk_x, chunk_y)); - } - } - } - chunks -} - #[instrument(skip(wall, haku, value))] fn draw_to_chunks( wall: &Wall, diff --git a/crates/rkgk/src/wall/auto_save.rs b/crates/rkgk/src/wall/auto_save.rs index e6fba3f..55f26d1 100644 --- a/crates/rkgk/src/wall/auto_save.rs +++ b/crates/rkgk/src/wall/auto_save.rs @@ -5,7 +5,7 @@ use tokio::{ sync::mpsc, time::{interval, MissedTickBehavior}, }; -use tracing::instrument; +use tracing::{info, instrument}; use super::{chunk_images::ChunkImages, ChunkPosition}; @@ -77,9 +77,12 @@ impl AutoSaveLoop { // NOTE: We don't care about actually using the images here - // the ChunkImages service writes them to the database by itself, and that's all our // request is for. - _ = self - .chunk_images - .encoded(self.unsaved_chunks.drain().collect()) - .await; + if !self.unsaved_chunks.is_empty() { + info!("saving chunks"); + _ = self + .chunk_images + .encoded(self.unsaved_chunks.drain().collect()) + .await; + } } }