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> {
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<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
/// 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 {
/// 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<Weak<Signal>> 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

@ -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<Weak<Signal>> {
@ -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<()> {

@ -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;
}
}

@ -18,18 +18,23 @@ Cloning a `FullTile` results in a `FullTile` that does not have any signal.
pub struct FullTile {
cell: Option<AnyTile>,
signal: Option<Rc<Signal>>,
// TODO: state
state: State,
}
// SAFETY: should not implement Tile
impl FullTile {
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 {
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<Signal>> {
@ -59,13 +70,29 @@ impl FullTile {
}
#[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)
}
// 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<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.
/// 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<Signal>, context: TransmitContext<'b>);

@ -12,16 +12,15 @@ impl 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() {
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<Rc<Signal>>, 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<Signal>, 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));

@ -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);
}
}

Loading…
Cancel
Save