microscope_memory/
recall_ctx.rs1use std::path::{Path, PathBuf};
30use std::sync::{Mutex, OnceLock};
31
32use crate::config::Config;
33use crate::reader::MicroscopeReader;
34
35pub struct RecallContext {
37 pub reader: MicroscopeReader,
38 pub attention: crate::attention::AttentionState,
39 pub hebb: crate::hebbian::HebbianState,
40 pub thought_graph: crate::thought_graph::ThoughtGraphState,
41 pub pred_cache: crate::predictive_cache::PredictiveCache,
42 pub emotion_21d: crate::emotional_21d::EmotionalState21D,
43 pub mirror: crate::mirror::MirrorState,
44 pub resonance: crate::resonance::ResonanceState,
45 pub archetypes: crate::archetype::ArchetypeState,
46 pub temporal: crate::temporal_archetype::TemporalArchetypeState,
47 pub eureka_log: crate::eureka::EurekaLog,
48 pub spaced: crate::spaced_repetition::SpacedRepetition,
49 pub narrative: crate::narrative::NarrativeState,
50 pub esr: crate::emotional_state::EmotionalStateRing,
51 pub self_model: crate::self_model::SelfModel,
52 pub curiosity: crate::curiosity::CuriosityState,
53 pub narrative_memory: crate::narrative_memory::NarrativeMemory,
54 pub monologue: crate::inner_monologue::MonologueState,
55}
56
57impl RecallContext {
58 pub fn open(config: &Config) -> Result<Self, String> {
61 let reader = MicroscopeReader::open(config)?;
62 let output_dir = Path::new(&config.paths.output_dir);
63 let emotion_file = output_dir.join("emotion_21d.bin");
64 Ok(Self {
65 attention: crate::attention::AttentionState::load_or_init(output_dir),
66 hebb: crate::hebbian::HebbianState::load_or_init(output_dir, reader.block_count),
67 thought_graph: crate::thought_graph::ThoughtGraphState::load_or_init(output_dir),
68 pred_cache: crate::predictive_cache::PredictiveCache::load_or_init(output_dir),
69 emotion_21d: crate::emotional_21d::EmotionalState21D::load_or_init(&emotion_file),
70 mirror: crate::mirror::MirrorState::load_or_init(output_dir),
71 resonance: crate::resonance::ResonanceState::load_or_init(output_dir),
72 archetypes: crate::archetype::ArchetypeState::load_or_init(output_dir),
73 temporal: crate::temporal_archetype::TemporalArchetypeState::load_or_init(output_dir),
74 eureka_log: crate::eureka::EurekaLog::load_or_init(output_dir),
75 spaced: crate::spaced_repetition::SpacedRepetition::load_or_init(output_dir),
76 narrative: crate::narrative::NarrativeState::load_or_init(output_dir),
77 esr: crate::emotional_state::EmotionalStateRing::load_or_init(output_dir),
78 self_model: crate::self_model::SelfModel::load_or_init(output_dir),
79 curiosity: crate::curiosity::CuriosityState::load_or_init(output_dir),
80 narrative_memory: crate::narrative_memory::NarrativeMemory::load_or_init(output_dir),
81 monologue: crate::inner_monologue::MonologueState::load_or_init(output_dir),
82 reader,
83 })
84 }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91struct Generation {
92 meta_len: u64,
93 meta_mtime_secs: u64,
94 meta_mtime_nanos: u32,
95 block_count: usize,
96}
97
98impl Generation {
99 fn capture(output_dir: &Path, block_count: usize) -> Option<Self> {
100 let md = std::fs::metadata(output_dir.join("meta.bin")).ok()?;
101 let mtime = md.modified().ok()?;
102 let dur = mtime
103 .duration_since(std::time::UNIX_EPOCH)
104 .unwrap_or_default();
105 Some(Self {
106 meta_len: md.len(),
107 meta_mtime_secs: dur.as_secs(),
108 meta_mtime_nanos: dur.subsec_nanos(),
109 block_count,
110 })
111 }
112
113 fn is_current(&self, output_dir: &Path, block_count: usize) -> bool {
114 if self.block_count != block_count {
115 return false;
116 }
117 match Self::capture(output_dir, block_count) {
118 Some(now) => now == *self,
119 None => false,
121 }
122 }
123}
124
125pub struct SharedContext {
127 ctx: Option<RecallContext>,
128 generation: Option<Generation>,
129 output_dir: PathBuf,
130}
131
132impl SharedContext {
133 pub fn new(output_dir: PathBuf) -> Self {
134 Self {
135 ctx: None,
136 generation: None,
137 output_dir,
138 }
139 }
140
141 pub fn get(&mut self, config: &Config) -> Result<&mut RecallContext, String> {
147 let output_dir = Path::new(&config.paths.output_dir);
148 let need_reopen = match (&self.ctx, &self.generation) {
149 (Some(ctx), Some(gen)) => !gen.is_current(output_dir, ctx.reader.block_count),
150 _ => true,
151 };
152 if need_reopen {
153 let ctx = RecallContext::open(config)?;
154 let gen = Generation::capture(output_dir, ctx.reader.block_count);
155 self.ctx = Some(ctx);
156 self.generation = gen;
157 self.output_dir = output_dir.to_path_buf();
158 }
159 self.ctx
160 .as_mut()
161 .ok_or_else(|| "shared recall context failed to open".to_string())
162 }
163
164 pub fn invalidate(&mut self) {
170 self.ctx = None;
171 self.generation = None;
172 }
173}
174
175static GLOBAL: OnceLock<Mutex<SharedContext>> = OnceLock::new();
182
183pub fn with_shared<R>(
189 config: &Config,
190 f: impl FnOnce(&mut RecallContext) -> R,
191) -> Result<R, String> {
192 let cell = GLOBAL
193 .get_or_init(|| Mutex::new(SharedContext::new(PathBuf::from(&config.paths.output_dir))));
194 let mut shared = cell.lock().map_err(|e| format!("shared ctx lock: {e}"))?;
195 if shared.output_dir != Path::new(&config.paths.output_dir) {
198 shared.invalidate();
199 }
200 let ctx = shared.get(config)?;
201 Ok(f(ctx))
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
209 fn generation_roundtrip() {
210 let dir = tempfile::tempdir().unwrap();
211 let meta = dir.path().join("meta.bin");
212 std::fs::write(&meta, vec![0u8; 64]).unwrap();
213 let g = Generation::capture(dir.path(), 10).unwrap();
214 assert!(g.is_current(dir.path(), 10));
215 assert!(!g.is_current(dir.path(), 11));
216 std::thread::sleep(std::time::Duration::from_millis(5));
218 std::fs::write(&meta, vec![0u8; 64]).unwrap();
219 std::fs::write(&meta, vec![0u8; 65]).unwrap();
221 assert!(!g.is_current(dir.path(), 10));
222 }
223
224 #[test]
225 fn shared_context_opens_and_reopens() {
226 let dir = tempfile::tempdir().unwrap();
229 let mut cfg = crate::config::Config::default();
230 cfg.paths.output_dir = dir.path().to_string_lossy().to_string();
231 cfg.paths.layers_dir = dir.path().to_string_lossy().to_string();
232 cfg.paths.temp_dir = dir.path().to_string_lossy().to_string();
233
234 let mut shared = SharedContext::new(dir.path().to_path_buf());
235 assert!(shared.get(&cfg).is_err());
236 assert!(shared.get(&cfg).is_err());
238 shared.invalidate();
239 assert!(shared.get(&cfg).is_err());
240 }
241}