States and improvements to Context

main
Shad Amethyst 2 years ago
parent 2338f94c99
commit 4b60d13943
Signed by: amethyst
GPG Key ID: D970C8DD1D6DEE36

@ -18,6 +18,7 @@ use super::*;
pub struct UpdateContext<'a> { pub struct UpdateContext<'a> {
position: (usize, usize), position: (usize, usize),
pane: *const Pane, pane: *const Pane,
state: &'a mut State,
phantom: PhantomData<&'a Pane>, 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)> { pub(crate) fn new(pane: &'a mut Pane, position: (usize, usize)) -> Option<(Self, &'a mut AnyTile)> {
let ptr: *const Pane = &*pane; 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 { let res = Self {
position, position,
pane: ptr, pane: ptr,
state,
phantom: PhantomData phantom: PhantomData
}; };
Some((res, tile)) Some((res, tile.as_mut()?))
} }
#[inline] #[inline]
@ -51,6 +53,21 @@ impl<'a> UpdateContext<'a> {
pane.get(self.position).unwrap().signal() 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 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 the current tile (see [UpdateContext]) or if it does not exist.
#[inline] #[inline]
@ -74,6 +91,12 @@ impl<'a> UpdateContext<'a> {
pane.offset(self.position, offset) 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`, // SAFETY: `self.pane` originates from a `&'a mut Pane`,
// guaranteeing that no accesses may be done outside of ours. // guaranteeing that no accesses may be done outside of ours.
// No access to `pane[position].cell` may be done! // 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. /// 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 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. // Other parts of `pane` may be accessed and returned immutably.
pub struct TransmitContext<'a> { pub struct TransmitContext<'a> {
position: (usize, usize), position: (usize, usize),
@ -132,14 +156,58 @@ impl<'a> TransmitContext<'a> {
pane.get(pos)?.get() 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) {
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 /// 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 /// Returns true if the signal was stored in a cell, false otherwise.
pub fn send<'b>(&'b self, pos: (usize, usize), signal: Signal) -> Option<Weak<Signal>> where 'a: 'b { /// 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<Weak<Signal>> where 'a: 'b {
// SAFETY: we do not return any reference to any data borrowed in this function // 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() }; 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 /// Returns `Some((position.x + Δx, position.y + Δy))` iff `(x + Δx, y + Δy)` is inside the pane

@ -65,6 +65,10 @@ impl Pane {
self.tiles.get_mut(position.1 * self.width.get() + position.0) 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` // SAFETY: may only access `self[pos].signal` and `self.signals`
#[inline] #[inline]
pub fn set_signal(&mut self, position: (usize, usize), signal: Signal) -> Option<Weak<Signal>> { pub fn set_signal(&mut self, position: (usize, usize), signal: Signal) -> Option<Weak<Signal>> {
@ -87,7 +91,17 @@ impl Pane {
Some(()) 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] #[inline]
pub fn transmit(&mut self, position: (usize, usize)) -> Option<()> { pub fn transmit(&mut self, position: (usize, usize)) -> Option<()> {

@ -33,4 +33,8 @@ impl Signal {
pub fn position(&self) -> (usize, usize) { pub fn position(&self) -> (usize, usize) {
self.position self.position
} }
pub(crate) fn set_position(&mut self, position: (usize, usize)) {
self.position = position;
}
} }

@ -18,18 +18,23 @@ Cloning a `FullTile` results in a `FullTile` that does not have any signal.
pub struct FullTile { pub struct FullTile {
cell: Option<AnyTile>, cell: Option<AnyTile>,
signal: Option<Rc<Signal>>, signal: Option<Rc<Signal>>,
// TODO: state state: State,
} }
// SAFETY: should not implement Tile // SAFETY: should not implement Tile
impl FullTile { impl FullTile {
pub fn new(cell: Option<AnyTile>) -> Self { pub fn new(cell: Option<AnyTile>) -> 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 { pub fn accepts_signal(&self, direction: Direction) -> bool {
match self.cell { match self.cell {
Some(ref tile) => tile.accepts_signal(direction), Some(ref tile) => self.state.accepts_signal() && tile.accepts_signal(direction),
None => false, None => false,
} }
} }
@ -52,6 +57,12 @@ impl FullTile {
self.cell.as_ref() 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 /// Returns the signal of this tile
#[inline] #[inline]
pub fn signal<'b>(&'b self) -> Option<&'b Rc<Signal>> { pub fn signal<'b>(&'b self) -> Option<&'b Rc<Signal>> {
@ -59,13 +70,29 @@ impl FullTile {
} }
#[inline] #[inline]
pub(crate) fn take_signal(&mut self) -> Option<Rc<Signal>> { pub fn take_signal(&mut self) -> Option<Rc<Signal>> {
std::mem::take(&mut self.signal) std::mem::take(&mut self.signal)
} }
// SAFETY: may only access `self.state`
#[inline] #[inline]
pub(crate) fn get_mut<'b>(&'b mut self) -> Option<&'b mut AnyTile> { pub fn state(&self) -> State {
self.cell.as_mut() 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<AnyTile>, &'b mut Option<Rc<Signal>>, &'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. /// 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. /// During the "update" phase, the tile may access its signal and the other tiles immutably.
#[inline] #[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. /// Function that will be called if the tile has a signal.
fn transmit<'b>(&'b self, signal: Rc<Signal>, context: TransmitContext<'b>); fn transmit<'b>(&'b self, signal: Rc<Signal>, context: TransmitContext<'b>);

@ -12,16 +12,15 @@ impl Wire {
} }
impl Tile for Wire { impl Tile for Wire {
fn transmit<'b>(&'b self, signal: Rc<Signal>, context: TransmitContext<'b>) { fn transmit<'b>(&'b self, signal: Rc<Signal>, mut context: TransmitContext<'b>) {
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;
} }
if let Some(new_pos) = context.offset(direction.into_offset()) { if let Some(pos) = context.offset(direction.into_offset()) {
let tile = context.get(new_pos); if context.accepts_signal(pos, direction) {
if tile.map(|t| t.accepts_signal(direction)).unwrap_or(false) { context.send(pos, signal.clone_move(direction).unwrap_or_else(|| unreachable!()));
context.send(new_pos, signal.clone_move(direction).unwrap());
} }
} }
} }
@ -41,18 +40,15 @@ impl Diode {
} }
} }
// impl Tile for Diode { impl Tile for Diode {
// fn update(&mut self, world: &mut World, signal: &mut Option<Rc<Signal>>, pos: (usize, usize)) { fn transmit<'b>(&'b self, signal: Rc<Signal>, mut context: TransmitContext<'b>) {
// if let Some(signal) = std::mem::take(signal) { if let Some(pos) = context.offset(self.0.into_offset()) {
// if let Some(new_pos) = world.offset(pos, self.0.into_offset()) { if context.accepts_signal(pos, self.0) {
// let tile = world.get(new_pos).unwrap(); context.send(pos, signal.clone_move(self.0).unwrap_or_else(|| unreachable!()));
// if tile.borrow().accepts_signal(self.0) { }
// world.send_signal(new_pos, (*signal).clone_with_dir(self.0)).unwrap(); }
// } }
// } }
// }
// }
// }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
@ -70,6 +66,7 @@ mod test {
// Test the signal going from left to right // Test the signal going from left to right
test_set_signal!(pane, (0, 0), Direction::Right); test_set_signal!(pane, (0, 0), Direction::Right);
pane.update_all();
pane.transmit_all(); pane.transmit_all();
assert_signal!(pane, (1, 0)); assert_signal!(pane, (1, 0));
@ -77,6 +74,7 @@ mod test {
assert_no_signal!(pane, (2, 0)); assert_no_signal!(pane, (2, 0));
assert_no_signal!(pane, (1, 1)); assert_no_signal!(pane, (1, 1));
pane.update_all();
pane.transmit_all(); pane.transmit_all();
assert_signal!(pane, (2, 0)); assert_signal!(pane, (2, 0));
@ -84,14 +82,20 @@ mod test {
assert_no_signal!(pane, (0, 0)); assert_no_signal!(pane, (0, 0));
assert_no_signal!(pane, (1, 0)); assert_no_signal!(pane, (1, 0));
pane.update_all();
pane.transmit_all(); pane.transmit_all();
for (_, _, tile) in pane.tiles() { for (_, _, tile) in pane.tiles() {
assert!(tile.signal().is_none()); 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 the signal going from right to left
test_set_signal!(pane, (2, 0), Direction::Left); test_set_signal!(pane, (2, 0), Direction::Left);
pane.update_all();
pane.transmit_all(); pane.transmit_all();
assert_signal!(pane, (1, 0)); assert_signal!(pane, (1, 0));
@ -99,6 +103,7 @@ mod test {
assert_no_signal!(pane, (2, 0)); assert_no_signal!(pane, (2, 0));
assert_no_signal!(pane, (1, 1)); assert_no_signal!(pane, (1, 1));
pane.update_all();
pane.transmit_all(); pane.transmit_all();
assert_signal!(pane, (0, 0)); assert_signal!(pane, (0, 0));

@ -17,6 +17,16 @@ pub enum Direction {
Right, 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 HORIZONTAL: [Direction; 2] = [Direction::Left, Direction::Right];
const VERTICAL: [Direction; 2] = [Direction::Up, Direction::Down]; const VERTICAL: [Direction; 2] = [Direction::Up, Direction::Down];
const ANY: [Direction; 4] = [ 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)] #[cfg(test)]
mod test { mod test {
use super::*; 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);
}
} }

Loading…
Cancel
Save