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