-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathReplaySystem.js
More file actions
127 lines (108 loc) · 3.34 KB
/
Copy pathReplaySystem.js
File metadata and controls
127 lines (108 loc) · 3.34 KB
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
/*
Toolbox Aid
David Quesenberry
03/22/2026
ReplaySystem.js
*/
import { asNonNegativeInteger } from '../../shared/math/numberNormalization.js';
import { cloneRuntimeValue } from '../../shared/runtime/snapshotClone.js';
import { createReplayModel, normalizeReplayModel, withFinalState } from './ReplayModel.js';
import { ReplayTimeline } from './ReplayTimeline.js';
export default class ReplaySystem {
constructor({ timelineWindowFrames } = {}) {
this.replay = createReplayModel();
this.frames = this.replay.frames;
this.playbackIndex = 0;
this.recording = false;
this.playing = false;
this.timeline = new ReplayTimeline({ maxFrames: timelineWindowFrames });
}
createReplay({ metadata = null, initialState = null } = {}) {
return createReplayModel({ metadata, initialState });
}
rebuildTimelineFromFrames() {
this.timeline.loadFromSnapshots(this.frames);
}
startRecording({ metadata = null, initialState = null } = {}) {
this.replay = createReplayModel({ metadata, initialState });
this.frames = this.replay.frames;
this.timeline.clear();
this.recording = true;
this.playing = false;
this.playbackIndex = 0;
}
recordFrame(frame) {
if (this.recording) {
const clonedFrame = cloneRuntimeValue(frame);
this.frames.push(clonedFrame);
this.timeline.pushSnapshot(this.frames.length - 1, clonedFrame);
}
}
stopRecording({ finalState = null } = {}) {
this.recording = false;
this.replay = withFinalState(this.replay, finalState);
this.frames = this.replay.frames;
return [...this.frames];
}
getReplay() {
return cloneRuntimeValue(this.replay);
}
loadReplay(replay) {
this.replay = normalizeReplayModel(replay);
this.frames = this.replay.frames;
this.rebuildTimelineFromFrames();
this.playbackIndex = 0;
this.recording = false;
this.playing = false;
return this.getReplay();
}
startPlayback(replay = null) {
if (replay) {
this.loadReplay(replay);
}
if (!this.frames.length) {
this.playing = false;
return false;
}
this.playbackIndex = 0;
this.playing = true;
this.recording = false;
return true;
}
getTimeline() {
return this.timeline.toArray();
}
getTimelineSnapshot(frameId) {
return this.timeline.getSnapshot(frameId);
}
getNearestTimelineSnapshot(frameId) {
return this.timeline.getNearestSnapshot(frameId);
}
replaceReplayFromFrame(frameId, frames = []) {
const normalizedFrameId = asNonNegativeInteger(frameId, this.frames.length);
const prefix = this.frames.slice(0, normalizedFrameId);
const replacementFrames = Array.isArray(frames)
? frames.map((frame) => cloneRuntimeValue(frame))
: [];
const nextFrames = [...prefix, ...replacementFrames];
this.replay = normalizeReplayModel({
...this.replay,
frames: nextFrames
});
this.frames = this.replay.frames;
this.timeline.replaceFromFrame(normalizedFrameId, replacementFrames);
if (this.playbackIndex > this.frames.length) {
this.playbackIndex = this.frames.length;
}
return this.getReplay();
}
nextFrame() {
if (!this.playing || this.playbackIndex >= this.frames.length) {
this.playing = false;
return null;
}
const frame = this.frames[this.playbackIndex];
this.playbackIndex += 1;
return frame;
}
}