🔥 Replace multi-phase model with commit log model

main
Shad Amethyst 2 years ago
parent dd2de9ee14
commit 9b63640e06
Signed by: amethyst
GPG Key ID: D970C8DD1D6DEE36

@ -1,84 +1,42 @@
use std::marker::PhantomData;
use super::*; use super::*;
use std::cell::{Ref, RefMut};
/** An `UpdateContext` is created for every tile update during the "update" phase, /** Provides an interface between a [`Tile`] and its parent [`Pane`] during [`Tile::update`].
and it contains the necessary data for a tile to update its internal state. All actions performed through `UpdateContext` will be executed *after* all the tiles have updated.
During the update phase, a tile may only access itself mutably, through the mutable
reference it was initially passed through its [`update`](Tile::update) method.
All accesses to other tiles and all signals must be done immutably.
It thus *cannot* access itself through this context structure, although it may read its
signal here.
It *can* access the other tiles and their signals immutably.
## Examples
This type is most commonly found when implementing [`Tile::update`]:
```
# use stackline::{*, tile::Tile, utils::State, context::*};
#
#[derive(Clone, Debug)]
struct MyTile(u8);
impl Tile for MyTile {
fn update<'b>(&'b mut self, mut ctx: UpdateContext<'b>) {
// Counts the number of active neighbors
let mut active_neighbors = 0;
for dy in -1..1 {
for dx in -1..1 {
let offset = (dx, dy);
if offset == (0, 0) {
continue;
}
// Get tile next to us:
if let Some((_position, tile)) = ctx.get_offset(offset) {
if tile.state() == State::Active {
active_neighbors += 1;
}
}
}
}
self.0 = active_neighbors;
ctx.next_state(); // Become dormant
}
#
# fn transmit<'b>(&'b self, signal: Signal, ctx: TransmitContext<'b>) {}
}
```
**/ **/
// SAFETY: `pane[position].cell` is borrow mutably, while a pointer to the original Pane is kept;
// thus, no other reference to `pane[position].cell` may be done
pub struct UpdateContext<'a> { pub struct UpdateContext<'a> {
position: (usize, usize), position: (usize, usize),
pane: *const Pane, pane: &'a Pane,
state: &'a mut State, state: State,
signal: Option<Signal>,
phantom: PhantomData<&'a Pane>, commit: &'a mut UpdateCommit
} }
impl<'a> UpdateContext<'a> { impl<'a> UpdateContext<'a> {
/// Creates a new context, returning the only mutable reference to `pane[position].cell` and the `UpdateContext`. /// Returns `None` if the tile was already updated or is empty
#[inline] pub(crate) fn new(pane: &'a Pane, position: (usize, usize), commit: &'a mut UpdateCommit) -> Option<(Self, RefMut<'a, AnyTile>)> {
pub(crate) fn new(pane: &'a mut Pane, position: (usize, usize)) -> Option<(Self, &'a mut AnyTile)> { let mut guard = pane.get_mut(position)?;
if guard.updated {
return None
}
guard.updated = true; // prevent duplicate updates
commit.updates.push(position);
let ptr: *const Pane = &*pane; let (tile, signal, state) = guard.get_raw_mut();
let (tile, _signal, state) = pane.get_mut(position)?.into_raw_mut();
if tile.is_none() {
return None
}
let res = Self { let res = Self {
position, position,
pane: ptr, pane,
state, state: *state,
phantom: PhantomData signal: std::mem::take(signal),
commit
}; };
Some((res, tile.as_mut()?)) Some((res, RefMut::map(guard, |tile| tile.get_mut().unwrap_or_else(|| unreachable!()))))
} }
/// Returns the position of the currently updated tile. /// Returns the position of the currently updated tile.
@ -90,146 +48,62 @@ impl<'a> UpdateContext<'a> {
/// Returns the [signal](crate::FullTile::signal) of the currently updated tile. /// Returns the [signal](crate::FullTile::signal) of the currently updated tile.
#[inline] #[inline]
pub fn signal<'b>(&'b self) -> Option<&'b Signal> where 'a: 'b { pub fn signal<'b>(&'b self) -> Option<&'b Signal> where 'a: 'b {
let pane = unsafe { self.pane() }; self.signal.as_ref()
}
// SAFETY: `pane[position].signal` is not borrowed mutably /// Performs [`std::mem::take`] on the signal of the currently updated tile.
pane.get(self.position).unwrap().signal() #[inline]
pub fn take_signal(&mut self) -> Option<Signal> {
std::mem::take(&mut self.signal)
} }
// #[inline]
// pub fn set_signal(&mut self, signal: Option<Signal>) {
// *self.signal = signal;
// }
/// Returns the state of the current tile. /// Returns the state of the current tile.
#[inline] #[inline]
pub fn state(&self) -> State { pub fn state(&self) -> State {
*self.state self.state
} }
/// Sets the state of the current tile to `state`. /// Sets the state of the current tile to `state`.
#[inline] #[inline]
pub fn set_state(&mut self, state: State) { pub fn set_state(&mut self, state: State) {
*self.state = state; self.commit.set_state(self.position, state);
} }
/// Sets the state of the current tile to `state.next()` /// Sets the state of the current tile to `state.next()`
#[inline] #[inline]
pub fn next_state(&mut self) { pub fn next_state(&mut self) {
*self.state = self.state.next(); self.commit.set_state(self.position, self.state.next());
} }
/// Returns an immutable reference to the [FullTile] at `pos` in the current [Pane]. /// 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. /// Returns `None` if the tile is borrowed mutably or if it does not exist.
#[inline] #[inline]
pub fn get<'b>(&'b self, pos: (usize, usize)) -> Option<&'b FullTile> where 'a: 'b { pub fn get<'b>(&'b self, pos: (usize, usize)) -> Option<Ref<'b, FullTile>> where 'a: 'b {
let pane = unsafe { self.pane() }; self.pane.get(pos)
// SAFETY: we only access `pane[pos]` if `position != pos`
if pos != self.position {
pane.get(pos)
} else {
None
}
} }
/// Returns `Some((position.x + Δx, position.y + Δy))` iff `(x + Δx, y + Δy)` is inside the pane /// Returns `Some((position.x + Δx, position.y + Δy))` iff `(x + Δx, y + Δy)` is inside the pane
#[inline] #[inline]
pub fn offset(&self, offset: (i8, i8)) -> Option<(usize, usize)> { pub fn offset(&self, offset: (i8, i8)) -> Option<(usize, usize)> {
let pane = unsafe { self.pane() }; self.pane.offset(self.position, offset)
// SAFETY: pane.offset does not access pane[position].cell
pane.offset(self.position, offset)
} }
/// Shortcut for calling both `ctx.offset(offset)` and `ctx.get(pos)` /// Shortcut for calling both `ctx.offset(offset)` and `ctx.get(pos)`
#[inline] #[inline]
pub fn get_offset<'b>(&'b self, offset: (i8, i8)) -> Option<((usize, usize), &'b FullTile)> where 'a: 'b { pub fn get_offset<'b>(&'b self, offset: (i8, i8)) -> Option<((usize, usize), Ref<'b, FullTile>)> where 'a: 'b {
self.offset(offset).and_then(|pos| self.get(pos).map(|tile| (pos, tile))) 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!
#[inline]
unsafe fn pane<'b>(&'b self) -> &'b Pane {
&*self.pane
}
}
/// An UpdateContext is created for every tile update during the "transmit" phase,
/// and it contains the necessary data for a tile to transmit its internal signal to other tiles.
///
/// During this phase, the tile may access itself through an immutable borrow and its signal through an owned reference.
/// It *can* 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, pane[x].state` and `pane.signals`.
// Other parts of `pane` may be accessed and returned immutably.
pub struct TransmitContext<'a> {
position: (usize, usize),
pane: *mut Pane,
phantom: PhantomData<&'a mut Pane>,
}
impl<'a> TransmitContext<'a> {
pub(crate) fn new(pane: &'a mut Pane, position: (usize, usize)) -> Option<(Self, &'a AnyTile, Signal)> {
let ptr: *mut Pane = &mut *pane;
// SAFETY: no mutable accesses to `∀x, pane[x].cell` are made by `TransmitContext`
let tile: &AnyTile = unsafe {
(*ptr).get(position).unwrap().get()?
};
let signal = pane.get_mut(position)?.take_signal()?;
let res = Self {
position,
pane: ptr,
phantom: PhantomData
};
Some((res, tile, signal))
}
/// Returns the position of the current tile
#[inline]
pub fn position(&self) -> (usize, usize) {
self.position
}
/// Returns an immutable reference to the [tile](AnyTile) at `pos` in the current [Pane].
/// Returns `None` if that tile does not exist.
#[inline]
pub fn get<'b>(&'b self, pos: (usize, usize)) -> Option<&'b AnyTile> where 'a: 'b {
let pane = unsafe { self.pane() };
// SAFETY: we only return pane[pos].cell
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<State> {
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`. /// 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`. /// If the tile does not exist, then this function will return `false`.
#[inline] #[inline]
pub fn accepts_signal(&self, pos: (usize, usize), direction: Direction) -> bool { pub fn accepts_signal(&self, pos: (usize, usize), direction: Direction) -> bool {
let pane = unsafe { self.pane() }; match self.pane.get(pos) {
// SAFETY: does not access `pane[pos].signal`
match pane.get(pos) {
Some(tile) => tile.accepts_signal(direction), Some(tile) => tile.accepts_signal(direction),
None => false None => false
} }
@ -239,82 +113,68 @@ impl<'a> TransmitContext<'a> {
/// Returns true if the signal was stored in a cell, false otherwise. /// 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 target cell's state will be set to `Active` if it received the signal.
/// The signal's `position` will be set to `pos`. /// 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 { pub fn send(&mut self, pos: (usize, usize), mut signal: Signal) -> Option<()> {
// SAFETY: we do not return any reference to any data borrowed in this function
// SAFETY: we only access `pane[pos].signal`, `pane[pos].state` and `pane.signals`
let pane = unsafe { self.pane_mut() };
signal.set_position(pos); signal.set_position(pos);
pane.set_signal(pos, signal)?; if !self.pane.in_bounds(pos) {
// SAFETY: we only access `pane[pos].state` return None
pane.get_mut(pos).unwrap_or_else(|| unreachable!()).set_state(State::Active);
Some(())
} }
/// Returns `Some((position.x + Δx, position.y + Δy))` iff `(x + Δx, y + Δy)` is inside the pane self.commit.send(pos, signal);
#[inline]
pub fn offset(&self, offset: (i8, i8)) -> Option<(usize, usize)> {
let pane = unsafe { self.pane() };
// SAFETY: pane.offset does not access pane[position].signal or pane.signals Some(())
pane.offset(self.position, offset) }
} }
// SAFETY: `self.pane` originates from a `&'a mut Pane`, pub(crate) struct UpdateCommit {
// guaranteeing that no accesses may be done outside of ours. states: Vec<(usize, usize, State)>,
#[inline] signals: Vec<(usize, usize, Option<Signal>)>,
unsafe fn pane<'b>(&'b self) -> &'b Pane { updates: Vec<(usize, usize)>,
&*self.pane
} }
// SAFETY: `self.pane` originates from a `&'a mut Pane`, impl UpdateCommit {
// guaranteeing that no accesses may be done outside of ours. pub(crate) fn new() -> Self {
#[inline] Self {
unsafe fn pane_mut<'b>(&'b self) -> &'b mut Pane { states: Vec::new(),
&mut *self.pane signals: Vec::new(),
updates: Vec::new(),
} }
} }
#[cfg(test)] fn send(&mut self, pos: (usize, usize), signal: Signal) {
mod test { self.signals.push((pos.0, pos.1, Some(signal)));
use super::*;
#[test]
fn test_update_exclusivity() {
// Check that UpdateContext does not allow any other reference to `tiles[position].cell` to be made
let mut pane = Pane::empty(4, 4).unwrap();
let mut tile = FullTile::from(Wire::new(Orientation::Any));
tile.set_signal(Signal::empty((1, 2), Direction::Up)).unwrap();
*pane.get_mut((1, 2)).unwrap() = tile;
let (ctx, _tile) = UpdateContext::new(&mut pane, (1, 2)).unwrap();
assert_eq!(ctx.position(), (1, 2));
let tile_self: Option<&FullTile> = ctx.get((1, 2)); // The FullTile may not be read
assert!(tile_self.is_none());
assert!(ctx.signal().is_some()); // Our Signal may be read, though
} }
#[test] fn set_state(&mut self, pos: (usize, usize), state: State) {
fn test_transmit() { self.states.push((pos.0, pos.1, state));
let mut pane = Pane::empty(4, 4).unwrap(); }
let mut tile = FullTile::from(Wire::new(Orientation::Any));
tile.set_signal(Signal::empty((1, 2), Direction::Up)).unwrap();
*pane.get_mut((1, 2)).unwrap() = tile;
let (ctx, _tile, _signal) = TransmitContext::new(&mut pane, (1, 2)).unwrap(); pub(crate) fn apply(self, pane: &mut Pane) {
for (x, y) in self.updates {
if let Some(mut tile) = pane.get_mut((x, y)) {
tile.updated = false;
}
}
assert_eq!(ctx.position(), (1, 2)); for (x, y, state) in self.states {
if let Some(mut tile) = pane.get_mut((x, y)) {
tile.set_state(state);
}
}
let tile_self: Option<&AnyTile> = ctx.get((1, 2)); for (x, y, signal) in self.signals {
assert!(tile_self.is_some()); // We may read our AnyTile, as they are under an immutable borrow let push_signal = if let Some(mut 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
true
} else {
false
};
// Check that the signal was dropped if push_signal {
std::mem::drop(ctx); pane.signals.push((x, y));
assert!(pane.get((1, 2)).unwrap().signal().is_none()); }
}
} }
} }

