Skip to content

Commit

Permalink
Move Handle and ObjectId to dedicated module
Browse files Browse the repository at this point in the history
  • Loading branch information
hannobraun committed Sep 20, 2022
1 parent 7769599 commit d7ad3d7
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 157 deletions.
155 changes: 155 additions & 0 deletions crates/fj-kernel/src/stores/handle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
use std::{any::type_name, fmt, hash::Hash, ops::Deref};

use super::store::StoreInner;

/// A handle for an object
///
/// You can get an instance of `Handle` by inserting an object into a store. See
/// [`Store::insert`]. A handle dereferences to the object it points to, via its
/// [`Deref`] implementation.
///
/// # Equality and Identity
///
/// Equality of `Handle`s is defined via the objects they reference. If those
/// objects are equal, the `Handle`s are considered equal.
///
/// This is distinct from the *identity* of the referenced objects. Two objects
/// might be equal, but they might be have been created at different times, for
/// different reasons, and thus live in different slots in the storage. This is
/// a relevant distinction when validating objects, as equal but not identical
/// objects might be a sign of a bug.
///
/// You can compare the identity of two objects through their `Handle`s, by
/// comparing the values returned by [`Handle::id`].
pub struct Handle<T> {
pub(super) store: StoreInner<T>,
pub(super) ptr: *const Option<T>,
}

impl<T> Handle<T> {
/// Access this pointer's unique id
pub fn id(&self) -> ObjectId {
ObjectId(self.ptr as u64)
}

/// Return a clone of the object this handle refers to
pub fn clone_object(&self) -> T
where
T: Clone,
{
self.deref().clone()
}
}

impl<T> Deref for Handle<T> {
type Target = T;

fn deref(&self) -> &Self::Target {
// `Handle` keeps a reference to `StoreInner`. Since that is an `Arc`
// under the hood, we know that as long as an instance of `Handle`
// exists, the `StoreInner` its data lives in is still alive. Even if
// the `Store` was dropped.
//
// The `Store` API ensures two things:
//
// 1. That no `Handle` is ever created, until the object it references
// has at least been reserved.
// 2. That the memory objects live in is never deallocated.
//
// That means that as long as a `Handle` exists, the object is
// references has at least been reserved, and has not been deallocated.
//
// Given all this, we know that the following must be true:
//
// - The pointer is not null.
// - The pointer is properly aligned.
// - The pointer is dereferenceable.
// - The pointer points to an initialized instance of `T`.
//
// Further, there is no way to (safely) get a `&mut` reference to any
// object in a `Store`/`Block`. So we know that the aliasing rules for
// the reference we return here are enforced.
//
// Furthermore, all of the code mentioned here is covered by unit tests,
// which I've run successfully run under Miri.
let cell = unsafe { &*self.ptr };

// Can only happen, if the object has been reserved, but the reservation
// was never completed.
cell.as_ref()
.expect("Handle references non-existing object")
}
}

impl<T> Clone for Handle<T> {
fn clone(&self) -> Self {
Self {
store: self.store.clone(),
ptr: self.ptr,
}
}
}

impl<T> Eq for Handle<T> where T: Eq {}

impl<T> PartialEq for Handle<T>
where
T: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.deref().eq(other.deref())
}
}

impl<T> Hash for Handle<T>
where
T: Hash,
{
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.deref().hash(state)
}
}

impl<T> Ord for Handle<T>
where
T: Ord,
{
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.deref().cmp(other.deref())
}
}

impl<T> PartialOrd for Handle<T>
where
T: PartialOrd,
{
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.deref().partial_cmp(other.deref())
}
}

impl<T> fmt::Debug for Handle<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let name = {
let type_name = type_name::<T>();
match type_name.rsplit_once("::") {
Some((_, name)) => name,
None => type_name,
}
};
let id = self.id().0;

write!(f, "{name} @ {id:#x}")?;

Ok(())
}
}

unsafe impl<T> Send for Handle<T> {}
unsafe impl<T> Sync for Handle<T> {}

/// Represents the ID of an object
///
/// See [`Handle::id`].
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct ObjectId(u64);
6 changes: 5 additions & 1 deletion crates/fj-kernel/src/stores/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
//! Append-only object storage
mod blocks;
mod handle;
mod store;

use crate::objects::GlobalCurve;

pub use self::store::{Handle, Iter, ObjectId, Reservation, Store};
pub use self::{
handle::{Handle, ObjectId},
store::{Iter, Reservation, Store},
};

