From 4b60d139432b8efccf9053dc14d44888048d8461 Mon Sep 17 00:00:00 2001 From: Adrien Burgun Date: Sat, 18 Jun 2022 11:39:37 +0200 Subject: [PATCH] :sparkles: States and improvements to Context --- stackline/src/context.rs | 82 ++++++++++++++++++++++++++++++++++---- stackline/src/pane.rs | 16 +++++++- stackline/src/signal.rs | 4 ++ stackline/src/tile/mod.rs | 43 ++++++++++++++++---- stackline/src/tile/wire.rs | 39 ++++++++++-------- stackline/src/utils.rs | 57 ++++++++++++++++++++++++++ 6 files changed, 209 insertions(+), 32 deletions(-) diff --git a/stackline/src/context.rs b/stackline/src/context.rs index ff39fea..ff3156d 100644 --- a/stackline/src/context.rs +++ b/stackline/src/context.rs @@ -18,6 +18,7 @@ use super::*; pub struct UpdateContext<'a> { position: (usize, usize), pane: *const Pane, + state: &'a mut State, phantom: PhantomData<&'a Pane>, } @@ -27,15 +28,16 @@ impl<'a> UpdateContext<'a> { pub(crate) fn new(pane: &'a mut Pane, position: (usize, usize)) -> Option<(Self, &'a mut AnyTile)> { let ptr: *const Pane = &*pane; - let tile = pane.get_mut(position)?.get_mut()?; + let (tile, _signal, state) = pane.get_mut(position)?.into_raw_mut(); let res = Self { position, pane: ptr, + state, phantom: PhantomData }; - Some((res, tile)) + Some((res, tile.as_mut()?)) } #[inline] @@ -51,6 +53,21 @@ impl<'a> UpdateContext<'a> { pane.get(self.position).unwrap().signal() } + #[inline] + pub fn state(&self) -> State { + *self.state + } + + #[inline] + pub fn set_state(&mut self, state: State) { + *self.state = state; + } + + #[inline] + pub fn next_state(&mut self) { + *self.state = self.state.next(); + } + /// Returns an immutable reference to the [FullTile] at `pos` in the current [Pane]. /// Returns `None` if the tile is the current tile (see [UpdateContext]) or if it does not exist. #[inline] @@ -74,6 +91,12 @@ impl<'a> UpdateContext<'a> { pane.offset(self.position, offset) } + /// 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), &'b FullTile)> where 'a: 'b { + self.offset(offset).and_then(|pos| self.get(pos).map(|tile| (pos, tile))) + } + // SAFETY: `self.pane` originates from a `&'a mut Pane`, // guaranteeing that no accesses may be done outside of ours. // No access to `pane[position].cell` may be done! @@ -88,8 +111,9 @@ impl<'a> UpdateContext<'a> { /// /// During this phase, the tile may access itself through an immutable borrow and its signal through an owned reference. /// It may access the other tiles immutably, but it cannot access the other signals. +/// It can read and modify any tile's state. -// SAFETY: this structures ensures that it has exlusive, mutable access to `∀x, pane[x].signal` and `pane.signals`. +// SAFETY: this structures ensures that it has exlusive, mutable access to `∀x, pane[x].signal, pane[x].state` and `pane.signals`. // Other parts of `pane` may be accessed and returned immutably. pub struct TransmitContext<'a> { position: (usize, usize), @@ -132,14 +156,58 @@ impl<'a> TransmitContext<'a> { pane.get(pos)?.get() } + /// Returns the state of the tile at `pos` in the current [Pane] + #[inline] + pub fn get_state(&self, pos: (usize, usize)) -> Option { + let pane = unsafe { self.pane() }; + + // SAFETY: we only return a copy of pane[pos].state + Some(pane.get(pos)?.state()) + } + + /// Sets the state of the tile at `pos` in the current [Pane] + #[inline] + pub fn set_state(&self, pos: (usize, usize), state: State) -> Option<()> { + let pane = unsafe { self.pane_mut() }; + + // SAFETY: there are no borrows of pane[pos].state + pane.get_mut(pos)?.set_state(state); + Some(()) + } + + /// Returns whether or not the tile at `pos` accepts a signal coming from `direction`. + /// If the tile does not exist, then this function will return `false`. + #[inline] + pub fn accepts_signal(&self, pos: (usize, usize), direction: Direction) -> bool { + let pane = unsafe { self.pane() }; + + // SAFETY: does not access `pane[pos].signal` + match pane.get(pos) { + Some(tile) => tile.accepts_signal(direction), + None => false + } + } + /// 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 - pub fn send<'b>(&'b self, pos: (usize, usize), signal: Signal) -> Option> where 'a: 'b { + /// 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. + /// The signal's `position` will be set to `pos`. + pub fn send<'b>(&'b mut self, pos: (usize, usize), mut signal: Signal) -> Option> where 'a: 'b { // SAFETY: we do not return any reference to any data borrowed in this function - // SAFETY: we only access `pane[pos].signal` and `pane.signals` + // SAFETY: we only access `pane[pos].signal`, `pane[pos].state` and `pane.signals` let pane = unsafe { self.pane_mut() }; - pane.set_signal(pos, signal) + signal.set_position(pos); + + match pane.set_signal(pos, signal) { + Some(signal) => { + // SAFETY: we only access `pane[pos].state` + pane.get_mut(pos).unwrap_or_else(|| unreachable!()).set_state(State::Active); + + Some(signal) + } + None => None + } } /// Returns `Some((position.x + Δx, position.y + Δy))` iff `(x + Δx, y + Δy)` is inside the pane diff --git a/stackline/src/pane.rs b/stackline/src/pane.rs index 6aa4f6d..5b5ff07 100644 --- a/stackline/src/pane.rs +++ b/stackline/src/pane.rs @@ -65,6 +65,10 @@ impl Pane { self.tiles.get_mut(position.1 * self.width.get() + position.0) } + /// Sets the signal for the tile at `position` to `signal`. + /// Returns `Some` iff: + /// - the tile exists + /// - the tile accepts a signal (ie. it isn't empty) // SAFETY: may only access `self[pos].signal` and `self.signals` #[inline] pub fn set_signal(&mut self, position: (usize, usize), signal: Signal) -> Option> { @@ -87,7 +91,17 @@ impl Pane { Some(()) } - // TODO: update_all (requires FullTile::state) + pub fn update_all(&mut self) { + for y in 0..self.height.get() { + for x in 0..self.width.get() { + if let Some((ctx, tile)) = UpdateContext::new(self, (x, y)) { + if ctx.state() != State::Idle { + tile.update(ctx); + } + } + } + } + } #[inline] pub fn transmit(&mut self, position: (usize, usize)) -> Option<()> { diff --git a/stackline/src/signal.rs b/stackline/src/signal.rs index d9a1524..e4a2a58 100644 --- a/stackline/src/signal.rs +++ b/stackline/src/signal.rs @@ -33,4 +33,8 @@ impl Signal { pub fn position(&self) -> (usize, usize) { self.position } + + pub(crate) fn set_position(&mut self, position: (usize, usize)) { + self.position = position; + } } diff --git a/stackline/src/tile/mod.rs b/stackline/src/tile/mod.rs index 3f2718f..86f5da4 100644 --- a/stackline/src/tile/mod.rs +++ b/stackline/src/tile/mod.rs @@ -18,18 +18,23 @@ Cloning a `FullTile` results in a `FullTile` that does not have any signal. pub struct FullTile { cell: Option, signal: Option>, - // TODO: state + state: State, } // SAFETY: should not implement Tile impl FullTile { pub fn new(cell: Option) -> Self { - Self { cell, signal: None } + Self { + cell, + signal: None, + state: State::default() + } } + // SAFETY: must not access `self.signal` pub fn accepts_signal(&self, direction: Direction) -> bool { match self.cell { - Some(ref tile) => tile.accepts_signal(direction), + Some(ref tile) => self.state.accepts_signal() && tile.accepts_signal(direction), None => false, } } @@ -52,6 +57,12 @@ impl FullTile { self.cell.as_ref() } + /// Returns a mutable reference to the internal state of this tile + #[inline] + pub fn get_mut<'b>(&'b mut self) -> Option<&'b mut AnyTile> { + self.cell.as_mut() + } + /// Returns the signal of this tile #[inline] pub fn signal<'b>(&'b self) -> Option<&'b Rc> { @@ -59,13 +70,29 @@ impl FullTile { } #[inline] - pub(crate) fn take_signal(&mut self) -> Option> { + pub fn take_signal(&mut self) -> Option> { std::mem::take(&mut self.signal) } + // SAFETY: may only access `self.state` #[inline] - pub(crate) fn get_mut<'b>(&'b mut self) -> Option<&'b mut AnyTile> { - self.cell.as_mut() + pub fn state(&self) -> State { + self.state + } + + // SAFETY: may only access `self.state` + #[inline] + pub fn set_state(&mut self, state: State) { + self.state = state + } + + #[inline] + pub fn next_state(&mut self) { + self.state = self.state.next(); + } + + pub fn into_raw_mut<'b>(&'b mut self) -> (&'b mut Option, &'b mut Option>, &'b mut State) { + (&mut self.cell, &mut self.signal, &mut self.state) } } @@ -94,7 +121,9 @@ pub trait Tile: DynClone + std::fmt::Debug { /// Function to be called when the tile needs to update its internal state. /// During the "update" phase, the tile may access its signal and the other tiles immutably. #[inline] - fn update<'b>(&'b mut self, _context: UpdateContext<'b>) {} + fn update<'b>(&'b mut self, mut context: UpdateContext<'b>) { + context.next_state(); + } /// Function that will be called if the tile has a signal. fn transmit<'b>(&'b self, signal: Rc, context: TransmitContext<'b>); diff --git a/stackline/src/tile/wire.rs b/stackline/src/tile/wire.rs index 1b95498..7e36a5d 100644 --- a/stackline/src/tile/wire.rs +++ b/stackline/src/tile/wire.rs @@ -12,16 +12,15 @@ impl Wire { } impl Tile for Wire { - fn transmit<'b>(&'b self, signal: Rc, context: TransmitContext<'b>) { + fn transmit<'b>(&'b self, signal: Rc, mut context: TransmitContext<'b>) { for &direction in self.0.into_directions() { if direction == signal.direction().opposite() { continue; } - if let Some(new_pos) = context.offset(direction.into_offset()) { - let tile = context.get(new_pos); - if tile.map(|t| t.accepts_signal(direction)).unwrap_or(false) { - context.send(new_pos, signal.clone_move(direction).unwrap()); + 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!())); } } } @@ -41,18 +40,15 @@ impl Diode { } } -// impl Tile for Diode { -// fn update(&mut self, world: &mut World, signal: &mut Option>, pos: (usize, usize)) { -// if let Some(signal) = std::mem::take(signal) { -// if let Some(new_pos) = world.offset(pos, self.0.into_offset()) { -// let tile = world.get(new_pos).unwrap(); -// if tile.borrow().accepts_signal(self.0) { -// world.send_signal(new_pos, (*signal).clone_with_dir(self.0)).unwrap(); -// } -// } -// } -// } -// } +impl Tile for Diode { + fn transmit<'b>(&'b self, signal: Rc, mut context: TransmitContext<'b>) { + 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!())); + } + } + } +} #[cfg(test)] mod test { @@ -70,6 +66,7 @@ mod test { // Test the signal going from left to right test_set_signal!(pane, (0, 0), Direction::Right); + pane.update_all(); pane.transmit_all(); assert_signal!(pane, (1, 0)); @@ -77,6 +74,7 @@ mod test { assert_no_signal!(pane, (2, 0)); assert_no_signal!(pane, (1, 1)); + pane.update_all(); pane.transmit_all(); assert_signal!(pane, (2, 0)); @@ -84,14 +82,20 @@ mod test { assert_no_signal!(pane, (0, 0)); assert_no_signal!(pane, (1, 0)); + pane.update_all(); pane.transmit_all(); for (_, _, tile) in pane.tiles() { assert!(tile.signal().is_none()); } + // Let the simulation cool down + pane.update_all(); + pane.update_all(); + // Test the signal going from right to left test_set_signal!(pane, (2, 0), Direction::Left); + pane.update_all(); pane.transmit_all(); assert_signal!(pane, (1, 0)); @@ -99,6 +103,7 @@ mod test { assert_no_signal!(pane, (2, 0)); assert_no_signal!(pane, (1, 1)); + pane.update_all(); pane.transmit_all(); assert_signal!(pane, (0, 0)); diff --git a/stackline/src/utils.rs b/stackline/src/utils.rs index c71d482..9b36060 100644 --- a/stackline/src/utils.rs +++ b/stackline/src/utils.rs @@ -17,6 +17,16 @@ pub enum Direction { Right, } +/// Represents the state that a cell may be in. The usual state transition schema is `Idle → Active → Dormant → Idle`. +/// A tile will only be [`update`d](Tile::update) if it is in the `Active` or `Dormant` state. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum State { + Idle, + Active, + Dormant, +} + const HORIZONTAL: [Direction; 2] = [Direction::Left, Direction::Right]; const VERTICAL: [Direction; 2] = [Direction::Up, Direction::Down]; const ANY: [Direction; 4] = [ @@ -75,6 +85,34 @@ impl Direction { } } +impl State { + /// Rotates the state: + /// - `Idle → Active` + /// - `Active → Dormant` + /// - `Dormant → Idle` + pub fn next(self) -> Self { + match self { + State::Idle => State::Active, + State::Active => State::Dormant, + State::Dormant => State::Idle, + } + } + + /// Returns true if `Idle` + pub fn accepts_signal(self) -> bool { + match self { + State::Idle => true, + _ => false, + } + } +} + +impl Default for State { + fn default() -> Self { + State::Idle + } +} + #[cfg(test)] mod test { use super::*; @@ -92,4 +130,23 @@ mod test { } } } + + #[test] + fn test_state_next_rotate_3() { + let state = State::default(); + + assert_eq!(state, State::Idle); + assert!(state.accepts_signal()); + + let state = state.next(); + assert_eq!(state, State::Active); + assert!(!state.accepts_signal()); + + let state = state.next(); + assert_eq!(state, State::Dormant); + assert!(!state.accepts_signal()); + + let state = state.next(); + assert_eq!(state, State::Idle); + } }