@ -1,12 +1,13 @@
use super::*; use super::*;
use std::cell::{RefCell, Ref, RefMut};
#[derive(Debug)] #[derive(Debug)]
pub struct Pane { pub struct Pane {
tiles: Vec<FullTile>, tiles: Vec<RefCell<FullTile>>,
width: NonZeroUsize, width: NonZeroUsize,
height: NonZeroUsize, height: NonZeroUsize,
signals: Vec<(usize, usize)>, pub(crate) signals: Vec<(usize, usize)>,
} }
impl Pane { impl Pane {
@ -17,14 +18,13 @@ impl Pane {
Some(Self { Some(Self {
width: width.try_into().ok()?, width: width.try_into().ok()?,
height: height.try_into().ok()?, height: height.try_into().ok()?,
tiles: vec![FullTile::default(); length], tiles: vec![RefCell::new(FullTile::default()); length],
signals: vec![], signals: Vec::new(),
}) })
} }
/// Returns `Some((x + Δx, y + Δy))` iff `(x + Δx, y + Δy)` is inside the world /// Returns `Some((x + Δx, y + Δy))` iff `(x + Δx, y + Δy)` is inside the world
// SAFETY: this function may *not* access `self.signals`, `∀x, self.tiles[x].cell` or `∀x, self.tiles[x].signal`
#[inline] #[inline]
pub fn offset(&self, position: (usize, usize), offset: (i8, i8)) -> Option<(usize, usize)> { pub fn offset(&self, position: (usize, usize), offset: (i8, i8)) -> Option<(usize, usize)> {
if offset.0 < 0 && (-offset.0) as usize > position.0 if offset.0 < 0 && (-offset.0) as usize > position.0
@ -46,34 +46,38 @@ impl Pane {
} }
} }
// SAFETY: this function may not access `self.signals`, nor may it read the contents of `self.tiles[position]` // TODO: Have a Result instead of an Option
#[inline] #[inline]
pub fn get<'b>(&'b self, position: (usize, usize)) -> Option<&'b FullTile> { pub fn get<'b>(&'b self, position: (usize, usize)) -> Option<Ref<'b, FullTile>> {
if !self.in_bounds(position) { if !self.in_bounds(position) {
return None; return None;
} }
self.tiles.get(position.1 * self.width.get() + position.0) self.tiles.get(position.1 * self.width.get() + position.0).map(|tile| tile.try_borrow().ok()).flatten()
} }
#[inline] #[inline]
pub fn get_mut<'b>(&'b mut self, position: (usize, usize)) -> Option<&'b mut FullTile> { pub fn get_mut<'b>(&'b self, position: (usize, usize)) -> Option<RefMut<'b, FullTile>> {
if !self.in_bounds(position) { if !self.in_bounds(position) {
return None; return None;
} }
self.tiles.get_mut(position.1 * self.width.get() + position.0) self.tiles.get(position.1 * self.width.get() + position.0).map(|tile| tile.try_borrow_mut().ok()).flatten()
}
#[inline]
pub fn get_state(&self, position: (usize, usize)) -> Option<State> {
self.get(position).map(|x| x.state().clone())
} }
/// Sets the signal for the tile at `position` to `signal`. /// Sets the signal for the tile at `position` to `signal`.
/// Returns `Some` iff: /// Returns `Some` iff:
/// - the tile exists /// - the tile exists
/// - the tile accepts a signal (ie. it isn't empty) /// - the tile accepts a signal (ie. it isn't empty)
// SAFETY: may only access `self[pos].signal` and `self.signals`
#[inline] #[inline]
pub fn set_signal(&mut self, position: (usize, usize), mut signal: Signal) -> Option<()> { pub fn set_signal(&mut self, position: (usize, usize), mut signal: Signal) -> Option<()> {
signal.set_position(position); signal.set_position(position);
self.get_mut(position)?.set_signal(signal)?; self.get_mut(position)?.set_signal(Some(signal))?;
self.signals.push(position); self.signals.push(position);
Some(()) Some(())
} }
@ -84,62 +88,38 @@ impl Pane {
} }
#[inline] #[inline]
pub fn update(&mut self, position: (usize, usize)) -> Option<()> { fn update(&mut self, position: (usize, usize), commit: &mut UpdateCommit) -> Option<()> {
let (ctx, tile) = UpdateContext::new(self, position)?; let (ctx, mut tile) = UpdateContext::new(self, position, commit)?;
tile.update(ctx); tile.update(ctx);
Some(()) Some(())
} }
/// Calls [`Pane::update`] on all non-empty, non-idle tiles // TODO: document
fn update_all(&mut self) { pub fn step(&mut self) {
for y in 0..self.height.get() { let mut commit = UpdateCommit::new();
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<()> {
let (ctx, tile, signal) = TransmitContext::new(self, position)?;
tile.transmit(signal, ctx);
Some(()) for position in std::mem::replace(&mut self.signals, Vec::new()) {
let _ = self.update(position, &mut commit);
} }
/// Calls [`Pane::transmit`] on all tiles with a signal for y in 0..self.height.get() {
fn transmit_all(&mut self) { for x in 0..self.width.get() {
// TODO: store a second buffer and perform swap reads if self.get_state((x, y)).unwrap() != State::Idle {
for position in std::mem::replace(&mut self.signals, vec![]) { let _ = self.update((x, y), &mut commit);
let _ = self.transmit(position); // May return None if the signal was aliased
} }
} }
/// Runs a single simulation step, which consists of:
/// - an update phase, which mutates the inner state of [active](State::Active)] cells by calling [`Tile::update`]
/// - a transmit phase, which mutates and moves signals between cells by calling [`Tile::transmit`]
pub fn step(&mut self) {
self.update_all();
self.transmit_all();
} }
/// Returns an iterator over the tiles and their coordinates commit.apply(self);
#[inline]
pub fn tiles<'b>(&'b self) -> impl Iterator<Item=(usize, usize, &'b FullTile)> + 'b {
self.tiles.iter().enumerate().map(move |(i, v)| (i % self.width, i / self.width, v))
} }
/// Returns a mutable iterator over the tiles and their coordinates /// Returns an iterator over the tiles and their coordinates
#[inline] #[inline]
pub fn tiles_mut<'b>(&'b mut self) -> impl Iterator<Item=(usize, usize, &'b mut FullTile)> + 'b { pub fn tiles<'b>(&'b self) -> impl Iterator<Item=(usize, usize, &RefCell<FullTile>)> + 'b {
let width = self.width; self.tiles.iter().enumerate().filter_map(move |(i, v)| {
self.tiles.iter_mut().enumerate().map(move |(i, v)| (i % width, i / width, v)) Some((i % self.width, i / self.width, v))
})
} }
} }

