automatick 0.0.2 → 0.0.3
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/dist/{chunk-UU5V7HGM.js → chunk-A375T3UD.js} +1 -1
- package/dist/chunk-LMHH7YPE.js +89 -0
- package/dist/chunk-VPS3ZXWI.js +132 -0
- package/dist/engine.cjs +1 -1
- package/dist/engine.d.cts +8 -4
- package/dist/engine.d.ts +8 -4
- package/dist/engine.js +1 -1
- package/dist/react/Simulation.cjs +294 -99
- package/dist/react/Simulation.d.cts +36 -17
- package/dist/react/Simulation.d.ts +36 -17
- package/dist/react/Simulation.js +84 -99
- package/dist/sim.d.cts +8 -4
- package/dist/sim.d.ts +8 -4
- package/dist/standalone/engine.js +242 -0
- package/dist/standalone/sim.js +11 -0
- package/dist/worker/createSimWorker.cjs +10 -3
- package/dist/worker/createSimWorker.js +3 -121
- package/dist/worker/protocol.d.cts +3 -2
- package/dist/worker/protocol.d.ts +3 -2
- package/dist/worker/workerRunner.cjs +20 -2
- package/dist/worker/workerRunner.d.cts +9 -0
- package/dist/worker/workerRunner.d.ts +9 -0
- package/dist/worker/workerRunner.js +3 -67
- package/package.json +2 -2
|
@@ -35,7 +35,7 @@ var SimulationEngine = class {
|
|
|
35
35
|
this.maxTime = config.maxTime;
|
|
36
36
|
this.delayMs = config.delayMs ?? 0;
|
|
37
37
|
this.ticksPerFrame = config.ticksPerFrame ?? 1;
|
|
38
|
-
this.params = { ...config.initialParams };
|
|
38
|
+
this.params = config.initialParams ? { ...config.initialParams } : {};
|
|
39
39
|
this.data = this.initFn(this.params);
|
|
40
40
|
if (config.render) {
|
|
41
41
|
this.listeners.add(config.render);
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// src/worker/serialize.ts
|
|
2
|
+
function deserializeWorkerMessage(raw) {
|
|
3
|
+
return raw;
|
|
4
|
+
}
|
|
5
|
+
function serializeMainMessage(msg) {
|
|
6
|
+
return msg;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// src/worker/workerRunner.ts
|
|
10
|
+
var PERF_BUFFER_SIZE = 120;
|
|
11
|
+
function createWorkerRunner(worker, config) {
|
|
12
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
13
|
+
let currentSnapshot = {
|
|
14
|
+
data: void 0,
|
|
15
|
+
params: config.initialParams,
|
|
16
|
+
tick: 0,
|
|
17
|
+
status: "idle",
|
|
18
|
+
stepDurationMs: 0
|
|
19
|
+
};
|
|
20
|
+
const perfBuffer = [];
|
|
21
|
+
function send(msg) {
|
|
22
|
+
worker.postMessage(serializeMainMessage(msg));
|
|
23
|
+
}
|
|
24
|
+
function emit() {
|
|
25
|
+
for (const l of listeners) {
|
|
26
|
+
l(currentSnapshot);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function pushPerf(snapshot) {
|
|
30
|
+
if (snapshot.tick <= 0) return;
|
|
31
|
+
const last = perfBuffer[perfBuffer.length - 1];
|
|
32
|
+
if (last && last.tick === snapshot.tick) return;
|
|
33
|
+
if (perfBuffer.length >= PERF_BUFFER_SIZE) perfBuffer.shift();
|
|
34
|
+
perfBuffer.push({ tick: snapshot.tick, stepMs: snapshot.stepDurationMs });
|
|
35
|
+
}
|
|
36
|
+
worker.onmessage = (event) => {
|
|
37
|
+
const msg = deserializeWorkerMessage(event.data);
|
|
38
|
+
switch (msg.kind) {
|
|
39
|
+
case "snapshot":
|
|
40
|
+
currentSnapshot = msg.snapshot;
|
|
41
|
+
pushPerf(msg.snapshot);
|
|
42
|
+
emit();
|
|
43
|
+
break;
|
|
44
|
+
case "error":
|
|
45
|
+
currentSnapshot = { ...currentSnapshot, status: "stopped" };
|
|
46
|
+
emit();
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
worker.onerror = () => {
|
|
51
|
+
currentSnapshot = { ...currentSnapshot, status: "stopped" };
|
|
52
|
+
emit();
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
getSnapshot: () => currentSnapshot,
|
|
56
|
+
subscribe(listener) {
|
|
57
|
+
listeners.add(listener);
|
|
58
|
+
return () => {
|
|
59
|
+
listeners.delete(listener);
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
play: () => send({ kind: "play" }),
|
|
63
|
+
pause: () => send({ kind: "pause" }),
|
|
64
|
+
stop: () => send({ kind: "stop" }),
|
|
65
|
+
seek: (tick) => send({ kind: "seek", tick }),
|
|
66
|
+
advance: (count = 1) => send({ kind: "advance", count }),
|
|
67
|
+
setParams: (patch) => send({ kind: "setParams", patch }),
|
|
68
|
+
resetWith: (patch) => send({ kind: "resetWith", patch }),
|
|
69
|
+
setConfig: (patch) => send({ kind: "setConfig", patch }),
|
|
70
|
+
recordDrawTime(tick, ms) {
|
|
71
|
+
for (let i = perfBuffer.length - 1; i >= 0; i--) {
|
|
72
|
+
if (perfBuffer[i].tick === tick) {
|
|
73
|
+
perfBuffer[i].drawMs = ms;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
getPerformance: () => perfBuffer,
|
|
79
|
+
destroy() {
|
|
80
|
+
listeners.clear();
|
|
81
|
+
send({ kind: "destroy" });
|
|
82
|
+
worker.terminate();
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export {
|
|
88
|
+
createWorkerRunner
|
|
89
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// src/worker/createSimWorker.ts
|
|
2
|
+
var WORKER_SCRIPT = `
|
|
3
|
+
let engine = null;
|
|
4
|
+
let loopTimer = null;
|
|
5
|
+
let snapshotIntervalMs = 16;
|
|
6
|
+
let lastSnapshotMs = 0;
|
|
7
|
+
let ticksPerFrame = 1;
|
|
8
|
+
let delayMs = 0;
|
|
9
|
+
|
|
10
|
+
function emitSnapshot() {
|
|
11
|
+
if (!engine) return;
|
|
12
|
+
postMessage({ kind: 'snapshot', snapshot: engine.getSnapshot() });
|
|
13
|
+
lastSnapshotMs = performance.now();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function tickLoop() {
|
|
17
|
+
if (!engine || engine.getStatus() !== 'playing') return;
|
|
18
|
+
|
|
19
|
+
for (let i = 0; i < ticksPerFrame; i++) {
|
|
20
|
+
engine.advance(1);
|
|
21
|
+
const s = engine.getStatus();
|
|
22
|
+
if (s === 'stopped') { emitSnapshot(); return; }
|
|
23
|
+
if (s !== 'paused') break;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// advance() transitions to paused; resume playing for the next batch
|
|
27
|
+
if (engine.getStatus() === 'paused') engine.play();
|
|
28
|
+
|
|
29
|
+
if (performance.now() - lastSnapshotMs >= snapshotIntervalMs) emitSnapshot();
|
|
30
|
+
loopTimer = setTimeout(tickLoop, delayMs);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function stopLoop() {
|
|
34
|
+
if (loopTimer !== null) { clearTimeout(loopTimer); loopTimer = null; }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
self.onmessage = async (event) => {
|
|
38
|
+
const msg = event.data;
|
|
39
|
+
try {
|
|
40
|
+
switch (msg.kind) {
|
|
41
|
+
case 'init': {
|
|
42
|
+
delayMs = msg.config.delayMs || 0;
|
|
43
|
+
ticksPerFrame = msg.config.ticksPerFrame || 1;
|
|
44
|
+
snapshotIntervalMs = msg.config.snapshotIntervalMs || 16;
|
|
45
|
+
|
|
46
|
+
const [simMod, engineMod] = await Promise.all([
|
|
47
|
+
import(msg.moduleUrl),
|
|
48
|
+
import(msg.engineUrl),
|
|
49
|
+
]);
|
|
50
|
+
const sim = simMod.default;
|
|
51
|
+
// Merge sim.defaultParams under the patch sent from main \u2014 the main
|
|
52
|
+
// thread sees the sim module via a URL, so it can't apply defaults.
|
|
53
|
+
const initialParams = { ...(sim.defaultParams || {}), ...(msg.params || {}) };
|
|
54
|
+
engine = engineMod.createEngine({
|
|
55
|
+
init: sim.init,
|
|
56
|
+
step: sim.step,
|
|
57
|
+
shouldStop: sim.shouldStop,
|
|
58
|
+
initialParams,
|
|
59
|
+
maxTime: msg.config.maxTime,
|
|
60
|
+
// Worker host owns its own setTimeout-driven loop; rAF wouldn't exist here anyway.
|
|
61
|
+
autoFrame: false,
|
|
62
|
+
});
|
|
63
|
+
emitSnapshot();
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
case 'play':
|
|
67
|
+
if (!engine) return;
|
|
68
|
+
engine.play(); emitSnapshot(); stopLoop(); loopTimer = setTimeout(tickLoop, 0);
|
|
69
|
+
break;
|
|
70
|
+
case 'pause':
|
|
71
|
+
if (!engine) return;
|
|
72
|
+
stopLoop(); engine.pause(); emitSnapshot();
|
|
73
|
+
break;
|
|
74
|
+
case 'stop':
|
|
75
|
+
if (!engine) return;
|
|
76
|
+
stopLoop(); engine.stop(); emitSnapshot();
|
|
77
|
+
break;
|
|
78
|
+
case 'seek':
|
|
79
|
+
if (!engine) return;
|
|
80
|
+
stopLoop(); engine.seek(msg.tick); emitSnapshot();
|
|
81
|
+
break;
|
|
82
|
+
case 'advance':
|
|
83
|
+
if (!engine) return;
|
|
84
|
+
engine.advance(msg.count); emitSnapshot();
|
|
85
|
+
break;
|
|
86
|
+
case 'setParams':
|
|
87
|
+
if (!engine) return;
|
|
88
|
+
engine.setParams(msg.patch); emitSnapshot();
|
|
89
|
+
break;
|
|
90
|
+
case 'resetWith':
|
|
91
|
+
if (!engine) return;
|
|
92
|
+
stopLoop(); engine.resetWith(msg.patch); emitSnapshot();
|
|
93
|
+
break;
|
|
94
|
+
case 'setConfig':
|
|
95
|
+
if (msg.patch.delayMs !== undefined) delayMs = msg.patch.delayMs;
|
|
96
|
+
if (msg.patch.ticksPerFrame !== undefined) ticksPerFrame = msg.patch.ticksPerFrame;
|
|
97
|
+
if (msg.patch.snapshotIntervalMs !== undefined) snapshotIntervalMs = msg.patch.snapshotIntervalMs;
|
|
98
|
+
break;
|
|
99
|
+
case 'destroy':
|
|
100
|
+
stopLoop();
|
|
101
|
+
if (engine) { engine.destroy(); engine = null; }
|
|
102
|
+
self.close();
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
} catch (err) {
|
|
106
|
+
postMessage({ kind: 'error', error: { message: err.message, stack: err.stack } });
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
`;
|
|
110
|
+
function createSimWorker(options) {
|
|
111
|
+
const blob = new Blob([WORKER_SCRIPT], { type: "text/javascript" });
|
|
112
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
113
|
+
const worker = new Worker(blobUrl, { type: "module" });
|
|
114
|
+
worker.addEventListener("message", function cleanup(event) {
|
|
115
|
+
if (event.data?.kind === "snapshot" || event.data?.kind === "error") {
|
|
116
|
+
URL.revokeObjectURL(blobUrl);
|
|
117
|
+
worker.removeEventListener("message", cleanup);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
worker.postMessage({
|
|
121
|
+
kind: "init",
|
|
122
|
+
moduleUrl: options.moduleUrl,
|
|
123
|
+
engineUrl: options.engineUrl,
|
|
124
|
+
params: options.initialParams,
|
|
125
|
+
config: options.config
|
|
126
|
+
});
|
|
127
|
+
return worker;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export {
|
|
131
|
+
createSimWorker
|
|
132
|
+
};
|
package/dist/engine.cjs
CHANGED
|
@@ -63,7 +63,7 @@ var SimulationEngine = class {
|
|
|
63
63
|
this.maxTime = config.maxTime;
|
|
64
64
|
this.delayMs = config.delayMs ?? 0;
|
|
65
65
|
this.ticksPerFrame = config.ticksPerFrame ?? 1;
|
|
66
|
-
this.params = { ...config.initialParams };
|
|
66
|
+
this.params = config.initialParams ? { ...config.initialParams } : {};
|
|
67
67
|
this.data = this.initFn(this.params);
|
|
68
68
|
if (config.render) {
|
|
69
69
|
this.listeners.add(config.render);
|
package/dist/engine.d.cts
CHANGED
|
@@ -6,7 +6,7 @@ type TickPerformance = {
|
|
|
6
6
|
stepMs: number;
|
|
7
7
|
drawMs?: number;
|
|
8
8
|
};
|
|
9
|
-
type EngineConfig<Data, Params
|
|
9
|
+
type EngineConfig<Data, Params = Record<string, never>> = {
|
|
10
10
|
/**
|
|
11
11
|
* Initial simulation state — value or `(params) => Data`. See `SimInit`.
|
|
12
12
|
* When a value is passed, the engine `structuredClone`s it on each (re)init.
|
|
@@ -14,7 +14,11 @@ type EngineConfig<Data, Params> = {
|
|
|
14
14
|
init: SimInit<Data, Params>;
|
|
15
15
|
step: (state: State<Data, Params>) => Data;
|
|
16
16
|
shouldStop?: (data: Data, params: Params) => boolean;
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Initial param values. Optional — when omitted, the engine seeds an empty
|
|
19
|
+
* params object and `Params` defaults to `Record<string, never>`.
|
|
20
|
+
*/
|
|
21
|
+
initialParams?: Params;
|
|
18
22
|
maxTime?: number;
|
|
19
23
|
delayMs?: number;
|
|
20
24
|
ticksPerFrame?: number;
|
|
@@ -40,7 +44,7 @@ type EngineConfig<Data, Params> = {
|
|
|
40
44
|
*/
|
|
41
45
|
autoFrame?: boolean;
|
|
42
46
|
};
|
|
43
|
-
declare class SimulationEngine<Data, Params
|
|
47
|
+
declare class SimulationEngine<Data, Params = Record<string, never>> {
|
|
44
48
|
private data;
|
|
45
49
|
private params;
|
|
46
50
|
private tick;
|
|
@@ -87,6 +91,6 @@ declare class SimulationEngine<Data, Params> {
|
|
|
87
91
|
private emit;
|
|
88
92
|
private emitHistory;
|
|
89
93
|
}
|
|
90
|
-
declare function createEngine<Data, Params
|
|
94
|
+
declare function createEngine<Data, Params = Record<string, never>>(config: EngineConfig<Data, Params>): SimulationEngine<Data, Params>;
|
|
91
95
|
|
|
92
96
|
export { type EngineConfig, SimulationEngine, SimulationStatus, State, type TickPerformance, createEngine };
|
package/dist/engine.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ type TickPerformance = {
|
|
|
6
6
|
stepMs: number;
|
|
7
7
|
drawMs?: number;
|
|
8
8
|
};
|
|
9
|
-
type EngineConfig<Data, Params
|
|
9
|
+
type EngineConfig<Data, Params = Record<string, never>> = {
|
|
10
10
|
/**
|
|
11
11
|
* Initial simulation state — value or `(params) => Data`. See `SimInit`.
|
|
12
12
|
* When a value is passed, the engine `structuredClone`s it on each (re)init.
|
|
@@ -14,7 +14,11 @@ type EngineConfig<Data, Params> = {
|
|
|
14
14
|
init: SimInit<Data, Params>;
|
|
15
15
|
step: (state: State<Data, Params>) => Data;
|
|
16
16
|
shouldStop?: (data: Data, params: Params) => boolean;
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Initial param values. Optional — when omitted, the engine seeds an empty
|
|
19
|
+
* params object and `Params` defaults to `Record<string, never>`.
|
|
20
|
+
*/
|
|
21
|
+
initialParams?: Params;
|
|
18
22
|
maxTime?: number;
|
|
19
23
|
delayMs?: number;
|
|
20
24
|
ticksPerFrame?: number;
|
|
@@ -40,7 +44,7 @@ type EngineConfig<Data, Params> = {
|
|
|
40
44
|
*/
|
|
41
45
|
autoFrame?: boolean;
|
|
42
46
|
};
|
|
43
|
-
declare class SimulationEngine<Data, Params
|
|
47
|
+
declare class SimulationEngine<Data, Params = Record<string, never>> {
|
|
44
48
|
private data;
|
|
45
49
|
private params;
|
|
46
50
|
private tick;
|
|
@@ -87,6 +91,6 @@ declare class SimulationEngine<Data, Params> {
|
|
|
87
91
|
private emit;
|
|
88
92
|
private emitHistory;
|
|
89
93
|
}
|
|
90
|
-
declare function createEngine<Data, Params
|
|
94
|
+
declare function createEngine<Data, Params = Record<string, never>>(config: EngineConfig<Data, Params>): SimulationEngine<Data, Params>;
|
|
91
95
|
|
|
92
96
|
export { type EngineConfig, SimulationEngine, SimulationStatus, State, type TickPerformance, createEngine };
|