use std::collections::VecDeque;
use std::rc::Rc;
use crate::Context;
use crate::cell::CellHandle;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QueuePushError {
Full,
Closed,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QueuePopError {
Empty,
Closed,
}
#[allow(clippy::len_without_is_empty)]
pub trait QueueStorage<T> {
fn try_push(&mut self, value: T) -> Result<(), QueuePushError>;
fn try_pop(&mut self) -> Result<T, QueuePopError>;
fn peek(&self) -> Option<&T>;
fn len(&self) -> usize;
fn capacity(&self) -> Option<usize>;
fn is_closed(&self) -> bool;
fn close(&mut self);
}
#[derive(Debug, Clone, Default)]
pub struct VecDequeStorage<T> {
elements: VecDeque<T>,
capacity: Option<usize>,
closed: bool,
}
impl<T> VecDequeStorage<T> {
pub fn unbounded() -> Self {
Self {
elements: VecDeque::new(),
capacity: None,
closed: false,
}
}
pub fn with_capacity(capacity: usize) -> Self {
assert!(capacity > 0, "VecDequeStorage capacity must be > 0");
Self {
elements: VecDeque::with_capacity(capacity),
capacity: Some(capacity),
closed: false,
}
}
pub fn elements(&self) -> Vec<T>
where
T: Clone,
{
self.elements.iter().cloned().collect()
}
}
impl<T> QueueStorage<T> for VecDequeStorage<T> {
fn try_push(&mut self, value: T) -> Result<(), QueuePushError> {
if self.closed {
return Err(QueuePushError::Closed);
}
if let Some(cap) = self.capacity
&& self.elements.len() >= cap
{
return Err(QueuePushError::Full);
}
self.elements.push_back(value);
Ok(())
}
fn try_pop(&mut self) -> Result<T, QueuePopError> {
match self.elements.pop_front() {
Some(v) => Ok(v),
None => {
if self.closed {
Err(QueuePopError::Closed)
} else {
Err(QueuePopError::Empty)
}
}
}
}
fn peek(&self) -> Option<&T> {
self.elements.front()
}
fn len(&self) -> usize {
self.elements.len()
}
fn capacity(&self) -> Option<usize> {
self.capacity
}
fn is_closed(&self) -> bool {
self.closed
}
fn close(&mut self) {
self.closed = true;
}
}
#[cfg(feature = "serde")]
impl<T> serde::Serialize for VecDequeStorage<T>
where
T: serde::Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeSeq;
let mut seq = serializer.serialize_seq(None)?;
for e in &self.elements {
seq.serialize_element(e)?;
}
seq.end()
}
}
struct QueueCellInner<T, S> {
storage: std::cell::RefCell<S>,
head: CellHandle<Option<T>>,
len: CellHandle<usize>,
is_empty: CellHandle<bool>,
is_full: CellHandle<bool>,
closed: CellHandle<bool>,
}
pub struct QueueCell<T, S = VecDequeStorage<T>> {
inner: Rc<QueueCellInner<T, S>>,
}
impl<T, S> Clone for QueueCell<T, S> {
fn clone(&self) -> Self {
Self {
inner: Rc::clone(&self.inner),
}
}
}
impl<T> QueueCell<T, VecDequeStorage<T>>
where
T: PartialEq + Clone + 'static,
{
pub fn new(ctx: &Context) -> Self {
Self::with_storage(ctx, VecDequeStorage::unbounded())
}
pub fn with_capacity(ctx: &Context, capacity: usize) -> Self {
Self::with_storage(ctx, VecDequeStorage::with_capacity(capacity))
}
}
impl<T, S> QueueCell<T, S>
where
T: PartialEq + Clone + 'static,
S: QueueStorage<T>,
{
pub fn with_storage(ctx: &Context, storage: S) -> Self {
let (head_val, len_val, is_full_val, closed_val) = {
let is_full_val = match storage.capacity() {
Some(cap) => storage.len() >= cap,
None => false,
};
(
storage.peek().cloned(),
storage.len(),
is_full_val,
storage.is_closed(),
)
};
Self {
inner: Rc::new(QueueCellInner {
storage: std::cell::RefCell::new(storage),
head: ctx.cell(head_val),
len: ctx.cell(len_val),
is_empty: ctx.cell(len_val == 0),
is_full: ctx.cell(is_full_val),
closed: ctx.cell(closed_val),
}),
}
}
fn sync_content(&self, ctx: &Context) {
let (head_val, len_val, is_empty_val, is_full_val) = {
let s = self.inner.storage.borrow();
let len_val = s.len();
let is_full_val = match s.capacity() {
Some(cap) => len_val >= cap,
None => false,
};
(s.peek().cloned(), len_val, len_val == 0, is_full_val)
};
ctx.batch(|ctx| {
ctx.set_cell(&self.inner.head, head_val);
ctx.set_cell(&self.inner.len, len_val);
ctx.set_cell(&self.inner.is_empty, is_empty_val);
ctx.set_cell(&self.inner.is_full, is_full_val);
});
}
pub fn try_push(&self, ctx: &Context, value: T) -> Result<(), QueuePushError> {
let result = self.inner.storage.borrow_mut().try_push(value);
if result.is_ok() {
self.sync_content(ctx);
}
result
}
pub fn try_pop(&self, ctx: &Context) -> Result<T, QueuePopError> {
let result = self.inner.storage.borrow_mut().try_pop();
if result.is_ok() {
self.sync_content(ctx);
}
result
}
pub fn close(&self, ctx: &Context) {
let was_closed = self.inner.storage.borrow().is_closed();
if was_closed {
return;
}
self.inner.storage.borrow_mut().close();
ctx.set_cell(&self.inner.closed, true);
}
pub fn head(&self, ctx: &Context) -> Option<T> {
ctx.get_cell(&self.inner.head)
}
pub fn head_handle(&self) -> CellHandle<Option<T>> {
self.inner.head
}
pub fn len(&self, ctx: &Context) -> usize {
ctx.get_cell(&self.inner.len)
}
pub fn is_empty(&self, ctx: &Context) -> bool {
ctx.get_cell(&self.inner.is_empty)
}
pub fn is_full(&self, ctx: &Context) -> bool {
ctx.get_cell(&self.inner.is_full)
}
pub fn is_closed(&self, ctx: &Context) -> bool {
ctx.get_cell(&self.inner.closed)
}
pub fn reader_handles(&self) -> QueueReaderHandles<T> {
QueueReaderHandles {
head: self.inner.head,
len: self.inner.len,
is_empty: self.inner.is_empty,
is_full: self.inner.is_full,
closed: self.inner.closed,
}
}
pub fn capacity(&self) -> Option<usize> {
self.inner.storage.borrow().capacity()
}
}
impl<T> QueueCell<T, VecDequeStorage<T>>
where
T: PartialEq + Clone + 'static,
{
pub fn elements(&self) -> Vec<T> {
self.inner.storage.borrow().elements()
}
}
#[derive(Debug, Clone, Copy)]
pub struct QueueReaderHandles<T> {
pub head: CellHandle<Option<T>>,
pub len: CellHandle<usize>,
pub is_empty: CellHandle<bool>,
pub is_full: CellHandle<bool>,
pub closed: CellHandle<bool>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn spsc_fifo_basic() {
let ctx = Context::new();
let q: QueueCell<i32> = QueueCell::new(&ctx);
assert!(q.is_empty(&ctx));
assert_eq!(q.head(&ctx), None);
q.try_push(&ctx, 1).unwrap();
q.try_push(&ctx, 2).unwrap();
q.try_push(&ctx, 3).unwrap();
assert_eq!(q.len(&ctx), 3);
assert_eq!(q.head(&ctx), Some(1));
assert_eq!(q.elements(), vec![1, 2, 3]);
assert_eq!(q.try_pop(&ctx).unwrap(), 1);
assert_eq!(q.try_pop(&ctx).unwrap(), 2);
assert_eq!(q.try_pop(&ctx).unwrap(), 3);
assert_eq!(q.try_pop(&ctx), Err(QueuePopError::Empty));
}
#[test]
fn bounded_rejects_at_capacity() {
let ctx = Context::new();
let q = QueueCell::<i32>::with_capacity(&ctx, 2);
assert_eq!(q.capacity(), Some(2));
assert!(!q.is_full(&ctx));
q.try_push(&ctx, 1).unwrap();
q.try_push(&ctx, 2).unwrap();
assert!(q.is_full(&ctx));
assert_eq!(q.try_push(&ctx, 3), Err(QueuePushError::Full));
assert_eq!(q.try_pop(&ctx).unwrap(), 1);
assert!(!q.is_full(&ctx));
q.try_push(&ctx, 3).unwrap();
assert!(q.is_full(&ctx));
}
#[test]
fn closure_lifecycle() {
let ctx = Context::new();
let q: QueueCell<&str> = QueueCell::new(&ctx);
q.try_push(&ctx, "a").unwrap();
q.try_push(&ctx, "b").unwrap();
q.close(&ctx);
assert!(q.is_closed(&ctx));
assert_eq!(q.try_push(&ctx, "c"), Err(QueuePushError::Closed));
assert_eq!(q.try_pop(&ctx).unwrap(), "a");
assert_eq!(q.try_pop(&ctx).unwrap(), "b");
assert_eq!(q.try_pop(&ctx), Err(QueuePopError::Closed));
q.close(&ctx);
assert!(q.is_closed(&ctx));
}
#[test]
fn reader_kind_independence_head_not_invalidated_on_push_to_nonempty() {
let ctx = Context::new();
let q: QueueCell<i32> = QueueCell::new(&ctx);
let head_reader = ctx.computed({
let q = q.clone();
move |ctx| q.head(ctx)
});
assert_eq!(ctx.get(&head_reader), None);
q.try_push(&ctx, 1).unwrap();
assert_eq!(ctx.get(&head_reader), Some(1));
q.try_push(&ctx, 2).unwrap();
q.try_push(&ctx, 3).unwrap();
assert!(
ctx.is_set(&head_reader),
"push to non-empty must not invalidate head reader"
);
q.try_pop(&ctx).unwrap();
assert_eq!(ctx.get(&head_reader), Some(2));
}
#[test]
fn mpsc_via_batch_is_one_invalidation_pass() {
let ctx = Context::new();
let q: QueueCell<i32> = QueueCell::new(&ctx);
let len_reader = ctx.computed({
let q = q.clone();
move |ctx| q.len(ctx)
});
assert_eq!(ctx.get(&len_reader), 0);
ctx.batch(|ctx| {
q.try_push(ctx, 10).unwrap();
q.try_push(ctx, 20).unwrap();
q.try_push(ctx, 30).unwrap();
});
assert!(
!ctx.is_set(&len_reader),
"batch should have invalidated the len reader once"
);
assert_eq!(ctx.get(&len_reader), 3);
assert_eq!(q.elements(), vec![10, 20, 30]);
}
#[test]
fn clone_shares_state() {
let ctx = Context::new();
let q: QueueCell<i32> = QueueCell::new(&ctx);
let producer = q.clone();
producer.try_push(&ctx, 42).unwrap();
assert_eq!(q.try_pop(&ctx).unwrap(), 42);
}
}