/// The available object stores
#[derive(Debug, Default)]
Expand Down
158 changes: 2 additions & 156 deletions crates/fj-kernel/src/stores/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,11 @@
//!
//! But in any case, this was fun to write, and not that much work.
use std::{
any::type_name, fmt, hash::Hash, marker::PhantomData, ops::Deref, sync::Arc,
};
use std::{marker::PhantomData, sync::Arc};

use parking_lot::RwLock;

use super::blocks::Blocks;
use super::{blocks::Blocks, Handle};

/// Append-only object storage
#[derive(Debug)]
Expand Down Expand Up @@ -98,158 +96,6 @@ impl<'a, T> IntoIterator for &'a Store<T> {
}
}

/// A handle for an object
///
/// You can get an instance of `Handle` by inserting an object into a store. See
/// [`Store::insert`]. A handle dereferences to the object it points to, via its
/// [`Deref`] implementation.
///
/// # Equality and Identity
///
/// Equality of `Handle`s is defined via the objects they reference. If those
/// objects are equal, the `Handle`s are considered equal.
///
/// This is distinct from the *identity* of the referenced objects. Two objects
/// might be equal, but they might be have been created at different times, for
/// different reasons, and thus live in different slots in the storage. This is
/// a relevant distinction when validating objects, as equal but not identical
/// objects might be a sign of a bug.
///
/// You can compare the identity of two objects through their `Handle`s, by
/// comparing the values returned by [`Handle::id`].
pub struct Handle<T> {
store: StoreInner<T>,
ptr: *const Option<T>,
}

impl<T> Handle<T> {
/// Access this pointer's unique id
pub fn id(&self) -> ObjectId {
ObjectId(self.ptr as u64)
}

/// Return a clone of the object this handle refers to
pub fn clone_object(&self) -> T
where
T: Clone,
{
self.deref().clone()
}
}

impl<T> Deref for Handle<T> {
type Target = T;

fn deref(&self) -> &Self::Target {
// `Handle` keeps a reference to `StoreInner`. Since that is an `Arc`
// under the hood, we know that as long as an instance of `Handle`
// exists, the `StoreInner` its data lives in is still alive. Even if
// the `Store` was dropped.
//
// The `Store` API ensures two things:
//
// 1. That no `Handle` is ever created, until the object it references
// has at least been reserved.
// 2. That the memory objects live in is never deallocated.
//
// That means that as long as a `Handle` exists, the object is
// references has at least been reserved, and has not been deallocated.
//
// Given all this, we know that the following must be true:
//
// - The pointer is not null.
// - The pointer is properly aligned.
// - The pointer is dereferenceable.
// - The pointer points to an initialized instance of `T`.
//
// Further, there is no way to (safely) get a `&mut` reference to any
// object in a `Store`/`Block`. So we know that the aliasing rules for
// the reference we return here are enforced.
//
// Furthermore, all of the code mentioned here is covered by unit tests,
// which I've run successfully run under Miri.
let cell = unsafe { &*self.ptr };

// Can only happen, if the object has been reserved, but the reservation
// was never completed.
cell.as_ref()
.expect("Handle references non-existing object")
}
}

impl<T> Clone for Handle<T> {
fn clone(&self) -> Self {
Self {
store: self.store.clone(),
ptr: self.ptr,
}
}
}

impl<T> Eq for Handle<T> where T: Eq {}

impl<T> PartialEq for Handle<T>
where
T: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.deref().eq(other.deref())
}
}

impl<T> Hash for Handle<T>
where
T: Hash,
{
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.deref().hash(state)
}
}

impl<T> Ord for Handle<T>
where
T: Ord,
{
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.deref().cmp(other.deref())
}
}

impl<T> PartialOrd for Handle<T>
where
T: PartialOrd,
{
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.deref().partial_cmp(other.deref())
}
}

impl<T> fmt::Debug for Handle<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let name = {
let type_name = type_name::<T>();
match type_name.rsplit_once("::") {
Some((_, name)) => name,
None => type_name,
}
};
let id = self.id().0;

write!(f, "{name} @ {id:#x}")?;

Ok(())
}
}

unsafe impl<T> Send for Handle<T> {}
unsafe impl<T> Sync for Handle<T> {}

/// Represents the ID of an object
///
/// See [`Handle::id`].
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct ObjectId(u64);

/// An iterator over objects in a [`Store`]
pub struct Iter<'a, T> {
store: StoreInner<T>,
Expand Down

0 comments on commit d7ad3d7

Please sign in to comment.