use crate::key::LayoutKey;
use crate::plan::{LayoutError, TypeLayout};
use crate::xcdr::{DepCollector, plan};
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use zerodds_ast::{Ast, TypeNode};
use zerodds_ast_core::{Change, Changeset, Id, NodeId};
#[derive(Debug, Clone)]
struct Entry {
layout: TypeLayout,
deps: Vec<NodeId>,
}
#[derive(Debug, Clone, Default)]
pub struct LayoutCache {
entries: BTreeMap<(NodeId, crate::OffsetKey), Entry>,
hits: u64,
misses: u64,
}
impl LayoutCache {
#[must_use]
pub const fn new() -> Self {
Self {
entries: BTreeMap::new(),
hits: 0,
misses: 0,
}
}
pub fn layout(
&mut self,
ast: &Ast,
type_: Id<TypeNode>,
key: LayoutKey,
) -> Result<&TypeLayout, LayoutError> {
let node_id = ast.type_(type_)?.node_id;
let slot = (node_id, key.offset_key());
if let alloc::collections::btree_map::Entry::Vacant(vacant) = self.entries.entry(slot) {
let mut deps = DepCollector::new();
let layout = plan(ast, type_, key, &mut deps)?;
let _ = vacant.insert(Entry {
layout,
deps: deps.into_deps(),
});
self.misses = self.misses.saturating_add(1);
} else {
self.hits = self.hits.saturating_add(1);
}
self.entries
.get(&slot)
.map(|entry| &entry.layout)
.ok_or(LayoutError::Unresolved { node: node_id })
}
pub fn invalidate(&mut self, changeset: &Changeset) {
let touched: Vec<NodeId> = changeset.nodes_touched().collect();
if touched.is_empty() {
return;
}
self.entries.retain(|(node, _), entry| {
!touched.contains(node) && !entry.deps.iter().any(|dep| touched.contains(dep))
});
}
pub fn clear(&mut self) {
self.entries.clear();
}
#[must_use]
pub fn len(&self) -> usize {
self.entries.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[must_use]
pub const fn hits(&self) -> u64 {
self.hits
}
#[must_use]
pub const fn misses(&self) -> u64 {
self.misses
}
#[must_use]
pub fn contains(&self, type_: NodeId, key: LayoutKey) -> bool {
self.entries.contains_key(&(type_, key.offset_key()))
}
}
#[cfg(feature = "std")]
pub struct SharedLayoutCache {
inner: std::sync::Mutex<LayoutCache>,
}
#[cfg(feature = "std")]
impl SharedLayoutCache {
#[must_use]
pub fn new() -> Self {
Self {
inner: std::sync::Mutex::new(LayoutCache::new()),
}
}
pub fn with<R>(&self, f: impl FnOnce(&mut LayoutCache) -> R) -> Option<R> {
self.inner.lock().ok().map(|mut guard| f(&mut guard))
}
#[must_use]
pub fn len(&self) -> usize {
self.with(|cache| cache.len()).unwrap_or(0)
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[cfg(feature = "std")]
impl Default for SharedLayoutCache {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "std")]
impl core::fmt::Debug for SharedLayoutCache {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("SharedLayoutCache")
.field("len", &self.len())
.finish()
}
}
#[cfg(feature = "std")]
impl zerodds_ast_core::AstObserver for SharedLayoutCache {
fn on_commit(&self, changeset: &Changeset) {
let _ = self.with(|cache| cache.invalidate(changeset));
}
}
#[must_use]
pub fn touches_layout(changeset: &Changeset) -> bool {
changeset.iter().any(|change| {
matches!(
change,
Change::Updated { .. } | Change::Deleted { .. } | Change::Created { .. }
)
})
}