Use veccell, making Miri happy

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

@ -2,4 +2,7 @@
This is the successor of [stackline](https://github.com/adri326/stackline), an esoteric language inspired by [Wireworld](https://mathworld.wolfram.com/WireWorld.html) and [ORCA](https://github.com/hundredrabbits/Orca).
You can read the documentation here at [https://adri326.github.io/stackline2](https://adri326.github.io/stackline2).
Although it wasn't influenced by it, stackline has a lot in common with [Befunge](https://esolangs.org/wiki/Befunge).
[Befungell](https://github.com/zwade/Befungell)'s concurrency model acts in a very similar way to Stackline's, with the main difference being that Stackline's concurrency stems from a cellular automaton.
Until the core of the language is stabilized and submitted to crates.io, its documentation will be rendered daily on [https://shadamethyst.xyz/stackline/](https://shadamethyst.xyz/stackline/).

@ -10,6 +10,7 @@ edition = "2021"
palette = "0.6"
enum_dispatch = "0.3"
contracts = { version = "0.6.3", features = ["override_debug"] }
veccell = "0.1"
[dev-dependencies]
colored = "2.0"

@ -1,5 +1,6 @@
use super::*;
use std::ptr::NonNull;
use veccell::{VecRef, VecRefMut};
// TODO: write VecCell, to make miri happy
@ -55,15 +56,10 @@ use std::ptr::NonNull;
}
```
# Safety
Because [`Tile::update`] requires a `&mut self` reference, the current [`Tile`] cannot be accessed through [`UpdateContext::get`].
This structure stores the [`State`] and [`Signal`] of the [`FullTile`] containing the current tile, so these can be accessed nonetheless, and it is still possible and safe to call [`UpdateContext::send`] on the current position.
**/
pub struct UpdateContext<'a> {
position: (usize, usize),
pane: NonNull<Pane>,
pane: &'a Pane,
state: State,
signal: Option<Signal>,
commit: &'a mut UpdateCommit,
@ -82,7 +78,7 @@ impl<'a> UpdateContext<'a> {
"Should return None if the tile was already updated"
)]
#[ensures(
old(pane.get(position).is_some() && pane.get(position).unwrap().get().is_none()) -> ret.is_none(),
old(pane.get(position).is_some() && (*pane.get(position).unwrap()).get().is_none()) -> ret.is_none(),
"Should return None if the tile is empty"
)]
#[ensures(
@ -90,30 +86,28 @@ impl<'a> UpdateContext<'a> {
"Should add an entry in self.commit.updates if result is Some"
)]
pub(crate) fn new(
pane: &'a mut Pane,
pane: &'a Pane,
position: (usize, usize),
commit: &'a mut UpdateCommit,
) -> Option<(UpdateContext<'a>, &'a mut AnyTile)> {
let mut tile = pane.get_mut(position)?;
) -> Option<(UpdateContext<'a>, VecRefMut<'a, FullTile>)> {
let mut tile = pane.borrow_mut(position)?;
if tile.updated {
return None;
}
tile.updated = true; // prevent duplicate updates
commit.updates.push(position);
let ptr: *mut AnyTile = &mut **(tile.get_mut().as_mut()?);
let res = Self {
position,
state: tile.state(),
signal: tile.take_signal(),
pane: unsafe { NonNull::new_unchecked(&mut *pane) },
pane: pane,
commit,
};
// SAFETY: ptr is a valid pointer
// SAFETY: aliasing is prevented by the invariants of UpdateContext
Some((res, unsafe { &mut *ptr }))
Some((res, tile))
}
/// Returns the position of the currently updated tile.
@ -142,19 +136,13 @@ impl<'a> UpdateContext<'a> {
/// Returns the [`width`](Pane::width) of the current [`Pane`].
#[inline]
pub fn width(&self) -> NonZeroUsize {
unsafe {
// SAFETY: we only read self.pane.width
self.pane.as_ref().width()
}
self.pane.width()
}
/// Returns the [`height`](Pane::height) of the current [`Pane`].
#[inline]
pub fn height(&self) -> NonZeroUsize {
unsafe {
// SAFETY: we only read self.pane.height
self.pane.as_ref().height()
}
self.pane.height()
}
/// Returns a reference to the [signal](crate::FullTile::signal) of the currently updated tile.
@ -239,46 +227,33 @@ impl<'a> UpdateContext<'a> {
/// Returns an immutable reference to the [FullTile] at `pos` in the current [Pane].
/// Returns `None` if the tile is borrowed mutably, if it is the current tile or if it does not exist.
#[inline]
#[ensures(
ret.is_some() -> ret.unwrap().get().is_some() ->
std::ptr::addr_of!(*ret.unwrap().get().unwrap()) != self.current_ptr()
)]
pub fn get<'b>(&'b self, pos: (usize, usize)) -> Option<&'b FullTile>
pub fn get<'b>(&'b self, pos: (usize, usize)) -> Option<VecRef<'b, FullTile>>
where
'a: 'b,
{
if self.position == pos {
None
} else {
unsafe {
// SAFETY: pos != self.position, thus self.pane[self.position].cell cannot be accessed
self.pane.as_ref().get(pos)
}
self.pane.get(pos)
}
}
/// Returns `Some((position.x + Δx, position.y + Δy))` iff `(x + Δx, y + Δy)` is inside the current pane.
#[inline]
pub fn offset(&self, offset: (i8, i8)) -> Option<(usize, usize)> {
unsafe {
// SAFETY: Pane::offset does not read `self.pane.cells`
self.pane.as_ref().offset(self.position, offset)
}
self.pane.offset(self.position, offset)
}
/// Returns `true` iff `(x, y)` is within the bounds of the current pane.
#[inline]
#[ensures(ret == true -> position.0 < self.width().get() && position.1 < self.height().get())]
pub fn in_bounds(&self, position: (usize, usize)) -> bool {
unsafe {
// SAFETY: Pane::in_bounds does not read `self.pane.cells`
self.pane.as_ref().in_bounds(position)
}
self.pane.in_bounds(position)
}
/// 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)>
pub fn get_offset<'b>(&'b self, offset: (i8, i8)) -> Option<((usize, usize), VecRef<'b, FullTile>)>
where
'a: 'b,
{
@ -289,7 +264,7 @@ impl<'a> UpdateContext<'a> {
/// 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]
#[ensures(ret == true -> self.get(pos).is_some() && self.get(pos).unwrap().get().is_some())]
#[ensures(ret == true -> self.get(pos).is_some() && (*self.get(pos).unwrap()).get().is_some())]
pub fn accepts_signal(&self, pos: (usize, usize), direction: Direction) -> bool {
match self.get(pos) {
Some(tile) => tile.accepts_signal(direction),
@ -387,57 +362,51 @@ impl<'a> UpdateContext<'a> {
}
}
/// Stores the current signal back in the current tile, guaranteeing that it will stay there for
/// this update cycle. See [`take_signal`](UpdateContext::take_signal) for more information.
///
/// This method differs from [`send`](UpdateContext::send), as it takes action immediately.
/// The signal may also not be modified, as it would otherwise break the guarantees of [`Pane::step`].
///
/// This function will [`std::mem::take`] the signal stored in `UpdateContext`, similar to [`take_signal`](UpdateContext::take_signal).
/// If you wish to modify or send copies of the signal, then you will need to call [`signal`](UpdateContext::signal) beforehand and make
/// clones of the signal before calling `keep`.
///
/// If `take_signal` or `keep` are called before this functions, then it will do nothing.
///
/// # Example
///
/// ```
/// # use stackline::prelude::*;
/// #[derive(Clone, Debug)]
/// pub struct StorageTile {};
///
/// impl Tile for StorageTile {
/// fn update<'b>(&'b mut self, mut ctx: UpdateContext<'b>) {
/// if ctx.signal().is_some() {
/// ctx.keep();
/// }
/// // If we weren't to do this, then the signal would get dropped here
/// }
/// }
/// ```
#[ensures(self.signal.is_none())]
pub fn keep(&mut self) {
if self.signal.is_none() {
return;
}
unsafe {
// SAFETY: we only access self.pane[self.position].signal, not self.pane[self.position].cell
self.pane
.as_mut()
.get_mut(self.position)
.unwrap_or_else(|| unreachable!())
.set_signal(std::mem::take(&mut self.signal));
}
}
/// Returns a pointer to self.pane[self.position].cell
fn current_ptr(&self) -> *const AnyTile {
let reference = unsafe {
self.pane.as_ref().get(self.position).unwrap()
};
&*reference.get().unwrap()
}
// TODO: re-implement through UpdateCommit
// /// Stores the current signal back in the current tile, guaranteeing that it will stay there for
// /// this update cycle. See [`take_signal`](UpdateContext::take_signal) for more information.
// ///
// /// This method differs from [`send`](UpdateContext::send), as it takes action immediately.
// /// The signal may also not be modified, as it would otherwise break the guarantees of [`Pane::step`].
// ///
// /// This function will [`std::mem::take`] the signal stored in `UpdateContext`, similar to [`take_signal`](UpdateContext::take_signal).
// /// If you wish to modify or send copies of the signal, then you will need to call [`signal`](UpdateContext::signal) beforehand and make
// /// clones of the signal before calling `keep`.
// ///
// /// If `take_signal` or `keep` are called before this functions, then it will do nothing.
// ///
// /// # Example
// ///
// /// ```
// /// # use stackline::prelude::*;
// /// #[derive(Clone, Debug)]
// /// pub struct StorageTile {};
// ///
// /// impl Tile for StorageTile {
// /// fn update<'b>(&'b mut self, mut ctx: UpdateContext<'b>) {
// /// if ctx.signal().is_some() {
// /// ctx.keep();
// /// }
// /// // If we weren't to do this, then the signal would get dropped here
// /// }
// /// }
// /// ```
// #[ensures(self.signal.is_none())]
// pub fn keep(&mut self) {
// if self.signal.is_none() {
// return;
// }
// unsafe {
// // SAFETY: we only access self.pane[self.position].signal, not self.pane[self.position].cell
// self.pane
// .as_mut()
// .get_mut(self.position)
// .unwrap_or_else(|| unreachable!())
// .set_signal(std::mem::take(&mut self.signal));
// }
// }
}
pub struct SendError(pub Signal);

@ -1,8 +1,9 @@
use super::*;
use veccell::{VecCell, VecRef, VecRefMut};
#[derive(Debug)]
pub struct Pane {
tiles: Vec<FullTile>,
tiles: VecCell<FullTile>,
width: NonZeroUsize,
height: NonZeroUsize,
@ -37,11 +38,16 @@ impl Pane {
pub fn empty(width: usize, height: usize) -> Option<Self> {
// TODO: check that width * height is a valid usize
let length = width.checked_mul(height)?;
let mut tiles = VecCell::with_capacity(length);
for _ in 0..length {
tiles.push(FullTile::default());
}
Some(Self {
width: width.try_into().ok()?,
height: height.try_into().ok()?,
tiles: vec![FullTile::default(); length],
tiles,
signals: Vec::new(),
})
@ -138,7 +144,7 @@ impl Pane {
/// ```
#[inline]
#[ensures(self.in_bounds(position) -> ret.is_some())]
pub fn get<'b>(&'b self, position: (usize, usize)) -> Option<&'b FullTile> {
pub fn get<'b>(&'b self, position: (usize, usize)) -> Option<VecRef<'b, FullTile>> {
if !self.in_bounds(position) {
return None;
}
@ -172,6 +178,15 @@ impl Pane {
.get_mut(position.1 * self.width.get() + position.0)
}
pub(crate) fn borrow_mut<'b>(&'b self, position: (usize, usize)) -> Option<VecRefMut<'b, FullTile>> {
if !self.in_bounds(position) {
return None;
}
self.tiles
.borrow_mut(position.1 * self.width.get() + position.0)
}
/// Sets the tile at `position` to `tile`. `T` must either implement [`Tile`] or be `()`.
#[inline]
#[ensures(self.in_bounds(position) -> ret.is_some())]
@ -240,7 +255,7 @@ impl Pane {
/// assert!(pane.get((0, 0)).unwrap().signal().is_some());
/// ```
#[inline]
#[ensures(ret.is_some() -> self.in_bounds(position) && self.get(position).unwrap().get().is_some())]
#[ensures(ret.is_some() -> self.in_bounds(position) && (*self.get(position).unwrap()).get().is_some())]
#[ensures(!self.in_bounds(position) -> ret.is_none())]
pub fn set_signal(&mut self, position: (usize, usize), mut signal: Signal) -> Option<()> {
signal.set_position(position);
@ -277,9 +292,9 @@ impl Pane {
#[ensures(!self.in_bounds(position) -> ret.is_none())]
fn update(&mut self, position: (usize, usize), commit: &mut UpdateCommit) -> Option<()> {
// NOTE: Tiles will only be updated once as per UpdateContext::new
let (ctx, tile) = UpdateContext::new(self, position, commit)?;
let (ctx, mut tile) = UpdateContext::new(self, position, commit)?;
tile.update(ctx);
(*tile).get_mut()?.update(ctx);
Some(())
}
@ -353,7 +368,7 @@ impl Pane {
/// Returns an iterator over the tiles and their coordinates
#[inline]
pub fn tiles<'b>(&'b self) -> impl Iterator<Item = (usize, usize, &FullTile)> + 'b {
pub fn tiles<'b>(&'b self) -> impl Iterator<Item = (usize, usize, VecRef<'b, FullTile>)> + 'b {
self.tiles
.iter()
.enumerate()

Loading…
Cancel
Save