diff --git a/CHANGELOG.md b/CHANGELOG.md index 35770495..9cc5c553 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ Tracking changes per date: +## 240909 + +- Added un-clock history for set of active components + +## 240908 + +- Added functionality to determine set of active components + ## 230801 - Simulator run/halt is implemented in `vizia` using a simple eventing mechanism. Later we might want to spawn a simulation thread for faster execution (right now its tied to frame rate). diff --git a/Cargo.toml b/Cargo.toml index ae098d90..ec47c85d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ typetag = "0.2.10" [dependencies.vizia] git = "https://github.com/vizia/vizia.git" +rev = "7093bfd518c4bee5544a75c2ffc92dfe4f817bc0" #path = "../vizia" optional = true diff --git a/examples/mux2.rs b/examples/mux2.rs new file mode 100644 index 00000000..4d2000b5 --- /dev/null +++ b/examples/mux2.rs @@ -0,0 +1,111 @@ +use std::path::PathBuf; +#[cfg(feature = "gui-egui")] +use syncrim::gui_egui::editor::Library; +use syncrim::{ + common::{ComponentStore, Input}, + components::*, + fern::fern_setup, +}; +fn main() { + fern_setup(); + let cs = ComponentStore { + store: vec![ + Mux::rc_new( + "mux", + (200.0, 200.0), + Input::new("ctrl", "out"), + vec![ + Input::new("c1", "out"), + Input::new("c2", "out"), + Input::new("c3", "out"), + Input::new("c4", "out"), + ], + ), + Mux::rc_new( + "mux2", + (300.0, 300.0), + Input::new("mux", "out"), + vec![ + Input::new("c1_2", "out"), + Input::new("c2_2", "out"), + Input::new("c3_2", "out"), + Input::new("c4_2", "out"), + ], + ), + ProbeEdit::rc_new("ctrl", (190.0, 100.0)), + Wire::rc_new( + "w0", + vec![(190.0, 110.0), (190.0, 150.0)], + Input::new("ctrl", "out"), + ), + Constant::rc_new("c1", (140.0, 170.0), 0), + Constant::rc_new("c2", (140.0, 190.0), 1), + Constant::rc_new("c3", (140.0, 210.0), 2), + Constant::rc_new("c4", (140.0, 230.0), 3), + Constant::rc_new("c1_2", (140.0, 270.0), 20), + Constant::rc_new("c2_2", (140.0, 290.0), 21), + Constant::rc_new("c3_2", (140.0, 310.0), 22), + Constant::rc_new("c4_2", (140.0, 330.0), 23), + Wire::rc_new( + "w1", + vec![(150.0, 170.0), (180.0, 170.0)], + Input::new("c1", "out"), + ), + Wire::rc_new( + "w2", + vec![(150.0, 190.0), (180.0, 190.0)], + Input::new("c2", "out"), + ), + Wire::rc_new( + "w3", + vec![(150.0, 210.0), (180.0, 210.0)], + Input::new("c3", "out"), + ), + Wire::rc_new( + "w4", + vec![(150.0, 230.0), (180.0, 230.0)], + Input::new("c4", "out"), + ), + Wire::rc_new( + "w1_2", + vec![(150.0, 270.0), (280.0, 270.0)], + Input::new("c1_2", "out"), + ), + Wire::rc_new( + "w2_2", + vec![(150.0, 290.0), (280.0, 290.0)], + Input::new("c2_2", "out"), + ), + Wire::rc_new( + "w3_2", + vec![(150.0, 310.0), (280.0, 310.0)], + Input::new("c3_2", "out"), + ), + Wire::rc_new( + "w4_2", + vec![(150.0, 330.0), (280.0, 330.0)], + Input::new("c4_2", "out"), + ), + Wire::rc_new( + "w5", + vec![(210.0, 200.0), (290.0, 200.0), (290.0, 250.0)], + Input::new("mux", "out"), + ), + Wire::rc_new( + "w6", + vec![(310.0, 300.0), (350.0, 300.0)], + Input::new("mux", "out"), + ), + Probe::rc_new("p_mux", (360.0, 300.0), Input::new("mux2", "out")), + ], + }; + + let path = PathBuf::from("mux2.json"); + cs.save_file(&path); + + #[cfg(feature = "gui-egui")] + syncrim::gui_egui::gui(cs, &path, Library::default()).ok(); + + #[cfg(feature = "gui-vizia")] + syncrim::gui_vizia::gui(cs, &path); +} diff --git a/examples/mux3.rs b/examples/mux3.rs new file mode 100644 index 00000000..aee41757 --- /dev/null +++ b/examples/mux3.rs @@ -0,0 +1,121 @@ +use std::path::PathBuf; +#[cfg(feature = "gui-egui")] +use syncrim::gui_egui::editor::Library; +use syncrim::{ + common::{ComponentStore, Input}, + components::*, + fern::fern_setup, +}; +fn main() { + fern_setup(); + let cs = ComponentStore { + store: vec![ + Mux::rc_new( + "mux", + (200.0, 200.0), + Input::new("ctrl", "out"), + vec![ + Input::new("c1", "out"), + Input::new("c2", "out"), + Input::new("c3", "out"), + Input::new("c4", "out"), + ], + ), + Mux::rc_new( + "mux2", + (300.0, 300.0), + Input::new("ctrl2", "out"), + vec![ + Input::new("mux", "out"), + Input::new("c2_2", "out"), + Input::new("c3_2", "out"), + Input::new("c4_2", "out"), + ], + ), + ProbeEdit::rc_new("ctrl", (190.0, 100.0)), + ProbeEdit::rc_new("ctrl2", (290.0, 200.0)), + Wire::rc_new( + "w0", + vec![(190.0, 110.0), (190.0, 150.0)], + Input::new("ctrl", "out"), + ), + Wire::rc_new( + "w0_2", + vec![(290.0, 210.0), (290.0, 250.0)], + Input::new("ctrl2", "out"), + ), + Constant::rc_new("c1", (140.0, 170.0), 0), + Constant::rc_new("c2", (140.0, 190.0), 1), + Constant::rc_new("c3", (140.0, 210.0), 2), + Constant::rc_new("c4", (140.0, 230.0), 3), + Constant::rc_new("c2_2", (140.0, 290.0), 21), + Constant::rc_new("c3_2", (140.0, 310.0), 22), + Constant::rc_new("c4_2", (140.0, 330.0), 23), + Wire::rc_new( + "w1", + vec![(150.0, 170.0), (180.0, 170.0)], + Input::new("c1", "out"), + ), + Wire::rc_new( + "w2", + vec![(150.0, 190.0), (180.0, 190.0)], + Input::new("c2", "out"), + ), + Wire::rc_new( + "w3", + vec![(150.0, 210.0), (180.0, 210.0)], + Input::new("c3", "out"), + ), + Wire::rc_new( + "w4", + vec![(150.0, 230.0), (180.0, 230.0)], + Input::new("c4", "out"), + ), + // Wire::rc_new( + // "w1_2", + // vec![(150.0, 270.0), (280.0, 270.0)], + // Input::new("c1_2", "out"), + // ), + Wire::rc_new( + "w2_2", + vec![(150.0, 290.0), (280.0, 290.0)], + Input::new("c2_2", "out"), + ), + Wire::rc_new( + "w3_2", + vec![(150.0, 310.0), (280.0, 310.0)], + Input::new("c3_2", "out"), + ), + Wire::rc_new( + "w4_2", + vec![(150.0, 330.0), (280.0, 330.0)], + Input::new("c4_2", "out"), + ), + Wire::rc_new( + "w5", + vec![ + (210.0, 200.0), + (230.0, 200.0), + (230.0, 270.0), + (280.0, 270.0), + ], + Input::new("mux", "out"), + ), + Wire::rc_new( + "w6", + vec![(310.0, 300.0), (350.0, 300.0)], + Input::new("mux2", "out"), + ), + Probe::rc_new("probe_mux", (360.0, 300.0), Input::new("mux2", "out")), + ], + }; + + let path = PathBuf::from("mux2.json"); + cs.save_file(&path); + + #[cfg(feature = "gui-egui")] + syncrim::gui_egui::gui(cs, &path, Library::default()).ok(); + + #[cfg(feature = "gui-vizia")] + syncrim::gui_vizia::gui(cs, &path); +} diff --git a/src/common.rs b/src/common.rs index 69f1fa9d..ac5d1818 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,7 +1,10 @@ use petgraph::Graph; use serde::{Deserialize, Serialize}; use std::any::Any; -use std::{collections::HashMap, rc::Rc}; +use std::{ + collections::{HashMap, HashSet}, + rc::Rc, +}; #[cfg(feature = "gui-egui")] use crate::gui_egui::editor::{EditorMode, EditorRenderReturn, GridOptions, SnapPriority}; @@ -34,11 +37,16 @@ pub struct Simulator { pub sim_state: Vec, pub id_nr_outputs: IdNrOutputs, pub id_field_index: IdFieldIndex, - pub history: Vec>, + 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, + + // Used to determine active components + pub sinks: Vec, + pub inputs_read: HashMap>, + pub active: HashSet, } #[derive(Serialize, Deserialize)] @@ -82,6 +90,13 @@ pub trait Component { fn un_clock(&self) {} /// reset component internal state to initial value fn reset(&self) {} + + /// consider component to be a sink + /// either output to environment (e.g., for visualization) + /// or stateful (e.g., register) + fn is_sink(&self) -> bool { + false + } /// any fn as_any(&self) -> &dyn Any; } @@ -177,7 +192,7 @@ impl Ports { } } -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)] pub struct Input { pub id: Id, pub field: Id, diff --git a/src/components/add.rs b/src/components/add.rs index 005b7f65..b64298ca 100644 --- a/src/components/add.rs +++ b/src/components/add.rs @@ -60,8 +60,8 @@ impl Component for Add { // propagate addition to output fn clock(&self, simulator: &mut Simulator) -> Result<(), Condition> { // get input values - let a_in = u32::try_from(simulator.get_input_value(&self.a_in)); - let b_in = u32::try_from(simulator.get_input_value(&self.b_in)); + let a_in = u32::try_from(simulator.get_input_value_mut(self.id.clone(), &self.a_in)); + let b_in = u32::try_from(simulator.get_input_value_mut(self.id.clone(), &self.b_in)); let (value, overflow, res) = match (&a_in, &b_in) { (Ok(a), Ok(b)) => { diff --git a/src/components/mux.rs b/src/components/mux.rs index 1883ebbc..f8d83001 100644 --- a/src/components/mux.rs +++ b/src/components/mux.rs @@ -62,13 +62,16 @@ impl Component for Mux { // propagate selected input value to output fn clock(&self, simulator: &mut Simulator) -> Result<(), Condition> { // get input value - let select: SignalValue = simulator.get_input_value(&self.select); + let select: SignalValue = simulator.get_input_value_mut(self.id.clone(), &self.select); trace!("-----------{}------------", self.id); let (value, res) = if let Ok(select) = TryInto::::try_into(select) { let select = select as usize; trace!("select {}", select); if select < self.m_in.len() { - (simulator.get_input_value(&self.m_in[select]), Ok(())) + ( + simulator.get_input_value_mut(self.id.clone(), &self.m_in[select]), + Ok(()), + ) } else { ( SignalValue::Unknown, diff --git a/src/components/probe.rs b/src/components/probe.rs index 9f8c2a82..d07fce2d 100644 --- a/src/components/probe.rs +++ b/src/components/probe.rs @@ -1,6 +1,6 @@ #[cfg(feature = "gui-egui")] use crate::common::EguiComponent; -use crate::common::{Component, Id, Input, InputPort, OutputType, Ports}; +use crate::common::{Component, Condition, Id, Input, InputPort, OutputType, Ports, Simulator}; use log::*; use serde::{Deserialize, Serialize}; use std::any::Any; @@ -47,12 +47,23 @@ impl Component for Probe { ) } + fn clock(&self, simulator: &mut Simulator) -> Result<(), Condition> { + // get input value + let value = simulator.get_input_value_mut(self.id.clone(), &self.input); + trace!("probe: register id {} in {:?}", self.id, value); + Ok(()) + } + fn set_id_port(&mut self, target_port_id: Id, new_input: Input) { if target_port_id.as_str() == PROBE_IN_ID { self.input = new_input } } + fn is_sink(&self) -> bool { + true + } + fn as_any(&self) -> &dyn Any { self } diff --git a/src/components/register.rs b/src/components/register.rs index cba6e29a..1752cebb 100644 --- a/src/components/register.rs +++ b/src/components/register.rs @@ -49,7 +49,7 @@ impl Component for Register { // propagate input value to output fn clock(&self, simulator: &mut Simulator) -> Result<(), Condition> { // get input value - let value = simulator.get_input_value(&self.r_in); + let value = simulator.get_input_value_mut(self.id.clone(), &self.r_in); // set output simulator.set_out_value(&self.id, "out", value); trace!("eval: register id {} in {:?}", self.id, value); @@ -62,6 +62,10 @@ impl Component for Register { } } + fn is_sink(&self) -> bool { + true + } + fn as_any(&self) -> &dyn Any { self } diff --git a/src/fern.rs b/src/fern.rs index eedf3f78..b8a03bef 100644 --- a/src/fern.rs +++ b/src/fern.rs @@ -32,8 +32,14 @@ pub fn fern_setup() { #[cfg(feature = "gui-egui")] let f = f - .level_for("eframe::native::run", LevelFilter::Info) - .level_for("async_io::driver", LevelFilter::Warn); + .level_for("eframe", LevelFilter::Warn) + .level_for("async_io", LevelFilter::Warn) + .level_for("polling", LevelFilter::Warn) + .level_for("arboard", LevelFilter::Warn) + .level_for("egui_glow", LevelFilter::Warn) + .level_for("syncrim::gui_egui::components", LevelFilter::Warn) + // .level_for("egui", LevelFilter::Error) + ; f // Output to stdout, files, and other Dispatch configurations diff --git a/src/gui_egui/components/constant.rs b/src/gui_egui/components/constant.rs index 88a70186..09b1dac8 100644 --- a/src/gui_egui/components/constant.rs +++ b/src/gui_egui/components/constant.rs @@ -7,6 +7,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, DragValue, Order, Pos2, Rect, Response, RichText, Ui, Vec2}; +use log::trace; #[typetag::serde] impl EguiComponent for Constant { @@ -20,6 +21,10 @@ impl EguiComponent for Constant { clip_rect: Rect, editor_mode: EditorMode, ) -> Option> { + let is_active = _simulator.map_or(false, |sim| sim.is_active(&self.id)); + + trace!("render constant {}, active {}", self.id, is_active); + let offset_old = offset; let mut offset = offset; offset.x += self.pos.0 * scale; @@ -37,7 +42,11 @@ impl EguiComponent for Constant { EditorMode::Simulator => ui.label( RichText::new(format!("{}", self.value)) .size(scale * 12f32) - .background_color(Color32::LIGHT_GREEN), + .background_color(if is_active { + Color32::LIGHT_GREEN + } else { + Color32::LIGHT_GRAY + }), ), _ => ui.label( RichText::new(format!("{}", self.value)) diff --git a/src/gui_egui/components/mux.rs b/src/gui_egui/components/mux.rs index d2a747f6..45b82029 100644 --- a/src/gui_egui/components/mux.rs +++ b/src/gui_egui/components/mux.rs @@ -8,6 +8,7 @@ use crate::gui_egui::editor::{EditorMode, EditorRenderReturn, GridOptions}; use crate::gui_egui::gui::EguiExtra; use crate::gui_egui::helper::offset_helper; use egui::{Color32, Pos2, Rect, Response, Shape, Stroke, Ui, Vec2}; +use log::trace; #[typetag::serde] impl EguiComponent for Mux { @@ -21,6 +22,12 @@ impl EguiComponent for Mux { clip_rect: Rect, editor_mode: EditorMode, ) -> Option> { + let is_active = simulator + .as_ref() + .map_or(false, |sim| sim.is_active(&self.id)); + + trace!("render mux {}, active {}", self.id, is_active); + // 41x(20*ports + 11) // middle: 21x ((20*ports + 10)/2+1)y (0 0) let oh: fn((f32, f32), f32, Vec2) -> Pos2 = offset_helper; @@ -66,7 +73,11 @@ impl EguiComponent for Mux { ], Stroke { width: scale, - color: Color32::RED, + color: if is_active { + Color32::RED + } else { + Color32::BLACK + }, }, )); diff --git a/src/gui_egui/components/wire.rs b/src/gui_egui/components/wire.rs index 73d28a38..a7b184b1 100644 --- a/src/gui_egui/components/wire.rs +++ b/src/gui_egui/components/wire.rs @@ -11,6 +11,7 @@ use egui::{ Response, Rounding, Shape, Stroke, Ui, Vec2, Window, }; use epaint::Shadow; +use log::trace; #[typetag::serde] impl EguiComponent for Wire { @@ -24,6 +25,12 @@ impl EguiComponent for Wire { clip_rect: Rect, editor_mode: EditorMode, ) -> Option> { + let is_active = simulator + .as_ref() + .map_or(false, |sim| sim.is_active(&self.input.id)); + + trace!("render constant {}, active {}", self.id, is_active); + let oh: fn((f32, f32), f32, Vec2) -> Pos2 = offset_helper; let offset_old = offset; let s = scale; @@ -37,7 +44,11 @@ impl EguiComponent for Wire { line_vec.clone(), Stroke { width: scale, - color: Color32::BLACK, + color: if is_active { + Color32::RED + } else { + Color32::BLACK + }, }, )); let mut r_vec = vec![]; diff --git a/src/simulator.rs b/src/simulator.rs index 015a9e56..ab74d829 100644 --- a/src/simulator.rs +++ b/src/simulator.rs @@ -8,7 +8,7 @@ use petgraph::{ dot::{Config, Dot}, Graph, }; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::{fs::File, io::prelude::*, path::PathBuf}; pub struct IdComponent(pub HashMap>); @@ -32,6 +32,8 @@ impl Simulator { let mut id_nr_outputs = HashMap::new(); let mut id_field_index = HashMap::new(); + + let mut sinks = vec![]; // allocate storage for lensed outputs trace!("-- allocate storage for lensed outputs"); @@ -39,6 +41,11 @@ impl Simulator { trace!("{:?}", c.get_id_ports().0); let (id, ports) = c.get_id_ports(); + // push all sinks + if c.is_sink() { + sinks.push(id.clone()); + } + trace!("id {}, ports {:?}", id, ports); // start index for outputs related to component if id_start_index @@ -65,6 +72,8 @@ impl Simulator { id_nr_outputs.insert(id.clone(), ports.outputs.len()); } + trace!("sinks {:?}", sinks); + let mut graph = Graph::<_, (), petgraph::Directed>::new(); let mut id_node = HashMap::new(); let mut node_comp = HashMap::new(); @@ -159,6 +168,10 @@ impl Simulator { component_ids, graph, running: false, + // used for determine active components + sinks, + inputs_read: HashMap::new(), + active: HashSet::new(), }; trace!("sim_state {:?}", simulator.sim_state); @@ -200,6 +213,28 @@ impl Simulator { /// get input value pub fn get_input_value(&self, input: &Input) -> SignalValue { + // trace!("get_input_value, input {:?}", input); + + self.get_input_signal(input).get_value() + } + + /// get input value and update set of inputs read + /// id, represents the component reading + /// input, represents the input it is reading + pub fn get_input_value_mut(&mut self, id: Id, input: &Input) -> SignalValue { + trace!("get_input_value_mut {:?} reading {:?}", id, input); + + self.inputs_read + .entry(id) + .and_modify(|hs| { + hs.insert(input.id.clone()); + }) + .or_insert({ + let mut hs = HashSet::new(); + hs.insert(input.id.clone()); + hs + }); + self.get_input_signal(input).get_value() } @@ -248,10 +283,14 @@ impl Simulator { /// iterate over the evaluators and increase clock by one pub fn clock(&mut self) { // push current state - self.history.push(self.sim_state.clone()); + self.history + .push((self.sim_state.clone(), self.active.clone())); trace!("cycle:{}", self.cycle); + + self.clean_active(); + for component in self.ordered_components.clone() { - //trace!("evaling component:{}", component.get_id_ports().0); + trace!("evaluating component:{}", component.get_id_ports().0); match component.clock(self) { Ok(_) => {} Err(cond) => match cond { @@ -271,6 +310,44 @@ impl Simulator { } } self.cycle = self.history.len(); + self.active_components() + // self.clock_mode = false; + } + + // internal function to clear inputs read + fn clean_active(&mut self) { + trace!("clear_active"); + self.inputs_read = HashMap::new(); + } + + // internal function to determine active components + fn active_components(&mut self) { + trace!("active - determine active components"); + trace!("inputs read {:?}", self.inputs_read); + + self.active = HashSet::new(); + + // iterate from sinks towards inputs + let mut to_visit = self.sinks.clone(); + + // extremely un-Rusty + while let Some(id) = to_visit.pop() { + if !self.active.contains(&id) { + trace!("id not found {}", id); + if let Some(ids) = self.inputs_read.get(&id) { + trace!("reading input(s) {:?}", ids); + for id in ids { + to_visit.push(id.clone()); + } + } + self.active.insert(id); + } + } + } + + /// check if component is active + pub fn is_active(&self, id: &Id) -> bool { + self.active.contains(id) } /// free running mode until Halt condition @@ -295,9 +372,11 @@ impl Simulator { /// reverse simulation using history if clock > 1 pub fn un_clock(&mut self) { if self.cycle > 1 { - let state = self.history.pop().unwrap(); + let (state, active) = self.history.pop().unwrap(); // set old state self.sim_state = state; + self.active = active; + // to ensure that history length and cycle count complies self.cycle = self.history.len();