use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use crate::abi::InstanceId;
use crate::state::instance::InstanceSnapshot;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KernelSnapshot {
pub(crate) instances: BTreeMap<InstanceId, InstanceSnapshot>,
pub(crate) next_instance_id: u64,
}
impl KernelSnapshot {
pub fn serialize(&self) -> Result<Vec<u8>, SnapshotError> {
postcard::to_allocvec(self).map_err(|e| SnapshotError::SerializeFailed(format!("{}", e)))
}
pub fn deserialize(bytes: &[u8]) -> Result<Self, SnapshotError> {
postcard::from_bytes(bytes).map_err(|e| SnapshotError::DeserializeFailed(format!("{}", e)))
}
pub fn instance_count(&self) -> usize {
self.instances.len()
}
pub fn instance_ids(&self) -> impl Iterator<Item = InstanceId> + '_ {
self.instances.keys().copied()
}
#[doc(hidden)]
pub fn __construct(
instances: BTreeMap<InstanceId, InstanceSnapshot>,
next_instance_id: u64,
) -> Self {
Self {
instances,
next_instance_id,
}
}
#[doc(hidden)]
pub fn __into_parts(self) -> (BTreeMap<InstanceId, InstanceSnapshot>, u64) {
(self.instances, self.next_instance_id)
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SnapshotError {
SerializeFailed(String),
DeserializeFailed(String),
}
impl core::fmt::Display for SnapshotError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::SerializeFailed(m) => write!(f, "snapshot serialize failed: {}", m),
Self::DeserializeFailed(m) => write!(f, "snapshot deserialize failed: {}", m),
}
}
}
impl std::error::Error for SnapshotError {}
#[cfg(test)]
mod tests {
use super::*;
use crate::abi::{CapabilityMask, EntityId, Principal, Tick, TypeCode};
use crate::state::traits::_sealed::Sealed;
use crate::state::{ActionCompute, ActionContext, ActionDeriv, InstanceConfig, Op};
use crate::Kernel;
use bytes::Bytes;
use serde::{Deserialize as De, Serialize as Ser};
#[derive(Ser, De)]
struct SpawnSetAction {
id: u64,
}
impl Sealed for SpawnSetAction {}
impl ActionDeriv for SpawnSetAction {
const TYPE_CODE: TypeCode = TypeCode(900);
const SCHEMA_VERSION: u32 = 1;
}
impl ActionCompute for SpawnSetAction {
fn compute(&self, _ctx: &ActionContext) -> Vec<Op> {
let entity = EntityId::new(self.id).unwrap();
vec![
Op::SpawnEntity {
id: entity,
owner: Principal::System,
},
Op::SetComponent {
entity,
type_code: TypeCode(7),
bytes: Bytes::from(vec![0xCDu8; 4]),
size: 4,
},
]
}
}
fn submit(k: &mut Kernel, inst: InstanceId, id: u64) {
use crate::state::Action;
let bytes = Action::canonical_bytes(&SpawnSetAction { id });
k.submit(
inst,
Principal::System,
None,
Tick(0),
SpawnSetAction::TYPE_CODE,
bytes,
)
.unwrap();
}
fn boot_with_state(actions: &[u64]) -> (Kernel, InstanceId) {
let mut k = Kernel::new();
k.register_action::<SpawnSetAction>();
let inst = k.create_instance(InstanceConfig::default());
for id in actions {
submit(&mut k, inst, *id);
let _ = k.step(Tick(0), CapabilityMask::SYSTEM);
}
(k, inst)
}
#[test]
fn snapshot_empty_kernel_serdes_roundtrip() {
let k = Kernel::new();
let snap = k.snapshot();
assert_eq!(snap.instance_count(), 0);
let bytes = snap.serialize().unwrap();
let back = KernelSnapshot::deserialize(&bytes).unwrap();
assert_eq!(back.instance_count(), 0);
}
#[test]
fn snapshot_captures_instance_state() {
let (k, inst) = boot_with_state(&[1, 2, 3]);
let snap = k.snapshot();
assert_eq!(snap.instance_count(), 1);
let ids: Vec<InstanceId> = snap.instance_ids().collect();
assert_eq!(ids, vec![inst]);
}
#[test]
fn snapshot_preserves_entities_and_components() {
let (k1, inst) = boot_with_state(&[1, 2]);
let snap = k1.snapshot();
let bytes = snap.serialize().unwrap();
let snap2 = KernelSnapshot::deserialize(&bytes).unwrap();
let mut k2 = Kernel::from_snapshot(snap2);
let v1 = k1.instance_view(inst).unwrap();
let v2 = k2.instance_view(inst).unwrap();
assert_eq!(v1.entity_count(), v2.entity_count());
assert_eq!(v1.component_count(), v2.component_count());
assert_eq!(
v1.component(EntityId::new(1).unwrap(), TypeCode(7)),
v2.component(EntityId::new(1).unwrap(), TypeCode(7)),
);
assert_eq!(
v1.component(EntityId::new(2).unwrap(), TypeCode(7)),
v2.component(EntityId::new(2).unwrap(), TypeCode(7)),
);
k2.register_action::<SpawnSetAction>();
submit(&mut k2, inst, 3);
let _ = k2.step(Tick(0), CapabilityMask::SYSTEM);
assert_eq!(k2.instance_view(inst).unwrap().entity_count(), 3);
}
#[test]
fn snapshot_preserves_id_counters() {
let mut k1 = Kernel::new();
let _ = k1.create_instance(InstanceConfig::default());
let _ = k1.create_instance(InstanceConfig::default());
let _ = k1.create_instance(InstanceConfig::default()); let snap = k1.snapshot();
let bytes = snap.serialize().unwrap();
let snap2 = KernelSnapshot::deserialize(&bytes).unwrap();
let mut k2 = Kernel::from_snapshot(snap2);
let next1 = k1.create_instance(InstanceConfig::default());
let next2 = k2.create_instance(InstanceConfig::default());
assert_eq!(next1, next2);
assert_eq!(next1.get(), 4);
}
#[test]
fn snapshot_preserves_local_tick_and_wall_remainder() {
let (k1, inst) = boot_with_state(&[1]);
let snap = k1.snapshot();
let bytes = snap.serialize().unwrap();
let k2 = Kernel::from_snapshot(KernelSnapshot::deserialize(&bytes).unwrap());
assert_eq!(
k1.instance_view(inst).unwrap().local_tick(),
k2.instance_view(inst).unwrap().local_tick(),
);
}
#[test]
fn snapshot_deserialize_fresh_kernel_no_observers_no_registry() {
let (k1, inst) = boot_with_state(&[1]);
let snap = k1.snapshot();
let mut k2 =
Kernel::from_snapshot(KernelSnapshot::deserialize(&snap.serialize().unwrap()).unwrap());
assert_eq!(k2.stats().observer_count, 0);
k2.submit(
inst,
Principal::System,
None,
Tick(0),
TypeCode(900),
vec![1u8],
)
.unwrap();
let report = k2.step(Tick(0), CapabilityMask::SYSTEM);
assert_eq!(report.actions_executed, 1);
assert_eq!(report.effects_applied, 0);
}
#[test]
fn snapshot_deterministic_same_state_same_bytes() {
let (k1, _) = boot_with_state(&[1, 2, 3]);
let (k2, _) = boot_with_state(&[1, 2, 3]);
let b1 = k1.snapshot().serialize().unwrap();
let b2 = k2.snapshot().serialize().unwrap();
assert_eq!(
b1, b2,
"identical state must produce identical snapshot bytes"
);
}
}