#![allow(clippy::cast_possible_truncation)]
use parking_lot::RwLock;
use std::collections::BTreeMap;
#[derive(Debug, Clone)]
pub struct TemporalEntry {
pub id: u64,
pub timestamp: i64,
}
pub struct TemporalIndex {
by_timestamp: RwLock<BTreeMap<i64, Vec<u64>>>,
id_to_timestamp: RwLock<std::collections::HashMap<u64, i64>>,
}
impl Default for TemporalIndex {
fn default() -> Self {
Self::new()
}
}
impl TemporalIndex {
#[must_use]
pub fn new() -> Self {
Self {
by_timestamp: RwLock::new(BTreeMap::new()),
id_to_timestamp: RwLock::new(std::collections::HashMap::new()),
}
}
pub fn insert(&self, id: u64, timestamp: i64) {
let mut id_to_ts = self.id_to_timestamp.write();
let mut by_ts = self.by_timestamp.write();
if let Some(&old_ts) = id_to_ts.get(&id) {
if old_ts == timestamp {
return;
}
if let Some(ids) = by_ts.get_mut(&old_ts) {
ids.retain(|&x| x != id);
if ids.is_empty() {
by_ts.remove(&old_ts);
}
}
}
id_to_ts.insert(id, timestamp);
by_ts.entry(timestamp).or_default().push(id);
}
pub fn remove(&self, id: u64) {
let mut id_to_ts = self.id_to_timestamp.write();
let mut by_ts = self.by_timestamp.write();
if let Some(ts) = id_to_ts.remove(&id) {
if let Some(ids) = by_ts.get_mut(&ts) {
ids.retain(|&x| x != id);
if ids.is_empty() {
by_ts.remove(&ts);
}
}
}
}
#[must_use]
pub fn range(&self, start: i64, end: i64) -> Vec<TemporalEntry> {
let by_ts = self.by_timestamp.read();
let mut results = Vec::new();
for (&ts, ids) in by_ts.range(start..=end) {
for &id in ids {
results.push(TemporalEntry { id, timestamp: ts });
}
}
results
}
#[must_use]
pub fn recent(&self, limit: usize, since_timestamp: Option<i64>) -> Vec<TemporalEntry> {
let by_ts = self.by_timestamp.read();
let mut results = Vec::with_capacity(limit);
for (&ts, ids) in by_ts.iter().rev() {
if let Some(since) = since_timestamp {
if ts <= since {
continue;
}
}
for &id in ids.iter().rev() {
results.push(TemporalEntry { id, timestamp: ts });
if results.len() >= limit {
return results;
}
}
}
results
}
#[must_use]
pub fn older_than(&self, before_timestamp: i64, limit: usize) -> Vec<TemporalEntry> {
let by_ts = self.by_timestamp.read();
let mut results = Vec::with_capacity(limit);
for (&ts, ids) in by_ts.iter() {
if ts >= before_timestamp {
break;
}
for &id in ids {
results.push(TemporalEntry { id, timestamp: ts });
if results.len() >= limit {
return results;
}
}
}
results
}
#[must_use]
pub fn get_timestamp(&self, id: u64) -> Option<i64> {
self.id_to_timestamp.read().get(&id).copied()
}
#[must_use]
pub fn len(&self) -> usize {
self.id_to_timestamp.read().len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.id_to_timestamp.read().is_empty()
}
pub fn clear(&self) {
let mut id_to_ts = self.id_to_timestamp.write();
let mut by_ts = self.by_timestamp.write();
by_ts.clear();
id_to_ts.clear();
}
#[must_use]
pub fn all_ids(&self) -> Vec<u64> {
self.id_to_timestamp.read().keys().copied().collect()
}
#[must_use]
pub fn serialize(&self) -> Vec<u8> {
let id_to_ts = self.id_to_timestamp.read();
let count = id_to_ts.len();
let mut buf = Vec::with_capacity(8 + count * 16);
buf.extend_from_slice(&(count as u64).to_le_bytes());
for (&id, &ts) in id_to_ts.iter() {
buf.extend_from_slice(&id.to_le_bytes());
buf.extend_from_slice(&ts.to_le_bytes());
}
buf
}
#[must_use]
pub fn deserialize(data: &[u8]) -> Option<Self> {
if data.len() < 8 {
return None;
}
let count = u64::from_le_bytes(data[0..8].try_into().ok()?) as usize;
let expected_len = 8 + count * 16;
if data.len() != expected_len {
return None;
}
let index = Self::new();
for i in 0..count {
let offset = 8 + i * 16;
let id = u64::from_le_bytes(data[offset..offset + 8].try_into().ok()?);
let ts = i64::from_le_bytes(data[offset + 8..offset + 16].try_into().ok()?);
index.insert(id, ts);
}
Some(index)
}
pub fn rebuild_from_entries(&self, entries: impl IntoIterator<Item = (u64, i64)>) {
self.clear();
for (id, timestamp) in entries {
self.insert(id, timestamp);
}
}
}
#[derive(Debug, Clone, Default)]
pub struct TemporalIndexStats {
pub entry_count: usize,
pub unique_timestamps: usize,
pub min_timestamp: Option<i64>,
pub max_timestamp: Option<i64>,
}
impl TemporalIndex {
#[must_use]
pub fn stats(&self) -> TemporalIndexStats {
let by_ts = self.by_timestamp.read();
let id_to_ts = self.id_to_timestamp.read();
TemporalIndexStats {
entry_count: id_to_ts.len(),
unique_timestamps: by_ts.len(),
min_timestamp: by_ts.keys().next().copied(),
max_timestamp: by_ts.keys().next_back().copied(),
}
}
}