use std::sync::{
Arc,
atomic::{AtomicU64, Ordering},
};
use super::names::{Name, NameRegistry};
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]>,
names: NameRegistry,
}
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(),
names: NameRegistry::new(),
}
}
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);
self.names.stamp(Name::View, view, stamp);
}
}
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);
self.names.clear();
}
#[cfg(test)]
pub(super) fn name_count(&self) -> usize {
self.names.len()
}
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);
self.names.stamp(Name::FeedView, view, stamp);
}
}
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);
self.names.stamp(Name::WriteView, view, stamp);
}
}
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);
self.names.stamp(Name::ByIdType, type_name, stamp);
}
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);
self.names.stamp(Name::CollectionType, child_type, stamp);
}
pub(super) fn stamp_collection(&self, child_type: &str, column: &str, parent: &str) {
let stamp = self.next();
self.row_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))
}
pub(super) fn counters(&self, reach: &Reach<'_>) -> Box<[Arc<AtomicU64>]> {
let views = reach
.views
.iter()
.flat_map(|v| [Name::View, Name::FeedView, Name::WriteView].map(|kind| (kind, *v)));
let collected_views = reach
.collected_views
.iter()
.flat_map(|v| [Name::View, Name::WriteView].map(|kind| (kind, *v)));
let by_id = reach.by_id.iter().flat_map(|((t, _), views)| {
views.iter().map(|v| (Name::View, *v)).chain([(Name::ByIdType, *t)])
});
let collections = reach.collections.iter().map(|(t, _, _)| (Name::CollectionType, *t));
let mut names: Vec<(Name, &str)> =
views.chain(collected_views).chain(by_id).chain(collections).collect();
names.sort_unstable();
names.dedup();
names
.into_iter()
.map(|(kind, name)| {
self.names
.counter(kind, name, || self.slot(domain(kind), &[name]).load(Ordering::SeqCst))
})
.collect()
}
pub(super) fn current(&self, snapshot: u64, counters: &[Arc<AtomicU64>]) -> bool {
self.everything.load(Ordering::SeqCst) <= snapshot
&& counters.iter().all(|counter| counter.load(Ordering::SeqCst) <= snapshot)
}
fn reaches(&self, snapshot: u64, reach: &Reach<'_>, read: impl Fn(&AtomicU64) -> u64) -> bool {
let moved = |domain: u8, parts: &[&str]| read(self.row_slot(domain, parts)) > snapshot;
read(&self.everything) > snapshot
|| reach
.by_id
.as_ref()
.is_some_and(|(row, _)| moved(BY_ID, &<[&str; 2]>::from(*row)))
|| reach.entities.iter().any(|&row| moved(ENTITY, &<[&str; 2]>::from(row)))
|| reach
.collections
.iter()
.any(|&member| moved(COLLECTION_MEMBER, &<[&str; 3]>::from(member)))
}
fn next(&self) -> u64 {
self.sequence.fetch_add(1, Ordering::SeqCst) + 1
}
fn row_slot(&self, domain: u8, parts: &[&str]) -> &AtomicU64 {
#[allow(clippy::cast_possible_truncation)] let index = (hash(domain, parts) & (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]
}
}
const fn domain(kind: Name) -> u8 {
match kind {
Name::View => VIEW,
Name::FeedView => FEED_VIEW,
Name::WriteView => WRITE_VIEW,
Name::ByIdType => BY_ID_TYPE,
Name::CollectionType => COLLECTION_TYPE,
}
}
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
}