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
// src/editor/sim.rs
//
// The editor's simulation transport: the Play / Pause / Step / Stop state
// machine over the live preview world. Pure state -- the hook's drive
// (`hook/sim_control.rs`) maps it onto the engine's freeze gate
// (`MenuOverride`) each frame and onto the preview rebuild on Stop.
//
// Stopped is the editing baseline: the world sits frozen at its authored
// state. Play unfreezes it (and hands the cursor to the world, like the old
// capture toggle). Pause freezes mid-state for inspection with the cursor
// free. Stop rebuilds the preview world from the in-memory entries, which is
// the same restore-to-authored path every committed edit already takes --
// so a committed edit while playing or paused also drops the transport to
// Stopped (`on_edit`).
// Where the transport stands.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(crate) enum SimState {
#[default]
Stopped,
Playing,
Paused,
}
// The transport plus its one-frame step pulse. `step_queued` is consumed by
// the hook's per-frame publish: the frame that takes it runs the world for
// exactly one step, then the freeze resumes.
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct SimControl {
pub(crate) state: SimState,
step_queued: bool,
}
impl SimControl {
// Whether the world is running (and owns the cursor / gameplay input).
pub(crate) fn playing(&self) -> bool {
self.state == SimState::Playing
}
// The Play / Pause control: run from Stopped or Paused, freeze mid-state
// from Playing.
pub(crate) fn toggle_play(&mut self) {
self.state = match self.state {
SimState::Playing => SimState::Paused,
SimState::Stopped | SimState::Paused => SimState::Playing,
};
self.step_queued = false;
}
// Freeze mid-state (Escape while playing). A no-op elsewhere.
pub(crate) fn pause(&mut self) {
if self.state == SimState::Playing {
self.state = SimState::Paused;
}
}
// Advance exactly one frame. From Stopped or Paused the world lands
// paused one step further on; while Playing this is just a pause (the
// frame already runs).
pub(crate) fn step(&mut self) {
match self.state {
SimState::Playing => self.state = SimState::Paused,
SimState::Stopped | SimState::Paused => {
self.state = SimState::Paused;
self.step_queued = true;
}
}
}
// Drop to Stopped. Returns whether the authored state must be restored
// (a rebuild): true from Playing / Paused, false when already there.
#[must_use]
pub(crate) fn stop(&mut self) -> bool {
let restore = self.state != SimState::Stopped;
self.state = SimState::Stopped;
self.step_queued = false;
restore
}
// A committed edit rebuilds the preview world, which discards the run's
// state -- so the transport honestly drops to Stopped. The rebuild is
// already flagged by the edit path; nothing more to restore.
pub(crate) fn on_edit(&mut self) {
self.state = SimState::Stopped;
self.step_queued = false;
}
// Whether the world simulates this frame, consuming a queued step. The
// hook publishes `MenuOverride(Some(!run))` from this once per frame.
pub(crate) fn take_run_frame(&mut self) -> bool {
self.playing() || std::mem::take(&mut self.step_queued)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn play_pause_cycle() {
let mut sim = SimControl::default();
assert_eq!(sim.state, SimState::Stopped);
sim.toggle_play();
assert!(sim.playing());
sim.toggle_play();
assert_eq!(sim.state, SimState::Paused);
sim.toggle_play();
assert!(sim.playing(), "Play resumes from Paused");
}
#[test]
fn pause_only_freezes_a_running_world() {
let mut sim = SimControl::default();
sim.pause();
assert_eq!(
sim.state,
SimState::Stopped,
"Pause from Stopped is a no-op"
);
sim.toggle_play();
sim.pause();
assert_eq!(sim.state, SimState::Paused);
sim.pause();
assert_eq!(sim.state, SimState::Paused);
}
#[test]
fn step_advances_one_frame_then_freezes() {
let mut sim = SimControl::default();
sim.step();
assert_eq!(sim.state, SimState::Paused);
assert!(sim.take_run_frame(), "the queued step runs one frame");
assert!(!sim.take_run_frame(), "then the freeze resumes");
sim.step();
sim.step();
assert!(sim.take_run_frame());
assert!(!sim.take_run_frame(), "steps do not accumulate past one");
}
#[test]
fn step_while_playing_pauses() {
let mut sim = SimControl::default();
sim.toggle_play();
sim.step();
assert_eq!(sim.state, SimState::Paused);
assert!(
!sim.take_run_frame(),
"the playing frame already ran; no extra pulse"
);
}
#[test]
fn stop_reports_when_a_restore_is_due() {
let mut sim = SimControl::default();
assert!(!sim.stop(), "already at the authored state");
sim.toggle_play();
assert!(sim.stop());
assert_eq!(sim.state, SimState::Stopped);
sim.step();
assert!(sim.stop(), "a paused run also restores");
assert!(!sim.take_run_frame(), "Stop drops a queued step");
}
#[test]
fn an_edit_drops_the_transport_without_a_restore_request() {
let mut sim = SimControl::default();
sim.toggle_play();
sim.on_edit();
assert_eq!(sim.state, SimState::Stopped);
assert!(!sim.take_run_frame());
}
#[test]
fn playing_runs_every_frame() {
let mut sim = SimControl::default();
sim.toggle_play();
assert!(sim.take_run_frame());
assert!(sim.take_run_frame());
}
}