use std::marker::PhantomData;
use crate::Context;
use crate::cell::CellHandle;
use crate::merge::MergePolicy;
use crate::slot::SlotHandle;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BoundDim {
Count,
Bytes,
Keys,
Age,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Overflow {
Block,
DropNewest,
DropOldest,
Conflate,
Spill,
}
pub struct BackpressurePolicy {
pub dimension: CellHandle<BoundDim>,
pub high_water: CellHandle<u64>,
pub low_water: CellHandle<u64>,
pub overflow: CellHandle<Overflow>,
}
impl BackpressurePolicy {
pub fn new(
ctx: &Context,
dimension: BoundDim,
high_water: u64,
low_water: u64,
overflow: Overflow,
) -> Self {
Self {
dimension: ctx.cell(dimension),
high_water: ctx.cell(high_water),
low_water: ctx.cell(low_water),
overflow: ctx.cell(overflow),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RelayConfigError {
ConflateNotBounding,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IngressOutcome {
Accepted,
Conflated,
Dropped,
Blocked,
}
pub struct RelayCell<T, M> {
head: CellHandle<Option<T>>,
pending: CellHandle<u64>,
policy: BackpressurePolicy,
depth: SlotHandle<u64>,
is_full: SlotHandle<bool>,
is_empty: SlotHandle<bool>,
_marker: PhantomData<M>,
}
impl<T, M> RelayCell<T, M>
where
T: Clone + PartialEq + 'static,
M: MergePolicy<T>,
{
pub fn new(ctx: &Context, policy: BackpressurePolicy) -> Result<Self, RelayConfigError> {
if policy.overflow.get(ctx) == Overflow::Conflate && !M::CONFLATES {
return Err(RelayConfigError::ConflateNotBounding);
}
let head: CellHandle<Option<T>> = ctx.cell(None);
let pending = ctx.cell(0u64);
let depth = ctx.computed(move |c| pending.get(c));
let high_water = policy.high_water;
let is_full = ctx.computed(move |c| depth.get(c) >= high_water.get(c));
let is_empty = ctx.computed(move |c| head.get(c).is_none());
Ok(Self {
head,
pending,
policy,
depth,
is_full,
is_empty,
_marker: PhantomData,
})
}
pub fn overflow_is_legal(&self, ctx: &Context) -> bool {
self.policy.overflow.get(ctx) != Overflow::Conflate || M::CONFLATES
}
pub fn depth(&self) -> SlotHandle<u64> {
self.depth
}
pub fn is_full(&self) -> SlotHandle<bool> {
self.is_full
}
pub fn is_empty(&self) -> SlotHandle<bool> {
self.is_empty
}
fn read_pending(&self, ctx: &Context) -> u64 {
self.pending.get(ctx)
}
fn read_full(&self, ctx: &Context) -> bool {
self.read_pending(ctx) >= self.policy.high_water.get(ctx)
}
fn merge_into_head(&self, ctx: &Context, op: T) {
let cur = self.head.get(ctx);
let next = match cur {
None => op,
Some(v) => M::merge(&v, op),
};
self.head.set(ctx, Some(next));
}
pub fn ingress(&self, ctx: &Context, op: T) -> IngressOutcome {
let was_empty = self.pending.get(ctx) == 0;
if self.read_full(ctx) {
match self.policy.overflow.get(ctx) {
Overflow::Block => return IngressOutcome::Blocked,
Overflow::DropNewest => return IngressOutcome::Dropped,
Overflow::DropOldest => {
self.head.set(ctx, Some(op));
self.pending.set(ctx, 1);
return IngressOutcome::Dropped;
}
Overflow::Conflate | Overflow::Spill => {}
}
}
self.merge_into_head(ctx, op);
self.pending.set(ctx, self.pending.get(ctx) + 1);
if was_empty {
IngressOutcome::Accepted
} else {
IngressOutcome::Conflated
}
}
pub fn drain(&self, ctx: &Context) -> Option<T> {
let cur = self.head.get(ctx);
if cur.is_some() {
self.head.set(ctx, None);
self.pending.set(ctx, 0);
}
cur
}
pub fn peek(&self, ctx: &Context) -> Option<T> {
self.head.get(ctx)
}
}