use std::sync::atomic::{AtomicU64, Ordering};
const SLOTS: usize = 1024;
const ROW_SLOTS: usize = 65_536;
const VIEW: u8 = b'v';
const FEED_VIEW: u8 = b'f';
const ENTITY: u8 = b'e';
const COLLECTION_TYPE: u8 = b'c';
const COLLECTION_MEMBER: u8 = b'm';
const WRITE_VIEW: u8 = b'w';
const BY_ID: u8 = b'i';
const BY_ID_TYPE: u8 = b't';
#[derive(Default)]
pub(super) struct Reach<'a> {
pub(super) views: Vec<&'a str>,
pub(super) collected_views: Vec<&'a str>,
pub(super) entities: Vec<(&'a str, &'a str)>,
pub(super) collections: Vec<(&'a str, &'a str, &'a str)>,
pub(super) by_id: Option<((&'a str, &'a str), Vec<&'a str>)>,
}
pub(super) struct InvalidationFence {
sequence: AtomicU64,
everything: AtomicU64,
slots: Box<[AtomicU64]>,
rows: Box<[AtomicU64]>,
}
impl InvalidationFence {
pub(super) fn new() -> Self {
Self {
sequence: AtomicU64::new(0),
everything: AtomicU64::new(0),
slots: (0..SLOTS).map(|_| AtomicU64::new(0)).collect(),
rows: (0..ROW_SLOTS).map(|_| AtomicU64::new(0)).collect(),
}
}
pub(super) fn snapshot(&self) -> u64 {
self.sequence.load(Ordering::SeqCst)
}
pub(super) fn stamp_views<'a>(&self, views: impl IntoIterator<Item = &'a str>) {
let stamp = self.next();
for view in views {
self.slot(VIEW, &[view]).fetch_max(stamp, Ordering::SeqCst);
}
}
pub(super) fn stamp_entity(&self, entity_type: &str, id: &str) {
let stamp = self.next();
self.row_slot(ENTITY, entity_type, id).fetch_max(stamp, Ordering::SeqCst);
}
pub(super) fn stamp_everything(&self) {
let stamp = self.next();
self.everything.fetch_max(stamp, Ordering::SeqCst);
}
pub(super) fn stamp_feed_views<'a>(&self, views: impl IntoIterator<Item = &'a str>) {
let stamp = self.next();
for view in views {
self.slot(FEED_VIEW, &[view]).fetch_max(stamp, Ordering::SeqCst);
}
}
pub(super) fn stamp_write_views<'a>(&self, views: impl IntoIterator<Item = &'a str>) {
let stamp = self.next();
for view in views {
self.slot(WRITE_VIEW, &[view]).fetch_max(stamp, Ordering::SeqCst);
}
}
pub(super) fn stamp_by_id(&self, type_name: &str, id: &str) {
let stamp = self.next();
self.row_slot(BY_ID, type_name, id).fetch_max(stamp, Ordering::SeqCst);
}
pub(super) fn stamp_by_id_type(&self, type_name: &str) {
let stamp = self.next();
self.slot(BY_ID_TYPE, &[type_name]).fetch_max(stamp, Ordering::SeqCst);
}
pub(super) fn stamp_collection_type(&self, child_type: &str) {
let stamp = self.next();
self.slot(COLLECTION_TYPE, &[child_type]).fetch_max(stamp, Ordering::SeqCst);
}
pub(super) fn stamp_collection(&self, child_type: &str, column: &str, parent: &str) {
let stamp = self.next();
self.slot(COLLECTION_MEMBER, &[child_type, column, parent])
.fetch_max(stamp, Ordering::SeqCst);
}
pub(super) fn moved_since(&self, snapshot: u64, reach: &Reach<'_>) -> bool {
self.reaches(snapshot, reach, |slot| slot.load(Ordering::SeqCst))
}
pub(super) fn moved_since_inserted(&self, snapshot: u64, reach: &Reach<'_>) -> bool {
self.reaches(snapshot, reach, |slot| slot.fetch_add(0, Ordering::SeqCst))
}
fn reaches(&self, snapshot: u64, reach: &Reach<'_>, read: impl Fn(&AtomicU64) -> u64) -> bool {
let moved = |domain: u8, parts: &[&str]| read(self.slot(domain, parts)) > snapshot;
read(&self.everything) > snapshot
|| reach
.views
.iter()
.any(|v| moved(VIEW, &[v]) || moved(FEED_VIEW, &[v]) || moved(WRITE_VIEW, &[v]))
|| reach
.collected_views
.iter()
.any(|v| moved(VIEW, &[v]) || moved(WRITE_VIEW, &[v]))
|| reach.by_id.as_ref().is_some_and(|((t, id), views)| {
read(self.row_slot(BY_ID, t, id)) > snapshot
|| moved(BY_ID_TYPE, &[t])
|| views.iter().any(|v| moved(VIEW, &[v]))
})
|| reach
.entities
.iter()
.any(|(t, id)| read(self.row_slot(ENTITY, t, id)) > snapshot)
|| reach.collections.iter().any(|(t, c, p)| {
moved(COLLECTION_TYPE, &[t]) || moved(COLLECTION_MEMBER, &[t, c, p])
})
}
fn next(&self) -> u64 {
self.sequence.fetch_add(1, Ordering::SeqCst) + 1
}
fn row_slot(&self, domain: u8, type_name: &str, id: &str) -> &AtomicU64 {
#[allow(clippy::cast_possible_truncation)] let index = (hash(domain, &[type_name, id]) & (ROW_SLOTS as u64 - 1)) as usize;
&self.rows[index]
}
fn slot(&self, domain: u8, parts: &[&str]) -> &AtomicU64 {
#[allow(clippy::cast_possible_truncation)] let index = (hash(domain, parts) & (SLOTS as u64 - 1)) as usize;
&self.slots[index]
}
}
fn hash(domain: u8, parts: &[&str]) -> u64 {
let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
let bytes = parts
.iter()
.enumerate()
.flat_map(|(i, part)| (i > 0).then_some(0u8).into_iter().chain(part.bytes()));
for byte in std::iter::once(domain).chain(bytes) {
hash ^= u64::from(byte);
hash = hash.wrapping_mul(0x0100_0000_01b3);
}
hash
}