diff --git a/stackline/src/context.rs b/stackline/src/context.rs index 28818f7..c381af1 100644 --- a/stackline/src/context.rs +++ b/stackline/src/context.rs @@ -1,8 +1,12 @@ use super::*; -use std::cell::{Ref, RefMut}; /** Provides an interface between a [`Tile`] and its parent [`Pane`] during [`Tile::update`]. All actions performed through `UpdateContext` will be executed *after* all the tiles have updated. + + ## Safety + + Because [`Tile::update`] requires a `&mut self` reference, the current [`Tile`] cannot be accessed through [`UpdateContext::get`] + This structure stores the state and signal of the [`FullTile`] containing the current tile, and it is still possible and safe to call [`UpdateContext::send`] on the current position. **/ pub struct UpdateContext<'a> { position: (usize, usize), @@ -12,31 +16,32 @@ pub struct UpdateContext<'a> { commit: &'a mut UpdateCommit } +// SAFETY: self.pane.tiles[self.position] may not be accessed from any method impl<'a> UpdateContext<'a> { /// Returns `None` if the tile was already updated or is empty - pub(crate) fn new(pane: &'a Pane, position: (usize, usize), commit: &'a mut UpdateCommit) -> Option<(Self, RefMut<'a, AnyTile>)> { - let mut guard = pane.get_mut(position)?; - if guard.updated { + pub(crate) fn new(pane: &'a mut Pane, position: (usize, usize), commit: &'a mut UpdateCommit) -> Option<(UpdateContext<'a>, &'a mut AnyTile)> { + let mut tile = pane.get_mut(position)?; + if tile.updated { return None } - guard.updated = true; // prevent duplicate updates + tile.updated = true; // prevent duplicate updates commit.updates.push(position); - let (tile, signal, state) = guard.get_raw_mut(); - - if tile.is_none() { - return None - } + let ptr: *mut AnyTile = &mut **(tile.get_mut().as_mut()?); let res = Self { position, + state: tile.state(), + signal: tile.take_signal(), pane, - state: *state, - signal: std::mem::take(signal), commit }; - Some((res, RefMut::map(guard, |tile| tile.get_mut().unwrap_or_else(|| unreachable!())))) + // SAFETY: ptr is a valid pointer + // SAFETY: aliasing is prevented by the invariants of UpdateContext + Some((res, unsafe { + &mut *ptr + })) } /// Returns the position of the currently updated tile. @@ -81,10 +86,14 @@ impl<'a> UpdateContext<'a> { } /// Returns an immutable reference to the [FullTile] at `pos` in the current [Pane]. - /// Returns `None` if the tile is borrowed mutably or if it does not exist. + /// Returns `None` if the tile is borrowed mutably, if it is the current tile or if it does not exist. #[inline] - pub fn get<'b>(&'b self, pos: (usize, usize)) -> Option> where 'a: 'b { - self.pane.get(pos) + pub fn get<'b>(&'b self, pos: (usize, usize)) -> Option<&'b FullTile> where 'a: 'b { + if self.position == pos { + None + } else { + self.pane.get(pos) + } } /// Returns `Some((position.x + Δx, position.y + Δy))` iff `(x + Δx, y + Δy)` is inside the pane @@ -95,7 +104,7 @@ impl<'a> UpdateContext<'a> { /// Shortcut for calling both `ctx.offset(offset)` and `ctx.get(pos)` #[inline] - pub fn get_offset<'b>(&'b self, offset: (i8, i8)) -> Option<((usize, usize), Ref<'b, FullTile>)> where 'a: 'b { + pub fn get_offset<'b>(&'b self, offset: (i8, i8)) -> Option<((usize, usize), &'b FullTile)> where 'a: 'b { self.offset(offset).and_then(|pos| self.get(pos).map(|tile| (pos, tile))) } @@ -103,7 +112,7 @@ impl<'a> UpdateContext<'a> { /// If the tile does not exist, then this function will return `false`. #[inline] pub fn accepts_signal(&self, pos: (usize, usize), direction: Direction) -> bool { - match self.pane.get(pos) { + match self.get(pos) { Some(tile) => tile.accepts_signal(direction), None => false } @@ -151,19 +160,19 @@ impl UpdateCommit { pub(crate) fn apply(self, pane: &mut Pane) { for (x, y) in self.updates { - if let Some(mut tile) = pane.get_mut((x, y)) { + if let Some(tile) = pane.get_mut((x, y)) { tile.updated = false; } } for (x, y, state) in self.states { - if let Some(mut tile) = pane.get_mut((x, y)) { + if let Some(tile) = pane.get_mut((x, y)) { tile.set_state(state); } } for (x, y, signal) in self.signals { - let push_signal = if let Some(mut tile) = pane.get_mut((x, y)) { + let push_signal = if let Some(tile) = pane.get_mut((x, y)) { tile.set_signal(signal); tile.set_state(State::Active); // For some reason std::mem::drop(tile) isn't enough here diff --git a/stackline/src/pane.rs b/stackline/src/pane.rs index bb4f0f5..f2eb18d 100644 --- a/stackline/src/pane.rs +++ b/stackline/src/pane.rs @@ -1,9 +1,8 @@ use super::*; -use std::cell::{RefCell, Ref, RefMut}; #[derive(Debug)] pub struct Pane { - tiles: Vec>, + tiles: Vec, width: NonZeroUsize, height: NonZeroUsize, @@ -18,7 +17,7 @@ impl Pane { Some(Self { width: width.try_into().ok()?, height: height.try_into().ok()?, - tiles: vec![RefCell::new(FullTile::default()); length], + tiles: vec![FullTile::default(); length], signals: Vec::new(), }) @@ -48,21 +47,21 @@ impl Pane { // TODO: Have a Result instead of an Option #[inline] - pub fn get<'b>(&'b self, position: (usize, usize)) -> Option> { + pub fn get<'b>(&'b self, position: (usize, usize)) -> Option<&'b FullTile> { if !self.in_bounds(position) { return None; } - self.tiles.get(position.1 * self.width.get() + position.0).map(|tile| tile.try_borrow().ok()).flatten() + self.tiles.get(position.1 * self.width.get() + position.0) } #[inline] - pub fn get_mut<'b>(&'b self, position: (usize, usize)) -> Option> { + pub fn get_mut<'b>(&'b mut self, position: (usize, usize)) -> Option<&'b mut FullTile> { if !self.in_bounds(position) { return None; } - self.tiles.get(position.1 * self.width.get() + position.0).map(|tile| tile.try_borrow_mut().ok()).flatten() + self.tiles.get_mut(position.1 * self.width.get() + position.0) } #[inline] @@ -89,7 +88,7 @@ impl Pane { #[inline] fn update(&mut self, position: (usize, usize), commit: &mut UpdateCommit) -> Option<()> { - let (ctx, mut tile) = UpdateContext::new(self, position, commit)?; + let (ctx, tile) = UpdateContext::new(self, position, commit)?; tile.update(ctx); @@ -117,7 +116,7 @@ impl Pane { /// Returns an iterator over the tiles and their coordinates #[inline] - pub fn tiles<'b>(&'b self) -> impl Iterator)> + 'b { + pub fn tiles<'b>(&'b self) -> impl Iterator + 'b { self.tiles.iter().enumerate().filter_map(move |(i, v)| { Some((i % self.width, i / self.width, v)) }) diff --git a/stackline/src/signal.rs b/stackline/src/signal.rs index e4a2a58..2f0ce27 100644 --- a/stackline/src/signal.rs +++ b/stackline/src/signal.rs @@ -14,16 +14,17 @@ impl Signal { } } - pub fn clone_move(&self, direction: Direction) -> Option { + pub fn clone_move(&self, direction: Direction) -> Self { let mut res = self.clone(); res.direction = direction; - let (dx, dy) = direction.into_offset(); + res + } - res.position.0 = (res.position.0 as isize + dx as isize).try_into().ok()?; - res.position.1 = (res.position.1 as isize + dy as isize).try_into().ok()?; + pub fn moved(mut self, direction: Direction) -> Self { + self.direction = direction; - Some(res) + self } pub fn direction(&self) -> Direction { diff --git a/stackline/src/tile/mod.rs b/stackline/src/tile/mod.rs index 5099f75..0162468 100644 --- a/stackline/src/tile/mod.rs +++ b/stackline/src/tile/mod.rs @@ -164,7 +164,7 @@ mod crate_macros { ( $width:expr, $height:expr, [ $( $x:expr ),* ] ) => {{ assert!($width > 0); assert!($height > 0); - let pane = Pane::empty($width, $height).unwrap(); + let mut pane = Pane::empty($width, $height).unwrap(); let mut index = 0; $( diff --git a/stackline/src/tile/wire.rs b/stackline/src/tile/wire.rs index aaaca58..6988ec9 100644 --- a/stackline/src/tile/wire.rs +++ b/stackline/src/tile/wire.rs @@ -21,7 +21,7 @@ impl Tile for Wire { if let Some(pos) = context.offset(direction.into_offset()) { if context.accepts_signal(pos, direction) { - context.send(pos, signal.clone_move(direction).unwrap_or_else(|| unreachable!())); + context.send(pos, signal.clone_move(direction)); } } } @@ -56,7 +56,7 @@ impl Tile for Diode { if let Some(pos) = context.offset(self.0.into_offset()) { if context.accepts_signal(pos, self.0) { - context.send(pos, signal.clone_move(self.0).unwrap_or_else(|| unreachable!())); + context.send(pos, signal.moved(self.0)); } } } @@ -67,6 +67,42 @@ impl Tile for Diode { } } +#[derive(Clone, Debug)] +pub struct Resistor { + direction: Direction, + signal: Option, +} + +impl Resistor { + pub fn new(direction: Direction) -> Self { + Self { + direction, + signal: None + } + } +} + +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(signal) = context.take_signal() { + self.signal = Some(signal); + context.set_state(State::Active); + } else { + if context.state() != State::Idle { + context.next_state(); + } + } + } +} + #[cfg(test)] mod test { use super::*; @@ -101,7 +137,7 @@ mod test { pane.step(); for (_, _, tile) in pane.tiles() { - assert!(tile.borrow().signal().is_none()); + assert!(tile.signal().is_none()); } // Let the simulation cool down @@ -161,4 +197,51 @@ mod test { } } } + + #[test] + fn test_resistor_transmit() { + use crate::Direction::*; + + let mut pane = test_tile_setup!(4, 1, [ + Diode::new(Right), Resistor::new(Right), Resistor::new(Right), Diode::new(Right) + ]); + + test_set_signal!(pane, (0, 0), Direction::Right); + + pane.step(); + assert_no_signal!(pane, (0, 0)); + assert_signal!(pane, (1, 0)); + assert_no_signal!(pane, (2, 0)); + assert_no_signal!(pane, (3, 0)); + + pane.step(); + assert_no_signal!(pane, (0, 0)); + assert_no_signal!(pane, (1, 0)); + assert_no_signal!(pane, (2, 0)); + assert_no_signal!(pane, (3, 0)); + + pane.step(); + assert_no_signal!(pane, (0, 0)); + assert_no_signal!(pane, (1, 0)); + assert_signal!(pane, (2, 0)); + assert_no_signal!(pane, (3, 0)); + + pane.step(); + assert_no_signal!(pane, (0, 0)); + assert_no_signal!(pane, (1, 0)); + assert_no_signal!(pane, (2, 0)); + assert_no_signal!(pane, (3, 0)); + + pane.step(); + assert_no_signal!(pane, (0, 0)); + assert_no_signal!(pane, (1, 0)); + assert_no_signal!(pane, (2, 0)); + assert_signal!(pane, (3, 0)); + + pane.step(); + assert_no_signal!(pane, (0, 0)); + assert_no_signal!(pane, (1, 0)); + assert_no_signal!(pane, (2, 0)); + assert_no_signal!(pane, (3, 0)); + } }