diff --git a/stackline/src/context.rs b/stackline/src/context.rs index 3c91891..1e79b55 100644 --- a/stackline/src/context.rs +++ b/stackline/src/context.rs @@ -361,6 +361,12 @@ impl<'a> UpdateContext<'a> { } } + /// Sends a signal to another [`Pane`] in the world. + /// If the tile at `coordinates = (pane, x, y)` does not exist, then the signal will be lost. + pub fn send_outbound(&mut self, coordinates: (String, usize, usize), signal: Signal) { + self.commit.send_outbound(coordinates, signal); + } + /// Stores the current signal back in the current tile, guaranteeing that it will stay there for /// this update cycle. See [`take_signal`](UpdateContext::take_signal) for more information. /// @@ -398,6 +404,11 @@ impl<'a> UpdateContext<'a> { _ => {} } } + + /// Executes an arbitrary function after all the updates. + pub fn callback FnOnce(&'c mut Pane) + 'static>(&mut self, callback: F) { + self.commit.set_callback(Box::new(callback)); + } } pub struct SendError(pub Signal); @@ -416,12 +427,16 @@ impl std::fmt::Display for SendError { } } +pub type UpdateCommitCallback = Box FnOnce(&'c mut Pane) + 'static>; + /// Temporarily holds a list of actions to be made on a given Pane, which should be [applied](UpdateCommit::apply) /// after every tile was updated. pub(crate) struct UpdateCommit { states: Vec<(usize, usize, State)>, signals: Vec<(usize, usize, Option)>, updates: Vec<(usize, usize)>, + callbacks: Vec, + outbound_signals: Vec<((String, usize, usize), Signal)>, self_signal: Option, } @@ -432,6 +447,8 @@ impl UpdateCommit { states: Vec::new(), signals: Vec::new(), updates: Vec::new(), + callbacks: Vec::new(), + outbound_signals: Vec::new(), self_signal: None, } @@ -449,7 +466,15 @@ impl UpdateCommit { self.self_signal = signal; } - pub(crate) fn apply(self, pane: &mut Pane) { + fn set_callback(&mut self, callback: UpdateCommitCallback) { + self.callbacks.push(callback); + } + + fn send_outbound(&mut self, coordinates: (String, usize, usize), signal: Signal) { + self.outbound_signals.push((coordinates, signal)); + } + + pub(crate) fn apply(self, pane: &mut Pane) -> PaneResult { for (x, y) in self.updates { if let Some(tile) = pane.get_mut((x, y)) { tile.updated = false; @@ -476,6 +501,14 @@ impl UpdateCommit { pane.signals.push((x, y)); } } + + for callback in self.callbacks { + (callback)(pane); + } + + PaneResult { + outbound_signals: self.outbound_signals, + } } /// Applies transformations on a FullTile before the end of the update phase diff --git a/stackline/src/lib.rs b/stackline/src/lib.rs index fba643f..e6f099b 100644 --- a/stackline/src/lib.rs +++ b/stackline/src/lib.rs @@ -18,6 +18,10 @@ use signal::*; pub mod pane; use pane::*; +pub mod world; +#[allow(unused_imports)] +use world::*; + pub mod utils; use utils::*; @@ -30,13 +34,9 @@ use context::*; pub mod text; use text::*; -pub struct World { - panes: Vec, -} - pub mod prelude { pub use crate::pane::Pane; - pub use crate::World; + pub use crate::world::World; pub use crate::text::{TextChar, TextSurface}; diff --git a/stackline/src/pane.rs b/stackline/src/pane.rs index e6d0d63..a8963af 100644 --- a/stackline/src/pane.rs +++ b/stackline/src/pane.rs @@ -351,7 +351,7 @@ impl Pane { /// // The signal is now at (1, 1) /// assert!(pane.get((1, 1)).unwrap().signal().is_some()); /// ``` - pub fn step(&mut self) { + pub fn step(&mut self) -> PaneResult { let mut commit = UpdateCommit::new(); for position in std::mem::replace(&mut self.signals, Vec::new()) { @@ -366,7 +366,7 @@ impl Pane { } } - commit.apply(self); + commit.apply(self) } /// Returns an iterator over the tiles and their coordinates @@ -390,6 +390,12 @@ impl Pane { } } +/// Stores the results of a [`Pane`]'s update step. +pub struct PaneResult { + /// Signals to be sent to other panes. + pub outbound_signals: Vec<((String, usize, usize), Signal)>, +} + #[cfg(test)] mod test { use super::*; diff --git a/stackline/src/tile/macros.rs b/stackline/src/tile/macros.rs index 3b818ae..186c591 100644 --- a/stackline/src/tile/macros.rs +++ b/stackline/src/tile/macros.rs @@ -1,6 +1,6 @@ #[macro_export] macro_rules! test_tile_setup { - ( $width:expr, $height:expr, [ $( $x:expr ),* ] ) => {{ + ( $width:expr, $height:expr, [ $( $x:expr ),* $(,)? ] ) => {{ assert!($width > 0); assert!($height > 0); let mut pane = crate::pane::Pane::empty($width, $height).unwrap(); diff --git a/stackline/src/world.rs b/stackline/src/world.rs new file mode 100644 index 0000000..7989b89 --- /dev/null +++ b/stackline/src/world.rs @@ -0,0 +1,55 @@ +use super::*; +use std::collections::HashMap; + +pub struct World { + panes: HashMap, +} + +impl World { + pub fn new() -> Self { + Self { + panes: HashMap::new(), + } + } + + pub fn step(&mut self) { + let mut outbound_signals = Vec::new(); + + for (_, pane) in self.panes.iter_mut() { + let mut res = pane.step(); + outbound_signals.append(&mut res.outbound_signals); + } + + for ((name, x, y), signal) in outbound_signals { + if let Some(pane) = self.get_pane_mut(&name) { + let _ = pane.set_signal((x, y), signal); // Errors are ignored + } + } + } + + pub fn set_pane(&mut self, name: String, pane: Pane) { + self.panes.insert(name, pane); + } + + pub fn get_pane<'b>(&'b self, name: &str) -> Option<&'b Pane> { + self.panes.get(name) + } + + pub fn get_pane_mut<'b>(&'b mut self, name: &str) -> Option<&'b mut Pane> { + self.panes.get_mut(name) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_world_set_pane() { + let mut world = World::new(); + + world.set_pane(String::from("main"), Pane::empty(10, 10).unwrap()); + + assert!(world.get_pane("main").is_some()); + } +} diff --git a/stackline/tiles/transmit.rs b/stackline/tiles/transmit.rs new file mode 100644 index 0000000..d372382 --- /dev/null +++ b/stackline/tiles/transmit.rs @@ -0,0 +1,143 @@ +//! Transmission tiles: allow for inter-Pane communication + +use crate::prelude::*; + +/// Instantly sends any incomming signals to `coordinates` +#[derive(Clone, Debug)] +pub struct Portal { + pub coordinates: (String, usize, usize), +} + +impl Portal { + pub fn new(name: String, x: usize, y: usize) -> Self { + Self { + coordinates: (name, x, y) + } + } +} + +impl Tile for Portal { + fn update<'b>(&'b mut self, mut context: UpdateContext<'b>) { + if let Some(signal) = context.take_signal() { + context.send_outbound(self.coordinates.clone(), signal); + } + + if context.state() != State::Idle { + context.next_state(); + } + } + + fn draw(&self, x: usize, y: usize, state: State, surface: &mut TextSurface) { + surface.set(x, y, TextChar::from_state('P', state)); + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_portal_transmit_same_pane() { + use crate::{Diode, Wire}; + use Direction::*; + use Orientation::*; + + let mut main_pane = test_tile_setup!( + 3, + 3, + [ + Diode::new(Right), Portal::new(String::from("main"), 2, 2), (), + (), (), (), + (), (), Wire::new(Any) + ] + ); + + test_set_signal!(main_pane, (0, 0), Right); + + let mut world = World::new(); + world.set_pane(String::from("main"), main_pane); + + world.step(); + + assert_signal!(world.get_pane("main").unwrap(), (1, 0)); + + world.step(); + + assert_no_signal!(world.get_pane("main").unwrap(), (1, 0)); + assert_signal!(world.get_pane("main").unwrap(), (2, 2)); + + world.step(); + + assert_no_signal!(world.get_pane("main").unwrap(), (0, 0)); + assert_no_signal!(world.get_pane("main").unwrap(), (1, 0)); + assert_no_signal!(world.get_pane("main").unwrap(), (2, 2)); + } + + #[test] + fn test_portal_transmit_other_pane() { + use crate::{Diode, Wire}; + use Direction::*; + use Orientation::*; + + let mut main_pane = test_tile_setup!( + 2, + 1, + [ + Diode::new(Right), Portal::new(String::from("sub"), 0, 0), + ] + ); + + test_set_signal!(main_pane, (0, 0), Right); + + let sub_pane = test_tile_setup!( + 1, + 1, + [ + Wire::new(Any), + ] + ); + + let mut world = World::new(); + world.set_pane(String::from("main"), main_pane); + world.set_pane(String::from("sub"), sub_pane); + + world.step(); + + assert_signal!(world.get_pane("main").unwrap(), (1, 0)); + + world.step(); + + assert_no_signal!(world.get_pane("main").unwrap(), (1, 0)); + assert_signal!(world.get_pane("sub").unwrap(), (0, 0)); + + world.step(); + + assert_no_signal!(world.get_pane("main").unwrap(), (0, 0)); + assert_no_signal!(world.get_pane("main").unwrap(), (1, 0)); + assert_no_signal!(world.get_pane("sub").unwrap(), (0, 0)); + } + + #[test] + fn test_portal_transmit_self() { + use Direction::*; + + let mut main_pane = test_tile_setup!( + 1, + 1, + [ + Portal::new(String::from("main"), 0, 0), + ] + ); + + test_set_signal!(main_pane, (0, 0), Right); + + let mut world = World::new(); + world.set_pane(String::from("main"), main_pane); + + for _ in 0..5 { + world.step(); + + assert_signal!(world.get_pane("main").unwrap(), (0, 0)); + } + } +}