From e303f15d5a7d9c0574df7035041d88aabe81e463 Mon Sep 17 00:00:00 2001 From: Adrien Burgun Date: Thu, 23 Jun 2022 21:44:41 +0200 Subject: [PATCH] :sparkles: :pencil: accepts_direction, Signal::push, document parts of Signal --- stackline/src/context.rs | 37 ++++++- stackline/src/lib.rs | 2 +- stackline/src/signal.rs | 195 +++++++++++++++++++++++++++++++++++++ stackline/src/tile/wire.rs | 18 ++-- stackline/src/utils.rs | 12 +++ 5 files changed, 249 insertions(+), 15 deletions(-) diff --git a/stackline/src/context.rs b/stackline/src/context.rs index 1198c46..e371653 100644 --- a/stackline/src/context.rs +++ b/stackline/src/context.rs @@ -5,7 +5,7 @@ use super::*; ## Design - There are several factors that come into the design of [`UpdateContext`]: + There are several factors that came into the design of [`UpdateContext`]: - all of its methods are considered hot-path code, which means that allocations must be kept at a minimum - all of the actions must be performed after all the tiles were updated @@ -13,6 +13,8 @@ use super::*; ## Example + Here is how you would implement a simple "counter" tile: + ``` # use stackline::{*, tile::*, context::*}; @@ -58,8 +60,9 @@ pub struct UpdateContext<'a> { commit: &'a mut UpdateCommit, } -// SAFETY: self.pane.tiles[self.position] may not be accessed from any method +// SAFETY: self.pane.tiles[self.position] may not be accessed by any method of UpdateContext impl<'a> UpdateContext<'a> { + /// Creates a new UpdateContext /// Returns `None` if the tile was already updated or is empty pub(crate) fn new( pane: &'a mut Pane, @@ -89,6 +92,23 @@ impl<'a> UpdateContext<'a> { } /// Returns the position of the currently updated tile. + /// + /// ## Example + /// + /// ``` + /// # use stackline::prelude::*; + /// # #[derive(Clone, Debug)] + /// # pub struct MyTile; + /// # impl Tile for MyTile { + /// fn update<'b>(&'b mut self, mut ctx: UpdateContext<'b>) { + /// if let Some(mut signal) = ctx.take_signal() { + /// let (x, y) = ctx.position(); + /// signal.push(Value::Number(y as f64)); + /// signal.push(Value::Number(x as f64)); + /// } + /// } + /// # } + /// ``` #[inline] pub fn position(&self) -> (usize, usize) { self.position @@ -167,6 +187,17 @@ impl<'a> UpdateContext<'a> { } } + /// Returns `Some(pos)` iff `pos = (x + Δx, y + Δy)` is a valid position and `self.get(pos).accepts_signal(direction)` + #[inline] + pub fn accepts_direction(&self, direction: Direction) -> Option<(usize, usize)> { + let (pos, tile) = self.get_offset(direction.into_offset())?; + if tile.accepts_signal(direction) { + Some(pos) + } else { + None + } + } + /// Sends a signal to be stored in a cell (may be the current one), the signal overrides that of the other cell /// Returns true if the signal was stored in a cell, false otherwise. /// The target cell's state will be set to `Active` if it received the signal. @@ -184,6 +215,8 @@ impl<'a> UpdateContext<'a> { } } +/// 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)>, diff --git a/stackline/src/lib.rs b/stackline/src/lib.rs index d2ae2f6..200a42e 100644 --- a/stackline/src/lib.rs +++ b/stackline/src/lib.rs @@ -38,7 +38,7 @@ pub mod prelude { pub use crate::text::{TextSurface, TextChar}; pub use crate::context::UpdateContext; - pub use crate::signal::Signal; + pub use crate::signal::{Signal, Value}; pub use crate::tile::Tile; pub use crate::utils::*; } diff --git a/stackline/src/signal.rs b/stackline/src/signal.rs index 8b6e6a7..0e81cd6 100644 --- a/stackline/src/signal.rs +++ b/stackline/src/signal.rs @@ -1,9 +1,65 @@ use super::*; +#[derive(Clone, Debug, PartialEq)] +pub enum Value { + Number(f64), + String(String), +} + +impl Value { + #[inline] + pub fn as_number(&self) -> Option { + match self { + Value::Number(x) => Some(*x), + _ => None + } + } + + #[inline] + pub fn as_int(&self) -> Option { + self.as_number().map(|x| x as i64) + } +} + +impl From for Value { + fn from(x: f64) -> Value { + Value::Number(x.into()) + } +} + +impl From for Value { + fn from(x: u32) -> Value { + Value::Number(x.into()) + } +} + +impl From for Value { + fn from(string: String) -> Value { + Value::String(string) + } +} + +impl<'a> From<&'a str> for Value { + fn from(string: &'a str) -> Value { + Value::String(String::from(string)) + } +} + +/// The unit of information that [`Tile`]s transmit between each other. +/// A `Signal` is made up of a [`stack`](Signal::stack), and tracks its [`position`](Signal::position) and [`direction`](Signal::direction). +/// +/// ## Creating a signal +/// +/// There are multiple ways to create a `Signal`: +/// +/// - By cloning it, through [`clone_move`](Signal::clone_move) (recommended) or [`clone`](Signal::clone) +/// - By creating an empty signal, with [`empty`](Signal::empty) +/// - Through the [`stackline::signal!`](crate::signal!) macro #[derive(Clone, Debug)] pub struct Signal { direction: Direction, position: (usize, usize), + stack: Vec, } impl Signal { @@ -11,9 +67,13 @@ impl Signal { Self { direction, position, + stack: Vec::new(), } } + /// Variant of [`moved`](Signal::moved), but clones the signal beforehand. + /// + /// See [`moved`](Signal::moved) for more information pub fn clone_move(&self, direction: Direction) -> Self { let mut res = self.clone(); res.direction = direction; @@ -21,6 +81,29 @@ impl Signal { res } + /// Sets the direction of the signal to `direction`, and returns that signal. + /// + /// This function or its sister function, [`clone_move`](Signal::clone_move), should always be called before [`send`ing](UpdateContext::send) a signal to another tile. + /// + /// ## Example + /// + /// ``` + /// # use stackline::prelude::*; + /// # #[derive(Clone, Debug)] + /// # struct MyTile; + /// # impl Tile for MyTile { + /// fn update<'b>(&'b mut self, mut ctx: UpdateContext<'b>) { + /// let direction = Direction::Down; + /// + /// if let Some(signal) = ctx.take_signal() { + /// // We have a signal, see if it can be sent down + /// if let Some(pos) = ctx.accepts_direction(direction) { + /// ctx.send(pos, signal.moved(direction)); + /// } + /// } + /// } + /// # } + /// ``` pub fn moved(mut self, direction: Direction) -> Self { self.direction = direction; @@ -38,4 +121,116 @@ impl Signal { pub(crate) fn set_position(&mut self, position: (usize, usize)) { self.position = position; } + + /// Pushes a value onto the stack of the signal. + /// Signals are pushed on top of the stack and can be [`pop`ped](Signal::pop) in reverse order. + /// + /// ## Example + /// + /// ``` + /// # use stackline::prelude::*; + /// let mut signal = Signal::empty((0, 0), Direction::Down); + /// assert_eq!(signal.len(), 0); + /// + /// signal.push(Value::Number(1.0)); + /// assert_eq!(signal.len(), 1); + /// + /// signal.push(Value::Number(2.0)); + /// assert_eq!(signal.len(), 2); + /// ``` + pub fn push(&mut self, value: Value) { + self.stack.push(value); + } + + /// Pops a value from the stack of the signal, returning `Some(value)` + /// if the stack wasn't empty and `None` otherwise. + /// + /// ## Example + /// + /// ``` + /// # use stackline::prelude::*; + /// let mut signal = Signal::empty((0, 0), Direction::Down); + /// + /// signal.push(Value::Number(1.0)); + /// signal.push(Value::Number(2.0)); + /// assert_eq!(signal.len(), 2); + /// + /// // We pushed 2.0 last, so pop() will return that + /// assert_eq!(signal.pop(), Some(Value::Number(2.0))); + /// assert_eq!(signal.len(), 1); + /// ``` + pub fn pop(&mut self) -> Option { + self.stack.pop() + } + + /// Returns the number of elements in the stack of the signal. + /// + /// ## Example + /// + /// ``` + /// # use stackline::prelude::*; + /// let signal = stackline::signal!((0, 0), Direction::Down, [1.0, -1.5]); + /// + /// assert_eq!(signal.len(), 2); + /// ``` + pub fn len(&self) -> usize { + self.stack.len() + } + + // TODO: stack(), stack_mut() and other stack manipulation tools +} + +/// Creates a signal with initial values in its stack. +/// +/// The syntax for the macro is `signal!(position, direction, [value1, value2, ...])`, where: +/// - `position`: a pair `(x, y): (usize, usize)` +/// - `direction`: a [`Direction`] *(optional, defaults to [`Direction::default()`])* +/// - `value1`, `value2`, etc.: [`Value`] must implement [`From`](Value#trait-implementations) for each value *(optional)* +/// +/// ## Examples +/// +/// ``` +/// # use stackline::prelude::*; +/// // Creates an empty signal at (1, 2) +/// let signal = stackline::signal!((1, 2)); +/// +/// // Creates an empty signal going right at (2, 2) +/// let signal = stackline::signal!((2, 2), Direction::Right); +/// +/// // Creates a signal with the values 10 and 12 on its stack +/// let signal = stackline::signal!((0, 0), [10, 12]); +/// ``` +#[macro_export] +macro_rules! signal { + ( $pos:expr, $dir:expr, [ $( $x:expr ),* ] ) => {{ + let mut res = Signal::empty($pos, $dir); + + $({ + res.push(Value::from($x)); + })* + + res + }}; + + ( $pos:expr, [ $( $x:expr ),* ] ) => {{ + let mut res = Signal::empty($pos, Direction::default()); + + $({ + res.push(Value::from($x)); + })* + + res + }}; + + ( $pos:expr, $dir:expr) => {{ + let mut res = Signal::empty($pos, $dir); + + res + }}; + + ( $pos:expr) => {{ + let mut res = Signal::empty($pos, Direction::default()); + + res + }}; } diff --git a/stackline/src/tile/wire.rs b/stackline/src/tile/wire.rs index 769b5c5..cddab51 100644 --- a/stackline/src/tile/wire.rs +++ b/stackline/src/tile/wire.rs @@ -19,10 +19,8 @@ impl Tile for Wire { continue; } - if let Some(pos) = context.offset(direction.into_offset()) { - if context.accepts_signal(pos, direction) { - context.send(pos, signal.clone_move(direction)); - } + if let Some(pos) = context.accepts_direction(direction) { + context.send(pos, signal.clone_move(direction)); } } } @@ -64,10 +62,8 @@ impl Tile for Diode { return; } - if let Some(pos) = context.offset(self.0.into_offset()) { - if context.accepts_signal(pos, self.0) { - context.send(pos, signal.moved(self.0)); - } + if let Some(pos) = context.accepts_direction(self.0) { + context.send(pos, signal.moved(self.0)); } } @@ -106,10 +102,8 @@ impl Resistor { impl Tile for Resistor { fn update<'b>(&'b mut self, mut context: UpdateContext<'b>) { if let Some(signal) = std::mem::take(&mut self.signal) { - if let Some(pos) = context.offset(self.direction.into_offset()) { - if context.accepts_signal(pos, self.direction) { - context.send(pos, signal.moved(self.direction)); - } + if let Some(pos) = context.accepts_direction(self.direction) { + context.send(pos, signal.moved(self.direction)); } } diff --git a/stackline/src/utils.rs b/stackline/src/utils.rs index a723887..fd22dc0 100644 --- a/stackline/src/utils.rs +++ b/stackline/src/utils.rs @@ -62,6 +62,12 @@ impl Orientation { } } +impl Default for Orientation { + fn default() -> Self { + Orientation::Any + } +} + impl Direction { /// Converts a [Direction] in a pair `(Δx, Δy)`, with [Up](Direction::Up) being equal to `(0, -1)` #[inline] @@ -86,6 +92,12 @@ impl Direction { } } +impl Default for Direction { + fn default() -> Self { + Direction::Up + } +} + impl State { /// Rotates the state: /// - `Idle → Active`