diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cc5c553..5954b1fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ Tracking changes per date: +## 240911 +- Added so that simulator now tracks what component condition +- Added simulator running state, such as running, halt or error +- Added stepping functionality +- Added functionality to gui to show running state and component condition + ## 240909 - Added un-clock history for set of active components diff --git a/Cargo.toml b/Cargo.toml index ec47c85d..57fedf50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,6 @@ serde_derive = "1.0.171" serde_json = "1.0.103" typetag = "0.2.10" - [dependencies.vizia] git = "https://github.com/vizia/vizia.git" rev = "7093bfd518c4bee5544a75c2ffc92dfe4f817bc0" @@ -30,26 +29,21 @@ optional = true [dependencies.egui] optional = true -version = "0.23.0" - -[dependencies.winapi] -optional = true -version = "0.3.9" -features = ["winuser"] +version = "0.28.0" [dependencies.eframe] optional = true -version = "0.23.0" +version = "0.28.0" [dependencies.epaint] optional = true -version = "0.23.0" +version = "0.28.0" [features] default = ["gui-egui"] components = [] gui-vizia = ["vizia", "components"] -gui-egui = ["egui", "eframe", "epaint", "winapi", "components"] +gui-egui = ["egui", "eframe", "epaint", "components"] [profile.dev] debug = 1 # faster build, still allows for stack back trace diff --git a/TODO.md b/TODO.md index 90ccc773..8764651a 100644 --- a/TODO.md +++ b/TODO.md @@ -12,3 +12,7 @@ Each target (e.g. `mips`, has a separate `TODO.md`). - Better tooltips for components. (Complexity moderate.) - Better popups for components. (Complexity moderate.) + +## Egui + +- Add better formatted tooltip for probe, maybe use ``LayoutJob``, if style is not respected maybe force to ``galley`` (Complexity low, but weird egui stuff) diff --git a/riscv/Cargo.toml b/riscv/Cargo.toml index 606d0d43..644c6d18 100644 --- a/riscv/Cargo.toml +++ b/riscv/Cargo.toml @@ -16,7 +16,7 @@ log = "0.4.19" num_enum = "0.7.2" fern = "0.6.2" xmas-elf = "0.9.0" -egui = "0.23.0" +egui = "0.28.0" asm_riscv = { git = 'https://github.com/onsdagens/wari' } gimli = "0.27.3" object = "0.31.1" @@ -24,7 +24,7 @@ memmap2 = "0.7.1" riscv_asm_strings = { git = 'https://github.com/perlindgren/riscv_asm_strings' } priority-queue = { version = "1.3.2", features = ["serde"] } riscv-rt = "0.11.0" -egui_extras = "0.23.0" +egui_extras = "0.28.0" [dependencies.syncrim] path = "../" diff --git a/riscv/src/gui_egui/components/instr_mem.rs b/riscv/src/gui_egui/components/instr_mem.rs index 14989232..4dfccfb5 100644 --- a/riscv/src/gui_egui/components/instr_mem.rs +++ b/riscv/src/gui_egui/components/instr_mem.rs @@ -44,98 +44,95 @@ impl InstrMem { }); }) .body(|body| { - body.rows( - 15.0, - (self.range.end - self.range.start) / 4, - |index, mut row| { - let address = index * 4 + self.range.start; - let pc: u32 = { - if simulator.as_ref().is_some() { - simulator - .as_ref() - .unwrap() - .get_input_value(&self.pc) - .try_into() - .unwrap_or(0) - } else { - 0 - } - }; - let (bg_color, fg_color) = { - if pc as usize == address { - (Color32::DARK_GRAY, Color32::WHITE) - } else { - (Color32::TRANSPARENT, Color32::LIGHT_GRAY) - } - }; - let breakpoint_color = { - if self.breakpoints.borrow_mut().contains(&address) { - Color32::RED - } else { - Color32::TRANSPARENT - } - }; - row.col(|ui| match &self.symbols.get(&address) { - Some(s) => { - ui.add(Label::new(format!("{}:", s)).truncate(true)); - } - None => {} - }); - //breakpoint - row.col(|ui| { - ui.label(RichText::new("•").color(breakpoint_color)); - }); - //address - row.col(|ui| { - ui.add(Label::new(format!("0x{:08x}", address)).truncate(true)); - }); - let mut bytes = [0u8; 4]; - if !self.le { - bytes[3] = *self.bytes.get(&address).unwrap(); - bytes[2] = *self.bytes.get(&(address + 1)).unwrap(); - bytes[1] = *self.bytes.get(&(address + 2)).unwrap(); - bytes[0] = *self.bytes.get(&(address + 3)).unwrap(); + body.rows(15.0, (self.range.end - self.range.start) / 4, |mut row| { + let index = row.index(); + let address = index * 4 + self.range.start; + let pc: u32 = { + if simulator.as_ref().is_some() { + simulator + .as_ref() + .unwrap() + .get_input_value(&self.pc) + .try_into() + .unwrap_or(0) + } else { + 0 + } + }; + let (bg_color, fg_color) = { + if pc as usize == address { + (Color32::DARK_GRAY, Color32::WHITE) + } else { + (Color32::TRANSPARENT, Color32::LIGHT_GRAY) + } + }; + let breakpoint_color = { + if self.breakpoints.borrow_mut().contains(&address) { + Color32::RED } else { - bytes[0] = *self.bytes.get(&address).unwrap(); - bytes[1] = *self.bytes.get(&(address + 1)).unwrap(); - bytes[2] = *self.bytes.get(&(address + 2)).unwrap(); - bytes[3] = *self.bytes.get(&(address + 3)).unwrap(); + Color32::TRANSPARENT + } + }; + row.col(|ui| match &self.symbols.get(&address) { + Some(s) => { + ui.add(Label::new(format!("{}:", s)).truncate()); } - let instr = ((bytes[3] as u32) << 24) - | ((bytes[2] as u32) << 16) - | ((bytes[1] as u32) << 8) - | (bytes[0] as u32); + None => {} + }); + //breakpoint + row.col(|ui| { + ui.label(RichText::new("•").color(breakpoint_color)); + }); + //address + row.col(|ui| { + ui.add(Label::new(format!("0x{:08x}", address)).truncate()); + }); + let mut bytes = [0u8; 4]; + if !self.le { + bytes[3] = *self.bytes.get(&address).unwrap(); + bytes[2] = *self.bytes.get(&(address + 1)).unwrap(); + bytes[1] = *self.bytes.get(&(address + 2)).unwrap(); + bytes[0] = *self.bytes.get(&(address + 3)).unwrap(); + } else { + bytes[0] = *self.bytes.get(&address).unwrap(); + bytes[1] = *self.bytes.get(&(address + 1)).unwrap(); + bytes[2] = *self.bytes.get(&(address + 2)).unwrap(); + bytes[3] = *self.bytes.get(&(address + 3)).unwrap(); + } + let instr = ((bytes[3] as u32) << 24) + | ((bytes[2] as u32) << 16) + | ((bytes[1] as u32) << 8) + | (bytes[0] as u32); - let instr_fmt = match asm_riscv::I::try_from(instr) { - Ok(i) => riscv_asm_strings::StringifyUpperHex::to_string(&i), - Err(_) => "Unknown instruction".to_string(), - }; - //hex instr - row.col(|ui| { - ui.add(Label::new(format!("0x{:08X}", instr)).truncate(true)); - }); - row.col(|ui| { - if ui - .add( - Label::new( - RichText::new(instr_fmt) - .color(fg_color) - .background_color(bg_color), - ) - .truncate(true) - .sense(Sense::click()), + let instr_fmt = match asm_riscv::I::try_from(instr) { + Ok(i) => riscv_asm_strings::StringifyUpperHex::to_string(&i), + Err(_) => "Unknown instruction".to_string(), + }; + //hex instr + row.col(|ui| { + ui.add(Label::new(format!("0x{:08X}", instr)).truncate()); + }); + row.col(|ui| { + if ui + .add( + Label::new( + RichText::new(instr_fmt) + .color(fg_color) + .background_color(bg_color), ) - .clicked() - { - trace!("clicked"); - if !self.breakpoints.borrow_mut().remove(&address) { - self.breakpoints.borrow_mut().insert(address); - } - }; - }); - row.col(|_| {}); - }, - ); + .truncate() + .sense(Sense::click()), + ) + .clicked() + { + trace!("clicked"); + if !self.breakpoints.borrow_mut().remove(&address) { + self.breakpoints.borrow_mut().insert(address); + } + }; + }); + row.col(|_| {}); + }); }); }); } diff --git a/riscv/src/gui_egui/components/probe_label.rs b/riscv/src/gui_egui/components/probe_label.rs index 57ce4a63..d2611edf 100644 --- a/riscv/src/gui_egui/components/probe_label.rs +++ b/riscv/src/gui_egui/components/probe_label.rs @@ -29,7 +29,7 @@ impl EguiComponent for ProbeLabel { Some(s) => s.get_input_value(&input), None => SignalValue::Uninitialized, }; - let area = Area::new(self.id.to_string()) + let area = Area::new(self.id.clone().into()) .order(Order::Middle) .current_pos(offset.to_pos2()) .movable(false) diff --git a/riscv/src/gui_egui/components/reg_file.rs b/riscv/src/gui_egui/components/reg_file.rs index 208eb3e1..81657c28 100644 --- a/riscv/src/gui_egui/components/reg_file.rs +++ b/riscv/src/gui_egui/components/reg_file.rs @@ -124,7 +124,8 @@ impl EguiComponent for RegFile { 15.0 * scale, RegStore::lo_range().end as usize - RegStore::lo_range().start as usize, - |index, mut row| { + |mut row| { + let index = row.index(); row.col(|ui| { ui.add(Label::new( RichText::new(format!( diff --git a/riscv/src/gui_egui/components/rv_mem.rs b/riscv/src/gui_egui/components/rv_mem.rs index e70e1226..be3cffb9 100644 --- a/riscv/src/gui_egui/components/rv_mem.rs +++ b/riscv/src/gui_egui/components/rv_mem.rs @@ -36,8 +36,8 @@ impl RVMem { body.rows( 15.0, ((self.range.end - self.range.start) / 4) as usize, - |index, mut row| { - //println!("{}", index); + |mut row| { + let index = row.index(); let address = self.range.start as usize + index * 4; let memory = self.memory.0.borrow().clone(); row.col(|ui| { @@ -68,10 +68,10 @@ impl RVMem { } } row.col(|ui| { - ui.add(Label::new(word).truncate(true)); + ui.add(Label::new(word).truncate()); }); row.col(|ui| { - ui.add(Label::new(ascii).truncate(true)); + ui.add(Label::new(ascii).truncate()); }); }, ); diff --git a/src/common.rs b/src/common.rs index ac5d1818..e9573a70 100644 --- a/src/common.rs +++ b/src/common.rs @@ -26,6 +26,18 @@ type Components = Vec>; #[cfg(feature = "gui-egui")] pub type Components = Vec>; +pub enum SimulatorError { + RunningStateIsErr(), +} +#[derive(PartialEq, Clone, Debug)] +pub enum RunningState { + Running, + StepTo(usize), + Halt, + Err, + Stopped, +} + #[cfg_attr(feature = "gui-vizia", derive(Lens))] #[derive(Clone)] pub struct Simulator { @@ -40,8 +52,17 @@ pub struct Simulator { pub history: Vec<(Vec, HashSet)>, pub component_ids: Vec, pub graph: Graph, - // Running state, (do we need it accessible from other crates?) - pub(crate) running: bool, + + // if set to true, running state is set to Halt when a component returns Warning + // pub(crate) might not be needed but easier than implementing setters and getters + pub(crate) halt_on_warning: bool, + // says if simulation is running, halted, stopped or stepping to a specific cycle + pub running_state: RunningState, + pub running_state_history: Vec, + // stores if components return a condition + // TODO add component condition history + pub component_condition: Vec<(Id, Condition)>, + pub component_condition_history: Vec>, // Used to determine active components pub sinks: Vec, @@ -101,12 +122,12 @@ pub trait Component { fn as_any(&self) -> &dyn Any; } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] pub enum Condition { Warning(String), - Error(String), - Assert(String), Halt(String), + Assert(String), + Error(String), } #[cfg(feature = "gui-egui")] diff --git a/src/gui_egui/component_ui.rs b/src/gui_egui/component_ui.rs index 7e199d00..60e69e6f 100644 --- a/src/gui_egui/component_ui.rs +++ b/src/gui_egui/component_ui.rs @@ -8,7 +8,9 @@ use egui::{ containers, Color32, ComboBox, Context, DragValue, Frame, Key, KeyboardShortcut, Margin, Modifiers, PointerButton, Pos2, Rect, Response, Rounding, Shape, Stroke, Ui, Vec2, Window, }; -use epaint::{CircleShape, Shadow}; +use epaint::CircleShape; + +use super::helper; pub fn rect_with_hover

