1use crate::components::{
12 AccessControl, CarCall, DestinationQueue, Elevator, HallCall, Line, Patience, Position,
13 Preferences, Rider, Route, Stop, Velocity,
14};
15use crate::entity::EntityId;
16use crate::ids::GroupId;
17use crate::metrics::Metrics;
18use crate::stop::StopId;
19use crate::tagged_metrics::MetricTags;
20use serde::{Deserialize, Serialize};
21use std::collections::{BTreeMap, HashMap, HashSet};
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct EntitySnapshot {
26 pub original_id: EntityId,
28 pub position: Option<Position>,
30 pub velocity: Option<Velocity>,
32 pub elevator: Option<Elevator>,
34 pub stop: Option<Stop>,
36 pub rider: Option<Rider>,
38 pub route: Option<Route>,
40 #[serde(default)]
42 pub line: Option<Line>,
43 pub patience: Option<Patience>,
45 pub preferences: Option<Preferences>,
47 #[serde(default)]
49 pub access_control: Option<AccessControl>,
50 pub disabled: bool,
52 #[cfg(feature = "energy")]
54 #[serde(default)]
55 pub energy_profile: Option<crate::energy::EnergyProfile>,
56 #[cfg(feature = "energy")]
58 #[serde(default)]
59 pub energy_metrics: Option<crate::energy::EnergyMetrics>,
60 #[serde(default)]
62 pub service_mode: Option<crate::components::ServiceMode>,
63 #[serde(default)]
65 pub destination_queue: Option<DestinationQueue>,
66 #[serde(default)]
68 pub car_calls: Vec<CarCall>,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct WorldSnapshot {
91 pub tick: u64,
93 pub dt: f64,
95 pub entities: Vec<EntitySnapshot>,
98 pub groups: Vec<GroupSnapshot>,
100 pub stop_lookup: BTreeMap<StopId, usize>,
103 pub metrics: Metrics,
105 pub metric_tags: MetricTags,
107 pub extensions: BTreeMap<String, BTreeMap<EntityId, String>>,
110 pub ticks_per_second: f64,
112 #[serde(default)]
114 pub hall_calls: Vec<HallCall>,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct LineSnapshotInfo {
120 pub entity_index: usize,
122 pub elevator_indices: Vec<usize>,
124 pub stop_indices: Vec<usize>,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct GroupSnapshot {
131 pub id: GroupId,
133 pub name: String,
135 pub elevator_indices: Vec<usize>,
137 pub stop_indices: Vec<usize>,
139 pub strategy: crate::dispatch::BuiltinStrategy,
141 #[serde(default)]
143 pub lines: Vec<LineSnapshotInfo>,
144 #[serde(default)]
146 pub reposition: Option<crate::dispatch::BuiltinReposition>,
147 #[serde(default)]
149 pub hall_call_mode: crate::dispatch::HallCallMode,
150 #[serde(default)]
152 pub ack_latency_ticks: u32,
153}
154
155pub(crate) struct PendingExtensions(pub(crate) BTreeMap<String, BTreeMap<EntityId, String>>);
161
162type CustomStrategyFactory<'a> =
164 Option<&'a dyn Fn(&str) -> Option<Box<dyn crate::dispatch::DispatchStrategy>>>;
165
166impl WorldSnapshot {
167 pub fn restore(
181 self,
182 custom_strategy_factory: CustomStrategyFactory<'_>,
183 ) -> Result<crate::sim::Simulation, crate::error::SimError> {
184 use crate::world::{SortedStops, World};
185
186 let mut world = World::new();
187
188 let (index_to_id, id_remap) = Self::spawn_entities(&mut world, &self.entities);
190
191 Self::attach_components(&mut world, &self.entities, &index_to_id, &id_remap);
193
194 self.attach_hall_calls(&mut world, &id_remap);
196
197 let mut sorted: Vec<(f64, EntityId)> = world
199 .iter_stops()
200 .map(|(eid, stop)| (stop.position, eid))
201 .collect();
202 sorted.sort_by(|a, b| a.0.total_cmp(&b.0));
203 world.insert_resource(SortedStops(sorted));
204
205 let (mut groups, stop_lookup, dispatchers, strategy_ids) =
207 self.rebuild_groups_and_dispatchers(&index_to_id, custom_strategy_factory)?;
208
209 for group in &mut groups {
212 let group_id = group.id();
213 let lines = group.lines_mut();
214 for line_info in lines.iter_mut() {
215 if line_info.entity() != EntityId::default() {
216 continue;
217 }
218 let (min_pos, max_pos) = line_info
220 .serves()
221 .iter()
222 .filter_map(|&sid| world.stop(sid).map(|s| s.position))
223 .fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), p| {
224 (lo.min(p), hi.max(p))
225 });
226 let line_eid = world.spawn();
227 world.set_line(
228 line_eid,
229 Line {
230 name: format!("Legacy-{group_id}"),
231 group: group_id,
232 orientation: crate::components::Orientation::Vertical,
233 position: None,
234 min_position: if min_pos.is_finite() { min_pos } else { 0.0 },
235 max_position: if max_pos.is_finite() { max_pos } else { 0.0 },
236 max_cars: None,
237 },
238 );
239 for &elev_eid in line_info.elevators() {
241 if let Some(car) = world.elevator_mut(elev_eid) {
242 car.line = line_eid;
243 }
244 }
245 line_info.set_entity(line_eid);
246 }
247 }
248
249 let remapped_exts = Self::remap_extensions(&self.extensions, &id_remap);
251 world.insert_resource(PendingExtensions(remapped_exts));
252
253 let mut tags = self.metric_tags;
255 tags.remap_entity_ids(&id_remap);
256 world.insert_resource(tags);
257
258 let mut sim = crate::sim::Simulation::from_parts(
259 world,
260 self.tick,
261 self.dt,
262 groups,
263 stop_lookup,
264 dispatchers,
265 strategy_ids,
266 self.metrics,
267 self.ticks_per_second,
268 );
269
270 for gs in &self.groups {
272 if let Some(ref repo_id) = gs.reposition {
273 if let Some(strategy) = repo_id.instantiate() {
274 sim.set_reposition(gs.id, strategy, repo_id.clone());
275 } else {
276 sim.push_event(crate::events::Event::RepositionStrategyNotRestored {
277 group: gs.id,
278 });
279 }
280 }
281 }
282
283 Self::emit_dangling_warnings(
284 &self.entities,
285 &self.hall_calls,
286 &id_remap,
287 self.tick,
288 &mut sim,
289 );
290
291 Ok(sim)
292 }
293
294 fn spawn_entities(
296 world: &mut crate::world::World,
297 entities: &[EntitySnapshot],
298 ) -> (Vec<EntityId>, HashMap<EntityId, EntityId>) {
299 let mut index_to_id: Vec<EntityId> = Vec::with_capacity(entities.len());
300 let mut id_remap: HashMap<EntityId, EntityId> = HashMap::new();
301 for snap in entities {
302 let new_id = world.spawn();
303 index_to_id.push(new_id);
304 id_remap.insert(snap.original_id, new_id);
305 }
306 (index_to_id, id_remap)
307 }
308
309 fn attach_components(
311 world: &mut crate::world::World,
312 entities: &[EntitySnapshot],
313 index_to_id: &[EntityId],
314 id_remap: &HashMap<EntityId, EntityId>,
315 ) {
316 let remap = |old: EntityId| -> EntityId { id_remap.get(&old).copied().unwrap_or(old) };
317 let remap_opt = |old: Option<EntityId>| -> Option<EntityId> { old.map(&remap) };
318
319 for (i, snap) in entities.iter().enumerate() {
320 let eid = index_to_id[i];
321
322 if let Some(pos) = snap.position {
323 world.set_position(eid, pos);
324 }
325 if let Some(vel) = snap.velocity {
326 world.set_velocity(eid, vel);
327 }
328 if let Some(ref elev) = snap.elevator {
329 let mut e = elev.clone();
330 e.riders = e.riders.iter().map(|&r| remap(r)).collect();
331 e.target_stop = remap_opt(e.target_stop);
332 e.line = remap(e.line);
333 e.restricted_stops = e.restricted_stops.iter().map(|&s| remap(s)).collect();
334 e.phase = match e.phase {
335 crate::components::ElevatorPhase::MovingToStop(s) => {
336 crate::components::ElevatorPhase::MovingToStop(remap(s))
337 }
338 crate::components::ElevatorPhase::Repositioning(s) => {
339 crate::components::ElevatorPhase::Repositioning(remap(s))
340 }
341 other => other,
342 };
343 world.set_elevator(eid, e);
344 }
345 if let Some(ref stop) = snap.stop {
346 world.set_stop(eid, stop.clone());
347 }
348 if let Some(ref rider) = snap.rider {
349 use crate::components::RiderPhase;
350 let mut r = rider.clone();
351 r.current_stop = remap_opt(r.current_stop);
352 r.phase = match r.phase {
353 RiderPhase::Boarding(e) => RiderPhase::Boarding(remap(e)),
354 RiderPhase::Riding(e) => RiderPhase::Riding(remap(e)),
355 RiderPhase::Exiting(e) => RiderPhase::Exiting(remap(e)),
356 other => other,
357 };
358 world.set_rider(eid, r);
359 }
360 if let Some(ref route) = snap.route {
361 let mut rt = route.clone();
362 for leg in &mut rt.legs {
363 leg.from = remap(leg.from);
364 leg.to = remap(leg.to);
365 if let crate::components::TransportMode::Line(ref mut l) = leg.via {
366 *l = remap(*l);
367 }
368 }
369 world.set_route(eid, rt);
370 }
371 if let Some(ref line) = snap.line {
372 world.set_line(eid, line.clone());
373 }
374 if let Some(patience) = snap.patience {
375 world.set_patience(eid, patience);
376 }
377 if let Some(prefs) = snap.preferences {
378 world.set_preferences(eid, prefs);
379 }
380 if let Some(ref ac) = snap.access_control {
381 let remapped =
382 AccessControl::new(ac.allowed_stops().iter().map(|&s| remap(s)).collect());
383 world.set_access_control(eid, remapped);
384 }
385 if snap.disabled {
386 world.disable(eid);
387 }
388 #[cfg(feature = "energy")]
389 if let Some(ref profile) = snap.energy_profile {
390 world.set_energy_profile(eid, profile.clone());
391 }
392 #[cfg(feature = "energy")]
393 if let Some(ref em) = snap.energy_metrics {
394 world.set_energy_metrics(eid, em.clone());
395 }
396 if let Some(mode) = snap.service_mode {
397 world.set_service_mode(eid, mode);
398 }
399 if let Some(ref dq) = snap.destination_queue {
400 use crate::components::DestinationQueue as DQ;
401 let mut new_dq = DQ::new();
402 for &e in dq.queue() {
403 new_dq.push_back(remap(e));
404 }
405 world.set_destination_queue(eid, new_dq);
406 }
407 Self::attach_car_calls(world, eid, &snap.car_calls, id_remap);
408 }
409 }
410
411 fn attach_car_calls(
413 world: &mut crate::world::World,
414 car: EntityId,
415 car_calls: &[CarCall],
416 id_remap: &HashMap<EntityId, EntityId>,
417 ) {
418 if car_calls.is_empty() {
419 return;
420 }
421 let remap = |old: EntityId| -> EntityId { id_remap.get(&old).copied().unwrap_or(old) };
422 let Some(slot) = world.car_calls_mut(car) else {
423 return;
424 };
425 for cc in car_calls {
426 let mut c = cc.clone();
427 c.car = car;
428 c.floor = remap(c.floor);
429 c.pending_riders = c.pending_riders.iter().map(|&r| remap(r)).collect();
430 slot.push(c);
431 }
432 }
433
434 fn attach_hall_calls(
439 &self,
440 world: &mut crate::world::World,
441 id_remap: &HashMap<EntityId, EntityId>,
442 ) {
443 let remap = |old: EntityId| -> EntityId { id_remap.get(&old).copied().unwrap_or(old) };
444 let remap_opt = |old: Option<EntityId>| -> Option<EntityId> { old.map(&remap) };
445 for hc in &self.hall_calls {
446 let mut c = hc.clone();
447 c.stop = remap(c.stop);
448 c.destination = remap_opt(c.destination);
449 c.assigned_car = remap_opt(c.assigned_car);
450 c.pending_riders = c.pending_riders.iter().map(|&r| remap(r)).collect();
451 world.set_hall_call(c);
452 }
453 }
454
455 #[allow(clippy::type_complexity)]
457 fn rebuild_groups_and_dispatchers(
458 &self,
459 index_to_id: &[EntityId],
460 custom_strategy_factory: CustomStrategyFactory<'_>,
461 ) -> Result<
462 (
463 Vec<crate::dispatch::ElevatorGroup>,
464 HashMap<StopId, EntityId>,
465 std::collections::BTreeMap<GroupId, Box<dyn crate::dispatch::DispatchStrategy>>,
466 std::collections::BTreeMap<GroupId, crate::dispatch::BuiltinStrategy>,
467 ),
468 crate::error::SimError,
469 > {
470 use crate::dispatch::ElevatorGroup;
471
472 let groups: Vec<ElevatorGroup> = self
473 .groups
474 .iter()
475 .map(|gs| {
476 let elevator_entities: Vec<EntityId> = gs
477 .elevator_indices
478 .iter()
479 .filter_map(|&i| index_to_id.get(i).copied())
480 .collect();
481 let stop_entities: Vec<EntityId> = gs
482 .stop_indices
483 .iter()
484 .filter_map(|&i| index_to_id.get(i).copied())
485 .collect();
486
487 let lines = if gs.lines.is_empty() {
488 vec![crate::dispatch::LineInfo::new(
491 EntityId::default(),
492 elevator_entities,
493 stop_entities,
494 )]
495 } else {
496 gs.lines
497 .iter()
498 .filter_map(|lsi| {
499 let entity = index_to_id.get(lsi.entity_index).copied()?;
500 Some(crate::dispatch::LineInfo::new(
501 entity,
502 lsi.elevator_indices
503 .iter()
504 .filter_map(|&i| index_to_id.get(i).copied())
505 .collect(),
506 lsi.stop_indices
507 .iter()
508 .filter_map(|&i| index_to_id.get(i).copied())
509 .collect(),
510 ))
511 })
512 .collect()
513 };
514
515 ElevatorGroup::new(gs.id, gs.name.clone(), lines)
516 .with_hall_call_mode(gs.hall_call_mode)
517 .with_ack_latency_ticks(gs.ack_latency_ticks)
518 })
519 .collect();
520
521 let stop_lookup: HashMap<StopId, EntityId> = self
522 .stop_lookup
523 .iter()
524 .filter_map(|(sid, &idx)| index_to_id.get(idx).map(|&eid| (*sid, eid)))
525 .collect();
526
527 let mut dispatchers = std::collections::BTreeMap::new();
528 let mut strategy_ids = std::collections::BTreeMap::new();
529 for (gs, group) in self.groups.iter().zip(groups.iter()) {
530 let strategy: Box<dyn crate::dispatch::DispatchStrategy> =
531 if let Some(builtin) = gs.strategy.instantiate() {
532 builtin
533 } else if let crate::dispatch::BuiltinStrategy::Custom(ref name) = gs.strategy {
534 custom_strategy_factory
535 .and_then(|f| f(name))
536 .ok_or_else(|| crate::error::SimError::UnresolvedCustomStrategy {
537 name: name.clone(),
538 group: group.id(),
539 })?
540 } else {
541 Box::new(crate::dispatch::scan::ScanDispatch::new())
542 };
543 dispatchers.insert(group.id(), strategy);
544 strategy_ids.insert(group.id(), gs.strategy.clone());
545 }
546
547 Ok((groups, stop_lookup, dispatchers, strategy_ids))
548 }
549
550 fn remap_extensions(
552 extensions: &BTreeMap<String, BTreeMap<EntityId, String>>,
553 id_remap: &HashMap<EntityId, EntityId>,
554 ) -> BTreeMap<String, BTreeMap<EntityId, String>> {
555 extensions
556 .iter()
557 .map(|(name, entries)| {
558 let remapped: BTreeMap<EntityId, String> = entries
559 .iter()
560 .map(|(old_id, data)| {
561 let new_id = id_remap.get(old_id).copied().unwrap_or(*old_id);
562 (new_id, data.clone())
563 })
564 .collect();
565 (name.clone(), remapped)
566 })
567 .collect()
568 }
569
570 fn emit_dangling_warnings(
572 entities: &[EntitySnapshot],
573 hall_calls: &[HallCall],
574 id_remap: &HashMap<EntityId, EntityId>,
575 tick: u64,
576 sim: &mut crate::sim::Simulation,
577 ) {
578 let mut seen = HashSet::new();
579 let mut check = |old: EntityId| {
580 if !id_remap.contains_key(&old) && seen.insert(old) {
581 sim.push_event(crate::events::Event::SnapshotDanglingReference {
582 stale_id: old,
583 tick,
584 });
585 }
586 };
587 for snap in entities {
588 Self::collect_referenced_ids(snap, &mut check);
589 }
590 for hc in hall_calls {
591 check(hc.stop);
592 if let Some(car) = hc.assigned_car {
593 check(car);
594 }
595 if let Some(dest) = hc.destination {
596 check(dest);
597 }
598 for &rider in &hc.pending_riders {
599 check(rider);
600 }
601 }
602 }
603
604 fn collect_referenced_ids(snap: &EntitySnapshot, mut visit: impl FnMut(EntityId)) {
606 if let Some(ref elev) = snap.elevator {
607 for &r in &elev.riders {
608 visit(r);
609 }
610 if let Some(t) = elev.target_stop {
611 visit(t);
612 }
613 visit(elev.line);
614 match elev.phase {
615 crate::components::ElevatorPhase::MovingToStop(s)
616 | crate::components::ElevatorPhase::Repositioning(s) => visit(s),
617 _ => {}
618 }
619 for &s in &elev.restricted_stops {
620 visit(s);
621 }
622 }
623 if let Some(ref rider) = snap.rider {
624 if let Some(s) = rider.current_stop {
625 visit(s);
626 }
627 match rider.phase {
628 crate::components::RiderPhase::Boarding(e)
629 | crate::components::RiderPhase::Riding(e)
630 | crate::components::RiderPhase::Exiting(e) => visit(e),
631 _ => {}
632 }
633 }
634 if let Some(ref route) = snap.route {
635 for leg in &route.legs {
636 visit(leg.from);
637 visit(leg.to);
638 if let crate::components::TransportMode::Line(l) = leg.via {
639 visit(l);
640 }
641 }
642 }
643 if let Some(ref ac) = snap.access_control {
644 for &s in ac.allowed_stops() {
645 visit(s);
646 }
647 }
648 if let Some(ref dq) = snap.destination_queue {
649 for &e in dq.queue() {
650 visit(e);
651 }
652 }
653 for cc in &snap.car_calls {
654 visit(cc.floor);
655 for &r in &cc.pending_riders {
656 visit(r);
657 }
658 }
659 }
660}
661
662const SNAPSHOT_MAGIC: [u8; 8] = *b"ELEVSNAP";
664
665#[derive(Debug, Serialize, Deserialize)]
671struct SnapshotEnvelope {
672 magic: [u8; 8],
674 version: String,
676 payload: WorldSnapshot,
678}
679
680impl crate::sim::Simulation {
681 #[must_use]
696 #[allow(clippy::too_many_lines)]
697 pub fn snapshot(&self) -> WorldSnapshot {
698 self.snapshot_inner()
699 }
700
701 pub fn try_snapshot(&self) -> Result<WorldSnapshot, crate::error::SimError> {
711 if self.tick_in_progress {
712 return Err(crate::error::SimError::MidTickSnapshot);
713 }
714 Ok(self.snapshot())
715 }
716
717 #[allow(clippy::too_many_lines)]
721 fn snapshot_inner(&self) -> WorldSnapshot {
722 let world = self.world();
723
724 let all_ids: Vec<EntityId> = world.alive.keys().collect();
726 let id_to_index: HashMap<EntityId, usize> = all_ids
727 .iter()
728 .copied()
729 .enumerate()
730 .map(|(i, e)| (e, i))
731 .collect();
732
733 let entities: Vec<EntitySnapshot> = all_ids
735 .iter()
736 .map(|&eid| EntitySnapshot {
737 original_id: eid,
738 position: world.position(eid).copied(),
739 velocity: world.velocity(eid).copied(),
740 elevator: world.elevator(eid).cloned(),
741 stop: world.stop(eid).cloned(),
742 rider: world.rider(eid).cloned(),
743 route: world.route(eid).cloned(),
744 line: world.line(eid).cloned(),
745 patience: world.patience(eid).copied(),
746 preferences: world.preferences(eid).copied(),
747 access_control: world.access_control(eid).cloned(),
748 disabled: world.is_disabled(eid),
749 #[cfg(feature = "energy")]
750 energy_profile: world.energy_profile(eid).cloned(),
751 #[cfg(feature = "energy")]
752 energy_metrics: world.energy_metrics(eid).cloned(),
753 service_mode: world.service_mode(eid).copied(),
754 destination_queue: world.destination_queue(eid).cloned(),
755 car_calls: world.car_calls(eid).to_vec(),
756 })
757 .collect();
758
759 let groups: Vec<GroupSnapshot> = self
761 .groups()
762 .iter()
763 .map(|g| {
764 let lines: Vec<LineSnapshotInfo> = g
765 .lines()
766 .iter()
767 .filter_map(|li| {
768 let entity_index = id_to_index.get(&li.entity()).copied()?;
769 Some(LineSnapshotInfo {
770 entity_index,
771 elevator_indices: li
772 .elevators()
773 .iter()
774 .filter_map(|eid| id_to_index.get(eid).copied())
775 .collect(),
776 stop_indices: li
777 .serves()
778 .iter()
779 .filter_map(|eid| id_to_index.get(eid).copied())
780 .collect(),
781 })
782 })
783 .collect();
784 GroupSnapshot {
785 id: g.id(),
786 name: g.name().to_owned(),
787 elevator_indices: g
788 .elevator_entities()
789 .iter()
790 .filter_map(|eid| id_to_index.get(eid).copied())
791 .collect(),
792 stop_indices: g
793 .stop_entities()
794 .iter()
795 .filter_map(|eid| id_to_index.get(eid).copied())
796 .collect(),
797 strategy: self
798 .strategy_id(g.id())
799 .cloned()
800 .unwrap_or(crate::dispatch::BuiltinStrategy::Scan),
801 lines,
802 reposition: self.reposition_id(g.id()).cloned(),
803 hall_call_mode: g.hall_call_mode(),
804 ack_latency_ticks: g.ack_latency_ticks(),
805 }
806 })
807 .collect();
808
809 let stop_lookup: BTreeMap<StopId, usize> = self
811 .stop_lookup_iter()
812 .filter_map(|(sid, eid)| id_to_index.get(eid).map(|&idx| (*sid, idx)))
813 .collect();
814
815 WorldSnapshot {
816 tick: self.current_tick(),
817 dt: self.dt(),
818 entities,
819 groups,
820 stop_lookup,
821 metrics: self.metrics().clone(),
822 metric_tags: self
823 .world()
824 .resource::<MetricTags>()
825 .cloned()
826 .unwrap_or_default(),
827 extensions: self.world().serialize_extensions(),
828 ticks_per_second: 1.0 / self.dt(),
829 hall_calls: world.iter_hall_calls().cloned().collect(),
830 }
831 }
832
833 pub fn snapshot_bytes(&self) -> Result<Vec<u8>, crate::error::SimError> {
856 let envelope = SnapshotEnvelope {
857 magic: SNAPSHOT_MAGIC,
858 version: env!("CARGO_PKG_VERSION").to_owned(),
859 payload: self.try_snapshot()?,
860 };
861 postcard::to_allocvec(&envelope)
862 .map_err(|e| crate::error::SimError::SnapshotFormat(e.to_string()))
863 }
864
865 pub fn restore_bytes(
880 bytes: &[u8],
881 custom_strategy_factory: CustomStrategyFactory<'_>,
882 ) -> Result<Self, crate::error::SimError> {
883 let (envelope, tail): (SnapshotEnvelope, &[u8]) = postcard::take_from_bytes(bytes)
884 .map_err(|e| crate::error::SimError::SnapshotFormat(e.to_string()))?;
885 if !tail.is_empty() {
886 return Err(crate::error::SimError::SnapshotFormat(format!(
887 "trailing bytes: {} unread of {}",
888 tail.len(),
889 bytes.len()
890 )));
891 }
892 if envelope.magic != SNAPSHOT_MAGIC {
893 return Err(crate::error::SimError::SnapshotFormat(
894 "magic bytes do not match".to_string(),
895 ));
896 }
897 let current = env!("CARGO_PKG_VERSION");
898 if envelope.version != current {
899 return Err(crate::error::SimError::SnapshotVersion {
900 saved: envelope.version,
901 current: current.to_owned(),
902 });
903 }
904 envelope.payload.restore(custom_strategy_factory)
905 }
906}