diff --git a/examples/pass_trough.rs b/examples/pass_trough.rs new file mode 100644 index 0000000..165f6fe --- /dev/null +++ b/examples/pass_trough.rs @@ -0,0 +1,63 @@ +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![ + Add::rc_new( + "add", + (200.0, 120.0), + Input::new("c", "out"), + Input::new("reg", "out"), + ), + Constant::rc_new("c", (100.0, 100.0), 3), + Register::rc_new( + "reg", + (100.0, 140.0), + Input::new("pass", PASS_THROUGH_OUT_ID), + ), + Wire::rc_new( + "w1", + vec![(110.0, 100.0), (180.0, 100.0)], + Input::new("c", "out"), + ), + Wire::rc_new( + "w2", + vec![(110.0, 140.0), (180.0, 140.0)], + Input::new("reg", "out"), + ), + Wire::rc_new( + "w3", + vec![(220.0, 120.0), (260.0, 120.0), (260.0, 180.0)], + Input::new("add", "out"), + ), + PassThrough::rc_new("pass", (260.0, 180.0), Input::new("add", "out")), + Wire::rc_new( + "w4", + vec![(260.0, 180.0), (60.0, 180.0), (60.0, 140.0), (90.0, 140.0)], + Input::new("pass", PASS_THROUGH_OUT_ID), + ), + Probe::rc_new( + "p_add", + (280.0, 120.0), + Input::new("pass", PASS_THROUGH_OUT_ID), + ), + Probe::rc_new("p_reg", (130.0, 120.0), Input::new("reg", "out")), + ], + }; + + let path = PathBuf::from("add_reg_compound.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/components/mod.rs b/src/components/mod.rs index 53382f6..cbca357 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -5,6 +5,7 @@ mod cross; mod equals; mod mem; mod mux; +mod pass_through; mod probe; mod probe_assert; mod probe_edit; @@ -24,6 +25,7 @@ pub use cross::*; pub use equals::*; pub use mem::*; pub use mux::*; +pub use pass_through::*; pub use probe::*; pub use probe_assert::*; pub use probe_edit::*; diff --git a/src/components/pass_through.rs b/src/components/pass_through.rs new file mode 100644 index 0000000..9e39687 --- /dev/null +++ b/src/components/pass_through.rs @@ -0,0 +1,81 @@ +#[cfg(feature = "gui-egui")] +use crate::common::EguiComponent; +use crate::common::{Component, Condition, Id, Input, InputPort, OutputType, Ports, Simulator}; +use log::*; +use serde::{Deserialize, Serialize}; +use std::any::Any; +use std::rc::Rc; + +pub const PASS_THROUGH_IN_ID: &str = "pass_through_in"; + +pub const PASS_THROUGH_OUT_ID: &str = "pass_through_out"; + +#[derive(Serialize, Deserialize, Clone)] +pub struct PassThrough { + pub(crate) id: Id, + pub(crate) pos: (f32, f32), + pub(crate) input: Input, +} + +#[typetag::serde] +impl Component for PassThrough { + fn to_(&self) { + trace!("pass_through"); + } + #[cfg(feature = "gui-egui")] + fn dummy(&self, id: &str, pos: (f32, f32)) -> Box> { + let dummy_input = Input::new("dummy", "out"); + Box::new(Rc::new(PassThrough { + id: id.to_string(), + pos: (pos.0, pos.1), + input: dummy_input.clone(), + })) + } + fn get_id_ports(&self) -> (Id, Ports) { + ( + self.id.clone(), + Ports::new( + // Vector of inputs + vec![&InputPort { + port_id: PASS_THROUGH_IN_ID.to_string(), + input: self.input.clone(), + }], + OutputType::Combinatorial, + vec![PASS_THROUGH_OUT_ID], + ), + ) + } + + // propagate input value to output + fn clock(&self, simulator: &mut Simulator) -> Result<(), Condition> { + // get input value + let value = simulator.get_input_value_mut(self.id.clone(), &self.input); + // set output + simulator.set_out_value(&self.id, PASS_THROUGH_OUT_ID, value); + Ok(()) + } + + fn set_id_port(&mut self, target_port_id: Id, new_input: Input) { + if target_port_id == PASS_THROUGH_IN_ID { + self.input = new_input; + } + } + + fn as_any(&self) -> &dyn Any { + self + } +} + +impl PassThrough { + pub fn new(id: &str, pos: (f32, f32), input: Input) -> Self { + PassThrough { + id: id.to_string(), + pos, + input, + } + } + + pub fn rc_new(id: &str, pos: (f32, f32), input: Input) -> Rc { + Rc::new(PassThrough::new(id, pos, input)) + } +} diff --git a/src/gui_egui/components/mod.rs b/src/gui_egui/components/mod.rs index 6976570..d7b3212 100644 --- a/src/gui_egui/components/mod.rs +++ b/src/gui_egui/components/mod.rs @@ -5,6 +5,7 @@ mod cross; mod equal; mod mem; mod mux; +mod pass_through; mod probe; mod probe_assert; mod probe_edit; diff --git a/src/gui_egui/components/pass_through.rs b/src/gui_egui/components/pass_through.rs new file mode 100644 index 0000000..9b1fb0a --- /dev/null +++ b/src/gui_egui/components/pass_through.rs @@ -0,0 +1,120 @@ +use crate::common::{Component, EguiComponent, Ports, Simulator}; +use crate::components::PassThrough; +use crate::gui_egui::editor::{EditorMode, EditorRenderReturn, GridOptions}; +use crate::gui_egui::gui::EguiExtra; +use egui::{ + Align2, Area, Context, InnerResponse, Order, Pos2, Rect, Response, TextWrapMode, Ui, Vec2, +}; + +#[typetag::serde] +impl EguiComponent for PassThrough { + /// TODO this need to be rewritten when newer helper functions becomes available + fn render( + &self, + ui: &mut Ui, + _context: &mut EguiExtra, + _simulator: Option<&mut Simulator>, + offset: Vec2, + scale: f32, + clip_rect: Rect, + _editor_mode: EditorMode, + ) -> Option> { + fn component_area( + id: String, + ctx: &Context, + pos: impl Into, + content: impl FnOnce(&mut Ui) -> R, + ) -> InnerResponse { + Area::new(egui::Id::from(id)) + .order(Order::Middle) + .current_pos(pos) + .movable(false) + .enabled(true) + .interactable(false) + .pivot(Align2::CENTER_CENTER) + .constrain(false) + .show(ctx, content) + } + + let offset: Vec2 = offset.into(); + + let r = component_area( + self.get_id_ports().0, + ui.ctx(), + Pos2::from(self.get_pos()) * scale + offset, + |ui| { + ui.set_clip_rect(clip_rect); + + ui.style_mut().wrap_mode = Some(TextWrapMode::Extend); + + for (_text_style, font) in ui.style_mut().text_styles.iter_mut() { + font.size *= scale; + } + ui.spacing_mut().button_padding *= scale; + ui.spacing_mut().item_spacing *= scale; + ui.spacing_mut().combo_height *= scale; + ui.spacing_mut().combo_width *= scale; + ui.spacing_mut().icon_width *= scale; + ui.spacing_mut().icon_width_inner *= scale; + ui.spacing_mut().icon_spacing *= scale; + ui.spacing_mut().interact_size *= scale; + + let mut group = egui::containers::Frame::group(ui.style()); + group.inner_margin *= scale; + group.rounding *= scale; + // group.fill = Color32::LIGHT_RED; // Use this ween component background is implemented, probably when we implement dark mode + group + .show(ui, |ui| { + ui.label("➡️"); + }) + .response + }, + ) + .inner; + Some(vec![r]) + } + + fn render_editor( + &mut self, + _ui: &mut Ui, + _context: &mut EguiExtra, + _simulator: Option<&mut Simulator>, + _offset: Vec2, + _scale: f32, + _clip_rect: Rect, + _id_ports: &[(crate::common::Id, Ports)], + _grid: &GridOptions, + _editor_mode: EditorMode, + ) -> EditorRenderReturn { + EditorRenderReturn { + delete: false, + resp: Some(vec![]), + } + } + + fn ports_location(&self) -> Vec<(crate::common::Id, Pos2)> { + let own_pos = Vec2::new(self.pos.0, self.pos.1); + vec![ + ( + crate::components::REGISTER_R_IN_ID.to_string(), + Pos2::new(-10f32, 0f32) + own_pos, + ), + ( + crate::components::REGISTER_OUT_ID.to_string(), + Pos2::new(10f32, 0f32) + own_pos, + ), + ] + } + + fn top_padding(&self) -> f32 { + 20f32 + } + + fn set_pos(&mut self, pos: (f32, f32)) { + self.pos = pos; + } + + fn get_pos(&self) -> (f32, f32) { + self.pos + } +} diff --git a/src/simulator.rs b/src/simulator.rs index 036601b..7673b5b 100644 --- a/src/simulator.rs +++ b/src/simulator.rs @@ -17,9 +17,6 @@ pub struct IdComponent(pub HashMap>); // The topological order does not enforce any specific order of registers // Thus registers cannot point to other registers in a cyclic fashion // This is (likely) not occurring in practice. -// -// A solution is to evaluate register updates separately from other components -// ... but not currently implemented ... impl Simulator { pub fn new(component_store: ComponentStore) -> Result { for component in &component_store.store { @@ -138,6 +135,27 @@ impl Simulator { ordered_components.push(c); } } + + // check if a sequential components is linking to another sequential component and panic + // this avoids that the order of sequential components matter as they can't affect one other. + for seq_node in &ordered_components { + for seq_node_inputs in seq_node + .get_id_ports() + .1 + .inputs + .iter() + .map(|port| &port.input) + { + if ordered_components + .iter() + .find(|node| node.get_id_ports().0 == seq_node_inputs.id) + .is_some() + { + panic!("Component {} read data from {}. Sequential to sequential is not allowed, consider adding a pass trough component", seq_node.get_id_ports().0, seq_node_inputs.id) + } + } + } + //then the rest... for node in &top { #[allow(suspicious_double_ref_op)] @@ -569,6 +587,22 @@ mod test { assert_eq!(simulator.cycle, 1); } + #[test] + #[should_panic( + expected = "Component r_2 read data from r_1. Sequential to sequential is not allowed, consider adding a pass trough component" + )] + fn test_sequential_to_sequential() { + let cs = ComponentStore { + store: vec![ + Register::rc_new("r_1", (0.0, 0.0), Input::new("pass", PASS_THROUGH_OUT_ID)), + Register::rc_new("r_2", (70.0, 0.0), Input::new("r_1", REGISTER_OUT_ID)), + PassThrough::rc_new("pass", (35.0, 35.0), Input::new("r_2", REGISTER_OUT_ID)), + ], + }; + + let _simulator = Simulator::new(cs).unwrap(); + } + #[test] fn test_get_input_val() { let cs = ComponentStore {