use futures::executor; use geo::point; use petgraph::visit::{EdgeRef, IntoEdgeReferences}; use serde::{Deserialize, Serialize}; use std::{ fs::File, future::Future, sync::{ mpsc::{channel, Receiver, Sender}, Arc, Mutex, }, }; use topola::{ autorouter::{ invoker::{Command, Execute, Invoker, InvokerStatus}, Autorouter, }, drawing::{ dot::FixedDotIndex, graph::{MakePrimitive, PrimitiveIndex}, primitive::MakePrimitiveShape, rules::RulesTrait, Drawing, Infringement, LayoutException, }, dsn::{design::DsnDesign, mesadata::DsnMesadata}, geometry::{ compound::CompoundManagerTrait, primitive::{BendShape, DotShape, PrimitiveShape, PrimitiveShapeTrait, SegShape}, shape::ShapeTrait, GenericNode, }, layout::{via::ViaWeight, zone::MakePolyShape, Layout}, math::Circle, router::{ draw::DrawException, navmesh::{Navmesh, NavmeshEdgeReference, NavvertexIndex}, tracer::{Trace, Tracer}, EmptyRouterObserver, RouterObserverTrait, }, }; use crate::{layers::Layers, overlay::Overlay, painter::Painter}; #[derive(Debug, Default)] struct SharedData { pub from: Option, pub to: Option, pub navmesh: Option, pub path: Vec, pub ghosts: Vec, pub highlighteds: Vec, } /// Deserialize/Serialize is needed to persist app state between restarts. #[derive(Serialize, Deserialize)] #[serde(default)] pub struct App { #[serde(skip)] overlay: Option, #[serde(skip)] invoker: Option>>>, #[serde(skip)] shared_data: Arc>, #[serde(skip)] text_channel: (Sender, Receiver), #[serde(skip)] from_rect: egui::emath::Rect, #[serde(skip)] is_placing_via: bool, #[serde(skip)] show_ratsnest: bool, #[serde(skip)] layers: Option, } impl Default for App { fn default() -> Self { Self { overlay: None, invoker: None, shared_data: Default::default(), text_channel: channel(), from_rect: egui::Rect::from_x_y_ranges(0.0..=1000000.0, 0.0..=500000.0), is_placing_via: false, show_ratsnest: true, layers: None, } } } impl App { /// Called once on start. pub fn new(cc: &eframe::CreationContext<'_>) -> Self { // Load previous app state if one exists. if let Some(storage) = cc.storage { return eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default(); } Default::default() } } struct DebugRouterObserver { shared_data: Arc>, } impl RouterObserverTrait for DebugRouterObserver { fn on_rework(&mut self, tracer: &Tracer, trace: &Trace) { //dbg!(_tracer, _trace); let mut shared_data = self.shared_data.lock().unwrap(); shared_data.path = trace.path.clone(); shared_data.ghosts = vec![]; shared_data.highlighteds = vec![]; std::thread::sleep_ms(500); } fn before_probe(&mut self, tracer: &Tracer, trace: &Trace, edge: NavmeshEdgeReference) { //dbg!(_tracer, _trace, _edge); let mut shared_data = self.shared_data.lock().unwrap(); shared_data.path = trace.path.clone(); shared_data.path.push(edge.target()); shared_data.ghosts = vec![]; shared_data.highlighteds = vec![]; std::thread::sleep_ms(100); } fn on_probe( &mut self, tracer: &Tracer, trace: &Trace, edge: NavmeshEdgeReference, result: Result<(), DrawException>, ) { //dbg!(_tracer, _trace, _edge, _result); let mut shared_data = self.shared_data.lock().unwrap(); let (ghosts, highlighteds, delay) = match result { Err(DrawException::CannotWrapAround( .., LayoutException::Infringement(Infringement(shape1, infringee1)), LayoutException::Infringement(Infringement(shape2, infringee2)), )) => (vec![shape1, shape2], vec![infringee1, infringee2], 1500), _ => (vec![], vec![], 300), }; shared_data.path = trace.path.clone(); shared_data.ghosts = ghosts; shared_data.highlighteds = highlighteds; std::thread::sleep_ms(delay); } fn on_estimate(&mut self, _tracer: &Tracer, _vertex: NavvertexIndex) { //dbg!(_tracer, _vertex); } } impl eframe::App for App { /// Called to save state before shutdown. fn save(&mut self, storage: &mut dyn eframe::Storage) { eframe::set_value(storage, eframe::APP_KEY, self); } /// Called each time the UI has to be repainted. fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { let mut has_acted = false; if cfg!(target_arch = "wasm32") { if let Ok(file_contents) = self.text_channel.1.try_recv() { let design = DsnDesign::load_from_string(file_contents).unwrap(); let board = design.make_board(); self.overlay = Some(Overlay::new(&board).unwrap()); self.layers = Some(Layers::new(&board)); self.invoker = Some(Arc::new(Mutex::new(Invoker::new( Autorouter::new(board).unwrap(), )))); } } else { if let Ok(path) = self.text_channel.1.try_recv() { let design = DsnDesign::load_from_file(&path).unwrap(); let board = design.make_board(); self.overlay = Some(Overlay::new(&board).unwrap()); self.layers = Some(Layers::new(&board)); self.invoker = Some(Arc::new(Mutex::new(Invoker::new( Autorouter::new(board).unwrap(), )))); } } egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { egui::menu::bar(ui, |ui| { ui.menu_button("File", |ui| { if ui.button("Open").clicked() { // `Context` is cheap to clone as it's wrapped in an `Arc`. let ctx = ui.ctx().clone(); // NOTE: On Linux, this requires Zenity to be installed on your system. let sender = self.text_channel.0.clone(); let task = rfd::AsyncFileDialog::new().pick_file(); execute(async move { if let Some(file_handle) = task.await { sender.send(channel_text(file_handle).await); ctx.request_repaint(); } }); } ui.separator(); if ui.button("Load history").clicked() { if let Some(invoker_arc_mutex) = &self.invoker { let invoker_arc_mutex = invoker_arc_mutex.clone(); let ctx = ui.ctx().clone(); let task = rfd::AsyncFileDialog::new().pick_file(); execute(async move { if let Some(file_handle) = task.await { let path = file_handle.path(); let mut invoker = invoker_arc_mutex.lock().unwrap(); let mut file = File::open(path).unwrap(); invoker.replay(serde_json::from_reader(file).unwrap()); } }); } } if ui.button("Save history").clicked() { if let Some(invoker_arc_mutex) = &self.invoker { let invoker_arc_mutex = invoker_arc_mutex.clone(); let ctx = ui.ctx().clone(); let task = rfd::AsyncFileDialog::new().save_file(); execute(async move { if let Some(file_handle) = task.await { let path = file_handle.path(); let mut invoker = invoker_arc_mutex.lock().unwrap(); let mut file = File::create(path).unwrap(); serde_json::to_writer_pretty(file, invoker.history()); } }); } } ui.separator(); // "Quit" button wouldn't work on a Web page. if !cfg!(target_arch = "wasm32") { if ui.button("Quit").clicked() { ctx.send_viewport_cmd(egui::ViewportCommand::Close); } } }); ui.separator(); if ui.button("Autoroute").clicked() { if let (Some(invoker_arc_mutex), Some(overlay)) = (&self.invoker, &self.overlay) { let invoker_arc_mutex = invoker_arc_mutex.clone(); let shared_data_arc_mutex = self.shared_data.clone(); let selection = overlay.selection().clone(); execute(async move { let mut invoker = invoker_arc_mutex.lock().unwrap(); let mut execute = invoker.execute_walk(Command::Autoroute(selection)); if let Execute::Autoroute(ref mut autoroute) = execute { let from = autoroute.navmesh().as_ref().unwrap().source(); let to = autoroute.navmesh().as_ref().unwrap().target(); { let mut shared_data = shared_data_arc_mutex.lock().unwrap(); shared_data.from = Some(from); shared_data.to = Some(to); shared_data.navmesh = autoroute.navmesh().clone(); } } let _ = loop { let status = match execute.step( &mut invoker, &mut DebugRouterObserver { shared_data: shared_data_arc_mutex.clone(), }, ) { Ok(status) => status, Err(err) => return, }; if let Execute::Autoroute(ref mut autoroute) = execute { shared_data_arc_mutex.lock().unwrap().navmesh = autoroute.navmesh().clone(); } if let InvokerStatus::Finished = status { break; } }; }); } } if ui .toggle_value(&mut self.is_placing_via, "Place Via") .clicked() { has_acted = true; } ui.separator(); if ui.button("Undo").clicked() || ctx.input_mut(|i| i.consume_key(egui::Modifiers::CTRL, egui::Key::Z)) { if let Some(invoker_arc_mutex) = &self.invoker { let invoker_arc_mutex = invoker_arc_mutex.clone(); execute(async move { invoker_arc_mutex.lock().unwrap().undo(); }); } } if ui.button("Redo").clicked() || ctx.input_mut(|i| i.consume_key(egui::Modifiers::CTRL, egui::Key::Y)) { if let Some(invoker_arc_mutex) = &self.invoker { let invoker_arc_mutex = invoker_arc_mutex.clone(); execute(async move { invoker_arc_mutex.lock().unwrap().redo(); }); } } ui.separator(); ui.toggle_value(&mut self.show_ratsnest, "Show Ratsnest"); ui.separator(); egui::widgets::global_dark_light_mode_buttons(ui); }); }); if let Some(ref mut layers) = self.layers { if let Some(invoker_arc_mutex) = &self.invoker { layers.update(ctx, invoker_arc_mutex.lock().unwrap().autorouter().board()); } } egui::CentralPanel::default().show(ctx, |ui| { egui::Frame::canvas(ui.style()).show(ui, |ui| { ui.ctx().request_repaint(); let desired_size = ui.available_width() * egui::vec2(1.0, 0.5); let (_id, viewport_rect) = ui.allocate_space(desired_size); let old_transform = egui::emath::RectTransform::from_to(self.from_rect, viewport_rect); let latest_pos = old_transform .inverse() .transform_pos(ctx.input(|i| i.pointer.latest_pos().unwrap_or_default())); let old_scale = old_transform.scale().x; self.from_rect = self.from_rect / ctx.input(|i| i.zoom_delta()); let new_scale = egui::emath::RectTransform::from_to(self.from_rect, viewport_rect) .scale() .x; self.from_rect = self.from_rect.translate( ctx.input(|i| latest_pos.to_vec2() * (new_scale - old_scale) / new_scale), ); self.from_rect = self .from_rect .translate(ctx.input(|i| -i.raw_scroll_delta / new_scale)); let transform = egui::emath::RectTransform::from_to(self.from_rect, viewport_rect); let mut painter = Painter::new(ui, transform); if let Some(invoker_arc_mutex) = &self.invoker { if ctx.input(|i| i.pointer.any_click()) { if !has_acted { if self.is_placing_via { let invoker_arc_mutex = invoker_arc_mutex.clone(); execute(async move { let mut invoker = invoker_arc_mutex.lock().unwrap(); invoker.execute( Command::PlaceVia(ViaWeight { from_layer: 0, to_layer: 0, circle: Circle { pos: point! {x: latest_pos.x as f64, y: -latest_pos.y as f64}, r: 10000.0, }, maybe_net: Some(1234), }), &mut EmptyRouterObserver, ); }); } else if let Some(overlay) = &mut self.overlay { let invoker = invoker_arc_mutex.lock().unwrap(); overlay.click( invoker.autorouter().board(), point! {x: latest_pos.x as f64, y: -latest_pos.y as f64}, ); } } } if let (invoker, shared_data, Some(overlay)) = ( &invoker_arc_mutex.lock().unwrap(), self.shared_data.lock().unwrap(), &mut self.overlay, ) { let board = invoker.autorouter().board(); for primitive in board.layout().drawing().layer_primitive_nodes(1) { let shape = primitive.primitive(board.layout().drawing()).shape(); let color = if shared_data.highlighteds.contains(&primitive) || overlay .selection() .contains_node(board, GenericNode::Primitive(primitive)) { egui::Color32::from_rgb(100, 100, 255) } else { egui::Color32::from_rgb(52, 52, 200) }; painter.paint_primitive(&shape, color); } for zone in board.layout().layer_zone_nodes(1) { let color = if overlay .selection() .contains_node(board, GenericNode::Compound(zone.into())) { egui::Color32::from_rgb(100, 100, 255) } else { egui::Color32::from_rgb(52, 52, 200) }; painter.paint_polygon(&board.layout().zone(zone).shape().polygon, color) } for primitive in board.layout().drawing().layer_primitive_nodes(0) { let shape = primitive.primitive(board.layout().drawing()).shape(); let color = if shared_data.highlighteds.contains(&primitive) || overlay .selection() .contains_node(board, GenericNode::Primitive(primitive)) { egui::Color32::from_rgb(255, 100, 100) } else { egui::Color32::from_rgb(200, 52, 52) }; painter.paint_primitive(&shape, color); } for zone in board.layout().layer_zone_nodes(0) { let color = if overlay .selection() .contains_node(board, GenericNode::Compound(zone.into())) { egui::Color32::from_rgb(255, 100, 100) } else { egui::Color32::from_rgb(200, 52, 52) }; painter.paint_polygon(&board.layout().zone(zone).shape().polygon, color) } if self.show_ratsnest { for edge in overlay.ratsnest().graph().edge_references() { let from = overlay .ratsnest() .graph() .node_weight(edge.source()) .unwrap() .pos; let to = overlay .ratsnest() .graph() .node_weight(edge.target()) .unwrap() .pos; painter.paint_edge( from, to, egui::Stroke::new(1.0, egui::Color32::from_rgb(90, 90, 200)), ); } } if let Some(navmesh) = &shared_data.navmesh { for edge in navmesh.edge_references() { let from = edge .source() .primitive(board.layout().drawing()) .shape() .center(); let to = edge .target() .primitive(board.layout().drawing()) .shape() .center(); let stroke = 'blk: { if let (Some(source_pos), Some(target_pos)) = ( shared_data .path .iter() .position(|node| *node == edge.source()), shared_data .path .iter() .position(|node| *node == edge.target()), ) { if target_pos == source_pos + 1 || source_pos == target_pos + 1 { break 'blk egui::Stroke::new( 5.0, egui::Color32::from_rgb(250, 250, 0), ); } } egui::Stroke::new(1.0, egui::Color32::from_rgb(125, 125, 125)) }; painter.paint_edge(from, to, stroke); } } for ghost in shared_data.ghosts.iter() { painter.paint_primitive(&ghost, egui::Color32::from_rgb(75, 75, 150)); } if let (Some(from), Some(to)) = (shared_data.from, shared_data.to) { painter.paint_dot( Circle { pos: board.layout().drawing().primitive(from).shape().center(), r: 20.0, }, egui::Color32::from_rgb(255, 255, 100), ); painter.paint_dot( Circle { pos: board.layout().drawing().primitive(to).shape().center(), r: 20.0, }, egui::Color32::from_rgb(255, 255, 100), ); } //unreachable!(); } } }) }); if ctx.input(|i| i.key_pressed(egui::Key::Escape)) { ctx.send_viewport_cmd(egui::ViewportCommand::Close); } } } #[cfg(not(target_arch = "wasm32"))] fn execute + Send + 'static>(f: F) { std::thread::spawn(move || futures::executor::block_on(f)); } #[cfg(target_arch = "wasm32")] fn execute + 'static>(f: F) { wasm_bindgen_futures::spawn_local(f); } #[cfg(not(target_arch = "wasm32"))] async fn channel_text(file_handle: rfd::FileHandle) -> String { file_handle.path().to_str().unwrap().to_string() } #[cfg(target_arch = "wasm32")] async fn channel_text(file_handle: rfd::FileHandle) -> String { std::str::from_utf8(&file_handle.read().await) .unwrap() .to_string() }