Aufzeichnung an den Weltschritt koppeln
Recording::step führt einen Takt aus und zeichnet ihn auf. Andere Befehle als die ausgeführten können so nicht in eine Aufzeichnung geraten. Aufzeichnung und Welt prüfen gegenseitig ihren Takt, und ein beschädigter Startzustand fällt schon beim Einlesen auf. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
+38
-5
@@ -6,7 +6,7 @@ use std::fmt;
|
||||
use crate::codec::{DecodeError, Reader, Writer};
|
||||
use crate::command::Command;
|
||||
use crate::snapshot::SnapshotError;
|
||||
use crate::world::World;
|
||||
use crate::world::{Event, World};
|
||||
|
||||
const MAGIC: [u8; 4] = *b"GRWR";
|
||||
/// Version of the recording format. Raise it whenever the encoded recording,
|
||||
@@ -57,6 +57,7 @@ impl From<DecodeError> for RecordingError {
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Recording {
|
||||
start: Vec<u8>,
|
||||
start_tick: u64,
|
||||
tick_count: u32,
|
||||
/// Ticks that had commands, as (tick index, commands), in rising order.
|
||||
busy_ticks: Vec<(u32, Vec<Command>)>,
|
||||
@@ -67,18 +68,33 @@ impl Recording {
|
||||
pub fn start(world: &World) -> Self {
|
||||
Self {
|
||||
start: world.to_snapshot(),
|
||||
start_tick: world.tick(),
|
||||
tick_count: 0,
|
||||
busy_ticks: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Records the commands of one tick. Call it once for every
|
||||
/// [`World::step`], with the same commands.
|
||||
pub fn push_tick(&mut self, commands: &[Command]) {
|
||||
/// Advances `world` by one tick with `commands` and records them. This is
|
||||
/// the only way to add to a recording, so it can never hold other
|
||||
/// commands than the ones the world executed.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if `world` is not at [`Recording::end_tick`], which means it is
|
||||
/// not the world this recording follows.
|
||||
pub fn step(&mut self, world: &mut World, commands: &[Command]) -> Vec<Event> {
|
||||
assert_eq!(
|
||||
world.tick(),
|
||||
self.end_tick(),
|
||||
"the world is not at the tick this recording has reached"
|
||||
);
|
||||
if !commands.is_empty() {
|
||||
self.busy_ticks.push((self.tick_count, commands.to_vec()));
|
||||
}
|
||||
self.tick_count += 1;
|
||||
self.tick_count = self
|
||||
.tick_count
|
||||
.checked_add(1)
|
||||
.expect("a recording holds fewer than 2^32 ticks");
|
||||
world.step(commands)
|
||||
}
|
||||
|
||||
/// Number of recorded ticks.
|
||||
@@ -86,6 +102,11 @@ impl Recording {
|
||||
self.tick_count
|
||||
}
|
||||
|
||||
/// Tick of the recorded world after the last recorded tick.
|
||||
pub fn end_tick(&self) -> u64 {
|
||||
self.start_tick + u64::from(self.tick_count)
|
||||
}
|
||||
|
||||
/// Restores the starting state and applies every recorded tick.
|
||||
pub fn replay(&self) -> Result<World, RecordingError> {
|
||||
let mut world = World::from_snapshot(&self.start).map_err(RecordingError::BadStart)?;
|
||||
@@ -106,7 +127,15 @@ impl Recording {
|
||||
|
||||
/// Encodes the recording together with the state that replaying it must
|
||||
/// reach. `end` is the world after the last recorded tick.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if `end` is not at [`Recording::end_tick`].
|
||||
pub fn to_bytes(&self, end: &World) -> Vec<u8> {
|
||||
assert_eq!(
|
||||
end.tick(),
|
||||
self.end_tick(),
|
||||
"the end state is not at the tick this recording has reached"
|
||||
);
|
||||
let mut w = Writer::new();
|
||||
w.raw(&MAGIC);
|
||||
w.u32(RECORDING_VERSION);
|
||||
@@ -139,6 +168,9 @@ impl Recording {
|
||||
}
|
||||
let expected_hash = r.u64()?;
|
||||
let start = r.bytes()?.to_vec();
|
||||
let start_tick = World::from_snapshot(&start)
|
||||
.map_err(RecordingError::BadStart)?
|
||||
.tick();
|
||||
let tick_count = r.u32()?;
|
||||
let busy_count = r.u32()?;
|
||||
let mut busy_ticks = Vec::new();
|
||||
@@ -163,6 +195,7 @@ impl Recording {
|
||||
Ok((
|
||||
Self {
|
||||
start,
|
||||
start_tick,
|
||||
tick_count,
|
||||
busy_ticks,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user