use super::handle::PalaceHandle;
use super::open_sweep::{OPEN_WRITE_BUDGET, run_open_write};
use super::scope::RecallScope;
use super::types::{L1_CAP, RecallResult};
use crate::memory_core::palace::{Drawer, PalaceId};
use crate::memory_core::store::kg::KnowledgeGraph;
use crate::memory_core::store::l1_cache::L1Cache;
use anyhow::Result;
use std::collections::HashSet;
use std::sync::Arc;
use std::sync::atomic::{AtomicU8, Ordering};
use std::time::Duration;
use uuid::Uuid;
pub(super) fn select_l1(drawers: &[Drawer]) -> Vec<Drawer> {
let mut sorted = drawers.to_vec();
sorted.sort_by(super::types::drawer_listing_order);
sorted.truncate(L1_CAP);
sorted
}
pub fn retrieve_l0_l1_scoped(handle: &PalaceHandle, scope: &RecallScope) -> Vec<RecallResult> {
let allowed = match scope {
RecallScope::Wing(_) => scope.allowed_room_ids(&handle.kg),
RecallScope::All | RecallScope::Room(_) => None,
};
super::layers::l0_l1_admitted(handle, &allowed)
}
pub(super) fn gap_fill_needed(authority: &Result<bool>, degraded: bool, palace: &PalaceId) -> bool {
match authority {
Ok(stamped) => !stamped || degraded,
Err(e) => {
tracing::warn!(palace = %palace, "read drawers_authoritative failed, gap-filling: {e:#}");
true
}
}
}
pub(super) fn gap_fill(drawers: &mut Vec<Drawer>, snapshot: &[Drawer]) -> HashSet<Uuid> {
let present: HashSet<Uuid> = drawers.iter().map(|d| d.id).collect();
let mut added = HashSet::new();
for d in snapshot {
if !present.contains(&d.id) && added.insert(d.id) {
drawers.push(d.clone());
}
}
added
}
pub(super) fn maybe_adopt(
kg: &KnowledgeGraph,
authority: &Result<bool>,
maintainer: bool,
degraded: bool,
drawers: &[Drawer],
gap_filled: &HashSet<Uuid>,
palace: &PalaceId,
) -> bool {
if !maintainer || degraded || kg.is_read_only() || !matches!(authority, Ok(false)) {
return false;
}
let rows: Vec<Drawer> = drawers
.iter()
.filter(|d| gap_filled.contains(&d.id) && d.fact_key.is_none())
.cloned()
.collect();
adopt_within(kg, rows, palace, OPEN_WRITE_BUDGET)
}
const PENDING: u8 = 0;
const STARTED: u8 = 1;
const CANCELLED: u8 = 2;
fn claim(gate: &AtomicU8, to: u8) -> bool {
gate.compare_exchange(PENDING, to, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
}
fn adopt_within(
kg: &KnowledgeGraph,
rows: Vec<Drawer>,
palace: &PalaceId,
budget: Duration,
) -> bool {
let store = kg.store();
let worker = Arc::clone(&store);
let n = rows.len();
let gate = Arc::new(AtomicU8::new(PENDING));
let worker_gate = Arc::clone(&gate);
let work = move || worker.adopt_snapshot_drawers(&rows, || claim(&worker_gate, STARTED));
match run_open_write(&store, palace, "adopt_l1_snapshot", budget, work) {
Some(Ok(())) => {
tracing::info!(palace = %palace, adopted = n, "#9722: drawer table is authoritative");
true
}
Some(Err(e)) => {
tracing::warn!(palace = %palace, "adopt L1 snapshot drawers failed: {e:#}");
false
}
None => {
if claim(&gate, CANCELLED) {
tracing::warn!(palace = %palace, "#9722: adoption cancelled; the next open retries");
}
false
}
}
}
impl PalaceHandle {
pub fn refresh_l1(&self) {
let selected = select_l1(&self.drawers.read());
*self.l1_drawers.write() = selected;
}
pub(crate) fn save_and_refresh_l1(&self) -> Result<()> {
self.refresh_l1();
if let Some(data_dir) = self.data_dir.as_ref() {
let snap = self.drawers.read().clone();
L1Cache::save_l1_cache(&snap, data_dir)?;
}
Ok(())
}
}