🔥 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 std::cell::{Ref, RefMut};
/** An `UpdateContext` is created for every tile update during the "update" phase,
and it contains the necessary data for a tile to update its internal state.
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>) {}
}
```
/** 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: `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> {
position: (usize, usize),
pane: *const Pane,
state: &'a mut State,
phantom: PhantomData<&'a Pane>,
pane: &'a Pane,
state: State,
signal: Option<Signal>,
commit: &'a mut UpdateCommit
}
impl<'a> UpdateContext<'a> {
/// Creates a new context, returning the only mutable reference to `pane[position].cell` and the `UpdateContext`.
#[inline]
pub(crate) fn new(pane: &'a mut Pane, position: (usize, usize)) -> Option<(Self, &'a mut AnyTile)> {
/// 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 {
return None
}
guard.updated = true; // prevent duplicate updates
commit.updates.push(position);
let ptr: *const Pane = &*pane;
let (tile, _signal, state) = pane.get_mut(position)?.into_raw_mut();
let (tile, signal, state) = guard.get_raw_mut();
if tile.is_none() {
return None
}
let res = Self {
position,
pane: ptr,
state,
phantom: PhantomData
pane,
state: *state,
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.
@ -90,146 +48,62 @@ impl<'a> UpdateContext<'a> {
/// Returns the [signal](crate::FullTile::signal) of the currently updated tile.
#[inline]
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
pane.get(self.position).unwrap().signal()
/// Performs [`std::mem::take`] on the signal of the currently updated tile.
#[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.
#[inline]
pub fn state(&self) -> State {
*self.state
self.state
}
/// Sets the state of the current tile to `state`.
#[inline]
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()`
#[inline]
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 `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]
pub fn get<'b>(&'b self, pos: (usize, usize)) -> Option<&'b FullTile> where 'a: 'b {
let pane = unsafe { self.pane() };
// SAFETY: we only access `pane[pos]` if `position != pos`
if pos != self.position {
pane.get(pos)
} else {
None
}
pub fn get<'b>(&'b self, pos: (usize, usize)) -> Option<Ref<'b, FullTile>> where 'a: 'b {
self.pane.get(pos)
}
/// Returns `Some((position.x + Δx, position.y + Δy))` iff `(x + Δx, y + Δy)` is inside the pane
#[inline]
pub fn offset(&self, offset: (i8, i8)) -> Option<(usize, usize)> {
let pane = unsafe { self.pane() };
// SAFETY: pane.offset does not access pane[position].cell
pane.offset(self.position, offset)
self.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 {
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)))
}
// 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`.
/// 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) {
match self.pane.get(pos) {
Some(tile) => tile.accepts_signal(direction),
None => false
}
@ -239,82 +113,68 @@ impl<'a> TransmitContext<'a> {
/// 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`, `pane[pos].state` and `pane.signals`
let pane = unsafe { self.pane_mut() };
pub fn send(&mut self, pos: (usize, usize), mut signal: Signal) -> Option<()> {
signal.set_position(pos);
pane.set_signal(pos, signal)?;
// SAFETY: we only access `pane[pos].state`
pane.get_mut(pos).unwrap_or_else(|| unreachable!()).set_state(State::Active);
if !self.pane.in_bounds(pos) {
return None
}
self.commit.send(pos, signal);
Some(())
}
}
/// Returns `Some((position.x + Δx, position.y + Δy))` iff `(x + Δx, y + Δy)` is inside the pane
#[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
pane.offset(self.position, offset)
}
pub(crate) struct UpdateCommit {
states: Vec<(usize, usize, State)>,
signals: Vec<(usize, usize, Option<Signal>)>,
updates: Vec<(usize, usize)>,
}
// SAFETY: `self.pane` originates from a `&'a mut Pane`,
// guaranteeing that no accesses may be done outside of ours.
#[inline]
unsafe fn pane<'b>(&'b self) -> &'b Pane {
&*self.pane
impl UpdateCommit {
pub(crate) fn new() -> Self {
Self {
states: Vec::new(),
signals: Vec::new(),
updates: Vec::new(),
}
}
// SAFETY: `self.pane` originates from a `&'a mut Pane`,
// guaranteeing that no accesses may be done outside of ours.
#[inline]
unsafe fn pane_mut<'b>(&'b self) -> &'b mut Pane {
&mut *self.pane
fn send(&mut self, pos: (usize, usize), signal: Signal) {
self.signals.push((pos.0, pos.1, Some(signal)));
}
}
#[cfg(test)]
mod test {
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
fn set_state(&mut self, pos: (usize, usize), state: State) {
self.states.push((pos.0, pos.1, state));
}
#[test]
fn test_transmit() {
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();
assert_eq!(ctx.position(), (1, 2));
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;
}
}
let tile_self: Option<&AnyTile> = ctx.get((1, 2));
assert!(tile_self.is_some()); // We may read our AnyTile, as they are under an immutable borrow
for (x, y, state) in self.states {
if let Some(mut tile) = pane.get_mut((x, y)) {
tile.set_state(state);
}
}
// Check that the signal was dropped
std::mem::drop(ctx);
assert!(pane.get((1, 2)).unwrap().signal().is_none());
for (x, y, signal) in self.signals {
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
};
if push_signal {
pane.signals.push((x, y));
}
}
}
}

@ -1,12 +1,13 @@
use super::*;
use std::cell::{RefCell, Ref, RefMut};
#[derive(Debug)]
pub struct Pane {
tiles: Vec<FullTile>,
tiles: Vec<RefCell<FullTile>>,
width: NonZeroUsize,
height: NonZeroUsize,
signals: Vec<(usize, usize)>,
pub(crate) signals: Vec<(usize, usize)>,
}
impl Pane {
@ -17,14 +18,13 @@ impl Pane {
Some(Self {
width: width.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
// SAFETY: this function may *not* access `self.signals`, `∀x, self.tiles[x].cell` or `∀x, self.tiles[x].signal`
#[inline]
pub fn offset(&self, position: (usize, usize), offset: (i8, i8)) -> Option<(usize, usize)> {
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]
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) {
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]
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) {
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`.
/// 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), mut signal: Signal) -> Option<()> {
signal.set_position(position);
self.get_mut(position)?.set_signal(signal)?;
self.get_mut(position)?.set_signal(Some(signal))?;
self.signals.push(position);
Some(())
}
@ -84,62 +88,38 @@ impl Pane {
}
#[inline]
pub fn update(&mut self, position: (usize, usize)) -> Option<()> {
let (ctx, tile) = UpdateContext::new(self, position)?;
fn update(&mut self, position: (usize, usize), commit: &mut UpdateCommit) -> Option<()> {
let (ctx, mut tile) = UpdateContext::new(self, position, commit)?;
tile.update(ctx);
Some(())
}
/// Calls [`Pane::update`] on all non-empty, non-idle tiles
fn update_all(&mut self) {
// TODO: document
pub fn step(&mut self) {
let mut commit = UpdateCommit::new();
for position in std::mem::replace(&mut self.signals, Vec::new()) {
let _ = self.update(position, &mut commit);
}
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);
}
if self.get_state((x, y)).unwrap() != State::Idle {
let _ = self.update((x, y), &mut commit);
}
}
}
}
#[inline]
pub fn transmit(&mut self, position: (usize, usize)) -> Option<()> {
let (ctx, tile, signal) = TransmitContext::new(self, position)?;
tile.transmit(signal, ctx);
Some(())
}
/// Calls [`Pane::transmit`] on all tiles with a signal
fn transmit_all(&mut self) {
// TODO: store a second buffer and perform swap reads
for position in std::mem::replace(&mut self.signals, vec![]) {
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();
commit.apply(self);
}
/// Returns an iterator over the tiles and their coordinates
#[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
#[inline]
pub fn tiles_mut<'b>(&'b mut self) -> impl Iterator<Item=(usize, usize, &'b mut FullTile)> + 'b {
let width = self.width;
self.tiles.iter_mut().enumerate().map(move |(i, v)| (i % width, i / width, v))
pub fn tiles<'b>(&'b self) -> impl Iterator<Item=(usize, usize, &RefCell<FullTile>)> + 'b {
self.tiles.iter().enumerate().filter_map(move |(i, v)| {
Some((i % self.width, i / self.width, v))
})
}
}

@ -18,19 +18,20 @@ pub struct FullTile {
cell: Option<AnyTile>,
signal: Option<Signal>,
state: State,
pub(crate) updated: bool,
}
// SAFETY: should not implement Tile
// NOTE: should not implement Tile
impl FullTile {
pub fn new(cell: Option<AnyTile>) -> Self {
Self {
cell,
signal: None,
state: State::default()
state: State::default(),
updated: false
}
}
// SAFETY: must not access `self.signal`
pub fn accepts_signal(&self, direction: Direction) -> bool {
match self.cell {
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()
pub(crate) fn set_signal(&mut self, signal: Signal) -> Option<()> {
/// Returns `Some` iff self.cell.is_some()
pub(crate) fn set_signal(&mut self, signal: Option<Signal>) -> Option<()> {
if self.cell.is_some() {
self.signal = Some(signal);
self.signal = signal;
Some(())
} else {
None
@ -71,16 +72,16 @@ impl FullTile {
std::mem::take(&mut self.signal)
}
// SAFETY: may only access `self.state`
#[inline]
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
if self.cell.is_some() {
self.state = state
}
}
#[inline]
@ -88,7 +89,7 @@ impl FullTile {
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)
}
}
@ -115,16 +116,12 @@ impl From<()> for FullTile {
}
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.
/// Function to be called when the tile needs to be updated.
#[inline]
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: Signal, context: TransmitContext<'b>);
/// Should return true iff the tile accepts a signal travelling in `Direction`
#[inline]
#[allow(unused_variables)]
@ -147,11 +144,6 @@ impl AnyTile {
self.0.update(ctx)
}
#[inline]
pub fn transmit<'b>(&'b self, signal: Signal, context: TransmitContext<'b>) {
self.0.transmit(signal, context)
}
#[inline]
pub fn accepts_signal(&self, direction: Direction) -> bool {
self.0.accepts_signal(direction)
@ -172,7 +164,7 @@ mod crate_macros {
( $width:expr, $height:expr, [ $( $x:expr ),* ] ) => {{
assert!($width > 0);
assert!($height > 0);
let mut pane = Pane::empty($width, $height).unwrap();
let pane = Pane::empty($width, $height).unwrap();
let mut index = 0;
$(
@ -200,7 +192,8 @@ mod crate_macros {
#[macro_export]
macro_rules! assert_signal {
( $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());
signal
}};
@ -214,7 +207,8 @@ mod crate_macros {
#[macro_export]
macro_rules! assert_no_signal {
( $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());
}}
}

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

Loading…
Cancel
Save