Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Unreleased
==========
* Add Relaxed access mode to allow copyable keys and failable access [@bugadani](https://github.com/bugadani)
* Verify that keys are used to access data in their associated Slots instances [@bugadani](https://github.com/bugadani)
* Allow compiler to reduce memory footprint if possible [@chrysn](https://github.com/chrysn)
* Allow using FnOnce closures in `try_read`, `read` and `modify` [@chrysn](https://github.com/chrysn)
Expand Down
189 changes: 128 additions & 61 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
//! Example usage:
//!
//! ```
//! use slots::Slots;
//! use slots::{Slots, Strict};
//! use slots::consts::U6;
//!
//! let mut slots: Slots<_, U6> = Slots::new(); // Capacity of 6 elements
//! let mut slots: Slots<_, U6, Strict> = Slots::new(); // Capacity of 6 elements
//!
//! // Store elements
//! let k1 = slots.store(2).unwrap(); // returns Err(2) if full
Expand Down Expand Up @@ -50,28 +50,66 @@
#![cfg_attr(not(test), no_std)]

use core::marker::PhantomData;
#[cfg(feature = "verify_owner")]
use core::sync::atomic::{AtomicUsize, Ordering};
use generic_array::{GenericArray, sequence::GenericSequence};

pub use generic_array::typenum::consts;
pub use generic_array::ArrayLength;

use generic_array::typenum::Unsigned;

pub struct Key<IT, N> {
#[cfg(feature = "verify_owner")]
owner_id: usize,
mod access_mode {
pub struct Relaxed {}
pub struct Strict {}

pub trait AccessMode {
type ObjectId;

fn new_obj_id() -> Self::ObjectId;
}

impl AccessMode for Relaxed {
type ObjectId = ();

fn new_obj_id() -> Self::ObjectId {
()
}
}
impl AccessMode for Strict {
#[cfg(feature = "verify_owner")]
type ObjectId = usize;

#[cfg(not(feature = "verify_owner"))]
type ObjectId = ();

#[cfg(feature = "verify_owner")]
fn new_obj_id() -> Self::ObjectId {
use core::sync::atomic::{AtomicUsize, Ordering};
static COUNTER: AtomicUsize = AtomicUsize::new(0);

COUNTER.fetch_add(1, Ordering::Relaxed)
}

#[cfg(not(feature = "verify_owner"))]
fn new_obj_id() -> Self::ObjectId {
()
}
}
}

pub use access_mode::{Strict, Relaxed};

pub struct Key<IT, N>
where N: ArrayLength<Entry<IT>> + Unsigned {
owner_id: <Strict as access_mode::AccessMode>::ObjectId,
index: usize,
_item_marker: PhantomData<IT>,
_size_marker: PhantomData<N>
}

impl<IT, N> Key<IT, N>
where N: ArrayLength<Entry<IT>> + Unsigned {
fn new(owner: &Slots<IT, N>, idx: usize) -> Self {
fn new(owner: &Slots<IT, N, Strict>, idx: usize) -> Self {
Self {
#[cfg(feature = "verify_owner")]
owner_id: owner.id,
index: idx,
_item_marker: PhantomData,
Expand All @@ -95,53 +133,38 @@ enum EntryInner<IT> {
// Data type that stores values and returns a key that can be used to manipulate
// the stored values.
// Values can be read by anyone but can only be modified using the key.
pub struct Slots<IT, N>
where N: ArrayLength<Entry<IT>> + Unsigned {
#[cfg(feature = "verify_owner")]
id: usize,
pub struct Slots<IT, N, A>
where N: ArrayLength<Entry<IT>> + Unsigned,
A: access_mode::AccessMode {
id: A::ObjectId,
items: GenericArray<Entry<IT>, N>,
// Could be optimized by making it just usize and relying on free_count to determine its
// validity
// Could be optimized by making it just usize and relying on count to determine its validity
next_free: Option<usize>,
free_count: usize
count: usize,
_mode_marker: PhantomData<A>
}

#[cfg(feature = "verify_owner")]
fn new_instance_id() -> usize {
static COUNTER: AtomicUsize = AtomicUsize::new(0);

COUNTER.fetch_add(1, Ordering::Relaxed)
}

impl<IT, N> Slots<IT, N>
where N: ArrayLength<Entry<IT>> + Unsigned {
impl<IT, N, A> Slots<IT, N, A>
where N: ArrayLength<Entry<IT>> + Unsigned,
A: access_mode::AccessMode {
pub fn new() -> Self {
let size = N::to_usize();

Self {
#[cfg(feature = "verify_owner")]
id: new_instance_id(),
id: A::new_obj_id(),
items: GenericArray::generate(|i| Entry(i.checked_sub(1).map(EntryInner::EmptyNext).unwrap_or(EntryInner::EmptyLast))),
next_free: size.checked_sub(1),
free_count: size
count: 0,
_mode_marker: PhantomData
}
}

#[cfg(feature = "verify_owner")]
fn verify_key(&self, key: &Key<IT, N>) {
assert!(key.owner_id == self.id, "Key used in wrong instance");
}

#[cfg(not(feature = "verify_owner"))]
fn verify_key(&self, _key: &Key<IT, N>) {
}

pub fn capacity(&self) -> usize {
N::to_usize()
}

pub fn count(&self) -> usize {
self.capacity() - self.free_count
self.count
}

fn free(&mut self, idx: usize) {
Expand All @@ -150,7 +173,7 @@ impl<IT, N> Slots<IT, N>
None => Entry(EntryInner::EmptyLast),
};
self.next_free = Some(idx);
self.free_count += 1;
self.count -= 1;
}

fn alloc(&mut self) -> Option<usize> {
Expand All @@ -160,53 +183,97 @@ impl<IT, N> Slots<IT, N>
EntryInner::EmptyLast => None,
_ => unreachable!("Non-empty item in entry behind free chain"),
};
self.free_count -= 1;
self.count += 1;
Some(index)
}

pub fn store(&mut self, item: IT) -> Result<Key<IT, N>, IT> {
fn _store(&mut self, item: IT) -> Result<usize, IT> {
match self.alloc() {
Some(i) => {
self.items[i] = Entry(EntryInner::Used(item));
Ok(Key::new(self, i))
Ok(i)
}
None => Err(item)
}
}

pub fn take(&mut self, key: Key<IT, N>) -> IT {
self.verify_key(&key);
fn _remove(&mut self, idx: usize) -> Option<IT> {
let taken = core::mem::replace(&mut self.items[idx], Entry(EntryInner::EmptyLast));

let taken = core::mem::replace(&mut self.items[key.index], Entry(EntryInner::EmptyLast));
self.free(key.index);
match taken.0 {
EntryInner::Used(item) => item,
_ => unreachable!("Invalid key")
EntryInner::Used(item) => {
self.free(idx);
Some(item)
},
_ => None
}
}

pub fn read<T, F>(&self, key: &Key<IT, N>, function: F) -> T where F: FnOnce(&IT) -> T {
self.verify_key(&key);

match self.try_read(key.index, function) {
Some(t) => t,
None => unreachable!("Invalid key")
fn _read<T, F>(&self, idx: usize, function: F) -> Option<T> where F: FnOnce(&IT) -> T {
match &self.items[idx].0 {
EntryInner::Used(item) => Some(function(&item)),
_ => None
}
}

pub fn try_read<T, F>(&self, key: usize, function: F) -> Option<T> where F: FnOnce(&IT) -> T {
match &self.items[key].0 {
EntryInner::Used(item) => Some(function(&item)),
fn _modify<T, F>(&mut self, idx: usize, function: F) -> Option<T> where F: FnOnce(&mut IT) -> T {
match self.items[idx].0 {
EntryInner::Used(ref mut item) => Some(function(item)),
_ => None
}
}
}

impl<IT, N> Slots<IT, N, Strict>
where N: ArrayLength<Entry<IT>> + Unsigned {

fn verify_key(&self, key: &Key<IT, N>) {
assert!(key.owner_id == self.id, "Key used in wrong instance");
}

pub fn store(&mut self, item: IT) -> Result<Key<IT, N>, IT> {
self._store(item).map(|i| Key::new(self, i))
}

pub fn take(&mut self, key: Key<IT, N>) -> IT {
self.verify_key(&key);

self._remove(key.index).expect("Invalid key")
}

pub fn read<T, F>(&self, key: &Key<IT, N>, function: F) -> T where F: FnOnce(&IT) -> T {
self.verify_key(&key);

self._read(key.index, function).expect("Invalid key")
}

pub fn try_read<T, F>(&self, idx: usize, function: F) -> Option<T> where F: FnOnce(&IT) -> T {
self._read(idx, function)
}

pub fn modify<T, F>(&mut self, key: &Key<IT, N>, function: F) -> T where F: FnOnce(&mut IT) -> T {
self.verify_key(&key);

match self.items[key.index].0 {
EntryInner::Used(ref mut item) => function(item),
_ => unreachable!("Invalid key")
}
self._modify(key.index, function).expect("Invalid key")
}
}

impl<IT, N> Slots<IT, N, Relaxed>
where N: ArrayLength<Entry<IT>> + Unsigned {

pub fn store(&mut self, item: IT) -> Result<usize, IT> {
self._store(item)
}

pub fn take(&mut self, idx: usize) -> Option<IT> {
self._remove(idx)
}

pub fn modify<T, F>(&mut self, idx: usize, function: F) -> Option<T> where F: FnOnce(&mut IT) -> T {
self._modify(idx, function)
}

pub fn read<T, F>(&self, idx: usize, function: F) -> Option<T> where F: FnOnce(&IT) -> T {
self._read(idx, function)
}
}
Loading