@adatechnology/game-runtime 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +33 -2
- package/dist/index.d.ts +183 -0
- package/dist/index.global.js +573 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +503 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -3
package/dist/index.js
ADDED
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { DEFERRED_PRIORITY, FRAME_PHASES as FRAME_PHASES3 } from "@adatechnology/game-contracts";
|
|
3
|
+
|
|
4
|
+
// src/budget-entry.ts
|
|
5
|
+
var DEFAULT_MIN_EVERY_FRAMES = 4;
|
|
6
|
+
function createQueueEntry(queue, options) {
|
|
7
|
+
return {
|
|
8
|
+
queue,
|
|
9
|
+
phase: options.phase ?? "idle",
|
|
10
|
+
priority: options.priority,
|
|
11
|
+
minEveryFrames: options.minEveryFrames ?? DEFAULT_MIN_EVERY_FRAMES,
|
|
12
|
+
reserveMs: options.reserveMs ?? 0,
|
|
13
|
+
reserveLeft: 0,
|
|
14
|
+
waitingFrames: 0,
|
|
15
|
+
maxWaitingFrames: 0,
|
|
16
|
+
ranUnits: 0,
|
|
17
|
+
spentMs: 0,
|
|
18
|
+
forced: 0,
|
|
19
|
+
lastPending: 0
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function insertByPriority(entries, entry) {
|
|
23
|
+
let index = entries.length;
|
|
24
|
+
while (index > 0 && entries[index - 1].priority < entry.priority) index--;
|
|
25
|
+
entries.splice(index, 0, entry);
|
|
26
|
+
}
|
|
27
|
+
function queueStats(entry) {
|
|
28
|
+
return {
|
|
29
|
+
id: entry.queue.id,
|
|
30
|
+
pending: entry.lastPending,
|
|
31
|
+
ranUnits: entry.ranUnits,
|
|
32
|
+
spentMs: entry.spentMs,
|
|
33
|
+
framesWaiting: entry.waitingFrames,
|
|
34
|
+
maxFramesWaiting: entry.maxWaitingFrames,
|
|
35
|
+
forced: entry.forced
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/clock.ts
|
|
40
|
+
var MICROS_PER_SECOND = 1e6;
|
|
41
|
+
var toMicros = (seconds) => Math.round(seconds * MICROS_PER_SECOND);
|
|
42
|
+
function defaultClock() {
|
|
43
|
+
if (typeof performance !== "undefined" && typeof performance.now === "function") return () => performance.now();
|
|
44
|
+
return () => Date.now();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/budget.ts
|
|
48
|
+
var DEFAULT_MS = 4;
|
|
49
|
+
var MAX_RESERVE_SHARE = 0.5;
|
|
50
|
+
function createFrameBudget(options = {}) {
|
|
51
|
+
const clock = options.clock ?? defaultClock();
|
|
52
|
+
const entries = [];
|
|
53
|
+
const reportedQueues = /* @__PURE__ */ new Set();
|
|
54
|
+
let frame = 0;
|
|
55
|
+
let frameOpen = false;
|
|
56
|
+
let debt = 0;
|
|
57
|
+
let common = 0;
|
|
58
|
+
let spentThisFrame = 0;
|
|
59
|
+
let unbounded = false;
|
|
60
|
+
const reservedTotal = () => entries.reduce((sum, entry) => sum + entry.reserveMs, 0);
|
|
61
|
+
const beginFrame = () => {
|
|
62
|
+
frame++;
|
|
63
|
+
frameOpen = true;
|
|
64
|
+
spentThisFrame = 0;
|
|
65
|
+
let available = budget.ms;
|
|
66
|
+
for (const entry of entries) {
|
|
67
|
+
entry.reserveLeft = 0;
|
|
68
|
+
if (entry.reserveMs <= 0 || entry.queue.pending(frame) <= 0) continue;
|
|
69
|
+
entry.reserveLeft = Math.min(entry.reserveMs, available);
|
|
70
|
+
available -= entry.reserveLeft;
|
|
71
|
+
}
|
|
72
|
+
common = Math.max(0, available - debt);
|
|
73
|
+
debt = 0;
|
|
74
|
+
};
|
|
75
|
+
const reportError = (error, entry) => {
|
|
76
|
+
if (options.onError) return options.onError(error, { id: entry.queue.id, phase: entry.phase });
|
|
77
|
+
if (reportedQueues.has(entry.queue.id)) return;
|
|
78
|
+
reportedQueues.add(entry.queue.id);
|
|
79
|
+
console.error(`game-runtime: queue ${entry.queue.id} threw`, error);
|
|
80
|
+
};
|
|
81
|
+
const runQueue = (entry, deadline) => {
|
|
82
|
+
try {
|
|
83
|
+
return entry.queue.run(deadline, clock);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
reportError(error, entry);
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
const runEntry = (entry, deadline) => {
|
|
90
|
+
const started = clock();
|
|
91
|
+
const ran = runQueue(entry, deadline);
|
|
92
|
+
const spent = Math.max(0, clock() - started);
|
|
93
|
+
entry.ranUnits += ran;
|
|
94
|
+
entry.spentMs += spent;
|
|
95
|
+
entry.waitingFrames = 0;
|
|
96
|
+
spentThisFrame += spent;
|
|
97
|
+
if (unbounded) return;
|
|
98
|
+
const fromReserve = Math.min(entry.reserveLeft, spent);
|
|
99
|
+
entry.reserveLeft -= fromReserve;
|
|
100
|
+
common -= spent - fromReserve;
|
|
101
|
+
if (common < 0) {
|
|
102
|
+
debt = Math.min(budget.ms, debt - common);
|
|
103
|
+
common = 0;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
const drainEntry = (entry) => {
|
|
107
|
+
entry.lastPending = entry.queue.pending(frame);
|
|
108
|
+
if (entry.lastPending <= 0) {
|
|
109
|
+
entry.waitingFrames = 0;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (unbounded) return runEntry(entry, Infinity);
|
|
113
|
+
const allowance = entry.reserveLeft + common;
|
|
114
|
+
if (allowance > 0) return runEntry(entry, clock() + allowance);
|
|
115
|
+
entry.waitingFrames++;
|
|
116
|
+
entry.maxWaitingFrames = Math.max(entry.maxWaitingFrames, entry.waitingFrames);
|
|
117
|
+
if (entry.waitingFrames < entry.minEveryFrames) return;
|
|
118
|
+
entry.forced++;
|
|
119
|
+
runEntry(entry, clock());
|
|
120
|
+
};
|
|
121
|
+
const budget = {
|
|
122
|
+
ms: options.ms ?? DEFAULT_MS,
|
|
123
|
+
register(queue, queueOptions) {
|
|
124
|
+
if (entries.some((candidate) => candidate.queue === queue)) throw new Error(`budget.register(${queue.id}): this queue is already registered`);
|
|
125
|
+
const reserveMs = queueOptions.reserveMs ?? 0;
|
|
126
|
+
if (reservedTotal() + reserveMs > budget.ms * MAX_RESERVE_SHARE) {
|
|
127
|
+
throw new Error(`budget.register(${queue.id}): the reserves would pass half of the ${budget.ms} ms budget`);
|
|
128
|
+
}
|
|
129
|
+
const entry = createQueueEntry(queue, queueOptions);
|
|
130
|
+
insertByPriority(entries, entry);
|
|
131
|
+
queue.attach?.();
|
|
132
|
+
return () => {
|
|
133
|
+
const at = entries.indexOf(entry);
|
|
134
|
+
if (at < 0) return;
|
|
135
|
+
entries.splice(at, 1);
|
|
136
|
+
queue.detach?.();
|
|
137
|
+
};
|
|
138
|
+
},
|
|
139
|
+
beginFrame,
|
|
140
|
+
drain(phase) {
|
|
141
|
+
if (phase === "present" && !frameOpen) beginFrame();
|
|
142
|
+
for (const entry of [...entries]) if (entry.phase === phase) drainEntry(entry);
|
|
143
|
+
if (phase === "idle") frameOpen = false;
|
|
144
|
+
},
|
|
145
|
+
flushAll() {
|
|
146
|
+
for (const entry of entries) entry.queue.flush();
|
|
147
|
+
},
|
|
148
|
+
get unbounded() {
|
|
149
|
+
return unbounded;
|
|
150
|
+
},
|
|
151
|
+
set unbounded(value) {
|
|
152
|
+
unbounded = value;
|
|
153
|
+
},
|
|
154
|
+
stats() {
|
|
155
|
+
return {
|
|
156
|
+
ms: budget.ms,
|
|
157
|
+
spentMs: spentThisFrame,
|
|
158
|
+
debtMs: debt,
|
|
159
|
+
queues: entries.map(queueStats)
|
|
160
|
+
};
|
|
161
|
+
},
|
|
162
|
+
dispose() {
|
|
163
|
+
for (const entry of entries.splice(0)) entry.queue.detach?.();
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
return budget;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/commands.ts
|
|
170
|
+
var COMMAND_SYSTEM_ORDER = -1e4;
|
|
171
|
+
var NONE = [];
|
|
172
|
+
function createCommandBuffer(nextTick) {
|
|
173
|
+
let pending = [];
|
|
174
|
+
return {
|
|
175
|
+
push(kind, payload, tick) {
|
|
176
|
+
pending.push({ kind, payload, tick: tick ?? nextTick() });
|
|
177
|
+
},
|
|
178
|
+
take(tick) {
|
|
179
|
+
if (pending.length === 0) return NONE;
|
|
180
|
+
const due = [];
|
|
181
|
+
const later = [];
|
|
182
|
+
for (const command of pending) (command.tick <= tick ? due : later).push(command);
|
|
183
|
+
pending = later;
|
|
184
|
+
return due;
|
|
185
|
+
},
|
|
186
|
+
clear() {
|
|
187
|
+
pending = [];
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/loop.ts
|
|
193
|
+
function startLoop(scheduler, options = {}) {
|
|
194
|
+
const requestFrame = options.requestFrame ?? globalThis.requestAnimationFrame?.bind(globalThis);
|
|
195
|
+
const cancelFrame = options.cancelFrame ?? globalThis.cancelAnimationFrame?.bind(globalThis);
|
|
196
|
+
if (!requestFrame) throw new Error("startLoop: no requestAnimationFrame here, pass requestFrame");
|
|
197
|
+
let last = options.startMs ?? (typeof performance !== "undefined" ? performance.now() : 0);
|
|
198
|
+
let handle = 0;
|
|
199
|
+
let stopped = false;
|
|
200
|
+
const onFrame = (nowMs) => {
|
|
201
|
+
if (stopped) return;
|
|
202
|
+
handle = requestFrame(onFrame);
|
|
203
|
+
const gap = options.minFrameMs?.() ?? 0;
|
|
204
|
+
if (gap > 0 && nowMs - last < gap) return;
|
|
205
|
+
const realSeconds = (nowMs - last) / 1e3;
|
|
206
|
+
last = nowMs;
|
|
207
|
+
options.onFrame?.(realSeconds);
|
|
208
|
+
scheduler.frame(realSeconds, options.skipSimulation?.() ? { simulate: false } : void 0);
|
|
209
|
+
};
|
|
210
|
+
const onVisibility = () => {
|
|
211
|
+
if (document.visibilityState !== "visible") return;
|
|
212
|
+
if (typeof performance !== "undefined") last = performance.now();
|
|
213
|
+
scheduler.resetClock();
|
|
214
|
+
};
|
|
215
|
+
const hasDocument = typeof document !== "undefined" && typeof document.addEventListener === "function";
|
|
216
|
+
if (hasDocument) document.addEventListener("visibilitychange", onVisibility);
|
|
217
|
+
handle = requestFrame(onFrame);
|
|
218
|
+
return () => {
|
|
219
|
+
stopped = true;
|
|
220
|
+
cancelFrame?.(handle);
|
|
221
|
+
if (hasDocument) document.removeEventListener("visibilitychange", onVisibility);
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/scheduler.ts
|
|
226
|
+
import { FRAME_PHASES as FRAME_PHASES2 } from "@adatechnology/game-contracts";
|
|
227
|
+
|
|
228
|
+
// src/frame-time.ts
|
|
229
|
+
var sanitizeSeconds = (seconds) => Number.isFinite(seconds) && seconds > 0 ? seconds : 0;
|
|
230
|
+
var sanitizeTimeScale = (scale) => Number.isFinite(scale) ? Math.max(0, scale) : 1;
|
|
231
|
+
function resolveFrameTime(realSeconds, exact, residual, config) {
|
|
232
|
+
const given = sanitizeSeconds(realSeconds);
|
|
233
|
+
if (exact) return { seconds: given, micros: toMicros(given), residual };
|
|
234
|
+
const clamped = Math.min(given, config.maxFrameSeconds);
|
|
235
|
+
const micros = toMicros(clamped) + residual;
|
|
236
|
+
const wholeSteps = Math.round(micros / config.fixedMicros);
|
|
237
|
+
const drift = micros - wholeSteps * config.fixedMicros;
|
|
238
|
+
if (config.snapMicros > 0 && wholeSteps >= 1 && Math.abs(drift) <= config.snapMicros) {
|
|
239
|
+
return { seconds: wholeSteps * config.fixedDt, micros: wholeSteps * config.fixedMicros, residual: drift };
|
|
240
|
+
}
|
|
241
|
+
return { seconds: clamped, micros, residual: 0 };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// src/phase-runner.ts
|
|
245
|
+
import { FRAME_PHASES } from "@adatechnology/game-contracts";
|
|
246
|
+
function createPhaseRunner(deps) {
|
|
247
|
+
const { clock, ctx, lists, reportError, fixedDt } = deps;
|
|
248
|
+
const phaseMs = Object.fromEntries(FRAME_PHASES.map((phase) => [phase, 0]));
|
|
249
|
+
let tick = 0;
|
|
250
|
+
const runEntries = (phase) => {
|
|
251
|
+
for (const entry of lists[phase].entries) {
|
|
252
|
+
if (entry.dead) continue;
|
|
253
|
+
try {
|
|
254
|
+
entry.fn(ctx);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
reportError(error, { id: entry.id, phase });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
return {
|
|
261
|
+
runPhase(phase, dt) {
|
|
262
|
+
const started = clock();
|
|
263
|
+
ctx.dt = dt;
|
|
264
|
+
runEntries(phase);
|
|
265
|
+
phaseMs[phase] = clock() - started;
|
|
266
|
+
},
|
|
267
|
+
runFixed(steps) {
|
|
268
|
+
const started = clock();
|
|
269
|
+
ctx.dt = fixedDt;
|
|
270
|
+
for (let substep = 0; substep < steps; substep++) {
|
|
271
|
+
ctx.substep = substep;
|
|
272
|
+
ctx.tick = tick;
|
|
273
|
+
runEntries("fixedSimulate");
|
|
274
|
+
tick++;
|
|
275
|
+
}
|
|
276
|
+
ctx.substep = 0;
|
|
277
|
+
ctx.tick = tick;
|
|
278
|
+
phaseMs.fixedSimulate = clock() - started;
|
|
279
|
+
},
|
|
280
|
+
get tick() {
|
|
281
|
+
return tick;
|
|
282
|
+
},
|
|
283
|
+
phaseMs
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// src/phases.ts
|
|
288
|
+
function createSystemList(isRunning) {
|
|
289
|
+
let entries = [];
|
|
290
|
+
const waiting = [];
|
|
291
|
+
const insert = (entry) => {
|
|
292
|
+
let index = entries.length;
|
|
293
|
+
while (index > 0 && entries[index - 1].order > entry.order) index--;
|
|
294
|
+
entries.splice(index, 0, entry);
|
|
295
|
+
};
|
|
296
|
+
return {
|
|
297
|
+
add(entry) {
|
|
298
|
+
if (isRunning()) waiting.push(entry);
|
|
299
|
+
else insert(entry);
|
|
300
|
+
},
|
|
301
|
+
remove(entry) {
|
|
302
|
+
entry.dead = true;
|
|
303
|
+
if (!isRunning()) entries = entries.filter((candidate) => candidate !== entry);
|
|
304
|
+
},
|
|
305
|
+
get entries() {
|
|
306
|
+
return entries;
|
|
307
|
+
},
|
|
308
|
+
settle() {
|
|
309
|
+
if (entries.some((entry) => entry.dead)) entries = entries.filter((entry) => !entry.dead);
|
|
310
|
+
for (const entry of waiting.splice(0)) if (!entry.dead) insert(entry);
|
|
311
|
+
},
|
|
312
|
+
clear() {
|
|
313
|
+
for (const entry of entries) entry.dead = true;
|
|
314
|
+
for (const entry of waiting.splice(0)) entry.dead = true;
|
|
315
|
+
entries = isRunning() ? entries : [];
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/scheduler.ts
|
|
321
|
+
function createScheduler(options = {}) {
|
|
322
|
+
const fixedDt = options.fixedDt ?? 1 / 60;
|
|
323
|
+
const fixedMicros = toMicros(fixedDt);
|
|
324
|
+
const maxSubsteps = options.maxSubsteps ?? 4;
|
|
325
|
+
const timeConfig = { fixedMicros, fixedDt, maxFrameSeconds: options.maxFrameSeconds ?? 0.25, snapMicros: Math.round((options.snapMs ?? 0.25) * 1e3) };
|
|
326
|
+
const clock = options.clock ?? defaultClock();
|
|
327
|
+
let accumulator = 0;
|
|
328
|
+
let residual = 0;
|
|
329
|
+
let droppedMicros = 0;
|
|
330
|
+
let substepsLastFrame = 0;
|
|
331
|
+
let running = false;
|
|
332
|
+
let disposed = false;
|
|
333
|
+
let paused = false;
|
|
334
|
+
let scripted = false;
|
|
335
|
+
let timeScale = 1;
|
|
336
|
+
let nextSystem = 0;
|
|
337
|
+
const reportedErrors = /* @__PURE__ */ new Set();
|
|
338
|
+
const lists = Object.fromEntries(FRAME_PHASES2.map((phase) => [phase, createSystemList(() => running)]));
|
|
339
|
+
const ctx = { dt: 0, realDt: 0, fixedDt, alpha: 0, frame: 0, tick: 0, substep: 0, simTime: 0, paused: false, scripted: false, simulated: true };
|
|
340
|
+
const reportError = options.onError ?? ((error, system) => {
|
|
341
|
+
if (reportedErrors.has(system.id)) return;
|
|
342
|
+
reportedErrors.add(system.id);
|
|
343
|
+
console.error(`game-runtime: system ${system.id} (${system.phase}) threw`, error);
|
|
344
|
+
});
|
|
345
|
+
const budget = createFrameBudget({ ms: options.budgetMs ?? 4, clock, onError: (error, queue) => reportError(error, { id: `queue:${queue.id}`, phase: queue.phase }) });
|
|
346
|
+
const runner = createPhaseRunner({ clock, ctx, lists, reportError, fixedDt });
|
|
347
|
+
const { runPhase, runFixed } = runner;
|
|
348
|
+
const settleAll = () => {
|
|
349
|
+
for (const phase of FRAME_PHASES2) lists[phase].settle();
|
|
350
|
+
};
|
|
351
|
+
const begin = () => {
|
|
352
|
+
running = true;
|
|
353
|
+
budget.beginFrame();
|
|
354
|
+
ctx.frame++;
|
|
355
|
+
ctx.paused = paused;
|
|
356
|
+
ctx.scripted = scripted;
|
|
357
|
+
ctx.tick = runner.tick;
|
|
358
|
+
};
|
|
359
|
+
const end = () => {
|
|
360
|
+
running = false;
|
|
361
|
+
settleAll();
|
|
362
|
+
};
|
|
363
|
+
const simulate = (seconds, micros, exact) => {
|
|
364
|
+
runPhase("input", seconds);
|
|
365
|
+
if (paused) return;
|
|
366
|
+
accumulator += Math.round(micros * timeScale);
|
|
367
|
+
let steps = Math.floor(accumulator / fixedMicros);
|
|
368
|
+
if (!exact && steps > maxSubsteps) {
|
|
369
|
+
droppedMicros += accumulator - maxSubsteps * fixedMicros;
|
|
370
|
+
steps = maxSubsteps;
|
|
371
|
+
accumulator = maxSubsteps * fixedMicros;
|
|
372
|
+
}
|
|
373
|
+
accumulator -= steps * fixedMicros;
|
|
374
|
+
substepsLastFrame = steps;
|
|
375
|
+
runFixed(steps);
|
|
376
|
+
ctx.alpha = accumulator / fixedMicros;
|
|
377
|
+
ctx.simTime += seconds * timeScale;
|
|
378
|
+
runPhase("lateSimulate", seconds * timeScale);
|
|
379
|
+
};
|
|
380
|
+
const frame = (realSeconds, run = {}) => {
|
|
381
|
+
if (running) throw new Error("scheduler.frame: re-entrant call");
|
|
382
|
+
if (disposed) throw new Error("scheduler.frame: the scheduler was disposed");
|
|
383
|
+
const time = resolveFrameTime(realSeconds, run.exact === true, residual, timeConfig);
|
|
384
|
+
residual = time.residual;
|
|
385
|
+
const seconds = time.seconds;
|
|
386
|
+
begin();
|
|
387
|
+
try {
|
|
388
|
+
ctx.simulated = run.simulate !== false;
|
|
389
|
+
ctx.realDt = seconds;
|
|
390
|
+
ctx.alpha = accumulator / fixedMicros;
|
|
391
|
+
substepsLastFrame = 0;
|
|
392
|
+
if (ctx.simulated) simulate(seconds, time.micros, run.exact === true);
|
|
393
|
+
ctx.alpha = accumulator / fixedMicros;
|
|
394
|
+
runPhase("present", paused ? 0 : seconds * timeScale);
|
|
395
|
+
budget.drain("present");
|
|
396
|
+
if (run.render !== false) runPhase("render", seconds);
|
|
397
|
+
runPhase("idle", seconds);
|
|
398
|
+
budget.drain("idle");
|
|
399
|
+
} finally {
|
|
400
|
+
ctx.simulated = true;
|
|
401
|
+
end();
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
const advanceStep = (dt, steps) => {
|
|
405
|
+
begin();
|
|
406
|
+
try {
|
|
407
|
+
ctx.simulated = true;
|
|
408
|
+
ctx.realDt = dt;
|
|
409
|
+
ctx.alpha = 0;
|
|
410
|
+
runPhase("input", dt);
|
|
411
|
+
if (!paused) {
|
|
412
|
+
runFixed(steps);
|
|
413
|
+
ctx.simTime += dt;
|
|
414
|
+
runPhase("lateSimulate", dt);
|
|
415
|
+
}
|
|
416
|
+
runPhase("present", paused ? 0 : dt);
|
|
417
|
+
budget.drain("present");
|
|
418
|
+
runPhase("idle", dt);
|
|
419
|
+
budget.drain("idle");
|
|
420
|
+
} finally {
|
|
421
|
+
end();
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
const advance = (seconds, advanceOptions = {}) => {
|
|
425
|
+
if (running) throw new Error("scheduler.advance: re-entrant call");
|
|
426
|
+
if (disposed) throw new Error("scheduler.advance: the scheduler was disposed");
|
|
427
|
+
const dt = sanitizeSeconds(advanceOptions.dt ?? fixedDt) || fixedDt;
|
|
428
|
+
const iterations = Math.max(1, Math.round(sanitizeSeconds(seconds) / dt));
|
|
429
|
+
const steps = Math.max(1, Math.round(dt / fixedDt));
|
|
430
|
+
const wasUnbounded = budget.unbounded;
|
|
431
|
+
budget.unbounded = true;
|
|
432
|
+
scripted = true;
|
|
433
|
+
accumulator = 0;
|
|
434
|
+
residual = 0;
|
|
435
|
+
scheduler.commands.clear();
|
|
436
|
+
try {
|
|
437
|
+
for (let i = 0; i < iterations; i++) advanceStep(dt, steps);
|
|
438
|
+
} finally {
|
|
439
|
+
scripted = false;
|
|
440
|
+
ctx.scripted = false;
|
|
441
|
+
budget.unbounded = wasUnbounded;
|
|
442
|
+
accumulator = 0;
|
|
443
|
+
ctx.alpha = 0;
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
const scheduler = {
|
|
447
|
+
add(system, systemOptions) {
|
|
448
|
+
if (disposed) return () => void 0;
|
|
449
|
+
const entry = {
|
|
450
|
+
id: systemOptions.id ?? `${systemOptions.phase}#${nextSystem++}`,
|
|
451
|
+
phase: systemOptions.phase,
|
|
452
|
+
fn: system,
|
|
453
|
+
order: systemOptions.order ?? 0,
|
|
454
|
+
dead: false
|
|
455
|
+
};
|
|
456
|
+
lists[entry.phase].add(entry);
|
|
457
|
+
return () => lists[entry.phase].remove(entry);
|
|
458
|
+
},
|
|
459
|
+
frame,
|
|
460
|
+
advance,
|
|
461
|
+
pause() {
|
|
462
|
+
paused = true;
|
|
463
|
+
},
|
|
464
|
+
resume() {
|
|
465
|
+
paused = false;
|
|
466
|
+
},
|
|
467
|
+
get paused() {
|
|
468
|
+
return paused;
|
|
469
|
+
},
|
|
470
|
+
get timeScale() {
|
|
471
|
+
return timeScale;
|
|
472
|
+
},
|
|
473
|
+
set timeScale(value) {
|
|
474
|
+
timeScale = sanitizeTimeScale(value);
|
|
475
|
+
},
|
|
476
|
+
resetClock() {
|
|
477
|
+
accumulator = 0;
|
|
478
|
+
residual = 0;
|
|
479
|
+
},
|
|
480
|
+
budget,
|
|
481
|
+
commands: createCommandBuffer(() => runner.tick),
|
|
482
|
+
stats() {
|
|
483
|
+
return { frame: ctx.frame, tick: runner.tick, substepsLastFrame, droppedSeconds: droppedMicros / MICROS_PER_SECOND, phaseMs: { ...runner.phaseMs }, budget: budget.stats() };
|
|
484
|
+
},
|
|
485
|
+
dispose() {
|
|
486
|
+
disposed = true;
|
|
487
|
+
budget.dispose();
|
|
488
|
+
for (const phase of FRAME_PHASES2) lists[phase].clear();
|
|
489
|
+
settleAll();
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
return scheduler;
|
|
493
|
+
}
|
|
494
|
+
export {
|
|
495
|
+
COMMAND_SYSTEM_ORDER,
|
|
496
|
+
DEFERRED_PRIORITY,
|
|
497
|
+
FRAME_PHASES3 as FRAME_PHASES,
|
|
498
|
+
createCommandBuffer,
|
|
499
|
+
createFrameBudget,
|
|
500
|
+
createScheduler,
|
|
501
|
+
startLoop
|
|
502
|
+
};
|
|
503
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/budget-entry.ts","../src/clock.ts","../src/budget.ts","../src/commands.ts","../src/loop.ts","../src/scheduler.ts","../src/frame-time.ts","../src/phase-runner.ts","../src/phases.ts"],"sourcesContent":["/**\n * @adatechnology/game-runtime: the owner of the frame. A phased scheduler with a fixed simulation step and one time budget shared by every deferred\n * queue of the kit. L1: no three.js, and no DOM unless `startLoop` runs on a page.\n */\nexport { DEFERRED_PRIORITY, FRAME_PHASES } from '@adatechnology/game-contracts'\nexport type { DeferredQueue, DeferredQueueOptions, FramePhase } from '@adatechnology/game-contracts'\nexport { createFrameBudget } from './budget'\nexport type { FrameBudgetOptions } from './budget'\nexport { COMMAND_SYSTEM_ORDER, createCommandBuffer } from './commands'\nexport { startLoop } from './loop'\nexport { createScheduler } from './scheduler'\nexport type {\n BudgetStats,\n Command,\n CommandBuffer,\n FrameBudget,\n FrameContext,\n FrameRunOptions,\n LoopOptions,\n QueueStats,\n Scheduler,\n SchedulerOptions,\n SchedulerStats,\n SystemFn,\n SystemOptions,\n} from './types'\n","import type { DeferredQueue, DeferredQueueOptions } from '@adatechnology/game-contracts'\nimport type { QueueStats } from './types'\n\nconst DEFAULT_MIN_EVERY_FRAMES = 4\n\nexport interface QueueEntry {\n readonly queue: DeferredQueue\n readonly phase: 'present' | 'idle'\n readonly priority: number\n readonly minEveryFrames: number\n readonly reserveMs: number\n reserveLeft: number\n waitingFrames: number\n maxWaitingFrames: number\n ranUnits: number\n spentMs: number\n forced: number\n lastPending: number\n}\n\nexport function createQueueEntry(queue: DeferredQueue, options: DeferredQueueOptions): QueueEntry {\n return {\n queue,\n phase: options.phase ?? 'idle',\n priority: options.priority,\n minEveryFrames: options.minEveryFrames ?? DEFAULT_MIN_EVERY_FRAMES,\n reserveMs: options.reserveMs ?? 0,\n reserveLeft: 0,\n waitingFrames: 0,\n maxWaitingFrames: 0,\n ranUnits: 0,\n spentMs: 0,\n forced: 0,\n lastPending: 0,\n }\n}\n\n/** Puts the entry before the first one of lower priority, so equal priorities keep the order of registration. */\nexport function insertByPriority(entries: QueueEntry[], entry: QueueEntry): void {\n let index = entries.length\n while (index > 0 && (entries[index - 1] as QueueEntry).priority < entry.priority) index--\n entries.splice(index, 0, entry)\n}\n\nexport function queueStats(entry: QueueEntry): QueueStats {\n return {\n id: entry.queue.id,\n pending: entry.lastPending,\n ranUnits: entry.ranUnits,\n spentMs: entry.spentMs,\n framesWaiting: entry.waitingFrames,\n maxFramesWaiting: entry.maxWaitingFrames,\n forced: entry.forced,\n }\n}\n","export const MICROS_PER_SECOND = 1_000_000\n\nexport const toMicros = (seconds: number): number => Math.round(seconds * MICROS_PER_SECOND)\n\nexport function defaultClock(): () => number {\n if (typeof performance !== 'undefined' && typeof performance.now === 'function') return () => performance.now()\n return () => Date.now()\n}\n","import type { DeferredQueueOptions } from '@adatechnology/game-contracts'\nimport { createQueueEntry, insertByPriority, queueStats, type QueueEntry } from './budget-entry'\nimport { defaultClock } from './clock'\nimport type { BudgetStats, FrameBudget } from './types'\n\nconst DEFAULT_MS = 4\nconst MAX_RESERVE_SHARE = 0.5\n\nexport interface FrameBudgetOptions {\n readonly ms?: number\n readonly clock?: () => number\n /** A queue that throws in `run`: default logs once per queue id. The frame goes on and the queue counts 0 units. */\n readonly onError?: (error: unknown, queue: { readonly id: string; readonly phase: 'present' | 'idle' }) => void\n}\n\nexport function createFrameBudget(options: FrameBudgetOptions = {}): FrameBudget {\n const clock = options.clock ?? defaultClock()\n const entries: QueueEntry[] = []\n const reportedQueues = new Set<string>()\n let frame = 0\n let frameOpen = false\n let debt = 0\n let common = 0\n let spentThisFrame = 0\n let unbounded = false\n\n const reservedTotal = (): number => entries.reduce((sum, entry) => sum + entry.reserveMs, 0)\n\n const beginFrame = (): void => {\n frame++\n frameOpen = true\n spentThisFrame = 0\n let available = budget.ms\n for (const entry of entries) {\n entry.reserveLeft = 0\n if (entry.reserveMs <= 0 || entry.queue.pending(frame) <= 0) continue\n entry.reserveLeft = Math.min(entry.reserveMs, available)\n available -= entry.reserveLeft\n }\n common = Math.max(0, available - debt)\n debt = 0\n }\n\n const reportError = (error: unknown, entry: QueueEntry): void => {\n if (options.onError) return options.onError(error, { id: entry.queue.id, phase: entry.phase })\n if (reportedQueues.has(entry.queue.id)) return\n reportedQueues.add(entry.queue.id)\n console.error(`game-runtime: queue ${entry.queue.id} threw`, error)\n }\n\n const runQueue = (entry: QueueEntry, deadline: number): number => {\n try {\n return entry.queue.run(deadline, clock)\n } catch (error) {\n reportError(error, entry)\n return 0\n }\n }\n\n const runEntry = (entry: QueueEntry, deadline: number): void => {\n const started = clock()\n const ran = runQueue(entry, deadline)\n const spent = Math.max(0, clock() - started)\n entry.ranUnits += ran\n entry.spentMs += spent\n entry.waitingFrames = 0\n spentThisFrame += spent\n if (unbounded) return\n const fromReserve = Math.min(entry.reserveLeft, spent)\n entry.reserveLeft -= fromReserve\n common -= spent - fromReserve\n if (common < 0) {\n debt = Math.min(budget.ms, debt - common)\n common = 0\n }\n }\n\n const drainEntry = (entry: QueueEntry): void => {\n entry.lastPending = entry.queue.pending(frame)\n if (entry.lastPending <= 0) {\n entry.waitingFrames = 0\n return\n }\n if (unbounded) return runEntry(entry, Infinity)\n const allowance = entry.reserveLeft + common\n if (allowance > 0) return runEntry(entry, clock() + allowance)\n entry.waitingFrames++\n entry.maxWaitingFrames = Math.max(entry.maxWaitingFrames, entry.waitingFrames)\n if (entry.waitingFrames < entry.minEveryFrames) return\n entry.forced++\n runEntry(entry, clock())\n }\n\n const budget: FrameBudget = {\n ms: options.ms ?? DEFAULT_MS,\n register(queue, queueOptions: DeferredQueueOptions) {\n if (entries.some((candidate) => candidate.queue === queue)) throw new Error(`budget.register(${queue.id}): this queue is already registered`)\n const reserveMs = queueOptions.reserveMs ?? 0\n if (reservedTotal() + reserveMs > budget.ms * MAX_RESERVE_SHARE) {\n throw new Error(`budget.register(${queue.id}): the reserves would pass half of the ${budget.ms} ms budget`)\n }\n const entry = createQueueEntry(queue, queueOptions)\n insertByPriority(entries, entry)\n queue.attach?.()\n return () => {\n const at = entries.indexOf(entry)\n if (at < 0) return\n entries.splice(at, 1)\n queue.detach?.()\n }\n },\n beginFrame,\n drain(phase) {\n if (phase === 'present' && !frameOpen) beginFrame()\n for (const entry of [...entries]) if (entry.phase === phase) drainEntry(entry)\n if (phase === 'idle') frameOpen = false\n },\n flushAll() {\n for (const entry of entries) entry.queue.flush()\n },\n get unbounded() {\n return unbounded\n },\n set unbounded(value: boolean) {\n unbounded = value\n },\n stats(): BudgetStats {\n return {\n ms: budget.ms,\n spentMs: spentThisFrame,\n debtMs: debt,\n queues: entries.map(queueStats),\n }\n },\n dispose() {\n for (const entry of entries.splice(0)) entry.queue.detach?.()\n },\n }\n return budget\n}\n","import type { Command, CommandBuffer } from './types'\n\n/** `order` of the fixedSimulate system that takes the tick's commands: it runs before every other fixed system. */\nexport const COMMAND_SYSTEM_ORDER = -10000\n\nconst NONE: readonly Command[] = []\n\n/** `nextTick` answers which fixed tick runs next: that is the tick a command pushed from `input` becomes valid at. */\nexport function createCommandBuffer(nextTick: () => number): CommandBuffer {\n let pending: Command[] = []\n return {\n push(kind, payload, tick) {\n pending.push({ kind, payload, tick: tick ?? nextTick() })\n },\n take(tick) {\n if (pending.length === 0) return NONE\n const due: Command[] = []\n const later: Command[] = []\n for (const command of pending) (command.tick <= tick ? due : later).push(command)\n pending = later\n return due\n },\n clear() {\n pending = []\n },\n }\n}\n","import type { LoopOptions, Scheduler } from './types'\n\n/** Drives `scheduler.frame` from the display's frames; listens to `visibilitychange` when there is a document. Returns the stop function. */\nexport function startLoop(scheduler: Scheduler, options: LoopOptions = {}): () => void {\n const requestFrame = options.requestFrame ?? globalThis.requestAnimationFrame?.bind(globalThis)\n const cancelFrame = options.cancelFrame ?? globalThis.cancelAnimationFrame?.bind(globalThis)\n if (!requestFrame) throw new Error('startLoop: no requestAnimationFrame here, pass requestFrame')\n let last: number = options.startMs ?? (typeof performance !== 'undefined' ? performance.now() : 0)\n let handle = 0\n let stopped = false\n\n const onFrame = (nowMs: number): void => {\n if (stopped) return\n handle = requestFrame(onFrame)\n const gap = options.minFrameMs?.() ?? 0\n if (gap > 0 && nowMs - last < gap) return\n const realSeconds = (nowMs - last) / 1000\n last = nowMs\n options.onFrame?.(realSeconds)\n scheduler.frame(realSeconds, options.skipSimulation?.() ? { simulate: false } : undefined)\n }\n\n const onVisibility = (): void => {\n if (document.visibilityState !== 'visible') return\n // the first frame back measures from now, not from before the tab went away\n if (typeof performance !== 'undefined') last = performance.now()\n scheduler.resetClock()\n }\n const hasDocument = typeof document !== 'undefined' && typeof document.addEventListener === 'function'\n if (hasDocument) document.addEventListener('visibilitychange', onVisibility)\n handle = requestFrame(onFrame)\n\n return () => {\n stopped = true\n cancelFrame?.(handle)\n if (hasDocument) document.removeEventListener('visibilitychange', onVisibility)\n }\n}\n","import { FRAME_PHASES, type FramePhase } from '@adatechnology/game-contracts'\nimport { createFrameBudget } from './budget'\nimport { MICROS_PER_SECOND, defaultClock, toMicros } from './clock'\nimport { createCommandBuffer } from './commands'\nimport { resolveFrameTime, sanitizeSeconds, sanitizeTimeScale } from './frame-time'\nimport { createPhaseRunner, type MutableContext } from './phase-runner'\nimport { createSystemList, type SystemEntry, type SystemList } from './phases'\nimport type { FrameRunOptions, Scheduler, SchedulerOptions, SchedulerStats, SystemFn, SystemOptions } from './types'\n\nexport function createScheduler(options: SchedulerOptions = {}): Scheduler {\n const fixedDt = options.fixedDt ?? 1 / 60\n const fixedMicros = toMicros(fixedDt)\n const maxSubsteps = options.maxSubsteps ?? 4\n const timeConfig = { fixedMicros, fixedDt, maxFrameSeconds: options.maxFrameSeconds ?? 0.25, snapMicros: Math.round((options.snapMs ?? 0.25) * 1000) }\n const clock = options.clock ?? defaultClock()\n let accumulator = 0\n let residual = 0\n let droppedMicros = 0\n let substepsLastFrame = 0\n let running = false\n let disposed = false\n let paused = false\n let scripted = false\n let timeScale = 1\n let nextSystem = 0\n const reportedErrors = new Set<string>()\n const lists = Object.fromEntries(FRAME_PHASES.map((phase) => [phase, createSystemList(() => running)])) as Record<FramePhase, SystemList>\n const ctx: MutableContext = { dt: 0, realDt: 0, fixedDt, alpha: 0, frame: 0, tick: 0, substep: 0, simTime: 0, paused: false, scripted: false, simulated: true }\n\n const reportError =\n options.onError ??\n ((error: unknown, system: { id: string; phase: FramePhase }): void => {\n if (reportedErrors.has(system.id)) return\n reportedErrors.add(system.id)\n console.error(`game-runtime: system ${system.id} (${system.phase}) threw`, error)\n })\n const budget = createFrameBudget({ ms: options.budgetMs ?? 4, clock, onError: (error, queue) => reportError(error, { id: `queue:${queue.id}`, phase: queue.phase }) })\n const runner = createPhaseRunner({ clock, ctx, lists, reportError, fixedDt })\n const { runPhase, runFixed } = runner\n\n const settleAll = (): void => {\n for (const phase of FRAME_PHASES) lists[phase].settle()\n }\n\n const begin = (): void => {\n running = true\n budget.beginFrame()\n ctx.frame++\n ctx.paused = paused\n ctx.scripted = scripted\n ctx.tick = runner.tick\n }\n\n const end = (): void => {\n running = false\n settleAll()\n }\n\n /** The input phase and, unless paused, the fixed steps and the late phase; `micros` is the real time to feed the accumulator. */\n const simulate = (seconds: number, micros: number, exact: boolean): void => {\n runPhase('input', seconds)\n if (paused) return\n accumulator += Math.round(micros * timeScale)\n let steps = Math.floor(accumulator / fixedMicros)\n if (!exact && steps > maxSubsteps) {\n droppedMicros += accumulator - maxSubsteps * fixedMicros\n steps = maxSubsteps\n accumulator = maxSubsteps * fixedMicros\n }\n accumulator -= steps * fixedMicros\n substepsLastFrame = steps\n runFixed(steps)\n ctx.alpha = accumulator / fixedMicros\n ctx.simTime += seconds * timeScale\n runPhase('lateSimulate', seconds * timeScale)\n }\n\n const frame = (realSeconds: number, run: FrameRunOptions = {}): void => {\n if (running) throw new Error('scheduler.frame: re-entrant call')\n if (disposed) throw new Error('scheduler.frame: the scheduler was disposed')\n const time = resolveFrameTime(realSeconds, run.exact === true, residual, timeConfig)\n residual = time.residual\n const seconds = time.seconds\n begin()\n try {\n ctx.simulated = run.simulate !== false\n ctx.realDt = seconds\n ctx.alpha = accumulator / fixedMicros\n substepsLastFrame = 0\n if (ctx.simulated) simulate(seconds, time.micros, run.exact === true)\n ctx.alpha = accumulator / fixedMicros\n runPhase('present', paused ? 0 : seconds * timeScale)\n budget.drain('present')\n if (run.render !== false) runPhase('render', seconds)\n runPhase('idle', seconds)\n budget.drain('idle')\n } finally {\n ctx.simulated = true\n end()\n }\n }\n\n const advanceStep = (dt: number, steps: number): void => {\n begin()\n try {\n ctx.simulated = true\n ctx.realDt = dt\n ctx.alpha = 0\n runPhase('input', dt)\n if (!paused) {\n runFixed(steps)\n ctx.simTime += dt\n runPhase('lateSimulate', dt)\n }\n runPhase('present', paused ? 0 : dt)\n budget.drain('present')\n runPhase('idle', dt)\n budget.drain('idle')\n } finally {\n end()\n }\n }\n\n const advance = (seconds: number, advanceOptions: { readonly dt?: number } = {}): void => {\n if (running) throw new Error('scheduler.advance: re-entrant call')\n if (disposed) throw new Error('scheduler.advance: the scheduler was disposed')\n const dt = sanitizeSeconds(advanceOptions.dt ?? fixedDt) || fixedDt\n const iterations = Math.max(1, Math.round(sanitizeSeconds(seconds) / dt))\n const steps = Math.max(1, Math.round(dt / fixedDt))\n const wasUnbounded = budget.unbounded\n budget.unbounded = true\n scripted = true\n accumulator = 0\n residual = 0\n scheduler.commands.clear()\n try {\n for (let i = 0; i < iterations; i++) advanceStep(dt, steps)\n } finally {\n scripted = false\n ctx.scripted = false\n budget.unbounded = wasUnbounded\n accumulator = 0\n ctx.alpha = 0\n }\n }\n\n const scheduler: Scheduler = {\n add(system: SystemFn, systemOptions: SystemOptions) {\n if (disposed) return () => undefined\n const entry: SystemEntry = {\n id: systemOptions.id ?? `${systemOptions.phase}#${nextSystem++}`,\n phase: systemOptions.phase,\n fn: system,\n order: systemOptions.order ?? 0,\n dead: false,\n }\n lists[entry.phase].add(entry)\n return () => lists[entry.phase].remove(entry)\n },\n frame,\n advance,\n pause() {\n paused = true\n },\n resume() {\n paused = false\n },\n get paused() {\n return paused\n },\n get timeScale() {\n return timeScale\n },\n set timeScale(value: number) {\n timeScale = sanitizeTimeScale(value)\n },\n resetClock() {\n accumulator = 0\n residual = 0\n },\n budget,\n commands: createCommandBuffer(() => runner.tick),\n stats(): SchedulerStats {\n return { frame: ctx.frame, tick: runner.tick, substepsLastFrame, droppedSeconds: droppedMicros / MICROS_PER_SECOND, phaseMs: { ...runner.phaseMs }, budget: budget.stats() }\n },\n dispose() {\n disposed = true\n budget.dispose()\n for (const phase of FRAME_PHASES) lists[phase].clear()\n settleAll()\n },\n }\n return scheduler\n}\n","import { toMicros } from './clock'\n\nexport interface FrameTimeConfig {\n readonly fixedMicros: number\n readonly fixedDt: number\n readonly maxFrameSeconds: number\n readonly snapMicros: number\n}\n\nexport interface FrameTime {\n readonly seconds: number\n readonly micros: number\n /** What the snap left over, carried to the next frame. */\n readonly residual: number\n}\n\n/** A time that is not a finite positive number (NaN, a negative delta from a clock that went back) counts as no time. */\nexport const sanitizeSeconds = (seconds: number): number => (Number.isFinite(seconds) && seconds > 0 ? seconds : 0)\n\n/** The scale is never negative; a NaN scale is read as real time. */\nexport const sanitizeTimeScale = (scale: number): number => (Number.isFinite(scale) ? Math.max(0, scale) : 1)\n\n/** Clamps a real frame, adds the residue of the last snap and snaps a frame within a hair of whole fixed steps to exactly those steps. */\nexport function resolveFrameTime(realSeconds: number, exact: boolean, residual: number, config: FrameTimeConfig): FrameTime {\n const given = sanitizeSeconds(realSeconds)\n if (exact) return { seconds: given, micros: toMicros(given), residual }\n const clamped = Math.min(given, config.maxFrameSeconds)\n const micros = toMicros(clamped) + residual\n const wholeSteps = Math.round(micros / config.fixedMicros)\n const drift = micros - wholeSteps * config.fixedMicros\n if (config.snapMicros > 0 && wholeSteps >= 1 && Math.abs(drift) <= config.snapMicros) {\n return { seconds: wholeSteps * config.fixedDt, micros: wholeSteps * config.fixedMicros, residual: drift }\n }\n return { seconds: clamped, micros, residual: 0 }\n}\n","import { FRAME_PHASES, type FramePhase } from '@adatechnology/game-contracts'\nimport type { SystemList } from './phases'\nimport type { FrameContext } from './types'\n\nexport type MutableContext = { -readonly [Key in keyof FrameContext]: FrameContext[Key] }\n\nexport interface PhaseRunnerDeps {\n readonly clock: () => number\n readonly ctx: MutableContext\n readonly lists: Readonly<Record<FramePhase, SystemList>>\n readonly fixedDt: number\n readonly reportError: (error: unknown, system: { id: string; phase: FramePhase }) => void\n}\n\nexport interface PhaseRunner {\n runPhase(phase: FramePhase, dt: number): void\n /** Runs `steps` fixed steps of the `fixedSimulate` phase. */\n runFixed(steps: number): void\n /** Fixed steps run since start. */\n readonly tick: number\n readonly phaseMs: Readonly<Record<FramePhase, number>>\n}\n\n/** Runs the systems of a phase, one at a time: a system that throws is reported and the rest of the phase goes on. */\nexport function createPhaseRunner(deps: PhaseRunnerDeps): PhaseRunner {\n const { clock, ctx, lists, reportError, fixedDt } = deps\n const phaseMs = Object.fromEntries(FRAME_PHASES.map((phase) => [phase, 0])) as Record<FramePhase, number>\n let tick = 0\n\n const runEntries = (phase: FramePhase): void => {\n for (const entry of lists[phase].entries) {\n if (entry.dead) continue\n try {\n entry.fn(ctx)\n } catch (error) {\n reportError(error, { id: entry.id, phase })\n }\n }\n }\n\n return {\n runPhase(phase, dt) {\n const started = clock()\n ctx.dt = dt\n runEntries(phase)\n phaseMs[phase] = clock() - started\n },\n runFixed(steps) {\n const started = clock()\n ctx.dt = fixedDt\n for (let substep = 0; substep < steps; substep++) {\n ctx.substep = substep\n ctx.tick = tick\n runEntries('fixedSimulate')\n tick++\n }\n ctx.substep = 0\n ctx.tick = tick\n phaseMs.fixedSimulate = clock() - started\n },\n get tick() {\n return tick\n },\n phaseMs,\n }\n}\n","import type { FramePhase } from '@adatechnology/game-contracts'\nimport type { SystemFn } from './types'\n\nexport interface SystemEntry {\n readonly id: string\n readonly phase: FramePhase\n readonly fn: SystemFn\n readonly order: number\n dead: boolean\n}\n\n/** Systems of one phase sorted by `order` and then by registration, kept sorted by insertion. A system added while a frame runs waits for the next one. */\nexport interface SystemList {\n add(entry: SystemEntry): void\n remove(entry: SystemEntry): void\n /** The live entries; the array is never replaced while the phase runs. */\n readonly entries: readonly SystemEntry[]\n /** Called at the end of a phase: takes in the pending additions and drops the removed. */\n settle(): void\n /** Marks every entry dead and forgets the ones waiting to come in (the scheduler was disposed). */\n clear(): void\n}\n\nexport function createSystemList(isRunning: () => boolean): SystemList {\n let entries: SystemEntry[] = []\n const waiting: SystemEntry[] = []\n const insert = (entry: SystemEntry): void => {\n let index = entries.length\n while (index > 0 && (entries[index - 1] as SystemEntry).order > entry.order) index--\n entries.splice(index, 0, entry)\n }\n return {\n add(entry) {\n if (isRunning()) waiting.push(entry)\n else insert(entry)\n },\n remove(entry) {\n entry.dead = true\n if (!isRunning()) entries = entries.filter((candidate) => candidate !== entry)\n },\n get entries() {\n return entries\n },\n settle() {\n if (entries.some((entry) => entry.dead)) entries = entries.filter((entry) => !entry.dead)\n for (const entry of waiting.splice(0)) if (!entry.dead) insert(entry)\n },\n clear() {\n for (const entry of entries) entry.dead = true\n for (const entry of waiting.splice(0)) entry.dead = true\n entries = isRunning() ? entries : []\n },\n }\n}\n"],"mappings":"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","names":["FRAME_PHASES","FRAME_PHASES","FRAME_PHASES"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,53 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adatechnology/game-runtime",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "The owner of the frame: a phased scheduler (input, fixed simulation, late simulation, present, render, idle) with a fixed step, and one time budget shared by every deferred queue. No three.js, no DOM required.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"game",
|
|
8
|
+
"scheduler",
|
|
9
|
+
"game-loop",
|
|
10
|
+
"fixed-step",
|
|
11
|
+
"frame-budget",
|
|
12
|
+
"deterministic"
|
|
13
|
+
],
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/Andersonfrfilho/adatechnology-game-kit.git",
|
|
17
|
+
"directory": "packages/game-runtime"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"type": "module",
|
|
23
|
+
"main": "dist/index.js",
|
|
24
|
+
"module": "dist/index.js",
|
|
25
|
+
"types": "dist/index.d.ts",
|
|
26
|
+
"unpkg": "dist/index.global.js",
|
|
27
|
+
"jsdelivr": "dist/index.global.js",
|
|
28
|
+
"sideEffects": false,
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"import": "./dist/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./global": "./dist/index.global.js"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist"
|
|
38
|
+
],
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@adatechnology/game-contracts": "^0.22.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^22.10.7",
|
|
44
|
+
"tsup": "^8.5.1",
|
|
45
|
+
"typescript": "^5.7.3",
|
|
46
|
+
"vitest": "^3.2.4"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsup",
|
|
50
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
51
|
+
"test": "vitest run"
|
|
52
|
+
}
|
|
6
53
|
}
|