📝 accepts_direction, Signal::push, document parts of Signal

main
Shad Amethyst 2 years ago
parent f5aa5e86ff
commit e303f15d5a
Signed by: amethyst
GPG Key ID: D970C8DD1D6DEE36

@ -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<Signal>)>,

@ -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::*;
}

@ -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<f64> {
match self {
Value::Number(x) => Some(*x),
_ => None
}
}
#[inline]
pub fn as_int(&self) -> Option<i64> {
self.as_number().map(|x| x as i64)
}
}
impl From<f64> for Value {
fn from(x: f64) -> Value {
Value::Number(x.into())
}
}
impl From<u32> for Value {
fn from(x: u32) -> Value {
Value::Number(x.into())
}
}
impl From<String> 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<Value>,
}
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<Value> {
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<T>`](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
}};
}

@ -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));
}
}

@ -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`

Loading…
Cancel
Save