@ -18,19 +18,20 @@ pub struct FullTile {
cell: Option<AnyTile>, cell: Option<AnyTile>,
signal: Option<Signal>, signal: Option<Signal>,
state: State, state: State,
pub(crate) updated: bool,
} }
// SAFETY: should not implement Tile // NOTE: should not implement Tile
impl FullTile { impl FullTile {
pub fn new(cell: Option<AnyTile>) -> Self { pub fn new(cell: Option<AnyTile>) -> Self {
Self { Self {
cell, cell,
signal: None, signal: None,
state: State::default() state: State::default(),
updated: false
} }
} }
// SAFETY: must not access `self.signal`
pub fn accepts_signal(&self, direction: Direction) -> bool { pub fn accepts_signal(&self, direction: Direction) -> bool {
match self.cell { match self.cell {
Some(ref tile) => self.state.accepts_signal() && tile.accepts_signal(direction), Some(ref tile) => self.state.accepts_signal() && tile.accepts_signal(direction),
@ -38,10 +39,10 @@ impl FullTile {
} }
} }
/// Returns Some(signal) iff self.cell.is_some() /// Returns `Some` iff self.cell.is_some()
pub(crate) fn set_signal(&mut self, signal: Signal) -> Option<()> { pub(crate) fn set_signal(&mut self, signal: Option<Signal>) -> Option<()> {
if self.cell.is_some() { if self.cell.is_some() {
self.signal = Some(signal); self.signal = signal;
Some(()) Some(())
} else { } else {
None None
@ -71,24 +72,24 @@ impl FullTile {
std::mem::take(&mut self.signal) std::mem::take(&mut self.signal)
} }
// SAFETY: may only access `self.state`
#[inline] #[inline]
pub fn state(&self) -> State { pub fn state(&self) -> State {
self.state self.state
} }
// SAFETY: may only access `self.state`
#[inline] #[inline]
pub fn set_state(&mut self, state: State) { pub fn set_state(&mut self, state: State) {
if self.cell.is_some() {
self.state = state self.state = state
} }
}
#[inline] #[inline]
pub fn next_state(&mut self) { pub fn next_state(&mut self) {
self.state = self.state.next(); self.state = self.state.next();
} }
pub fn into_raw_mut<'b>(&'b mut self) -> (&'b mut Option<AnyTile>, &'b mut Option<Signal>, &'b mut State) { pub fn get_raw_mut<'b>(&'b mut self) -> (&'b mut Option<AnyTile>, &'b mut Option<Signal>, &'b mut State) {
(&mut self.cell, &mut self.signal, &mut self.state) (&mut self.cell, &mut self.signal, &mut self.state)
} }
} }
@ -115,16 +116,12 @@ impl From<()> for FullTile {
} }
pub trait Tile: DynClone + std::fmt::Debug { pub trait Tile: DynClone + std::fmt::Debug {
/// Function to be called when the tile needs to update its internal state. /// Function to be called when the tile needs to be updated.
/// During the "update" phase, the tile may access its signal and the other tiles immutably.
#[inline] #[inline]
fn update<'b>(&'b mut self, mut context: UpdateContext<'b>) { fn update<'b>(&'b mut self, mut context: UpdateContext<'b>) {
context.next_state(); context.next_state();
} }
/// Function that will be called if the tile has a signal.
fn transmit<'b>(&'b self, signal: Signal, context: TransmitContext<'b>);
/// Should return true iff the tile accepts a signal travelling in `Direction` /// Should return true iff the tile accepts a signal travelling in `Direction`
#[inline] #[inline]
#[allow(unused_variables)] #[allow(unused_variables)]
@ -147,11 +144,6 @@ impl AnyTile {
self.0.update(ctx) self.0.update(ctx)
} }
#[inline]
pub fn transmit<'b>(&'b self, signal: Signal, context: TransmitContext<'b>) {
self.0.transmit(signal, context)
}
#[inline] #[inline]
pub fn accepts_signal(&self, direction: Direction) -> bool { pub fn accepts_signal(&self, direction: Direction) -> bool {
self.0.accepts_signal(direction) self.0.accepts_signal(direction)
@ -172,7 +164,7 @@ mod crate_macros {
( $width:expr, $height:expr, [ $( $x:expr ),* ] ) => {{ ( $width:expr, $height:expr, [ $( $x:expr ),* ] ) => {{
assert!($width > 0); assert!($width > 0);
assert!($height > 0); assert!($height > 0);
let mut pane = Pane::empty($width, $height).unwrap(); let pane = Pane::empty($width, $height).unwrap();
let mut index = 0; let mut index = 0;
$( $(
@ -200,7 +192,8 @@ mod crate_macros {
#[macro_export] #[macro_export]
macro_rules! assert_signal { macro_rules! assert_signal {
( $pane:expr, $pos:expr ) => {{ ( $pane:expr, $pos:expr ) => {{
let signal = $pane.get($pos).expect(&format!("Couldn't get tile at {:?}", $pos)).signal(); let guard = $pane.get($pos).expect(&format!("Couldn't get tile at {:?}", $pos));
let signal = guard.signal();
assert!(signal.is_some()); assert!(signal.is_some());
signal signal
}}; }};
@ -214,7 +207,8 @@ mod crate_macros {
#[macro_export] #[macro_export]
macro_rules! assert_no_signal { macro_rules! assert_no_signal {
( $pane:expr, $pos:expr) => {{ ( $pane:expr, $pos:expr) => {{
let signal = $pane.get($pos).expect(&format!("Couldn't get tile at {:?}", $pos)).signal(); let guard = $pane.get($pos).expect(&format!("Couldn't get tile at {:?}", $pos));
let signal = guard.signal();
assert!(signal.is_none()); assert!(signal.is_none());
}} }}
} }

@ -12,7 +12,8 @@ impl Wire {
} }
impl Tile for Wire { impl Tile for Wire {
fn transmit<'b>(&'b self, signal: Signal, mut context: TransmitContext<'b>) { fn update<'b>(&'b mut self, mut context: UpdateContext<'b>) {
if let Some(signal) = context.take_signal() {
for &direction in self.0.into_directions() { for &direction in self.0.into_directions() {
if direction == signal.direction().opposite() { if direction == signal.direction().opposite() {
continue; continue;
@ -26,6 +27,11 @@ impl Tile for Wire {
} }
} }
if context.state() != State::Idle {
context.next_state();
}
}
fn accepts_signal(&self, direction: Direction) -> bool { fn accepts_signal(&self, direction: Direction) -> bool {
self.0.contains(direction) self.0.contains(direction)
} }
@ -41,7 +47,8 @@ impl Diode {
} }
impl Tile for Diode { impl Tile for Diode {
fn transmit<'b>(&'b self, signal: Signal, mut context: TransmitContext<'b>) { fn update<'b>(&'b mut self, mut context: UpdateContext<'b>) {
if let Some(signal) = context.take_signal() {
// Block signals coming from where the diode is looking // Block signals coming from where the diode is looking
if signal.direction().opposite() == self.0 { if signal.direction().opposite() == self.0 {
return; return;
@ -53,6 +60,11 @@ impl Tile for Diode {
} }
} }
} }
if context.state() != State::Idle {
context.next_state();
}
}
} }
#[cfg(test)] #[cfg(test)]
@ -73,6 +85,8 @@ mod test {
pane.step(); pane.step();
println!("{:#?}", pane);
assert_signal!(pane, (1, 0)); assert_signal!(pane, (1, 0));
assert_no_signal!(pane, (0, 0)); assert_no_signal!(pane, (0, 0));
assert_no_signal!(pane, (2, 0)); assert_no_signal!(pane, (2, 0));
@ -87,7 +101,7 @@ mod test {
pane.step(); pane.step();
for (_, _, tile) in pane.tiles() { for (_, _, tile) in pane.tiles() {
assert!(tile.signal().is_none()); assert!(tile.borrow().signal().is_none());
} }
// Let the simulation cool down // Let the simulation cool down
@ -137,6 +151,7 @@ mod test {
for &pos in positions.iter().cycle().take(16) { for &pos in positions.iter().cycle().take(16) {
pane.step(); pane.step();
println!("{:#?}", pane);
assert_signal!(pane, pos); assert_signal!(pane, pos);
for &pos2 in positions.iter() { for &pos2 in positions.iter() {
if pos == pos2 { if pos == pos2 {

Loading…
Cancel
Save