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> { 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 position(&self) -> (usize, usize) { self.position } #[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> { 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)) } #[inline] pub fn position(&self) -> (usize, usize) { self.position } /// 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) -> Option> 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() }; pane.set_signal(pos, signal) } /// 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 } } #[cfg(test)] mod test { use super::*; #[test] fn test_update_exclusivity() { // Check that UpdateContext does not allow any other reference to `tiles[position].cell` to be made let mut pane = Pane::empty(4, 4).unwrap(); let mut tile = FullTile::from(Wire::new(Orientation::Any)); tile.set_signal(Signal::empty((1, 2), Direction::Up)).unwrap(); *pane.get_mut((1, 2)).unwrap() = tile; let (ctx, _tile) = UpdateContext::new(&mut pane, (1, 2)).unwrap(); assert_eq!(ctx.position(), (1, 2)); let tile_self: Option<&FullTile> = ctx.get((1, 2)); // The FullTile may not be read assert!(tile_self.is_none()); assert!(ctx.signal().is_some()); // Our Signal may be read, though } #[test] fn test_transmit() { let mut pane = Pane::empty(4, 4).unwrap(); let mut tile = FullTile::from(Wire::new(Orientation::Any)); tile.set_signal(Signal::empty((1, 2), Direction::Up)).unwrap(); *pane.get_mut((1, 2)).unwrap() = tile; let (ctx, _tile, _signal) = TransmitContext::new(&mut pane, (1, 2)).unwrap(); assert_eq!(ctx.position(), (1, 2)); let tile_self: Option<&AnyTile> = ctx.get((1, 2)); assert!(tile_self.is_some()); // We may read our AnyTile, as they are under an immutable borrow // Check that the signal was dropped std::mem::drop(ctx); assert!(pane.get((1, 2)).unwrap().signal().is_none()); } }