@al8b/time 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/constants.d.mts +6 -0
- package/dist/constants.d.ts +6 -0
- package/dist/constants.js +34 -0
- package/dist/constants.js.map +1 -0
- package/dist/constants.mjs +8 -0
- package/dist/constants.mjs.map +1 -0
- package/dist/core/index.d.mts +2 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +522 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/index.mjs +497 -0
- package/dist/core/index.mjs.map +1 -0
- package/dist/core/machine.d.mts +101 -0
- package/dist/core/machine.d.ts +101 -0
- package/dist/core/machine.js +520 -0
- package/dist/core/machine.js.map +1 -0
- package/dist/core/machine.mjs +497 -0
- package/dist/core/machine.mjs.map +1 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +526 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +499 -0
- package/dist/index.mjs.map +1 -0
- package/dist/playback/index.d.mts +2 -0
- package/dist/playback/index.d.ts +2 -0
- package/dist/playback/index.js +172 -0
- package/dist/playback/index.js.map +1 -0
- package/dist/playback/index.mjs +147 -0
- package/dist/playback/index.mjs.map +1 -0
- package/dist/playback/player.d.mts +41 -0
- package/dist/playback/player.d.ts +41 -0
- package/dist/playback/player.js +172 -0
- package/dist/playback/player.js.map +1 -0
- package/dist/playback/player.mjs +147 -0
- package/dist/playback/player.mjs.map +1 -0
- package/dist/recording/index.d.mts +2 -0
- package/dist/recording/index.d.ts +2 -0
- package/dist/recording/index.js +149 -0
- package/dist/recording/index.js.map +1 -0
- package/dist/recording/index.mjs +124 -0
- package/dist/recording/index.mjs.map +1 -0
- package/dist/recording/recorder.d.mts +41 -0
- package/dist/recording/recorder.d.ts +41 -0
- package/dist/recording/recorder.js +149 -0
- package/dist/recording/recorder.js.map +1 -0
- package/dist/recording/recorder.mjs +124 -0
- package/dist/recording/recorder.mjs.map +1 -0
- package/dist/types/index.d.mts +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +19 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/index.mjs +1 -0
- package/dist/types/index.mjs.map +1 -0
- package/dist/types/state.d.mts +33 -0
- package/dist/types/state.d.ts +33 -0
- package/dist/types/state.js +19 -0
- package/dist/types/state.js.map +1 -0
- package/dist/types/state.mjs +1 -0
- package/dist/types/state.mjs.map +1 -0
- package/dist/utils.d.mts +9 -0
- package/dist/utils.d.ts +9 -0
- package/dist/utils.js +60 -0
- package/dist/utils.js.map +1 -0
- package/dist/utils.mjs +37 -0
- package/dist/utils.mjs.map +1 -0
- package/package.json +37 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/playback/index.ts
|
|
22
|
+
var playback_exports = {};
|
|
23
|
+
__export(playback_exports, {
|
|
24
|
+
StatePlayer: () => StatePlayer
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(playback_exports);
|
|
27
|
+
|
|
28
|
+
// src/constants.ts
|
|
29
|
+
var DEFAULT_RECORD_BUFFER_FRAMES = 60 * 30;
|
|
30
|
+
var DEFAULT_LOOP_BUFFER_FRAMES = 60 * 4;
|
|
31
|
+
|
|
32
|
+
// src/utils.ts
|
|
33
|
+
function deepCopy(value, excluded) {
|
|
34
|
+
if (value == null) {
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
if (excluded && excluded.includes(value)) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
if (Array.isArray(value)) {
|
|
44
|
+
const result = [];
|
|
45
|
+
for (let i = 0; i < value.length; i++) {
|
|
46
|
+
result[i] = deepCopy(value[i], excluded);
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
if (typeof value === "object") {
|
|
51
|
+
const result = {};
|
|
52
|
+
for (const key in value) {
|
|
53
|
+
if (Object.hasOwn(value, key)) {
|
|
54
|
+
result[key] = deepCopy(value[key], excluded);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
return excluded ? null : value;
|
|
60
|
+
}
|
|
61
|
+
__name(deepCopy, "deepCopy");
|
|
62
|
+
|
|
63
|
+
// src/playback/player.ts
|
|
64
|
+
var StatePlayer = class {
|
|
65
|
+
static {
|
|
66
|
+
__name(this, "StatePlayer");
|
|
67
|
+
}
|
|
68
|
+
looping = false;
|
|
69
|
+
loopStart = 0;
|
|
70
|
+
loopIndex = 0;
|
|
71
|
+
loopLength;
|
|
72
|
+
loopCallback = null;
|
|
73
|
+
constructor(loopLength = DEFAULT_LOOP_BUFFER_FRAMES) {
|
|
74
|
+
this.loopLength = loopLength;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Check if currently looping
|
|
78
|
+
*/
|
|
79
|
+
isLooping() {
|
|
80
|
+
return this.looping;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Start loop playback
|
|
84
|
+
*/
|
|
85
|
+
startLoop(position, callback) {
|
|
86
|
+
this.looping = true;
|
|
87
|
+
this.loopStart = Math.max(position, 1);
|
|
88
|
+
this.loopIndex = 0;
|
|
89
|
+
this.loopCallback = callback;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Stop loop playback
|
|
93
|
+
*/
|
|
94
|
+
stopLoop() {
|
|
95
|
+
this.looping = false;
|
|
96
|
+
this.loopCallback = null;
|
|
97
|
+
return this.loopStart;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Update loop (call each frame)
|
|
101
|
+
* Returns position to replay, or null if not looping
|
|
102
|
+
*/
|
|
103
|
+
updateLoop() {
|
|
104
|
+
if (!this.looping) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
if (this.loopIndex === 0) {
|
|
108
|
+
this.loopIndex++;
|
|
109
|
+
return this.loopStart;
|
|
110
|
+
}
|
|
111
|
+
this.loopIndex++;
|
|
112
|
+
if (this.loopIndex > this.loopLength) {
|
|
113
|
+
this.loopIndex = 0;
|
|
114
|
+
}
|
|
115
|
+
return this.loopStart - this.loopIndex;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Execute loop callback
|
|
119
|
+
*/
|
|
120
|
+
executeCallback() {
|
|
121
|
+
if (this.loopCallback) {
|
|
122
|
+
this.loopCallback();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Restore state to target object
|
|
127
|
+
*/
|
|
128
|
+
restoreState(target, snapshot) {
|
|
129
|
+
if (!snapshot || !target) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
for (const key in target) {
|
|
133
|
+
if (Object.hasOwn(target, key) && !this.isProtectedKey(key)) {
|
|
134
|
+
delete target[key];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
for (const key in snapshot) {
|
|
138
|
+
if (Object.hasOwn(snapshot, key)) {
|
|
139
|
+
target[key] = deepCopy(snapshot[key]);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Check if key should be protected from restoration
|
|
145
|
+
*/
|
|
146
|
+
isProtectedKey(key) {
|
|
147
|
+
const protected_keys = [
|
|
148
|
+
"screen",
|
|
149
|
+
"audio",
|
|
150
|
+
"keyboard",
|
|
151
|
+
"mouse",
|
|
152
|
+
"touch",
|
|
153
|
+
"gamepad",
|
|
154
|
+
"system",
|
|
155
|
+
"storage",
|
|
156
|
+
"sprites",
|
|
157
|
+
"maps",
|
|
158
|
+
"sounds",
|
|
159
|
+
"music",
|
|
160
|
+
"assets",
|
|
161
|
+
"host",
|
|
162
|
+
"session",
|
|
163
|
+
"memory"
|
|
164
|
+
];
|
|
165
|
+
return protected_keys.includes(key);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
169
|
+
0 && (module.exports = {
|
|
170
|
+
StatePlayer
|
|
171
|
+
});
|
|
172
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/playback/index.ts","../../src/constants.ts","../../src/utils.ts","../../src/playback/player.ts"],"sourcesContent":["/**\n * Playback module exports\n */\n\nexport { StatePlayer } from \"./player\";\n","/** Default recording buffer: 30 seconds at 60fps (1800 frames) */\nexport const DEFAULT_RECORD_BUFFER_FRAMES = 60 * 30;\n\n/** Default loop playback buffer: 4 seconds at 60fps (240 frames) */\nexport const DEFAULT_LOOP_BUFFER_FRAMES = 60 * 4;\n","/**\n * Deep copy a value, optionally skipping excluded references.\n *\n * @param value - The value to deep copy\n * @param excluded - Optional array of object references to skip (replaced with null)\n */\nexport function deepCopy(value: any, excluded?: any[]): any {\n\tif (value == null) {\n\t\treturn value;\n\t}\n\n\tif (excluded && excluded.includes(value)) {\n\t\treturn null;\n\t}\n\n\tif (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n\t\treturn value;\n\t}\n\n\tif (Array.isArray(value)) {\n\t\tconst result: any[] = [];\n\t\tfor (let i = 0; i < value.length; i++) {\n\t\t\tresult[i] = deepCopy(value[i], excluded);\n\t\t}\n\t\treturn result;\n\t}\n\n\tif (typeof value === \"object\") {\n\t\tconst result: any = {};\n\t\tfor (const key in value) {\n\t\t\tif (Object.hasOwn(value, key)) {\n\t\t\t\tresult[key] = deepCopy(value[key], excluded);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\t// Non-serializable types: return null when filtering, passthrough otherwise\n\treturn excluded ? null : value;\n}\n","/**\n * StatePlayer - Handles replay and loop playback\n *\n * Responsibilities:\n * - Restore state snapshots\n * - Manage loop playback\n * - Control playback position\n */\nimport { DEFAULT_LOOP_BUFFER_FRAMES } from \"../constants\";\nimport { deepCopy } from \"../utils\";\n\nimport type { StateSnapshot } from \"../types\";\n\nexport class StatePlayer {\n\tprivate looping = false;\n\tprivate loopStart = 0;\n\tprivate loopIndex = 0;\n\tprivate loopLength: number;\n\tprivate loopCallback: (() => void) | null = null;\n\n\tconstructor(loopLength = DEFAULT_LOOP_BUFFER_FRAMES) {\n\t\tthis.loopLength = loopLength;\n\t}\n\n\t/**\n\t * Check if currently looping\n\t */\n\tisLooping(): boolean {\n\t\treturn this.looping;\n\t}\n\n\t/**\n\t * Start loop playback\n\t */\n\tstartLoop(position: number, callback: () => void): void {\n\t\tthis.looping = true;\n\t\tthis.loopStart = Math.max(position, 1);\n\t\tthis.loopIndex = 0;\n\t\tthis.loopCallback = callback;\n\t}\n\n\t/**\n\t * Stop loop playback\n\t */\n\tstopLoop(): number {\n\t\tthis.looping = false;\n\t\tthis.loopCallback = null;\n\t\treturn this.loopStart;\n\t}\n\n\t/**\n\t * Update loop (call each frame)\n\t * Returns position to replay, or null if not looping\n\t */\n\tupdateLoop(): number | null {\n\t\tif (!this.looping) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (this.loopIndex === 0) {\n\t\t\tthis.loopIndex++;\n\t\t\treturn this.loopStart;\n\t\t}\n\n\t\tthis.loopIndex++;\n\t\tif (this.loopIndex > this.loopLength) {\n\t\t\tthis.loopIndex = 0;\n\t\t}\n\n\t\treturn this.loopStart - this.loopIndex;\n\t}\n\n\t/**\n\t * Execute loop callback\n\t */\n\texecuteCallback(): void {\n\t\tif (this.loopCallback) {\n\t\t\tthis.loopCallback();\n\t\t}\n\t}\n\n\t/**\n\t * Restore state to target object\n\t */\n\trestoreState(target: any, snapshot: StateSnapshot): void {\n\t\tif (!snapshot || !target) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Remove all existing properties except protected system APIs\n\t\tfor (const key in target) {\n\t\t\tif (Object.hasOwn(target, key) && !this.isProtectedKey(key)) {\n\t\t\t\tdelete target[key];\n\t\t\t}\n\t\t}\n\n\t\t// Apply snapshot properties to target object via deep copy\n\t\tfor (const key in snapshot) {\n\t\t\tif (Object.hasOwn(snapshot, key)) {\n\t\t\t\ttarget[key] = deepCopy(snapshot[key]);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Check if key should be protected from restoration\n\t */\n\tprivate isProtectedKey(key: string): boolean {\n\t\t// Prevent system APIs and runtime objects from being overwritten during restore\n\t\tconst protected_keys = [\n\t\t\t\"screen\",\n\t\t\t\"audio\",\n\t\t\t\"keyboard\",\n\t\t\t\"mouse\",\n\t\t\t\"touch\",\n\t\t\t\"gamepad\",\n\t\t\t\"system\",\n\t\t\t\"storage\",\n\t\t\t\"sprites\",\n\t\t\t\"maps\",\n\t\t\t\"sounds\",\n\t\t\t\"music\",\n\t\t\t\"assets\",\n\t\t\t\"host\",\n\t\t\t\"session\",\n\t\t\t\"memory\",\n\t\t];\n\t\treturn protected_keys.includes(key);\n\t}\n\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;ACCO,IAAMA,+BAA+B,KAAK;AAG1C,IAAMC,6BAA6B,KAAK;;;ACExC,SAASC,SAASC,OAAYC,UAAgB;AACpD,MAAID,SAAS,MAAM;AAClB,WAAOA;EACR;AAEA,MAAIC,YAAYA,SAASC,SAASF,KAAAA,GAAQ;AACzC,WAAO;EACR;AAEA,MAAI,OAAOA,UAAU,YAAY,OAAOA,UAAU,YAAY,OAAOA,UAAU,WAAW;AACzF,WAAOA;EACR;AAEA,MAAIG,MAAMC,QAAQJ,KAAAA,GAAQ;AACzB,UAAMK,SAAgB,CAAA;AACtB,aAASC,IAAI,GAAGA,IAAIN,MAAMO,QAAQD,KAAK;AACtCD,aAAOC,CAAAA,IAAKP,SAASC,MAAMM,CAAAA,GAAIL,QAAAA;IAChC;AACA,WAAOI;EACR;AAEA,MAAI,OAAOL,UAAU,UAAU;AAC9B,UAAMK,SAAc,CAAC;AACrB,eAAWG,OAAOR,OAAO;AACxB,UAAIS,OAAOC,OAAOV,OAAOQ,GAAAA,GAAM;AAC9BH,eAAOG,GAAAA,IAAOT,SAASC,MAAMQ,GAAAA,GAAMP,QAAAA;MACpC;IACD;AACA,WAAOI;EACR;AAGA,SAAOJ,WAAW,OAAOD;AAC1B;AAjCgBD;;;ACOT,IAAMY,cAAN,MAAMA;EAbb,OAaaA;;;EACJC,UAAU;EACVC,YAAY;EACZC,YAAY;EACZC;EACAC,eAAoC;EAE5C,YAAYD,aAAaE,4BAA4B;AACpD,SAAKF,aAAaA;EACnB;;;;EAKAG,YAAqB;AACpB,WAAO,KAAKN;EACb;;;;EAKAO,UAAUC,UAAkBC,UAA4B;AACvD,SAAKT,UAAU;AACf,SAAKC,YAAYS,KAAKC,IAAIH,UAAU,CAAA;AACpC,SAAKN,YAAY;AACjB,SAAKE,eAAeK;EACrB;;;;EAKAG,WAAmB;AAClB,SAAKZ,UAAU;AACf,SAAKI,eAAe;AACpB,WAAO,KAAKH;EACb;;;;;EAMAY,aAA4B;AAC3B,QAAI,CAAC,KAAKb,SAAS;AAClB,aAAO;IACR;AAEA,QAAI,KAAKE,cAAc,GAAG;AACzB,WAAKA;AACL,aAAO,KAAKD;IACb;AAEA,SAAKC;AACL,QAAI,KAAKA,YAAY,KAAKC,YAAY;AACrC,WAAKD,YAAY;IAClB;AAEA,WAAO,KAAKD,YAAY,KAAKC;EAC9B;;;;EAKAY,kBAAwB;AACvB,QAAI,KAAKV,cAAc;AACtB,WAAKA,aAAY;IAClB;EACD;;;;EAKAW,aAAaC,QAAaC,UAA+B;AACxD,QAAI,CAACA,YAAY,CAACD,QAAQ;AACzB;IACD;AAGA,eAAWE,OAAOF,QAAQ;AACzB,UAAIG,OAAOC,OAAOJ,QAAQE,GAAAA,KAAQ,CAAC,KAAKG,eAAeH,GAAAA,GAAM;AAC5D,eAAOF,OAAOE,GAAAA;MACf;IACD;AAGA,eAAWA,OAAOD,UAAU;AAC3B,UAAIE,OAAOC,OAAOH,UAAUC,GAAAA,GAAM;AACjCF,eAAOE,GAAAA,IAAOI,SAASL,SAASC,GAAAA,CAAI;MACrC;IACD;EACD;;;;EAKQG,eAAeH,KAAsB;AAE5C,UAAMK,iBAAiB;MACtB;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;;AAED,WAAOA,eAAeC,SAASN,GAAAA;EAChC;AAED;","names":["DEFAULT_RECORD_BUFFER_FRAMES","DEFAULT_LOOP_BUFFER_FRAMES","deepCopy","value","excluded","includes","Array","isArray","result","i","length","key","Object","hasOwn","StatePlayer","looping","loopStart","loopIndex","loopLength","loopCallback","DEFAULT_LOOP_BUFFER_FRAMES","isLooping","startLoop","position","callback","Math","max","stopLoop","updateLoop","executeCallback","restoreState","target","snapshot","key","Object","hasOwn","isProtectedKey","deepCopy","protected_keys","includes"]}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/constants.ts
|
|
5
|
+
var DEFAULT_RECORD_BUFFER_FRAMES = 60 * 30;
|
|
6
|
+
var DEFAULT_LOOP_BUFFER_FRAMES = 60 * 4;
|
|
7
|
+
|
|
8
|
+
// src/utils.ts
|
|
9
|
+
function deepCopy(value, excluded) {
|
|
10
|
+
if (value == null) {
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
if (excluded && excluded.includes(value)) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
const result = [];
|
|
21
|
+
for (let i = 0; i < value.length; i++) {
|
|
22
|
+
result[i] = deepCopy(value[i], excluded);
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
if (typeof value === "object") {
|
|
27
|
+
const result = {};
|
|
28
|
+
for (const key in value) {
|
|
29
|
+
if (Object.hasOwn(value, key)) {
|
|
30
|
+
result[key] = deepCopy(value[key], excluded);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
return excluded ? null : value;
|
|
36
|
+
}
|
|
37
|
+
__name(deepCopy, "deepCopy");
|
|
38
|
+
|
|
39
|
+
// src/playback/player.ts
|
|
40
|
+
var StatePlayer = class {
|
|
41
|
+
static {
|
|
42
|
+
__name(this, "StatePlayer");
|
|
43
|
+
}
|
|
44
|
+
looping = false;
|
|
45
|
+
loopStart = 0;
|
|
46
|
+
loopIndex = 0;
|
|
47
|
+
loopLength;
|
|
48
|
+
loopCallback = null;
|
|
49
|
+
constructor(loopLength = DEFAULT_LOOP_BUFFER_FRAMES) {
|
|
50
|
+
this.loopLength = loopLength;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Check if currently looping
|
|
54
|
+
*/
|
|
55
|
+
isLooping() {
|
|
56
|
+
return this.looping;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Start loop playback
|
|
60
|
+
*/
|
|
61
|
+
startLoop(position, callback) {
|
|
62
|
+
this.looping = true;
|
|
63
|
+
this.loopStart = Math.max(position, 1);
|
|
64
|
+
this.loopIndex = 0;
|
|
65
|
+
this.loopCallback = callback;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Stop loop playback
|
|
69
|
+
*/
|
|
70
|
+
stopLoop() {
|
|
71
|
+
this.looping = false;
|
|
72
|
+
this.loopCallback = null;
|
|
73
|
+
return this.loopStart;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Update loop (call each frame)
|
|
77
|
+
* Returns position to replay, or null if not looping
|
|
78
|
+
*/
|
|
79
|
+
updateLoop() {
|
|
80
|
+
if (!this.looping) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
if (this.loopIndex === 0) {
|
|
84
|
+
this.loopIndex++;
|
|
85
|
+
return this.loopStart;
|
|
86
|
+
}
|
|
87
|
+
this.loopIndex++;
|
|
88
|
+
if (this.loopIndex > this.loopLength) {
|
|
89
|
+
this.loopIndex = 0;
|
|
90
|
+
}
|
|
91
|
+
return this.loopStart - this.loopIndex;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Execute loop callback
|
|
95
|
+
*/
|
|
96
|
+
executeCallback() {
|
|
97
|
+
if (this.loopCallback) {
|
|
98
|
+
this.loopCallback();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Restore state to target object
|
|
103
|
+
*/
|
|
104
|
+
restoreState(target, snapshot) {
|
|
105
|
+
if (!snapshot || !target) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
for (const key in target) {
|
|
109
|
+
if (Object.hasOwn(target, key) && !this.isProtectedKey(key)) {
|
|
110
|
+
delete target[key];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
for (const key in snapshot) {
|
|
114
|
+
if (Object.hasOwn(snapshot, key)) {
|
|
115
|
+
target[key] = deepCopy(snapshot[key]);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Check if key should be protected from restoration
|
|
121
|
+
*/
|
|
122
|
+
isProtectedKey(key) {
|
|
123
|
+
const protected_keys = [
|
|
124
|
+
"screen",
|
|
125
|
+
"audio",
|
|
126
|
+
"keyboard",
|
|
127
|
+
"mouse",
|
|
128
|
+
"touch",
|
|
129
|
+
"gamepad",
|
|
130
|
+
"system",
|
|
131
|
+
"storage",
|
|
132
|
+
"sprites",
|
|
133
|
+
"maps",
|
|
134
|
+
"sounds",
|
|
135
|
+
"music",
|
|
136
|
+
"assets",
|
|
137
|
+
"host",
|
|
138
|
+
"session",
|
|
139
|
+
"memory"
|
|
140
|
+
];
|
|
141
|
+
return protected_keys.includes(key);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
export {
|
|
145
|
+
StatePlayer
|
|
146
|
+
};
|
|
147
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/constants.ts","../../src/utils.ts","../../src/playback/player.ts"],"sourcesContent":["/** Default recording buffer: 30 seconds at 60fps (1800 frames) */\nexport const DEFAULT_RECORD_BUFFER_FRAMES = 60 * 30;\n\n/** Default loop playback buffer: 4 seconds at 60fps (240 frames) */\nexport const DEFAULT_LOOP_BUFFER_FRAMES = 60 * 4;\n","/**\n * Deep copy a value, optionally skipping excluded references.\n *\n * @param value - The value to deep copy\n * @param excluded - Optional array of object references to skip (replaced with null)\n */\nexport function deepCopy(value: any, excluded?: any[]): any {\n\tif (value == null) {\n\t\treturn value;\n\t}\n\n\tif (excluded && excluded.includes(value)) {\n\t\treturn null;\n\t}\n\n\tif (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n\t\treturn value;\n\t}\n\n\tif (Array.isArray(value)) {\n\t\tconst result: any[] = [];\n\t\tfor (let i = 0; i < value.length; i++) {\n\t\t\tresult[i] = deepCopy(value[i], excluded);\n\t\t}\n\t\treturn result;\n\t}\n\n\tif (typeof value === \"object\") {\n\t\tconst result: any = {};\n\t\tfor (const key in value) {\n\t\t\tif (Object.hasOwn(value, key)) {\n\t\t\t\tresult[key] = deepCopy(value[key], excluded);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\t// Non-serializable types: return null when filtering, passthrough otherwise\n\treturn excluded ? null : value;\n}\n","/**\n * StatePlayer - Handles replay and loop playback\n *\n * Responsibilities:\n * - Restore state snapshots\n * - Manage loop playback\n * - Control playback position\n */\nimport { DEFAULT_LOOP_BUFFER_FRAMES } from \"../constants\";\nimport { deepCopy } from \"../utils\";\n\nimport type { StateSnapshot } from \"../types\";\n\nexport class StatePlayer {\n\tprivate looping = false;\n\tprivate loopStart = 0;\n\tprivate loopIndex = 0;\n\tprivate loopLength: number;\n\tprivate loopCallback: (() => void) | null = null;\n\n\tconstructor(loopLength = DEFAULT_LOOP_BUFFER_FRAMES) {\n\t\tthis.loopLength = loopLength;\n\t}\n\n\t/**\n\t * Check if currently looping\n\t */\n\tisLooping(): boolean {\n\t\treturn this.looping;\n\t}\n\n\t/**\n\t * Start loop playback\n\t */\n\tstartLoop(position: number, callback: () => void): void {\n\t\tthis.looping = true;\n\t\tthis.loopStart = Math.max(position, 1);\n\t\tthis.loopIndex = 0;\n\t\tthis.loopCallback = callback;\n\t}\n\n\t/**\n\t * Stop loop playback\n\t */\n\tstopLoop(): number {\n\t\tthis.looping = false;\n\t\tthis.loopCallback = null;\n\t\treturn this.loopStart;\n\t}\n\n\t/**\n\t * Update loop (call each frame)\n\t * Returns position to replay, or null if not looping\n\t */\n\tupdateLoop(): number | null {\n\t\tif (!this.looping) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (this.loopIndex === 0) {\n\t\t\tthis.loopIndex++;\n\t\t\treturn this.loopStart;\n\t\t}\n\n\t\tthis.loopIndex++;\n\t\tif (this.loopIndex > this.loopLength) {\n\t\t\tthis.loopIndex = 0;\n\t\t}\n\n\t\treturn this.loopStart - this.loopIndex;\n\t}\n\n\t/**\n\t * Execute loop callback\n\t */\n\texecuteCallback(): void {\n\t\tif (this.loopCallback) {\n\t\t\tthis.loopCallback();\n\t\t}\n\t}\n\n\t/**\n\t * Restore state to target object\n\t */\n\trestoreState(target: any, snapshot: StateSnapshot): void {\n\t\tif (!snapshot || !target) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Remove all existing properties except protected system APIs\n\t\tfor (const key in target) {\n\t\t\tif (Object.hasOwn(target, key) && !this.isProtectedKey(key)) {\n\t\t\t\tdelete target[key];\n\t\t\t}\n\t\t}\n\n\t\t// Apply snapshot properties to target object via deep copy\n\t\tfor (const key in snapshot) {\n\t\t\tif (Object.hasOwn(snapshot, key)) {\n\t\t\t\ttarget[key] = deepCopy(snapshot[key]);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Check if key should be protected from restoration\n\t */\n\tprivate isProtectedKey(key: string): boolean {\n\t\t// Prevent system APIs and runtime objects from being overwritten during restore\n\t\tconst protected_keys = [\n\t\t\t\"screen\",\n\t\t\t\"audio\",\n\t\t\t\"keyboard\",\n\t\t\t\"mouse\",\n\t\t\t\"touch\",\n\t\t\t\"gamepad\",\n\t\t\t\"system\",\n\t\t\t\"storage\",\n\t\t\t\"sprites\",\n\t\t\t\"maps\",\n\t\t\t\"sounds\",\n\t\t\t\"music\",\n\t\t\t\"assets\",\n\t\t\t\"host\",\n\t\t\t\"session\",\n\t\t\t\"memory\",\n\t\t];\n\t\treturn protected_keys.includes(key);\n\t}\n\n}\n"],"mappings":";;;;AACO,IAAMA,+BAA+B,KAAK;AAG1C,IAAMC,6BAA6B,KAAK;;;ACExC,SAASC,SAASC,OAAYC,UAAgB;AACpD,MAAID,SAAS,MAAM;AAClB,WAAOA;EACR;AAEA,MAAIC,YAAYA,SAASC,SAASF,KAAAA,GAAQ;AACzC,WAAO;EACR;AAEA,MAAI,OAAOA,UAAU,YAAY,OAAOA,UAAU,YAAY,OAAOA,UAAU,WAAW;AACzF,WAAOA;EACR;AAEA,MAAIG,MAAMC,QAAQJ,KAAAA,GAAQ;AACzB,UAAMK,SAAgB,CAAA;AACtB,aAASC,IAAI,GAAGA,IAAIN,MAAMO,QAAQD,KAAK;AACtCD,aAAOC,CAAAA,IAAKP,SAASC,MAAMM,CAAAA,GAAIL,QAAAA;IAChC;AACA,WAAOI;EACR;AAEA,MAAI,OAAOL,UAAU,UAAU;AAC9B,UAAMK,SAAc,CAAC;AACrB,eAAWG,OAAOR,OAAO;AACxB,UAAIS,OAAOC,OAAOV,OAAOQ,GAAAA,GAAM;AAC9BH,eAAOG,GAAAA,IAAOT,SAASC,MAAMQ,GAAAA,GAAMP,QAAAA;MACpC;IACD;AACA,WAAOI;EACR;AAGA,SAAOJ,WAAW,OAAOD;AAC1B;AAjCgBD;;;ACOT,IAAMY,cAAN,MAAMA;EAbb,OAaaA;;;EACJC,UAAU;EACVC,YAAY;EACZC,YAAY;EACZC;EACAC,eAAoC;EAE5C,YAAYD,aAAaE,4BAA4B;AACpD,SAAKF,aAAaA;EACnB;;;;EAKAG,YAAqB;AACpB,WAAO,KAAKN;EACb;;;;EAKAO,UAAUC,UAAkBC,UAA4B;AACvD,SAAKT,UAAU;AACf,SAAKC,YAAYS,KAAKC,IAAIH,UAAU,CAAA;AACpC,SAAKN,YAAY;AACjB,SAAKE,eAAeK;EACrB;;;;EAKAG,WAAmB;AAClB,SAAKZ,UAAU;AACf,SAAKI,eAAe;AACpB,WAAO,KAAKH;EACb;;;;;EAMAY,aAA4B;AAC3B,QAAI,CAAC,KAAKb,SAAS;AAClB,aAAO;IACR;AAEA,QAAI,KAAKE,cAAc,GAAG;AACzB,WAAKA;AACL,aAAO,KAAKD;IACb;AAEA,SAAKC;AACL,QAAI,KAAKA,YAAY,KAAKC,YAAY;AACrC,WAAKD,YAAY;IAClB;AAEA,WAAO,KAAKD,YAAY,KAAKC;EAC9B;;;;EAKAY,kBAAwB;AACvB,QAAI,KAAKV,cAAc;AACtB,WAAKA,aAAY;IAClB;EACD;;;;EAKAW,aAAaC,QAAaC,UAA+B;AACxD,QAAI,CAACA,YAAY,CAACD,QAAQ;AACzB;IACD;AAGA,eAAWE,OAAOF,QAAQ;AACzB,UAAIG,OAAOC,OAAOJ,QAAQE,GAAAA,KAAQ,CAAC,KAAKG,eAAeH,GAAAA,GAAM;AAC5D,eAAOF,OAAOE,GAAAA;MACf;IACD;AAGA,eAAWA,OAAOD,UAAU;AAC3B,UAAIE,OAAOC,OAAOH,UAAUC,GAAAA,GAAM;AACjCF,eAAOE,GAAAA,IAAOI,SAASL,SAASC,GAAAA,CAAI;MACrC;IACD;EACD;;;;EAKQG,eAAeH,KAAsB;AAE5C,UAAMK,iBAAiB;MACtB;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;;AAED,WAAOA,eAAeC,SAASN,GAAAA;EAChC;AAED;","names":["DEFAULT_RECORD_BUFFER_FRAMES","DEFAULT_LOOP_BUFFER_FRAMES","deepCopy","value","excluded","includes","Array","isArray","result","i","length","key","Object","hasOwn","StatePlayer","looping","loopStart","loopIndex","loopLength","loopCallback","DEFAULT_LOOP_BUFFER_FRAMES","isLooping","startLoop","position","callback","Math","max","stopLoop","updateLoop","executeCallback","restoreState","target","snapshot","key","Object","hasOwn","isProtectedKey","deepCopy","protected_keys","includes"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { StateSnapshot } from '../types/state.mjs';
|
|
2
|
+
|
|
3
|
+
declare class StatePlayer {
|
|
4
|
+
private looping;
|
|
5
|
+
private loopStart;
|
|
6
|
+
private loopIndex;
|
|
7
|
+
private loopLength;
|
|
8
|
+
private loopCallback;
|
|
9
|
+
constructor(loopLength?: number);
|
|
10
|
+
/**
|
|
11
|
+
* Check if currently looping
|
|
12
|
+
*/
|
|
13
|
+
isLooping(): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Start loop playback
|
|
16
|
+
*/
|
|
17
|
+
startLoop(position: number, callback: () => void): void;
|
|
18
|
+
/**
|
|
19
|
+
* Stop loop playback
|
|
20
|
+
*/
|
|
21
|
+
stopLoop(): number;
|
|
22
|
+
/**
|
|
23
|
+
* Update loop (call each frame)
|
|
24
|
+
* Returns position to replay, or null if not looping
|
|
25
|
+
*/
|
|
26
|
+
updateLoop(): number | null;
|
|
27
|
+
/**
|
|
28
|
+
* Execute loop callback
|
|
29
|
+
*/
|
|
30
|
+
executeCallback(): void;
|
|
31
|
+
/**
|
|
32
|
+
* Restore state to target object
|
|
33
|
+
*/
|
|
34
|
+
restoreState(target: any, snapshot: StateSnapshot): void;
|
|
35
|
+
/**
|
|
36
|
+
* Check if key should be protected from restoration
|
|
37
|
+
*/
|
|
38
|
+
private isProtectedKey;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { StatePlayer };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { StateSnapshot } from '../types/state.js';
|
|
2
|
+
|
|
3
|
+
declare class StatePlayer {
|
|
4
|
+
private looping;
|
|
5
|
+
private loopStart;
|
|
6
|
+
private loopIndex;
|
|
7
|
+
private loopLength;
|
|
8
|
+
private loopCallback;
|
|
9
|
+
constructor(loopLength?: number);
|
|
10
|
+
/**
|
|
11
|
+
* Check if currently looping
|
|
12
|
+
*/
|
|
13
|
+
isLooping(): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Start loop playback
|
|
16
|
+
*/
|
|
17
|
+
startLoop(position: number, callback: () => void): void;
|
|
18
|
+
/**
|
|
19
|
+
* Stop loop playback
|
|
20
|
+
*/
|
|
21
|
+
stopLoop(): number;
|
|
22
|
+
/**
|
|
23
|
+
* Update loop (call each frame)
|
|
24
|
+
* Returns position to replay, or null if not looping
|
|
25
|
+
*/
|
|
26
|
+
updateLoop(): number | null;
|
|
27
|
+
/**
|
|
28
|
+
* Execute loop callback
|
|
29
|
+
*/
|
|
30
|
+
executeCallback(): void;
|
|
31
|
+
/**
|
|
32
|
+
* Restore state to target object
|
|
33
|
+
*/
|
|
34
|
+
restoreState(target: any, snapshot: StateSnapshot): void;
|
|
35
|
+
/**
|
|
36
|
+
* Check if key should be protected from restoration
|
|
37
|
+
*/
|
|
38
|
+
private isProtectedKey;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { StatePlayer };
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/playback/player.ts
|
|
22
|
+
var player_exports = {};
|
|
23
|
+
__export(player_exports, {
|
|
24
|
+
StatePlayer: () => StatePlayer
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(player_exports);
|
|
27
|
+
|
|
28
|
+
// src/constants.ts
|
|
29
|
+
var DEFAULT_RECORD_BUFFER_FRAMES = 60 * 30;
|
|
30
|
+
var DEFAULT_LOOP_BUFFER_FRAMES = 60 * 4;
|
|
31
|
+
|
|
32
|
+
// src/utils.ts
|
|
33
|
+
function deepCopy(value, excluded) {
|
|
34
|
+
if (value == null) {
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
if (excluded && excluded.includes(value)) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
if (Array.isArray(value)) {
|
|
44
|
+
const result = [];
|
|
45
|
+
for (let i = 0; i < value.length; i++) {
|
|
46
|
+
result[i] = deepCopy(value[i], excluded);
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
if (typeof value === "object") {
|
|
51
|
+
const result = {};
|
|
52
|
+
for (const key in value) {
|
|
53
|
+
if (Object.hasOwn(value, key)) {
|
|
54
|
+
result[key] = deepCopy(value[key], excluded);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
return excluded ? null : value;
|
|
60
|
+
}
|
|
61
|
+
__name(deepCopy, "deepCopy");
|
|
62
|
+
|
|
63
|
+
// src/playback/player.ts
|
|
64
|
+
var StatePlayer = class {
|
|
65
|
+
static {
|
|
66
|
+
__name(this, "StatePlayer");
|
|
67
|
+
}
|
|
68
|
+
looping = false;
|
|
69
|
+
loopStart = 0;
|
|
70
|
+
loopIndex = 0;
|
|
71
|
+
loopLength;
|
|
72
|
+
loopCallback = null;
|
|
73
|
+
constructor(loopLength = DEFAULT_LOOP_BUFFER_FRAMES) {
|
|
74
|
+
this.loopLength = loopLength;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Check if currently looping
|
|
78
|
+
*/
|
|
79
|
+
isLooping() {
|
|
80
|
+
return this.looping;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Start loop playback
|
|
84
|
+
*/
|
|
85
|
+
startLoop(position, callback) {
|
|
86
|
+
this.looping = true;
|
|
87
|
+
this.loopStart = Math.max(position, 1);
|
|
88
|
+
this.loopIndex = 0;
|
|
89
|
+
this.loopCallback = callback;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Stop loop playback
|
|
93
|
+
*/
|
|
94
|
+
stopLoop() {
|
|
95
|
+
this.looping = false;
|
|
96
|
+
this.loopCallback = null;
|
|
97
|
+
return this.loopStart;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Update loop (call each frame)
|
|
101
|
+
* Returns position to replay, or null if not looping
|
|
102
|
+
*/
|
|
103
|
+
updateLoop() {
|
|
104
|
+
if (!this.looping) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
if (this.loopIndex === 0) {
|
|
108
|
+
this.loopIndex++;
|
|
109
|
+
return this.loopStart;
|
|
110
|
+
}
|
|
111
|
+
this.loopIndex++;
|
|
112
|
+
if (this.loopIndex > this.loopLength) {
|
|
113
|
+
this.loopIndex = 0;
|
|
114
|
+
}
|
|
115
|
+
return this.loopStart - this.loopIndex;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Execute loop callback
|
|
119
|
+
*/
|
|
120
|
+
executeCallback() {
|
|
121
|
+
if (this.loopCallback) {
|
|
122
|
+
this.loopCallback();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Restore state to target object
|
|
127
|
+
*/
|
|
128
|
+
restoreState(target, snapshot) {
|
|
129
|
+
if (!snapshot || !target) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
for (const key in target) {
|
|
133
|
+
if (Object.hasOwn(target, key) && !this.isProtectedKey(key)) {
|
|
134
|
+
delete target[key];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
for (const key in snapshot) {
|
|
138
|
+
if (Object.hasOwn(snapshot, key)) {
|
|
139
|
+
target[key] = deepCopy(snapshot[key]);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Check if key should be protected from restoration
|
|
145
|
+
*/
|
|
146
|
+
isProtectedKey(key) {
|
|
147
|
+
const protected_keys = [
|
|
148
|
+
"screen",
|
|
149
|
+
"audio",
|
|
150
|
+
"keyboard",
|
|
151
|
+
"mouse",
|
|
152
|
+
"touch",
|
|
153
|
+
"gamepad",
|
|
154
|
+
"system",
|
|
155
|
+
"storage",
|
|
156
|
+
"sprites",
|
|
157
|
+
"maps",
|
|
158
|
+
"sounds",
|
|
159
|
+
"music",
|
|
160
|
+
"assets",
|
|
161
|
+
"host",
|
|
162
|
+
"session",
|
|
163
|
+
"memory"
|
|
164
|
+
];
|
|
165
|
+
return protected_keys.includes(key);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
169
|
+
0 && (module.exports = {
|
|
170
|
+
StatePlayer
|
|
171
|
+
});
|
|
172
|
+
//# sourceMappingURL=player.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/playback/player.ts","../../src/constants.ts","../../src/utils.ts"],"sourcesContent":["/**\n * StatePlayer - Handles replay and loop playback\n *\n * Responsibilities:\n * - Restore state snapshots\n * - Manage loop playback\n * - Control playback position\n */\nimport { DEFAULT_LOOP_BUFFER_FRAMES } from \"../constants\";\nimport { deepCopy } from \"../utils\";\n\nimport type { StateSnapshot } from \"../types\";\n\nexport class StatePlayer {\n\tprivate looping = false;\n\tprivate loopStart = 0;\n\tprivate loopIndex = 0;\n\tprivate loopLength: number;\n\tprivate loopCallback: (() => void) | null = null;\n\n\tconstructor(loopLength = DEFAULT_LOOP_BUFFER_FRAMES) {\n\t\tthis.loopLength = loopLength;\n\t}\n\n\t/**\n\t * Check if currently looping\n\t */\n\tisLooping(): boolean {\n\t\treturn this.looping;\n\t}\n\n\t/**\n\t * Start loop playback\n\t */\n\tstartLoop(position: number, callback: () => void): void {\n\t\tthis.looping = true;\n\t\tthis.loopStart = Math.max(position, 1);\n\t\tthis.loopIndex = 0;\n\t\tthis.loopCallback = callback;\n\t}\n\n\t/**\n\t * Stop loop playback\n\t */\n\tstopLoop(): number {\n\t\tthis.looping = false;\n\t\tthis.loopCallback = null;\n\t\treturn this.loopStart;\n\t}\n\n\t/**\n\t * Update loop (call each frame)\n\t * Returns position to replay, or null if not looping\n\t */\n\tupdateLoop(): number | null {\n\t\tif (!this.looping) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (this.loopIndex === 0) {\n\t\t\tthis.loopIndex++;\n\t\t\treturn this.loopStart;\n\t\t}\n\n\t\tthis.loopIndex++;\n\t\tif (this.loopIndex > this.loopLength) {\n\t\t\tthis.loopIndex = 0;\n\t\t}\n\n\t\treturn this.loopStart - this.loopIndex;\n\t}\n\n\t/**\n\t * Execute loop callback\n\t */\n\texecuteCallback(): void {\n\t\tif (this.loopCallback) {\n\t\t\tthis.loopCallback();\n\t\t}\n\t}\n\n\t/**\n\t * Restore state to target object\n\t */\n\trestoreState(target: any, snapshot: StateSnapshot): void {\n\t\tif (!snapshot || !target) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Remove all existing properties except protected system APIs\n\t\tfor (const key in target) {\n\t\t\tif (Object.hasOwn(target, key) && !this.isProtectedKey(key)) {\n\t\t\t\tdelete target[key];\n\t\t\t}\n\t\t}\n\n\t\t// Apply snapshot properties to target object via deep copy\n\t\tfor (const key in snapshot) {\n\t\t\tif (Object.hasOwn(snapshot, key)) {\n\t\t\t\ttarget[key] = deepCopy(snapshot[key]);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Check if key should be protected from restoration\n\t */\n\tprivate isProtectedKey(key: string): boolean {\n\t\t// Prevent system APIs and runtime objects from being overwritten during restore\n\t\tconst protected_keys = [\n\t\t\t\"screen\",\n\t\t\t\"audio\",\n\t\t\t\"keyboard\",\n\t\t\t\"mouse\",\n\t\t\t\"touch\",\n\t\t\t\"gamepad\",\n\t\t\t\"system\",\n\t\t\t\"storage\",\n\t\t\t\"sprites\",\n\t\t\t\"maps\",\n\t\t\t\"sounds\",\n\t\t\t\"music\",\n\t\t\t\"assets\",\n\t\t\t\"host\",\n\t\t\t\"session\",\n\t\t\t\"memory\",\n\t\t];\n\t\treturn protected_keys.includes(key);\n\t}\n\n}\n","/** Default recording buffer: 30 seconds at 60fps (1800 frames) */\nexport const DEFAULT_RECORD_BUFFER_FRAMES = 60 * 30;\n\n/** Default loop playback buffer: 4 seconds at 60fps (240 frames) */\nexport const DEFAULT_LOOP_BUFFER_FRAMES = 60 * 4;\n","/**\n * Deep copy a value, optionally skipping excluded references.\n *\n * @param value - The value to deep copy\n * @param excluded - Optional array of object references to skip (replaced with null)\n */\nexport function deepCopy(value: any, excluded?: any[]): any {\n\tif (value == null) {\n\t\treturn value;\n\t}\n\n\tif (excluded && excluded.includes(value)) {\n\t\treturn null;\n\t}\n\n\tif (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n\t\treturn value;\n\t}\n\n\tif (Array.isArray(value)) {\n\t\tconst result: any[] = [];\n\t\tfor (let i = 0; i < value.length; i++) {\n\t\t\tresult[i] = deepCopy(value[i], excluded);\n\t\t}\n\t\treturn result;\n\t}\n\n\tif (typeof value === \"object\") {\n\t\tconst result: any = {};\n\t\tfor (const key in value) {\n\t\t\tif (Object.hasOwn(value, key)) {\n\t\t\t\tresult[key] = deepCopy(value[key], excluded);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\t// Non-serializable types: return null when filtering, passthrough otherwise\n\treturn excluded ? null : value;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;ACCO,IAAMA,+BAA+B,KAAK;AAG1C,IAAMC,6BAA6B,KAAK;;;ACExC,SAASC,SAASC,OAAYC,UAAgB;AACpD,MAAID,SAAS,MAAM;AAClB,WAAOA;EACR;AAEA,MAAIC,YAAYA,SAASC,SAASF,KAAAA,GAAQ;AACzC,WAAO;EACR;AAEA,MAAI,OAAOA,UAAU,YAAY,OAAOA,UAAU,YAAY,OAAOA,UAAU,WAAW;AACzF,WAAOA;EACR;AAEA,MAAIG,MAAMC,QAAQJ,KAAAA,GAAQ;AACzB,UAAMK,SAAgB,CAAA;AACtB,aAASC,IAAI,GAAGA,IAAIN,MAAMO,QAAQD,KAAK;AACtCD,aAAOC,CAAAA,IAAKP,SAASC,MAAMM,CAAAA,GAAIL,QAAAA;IAChC;AACA,WAAOI;EACR;AAEA,MAAI,OAAOL,UAAU,UAAU;AAC9B,UAAMK,SAAc,CAAC;AACrB,eAAWG,OAAOR,OAAO;AACxB,UAAIS,OAAOC,OAAOV,OAAOQ,GAAAA,GAAM;AAC9BH,eAAOG,GAAAA,IAAOT,SAASC,MAAMQ,GAAAA,GAAMP,QAAAA;MACpC;IACD;AACA,WAAOI;EACR;AAGA,SAAOJ,WAAW,OAAOD;AAC1B;AAjCgBD;;;AFOT,IAAMY,cAAN,MAAMA;EAbb,OAaaA;;;EACJC,UAAU;EACVC,YAAY;EACZC,YAAY;EACZC;EACAC,eAAoC;EAE5C,YAAYD,aAAaE,4BAA4B;AACpD,SAAKF,aAAaA;EACnB;;;;EAKAG,YAAqB;AACpB,WAAO,KAAKN;EACb;;;;EAKAO,UAAUC,UAAkBC,UAA4B;AACvD,SAAKT,UAAU;AACf,SAAKC,YAAYS,KAAKC,IAAIH,UAAU,CAAA;AACpC,SAAKN,YAAY;AACjB,SAAKE,eAAeK;EACrB;;;;EAKAG,WAAmB;AAClB,SAAKZ,UAAU;AACf,SAAKI,eAAe;AACpB,WAAO,KAAKH;EACb;;;;;EAMAY,aAA4B;AAC3B,QAAI,CAAC,KAAKb,SAAS;AAClB,aAAO;IACR;AAEA,QAAI,KAAKE,cAAc,GAAG;AACzB,WAAKA;AACL,aAAO,KAAKD;IACb;AAEA,SAAKC;AACL,QAAI,KAAKA,YAAY,KAAKC,YAAY;AACrC,WAAKD,YAAY;IAClB;AAEA,WAAO,KAAKD,YAAY,KAAKC;EAC9B;;;;EAKAY,kBAAwB;AACvB,QAAI,KAAKV,cAAc;AACtB,WAAKA,aAAY;IAClB;EACD;;;;EAKAW,aAAaC,QAAaC,UAA+B;AACxD,QAAI,CAACA,YAAY,CAACD,QAAQ;AACzB;IACD;AAGA,eAAWE,OAAOF,QAAQ;AACzB,UAAIG,OAAOC,OAAOJ,QAAQE,GAAAA,KAAQ,CAAC,KAAKG,eAAeH,GAAAA,GAAM;AAC5D,eAAOF,OAAOE,GAAAA;MACf;IACD;AAGA,eAAWA,OAAOD,UAAU;AAC3B,UAAIE,OAAOC,OAAOH,UAAUC,GAAAA,GAAM;AACjCF,eAAOE,GAAAA,IAAOI,SAASL,SAASC,GAAAA,CAAI;MACrC;IACD;EACD;;;;EAKQG,eAAeH,KAAsB;AAE5C,UAAMK,iBAAiB;MACtB;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;;AAED,WAAOA,eAAeC,SAASN,GAAAA;EAChC;AAED;","names":["DEFAULT_RECORD_BUFFER_FRAMES","DEFAULT_LOOP_BUFFER_FRAMES","deepCopy","value","excluded","includes","Array","isArray","result","i","length","key","Object","hasOwn","StatePlayer","looping","loopStart","loopIndex","loopLength","loopCallback","DEFAULT_LOOP_BUFFER_FRAMES","isLooping","startLoop","position","callback","Math","max","stopLoop","updateLoop","executeCallback","restoreState","target","snapshot","key","Object","hasOwn","isProtectedKey","deepCopy","protected_keys","includes"]}
|