use std::cmp::Reverse;
use std::collections::HashMap;
use slotmap::SlotMap;
use super::BucketStreamId;
use super::StreamKey;
use super::StreamMetadata;
use super::StreamSlot;
use super::TtlEntry;
use super::TtlIndex;
use super::stream_expiry_at_ms;
use super::stream_is_expired;
#[derive(Debug, Clone, Default)]
pub(super) struct StreamRegistry {
keys: HashMap<BucketStreamId, StreamKey>,
slots: SlotMap<StreamKey, StreamSlot>,
ttl: TtlIndex,
}
impl StreamRegistry {
pub(super) fn key(&self, stream_id: &BucketStreamId) -> Option<StreamKey> {
self.keys.get(stream_id).copied()
}
pub(super) fn slot(&self, stream_id: &BucketStreamId) -> Option<&StreamSlot> {
let key = self.key(stream_id)?;
self.slots.get(key)
}
pub(super) fn slot_mut(&mut self, stream_id: &BucketStreamId) -> Option<&mut StreamSlot> {
let key = self.key(stream_id)?;
self.slots.get_mut(key)
}
pub(super) fn metadata(&self, stream_id: &BucketStreamId) -> Option<&StreamMetadata> {
self.slot(stream_id).map(|slot| &slot.metadata)
}
pub(super) fn metadata_mut(
&mut self,
stream_id: &BucketStreamId,
) -> Option<&mut StreamMetadata> {
self.slot_mut(stream_id).map(|slot| &mut slot.metadata)
}
pub(super) fn is_empty(&self) -> bool {
self.keys.is_empty()
}
pub(super) fn stream_ids(&self) -> impl Iterator<Item = &BucketStreamId> {
self.keys.keys()
}
pub(super) fn slots(&self) -> impl Iterator<Item = &StreamSlot> {
self.slots.values()
}
pub(super) fn contains_key(&self, stream_id: &BucketStreamId) -> bool {
self.keys.contains_key(stream_id)
}
pub(super) fn insert(&mut self, slot: StreamSlot) -> Option<StreamKey> {
let stream_id = slot.metadata.stream_id.clone();
if self.keys.contains_key(&stream_id) {
return None;
}
let key = self.slots.insert(slot);
self.keys.insert(stream_id.clone(), key);
self.push_ttl_entry(&stream_id, key);
Some(key)
}
pub(super) fn remove(&mut self, stream_id: &BucketStreamId) -> Option<StreamSlot> {
let key = self.keys.remove(stream_id)?;
self.slots.remove(key)
}
pub(super) fn refresh_ttl(&mut self, stream_id: &BucketStreamId) {
if let Some(key) = self.key(stream_id) {
self.push_ttl_entry(stream_id, key);
}
}
pub(super) fn pop_expired(&mut self, now_ms: u64) -> Option<BucketStreamId> {
loop {
let Reverse(entry) = self.ttl.entries.peek().cloned()?;
if entry.expires_at_ms > now_ms {
return None;
}
self.ttl.entries.pop();
if self.key(&entry.stream_id) != Some(entry.key) {
continue;
}
let Some(slot) = self.slots.get(entry.key) else {
continue;
};
if stream_expiry_at_ms(&slot.metadata) != Some(entry.expires_at_ms) {
continue;
}
if !stream_is_expired(&slot.metadata, now_ms) {
continue;
}
return Some(entry.stream_id);
}
}
fn push_ttl_entry(&mut self, stream_id: &BucketStreamId, key: StreamKey) {
let Some(slot) = self.slots.get(key) else {
return;
};
let Some(expires_at_ms) = stream_expiry_at_ms(&slot.metadata) else {
return;
};
self.ttl.entries.push(Reverse(TtlEntry {
expires_at_ms,
stream_id: stream_id.clone(),
key,
}));
}
}