From d637897a9a530c65553021b6a048b5a10c0f649c Mon Sep 17 00:00:00 2001 From: Adrien Burgun Date: Fri, 17 Jun 2022 01:29:05 +0200 Subject: [PATCH] :fire: :sparkles: RefCell-less, two-phase update system [wip] --- stackline/src/context.rs | 167 +++++++++++++++++++++++++++++++++++++ stackline/src/lib.rs | 42 +++++----- stackline/src/tile/mod.rs | 42 +++++++--- stackline/src/tile/wire.rs | 44 +++++----- 4 files changed, 238 insertions(+), 57 deletions(-) create mode 100644 stackline/src/context.rs diff --git a/stackline/src/context.rs b/stackline/src/context.rs new file mode 100644 index 0000000..fe444cd --- /dev/null +++ b/stackline/src/context.rs @@ -0,0 +1,167 @@ +use std::marker::PhantomData; +use std::rc::Rc; +use super::*; + +/// 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. + +// 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 position: (usize, usize), + pane: *const Pane, + + phantom: PhantomData<&'a Pane>, +} + +impl<'a> UpdateContext<'a> { + #[inline] + 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 res = Self { + position, + pane: ptr, + phantom: PhantomData + }; + + Some((res, tile)) + } + + #[inline] + pub fn signal<'b>(&'b self) -> Option<&'b Rc> where 'a: 'b { + let pane = unsafe { self.pane() }; + + // SAFETY: `pane[position].signal` is not borrowed mutably + pane.get(self.position).unwrap().signal() + } + + /// 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] + 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 + } + } + + /// 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) + } + + // 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 may access the other tiles immutably, but it cannot access the other signals. + +// SAFETY: this structures ensures that it has exlusive, mutable access to `∀x, pane[x].signal` and `pane.signals`. +// Other parts of `pane` may be accessed and returned immutably. +pub struct TransmitContext<'a> { + pub 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, Rc)> { + 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 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() + } + + /// 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) -> bool 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` + let pane = unsafe { self.pane_mut() }; + + match pane.get_mut(pos) { + Some(ref mut tile) => { + if let Some(weak) = tile.set_signal(signal) { + pane.signals.push(weak); + true + } else { + false + } + } + _ => false + } + } + + /// 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) + } + + // 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 + } + + // 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 + } +} diff --git a/stackline/src/lib.rs b/stackline/src/lib.rs index d6bc474..85044c2 100644 --- a/stackline/src/lib.rs +++ b/stackline/src/lib.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::num::NonZeroUsize; use std::rc::Weak; @@ -11,44 +10,36 @@ use tile::*; mod signal; pub use signal::*; +pub mod context; +use context::*; + pub struct World { - tiles: Vec>, + panes: Vec, +} + +pub struct Pane { + tiles: Vec, width: NonZeroUsize, height: NonZeroUsize, signals: Vec>, } -impl World { +impl Pane { pub fn empty(width: usize, height: usize) -> Option { // TODO: check that width * height is a valid usize Some(Self { width: width.try_into().ok()?, height: height.try_into().ok()?, - tiles: vec![RefCell::new(FullTile::default()); width * height], + tiles: vec![FullTile::default(); width * height], signals: vec![], }) } - pub fn send_signal( - &mut self, - position: (usize, usize), - signal: Signal, - ) -> Option> { - let tile = self.get(position)?; - let weak = { - let mut guard = tile.try_borrow_mut().ok()?; - guard.set_signal(signal)? - }; - - self.signals.push(weak.clone()); - - Some(weak) - } - /// 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 @@ -71,7 +62,7 @@ impl World { } #[inline] - pub fn get<'b>(&'b self, position: (usize, usize)) -> Option<&'b RefCell> { + pub fn get<'b>(&'b self, position: (usize, usize)) -> Option<&'b FullTile> { if !self.in_bounds(position) { return None; } @@ -79,6 +70,15 @@ impl World { self.tiles.get(position.1 * self.width.get() + position.0) } + #[inline] + pub fn get_mut<'b>(&'b mut self, position: (usize, usize)) -> Option<&'b mut FullTile> { + if !self.in_bounds(position) { + return None; + } + + self.tiles.get_mut(position.1 * self.width.get() + position.0) + } + #[inline] pub fn in_bounds(&self, position: (usize, usize)) -> bool { position.0 < self.width.get() && position.1 < self.height.get() diff --git a/stackline/src/tile/mod.rs b/stackline/src/tile/mod.rs index 785333c..2fae950 100644 --- a/stackline/src/tile/mod.rs +++ b/stackline/src/tile/mod.rs @@ -18,22 +18,15 @@ Cloning a `FullTile` results in a `FullTile` that does not have any signal. pub struct FullTile { cell: Option, signal: Option>, + // TODO: state } +// SAFETY: should not implement Tile impl FullTile { pub fn new(cell: Option) -> Self { Self { cell, signal: None } } - pub fn update(&mut self, world: &mut World, pos: (usize, usize)) { - match self.cell { - Some(ref mut tile) => { - tile.update(world, &mut self.signal, pos); - } - None => {} - } - } - pub fn accepts_signal(&self, direction: Direction) -> bool { match self.cell { Some(ref tile) => tile.accepts_signal(direction), @@ -42,7 +35,7 @@ impl FullTile { } /// Returns Some(signal) iff self.cell.is_some() - pub fn set_signal(&mut self, signal: Signal) -> Option> { + pub(crate) fn set_signal(&mut self, signal: Signal) -> Option> { if self.cell.is_some() { let rc = Rc::new(signal); let weak = Rc::downgrade(&rc); @@ -52,6 +45,24 @@ impl FullTile { None } } + + /// Returns the internal state of this full tile + pub fn get<'b>(&'b self) -> Option<&'b AnyTile> { + self.cell.as_ref() + } + + /// Returns the signal of this tile + pub fn signal<'b>(&'b self) -> Option<&'b Rc> { + self.signal.as_ref() + } + + pub(crate) fn take_signal(&mut self) -> Option> { + std::mem::take(&mut self.signal) + } + + pub(crate) fn get_mut<'b>(&'b mut self) -> Option<&'b mut AnyTile> { + self.cell.as_mut() + } } impl Default for FullTile { @@ -61,7 +72,12 @@ impl Default for FullTile { } pub trait Tile: DynClone + std::fmt::Debug { - fn update(&mut self, world: &mut World, signal: &mut Option>, pos: (usize, usize)); + /// 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. + fn update<'b>(&'b mut self, _context: UpdateContext<'b>) {} + + /// Function that will be called if the tile has a signal. + fn transmit<'b>(&'b self, signal: Rc, context: TransmitContext<'b>); /// Should return true iff the tile accepts a signal travelling in `Direction` fn accepts_signal(&self, _direction: Direction) -> bool { @@ -73,8 +89,8 @@ pub trait Tile: DynClone + std::fmt::Debug { pub struct AnyTile(Box); impl AnyTile { - fn update(&mut self, world: &mut World, signal: &mut Option>, pos: (usize, usize)) { - self.0.update(world, signal, pos) + fn update<'b>(&'b mut self, ctx: UpdateContext<'b>) { + self.0.update(ctx) } fn accepts_signal(&self, direction: Direction) -> bool { diff --git a/stackline/src/tile/wire.rs b/stackline/src/tile/wire.rs index 5f4c8a0..569c3c8 100644 --- a/stackline/src/tile/wire.rs +++ b/stackline/src/tile/wire.rs @@ -6,18 +6,16 @@ use super::*; pub struct Wire(Orientation); impl Tile for Wire { - fn update(&mut self, world: &mut World, signal: &mut Option>, pos: (usize, usize)) { - if let Some(signal) = std::mem::take(signal) { - for &direction in self.0.into_directions() { - if direction == signal.direction.opposite() { - continue; - } + fn transmit<'b>(&'b self, signal: Rc, context: TransmitContext<'b>) { + for &direction in self.0.into_directions() { + if direction == signal.direction.opposite() { + continue; + } - if let Some(new_pos) = world.offset(pos, direction.into_offset()) { - let tile = world.get(new_pos).unwrap(); - if tile.borrow().accepts_signal(direction) { - world.send_signal(new_pos, (*signal).clone_with_dir(direction)).unwrap(); - } + 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_with_dir(direction)); } } } @@ -31,15 +29,15 @@ impl Tile for Wire { #[derive(Clone, Debug)] pub struct Diode(Direction); -impl Tile for Diode { - fn update(&mut self, world: &mut World, signal: &mut Option>, 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 update(&mut self, world: &mut World, signal: &mut Option>, 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(); +// } +// } +// } +// } +// }