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 #[serde(default)]
97 pub version: u32,
98 pub tick: u64,
100 pub dt: f64,
102 pub entities: Vec<EntitySnapshot>,
105 pub groups: Vec<GroupSnapshot>,
107 pub stop_lookup: BTreeMap<StopId, usize>,
110 pub metrics: Metrics,
112 pub metric_tags: MetricTags,
114 pub extensions: BTreeMap<String, BTreeMap<EntityId, String>>,
117 pub ticks_per_second: f64,
119 #[serde(default)]
121 pub hall_calls: Vec<HallCall>,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct LineSnapshotInfo {
127 pub entity_index: usize,
129 pub elevator_indices: Vec<usize>,
131 pub stop_indices: Vec<usize>,
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct GroupSnapshot {
138 pub id: GroupId,
140 pub name: String,
142 pub elevator_indices: Vec<usize>,
144 pub stop_indices: Vec<usize>,
146 pub strategy: crate::dispatch::BuiltinStrategy,
148 #[serde(default)]
150 pub lines: Vec<LineSnapshotInfo>,
151 #[serde(default)]
153 pub reposition: Option<crate::dispatch::BuiltinReposition>,
154 #[serde(default)]
156 pub hall_call_mode: crate::dispatch::HallCallMode,
157 #[serde(default)]
159 pub ack_latency_ticks: u32,
160}
161
162pub(crate) struct PendingExtensions(pub(crate) BTreeMap<String, BTreeMap<EntityId, String>>);
168
169type CustomStrategyFactory<'a> =
171 Option<&'a dyn Fn(&str) -> Option<Box<dyn crate::dispatch::DispatchStrategy>>>;
172
173impl WorldSnapshot {
174 pub fn restore(
188 self,
189 custom_strategy_factory: CustomStrategyFactory<'_>,
190 ) -> Result<crate::sim::Simulation, crate::error::SimError> {
191 use crate::world::{SortedStops, World};
192
193 if self.version != SNAPSHOT_SCHEMA_VERSION {
199 return Err(crate::error::SimError::SnapshotVersion {
200 saved: format!("schema {}", self.version),
201 current: format!("schema {SNAPSHOT_SCHEMA_VERSION}"),
202 });
203 }
204
205 let mut world = World::new();
206
207 let (index_to_id, id_remap) = Self::spawn_entities(&mut world, &self.entities);
209
210 Self::attach_components(&mut world, &self.entities, &index_to_id, &id_remap);
212
213 self.attach_hall_calls(&mut world, &id_remap);
215
216 let mut sorted: Vec<(f64, EntityId)> = world
218 .iter_stops()
219 .map(|(eid, stop)| (stop.position, eid))
220 .collect();
221 sorted.sort_by(|a, b| a.0.total_cmp(&b.0));
222 world.insert_resource(SortedStops(sorted));
223
224 let (mut groups, stop_lookup, dispatchers, strategy_ids) =
226 self.rebuild_groups_and_dispatchers(&index_to_id, custom_strategy_factory)?;
227
228 for group in &mut groups {
231 let group_id = group.id();
232 let lines = group.lines_mut();
233 for line_info in lines.iter_mut() {
234 if line_info.entity() != EntityId::default() {
235 continue;
236 }
237 let (min_pos, max_pos) = line_info
239 .serves()
240 .iter()
241 .filter_map(|&sid| world.stop(sid).map(|s| s.position))
242 .fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), p| {
243 (lo.min(p), hi.max(p))
244 });
245 let line_eid = world.spawn();
246 world.set_line(
247 line_eid,
248 Line {
249 name: format!("Legacy-{group_id}"),
250 group: group_id,
251 orientation: crate::components::Orientation::Vertical,
252 position: None,
253 min_position: if min_pos.is_finite() { min_pos } else { 0.0 },
254 max_position: if max_pos.is_finite() { max_pos } else { 0.0 },
255 max_cars: None,
256 },
257 );
258 for &elev_eid in line_info.elevators() {
260 if let Some(car) = world.elevator_mut(elev_eid) {
261 car.line = line_eid;
262 }
263 }
264 line_info.set_entity(line_eid);
265 }
266 }
267
268 let remapped_exts = Self::remap_extensions(&self.extensions, &id_remap);
270 world.insert_resource(PendingExtensions(remapped_exts));
271
272 let mut tags = self.metric_tags;
274 tags.remap_entity_ids(&id_remap);
275 world.insert_resource(tags);
276
277 let mut sim = crate::sim::Simulation::from_parts(
278 world,
279 self.tick,
280 self.dt,
281 groups,
282 stop_lookup,
283 dispatchers,
284 strategy_ids,
285 self.metrics,
286 self.ticks_per_second,
287 );
288
289 for gs in &self.groups {
291 if let Some(ref repo_id) = gs.reposition {
292 if let Some(strategy) = repo_id.instantiate() {
293 sim.set_reposition(gs.id, strategy, repo_id.clone());
294 } else {
295 sim.push_event(crate::events::Event::RepositionStrategyNotRestored {
296 group: gs.id,
297 });
298 }
299 }
300 }
301
302 Self::emit_dangling_warnings(
303 &self.entities,
304 &self.hall_calls,
305 &id_remap,
306 self.tick,
307 &mut sim,
308 );
309
310 Ok(sim)
311 }
312
313 fn spawn_entities(
315 world: &mut crate::world::World,
316 entities: &[EntitySnapshot],
317 ) -> (Vec<EntityId>, HashMap<EntityId, EntityId>) {
318 let mut index_to_id: Vec<EntityId> = Vec::with_capacity(entities.len());
319 let mut id_remap: HashMap<EntityId, EntityId> = HashMap::new();
320 for snap in entities {
321 let new_id = world.spawn();
322 index_to_id.push(new_id);
323 id_remap.insert(snap.original_id, new_id);
324 }
325 (index_to_id, id_remap)
326 }
327
328 fn attach_components(
330 world: &mut crate::world::World,
331 entities: &[EntitySnapshot],
332 index_to_id: &[EntityId],
333 id_remap: &HashMap<EntityId, EntityId>,
334 ) {
335 let remap = |old: EntityId| -> EntityId { id_remap.get(&old).copied().unwrap_or(old) };
336 let remap_opt = |old: Option<EntityId>| -> Option<EntityId> { old.map(&remap) };
337
338 for (i, snap) in entities.iter().enumerate() {
339 let eid = index_to_id[i];
340
341 if let Some(pos) = snap.position {
342 world.set_position(eid, pos);
343 }
344 if let Some(vel) = snap.velocity {
345 world.set_velocity(eid, vel);
346 }
347 if let Some(ref elev) = snap.elevator {
348 let mut e = elev.clone();
349 e.riders = e.riders.iter().map(|&r| remap(r)).collect();
350 e.target_stop = remap_opt(e.target_stop);
351 e.line = remap(e.line);
352 e.restricted_stops = e.restricted_stops.iter().map(|&s| remap(s)).collect();
353 e.phase = match e.phase {
354 crate::components::ElevatorPhase::MovingToStop(s) => {
355 crate::components::ElevatorPhase::MovingToStop(remap(s))
356 }
357 crate::components::ElevatorPhase::Repositioning(s) => {
358 crate::components::ElevatorPhase::Repositioning(remap(s))
359 }
360 other => other,
361 };
362 world.set_elevator(eid, e);
363 }
364 if let Some(ref stop) = snap.stop {
365 world.set_stop(eid, stop.clone());
366 }
367 if let Some(ref rider) = snap.rider {
368 use crate::components::RiderPhase;
369 let mut r = rider.clone();
370 r.current_stop = remap_opt(r.current_stop);
371 r.phase = match r.phase {
372 RiderPhase::Boarding(e) => RiderPhase::Boarding(remap(e)),
373 RiderPhase::Riding(e) => RiderPhase::Riding(remap(e)),
374 RiderPhase::Exiting(e) => RiderPhase::Exiting(remap(e)),
375 other => other,
376 };
377 world.set_rider(eid, r);
378 }
379 if let Some(ref route) = snap.route {
380 let mut rt = route.clone();
381 for leg in &mut rt.legs {
382 leg.from = remap(leg.from);
383 leg.to = remap(leg.to);
384 if let crate::components::TransportMode::Line(ref mut l) = leg.via {
385 *l = remap(*l);
386 }
387 }
388 world.set_route(eid, rt);
389 }
390 if let Some(ref line) = snap.line {
391 world.set_line(eid, line.clone());
392 }
393 if let Some(patience) = snap.patience {
394 world.set_patience(eid, patience);
395 }
396 if let Some(prefs) = snap.preferences {
397 world.set_preferences(eid, prefs);
398 }
399 if let Some(ref ac) = snap.access_control {
400 let remapped =
401 AccessControl::new(ac.allowed_stops().iter().map(|&s| remap(s)).collect());
402 world.set_access_control(eid, remapped);
403 }
404 if snap.disabled {
405 world.disable(eid);
406 }
407 #[cfg(feature = "energy")]
408 if let Some(ref profile) = snap.energy_profile {
409 world.set_energy_profile(eid, profile.clone());
410 }
411 #[cfg(feature = "energy")]
412 if let Some(ref em) = snap.energy_metrics {
413 world.set_energy_metrics(eid, em.clone());
414 }
415 if let Some(mode) = snap.service_mode {
416 world.set_service_mode(eid, mode);
417 }
418 if let Some(ref dq) = snap.destination_queue {
419 use crate::components::DestinationQueue as DQ;
420 let mut new_dq = DQ::new();
421 for &e in dq.queue() {
422 new_dq.push_back(remap(e));
423 }
424 world.set_destination_queue(eid, new_dq);
425 }
426 Self::attach_car_calls(world, eid, &snap.car_calls, id_remap);
427 }
428 }
429
430 fn attach_car_calls(
432 world: &mut crate::world::World,
433 car: EntityId,
434 car_calls: &[CarCall],
435 id_remap: &HashMap<EntityId, EntityId>,
436 ) {
437 if car_calls.is_empty() {
438 return;
439 }
440 let remap = |old: EntityId| -> EntityId { id_remap.get(&old).copied().unwrap_or(old) };
441 let Some(slot) = world.car_calls_mut(car) else {
442 return;
443 };
444 for cc in car_calls {
445 let mut c = cc.clone();
446 c.car = car;
447 c.floor = remap(c.floor);
448 c.pending_riders = c.pending_riders.iter().map(|&r| remap(r)).collect();
449 slot.push(c);
450 }
451 }
452
453 fn attach_hall_calls(
458 &self,
459 world: &mut crate::world::World,
460 id_remap: &HashMap<EntityId, EntityId>,
461 ) {
462 let remap = |old: EntityId| -> EntityId { id_remap.get(&old).copied().unwrap_or(old) };
463 let remap_opt = |old: Option<EntityId>| -> Option<EntityId> { old.map(&remap) };
464 for hc in &self.hall_calls {
465 let mut c = hc.clone();
466 c.stop = remap(c.stop);
467 c.destination = remap_opt(c.destination);
468 c.assigned_car = remap_opt(c.assigned_car);
469 c.pending_riders = c.pending_riders.iter().map(|&r| remap(r)).collect();
470 world.set_hall_call(c);
471 }
472 }
473
474 #[allow(clippy::type_complexity)]
476 fn rebuild_groups_and_dispatchers(
477 &self,
478 index_to_id: &[EntityId],
479 custom_strategy_factory: CustomStrategyFactory<'_>,
480 ) -> Result<
481 (
482 Vec<crate::dispatch::ElevatorGroup>,
483 HashMap<StopId, EntityId>,
484 std::collections::BTreeMap<GroupId, Box<dyn crate::dispatch::DispatchStrategy>>,
485 std::collections::BTreeMap<GroupId, crate::dispatch::BuiltinStrategy>,
486 ),
487 crate::error::SimError,
488 > {
489 use crate::dispatch::ElevatorGroup;
490
491 let groups: Vec<ElevatorGroup> = self
492 .groups
493 .iter()
494 .map(|gs| {
495 let elevator_entities: Vec<EntityId> = gs
496 .elevator_indices
497 .iter()
498 .filter_map(|&i| index_to_id.get(i).copied())
499 .collect();
500 let stop_entities: Vec<EntityId> = gs
501 .stop_indices
502 .iter()
503 .filter_map(|&i| index_to_id.get(i).copied())
504 .collect();
505
506 let lines = if gs.lines.is_empty() {
507 vec![crate::dispatch::LineInfo::new(
510 EntityId::default(),
511 elevator_entities,
512 stop_entities,
513 )]
514 } else {
515 gs.lines
516 .iter()
517 .filter_map(|lsi| {
518 let entity = index_to_id.get(lsi.entity_index).copied()?;
519 Some(crate::dispatch::LineInfo::new(
520 entity,
521 lsi.elevator_indices
522 .iter()
523 .filter_map(|&i| index_to_id.get(i).copied())
524 .collect(),
525 lsi.stop_indices
526 .iter()
527 .filter_map(|&i| index_to_id.get(i).copied())
528 .collect(),
529 ))
530 })
531 .collect()
532 };
533
534 ElevatorGroup::new(gs.id, gs.name.clone(), lines)
535 .with_hall_call_mode(gs.hall_call_mode)
536 .with_ack_latency_ticks(gs.ack_latency_ticks)
537 })
538 .collect();
539
540 let stop_lookup: HashMap<StopId, EntityId> = self
541 .stop_lookup
542 .iter()
543 .filter_map(|(sid, &idx)| index_to_id.get(idx).map(|&eid| (*sid, eid)))
544 .collect();
545
546 let mut dispatchers = std::collections::BTreeMap::new();
547 let mut strategy_ids = std::collections::BTreeMap::new();
548 for (gs, group) in self.groups.iter().zip(groups.iter()) {
549 let strategy: Box<dyn crate::dispatch::DispatchStrategy> =
550 if let Some(builtin) = gs.strategy.instantiate() {
551 builtin
552 } else if let crate::dispatch::BuiltinStrategy::Custom(ref name) = gs.strategy {
553 custom_strategy_factory
554 .and_then(|f| f(name))
555 .ok_or_else(|| crate::error::SimError::UnresolvedCustomStrategy {
556 name: name.clone(),
557 group: group.id(),
558 })?
559 } else {
560 Box::new(crate::dispatch::scan::ScanDispatch::new())
561 };
562 dispatchers.insert(group.id(), strategy);
563 strategy_ids.insert(group.id(), gs.strategy.clone());
564 }
565
566 Ok((groups, stop_lookup, dispatchers, strategy_ids))
567 }
568
569 fn remap_extensions(
571 extensions: &BTreeMap<String, BTreeMap<EntityId, String>>,
572 id_remap: &HashMap<EntityId, EntityId>,
573 ) -> BTreeMap<String, BTreeMap<EntityId, String>> {
574 extensions
575 .iter()
576 .map(|(name, entries)| {
577 let remapped: BTreeMap<EntityId, String> = entries
578 .iter()
579 .map(|(old_id, data)| {
580 let new_id = id_remap.get(old_id).copied().unwrap_or(*old_id);
581 (new_id, data.clone())
582 })
583 .collect();
584 (name.clone(), remapped)
585 })
586 .collect()
587 }
588
589 fn emit_dangling_warnings(
591 entities: &[EntitySnapshot],
592 hall_calls: &[HallCall],
593 id_remap: &HashMap<EntityId, EntityId>,
594 tick: u64,
595 sim: &mut crate::sim::Simulation,
596 ) {
597 let mut seen = HashSet::new();
598 let mut check = |old: EntityId| {
599 if !id_remap.contains_key(&old) && seen.insert(old) {
600 sim.push_event(crate::events::Event::SnapshotDanglingReference {
601 stale_id: old,
602 tick,
603 });
604 }
605 };
606 for snap in entities {
607 Self::collect_referenced_ids(snap, &mut check);
608 }
609 for hc in hall_calls {
610 check(hc.stop);
611 if let Some(car) = hc.assigned_car {
612 check(car);
613 }
614 if let Some(dest) = hc.destination {
615 check(dest);
616 }
617 for &rider in &hc.pending_riders {
618 check(rider);
619 }
620 }
621 }
622
623 fn collect_referenced_ids(snap: &EntitySnapshot, mut visit: impl FnMut(EntityId)) {
625 if let Some(ref elev) = snap.elevator {
626 for &r in &elev.riders {
627 visit(r);
628 }
629 if let Some(t) = elev.target_stop {
630 visit(t);
631 }
632 visit(elev.line);
633 match elev.phase {
634 crate::components::ElevatorPhase::MovingToStop(s)
635 | crate::components::ElevatorPhase::Repositioning(s) => visit(s),
636 _ => {}
637 }
638 for &s in &elev.restricted_stops {
639 visit(s);
640 }
641 }
642 if let Some(ref rider) = snap.rider {
643 if let Some(s) = rider.current_stop {
644 visit(s);
645 }
646 match rider.phase {
647 crate::components::RiderPhase::Boarding(e)
648 | crate::components::RiderPhase::Riding(e)
649 | crate::components::RiderPhase::Exiting(e) => visit(e),
650 _ => {}
651 }
652 }
653 if let Some(ref route) = snap.route {
654 for leg in &route.legs {
655 visit(leg.from);
656 visit(leg.to);
657 if let crate::components::TransportMode::Line(l) = leg.via {
658 visit(l);
659 }
660 }
661 }
662 if let Some(ref ac) = snap.access_control {
663 for &s in ac.allowed_stops() {
664 visit(s);
665 }
666 }
667 if let Some(ref dq) = snap.destination_queue {
668 for &e in dq.queue() {
669 visit(e);
670 }
671 }
672 for cc in &snap.car_calls {
673 visit(cc.floor);
674 for &r in &cc.pending_riders {
675 visit(r);
676 }
677 }
678 }
679}
680
681const SNAPSHOT_MAGIC: [u8; 8] = *b"ELEVSNAP";
683
684const SNAPSHOT_SCHEMA_VERSION: u32 = 1;
688
689#[derive(Debug, Serialize, Deserialize)]
695struct SnapshotEnvelope {
696 magic: [u8; 8],
698 version: String,
700 payload: WorldSnapshot,
702}
703
704impl crate::sim::Simulation {
705 #[must_use]
720 #[allow(clippy::too_many_lines)]
721 pub fn snapshot(&self) -> WorldSnapshot {
722 self.snapshot_inner()
723 }
724
725 pub fn try_snapshot(&self) -> Result<WorldSnapshot, crate::error::SimError> {
735 if self.tick_in_progress {
736 return Err(crate::error::SimError::MidTickSnapshot);
737 }
738 Ok(self.snapshot())
739 }
740
741 #[allow(clippy::too_many_lines)]
745 fn snapshot_inner(&self) -> WorldSnapshot {
746 let world = self.world();
747
748 let all_ids: Vec<EntityId> = world.alive.keys().collect();
750 let id_to_index: HashMap<EntityId, usize> = all_ids
751 .iter()
752 .copied()
753 .enumerate()
754 .map(|(i, e)| (e, i))
755 .collect();
756
757 let entities: Vec<EntitySnapshot> = all_ids
759 .iter()
760 .map(|&eid| EntitySnapshot {
761 original_id: eid,
762 position: world.position(eid).copied(),
763 velocity: world.velocity(eid).copied(),
764 elevator: world.elevator(eid).cloned(),
765 stop: world.stop(eid).cloned(),
766 rider: world.rider(eid).cloned(),
767 route: world.route(eid).cloned(),
768 line: world.line(eid).cloned(),
769 patience: world.patience(eid).copied(),
770 preferences: world.preferences(eid).copied(),
771 access_control: world.access_control(eid).cloned(),
772 disabled: world.is_disabled(eid),
773 #[cfg(feature = "energy")]
774 energy_profile: world.energy_profile(eid).cloned(),
775 #[cfg(feature = "energy")]
776 energy_metrics: world.energy_metrics(eid).cloned(),
777 service_mode: world.service_mode(eid).copied(),
778 destination_queue: world.destination_queue(eid).cloned(),
779 car_calls: world.car_calls(eid).to_vec(),
780 })
781 .collect();
782
783 let groups: Vec<GroupSnapshot> = self
785 .groups()
786 .iter()
787 .map(|g| {
788 let lines: Vec<LineSnapshotInfo> = g
789 .lines()
790 .iter()
791 .filter_map(|li| {
792 let entity_index = id_to_index.get(&li.entity()).copied()?;
793 Some(LineSnapshotInfo {
794 entity_index,
795 elevator_indices: li
796 .elevators()
797 .iter()
798 .filter_map(|eid| id_to_index.get(eid).copied())
799 .collect(),
800 stop_indices: li
801 .serves()
802 .iter()
803 .filter_map(|eid| id_to_index.get(eid).copied())
804 .collect(),
805 })
806 })
807 .collect();
808 GroupSnapshot {
809 id: g.id(),
810 name: g.name().to_owned(),
811 elevator_indices: g
812 .elevator_entities()
813 .iter()
814 .filter_map(|eid| id_to_index.get(eid).copied())
815 .collect(),
816 stop_indices: g
817 .stop_entities()
818 .iter()
819 .filter_map(|eid| id_to_index.get(eid).copied())
820 .collect(),
821 strategy: self
822 .strategy_id(g.id())
823 .cloned()
824 .unwrap_or(crate::dispatch::BuiltinStrategy::Scan),
825 lines,
826 reposition: self.reposition_id(g.id()).cloned(),
827 hall_call_mode: g.hall_call_mode(),
828 ack_latency_ticks: g.ack_latency_ticks(),
829 }
830 })
831 .collect();
832
833 let stop_lookup: BTreeMap<StopId, usize> = self
835 .stop_lookup_iter()
836 .filter_map(|(sid, eid)| id_to_index.get(eid).map(|&idx| (*sid, idx)))
837 .collect();
838
839 WorldSnapshot {
840 version: SNAPSHOT_SCHEMA_VERSION,
841 tick: self.current_tick(),
842 dt: self.dt(),
843 entities,
844 groups,
845 stop_lookup,
846 metrics: self.metrics().clone(),
847 metric_tags: self
848 .world()
849 .resource::<MetricTags>()
850 .cloned()
851 .unwrap_or_default(),
852 extensions: self.world().serialize_extensions(),
853 ticks_per_second: 1.0 / self.dt(),
854 hall_calls: world.iter_hall_calls().cloned().collect(),
855 }
856 }
857
858 pub fn snapshot_bytes(&self) -> Result<Vec<u8>, crate::error::SimError> {
881 let envelope = SnapshotEnvelope {
882 magic: SNAPSHOT_MAGIC,
883 version: env!("CARGO_PKG_VERSION").to_owned(),
884 payload: self.try_snapshot()?,
885 };
886 postcard::to_allocvec(&envelope)
887 .map_err(|e| crate::error::SimError::SnapshotFormat(e.to_string()))
888 }
889
890 pub fn restore_bytes(
905 bytes: &[u8],
906 custom_strategy_factory: CustomStrategyFactory<'_>,
907 ) -> Result<Self, crate::error::SimError> {
908 let (envelope, tail): (SnapshotEnvelope, &[u8]) = postcard::take_from_bytes(bytes)
909 .map_err(|e| crate::error::SimError::SnapshotFormat(e.to_string()))?;
910 if !tail.is_empty() {
911 return Err(crate::error::SimError::SnapshotFormat(format!(
912 "trailing bytes: {} unread of {}",
913 tail.len(),
914 bytes.len()
915 )));
916 }
917 if envelope.magic != SNAPSHOT_MAGIC {
918 return Err(crate::error::SimError::SnapshotFormat(
919 "magic bytes do not match".to_string(),
920 ));
921 }
922 let current = env!("CARGO_PKG_VERSION");
923 if envelope.version != current {
924 return Err(crate::error::SimError::SnapshotVersion {
925 saved: envelope.version,
926 current: current.to_owned(),
927 });
928 }
929 envelope.payload.restore(custom_strategy_factory)
930 }
931}