use std::path::{Path, PathBuf};
use std::sync::{Mutex, OnceLock};
use crate::config::Config;
use crate::reader::MicroscopeReader;
pub struct RecallContext {
pub reader: MicroscopeReader,
pub attention: crate::attention::AttentionState,
pub hebb: crate::hebbian::HebbianState,
pub thought_graph: crate::thought_graph::ThoughtGraphState,
pub pred_cache: crate::predictive_cache::PredictiveCache,
pub emotion_21d: crate::emotional_21d::EmotionalState21D,
pub mirror: crate::mirror::MirrorState,
pub resonance: crate::resonance::ResonanceState,
pub archetypes: crate::archetype::ArchetypeState,
pub temporal: crate::temporal_archetype::TemporalArchetypeState,
pub eureka_log: crate::eureka::EurekaLog,
pub spaced: crate::spaced_repetition::SpacedRepetition,
pub narrative: crate::narrative::NarrativeState,
pub esr: crate::emotional_state::EmotionalStateRing,
pub self_model: crate::self_model::SelfModel,
pub curiosity: crate::curiosity::CuriosityState,
pub narrative_memory: crate::narrative_memory::NarrativeMemory,
pub monologue: crate::inner_monologue::MonologueState,
}
impl RecallContext {
pub fn open(config: &Config) -> Result<Self, String> {
let reader = MicroscopeReader::open(config)?;
let output_dir = Path::new(&config.paths.output_dir);
let emotion_file = output_dir.join("emotion_21d.bin");
Ok(Self {
attention: crate::attention::AttentionState::load_or_init(output_dir),
hebb: crate::hebbian::HebbianState::load_or_init(output_dir, reader.block_count),
thought_graph: crate::thought_graph::ThoughtGraphState::load_or_init(output_dir),
pred_cache: crate::predictive_cache::PredictiveCache::load_or_init(output_dir),
emotion_21d: crate::emotional_21d::EmotionalState21D::load_or_init(&emotion_file),
mirror: crate::mirror::MirrorState::load_or_init(output_dir),
resonance: crate::resonance::ResonanceState::load_or_init(output_dir),
archetypes: crate::archetype::ArchetypeState::load_or_init(output_dir),
temporal: crate::temporal_archetype::TemporalArchetypeState::load_or_init(output_dir),
eureka_log: crate::eureka::EurekaLog::load_or_init(output_dir),
spaced: crate::spaced_repetition::SpacedRepetition::load_or_init(output_dir),
narrative: crate::narrative::NarrativeState::load_or_init(output_dir),
esr: crate::emotional_state::EmotionalStateRing::load_or_init(output_dir),
self_model: crate::self_model::SelfModel::load_or_init(output_dir),
curiosity: crate::curiosity::CuriosityState::load_or_init(output_dir),
narrative_memory: crate::narrative_memory::NarrativeMemory::load_or_init(output_dir),
monologue: crate::inner_monologue::MonologueState::load_or_init(output_dir),
reader,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Generation {
meta_len: u64,
meta_mtime_secs: u64,
meta_mtime_nanos: u32,
block_count: usize,
}
impl Generation {
fn capture(output_dir: &Path, block_count: usize) -> Option<Self> {
let md = std::fs::metadata(output_dir.join("meta.bin")).ok()?;
let mtime = md.modified().ok()?;
let dur = mtime
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default();
Some(Self {
meta_len: md.len(),
meta_mtime_secs: dur.as_secs(),
meta_mtime_nanos: dur.subsec_nanos(),
block_count,
})
}
fn is_current(&self, output_dir: &Path, block_count: usize) -> bool {
if self.block_count != block_count {
return false;
}
match Self::capture(output_dir, block_count) {
Some(now) => now == *self,
None => false,
}
}
}
pub struct SharedContext {
ctx: Option<RecallContext>,
generation: Option<Generation>,
output_dir: PathBuf,
}
impl SharedContext {
pub fn new(output_dir: PathBuf) -> Self {
Self {
ctx: None,
generation: None,
output_dir,
}
}
pub fn get(&mut self, config: &Config) -> Result<&mut RecallContext, String> {
let output_dir = Path::new(&config.paths.output_dir);
let need_reopen = match (&self.ctx, &self.generation) {
(Some(ctx), Some(gen)) => !gen.is_current(output_dir, ctx.reader.block_count),
_ => true,
};
if need_reopen {
let ctx = RecallContext::open(config)?;
let gen = Generation::capture(output_dir, ctx.reader.block_count);
self.ctx = Some(ctx);
self.generation = gen;
self.output_dir = output_dir.to_path_buf();
}
self.ctx
.as_mut()
.ok_or_else(|| "shared recall context failed to open".to_string())
}
pub fn invalidate(&mut self) {
self.ctx = None;
self.generation = None;
}
}
static GLOBAL: OnceLock<Mutex<SharedContext>> = OnceLock::new();
pub fn with_shared<R>(
config: &Config,
f: impl FnOnce(&mut RecallContext) -> R,
) -> Result<R, String> {
let cell = GLOBAL
.get_or_init(|| Mutex::new(SharedContext::new(PathBuf::from(&config.paths.output_dir))));
let mut shared = cell.lock().map_err(|e| format!("shared ctx lock: {e}"))?;
if shared.output_dir != Path::new(&config.paths.output_dir) {
shared.invalidate();
}
let ctx = shared.get(config)?;
Ok(f(ctx))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generation_roundtrip() {
let dir = tempfile::tempdir().unwrap();
let meta = dir.path().join("meta.bin");
std::fs::write(&meta, vec![0u8; 64]).unwrap();
let g = Generation::capture(dir.path(), 10).unwrap();
assert!(g.is_current(dir.path(), 10));
assert!(!g.is_current(dir.path(), 11));
std::thread::sleep(std::time::Duration::from_millis(5));
std::fs::write(&meta, vec![0u8; 64]).unwrap();
std::fs::write(&meta, vec![0u8; 65]).unwrap();
assert!(!g.is_current(dir.path(), 10));
}
#[test]
fn shared_context_opens_and_reopens() {
let dir = tempfile::tempdir().unwrap();
let mut cfg = crate::config::Config::default();
cfg.paths.output_dir = dir.path().to_string_lossy().to_string();
cfg.paths.layers_dir = dir.path().to_string_lossy().to_string();
cfg.paths.temp_dir = dir.path().to_string_lossy().to_string();
let mut shared = SharedContext::new(dir.path().to_path_buf());
assert!(shared.get(&cfg).is_err());
assert!(shared.get(&cfg).is_err());
shared.invalidate();
assert!(shared.get(&cfg).is_err());
}
}