From 489cdd1750a0b56b75dc019343cc7ab330e14b0a Mon Sep 17 00:00:00 2001 From: Adrien Burgun Date: Sun, 26 Jun 2022 17:45:43 +0200 Subject: [PATCH] :sparkles: Add contracts to several methods, force_send --- stackline/Cargo.toml | 3 +- stackline/src/context.rs | 130 ++++++++++++++++++++++--- stackline/src/lib.rs | 3 + stackline/src/pane.rs | 44 +++++++++ stackline/src/tile/macros.rs | 59 ++++++++++++ stackline/src/tile/mod.rs | 182 +++++++++++++++++++++++------------ stackline/tiles/wire.rs | 10 +- 7 files changed, 351 insertions(+), 80 deletions(-) create mode 100644 stackline/src/tile/macros.rs diff --git a/stackline/Cargo.toml b/stackline/Cargo.toml index b6b2695..ada9ccc 100755 --- a/stackline/Cargo.toml +++ b/stackline/Cargo.toml @@ -6,9 +6,10 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dyn-clone = "1.0" +# dyn-clone = "1.0" palette = "0.6" enum_dispatch = "0.3" +contracts = { version = "0.6.3", features = ["override_debug"] } [dev-dependencies] colored = "2.0" diff --git a/stackline/src/context.rs b/stackline/src/context.rs index 8e3eb72..057aee2 100644 --- a/stackline/src/context.rs +++ b/stackline/src/context.rs @@ -1,6 +1,8 @@ use super::*; use std::ptr::NonNull; +// TODO: write VecCell, to make miri happy + /** 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. @@ -14,11 +16,12 @@ use std::ptr::NonNull; As a result, there are a few oddities to take note of: - - If a [`Signal`] was in the updated tile, then it will be moved into the `UpdateContext`. + - If a [`Signal`] was in the updated tile, then it will be *moved* into the `UpdateContext`. If you wish to put the signal back into its tile, then you will need to call [`keep`](UpdateContext::keep) or [`send`](UpdateContext::send). See [`take_signal`](UpdateContext::take_signal) for more information. - - Most methods that + - Most methods that modify the state of a tile will instead store the modified state in a temporary buffer + and apply the modifications after every tile was updated. The only exception to this is [`UpdateContext::keep`]. # Example @@ -73,7 +76,23 @@ pub struct UpdateContext<'a> { // SAFETY: self.pane.tiles[self.position].cell 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 + /// Returns `None` if the tile was already updated, is empty or does not exist. + #[ensures( + old(pane.get(position).is_none()) -> ret.is_none(), + "Should return None if the tile does not exist" + )] + #[ensures( + old(pane.get(position).is_some() && pane.get(position).unwrap().updated) -> ret.is_none(), + "Should return None if the tile was already updated" + )] + #[ensures( + old(pane.get(position).is_some() && pane.get(position).unwrap().get().is_none()) -> ret.is_none(), + "Should return None if the tile is empty" + )] + #[ensures( + ret.is_some() -> ret.as_ref().unwrap().0.commit.updates.iter().find(|&&x| x == position).is_some(), + "Should add an entry in self.commit.updates if result is Some" + )] pub(crate) fn new( pane: &'a mut Pane, position: (usize, usize), @@ -124,7 +143,25 @@ impl<'a> UpdateContext<'a> { self.position } - /// Returns the [signal](crate::FullTile::signal) of the currently updated tile. + /// Returns the [`width`](Pane::width) of the current [`Pane`]. + #[inline] + pub fn width(&self) -> NonZeroUsize { + unsafe { + // SAFETY: we only read self.pane.width + self.pane.as_ref().width() + } + } + + /// Returns the [`height`](Pane::height) of the current [`Pane`]. + #[inline] + pub fn height(&self) -> NonZeroUsize { + unsafe { + // SAFETY: we only read self.pane.height + self.pane.as_ref().height() + } + } + + /// Returns a reference to the [signal](crate::FullTile::signal) of the currently updated tile. #[inline] pub fn signal<'b>(&'b self) -> Option<&'b Signal> where @@ -160,6 +197,7 @@ impl<'a> UpdateContext<'a> { /// } /// ``` #[inline] + #[ensures(self.signal.is_none(), "Should leave the signal to None")] pub fn take_signal(&mut self) -> Option { std::mem::take(&mut self.signal) } @@ -176,8 +214,14 @@ impl<'a> UpdateContext<'a> { /// /// The actions of this function will only be executed *after* all the tiles of the [`Pane`] were [`updated`](Pane::step). #[inline] + #[ensures( + self.commit.states.iter().find(|(x, y, _)| self.position == (*x, *y)).is_some(), + "Should add an entry in self.commit.states" + )] + #[ensures(self.state == state)] pub fn set_state(&mut self, state: State) { - self.commit.set_state(self.position, state); + self.state = state; + self.commit.set_state(self.position, self.state); } /// Sets the state of the current tile to `state.next()` @@ -186,13 +230,23 @@ impl<'a> UpdateContext<'a> { /// /// The actions of this function will only be executed *after* all the tiles of the [`Pane`] were [`updated`](Pane::step). #[inline] + #[ensures( + self.commit.states.iter().find(|(x, y, _)| self.position == (*x, *y)).is_some(), + "Should add an entry in self.commit.states" + )] + #[ensures(self.state == old(self.state).next())] pub fn next_state(&mut self) { - self.commit.set_state(self.position, self.state.next()); + self.state = self.state.next(); + self.commit.set_state(self.position, self.state); } /// Returns an immutable reference to the [FullTile] at `pos` in the current [Pane]. /// Returns `None` if the tile is borrowed mutably, if it is the current tile or if it does not exist. #[inline] + #[ensures( + ret.is_some() -> ret.unwrap().get().is_some() -> + std::ptr::addr_of!(*ret.unwrap().get().unwrap()) != self.current_ptr() + )] pub fn get<'b>(&'b self, pos: (usize, usize)) -> Option<&'b FullTile> where 'a: 'b, @@ -218,10 +272,11 @@ impl<'a> UpdateContext<'a> { /// Returns `true` iff `(x, y)` is within the bounds of the current pane. #[inline] - pub fn in_bounds(&self, pos: (usize, usize)) -> bool { + #[ensures(ret == true -> position.0 < self.width().get() && position.1 < self.height().get())] + pub fn in_bounds(&self, position: (usize, usize)) -> bool { unsafe { // SAFETY: Pane::in_bounds does not read `self.pane.cells` - self.pane.as_ref().in_bounds(pos) + self.pane.as_ref().in_bounds(position) } } @@ -238,6 +293,7 @@ impl<'a> UpdateContext<'a> { /// 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] + #[ensures(ret == true -> self.get(pos).is_some() && self.get(pos).unwrap().get().is_some())] pub fn accepts_signal(&self, pos: (usize, usize), direction: Direction) -> bool { match self.get(pos) { Some(tile) => tile.accepts_signal(direction), @@ -276,9 +332,10 @@ impl<'a> UpdateContext<'a> { } } + // TODO: return Result /// Sends a signal to be stored in a cell (may be the current one), overriding any signal that was in that cell. /// - /// Returns true if the signal was stored in a cell, false otherwise. + /// Returns Some(()) if the signal was stored in a cell, None 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`. /// @@ -286,18 +343,48 @@ impl<'a> UpdateContext<'a> { /// /// The actions of this function will only be executed *after* all the tiles of the [`Pane`] were [`updated`](Pane::step). /// See [`keep`](UpdateContext::keep) for a variant of this method that takes effect immediately. - pub fn send(&mut self, pos: (usize, usize), mut signal: Signal) -> Option<()> { - signal.set_position(pos); - - if !self.in_bounds(pos) { + #[ensures( + !self.in_bounds(position) -> ret.is_none(), + "Should return None if position is out of bounds" + )] + #[ensures( + ret.is_some() -> self.commit.signals.iter().find(|(x, y, _)| position == (*x, *y)).is_some(), + "Should add an entry in self.commit.signals if result is Some" + )] + pub fn force_send(&mut self, position: (usize, usize), mut signal: Signal) -> Option<()> { + signal.set_position(position); + + if !self.in_bounds(position) { return None; } - self.commit.send(pos, signal); + self.commit.send(position, signal); Some(()) } + /// Sends a signal to `position` if there is a tile at `position` that will accept our signal + /// + /// # Note + /// + /// The actions of this function will only be executed *after* all the tiles of the [`Pane`] were [`updated`](Pane::step). + /// See [`keep`](UpdateContext::keep) for a variant of this method that takes effect immediately. + #[ensures( + !self.in_bounds(position) -> ret.is_none(), + "Should return None if position is out of bounds" + )] + #[ensures( + ret.is_some() -> self.commit.signals.iter().find(|(x, y, _)| position == (*x, *y)).is_some(), + "Should add an entry in self.commit.signals if result is Some" + )] + pub fn send(&mut self, position: (usize, usize), signal: Signal) -> Option<()> { + if self.accepts_signal(position, signal.direction()) { + self.force_send(position, signal) + } else { + None + } + } + /// Stores the current signal back in the current tile, guaranteeing that it will stay there for /// this update cycle. See [`take_signal`](UpdateContext::take_signal) for more information. /// @@ -308,6 +395,8 @@ impl<'a> UpdateContext<'a> { /// If you wish to modify or send copies of the signal, then you will need to call [`signal`](UpdateContext::signal) beforehand and make /// clones of the signal before calling `keep`. /// + /// If `take_signal` or `keep` are called before this functions, then it will do nothing. + /// /// # Example /// /// ``` @@ -324,7 +413,12 @@ impl<'a> UpdateContext<'a> { /// } /// } /// ``` + #[ensures(self.signal.is_none())] pub fn keep(&mut self) { + if self.signal.is_none() { + return; + } + unsafe { // SAFETY: we only access self.pane[self.position].signal, not self.pane[self.position].cell self.pane @@ -334,6 +428,14 @@ impl<'a> UpdateContext<'a> { .set_signal(std::mem::take(&mut self.signal)); } } + + /// Returns a pointer to self.pane[self.position].cell + fn current_ptr(&self) -> *const AnyTile { + let reference = unsafe { + self.pane.as_ref().get(self.position).unwrap() + }; + &*reference.get().unwrap() + } } /// Temporarily holds a list of actions to be made on a given Pane, which should be [applied](UpdateCommit::apply) diff --git a/stackline/src/lib.rs b/stackline/src/lib.rs index d2a6a3e..fba643f 100644 --- a/stackline/src/lib.rs +++ b/stackline/src/lib.rs @@ -7,6 +7,9 @@ This library is the rust implementation of the core logic of the language. */ +#[macro_use] +extern crate contracts; + use std::num::NonZeroUsize; pub mod signal; diff --git a/stackline/src/pane.rs b/stackline/src/pane.rs index 48e9372..c709914 100644 --- a/stackline/src/pane.rs +++ b/stackline/src/pane.rs @@ -32,6 +32,8 @@ impl Pane { /// // Perform a simulation step /// pane.step(); /// ``` + #[ensures(ret.is_some() -> width > 0)] + #[ensures(ret.is_some() -> height > 0)] pub fn empty(width: usize, height: usize) -> Option { // TODO: check that width * height is a valid usize let length = width.checked_mul(height)?; @@ -45,6 +47,36 @@ impl Pane { }) } + /// Returns the width of the current pane + /// + /// ## Example + /// + /// ``` + /// # use stackline::prelude::*; + /// let pane = Pane::empty(4, 7).unwrap(); + /// + /// assert_eq!(pane.width().get(), 4); + /// ``` + #[inline] + pub fn width(&self) -> NonZeroUsize { + self.width + } + + /// Returns the height of the current pane + /// + /// ## Example + /// + /// ``` + /// # use stackline::prelude::*; + /// let pane = Pane::empty(4, 7).unwrap(); + /// + /// assert_eq!(pane.height().get(), 7); + /// ``` + #[inline] + pub fn height(&self) -> NonZeroUsize { + self.height + } + /// Given a `position = (x, y)` and an `offset = (Δx, Δy)`, /// returns `Some((x + Δx, y + Δy))` if `(x + Δx, y + Δy)` is inside the `Pane`. /// @@ -64,6 +96,10 @@ impl Pane { /// assert_eq!(pane.offset((1, 0), (3, 0)), None); // (1 + 3, 0 + 0) = (4, 0), outside /// ``` #[inline] + #[ensures(ret.is_some() -> position.0 as isize + offset.0 as isize >= 0)] + #[ensures(ret.is_some() -> position.1 as isize + offset.1 as isize >= 0)] + #[ensures(ret.is_some() -> position.0 as isize + (offset.0 as isize) < self.width.get() as isize)] + #[ensures(ret.is_some() -> position.1 as isize + (offset.1 as isize) < self.height.get() as isize)] pub fn offset(&self, position: (usize, usize), offset: (i8, i8)) -> Option<(usize, usize)> { if offset.0 < 0 && (-offset.0) as usize > position.0 || offset.1 < 0 && (-offset.1) as usize > position.1 @@ -101,6 +137,7 @@ impl Pane { /// let tile = pane.get((0, 0)).unwrap(); /// ``` #[inline] + #[ensures(self.in_bounds(position) -> ret.is_some())] pub fn get<'b>(&'b self, position: (usize, usize)) -> Option<&'b FullTile> { if !self.in_bounds(position) { return None; @@ -125,6 +162,7 @@ impl Pane { /// tile.set_state(State::Active); /// ``` #[inline] + #[ensures(old(self.in_bounds(position)) -> ret.is_some())] pub fn get_mut<'b>(&'b mut self, position: (usize, usize)) -> Option<&'b mut FullTile> { if !self.in_bounds(position) { return None; @@ -136,6 +174,7 @@ impl Pane { /// Sets the tile at `position` to `tile`. `T` must either implement [`Tile`] or be `()`. #[inline] + #[ensures(self.in_bounds(position) -> ret.is_some())] pub fn set_tile(&mut self, position: (usize, usize), tile: T) -> Option<()> where FullTile: From, @@ -170,6 +209,7 @@ impl Pane { /// assert_eq!(pane.get_state((0, 0)), Some(State::Dormant)); /// ``` #[inline] + #[ensures(self.in_bounds(position) -> ret.is_some())] pub fn get_state(&self, position: (usize, usize)) -> Option { self.get(position).map(|x| x.state().clone()) } @@ -200,6 +240,8 @@ impl Pane { /// assert!(pane.get((0, 0)).unwrap().signal().is_some()); /// ``` #[inline] + #[ensures(ret.is_some() -> self.in_bounds(position) && self.get(position).unwrap().get().is_some())] + #[ensures(!self.in_bounds(position) -> ret.is_none())] pub fn set_signal(&mut self, position: (usize, usize), mut signal: Signal) -> Option<()> { signal.set_position(position); if let Some(tile) = self.get_mut(position) { @@ -231,6 +273,8 @@ impl Pane { } #[inline] + #[ensures(self.in_bounds(position) -> self.get(position).unwrap().updated)] + #[ensures(!self.in_bounds(position) -> ret.is_none())] fn update(&mut self, position: (usize, usize), commit: &mut UpdateCommit) -> Option<()> { // NOTE: Tiles will only be updated once as per UpdateContext::new let (ctx, tile) = UpdateContext::new(self, position, commit)?; diff --git a/stackline/src/tile/macros.rs b/stackline/src/tile/macros.rs new file mode 100644 index 0000000..63b3765 --- /dev/null +++ b/stackline/src/tile/macros.rs @@ -0,0 +1,59 @@ +use super::*; + +#[macro_export] +macro_rules! test_tile_setup { + ( $width:expr, $height:expr, [ $( $x:expr ),* ] ) => {{ + assert!($width > 0); + assert!($height > 0); + let mut pane = crate::pane::Pane::empty($width, $height).unwrap(); + let mut index = 0; + + $( + { + let x = index % $width; + let y = index / $width; + *pane.get_mut((x, y)).unwrap() = crate::tile::FullTile::from($x); + index += 1; + } + )* + + assert!(index == $width * $height); + + pane + }} +} + +#[macro_export] +macro_rules! test_set_signal { + ( $pane:expr, $pos:expr, $dir:expr ) => { + $pane.set_signal($pos, crate::signal::Signal::empty($pos, $dir)).unwrap(); + }; +} + +#[macro_export] +macro_rules! assert_signal { + ( $pane:expr, $pos:expr ) => {{ + let guard = $pane + .get($pos) + .expect(&format!("Couldn't get tile at {:?}", $pos)); + let signal = guard.signal(); + assert!(signal.is_some()); + signal + }}; + + ( $pane:expr, $pos:expr, [ $( $data:expr ),* ] ) => {{ + let signal = assert_signal!($pane, $pos); + // TODO: check that signal.data == data + }}; +} + +#[macro_export] +macro_rules! assert_no_signal { + ( $pane:expr, $pos:expr) => {{ + let guard = $pane + .get($pos) + .expect(&format!("Couldn't get tile at {:?}", $pos)); + let signal = guard.signal(); + assert!(signal.is_none()); + }}; +} diff --git a/stackline/src/tile/mod.rs b/stackline/src/tile/mod.rs index 231bd0f..8be06fb 100644 --- a/stackline/src/tile/mod.rs +++ b/stackline/src/tile/mod.rs @@ -1,9 +1,18 @@ +/*! This module contains the [`Tile`] trait, which defines common behavior for all tiles in stackline. + * It also contains [`FullTile`], which holds the state and signal of the different tiles alongside a [`Tile`] wrapped in [`AnyTile`]. + * + * Lastly, [`AnyTile`] is an enum containing all of the [`Tile`] instances that were discovered in the `tiles/` directory. + * See [its documentation](AnyTile) for more information on the discovery process. +*/ use super::*; -use dyn_clone::{clone_box, DynClone}; mod full; pub use full::*; +#[cfg(test)] +#[macro_use] +mod macros; + // Generated rust file containing the AnyTile enum, which combines the structs implementing Tile found in tiles/. // Its definition looks like this: // #[derive(Clone, Debug)] @@ -17,8 +26,113 @@ pub use full::*; // Note that all the implementing types will be read from the files in tiles/ and re-exported. include!(concat!(env!("OUT_DIR"), "/anytile.rs")); +/// The `Tile` trait defines shared behavior for every tile in the language. +/// +/// Tiles are the building block of the language, and take the role of the instructions of the language. +/// With this trait, you may define the following behaviors of instructions: +/// - what it should do if it receives a [`Signal`], with [`Tile::update`] +/// - when [`Signal`s](Signal) can be sent to it, with [`Tile::accepts_signal`] +/// - how it should be displayed on the screen, with [`Tile::draw`] +/// +/// # Example +/// +/// Let's start by implementing a basic [`Tile`], which simply forwards any incomming [`Signal`] to its right. +/// Create a file in the `tiles/` folder containing the following +/// +/// ```ignore +/// // First, import the needed types. Because we are writing files +/// // that will be part of the "stackline" crate, we have to import them using `crate`: +/// use crate::prelude::*; +/// use crate::tile::prelude::*; +/// +/// // Tiles must implement Clone and Debug +/// #[derive(Clone, Debug)] +/// pub struct MyTile { +/// // This is where your tile can store its internal state. +/// // For this tile, we don't need any! +/// } +/// +/// impl MyTile { +/// // It's a good idea to provide a constructor for writing tests +/// pub fn new() -> Self { +/// MyTile {} +/// } +/// } +/// +/// impl Tile for MyTile { +/// // The update method is where we will put the logic of our simple tile +/// fn update<'b>(&'b mut self, mut context: UpdateContext<'b>) { +/// // Check if we have a signal +/// if let Some(signal) = context.take_signal() { +/// // We do have a signal, so we will forward it to the tile on our right. +/// +/// // First, get the coordinates of the tile to our right: +/// if let Some(right_position) = context.offset((1, 0)) { +/// // Then, send the signal! We need to tell that we are moving the signal to the right. +/// context.send(right_position, signal.moved(Direction::Right)); +/// } +/// } +/// +/// // If we are Active, become Dormant +/// // If we are Dormant, become Idle +/// if context.state() != State::Idle { +/// context.next_state(); +/// } +/// } +/// +/// // The Tile trait provides a default implementations for the other methods, +/// // which satisfy our needs. +/// } +/// +/// // Lastly, we should write unit tests for our code +/// #[cfg(test)] +/// mod test { +/// use super::*; +/// +/// #[test] +/// fn test_my_tile() { +/// use crate::tile::Wire; +/// use crate::utils::Orientation::*; +/// +/// // We create a Pane containing our tile, surrounded by wires: +/// // | +/// // -X- +/// // | +/// // With X our tile +/// let mut pane = test_tile_setup!(3, 3, [ +/// (), Wire::new(Vertical), (), +/// Wire::new(Horizontal), MyTile::new(), Wire::new(Horizontal), +/// (), Wire::new(Vertical), () +/// ]); +/// +/// // Send signals coming from the top, bottom and left of our tile, +/// // And check that they are all forwarded to the right +/// let signals = [ +/// (0, 1, Direction::Right), +/// (1, 0, Direction::Down), +/// (1, 2, Direction::Up), +/// ]; +/// +/// for (x, y, dir) in signals { +/// test_set_signal!(pane, (x, y), dir); +/// pane.step(); +/// +/// // Our tile should accept the signal +/// assert_signal!(pane, (1, 1)); +/// +/// pane.step(); +/// // Our tile should have moved the signal to the right +/// assert_no_signal!(pane, (1, 1)); +/// assert_signal!(pane, (2, 1)); +/// +/// pane.step(); +/// pane.step(); +/// } +/// } +/// } +/// ``` #[enum_dispatch(AnyTile)] -pub trait Tile: DynClone + std::fmt::Debug { +pub trait Tile: std::clone::Clone + std::fmt::Debug { /// Function to be called when the tile needs to be updated. #[inline] fn update<'b>(&'b mut self, mut context: UpdateContext<'b>) { @@ -80,63 +194,9 @@ pub trait Tile: DynClone + std::fmt::Debug { // } // } -#[cfg(test)] -mod crate_macros { - #[macro_export] - macro_rules! test_tile_setup { - ( $width:expr, $height:expr, [ $( $x:expr ),* ] ) => {{ - assert!($width > 0); - assert!($height > 0); - let mut pane = Pane::empty($width, $height).unwrap(); - let mut index = 0; - - $( - { - let x = index % $width; - let y = index / $width; - *pane.get_mut((x, y)).unwrap() = FullTile::from($x); - index += 1; - } - )* - - assert!(index == $width * $height); - - pane - }} - } - - #[macro_export] - macro_rules! test_set_signal { - ( $pane:expr, $pos:expr, $dir:expr ) => { - $pane.set_signal($pos, Signal::empty($pos, $dir)).unwrap(); - }; - } - - #[macro_export] - macro_rules! assert_signal { - ( $pane:expr, $pos:expr ) => {{ - let guard = $pane - .get($pos) - .expect(&format!("Couldn't get tile at {:?}", $pos)); - let signal = guard.signal(); - assert!(signal.is_some()); - signal - }}; - - ( $pane:expr, $pos:expr, [ $( $data:expr ),* ] ) => {{ - let signal = assert_signal!($pane, $pos); - // TODO: check that signal.data == data - }}; - } - - #[macro_export] - macro_rules! assert_no_signal { - ( $pane:expr, $pos:expr) => {{ - let guard = $pane - .get($pos) - .expect(&format!("Couldn't get tile at {:?}", $pos)); - let signal = guard.signal(); - assert!(signal.is_none()); - }}; - } +pub mod prelude { + pub use crate::tile::{FullTile, AnyTile}; + pub use crate::signal::Signal; + pub use crate::utils::State; + pub use crate::text::*; } diff --git a/stackline/tiles/wire.rs b/stackline/tiles/wire.rs index 8a5fc18..a353eca 100644 --- a/stackline/tiles/wire.rs +++ b/stackline/tiles/wire.rs @@ -1,6 +1,8 @@ //! Wires and diodes -use super::*; +// use super::*; +use crate::prelude::*; +use crate::tile::prelude::*; #[derive(Clone, Debug)] pub struct Wire(Orientation); @@ -20,7 +22,7 @@ impl Tile for Wire { } if let Some(pos) = context.accepts_direction(direction) { - context.send(pos, signal.clone_move(direction)); + context.force_send(pos, signal.clone_move(direction)); } } } @@ -62,7 +64,7 @@ impl Tile for Diode { return; } - if let Some(pos) = context.accepts_direction(self.0) { + if let Some(pos) = context.offset(self.0.into_offset()) { context.send(pos, signal.moved(self.0)); } } @@ -102,7 +104,7 @@ 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.accepts_direction(self.direction) { + if let Some(pos) = context.offset(self.direction.into_offset()) { context.send(pos, signal.moved(self.direction)); } }