🔥 RefCell-less, two-phase update system [wip]

main
Shad Amethyst 2 years ago
parent 9034801373
commit d637897a9a
Signed by: amethyst
GPG Key ID: D970C8DD1D6DEE36

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

@ -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<RefCell<FullTile>>,
panes: Vec<Pane>,
}
pub struct Pane {
tiles: Vec<FullTile>,
width: NonZeroUsize,
height: NonZeroUsize,
signals: Vec<Weak<Signal>>,
}
impl World {
impl Pane {
pub fn empty(width: usize, height: usize) -> Option<Self> {
// 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<Weak<Signal>> {
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<FullTile>> {
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()

@ -18,22 +18,15 @@ 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
}
// SAFETY: should not implement Tile
impl FullTile {
pub fn new(cell: Option<AnyTile>) -> 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<Weak<Signal>> {
pub(crate) fn set_signal(&mut self, signal: Signal) -> Option<Weak<Signal>> {
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<Signal>> {
self.signal.as_ref()
}
pub(crate) fn take_signal(&mut self) -> Option<Rc<Signal>> {
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<Rc<Signal>>, 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<Signal>, 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<dyn Tile>);
impl AnyTile {
fn update(&mut self, world: &mut World, signal: &mut Option<Rc<Signal>>, 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 {

@ -6,18 +6,16 @@ use super::*;
pub struct Wire(Orientation);
impl Tile for Wire {
fn update(&mut self, world: &mut World, signal: &mut Option<Rc<Signal>>, 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<Signal>, 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<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 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();
// }
// }
// }
// }
// }

Loading…
Cancel
Save