1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
use crate::{
command::CommandBuffer,
fixed_timestepper::{FixedTimestepper, Stepper},
timestamp::{Timestamp, Timestamped},
world::World,
};
use std::fmt::Debug;
use tracing::trace;
/// Whether the [`Simulation`] needs to wait for a snapshot to be applied before its display
/// state can be considered usable.
#[derive(PartialEq, Eq)]
pub(crate) enum InitializationType {
/// Implies that the world can be used as is without first being initialized with any snapshot.
/// A good example would be the server's world simulations.
PreInitialized,
/// Implies that the world starts off in an "invalid" state, and needs to be initialized with a
/// snapshot before its resulting display state is "valid" and usable. A good example would be
/// the clients' world simulations.
NeedsInitialization,
}
/// A "wrapper" for your [`World`] that is equipped with a `CommandBuffer`. This is responsible
/// for applying the correct commands at the correct time while running your [`World`] simulation.
#[derive(Debug)]
pub(crate) struct Simulation<WorldType: World, const INITIALIZATION_TYPE: InitializationType> {
world: WorldType,
command_buffer: CommandBuffer<WorldType::CommandType>,
has_initialized: bool,
}
impl<WorldType: World, const INITIALIZATION_TYPE: InitializationType> Default
for Simulation<WorldType, INITIALIZATION_TYPE>
{
fn default() -> Self {
Self {
world: Default::default(),
command_buffer: CommandBuffer::new(),
has_initialized: match INITIALIZATION_TYPE {
InitializationType::NeedsInitialization => false,
InitializationType::PreInitialized => true,
},
}
}
}
impl<WorldType: World, const INITIALIZATION_TYPE: InitializationType>
Simulation<WorldType, INITIALIZATION_TYPE>
{
/// Create a new [`Simulation`].
pub fn new() -> Self {
Self::default()
}
/// The timestamp for the next frame that is either about to be simulated, of is currently
/// being simulated. This timestamp is useful for issuing commands to be applied to the
/// world as soon as possible.
pub fn simulating_timestamp(&self) -> Timestamp {
self.last_completed_timestamp() + 1
}
/// Schedule a command to be applied to the world at the beginning of the frame with the
/// given timestamp.
pub fn schedule_command(&mut self, command: &Timestamped<WorldType::CommandType>) {
self.command_buffer.insert(command);
}
/// Tries to "fast-forward" the world simulation so that the timestamp of the last completed
/// frame matches the target timestamp. If the maximum number of steps is reached, the
/// `last_completed_timestamp()` will not match the target timestamp yet, since no frames are
/// to be skipped over.
pub fn try_completing_simulations_up_to(
&mut self,
target_completed_timestamp: Timestamp,
max_steps: usize,
) {
for _ in 0..max_steps {
if self.last_completed_timestamp() >= target_completed_timestamp {
break;
}
self.step();
}
}
/// Apply a snapshot of the world that has completed its simulation for the given
/// timestamped frame. Since the snapshots are usually at a different timestamp, (usually
/// in the past), the internal command buffer needs to be replenished/updated with commands
/// that it had already discarded.
/// Note: We had to discard commands once we have applied them because commands can arrive
/// out of order, and we do not want to skip past any stale commands.
pub fn apply_completed_snapshot(
&mut self,
completed_snapshot: &Timestamped<WorldType::SnapshotType>,
rewound_command_buffer: CommandBuffer<WorldType::CommandType>,
) {
self.world
.apply_snapshot(completed_snapshot.inner().clone());
self.command_buffer = rewound_command_buffer;
self.command_buffer
.update_timestamp(completed_snapshot.timestamp());
self.has_initialized = true;
}
/// Generates a timestamped snapshot of the world for the latest frame that has completed
/// its simulation.
pub fn last_completed_snapshot(&self) -> Timestamped<WorldType::SnapshotType> {
Timestamped::new(self.world.snapshot(), self.last_completed_timestamp())
}
/// Get the current display state of the world. Returns `None` if the world has not been
/// initialized with a snapshot yet, and [`INITIALIZATION_TYPE`](Simulation) is
/// [`InitializationType::NeedsInitialization`].
pub fn display_state(&self) -> Option<Timestamped<WorldType::DisplayStateType>> {
if self.has_initialized {
Some(Timestamped::new(
self.world.display_state(),
self.last_completed_timestamp(),
))
} else {
None
}
}
/// Get a list of commands currently buffered. This is primarily for diagnostic purposes.
pub fn buffered_commands(
&self,
) -> impl Iterator<Item = (Timestamp, &Vec<WorldType::CommandType>)> {
self.command_buffer.iter()
}
}
impl<WorldType: World, const INITIALIZATION_TYPE: InitializationType> Stepper
for Simulation<WorldType, INITIALIZATION_TYPE>
{
fn step(&mut self) {
trace!(
"World simulation step (simulation timestamp: {:?})",
self.simulating_timestamp()
);
// We first drain up to and including the commands that are scheduled for this
// timestamp that we are trying to simulate.
let commands = self.command_buffer.drain_up_to(self.simulating_timestamp());
for command in commands {
trace!("Applying command {:?}", &command);
self.world.apply_command(&command);
}
// We then advance the world simulation by one frame.
self.world.step();
// The simulation for this frame has been completed, so we can now increment the
// timestamp.
self.command_buffer
.update_timestamp(self.last_completed_timestamp() + 1);
}
}
impl<WorldType: World, const INITIALIZATION_TYPE: InitializationType> FixedTimestepper
for Simulation<WorldType, INITIALIZATION_TYPE>
{
/// The timestamp for the frame that has completed simulation, and whose resulting state is
/// now available.
fn last_completed_timestamp(&self) -> Timestamp {
self.command_buffer.timestamp()
}
/// Useful for initializing the timestamp, syncing the timestamp, and, when necessary,
/// (i.e. when the world is drifting too far behind and can't catch up), jump to a
/// corrected timestamp in the future without running any simulation. Note that any
/// leftover stale commands won't be dropped - they will still be applied in the next
/// frame, unless they are too stale that the must be dropped to maintain the transitivity
/// laws.
fn reset_last_completed_timestamp(&mut self, timestamp: Timestamp) {
let old_timestamp = self.last_completed_timestamp();
self.command_buffer.update_timestamp(timestamp);
// Note: If timeskip was so large that timestamp has wrapped around to the past, then we
// need to apply all the commands in the command buffer so that any pending commands to get
// replayed unexpectedly in the future at the wrong time.
if timestamp < old_timestamp {
let commands = self.command_buffer.drain_all();
for command in commands {
trace!("Applying stale command after large timeskip {:?}", &command);
self.world.apply_command(&command);
}
}
}
}