use serde::Serialize;
use std::{
collections::BTreeMap,
fmt,
sync::{Arc, RwLock},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
#[serde(transparent)]
pub struct SourceRevision(u64);
impl SourceRevision {
pub const fn get(self) -> u64 {
self.0
}
}
impl fmt::Display for SourceRevision {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(formatter)
}
}
#[derive(Debug, Clone)]
pub struct SourceEntry {
revision: SourceRevision,
text: Arc<str>,
}
impl SourceEntry {
pub const fn revision(&self) -> SourceRevision {
self.revision
}
pub fn text(&self) -> &str {
&self.text
}
pub fn shared_text(&self) -> Arc<str> {
self.text.clone()
}
}
pub trait SourceProvider {
fn populate_source_cache(&self, cache: &SourceCache);
fn source_cache(&self) -> SourceCache {
let cache = SourceCache::new();
self.populate_source_cache(&cache);
cache
}
}
#[derive(Debug, Clone, Default)]
pub struct SourceSnapshot {
inner: Arc<BTreeMap<String, SourceEntry>>,
}
impl SourceSnapshot {
pub fn get(&self, name: &str) -> Option<Arc<str>> {
self.inner.get(name).map(SourceEntry::shared_text)
}
pub fn entry(&self, name: &str) -> Option<SourceEntry> {
self.inner.get(name).cloned()
}
pub fn revision(&self, name: &str) -> Option<SourceRevision> {
self.inner.get(name).map(SourceEntry::revision)
}
pub fn is_current(&self, cache: &SourceCache, name: &str) -> bool {
match self.revision(name) {
Some(revision) => cache.revision(name) == Some(revision),
None => false,
}
}
pub fn is_stale(&self, cache: &SourceCache, name: &str) -> bool {
self.contains(name) && !self.is_current(cache, name)
}
pub fn contains(&self, name: &str) -> bool {
self.inner.contains_key(name)
}
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn names(&self) -> Vec<String> {
self.inner.keys().cloned().collect()
}
}
#[derive(Debug, Default)]
struct SourceCacheState {
entries: BTreeMap<String, SourceEntry>,
revisions: BTreeMap<String, u64>,
}
impl SourceCacheState {
fn next_revision(&mut self, name: &str) -> SourceRevision {
let revision = self.revisions.entry(name.to_owned()).or_insert(0);
let Some(next_revision) = revision.checked_add(1) else {
panic!("diagprint source revision space exhausted; refusing to reuse revisions");
};
*revision = next_revision;
SourceRevision(*revision)
}
}
#[derive(Debug, Clone, Default)]
pub struct SourceCache {
inner: Arc<RwLock<SourceCacheState>>,
}
impl SourceCache {
pub fn new() -> Self {
Self::default()
}
pub fn snapshot(&self) -> SourceSnapshot {
SourceSnapshot {
inner: Arc::new(self.read().entries.clone()),
}
}
pub fn insert(&self, name: impl Into<String>, source: impl Into<String>) -> Option<Arc<str>> {
let name = name.into();
let source: Arc<str> = Arc::from(source.into());
let mut state = self.write();
let revision = state.next_revision(&name);
state
.entries
.insert(
name,
SourceEntry {
revision,
text: source,
},
)
.map(|entry| entry.text)
}
pub fn insert_revisioned(
&self,
name: impl Into<String>,
source: impl Into<String>,
) -> SourceRevision {
let name = name.into();
let source: Arc<str> = Arc::from(source.into());
let mut state = self.write();
let revision = state.next_revision(&name);
state.entries.insert(
name,
SourceEntry {
revision,
text: source,
},
);
revision
}
pub fn get(&self, name: &str) -> Option<Arc<str>> {
self.read().entries.get(name).map(SourceEntry::shared_text)
}
pub fn entry(&self, name: &str) -> Option<SourceEntry> {
self.read().entries.get(name).cloned()
}
pub fn revision(&self, name: &str) -> Option<SourceRevision> {
self.read().entries.get(name).map(SourceEntry::revision)
}
pub fn is_revision_current(&self, name: &str, revision: SourceRevision) -> bool {
self.revision(name) == Some(revision)
}
pub fn contains(&self, name: &str) -> bool {
self.read().entries.contains_key(name)
}
pub fn remove(&self, name: &str) -> Option<Arc<str>> {
self.write().entries.remove(name).map(|entry| entry.text)
}
pub fn clear(&self) {
self.write().entries.clear();
}
pub fn len(&self) -> usize {
self.read().entries.len()
}
pub fn is_empty(&self) -> bool {
self.read().entries.is_empty()
}
pub fn names(&self) -> Vec<String> {
self.read().entries.keys().cloned().collect()
}
fn read(&self) -> std::sync::RwLockReadGuard<'_, SourceCacheState> {
self.inner
.read()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
fn write(&self) -> std::sync::RwLockWriteGuard<'_, SourceCacheState> {
self.inner
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
}