( rect: Rect, @@ -25,9 +27,15 @@ where let r = ui.allocate_rect(rect, editor_mode_to_sense(editor_mode)); if r.hovered() && !r.dragged() { - containers::popup::show_tooltip_for(ui.ctx(), egui::Id::new(id), &rect, |ui| { - f(ui); - }); + containers::popup::show_tooltip_for( + ui.ctx(), + ui.layer_id(), + egui::Id::new(id), + &rect, + |ui| { + f(ui); + }, + ); } r } @@ -48,7 +56,7 @@ pub fn properties_window

( inner_margin: Margin::same(10f32), outer_margin: Margin::same(0f32), rounding: Rounding::same(10f32), - shadow: Shadow::small_dark(), + shadow: helper::shadow_small_dark(), fill: ui.visuals().panel_fill, stroke: ui.visuals().window_stroke, }) @@ -69,6 +77,8 @@ pub fn properties_window

( } } +/// This function add a horizontal ui section which displays +/// the given pos and allows for modification pub fn pos_drag_value(ui: &mut Ui, pos: &mut (f32, f32)) { ui.horizontal(|ui| { ui.label("pos x"); @@ -213,12 +223,12 @@ pub fn drag_logic( if ctx.input_mut(|i| { i.consume_shortcut(&KeyboardShortcut { modifiers: mod_none, - key: Key::Delete, + logical_key: Key::Delete, }) }) || ctx.input_mut(|i| { i.consume_shortcut(&KeyboardShortcut { modifiers: mod_none, - key: Key::X, + logical_key: Key::X, }) }) { delete = true; @@ -234,7 +244,7 @@ pub fn drag_logic( *pos = (pos.0 + delta.x, pos.1 + delta.y); } } - if resp.drag_released_by(PointerButton::Primary) + if resp.drag_stopped_by(PointerButton::Primary) && resp.interact_pointer_pos().unwrap().x < offset.x { delete = true; diff --git a/src/gui_egui/components/constant.rs b/src/gui_egui/components/constant.rs index 09b1dac8..27db380d 100644 --- a/src/gui_egui/components/constant.rs +++ b/src/gui_egui/components/constant.rs @@ -6,7 +6,9 @@ use crate::gui_egui::component_ui::{ }; use crate::gui_egui::editor::{EditorMode, EditorRenderReturn, GridOptions}; use crate::gui_egui::gui::EguiExtra; -use egui::{Align2, Area, Color32, DragValue, Order, Pos2, Rect, Response, RichText, Ui, Vec2}; +use egui::{ + Align2, Area, Color32, DragValue, Order, Pos2, Rect, Response, RichText, TextWrapMode, Ui, Vec2, +}; use log::trace; #[typetag::serde] @@ -29,15 +31,17 @@ impl EguiComponent for Constant { let mut offset = offset; offset.x += self.pos.0 * scale; offset.y += self.pos.1 * scale; - let area = Area::new(self.id.to_string()) + let area = Area::new(egui::Id::from(self.id.to_string())) .order(Order::Middle) .current_pos(offset.to_pos2()) .movable(false) .enabled(true) .interactable(false) .pivot(Align2::CENTER_CENTER) + .constrain(false) .show(ui.ctx(), |ui| { ui.set_clip_rect(clip_rect); + ui.style_mut().wrap_mode = Some(TextWrapMode::Extend); match editor_mode { EditorMode::Simulator => ui.label( RichText::new(format!("{}", self.value)) diff --git a/src/gui_egui/components/probe.rs b/src/gui_egui/components/probe.rs index 848171ad..c15f6c81 100644 --- a/src/gui_egui/components/probe.rs +++ b/src/gui_egui/components/probe.rs @@ -6,7 +6,7 @@ use crate::gui_egui::component_ui::{ }; use crate::gui_egui::editor::{EditorMode, EditorRenderReturn, GridOptions}; use crate::gui_egui::gui::EguiExtra; -use egui::{Align2, Area, Color32, Order, Pos2, Rect, Response, RichText, Ui, Vec2}; +use egui::{Align2, Area, Color32, Order, Pos2, Rect, Response, RichText, TextWrapMode, Ui, Vec2}; #[typetag::serde] impl EguiComponent for Probe { @@ -29,15 +29,17 @@ impl EguiComponent for Probe { Some(s) => s.get_input_value(&input), None => SignalValue::Uninitialized, }; - let area = Area::new(self.id.to_string()) + let area = Area::new(egui::Id::from(self.id.to_string())) .order(Order::Middle) .current_pos(offset.to_pos2()) .movable(false) .enabled(true) .interactable(false) .pivot(Align2::CENTER_CENTER) + .constrain(false) .show(ui.ctx(), |ui| { ui.set_clip_rect(clip_rect); + ui.style_mut().wrap_mode = Some(TextWrapMode::Extend); let text = if let SignalValue::Data(v) = value { format!("{:#010x}", v) } else { @@ -51,24 +53,23 @@ impl EguiComponent for Probe { ), _ => ui.label(RichText::new(text.clone()).size(scale * 12f32).underline()), } - .on_hover_text(text); + .on_hover_text({ + if let SignalValue::Data(v) = value { + format!("{:#010x}\nAs unsigned: {}\nAs signed: {}", v, v, v as i32) + } else { + text + } + }); }); - - let r = rect_with_hover( - area.response.rect, - clip_rect, - editor_mode, - ui, - self.id.clone(), - |ui| { - ui.label(format!("Id: {}", self.id.clone())); - if let SignalValue::Data(v) = value { - ui.label(format!("{:#010x}", v)); - } else { - ui.label(format!("{:?}", value)); - } - }, - ); + let rect = area.response.rect; + let r = rect_with_hover(rect, clip_rect, editor_mode, ui, self.id.clone(), |ui| { + ui.label(format!("Id: {}", self.id.clone())); + if let SignalValue::Data(v) = value { + ui.label(format!("{:#010x}", v)); + } else { + ui.label(format!("{:?}", value)); + } + }); match editor_mode { EditorMode::Simulator => (), _ => visualize_ports(ui, self.ports_location(), offset_old, scale, clip_rect), diff --git a/src/gui_egui/components/probe_edit.rs b/src/gui_egui/components/probe_edit.rs index cadead9b..6ca94558 100644 --- a/src/gui_egui/components/probe_edit.rs +++ b/src/gui_egui/components/probe_edit.rs @@ -6,7 +6,7 @@ use crate::gui_egui::component_ui::{ }; use crate::gui_egui::editor::{EditorMode, EditorRenderReturn, GridOptions}; use crate::gui_egui::gui::EguiExtra; -use egui::{Align2, Area, DragValue, Order, Pos2, Rect, Response, Ui, Vec2}; +use egui::{Align2, Area, DragValue, Order, Pos2, Rect, Response, TextStyle, Ui, Vec2}; #[typetag::serde] impl EguiComponent for ProbeEdit { @@ -25,13 +25,14 @@ impl EguiComponent for ProbeEdit { offset.x += self.pos.0 * scale; offset.y += self.pos.1 * scale; let interact = matches!(editor_mode, EditorMode::Simulator); - let area = Area::new(self.id.to_string()) + let area = Area::new(egui::Id::from(self.id.to_string())) .order(Order::Middle) .current_pos(offset.to_pos2()) .movable(false) .enabled(true) .interactable(interact) .pivot(Align2::CENTER_CENTER) + .constrain(false) .show(ui.ctx(), |ui| { ui.set_clip_rect(clip_rect); let hst = self.edit_history.clone(); @@ -41,6 +42,11 @@ impl EguiComponent for ProbeEdit { SignalValue::Data(d) => { let mut val = d; // todo: Somehow make this scale... + ui.style_mut() // manage to change the font size but not the background + .text_styles + .get_mut(&TextStyle::Button) + .unwrap() + .size = scale * 12f32; let r = ui.add(DragValue::new(&mut val)); *self.edit_history.write().unwrap().last_mut().unwrap() = TextSignal { text: format!("{}", val), diff --git a/src/gui_egui/components/wire.rs b/src/gui_egui/components/wire.rs index a7b184b1..a75e7979 100644 --- a/src/gui_egui/components/wire.rs +++ b/src/gui_egui/components/wire.rs @@ -5,12 +5,11 @@ use crate::gui_egui::component_ui::{ }; use crate::gui_egui::editor::{EditorMode, EditorRenderReturn, GridOptions, SnapPriority}; use crate::gui_egui::gui::EguiExtra; -use crate::gui_egui::helper::offset_helper; +use crate::gui_egui::helper::{offset_helper, shadow_small_dark}; use egui::{ Color32, DragValue, Frame, Key, KeyboardShortcut, Margin, Modifiers, PointerButton, Pos2, Rect, Response, Rounding, Shape, Stroke, Ui, Vec2, Window, }; -use epaint::Shadow; use log::trace; #[typetag::serde] @@ -130,7 +129,7 @@ impl EguiComponent for Wire { mac_cmd: false, command: false, }, - key: Key::Delete, + logical_key: Key::Delete, }) }) || ui.ctx().input_mut(|i| { i.consume_shortcut(&KeyboardShortcut { @@ -141,7 +140,7 @@ impl EguiComponent for Wire { mac_cmd: false, command: false, }, - key: Key::X, + logical_key: Key::X, }) }) { delete = true; @@ -150,7 +149,7 @@ impl EguiComponent for Wire { self.pos[i] = (self.pos[i].0 + delta.x, self.pos[i].1 + delta.y); self.pos[i + 1] = (self.pos[i + 1].0 + delta.x, self.pos[i + 1].1 + delta.y); } - if resp.drag_released_by(PointerButton::Primary) + if resp.drag_stopped_by(PointerButton::Primary) && resp.interact_pointer_pos().unwrap().x < offset.x { delete = true; @@ -164,7 +163,7 @@ impl EguiComponent for Wire { inner_margin: Margin::same(10f32), outer_margin: Margin::same(0f32), rounding: Rounding::same(10f32), - shadow: Shadow::small_dark(), + shadow: shadow_small_dark(), fill: ui.visuals().panel_fill, stroke: ui.visuals().window_stroke, }) diff --git a/src/gui_egui/editor.rs b/src/gui_egui/editor.rs index cb88ccb5..a5a4d9fc 100644 --- a/src/gui_egui/editor.rs +++ b/src/gui_egui/editor.rs @@ -392,9 +392,9 @@ impl Editor { } if central_panel.response.hovered() { ctx.input_mut(|i| { - if i.scroll_delta.y > 0f32 { + if i.raw_scroll_delta.y > 0f32 { keymap::view_zoom_in_fn(gui); - } else if i.scroll_delta.y < 0f32 { + } else if i.raw_scroll_delta.y < 0f32 { keymap::view_zoom_out_fn(gui); } }); diff --git a/src/gui_egui/gui.rs b/src/gui_egui/gui.rs index f3131150..342d0ccf 100644 --- a/src/gui_egui/gui.rs +++ b/src/gui_egui/gui.rs @@ -6,7 +6,7 @@ use crate::gui_egui::{ keymap::Shortcuts, menu::Menu, }; -use eframe::{egui, Frame}; +use eframe::egui; use egui::{ containers, CentralPanel, Color32, Context, PointerButton, Pos2, Rect, Sense, TopBottomPanel, Vec2, @@ -26,6 +26,7 @@ pub struct Gui { pub clip_rect: Rect, pub shortcuts: Shortcuts, pub pause: bool, + pub step_amount: usize, //TODO change this to be a menu struct, and maybe move pause and other here pub editor: Option, pub editor_use: bool, pub contexts: HashMap, @@ -57,13 +58,14 @@ pub fn gui(cs: ComponentStore, path: &PathBuf, library: Library) -> Result<(), e clip_rect: Rect::NOTHING, shortcuts: Shortcuts::new(), pause: true, + step_amount: 10, editor: None, editor_use: false, contexts, library, }; - eframe::run_native("SyncRim", options, Box::new(|_cc| Box::new(gui))) + eframe::run_native("SyncRim", options, Box::new(|_cc| Ok(Box::new(gui)))) } impl eframe::App for Gui { @@ -103,23 +105,14 @@ impl eframe::App for Gui { self.top_bar(ctx); if self.simulator.is_some() { // self.side_panel(ctx); - self.draw_area(ctx, frame); - if self.simulator.as_ref().unwrap().running { - self.simulator.as_mut().unwrap().clock(); + if self.simulator.as_ref().unwrap().is_running() { + self.simulator.as_mut().unwrap().run(); + + // This makes the ui run agin as to not stop the simulation + // when no ui events are happening ctx.request_repaint(); } - } - } - } - fn post_rendering(&mut self, _window_size_px: [u32; 2], _frame: &Frame) { - if !self.pause { - match &mut self.simulator { - Some(s) => { - if s.running { - s.run(); - } - } - None => {} + self.draw_area(ctx, frame); } } } @@ -161,17 +154,13 @@ impl Gui { } if central_panel.response.hovered() { ctx.input_mut(|i| { - if i.scroll_delta.y > 0f32 { + if i.raw_scroll_delta.y > 0f32 { keymap::view_zoom_in_fn(self); - } else if i.scroll_delta.y < 0f32 { + } else if i.raw_scroll_delta.y < 0f32 { keymap::view_zoom_out_fn(self); } }); } - if self.simulator.as_ref().unwrap().running { - self.simulator.as_mut().unwrap().clock(); - ctx.request_repaint(); - } } fn top_bar(&mut self, ctx: &Context) { diff --git a/src/gui_egui/helper.rs b/src/gui_egui/helper.rs index 4ccb5520..2652fffe 100644 --- a/src/gui_egui/helper.rs +++ b/src/gui_egui/helper.rs @@ -1,6 +1,7 @@ use crate::common::{Components, Ports}; use crate::gui_egui::editor::{EditorMode, SnapPriority}; -use egui::{Pos2, Rect, Sense, Vec2}; +use egui::{Color32, Pos2, Rect, Sense, Vec2}; +use epaint::Shadow; pub fn offset_reverse_helper_pos2(xy: Pos2, scale: f32, offset: Vec2) -> Pos2 { egui::Pos2 { @@ -106,3 +107,12 @@ pub fn editor_mode_to_sense(editor_mode: EditorMode) -> Sense { }, } } + +pub fn shadow_small_dark() -> Shadow { + Shadow { + offset: Vec2 { x: 5.0, y: 5.0 }, + blur: 5.0, + spread: 0.0, + color: Color32::BLACK, + } +} diff --git a/src/gui_egui/keymap.rs b/src/gui_egui/keymap.rs index 6ca0cf39..8217022b 100644 --- a/src/gui_egui/keymap.rs +++ b/src/gui_egui/keymap.rs @@ -67,15 +67,15 @@ impl Shortcuts { Shortcuts { file_new: KeyboardShortcut { modifiers: ctrl, - key: Key::N, + logical_key: Key::N, }, file_open: KeyboardShortcut { modifiers: ctrl, - key: Key::O, + logical_key: Key::O, }, file_save: KeyboardShortcut { modifiers: ctrl, - key: Key::S, + logical_key: Key::S, }, file_save_as: KeyboardShortcut { modifiers: Modifiers { @@ -85,43 +85,43 @@ impl Shortcuts { mac_cmd: false, command: false, }, - key: Key::S, + logical_key: Key::S, }, file_editor_toggle: KeyboardShortcut { modifiers: ctrl, - key: Key::E, + logical_key: Key::E, }, file_preferences: KeyboardShortcut { modifiers: ctrl, - key: Key::P, + logical_key: Key::P, }, file_quit: KeyboardShortcut { modifiers: ctrl, - key: Key::Q, + logical_key: Key::Q, }, edit_cut: KeyboardShortcut { modifiers: ctrl, - key: Key::X, + logical_key: Key::X, }, edit_copy: KeyboardShortcut { modifiers: ctrl, - key: Key::C, + logical_key: Key::C, }, edit_paste: KeyboardShortcut { modifiers: ctrl, - key: Key::P, + logical_key: Key::P, }, view_zoom_in: KeyboardShortcut { modifiers: ctrl, - key: Key::PlusEquals, + logical_key: Key::Plus, }, view_zoom_out: KeyboardShortcut { modifiers: ctrl, - key: Key::Minus, + logical_key: Key::Minus, }, view_grid_toggle: KeyboardShortcut { modifiers: ctrl, - key: Key::G, + logical_key: Key::G, }, view_grid_snap_toggle: KeyboardShortcut { modifiers: Modifiers { @@ -131,19 +131,19 @@ impl Shortcuts { mac_cmd: false, command: false, }, - key: Key::G, + logical_key: Key::G, }, control_play: KeyboardShortcut { modifiers: none, - key: Key::F6, + logical_key: Key::F6, }, control_play_toggle: KeyboardShortcut { modifiers: none, - key: Key::F5, + logical_key: Key::F5, }, control_pause: KeyboardShortcut { modifiers: shift, - key: Key::F5, + logical_key: Key::F5, }, control_reset: KeyboardShortcut { modifiers: Modifiers { @@ -153,23 +153,23 @@ impl Shortcuts { mac_cmd: false, command: false, }, - key: Key::F5, + logical_key: Key::F5, }, control_step_forward: KeyboardShortcut { modifiers: none, - key: Key::F10, + logical_key: Key::F10, }, control_step_back: KeyboardShortcut { modifiers: shift, - key: Key::F10, + logical_key: Key::F10, }, editor_wire_mode: KeyboardShortcut { modifiers: none, - key: Key::W, + logical_key: Key::W, }, editor_escape: KeyboardShortcut { modifiers: none, - key: Key::Escape, + logical_key: Key::Escape, }, } } @@ -386,7 +386,7 @@ pub fn control_play_toggle_fn(gui: &mut Gui) { pub fn control_play_fn(gui: &mut Gui) { if !gui.editor_use { gui.pause = false; - gui.simulator.as_mut().unwrap().running = true; + let _ = gui.simulator.as_mut().unwrap().set_running(); //gui.simulator.as_mut().unwrap().run(); //gui.pause = true; } @@ -399,7 +399,7 @@ pub fn control_play_fn(gui: &mut Gui) { pub fn control_pause_fn(gui: &mut Gui) { if !gui.editor_use { gui.pause = true; - gui.simulator.as_mut().unwrap().running = false; + let _ = gui.simulator.as_mut().unwrap().stop(); } } pub fn control_reset_fn(gui: &mut Gui) { diff --git a/src/gui_egui/library.rs b/src/gui_egui/library.rs index 91982116..1d2e8db1 100644 --- a/src/gui_egui/library.rs +++ b/src/gui_egui/library.rs @@ -5,7 +5,9 @@ use crate::gui_egui::{ editor_wire_mode::get_grid_snap, helper::{id_ports_of_all_components, offset_reverse_helper_pos2, unique_component_name}, }; -use egui::{Context, CursorIcon, LayerId, PointerButton, Pos2, Rect, Response, Ui, Vec2}; +use egui::{ + Context, CursorIcon, LayerId, PointerButton, Pos2, Rect, Response, Ui, UiStackInfo, Vec2, +}; use std::{collections::HashMap, path::PathBuf, rc::Rc}; pub struct InputMode { @@ -41,6 +43,7 @@ pub fn input_mode(ctx: &Context, e: &mut Editor, cpr: Response, layer_id: Option "input".into(), clip_rect, Rect::EVERYTHING, + UiStackInfo::new(egui::UiKind::Frame), ); let pos = if e.grid.enable && e.grid.snap_enable { match get_grid_snap( diff --git a/src/gui_egui/menu.rs b/src/gui_egui/menu.rs index 6155e0ba..47c7ae99 100644 --- a/src/gui_egui/menu.rs +++ b/src/gui_egui/menu.rs @@ -36,8 +36,36 @@ impl Menu { if ui.button("⏸").clicked() { keymap::control_pause_fn(gui); } + ui.separator(); + + ui.add(DragValue::new(&mut gui.step_amount).prefix("Step: ")); + if ui.button("⟳").clicked() { + // TODO dont have simulator here add keymap + if let Some(s) = gui.simulator.as_mut() { + let _ = s.set_step_to(s.cycle + gui.step_amount); + } + } + + ui.separator(); + if let Some(s) = gui.simulator.as_ref() { - ui.label(format!("Cycle #{}", s.cycle)); + ui.label(format!("Cycle: {}", s.cycle)); + + // This displays the current state of the simulation, + // if hovered displays the component condition vector + let r = ui.label(format!("State: {:?}", s.get_state())); + + // if there are some components that reported a condition show it when + // state is hovered over + if let Some(cond_vec) = s.get_component_condition() { + r.on_hover_text( + cond_vec // format all id condition pairs and sum them to a string + .iter() + .fold(String::from("Reported conditions"), |acc, id_cond| { + acc + &format!("\nID: {}, {:?}", id_cond.0, id_cond.1) + }), + ); + } } }); } @@ -66,13 +94,13 @@ impl Menu { }); ui.horizontal(|ui| { ui.label("Grid Size:"); - ui.add(DragValue::new(&mut grid_size).clamp_range(5f32..=10000f32)); + ui.add(DragValue::new(&mut grid_size).range(5f32..=10000f32)); }); ui.horizontal(|ui| { ui.label("Grid Opacity:"); ui.add( DragValue::new(&mut grid_opacity) - .clamp_range(0f32..=1f32) + .range(0f32..=1f32) .speed(0.02f32), ); }); @@ -82,7 +110,7 @@ impl Menu { }); ui.horizontal(|ui| { ui.label("Grid Snap Distance:"); - ui.add(DragValue::new(&mut grid_snap_distance).clamp_range(0f32..=100f32)); + ui.add(DragValue::new(&mut grid_snap_distance).range(0f32..=100f32)); }); }); editor(gui).scale = scale; diff --git a/src/gui_vizia/gui.rs b/src/gui_vizia/gui.rs index 7f401d69..02f69357 100644 --- a/src/gui_vizia/gui.rs +++ b/src/gui_vizia/gui.rs @@ -1,5 +1,5 @@ use crate::{ - common::{ComponentStore, Simulator}, + common::{ComponentStore, RunningState, Simulator}, gui_vizia::{grid::Grid, keymap::init_keymap, menu::Menu, transport::Transport}, }; use rfd::FileDialog; @@ -70,15 +70,19 @@ impl Model for GuiData { self.simulator.reset(); } GuiEvent::Play => { - self.simulator.running = true; + self.simulator.running_state = RunningState::Running; self.simulator.clock(); } GuiEvent::Pause => { - self.simulator.running = false; + self.simulator.running_state = RunningState::Halt; } GuiEvent::PlayToggle => { - self.simulator.running = !self.simulator.running; - if self.simulator.running { + if self.simulator.running_state == RunningState::Running { + self.simulator.running_state = RunningState::Halt; + } else { + self.simulator.running_state = RunningState::Running; + } + if self.simulator.running_state == RunningState::Running { self.simulator.clock(); } } @@ -154,7 +158,7 @@ pub fn gui(cs: ComponentStore, path: &PathBuf) { //let simulator = GuiData::simulator.get(cx); let simulator = GuiData::simulator.view(cx.data().unwrap()).unwrap(); - if simulator.running { + if simulator.running_state == RunningState::Running { trace!("send clock event"); cx.emit(GuiEvent::Clock); }; diff --git a/src/gui_vizia/transport.rs b/src/gui_vizia/transport.rs index bacfa2e2..c0460e6f 100644 --- a/src/gui_vizia/transport.rs +++ b/src/gui_vizia/transport.rs @@ -1,4 +1,4 @@ -use crate::common::Simulator; +use crate::common::{RunningState, Simulator}; use crate::gui_vizia::{GuiData, GuiEvent}; use vizia::{icons, prelude::*}; pub(crate) struct Transport {} @@ -78,13 +78,15 @@ impl Transport { |cx| { Label::new( cx, - GuiData::simulator.then(Simulator::running).map(|running| { - if *running { - icons::ICON_PLAYER_PLAY_FILLED - } else { - icons::ICON_PLAYER_PLAY - } - }), + GuiData::simulator + .then(Simulator::running_state) + .map(|running| { + if *running == RunningState::Running { + icons::ICON_PLAYER_PLAY_FILLED + } else { + icons::ICON_PLAYER_PLAY + } + }), ) .on_press(|cx| cx.emit(GuiEvent::Play)) .class("icon") @@ -107,13 +109,15 @@ impl Transport { |cx| { Label::new( cx, - GuiData::simulator.then(Simulator::running).map(|running| { - if *running { - icons::ICON_PLAYER_PAUSE - } else { - icons::ICON_PLAYER_PAUSE_FILLED - } - }), + GuiData::simulator + .then(Simulator::running_state) + .map(|running| { + if *running == RunningState::Running { + icons::ICON_PLAYER_PAUSE + } else { + icons::ICON_PLAYER_PAUSE_FILLED + } + }), ) .on_press(|cx| cx.emit(GuiEvent::Pause)) .class("icon") diff --git a/src/simulator.rs b/src/simulator.rs index ab74d829..a72b1d5c 100644 --- a/src/simulator.rs +++ b/src/simulator.rs @@ -1,6 +1,6 @@ use crate::common::{ - Component, ComponentStore, Condition, Id, Input, OutputType, Signal, SignalFmt, SignalValue, - Simulator, + Component, ComponentStore, Condition, Id, Input, OutputType, RunningState, Signal, SignalFmt, + SignalValue, Simulator, SimulatorError, }; use log::*; use petgraph::{ @@ -167,11 +167,15 @@ impl Simulator { history: vec![], component_ids, graph, - running: false, + halt_on_warning: false, + running_state: RunningState::Stopped, + component_condition: vec![], // used for determine active components sinks, inputs_read: HashMap::new(), active: HashSet::new(), + running_state_history: vec![], + component_condition_history: vec![], }; trace!("sim_state {:?}", simulator.sim_state); @@ -282,31 +286,71 @@ impl Simulator { /// iterate over the evaluators and increase clock by one pub fn clock(&mut self) { + // if state is error stop simulator from clocking + if self.running_state == RunningState::Err { + return; + } // push current state self.history .push((self.sim_state.clone(), self.active.clone())); trace!("cycle:{}", self.cycle); + self.component_condition_history + .push(self.component_condition.clone()); + self.running_state_history.push(self.running_state.clone()); self.clean_active(); + // clear component condition data for this new cycle + self.component_condition.clear(); + for component in self.ordered_components.clone() { trace!("evaluating component:{}", component.get_id_ports().0); + + // Clock component and add its condition if error self.component_condition match component.clock(self) { Ok(_) => {} - Err(cond) => match cond { - Condition::Warning(warn) => { - trace!("warning {}", warn) - } - Condition::Error(err) => panic!("err {}", err), - Condition::Assert(assert) => { - error!("assertion failed {}", assert); - self.running = false; + Err(cond) => { + self.component_condition + .push((component.get_id_ports().0, cond.clone())); + // is this trace necessary? + match cond { + Condition::Warning(warn) => { + trace!("warning {}", warn); + } + Condition::Error(err) => { + error!("component error {}", err); + } + Condition::Assert(assert) => { + error!("assertion failed {}", assert); + } + Condition::Halt(halt) => { + info!("halt {}", halt); + } } - Condition::Halt(halt) => { - self.running = false; - info!("halt {}", halt) + } + } + } + // if there exist a component condition + // get the most severe component condition + // and update running state accordingly + // order of servery from low to high is + // warning -> halt -> assert -> error + if let Some(max) = self.component_condition.iter().max_by(|a, b| a.1.cmp(&b.1)) { + match max.1 { + Condition::Warning(_) => { + if self.halt_on_warning { + self.running_state = RunningState::Halt; } - }, + } + Condition::Halt(_) => { + self.running_state = RunningState::Halt; + } + Condition::Assert(_) => { + self.running_state = RunningState::Halt; + } + Condition::Error(_) => { + self.running_state = RunningState::Err; + } } } self.cycle = self.history.len(); @@ -350,23 +394,42 @@ impl Simulator { self.active.contains(id) } - /// free running mode until Halt condition + /// free running mode until Halt condition or target cycle, breaks after 1/30 sec pub fn run(&mut self) { use std::time::Instant; let now = Instant::now(); + let mut i: u32 = 0; // used to quickly and inaccurately test performance while now.elapsed().as_millis() < 1000 / 30 { - //60Hz - if self.running { - self.clock() + //30Hz + i += 1; + match self.running_state { + RunningState::Running => self.clock(), + RunningState::StepTo(target_cycle) => { + if self.cycle < target_cycle { + self.clock(); + } else { + self.running_state = RunningState::Stopped; + break; + } + } + _ => { + break; + } } } + trace!("clock per run {}", i) } pub fn run_threaded(&mut self) {} /// stop the simulator from gui or other external reason - pub fn stop(&mut self) { - self.running = false; + pub fn stop(&mut self) -> Result<(), SimulatorError> { + if self.running_state != RunningState::Err { + self.running_state = RunningState::Stopped; + Ok(()) + } else { + Err(SimulatorError::RunningStateIsErr()) + } } /// reverse simulation using history if clock > 1 @@ -379,6 +442,14 @@ impl Simulator { // to ensure that history length and cycle count complies self.cycle = self.history.len(); + // TODO add component_condition history pop + + self.component_condition = self.component_condition_history.pop().unwrap(); + match self.running_state_history.pop().unwrap() { + RunningState::Halt => self.running_state = RunningState::Halt, + RunningState::Err => self.running_state = RunningState::Err, + _ => self.running_state = RunningState::Stopped, + }; for component in self.ordered_components.clone() { component.un_clock(); @@ -392,10 +463,14 @@ impl Simulator { // with the exception that self.clock() needs to be last self.history = vec![]; self.cycle = 0; - self.stop(); + self.running_state = RunningState::Stopped; + self.component_condition_history = vec![]; + self.running_state_history = vec![]; + let _ = self.stop(); self.sim_state.iter_mut().for_each(|val| *val = 0.into()); + // TODO probably needed to reset component_condition, maybe is handeld correctly by clock who knows? for component in self.ordered_components.clone() { component.reset(); } @@ -403,8 +478,49 @@ impl Simulator { self.clock(); } - pub fn get_state(&self) -> bool { - self.running + // return the enum which describes the current state + // to get component_condition use get_component_condition() + pub fn get_state(&self) -> &RunningState { + &self.running_state + } + + pub fn is_running(&self) -> bool { + match self.running_state { + RunningState::Running => true, + RunningState::StepTo(_) => true, + RunningState::Halt => false, + RunningState::Err => false, + RunningState::Stopped => false, + } + } + + // TODO return error if simulator running state is Err + pub fn set_running(&mut self) -> Result<(), SimulatorError> { + if self.running_state != RunningState::Err { + self.running_state = RunningState::Running; + Ok(()) + } else { + Err(SimulatorError::RunningStateIsErr()) + } + } + + // TODO return error if simulator running state is Err + pub fn set_step_to(&mut self, target_cycle: usize) -> Result<(), SimulatorError> { + if self.running_state != RunningState::Err { + self.running_state = RunningState::StepTo(target_cycle); + Ok(()) + } else { + Err(SimulatorError::RunningStateIsErr()) + } + } + // This function returns Some() if there are any components + // in the current cycle that reported any Condition + pub fn get_component_condition(&self) -> Option> { + if self.component_condition.is_empty() { + None + } else { + Some(self.component_condition.clone()) + } } /// save as `dot` file with `.gv` extension