1use std::cell::UnsafeCell;
46use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
47use std::sync::RwLock;
48
49pub const SNAPSHOT_MAGIC: u32 = 0x534E_4F43;
51pub const SNAPSHOT_VERSION: u32 = 1;
53pub const SNAPSHOT_MAX_RETRIES: u32 = 8;
55pub const SNAPSHOT_FILE_NAME: &str = "consciousness_snapshot.bin";
57
58#[repr(C)]
66pub struct SharedSnapshot {
67 pub sequence: AtomicU64,
69 pub magic: u32,
71 pub version: u32,
73 _pad: [u32; 2],
75
76 data: UnsafeCell<SnapshotData>,
80
81 cached_format: RwLock<String>,
85 pub hot_cycle: AtomicU64,
87 pub hot_surprise_bits: AtomicU32,
89 pub hot_curiosity_bits: AtomicU32,
91 pub hot_predicted_hash: AtomicU64,
93}
94
95#[repr(C)]
97#[derive(Clone, Copy, Debug)]
98pub struct SnapshotData {
99 pub cycle: u64,
100 pub last_query_ms: u64,
101 pub activations_count: u32,
102 pub activations_total_energy: f64,
103 pub attention_layers: u32,
104 pub resonance_cells: u32,
105 pub patterns_crystallized: u32,
106 pub predictions_count: u32,
107 pub predictions_hit_rate: f32,
108 pub archetypes_count: u32,
109 pub mirror_echoes: u32,
110 pub predicted_query_hash: u64,
111 pub predicted_confidence: f32,
112 pub surprise_level: f32,
113 pub curiosity_level: f32,
114 pub emo_intensity: f32,
115 pub emo_dominant_idx: i32,
116 pub emo_dominant_val: f32,
117 _pad: [u8; 8],
118}
119
120impl SnapshotData {
121 pub const fn zeroed() -> Self {
122 Self {
123 cycle: 0,
124 last_query_ms: 0,
125 activations_count: 0,
126 activations_total_energy: 0.0,
127 attention_layers: 0,
128 resonance_cells: 0,
129 patterns_crystallized: 0,
130 predictions_count: 0,
131 predictions_hit_rate: 0.0,
132 archetypes_count: 0,
133 mirror_echoes: 0,
134 predicted_query_hash: 0,
135 predicted_confidence: 0.0,
136 surprise_level: 0.0,
137 curiosity_level: 0.0,
138 emo_intensity: 0.0,
139 emo_dominant_idx: -1,
140 emo_dominant_val: 0.0,
141 _pad: [0; 8],
142 }
143 }
144}
145
146unsafe impl Sync for SharedSnapshot {}
152unsafe impl Send for SharedSnapshot {}
153
154impl std::fmt::Debug for SharedSnapshot {
155 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156 f.debug_struct("SharedSnapshot")
157 .field("sequence", &self.sequence.load(Ordering::Relaxed))
158 .field("magic", &self.magic)
159 .field("version", &self.version)
160 .finish()
161 }
162}
163
164impl SharedSnapshot {
165 pub fn new_zeroed() -> Self {
167 Self {
168 sequence: AtomicU64::new(0),
169 magic: SNAPSHOT_MAGIC,
170 version: SNAPSHOT_VERSION,
171 _pad: [0; 2],
172 data: UnsafeCell::new(SnapshotData::zeroed()),
173 cached_format: RwLock::new(String::new()),
174 hot_cycle: AtomicU64::new(0),
175 hot_surprise_bits: AtomicU32::new(0),
176 hot_curiosity_bits: AtomicU32::new(0),
177 hot_predicted_hash: AtomicU64::new(0),
178 }
179 }
180
181 pub fn begin_write(&self) -> u64 {
185 let s = self.sequence.fetch_add(1, Ordering::AcqRel);
186 std::sync::atomic::fence(Ordering::Release);
187 s + 1
188 }
189
190 pub fn end_write(&self, expected: u64) {
193 std::sync::atomic::fence(Ordering::Release);
194 self.sequence.store(expected + 1, Ordering::Release);
195 }
196
197 #[allow(clippy::mut_from_ref)]
206 pub unsafe fn data_mut(&self) -> &mut SnapshotData {
207 &mut *self.data.get()
208 }
209
210 pub fn read_cached_format(&self) -> String {
215 match self.cached_format.read() {
216 Ok(guard) => guard.clone(),
217 Err(_) => "🧠 Consciousness Stream — (cache poisoned)".to_string(),
218 }
219 }
220
221 pub fn read_hot_fields(&self) -> (u64, f32, f32, u64) {
224 let cycle = self.hot_cycle.load(Ordering::Relaxed);
225 let surprise = f32::from_bits(self.hot_surprise_bits.load(Ordering::Relaxed));
226 let curiosity = f32::from_bits(self.hot_curiosity_bits.load(Ordering::Relaxed));
227 let hash = self.hot_predicted_hash.load(Ordering::Relaxed);
228 (cycle, surprise, curiosity, hash)
229 }
230
231 pub fn is_fresh(&self, expected_cycle: u64) -> bool {
233 self.hot_cycle.load(Ordering::Relaxed) >= expected_cycle
234 }
235
236 pub fn set_cached_format(&self, s: String) {
238 if let Ok(mut guard) = self.cached_format.write() {
239 *guard = s;
240 }
241 }
242
243 pub fn set_hot_fields(&self, cycle: u64, surprise: f32, curiosity: f32, predicted_hash: u64) {
245 self.hot_cycle.store(cycle, Ordering::Relaxed);
246 self.hot_surprise_bits
247 .store(surprise.to_bits(), Ordering::Relaxed);
248 self.hot_curiosity_bits
249 .store(curiosity.to_bits(), Ordering::Relaxed);
250 self.hot_predicted_hash
251 .store(predicted_hash, Ordering::Relaxed);
252 }
253
254 pub fn read(&self) -> Option<SnapshotData> {
257 for _ in 0..SNAPSHOT_MAX_RETRIES {
258 let s1 = self.sequence.load(Ordering::Acquire);
259 if s1 & 1 == 1 {
260 std::hint::spin_loop();
261 continue;
262 }
263 std::sync::atomic::fence(Ordering::Acquire);
264 let data = unsafe { *self.data.get() };
269 std::sync::atomic::fence(Ordering::Acquire);
270 let s2 = self.sequence.load(Ordering::Acquire);
271 if s1 == s2 && self.magic == SNAPSHOT_MAGIC {
272 return Some(data);
273 }
274 }
275 None
276 }
277}
278
279#[cfg(not(target_arch = "wasm32"))]
287#[repr(C)]
288pub struct MmapSnapshot {
289 sequence: AtomicU64,
290 magic: u32,
291 version: u32,
292 _pad: [u32; 2],
293 data: UnsafeCell<SnapshotData>,
294}
295
296#[cfg(not(target_arch = "wasm32"))]
300unsafe impl Sync for MmapSnapshot {}
301#[cfg(not(target_arch = "wasm32"))]
302unsafe impl Send for MmapSnapshot {}
303
304#[cfg(not(target_arch = "wasm32"))]
305impl MmapSnapshot {
306 pub const FILE_SIZE: usize = size_of::<MmapSnapshot>();
308
309 fn header_valid(&self) -> bool {
311 self.magic == SNAPSHOT_MAGIC && self.version == SNAPSHOT_VERSION
312 }
313
314 pub fn write(&self, data: &SnapshotData) -> Result<(), String> {
316 if !self.header_valid() {
317 return Err("snapshot file is not initialized (wrong magic/version)".to_string());
318 }
319 let s = self.sequence.fetch_add(1, Ordering::AcqRel); std::sync::atomic::fence(Ordering::Release);
321 unsafe {
323 *self.data.get() = *data;
324 }
325 std::sync::atomic::fence(Ordering::Release);
326 self.sequence.store(s + 2, Ordering::Release); Ok(())
328 }
329
330 pub fn begin_write(&self) -> Result<u64, String> {
332 if !self.header_valid() {
333 return Err("snapshot file is not initialized (wrong magic/version)".to_string());
334 }
335 let s = self.sequence.fetch_add(1, Ordering::AcqRel);
336 std::sync::atomic::fence(Ordering::Release);
337 Ok(s + 1)
338 }
339
340 pub fn end_write(&self, expected: u64) {
342 std::sync::atomic::fence(Ordering::Release);
343 self.sequence.store(expected + 1, Ordering::Release);
344 }
345
346 #[allow(clippy::mut_from_ref)]
351 pub unsafe fn data_mut(&self) -> &mut SnapshotData {
352 &mut *self.data.get()
353 }
354
355 pub fn read(&self) -> Option<SnapshotData> {
358 if !self.header_valid() {
359 return None;
360 }
361 for _ in 0..SNAPSHOT_MAX_RETRIES {
362 let s1 = self.sequence.load(Ordering::Acquire);
363 if s1 & 1 == 1 {
364 std::hint::spin_loop();
365 continue;
366 }
367 std::sync::atomic::fence(Ordering::Acquire);
368 let data = unsafe { *self.data.get() };
371 std::sync::atomic::fence(Ordering::Acquire);
372 let s2 = self.sequence.load(Ordering::Acquire);
373 if s1 == s2 {
374 return Some(data);
375 }
376 }
377 None
378 }
379
380 fn from_raw(ptr: *mut u8) -> &'static MmapSnapshot {
381 unsafe { &*(ptr as *const MmapSnapshot) }
385 }
386}
387
388#[cfg(not(target_arch = "wasm32"))]
390pub struct MappedSnapshot {
391 _file: std::fs::File,
392 _mmap: memmap2::Mmap,
393 shared: &'static MmapSnapshot,
394}
395
396#[cfg(not(target_arch = "wasm32"))]
397impl MappedSnapshot {
398 pub fn open(path: &std::path::Path) -> Result<Self, String> {
400 let file = std::fs::File::open(path).map_err(|e| format!("open snapshot file: {e}"))?;
401 if file.metadata().map_err(|e| e.to_string())?.len() < MmapSnapshot::FILE_SIZE as u64 {
402 return Err(format!(
403 "snapshot file too small ({} bytes, need {})",
404 file.metadata().map_err(|e| e.to_string())?.len(),
405 MmapSnapshot::FILE_SIZE
406 ));
407 }
408 let mmap =
409 unsafe { memmap2::Mmap::map(&file) }.map_err(|e| format!("mmap snapshot file: {e}"))?;
410 let shared = MmapSnapshot::from_raw(mmap.as_ptr() as *mut u8);
411 Ok(Self {
412 _file: file,
413 _mmap: mmap,
414 shared,
415 })
416 }
417
418 pub fn read(&self) -> Option<SnapshotData> {
420 self.shared.read()
421 }
422
423 pub fn is_initialized(&self) -> bool {
425 self.shared.header_valid()
426 }
427}
428
429#[cfg(not(target_arch = "wasm32"))]
431pub struct MappedSnapshotWriter {
432 _file: std::fs::File,
433 _mmap: memmap2::MmapMut,
434 shared: &'static MmapSnapshot,
435}
436
437#[cfg(not(target_arch = "wasm32"))]
438impl MappedSnapshotWriter {
439 pub fn create(path: &std::path::Path) -> Result<Self, String> {
441 let file = std::fs::OpenOptions::new()
442 .read(true)
443 .write(true)
444 .create(true)
445 .truncate(false)
446 .open(path)
447 .map_err(|e| format!("create snapshot file: {e}"))?;
448 file.set_len(MmapSnapshot::FILE_SIZE as u64)
449 .map_err(|e| format!("size snapshot file: {e}"))?;
450 let mut mmap = unsafe { memmap2::MmapMut::map_mut(&file) }
451 .map_err(|e| format!("mmap snapshot file: {e}"))?;
452 let raw = mmap.as_mut_ptr() as *mut MmapSnapshot;
453 unsafe {
456 (*raw).magic = SNAPSHOT_MAGIC;
457 (*raw).version = SNAPSHOT_VERSION;
458 (*raw).sequence.store(0, Ordering::Release);
459 }
460 let shared = MmapSnapshot::from_raw(mmap.as_mut_ptr());
461 Ok(Self {
462 _file: file,
463 _mmap: mmap,
464 shared,
465 })
466 }
467
468 pub fn open_or_create(path: &std::path::Path) -> Result<Self, String> {
477 let existing = path.exists();
478 let file = std::fs::OpenOptions::new()
479 .read(true)
480 .write(true)
481 .create(true)
482 .truncate(false)
483 .open(path)
484 .map_err(|e| format!("open snapshot file: {e}"))?;
485
486 if !existing {
487 file.set_len(MmapSnapshot::FILE_SIZE as u64)
488 .map_err(|e| format!("size snapshot file: {e}"))?;
489 let mut mmap = unsafe { memmap2::MmapMut::map_mut(&file) }
490 .map_err(|e| format!("mmap snapshot file: {e}"))?;
491 let raw = mmap.as_mut_ptr() as *mut MmapSnapshot;
492 unsafe {
495 (*raw).magic = SNAPSHOT_MAGIC;
496 (*raw).version = SNAPSHOT_VERSION;
497 (*raw).sequence.store(0, Ordering::Release);
498 }
499 let shared = MmapSnapshot::from_raw(mmap.as_mut_ptr());
500 Ok(Self {
501 _file: file,
502 _mmap: mmap,
503 shared,
504 })
505 } else {
506 if file.metadata().map_err(|e| e.to_string())?.len() < MmapSnapshot::FILE_SIZE as u64 {
507 return Err("snapshot file too small for a shared snapshot".to_string());
508 }
509 let mut mmap = unsafe { memmap2::MmapMut::map_mut(&file) }
510 .map_err(|e| format!("mmap snapshot file: {e}"))?;
511 let shared = MmapSnapshot::from_raw(mmap.as_mut_ptr());
512 if !shared.header_valid() {
513 return Err("snapshot file exists but is not initialized".to_string());
514 }
515 Ok(Self {
516 _file: file,
517 _mmap: mmap,
518 shared,
519 })
520 }
521 }
522
523 pub fn open(path: &std::path::Path) -> Result<Self, String> {
525 let file = std::fs::OpenOptions::new()
526 .read(true)
527 .write(true)
528 .open(path)
529 .map_err(|e| format!("open snapshot file: {e}"))?;
530 if file.metadata().map_err(|e| e.to_string())?.len() < MmapSnapshot::FILE_SIZE as u64 {
531 return Err("snapshot file too small for a shared snapshot".to_string());
532 }
533 let mut mmap = unsafe { memmap2::MmapMut::map_mut(&file) }
534 .map_err(|e| format!("mmap snapshot file: {e}"))?;
535 let shared = MmapSnapshot::from_raw(mmap.as_mut_ptr());
536 if !shared.header_valid() {
537 return Err("snapshot file is not initialized (wrong magic/version)".to_string());
538 }
539 Ok(Self {
540 _file: file,
541 _mmap: mmap,
542 shared,
543 })
544 }
545
546 pub fn write(&self, data: &SnapshotData) -> Result<(), String> {
548 self.shared.write(data)?;
549 let _ = self._mmap.flush();
550 Ok(())
551 }
552
553 pub fn begin_write(&self) -> Result<u64, String> {
555 self.shared.begin_write()
556 }
557
558 pub fn end_write(&self, expected: u64) {
560 self.shared.end_write(expected);
561 }
562
563 #[allow(clippy::mut_from_ref)]
568 pub unsafe fn data_mut(&self) -> &mut SnapshotData {
569 self.shared.data_mut()
570 }
571}
572
573#[cfg(test)]
574mod tests {
575 use super::*;
576
577 #[test]
578 fn seqlock_basic_roundtrip() {
579 let s = SharedSnapshot::new_zeroed();
580 let token = s.begin_write();
581 s.end_write(token);
585 assert!(s.read().is_some());
586 }
587
588 #[test]
589 fn seqlock_detects_in_progress_write() {
590 let s = SharedSnapshot::new_zeroed();
593 let token = s.begin_write();
594 assert_eq!(s.sequence.load(Ordering::Acquire) & 1, 1);
597 s.end_write(token);
598 assert_eq!(s.sequence.load(Ordering::Acquire) & 1, 0);
599 }
600
601 #[cfg(not(target_arch = "wasm32"))]
602 #[test]
603 fn mmap_snapshot_roundtrip() {
604 let tmp = tempfile::tempdir().unwrap();
605 let path = tmp.path().join("snapshot.bin");
606
607 let writer = MappedSnapshotWriter::create(&path).unwrap();
608 let mut data = SnapshotData::zeroed();
609 data.cycle = 42;
610 data.activations_count = 7;
611 data.surprise_level = 0.25;
612 writer.write(&data).unwrap();
613
614 let reader = MappedSnapshot::open(&path).unwrap();
615 assert!(reader.is_initialized());
616 let read = reader.read().expect("published snapshot must be readable");
617 assert_eq!(read.cycle, 42);
618 assert_eq!(read.activations_count, 7);
619 assert_eq!(read.surprise_level, 0.25);
620 }
621
622 #[cfg(not(target_arch = "wasm32"))]
623 #[test]
624 fn mmap_snapshot_uninitialized_file_reads_none() {
625 let tmp = tempfile::tempdir().unwrap();
626 let path = tmp.path().join("empty.bin");
627 std::fs::write(&path, vec![0u8; MmapSnapshot::FILE_SIZE]).unwrap();
628
629 let reader = MappedSnapshot::open(&path).unwrap();
630 assert!(!reader.is_initialized());
631 assert!(
632 reader.read().is_none(),
633 "uninitialized file must not yield data"
634 );
635 }
636
637 #[cfg(not(target_arch = "wasm32"))]
638 #[test]
639 fn mmap_snapshot_open_or_create_is_non_destructive() {
640 let tmp = tempfile::tempdir().unwrap();
641 let path = tmp.path().join("live.bin");
642
643 let writer = MappedSnapshotWriter::open_or_create(&path).unwrap();
645 let mut data = SnapshotData::zeroed();
646 data.cycle = 1;
647 writer.write(&data).unwrap();
648 drop(writer);
649
650 let writer2 = MappedSnapshotWriter::open_or_create(&path).unwrap();
652 let mut data2 = SnapshotData::zeroed();
653 data2.cycle = 2;
654 writer2.write(&data2).unwrap();
655
656 let reader = MappedSnapshot::open(&path).unwrap();
657 assert_eq!(reader.read().unwrap().cycle, 2);
658 }
659
660 #[cfg(not(target_arch = "wasm32"))]
661 #[test]
662 fn mmap_snapshot_no_torn_reads_under_concurrency() {
663 let tmp = tempfile::tempdir().unwrap();
664 let path = tmp.path().join("live.bin");
665 let writer = MappedSnapshotWriter::create(&path).unwrap();
666
667 let writer_handle = std::thread::spawn(move || {
668 for cycle in 1..=200u64 {
669 let mut data = SnapshotData::zeroed();
670 data.cycle = cycle;
671 data.activations_count = (cycle * 7) as u32;
673 writer.write(&data).unwrap();
674 }
675 });
676
677 let reader = MappedSnapshot::open(&path).unwrap();
678 let mut last_seen = 0u64;
679 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
680 while last_seen < 200 && std::time::Instant::now() < deadline {
681 if let Some(data) = reader.read() {
682 assert_eq!(
684 data.activations_count,
685 (data.cycle * 7) as u32,
686 "torn read observed: cycle={} count={}",
687 data.cycle,
688 data.activations_count
689 );
690 last_seen = last_seen.max(data.cycle);
691 }
692 }
693 writer_handle.join().unwrap();
694 assert_eq!(last_seen, 200, "reader must observe the final snapshot");
695 }
696}