From bff11a561782096f2f73174d285bbe5f0b9aa4f4 Mon Sep 17 00:00:00 2001 From: Alain Emilia Anna Zscheile Date: Thu, 3 Oct 2024 15:30:02 +0200 Subject: [PATCH] egui/Workspace: get rid of unnecessary Arc> --- src/bin/topola-egui/menu_bar.rs | 42 ++- src/bin/topola-egui/menu_bar.rs.orig | 390 ++++++++++++++++++++++++++ src/bin/topola-egui/viewport.rs | 4 +- src/bin/topola-egui/workspace.rs | 13 +- src/bin/topola-egui/workspace.rs.orig | 122 ++++++++ 5 files changed, 538 insertions(+), 33 deletions(-) create mode 100644 src/bin/topola-egui/menu_bar.rs.orig create mode 100644 src/bin/topola-egui/workspace.rs.orig diff --git a/src/bin/topola-egui/menu_bar.rs b/src/bin/topola-egui/menu_bar.rs index 2a74caf..adb6c10 100644 --- a/src/bin/topola-egui/menu_bar.rs +++ b/src/bin/topola-egui/menu_bar.rs @@ -258,9 +258,8 @@ impl MenuBar { ctx.send_viewport_cmd(egui::ViewportCommand::Close); } else if let Some(workspace) = maybe_workspace { if export_session.consume_key_triggered(ctx, ui) { - let invoker = workspace.invoker.lock().unwrap(); let ctx = ui.ctx().clone(); - let board = invoker.autorouter().board(); + let board = workspace.invoker.autorouter().board(); // FIXME: I don't know how to avoid buffering the entire exported file let mut writebuf = vec![]; @@ -303,13 +302,12 @@ impl MenuBar { } }); } else if export_history.consume_key_triggered(ctx, ui) { - let invoker = workspace.invoker.lock().unwrap(); let ctx = ctx.clone(); let task = rfd::AsyncFileDialog::new().save_file(); // FIXME: I don't think we should be buffering everything in a `Vec`. let mut writebuf = vec![]; - serde_json::to_writer_pretty(&mut writebuf, invoker.history()); + serde_json::to_writer_pretty(&mut writebuf, workspace.invoker.history()); execute(async move { if let Some(file_handle) = task.await { @@ -318,38 +316,37 @@ impl MenuBar { } }); } else if undo.consume_key_triggered(ctx, ui) { - workspace.invoker.lock().unwrap().undo(); + workspace.invoker.undo(); } else if redo.consume_key_triggered(ctx, ui) { - workspace.invoker.lock().unwrap().redo(); + workspace.invoker.redo(); } else if abort.consume_key_triggered(ctx, ui) { if let Some(activity) = &mut workspace.maybe_activity { activity.abort(&mut ActivityContext { interaction: InteractionContext {}, - invoker: &mut *workspace.invoker.lock().unwrap(), + invoker: &mut workspace.invoker, }); } } else if remove_bands.consume_key_triggered(ctx, ui) { if workspace.maybe_activity.as_mut().map_or(true, |activity| { matches!(activity.maybe_status(), Some(ActivityStatus::Finished(..))) }) { - let mut invoker = workspace.invoker.lock().unwrap(); let selection = workspace.overlay.take_selection(); - workspace.maybe_activity = Some( - ActivityStepperWithStatus::new_execution(invoker.execute_stepper( - Command::RemoveBands(selection.band_selection), - )?), - ); + workspace.maybe_activity = + Some(ActivityStepperWithStatus::new_execution( + workspace.invoker.execute_stepper(Command::RemoveBands( + selection.band_selection, + ))?, + )); } } else if place_via.consume_key_enabled(ctx, ui, &mut self.is_placing_via) { } else if autoroute.consume_key_triggered(ctx, ui) { if workspace.maybe_activity.as_mut().map_or(true, |activity| { matches!(activity.maybe_status(), Some(ActivityStatus::Finished(..))) }) { - let mut invoker = workspace.invoker.lock().unwrap(); let selection = workspace.overlay.take_selection(); workspace.maybe_activity = Some(ActivityStepperWithStatus::new_execution( - invoker.execute_stepper(Command::Autoroute( + workspace.invoker.execute_stepper(Command::Autoroute( selection.pin_selection, self.autorouter_options, ))?, @@ -359,11 +356,10 @@ impl MenuBar { if workspace.maybe_activity.as_mut().map_or(true, |activity| { matches!(activity.maybe_status(), Some(ActivityStatus::Finished(..))) }) { - let mut invoker = workspace.invoker.lock().unwrap(); let selection = workspace.overlay.take_selection(); workspace.maybe_activity = Some(ActivityStepperWithStatus::new_execution( - invoker.execute_stepper(Command::CompareDetours( + workspace.invoker.execute_stepper(Command::CompareDetours( selection.pin_selection, self.autorouter_options, ))?, @@ -373,13 +369,13 @@ impl MenuBar { if workspace.maybe_activity.as_mut().map_or(true, |activity| { matches!(activity.maybe_status(), Some(ActivityStatus::Finished(..))) }) { - let mut invoker = workspace.invoker.lock().unwrap(); let selection = workspace.overlay.take_selection(); - workspace.maybe_activity = Some( - ActivityStepperWithStatus::new_execution(invoker.execute_stepper( - Command::MeasureLength(selection.band_selection), - )?), - ); + workspace.maybe_activity = + Some(ActivityStepperWithStatus::new_execution( + workspace.invoker.execute_stepper(Command::MeasureLength( + selection.band_selection, + ))?, + )); } } } diff --git a/src/bin/topola-egui/menu_bar.rs.orig b/src/bin/topola-egui/menu_bar.rs.orig new file mode 100644 index 0000000..41af3e1 --- /dev/null +++ b/src/bin/topola-egui/menu_bar.rs.orig @@ -0,0 +1,390 @@ +use std::{ + path::Path, + sync::{mpsc::Sender, Arc, Mutex}, +}; + +use topola::{ + autorouter::{ + execution::Command, + history::History, + invoker::{Invoker, InvokerError}, + AutorouterOptions, + }, + router::RouterOptions, + specctra::{ + design::{LoadingError as SpecctraLoadingError, SpecctraDesign}, + mesadata::SpecctraMesadata, + }, + stepper::Abort, +}; + +use crate::{ + action::{Action, Switch, Trigger}, + activity::{ActivityContext, ActivityStatus, ActivityStepperWithStatus}, + app::{execute, handle_file}, + interaction::InteractionContext, + overlay::Overlay, + translator::Translator, + viewport::Viewport, + workspace::Workspace, +}; + +pub struct MenuBar { + pub autorouter_options: AutorouterOptions, + pub is_placing_via: bool, + pub show_ratsnest: bool, + pub show_navmesh: bool, + pub show_bboxes: bool, + pub show_origin_destination: bool, + pub show_layer_manager: bool, + pub frame_timestep: f32, +} + +impl MenuBar { + pub fn new() -> Self { + Self { + autorouter_options: AutorouterOptions { + presort_by_pairwise_detours: false, + router_options: RouterOptions { + wrap_around_bands: true, + squeeze_through_under_bands: true, + }, + }, + is_placing_via: false, + show_ratsnest: false, + show_navmesh: false, + show_bboxes: false, + show_origin_destination: false, + show_layer_manager: true, + frame_timestep: 0.1, + } + } + + pub fn update( + &mut self, + ctx: &egui::Context, + tr: &Translator, + content_sender: Sender>, + viewport: &mut Viewport, + maybe_workspace: Option<&mut Workspace>, + ) -> Result<(), InvokerError> { + let mut open_design = Trigger::new(Action::new( + tr.text("tr-menu-file-open"), + egui::Modifiers::CTRL, + egui::Key::O, + )); + let mut export_session = Trigger::new(Action::new( + tr.text("tr-menu-file-export-session-file"), + egui::Modifiers::CTRL, + egui::Key::S, + )); + let mut import_history = Trigger::new(Action::new( + tr.text("tr-menu-file-import-history"), + egui::Modifiers::CTRL, + egui::Key::I, + )); + let mut export_history = Trigger::new(Action::new( + tr.text("tr-menu-file-export-history"), + egui::Modifiers::CTRL, + egui::Key::E, + )); + let mut quit = Trigger::new(Action::new( + tr.text("tr-menu-file-quit"), + egui::Modifiers::CTRL, + egui::Key::Q, + )); + let mut undo = Trigger::new(Action::new( + tr.text("tr-menu-edit-undo"), + egui::Modifiers::CTRL, + egui::Key::Z, + )); + let mut redo = Trigger::new(Action::new( + tr.text("tr-menu-edit-redo"), + egui::Modifiers::CTRL, + egui::Key::Y, + )); + let mut abort = Trigger::new(Action::new( + tr.text("tr-menu-edit-abort"), + egui::Modifiers::NONE, + egui::Key::Escape, + )); + let mut remove_bands = Trigger::new(Action::new( + tr.text("tr-menu-edit-remove-bands"), + egui::Modifiers::NONE, + egui::Key::Delete, + )); + let mut place_via = Switch::new(Action::new( + tr.text("tr-menu-place-place-via"), + egui::Modifiers::CTRL, + egui::Key::P, + )); + let mut autoroute = Trigger::new(Action::new( + tr.text("tr-menu-route-autoroute"), + egui::Modifiers::CTRL, + egui::Key::A, + )); + let mut compare_detours = Trigger::new(Action::new( + tr.text("tr-menu-inspect-compare-detours"), + egui::Modifiers::NONE, + egui::Key::Minus, + )); + let mut measure_length = Trigger::new(Action::new( + tr.text("tr-menu-inspect-measure-length"), + egui::Modifiers::NONE, + egui::Key::Plus, + )); + + egui::TopBottomPanel::top("menu_bar") + .show(ctx, |ui| { + egui::menu::bar(ui, |ui| { + ui.menu_button(tr.text("tr-menu-file"), |ui| { + open_design.button(ctx, ui); + export_session.button(ctx, ui); + + ui.separator(); + + import_history.button(ctx, ui); + export_history.button(ctx, ui); + + ui.separator(); + + // "Quit" button wouldn't work on a Web page. + if !cfg!(target_arch = "wasm32") { + quit.button(ctx, ui); + } + }); + + ui.menu_button(tr.text("tr-menu-edit"), |ui| { + undo.button(ctx, ui); + redo.button(ctx, ui); + + ui.separator(); + + abort.button(ctx, ui); + + ui.separator(); + + remove_bands.button(ctx, ui); + }); + + ui.menu_button(tr.text("tr-menu-view"), |ui| { + ui.toggle_value( + &mut viewport.scheduled_zoom_to_fit, + tr.text("tr-menu-view-zoom-to-fit"), + ); + + ui.separator(); + + ui.checkbox( + &mut self.show_ratsnest, + tr.text("tr-menu-view-show-ratsnest"), + ); + ui.checkbox(&mut self.show_navmesh, tr.text("tr-menu-view-show-navmesh")); + ui.checkbox(&mut self.show_bboxes, tr.text("tr-menu-view-show-bboxes")); + ui.checkbox( + &mut self.show_origin_destination, + tr.text("tr-menu-view-show-origin-destination"), + ); + + ui.separator(); + + ui.checkbox( + &mut self.show_layer_manager, + tr.text("tr-menu-view-show-layer-manager"), + ); + + ui.separator(); + + ui.label(tr.text("tr-menu-view-frame-timestep")); + ui.add( + egui::widgets::Slider::new(&mut self.frame_timestep, 0.0..=3.0) + .suffix(" s"), + ); + }); + + ui.menu_button(tr.text("tr-menu-place"), |ui| { + place_via.toggle_widget(ctx, ui, &mut self.is_placing_via); + }); + + ui.menu_button(tr.text("tr-menu-route"), |ui| { + autoroute.button(ctx, ui); + ui.separator(); + + ui.menu_button(tr.text("tr-menu-options"), |ui| { + ui.checkbox( + &mut self.autorouter_options.presort_by_pairwise_detours, + tr.text("tr-menu-route-options-presort-by-pairwise-detours"), + ); + ui.checkbox( + &mut self + .autorouter_options + .router_options + .squeeze_through_under_bands, + tr.text("tr-menu-route-options-squeeze-through-under-bands"), + ); + ui.checkbox( + &mut self.autorouter_options.router_options.wrap_around_bands, + tr.text("tr-menu-route-options-wrap-around-bands"), + ); + }); + }); + + ui.menu_button(tr.text("tr-menu-inspect"), |ui| { + compare_detours.button(ctx, ui); + measure_length.button(ctx, ui); + }); + + ui.separator(); + + egui::widgets::global_theme_preference_buttons(ui); + }); + + if open_design.consume_key_triggered(ctx, ui) { + // NOTE: On Linux, this requires Zenity to be installed on your system. + let ctx = ctx.clone(); + let task = rfd::AsyncFileDialog::new().pick_file(); + + execute(async move { + if let Some(file_handle) = task.await { + let data = handle_file(&file_handle) + .await + .map_err(Into::into) + .and_then(SpecctraDesign::load); + content_sender.send(data); + ctx.request_repaint(); + } + }); + } else if quit.consume_key_triggered(ctx, ui) { + ctx.send_viewport_cmd(egui::ViewportCommand::Close); + } else if let Some(workspace) = maybe_workspace { + if export_session.consume_key_triggered(ctx, ui) { + let ctx = ui.ctx().clone(); + let board = workspace.invoker.autorouter().board(); + + // FIXME: I don't know how to avoid buffering the entire exported file + let mut writebuf = vec![]; + + workspace.design.write_ses(board, &mut writebuf); + + let mut dialog = rfd::AsyncFileDialog::new(); + if let Some(filename) = Path::new(workspace.design.get_name()).file_stem() { + if let Some(filename) = filename.to_str() { + dialog = dialog.set_file_name(filename); + } + } + + let task = dialog + .add_filter(tr.text("tr-menu-open-specctra-session-file"), &["ses"]) + .save_file(); + + execute(async move { + if let Some(file_handle) = task.await { + file_handle.write(&writebuf).await; + ctx.request_repaint(); + } + }); + } else if import_history.consume_key_triggered(ctx, ui) { + let ctx = ctx.clone(); + let task = rfd::AsyncFileDialog::new().pick_file(); + let history_sender = workspace.history_channel.0.clone(); + + execute(async move { + if let Some(file_handle) = task.await { + let data = handle_file(&file_handle).await.and_then(|data| { + match serde_json::from_reader(data) { + Ok(history) => Ok(Ok(history)), + Err(err) if err.is_io() => Err(err.into()), + Err(err) => Ok(Err(err)), + } + }); + history_sender.send(data); + ctx.request_repaint(); + } + }); + } else if export_history.consume_key_triggered(ctx, ui) { + let ctx = ctx.clone(); + let task = rfd::AsyncFileDialog::new().save_file(); + + // FIXME: I don't think we should be buffering everything in a `Vec`. + let mut writebuf = vec![]; + serde_json::to_writer_pretty(&mut writebuf, workspace.invoker.history()); + + execute(async move { + if let Some(file_handle) = task.await { + file_handle.write(&writebuf).await; + ctx.request_repaint(); + } + }); + } else if undo.consume_key_triggered(ctx, ui) { + workspace.invoker.undo(); + } else if redo.consume_key_triggered(ctx, ui) { + workspace.invoker.redo(); + } else if abort.consume_key_triggered(ctx, ui) { + if let Some(activity) = &mut workspace.maybe_activity { +<<<<<<< HEAD + activity.abort(&mut ActivityContext { + interaction: InteractionContext {}, + invoker: &mut *workspace.invoker.lock().unwrap(), + }); +======= + activity.abort(&mut workspace.invoker); +>>>>>>> 8938ce7 (egui/Workspace: get rid of unnecessary Arc>) + } + } else if remove_bands.consume_key_triggered(ctx, ui) { + if workspace.maybe_activity.as_mut().map_or(true, |activity| { + matches!(activity.maybe_status(), Some(ActivityStatus::Finished(..))) + }) { + let selection = workspace.overlay.take_selection(); + workspace.maybe_activity = + Some(ActivityStepperWithStatus::new_execution( + workspace.invoker.execute_stepper(Command::RemoveBands( + selection.band_selection, + ))?, + )); + } + } else if place_via.consume_key_enabled(ctx, ui, &mut self.is_placing_via) { + } else if autoroute.consume_key_triggered(ctx, ui) { + if workspace.maybe_activity.as_mut().map_or(true, |activity| { + matches!(activity.maybe_status(), Some(ActivityStatus::Finished(..))) + }) { + let selection = workspace.overlay.take_selection(); + workspace.maybe_activity = + Some(ActivityStepperWithStatus::new_execution( + workspace.invoker.execute_stepper(Command::Autoroute( + selection.pin_selection, + self.autorouter_options, + ))?, + )); + } + } else if compare_detours.consume_key_triggered(ctx, ui) { + if workspace.maybe_activity.as_mut().map_or(true, |activity| { + matches!(activity.maybe_status(), Some(ActivityStatus::Finished(..))) + }) { + let selection = workspace.overlay.take_selection(); + workspace.maybe_activity = + Some(ActivityStepperWithStatus::new_execution( + workspace.invoker.execute_stepper(Command::CompareDetours( + selection.pin_selection, + self.autorouter_options, + ))?, + )); + } + } else if measure_length.consume_key_triggered(ctx, ui) { + if workspace.maybe_activity.as_mut().map_or(true, |activity| { + matches!(activity.maybe_status(), Some(ActivityStatus::Finished(..))) + }) { + let selection = workspace.overlay.take_selection(); + workspace.maybe_activity = + Some(ActivityStepperWithStatus::new_execution( + workspace.invoker.execute_stepper(Command::MeasureLength( + selection.band_selection, + ))?, + )); + } + } + } + Ok::<(), InvokerError>(()) + }) + .inner + } +} diff --git a/src/bin/topola-egui/viewport.rs b/src/bin/topola-egui/viewport.rs index 9f39c52..8b9bdc8 100644 --- a/src/bin/topola-egui/viewport.rs +++ b/src/bin/topola-egui/viewport.rs @@ -46,7 +46,7 @@ impl Viewport { let viewport_rect = self.paint(ctx, top, maybe_workspace.as_deref_mut()); if self.scheduled_zoom_to_fit { - let mut maybe_invoker = maybe_workspace.as_ref().map(|w| w.invoker.lock().unwrap()); + let mut maybe_invoker = maybe_workspace.as_mut().map(|w| &mut w.invoker); self.zoom_to_fit(maybe_invoker.as_deref_mut(), &viewport_rect); } @@ -75,7 +75,7 @@ impl Viewport { let mut painter = Painter::new(ui, self.transform, top.show_bboxes); if let Some(workspace) = maybe_workspace { - let mut invoker = workspace.invoker.lock().unwrap(); + let invoker = &mut workspace.invoker; let layers = &mut workspace.layers; let overlay = &mut workspace.overlay; diff --git a/src/bin/topola-egui/workspace.rs b/src/bin/topola-egui/workspace.rs index d8f7b70..b2bf197 100644 --- a/src/bin/topola-egui/workspace.rs +++ b/src/bin/topola-egui/workspace.rs @@ -23,9 +23,7 @@ use crate::{ error_dialog::ErrorDialog, interaction::InteractionContext, layers::Layers, - menu_bar::MenuBar, overlay::Overlay, - status_bar::StatusBar, translator::Translator, viewport::Viewport, }; @@ -35,7 +33,7 @@ pub struct Workspace { pub design: SpecctraDesign, pub layers: Layers, pub overlay: Overlay, - pub invoker: Arc>>, + pub invoker: Invoker, pub maybe_activity: Option, @@ -67,7 +65,7 @@ impl Workspace { design, layers, overlay, - invoker: Arc::new(Mutex::new(Invoker::new(autorouter))), + invoker: Invoker::new(autorouter), maybe_activity: None, history_channel: channel(), }) @@ -77,7 +75,7 @@ impl Workspace { if let Ok(data) = self.history_channel.1.try_recv() { match data { Ok(Ok(data)) => { - self.invoker.lock().unwrap().replay(data); + self.invoker.replay(data); } Ok(Err(err)) => { error_dialog.push_error( @@ -101,7 +99,7 @@ impl Workspace { if let Some(activity) = &mut self.maybe_activity { return match activity.step(&mut ActivityContext { interaction: InteractionContext {}, - invoker: &mut *self.invoker.lock().unwrap(), + invoker: &mut self.invoker, }) { Ok(ActivityStatus::Running) => true, Ok(ActivityStatus::Finished(..)) => false, @@ -115,7 +113,6 @@ impl Workspace { } pub fn update_layers(&mut self, ctx: &egui::Context) { - let invoker = self.invoker.lock().unwrap(); - self.layers.update(ctx, invoker.autorouter().board()); + self.layers.update(ctx, self.invoker.autorouter().board()); } } diff --git a/src/bin/topola-egui/workspace.rs.orig b/src/bin/topola-egui/workspace.rs.orig new file mode 100644 index 0000000..adc451d --- /dev/null +++ b/src/bin/topola-egui/workspace.rs.orig @@ -0,0 +1,122 @@ +use serde::{Deserialize, Serialize}; +use std::{ + future::Future, + io, + sync::{ + mpsc::{channel, Receiver, Sender}, + Arc, Mutex, + }, +}; +use unic_langid::{langid, LanguageIdentifier}; + +use topola::{ + autorouter::{history::History, invoker::Invoker, Autorouter}, + specctra::{ + design::{LoadingError as SpecctraLoadingError, SpecctraDesign}, + mesadata::SpecctraMesadata, + }, + stepper::Step, +}; + +use crate::{ + activity::{ActivityContext, ActivityStatus, ActivityStepperWithStatus}, + error_dialog::ErrorDialog, + interaction::InteractionContext, + layers::Layers, + overlay::Overlay, + translator::Translator, + viewport::Viewport, +}; + +/// A loaded design and associated structures +pub struct Workspace { + pub design: SpecctraDesign, + pub layers: Layers, + pub overlay: Overlay, + pub invoker: Invoker, + + pub maybe_activity: Option, + + pub history_channel: ( + Sender>>, + Receiver>>, + ), +} + +impl Workspace { + pub fn new(design: SpecctraDesign, tr: &Translator) -> Result { + let board = design.make_board(); + let overlay = Overlay::new(&board).map_err(|err| { + format!( + "{}; {}", + tr.text("tr-error_unable-to-initialize-overlay"), + err + ) + })?; + let layers = Layers::new(&board); + let autorouter = Autorouter::new(board).map_err(|err| { + format!( + "{}; {}", + tr.text("tr-error_unable-to-initialize-autorouter"), + err + ) + })?; + Ok(Self { + design, + layers, + overlay, + invoker: Invoker::new(autorouter), + maybe_activity: None, + history_channel: channel(), + }) + } + + pub fn update_state(&mut self, tr: &Translator, error_dialog: &mut ErrorDialog) -> bool { + if let Ok(data) = self.history_channel.1.try_recv() { + match data { + Ok(Ok(data)) => { + self.invoker.replay(data); + } + Ok(Err(err)) => { + error_dialog.push_error( + "tr-module-history-file-loader", + format!( + "{}; {}", + tr.text("tr-error_failed-to-parse-as-history-json"), + err + ), + ); + } + Err(err) => { + error_dialog.push_error( + "tr-module-history-file-loader", + format!("{}; {}", tr.text("tr-error_unable-to-read-file"), err), + ); + } + } + } + + if let Some(activity) = &mut self.maybe_activity { +<<<<<<< HEAD + return match activity.step(&mut ActivityContext { + interaction: InteractionContext {}, + invoker: &mut *self.invoker.lock().unwrap(), + }) { +======= + return match activity.step(&mut self.invoker) { +>>>>>>> 8938ce7 (egui/Workspace: get rid of unnecessary Arc>) + Ok(ActivityStatus::Running) => true, + Ok(ActivityStatus::Finished(..)) => false, + Err(err) => { + error_dialog.push_error("tr-module-invoker", format!("{}", err)); + false + } + }; + } + false + } + + pub fn update_layers(&mut self, ctx: &egui::Context) { + self.layers.update(ctx, self.invoker.autorouter().board()); + } +}