use std::collections::VecDeque;
use std::rc::Rc;
use crate::Context;
use crate::cell::CellHandle;
use crate::slot::SlotHandle;
#[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> {
None
}
fn len(&self) -> usize;
fn capacity(&self) -> Option<usize> {
None
}
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: Rc<std::cell::RefCell<S>>,
capacity: Option<usize>,
head: SlotHandle<Option<T>>,
len: SlotHandle<usize>,
is_empty: SlotHandle<bool>,
is_full: SlotHandle<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> + 'static,
{
pub fn with_storage(ctx: &Context, storage: S) -> Self {
let closed_val = storage.is_closed();
let capacity = storage.capacity();
let storage = Rc::new(std::cell::RefCell::new(storage));
let head = {
let storage = Rc::clone(&storage);
ctx.memo(move |_ctx| storage.borrow().peek().cloned())
};
let len = {
let storage = Rc::clone(&storage);
ctx.memo(move |_ctx| storage.borrow().len())
};
let is_empty = {
let storage = Rc::clone(&storage);
ctx.memo(move |_ctx| storage.borrow().len() == 0)
};
let is_full = {
let storage = Rc::clone(&storage);
ctx.memo(move |_ctx| {
let s = storage.borrow();
match s.capacity() {
Some(cap) => s.len() >= cap,
None => false,
}
})
};
Self {
inner: Rc::new(QueueCellInner {
storage,
capacity,
head,
len,
is_empty,
is_full,
closed: ctx.cell(closed_val),
}),
}
}
fn invalidate_readers(
&self,
ctx: &Context,
len_before: usize,
len_after: usize,
head_changed: bool,
) {
let is_empty_changed = (len_before == 0) != (len_after == 0);
let is_full_changed = self
.inner
.capacity
.map(|c| (len_before >= c) != (len_after >= c))
.unwrap_or(false);
let mut roots = [self.inner.len.id; 4];
let mut n = 1;
if is_empty_changed {
roots[n] = self.inner.is_empty.id;
n += 1;
}
if is_full_changed {
roots[n] = self.inner.is_full.id;
n += 1;
}
if head_changed {
roots[n] = self.inner.head.id;
n += 1;
}
ctx.clear_slots(&roots[..n]);
}
pub fn try_push(&self, ctx: &Context, value: T) -> Result<(), QueuePushError> {
let (result, len_before) = {
let mut s = self.inner.storage.borrow_mut();
let len_before = s.len();
(s.try_push(value), len_before)
};
if result.is_ok() {
self.invalidate_readers(ctx, len_before, len_before + 1, len_before == 0);
}
result
}
pub fn try_pop(&self, ctx: &Context) -> Result<T, QueuePopError> {
let (result, len_before) = {
let mut s = self.inner.storage.borrow_mut();
let len_before = s.len();
(s.try_pop(), len_before)
};
if result.is_ok() {
self.invalidate_readers(ctx, len_before, len_before - 1, true);
}
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(&self.inner.head)
}
pub fn head_handle(&self) -> SlotHandle<Option<T>> {
self.inner.head
}
pub fn len(&self, ctx: &Context) -> usize {
ctx.get(&self.inner.len)
}
pub fn is_empty(&self, ctx: &Context) -> bool {
ctx.get(&self.inner.is_empty)
}
pub fn is_full(&self, ctx: &Context) -> bool {
ctx.get(&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.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: SlotHandle<Option<T>>,
pub len: SlotHandle<usize>,
pub is_empty: SlotHandle<bool>,
pub is_full: SlotHandle<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);
}
#[derive(Default)]
struct MinimalFifoStorage<T> {
elements: VecDeque<T>,
closed: bool,
}
impl<T> QueueStorage<T> for MinimalFifoStorage<T> {
fn try_push(&mut self, value: T) -> Result<(), QueuePushError> {
if self.closed {
return Err(QueuePushError::Closed);
}
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),
None => Err(QueuePopError::Empty),
}
}
fn len(&self) -> usize {
self.elements.len()
}
fn is_closed(&self) -> bool {
self.closed
}
fn close(&mut self) {
self.closed = true;
}
}
#[test]
fn raw_channel_backend_conforms_to_minimal_contract() {
let ctx = Context::new();
let q: QueueCell<i32, MinimalFifoStorage<i32>> =
QueueCell::with_storage(&ctx, MinimalFifoStorage::default());
assert!(q.is_empty(&ctx));
q.try_push(&ctx, 1).unwrap();
q.try_push(&ctx, 2).unwrap();
assert_eq!(q.len(&ctx), 2);
assert!(!q.is_empty(&ctx));
assert_eq!(q.head(&ctx), None);
assert!(!q.is_full(&ctx));
assert_eq!(q.capacity(), None);
assert_eq!(q.try_pop(&ctx).unwrap(), 1);
assert_eq!(q.try_pop(&ctx).unwrap(), 2);
assert!(q.is_empty(&ctx));
q.close(&ctx);
assert!(q.is_closed(&ctx));
assert_eq!(q.try_push(&ctx, 3), Err(QueuePushError::Closed));
assert_eq!(q.try_pop(&ctx), Err(QueuePopError::Closed));
}
#[test]
fn raw_channel_backend_reader_kinds_stay_reactive() {
let ctx = Context::new();
let q: QueueCell<i32, MinimalFifoStorage<i32>> =
QueueCell::with_storage(&ctx, MinimalFifoStorage::default());
let len_reader = ctx.computed({
let q = q.clone();
move |ctx| q.len(ctx)
});
assert_eq!(ctx.get(&len_reader), 0);
q.try_push(&ctx, 10).unwrap();
assert!(!ctx.is_set(&len_reader), "push must invalidate len reader");
assert_eq!(ctx.get(&len_reader), 1);
q.try_pop(&ctx).unwrap();
assert!(!ctx.is_set(&len_reader), "pop must invalidate len reader");
assert_eq!(ctx.get(&len_reader), 0);
}
}