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
|
@@ -1,124 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
engine = engineMod.createEngine({
|
|
52
|
-
init: sim.init,
|
|
53
|
-
step: sim.step,
|
|
54
|
-
shouldStop: sim.shouldStop,
|
|
55
|
-
initialParams: msg.params,
|
|
56
|
-
maxTime: msg.config.maxTime,
|
|
57
|
-
// Worker host owns its own setTimeout-driven loop; rAF wouldn't exist here anyway.
|
|
58
|
-
autoFrame: false,
|
|
59
|
-
});
|
|
60
|
-
postMessage({ kind: 'ready' });
|
|
61
|
-
emitSnapshot();
|
|
62
|
-
break;
|
|
63
|
-
}
|
|
64
|
-
case 'play':
|
|
65
|
-
if (!engine) return;
|
|
66
|
-
engine.play(); emitSnapshot(); stopLoop(); loopTimer = setTimeout(tickLoop, 0);
|
|
67
|
-
break;
|
|
68
|
-
case 'pause':
|
|
69
|
-
if (!engine) return;
|
|
70
|
-
stopLoop(); engine.pause(); emitSnapshot();
|
|
71
|
-
break;
|
|
72
|
-
case 'stop':
|
|
73
|
-
if (!engine) return;
|
|
74
|
-
stopLoop(); engine.stop(); emitSnapshot();
|
|
75
|
-
break;
|
|
76
|
-
case 'seek':
|
|
77
|
-
if (!engine) return;
|
|
78
|
-
stopLoop(); engine.seek(msg.tick); emitSnapshot();
|
|
79
|
-
break;
|
|
80
|
-
case 'advance':
|
|
81
|
-
if (!engine) return;
|
|
82
|
-
engine.advance(msg.count); emitSnapshot();
|
|
83
|
-
break;
|
|
84
|
-
case 'setParams':
|
|
85
|
-
if (!engine) return;
|
|
86
|
-
engine.setParams(msg.patch); emitSnapshot();
|
|
87
|
-
break;
|
|
88
|
-
case 'resetWith':
|
|
89
|
-
if (!engine) return;
|
|
90
|
-
stopLoop(); engine.resetWith(msg.patch); emitSnapshot();
|
|
91
|
-
break;
|
|
92
|
-
case 'destroy':
|
|
93
|
-
stopLoop();
|
|
94
|
-
if (engine) { engine.destroy(); engine = null; }
|
|
95
|
-
self.close();
|
|
96
|
-
break;
|
|
97
|
-
}
|
|
98
|
-
} catch (err) {
|
|
99
|
-
postMessage({ kind: 'error', error: { message: err.message, stack: err.stack } });
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
`;
|
|
103
|
-
function createSimWorker(options) {
|
|
104
|
-
const blob = new Blob([WORKER_SCRIPT], { type: "text/javascript" });
|
|
105
|
-
const blobUrl = URL.createObjectURL(blob);
|
|
106
|
-
const worker = new Worker(blobUrl, { type: "module" });
|
|
107
|
-
worker.addEventListener("message", function cleanup(event) {
|
|
108
|
-
if (event.data?.kind === "ready" || event.data?.kind === "error") {
|
|
109
|
-
URL.revokeObjectURL(blobUrl);
|
|
110
|
-
worker.removeEventListener("message", cleanup);
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
worker.postMessage({
|
|
114
|
-
kind: "init",
|
|
115
|
-
moduleUrl: options.moduleUrl,
|
|
116
|
-
engineUrl: options.engineUrl,
|
|
117
|
-
params: options.initialParams,
|
|
118
|
-
config: options.config
|
|
119
|
-
});
|
|
120
|
-
return worker;
|
|
121
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
createSimWorker
|
|
3
|
+
} from "../chunk-VPS3ZXWI.js";
|
|
122
4
|
export {
|
|
123
5
|
createSimWorker
|
|
124
6
|
};
|
|
@@ -31,6 +31,9 @@ type MainToWorkerMessage<Params> = {
|
|
|
31
31
|
} | {
|
|
32
32
|
kind: 'resetWith';
|
|
33
33
|
patch?: Partial<Params>;
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'setConfig';
|
|
36
|
+
patch: Partial<WorkerConfig>;
|
|
34
37
|
} | {
|
|
35
38
|
kind: 'destroy';
|
|
36
39
|
};
|
|
@@ -44,8 +47,6 @@ type WorkerToMainMessage<Data, Params> = {
|
|
|
44
47
|
message: string;
|
|
45
48
|
stack?: string;
|
|
46
49
|
};
|
|
47
|
-
} | {
|
|
48
|
-
kind: 'ready';
|
|
49
50
|
};
|
|
50
51
|
/** Worker-specific configuration passed at init time. */
|
|
51
52
|
type WorkerConfig = {
|
|
@@ -31,6 +31,9 @@ type MainToWorkerMessage<Params> = {
|
|
|
31
31
|
} | {
|
|
32
32
|
kind: 'resetWith';
|
|
33
33
|
patch?: Partial<Params>;
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'setConfig';
|
|
36
|
+
patch: Partial<WorkerConfig>;
|
|
34
37
|
} | {
|
|
35
38
|
kind: 'destroy';
|
|
36
39
|
};
|
|
@@ -44,8 +47,6 @@ type WorkerToMainMessage<Data, Params> = {
|
|
|
44
47
|
message: string;
|
|
45
48
|
stack?: string;
|
|
46
49
|
};
|
|
47
|
-
} | {
|
|
48
|
-
kind: 'ready';
|
|
49
50
|
};
|
|
50
51
|
/** Worker-specific configuration passed at init time. */
|
|
51
52
|
type WorkerConfig = {
|
|
@@ -33,6 +33,7 @@ function serializeMainMessage(msg) {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// src/worker/workerRunner.ts
|
|
36
|
+
var PERF_BUFFER_SIZE = 120;
|
|
36
37
|
function createWorkerRunner(worker, config) {
|
|
37
38
|
const listeners = /* @__PURE__ */ new Set();
|
|
38
39
|
let currentSnapshot = {
|
|
@@ -42,6 +43,7 @@ function createWorkerRunner(worker, config) {
|
|
|
42
43
|
status: "idle",
|
|
43
44
|
stepDurationMs: 0
|
|
44
45
|
};
|
|
46
|
+
const perfBuffer = [];
|
|
45
47
|
function send(msg) {
|
|
46
48
|
worker.postMessage(serializeMainMessage(msg));
|
|
47
49
|
}
|
|
@@ -50,19 +52,25 @@ function createWorkerRunner(worker, config) {
|
|
|
50
52
|
l(currentSnapshot);
|
|
51
53
|
}
|
|
52
54
|
}
|
|
55
|
+
function pushPerf(snapshot) {
|
|
56
|
+
if (snapshot.tick <= 0) return;
|
|
57
|
+
const last = perfBuffer[perfBuffer.length - 1];
|
|
58
|
+
if (last && last.tick === snapshot.tick) return;
|
|
59
|
+
if (perfBuffer.length >= PERF_BUFFER_SIZE) perfBuffer.shift();
|
|
60
|
+
perfBuffer.push({ tick: snapshot.tick, stepMs: snapshot.stepDurationMs });
|
|
61
|
+
}
|
|
53
62
|
worker.onmessage = (event) => {
|
|
54
63
|
const msg = deserializeWorkerMessage(event.data);
|
|
55
64
|
switch (msg.kind) {
|
|
56
65
|
case "snapshot":
|
|
57
66
|
currentSnapshot = msg.snapshot;
|
|
67
|
+
pushPerf(msg.snapshot);
|
|
58
68
|
emit();
|
|
59
69
|
break;
|
|
60
70
|
case "error":
|
|
61
71
|
currentSnapshot = { ...currentSnapshot, status: "stopped" };
|
|
62
72
|
emit();
|
|
63
73
|
break;
|
|
64
|
-
case "ready":
|
|
65
|
-
break;
|
|
66
74
|
}
|
|
67
75
|
};
|
|
68
76
|
worker.onerror = () => {
|
|
@@ -84,6 +92,16 @@ function createWorkerRunner(worker, config) {
|
|
|
84
92
|
advance: (count = 1) => send({ kind: "advance", count }),
|
|
85
93
|
setParams: (patch) => send({ kind: "setParams", patch }),
|
|
86
94
|
resetWith: (patch) => send({ kind: "resetWith", patch }),
|
|
95
|
+
setConfig: (patch) => send({ kind: "setConfig", patch }),
|
|
96
|
+
recordDrawTime(tick, ms) {
|
|
97
|
+
for (let i = perfBuffer.length - 1; i >= 0; i--) {
|
|
98
|
+
if (perfBuffer[i].tick === tick) {
|
|
99
|
+
perfBuffer[i].drawMs = ms;
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
getPerformance: () => perfBuffer,
|
|
87
105
|
destroy() {
|
|
88
106
|
listeners.clear();
|
|
89
107
|
send({ kind: "destroy" });
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { TickPerformance } from '../engine.cjs';
|
|
1
2
|
import { State } from '../state.cjs';
|
|
2
3
|
import { WorkerConfig } from './protocol.cjs';
|
|
4
|
+
import '../sim.cjs';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Main-thread side of the worker runner.
|
|
@@ -22,6 +24,13 @@ type WorkerRunner<Data, Params> = {
|
|
|
22
24
|
advance: (count?: number) => void;
|
|
23
25
|
setParams: (patch: Partial<Params>) => void;
|
|
24
26
|
resetWith: (patch?: Partial<Params>) => void;
|
|
27
|
+
setConfig: (patch: Partial<WorkerConfig>) => void;
|
|
28
|
+
/**
|
|
29
|
+
* Record a draw time for a tick. Draws happen on the main thread, so
|
|
30
|
+
* timings are tracked here — they never cross the postMessage boundary.
|
|
31
|
+
*/
|
|
32
|
+
recordDrawTime: (tick: number, ms: number) => void;
|
|
33
|
+
getPerformance: () => readonly TickPerformance[];
|
|
25
34
|
destroy: () => void;
|
|
26
35
|
};
|
|
27
36
|
declare function createWorkerRunner<Data, Params>(worker: Worker, config: WorkerRunnerConfig<Params>): WorkerRunner<Data, Params>;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { TickPerformance } from '../engine.js';
|
|
1
2
|
import { State } from '../state.js';
|
|
2
3
|
import { WorkerConfig } from './protocol.js';
|
|
4
|
+
import '../sim.js';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Main-thread side of the worker runner.
|
|
@@ -22,6 +24,13 @@ type WorkerRunner<Data, Params> = {
|
|
|
22
24
|
advance: (count?: number) => void;
|
|
23
25
|
setParams: (patch: Partial<Params>) => void;
|
|
24
26
|
resetWith: (patch?: Partial<Params>) => void;
|
|
27
|
+
setConfig: (patch: Partial<WorkerConfig>) => void;
|
|
28
|
+
/**
|
|
29
|
+
* Record a draw time for a tick. Draws happen on the main thread, so
|
|
30
|
+
* timings are tracked here — they never cross the postMessage boundary.
|
|
31
|
+
*/
|
|
32
|
+
recordDrawTime: (tick: number, ms: number) => void;
|
|
33
|
+
getPerformance: () => readonly TickPerformance[];
|
|
25
34
|
destroy: () => void;
|
|
26
35
|
};
|
|
27
36
|
declare function createWorkerRunner<Data, Params>(worker: Worker, config: WorkerRunnerConfig<Params>): WorkerRunner<Data, Params>;
|
|
@@ -1,70 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}
|
|
5
|
-
function serializeMainMessage(msg) {
|
|
6
|
-
return msg;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
// src/worker/workerRunner.ts
|
|
10
|
-
function createWorkerRunner(worker, config) {
|
|
11
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
12
|
-
let currentSnapshot = {
|
|
13
|
-
data: void 0,
|
|
14
|
-
params: config.initialParams,
|
|
15
|
-
tick: 0,
|
|
16
|
-
status: "idle",
|
|
17
|
-
stepDurationMs: 0
|
|
18
|
-
};
|
|
19
|
-
function send(msg) {
|
|
20
|
-
worker.postMessage(serializeMainMessage(msg));
|
|
21
|
-
}
|
|
22
|
-
function emit() {
|
|
23
|
-
for (const l of listeners) {
|
|
24
|
-
l(currentSnapshot);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
worker.onmessage = (event) => {
|
|
28
|
-
const msg = deserializeWorkerMessage(event.data);
|
|
29
|
-
switch (msg.kind) {
|
|
30
|
-
case "snapshot":
|
|
31
|
-
currentSnapshot = msg.snapshot;
|
|
32
|
-
emit();
|
|
33
|
-
break;
|
|
34
|
-
case "error":
|
|
35
|
-
currentSnapshot = { ...currentSnapshot, status: "stopped" };
|
|
36
|
-
emit();
|
|
37
|
-
break;
|
|
38
|
-
case "ready":
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
worker.onerror = () => {
|
|
43
|
-
currentSnapshot = { ...currentSnapshot, status: "stopped" };
|
|
44
|
-
emit();
|
|
45
|
-
};
|
|
46
|
-
return {
|
|
47
|
-
getSnapshot: () => currentSnapshot,
|
|
48
|
-
subscribe(listener) {
|
|
49
|
-
listeners.add(listener);
|
|
50
|
-
return () => {
|
|
51
|
-
listeners.delete(listener);
|
|
52
|
-
};
|
|
53
|
-
},
|
|
54
|
-
play: () => send({ kind: "play" }),
|
|
55
|
-
pause: () => send({ kind: "pause" }),
|
|
56
|
-
stop: () => send({ kind: "stop" }),
|
|
57
|
-
seek: (tick) => send({ kind: "seek", tick }),
|
|
58
|
-
advance: (count = 1) => send({ kind: "advance", count }),
|
|
59
|
-
setParams: (patch) => send({ kind: "setParams", patch }),
|
|
60
|
-
resetWith: (patch) => send({ kind: "resetWith", patch }),
|
|
61
|
-
destroy() {
|
|
62
|
-
listeners.clear();
|
|
63
|
-
send({ kind: "destroy" });
|
|
64
|
-
worker.terminate();
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
createWorkerRunner
|
|
3
|
+
} from "../chunk-LMHH7YPE.js";
|
|
68
4
|
export {
|
|
69
5
|
createWorkerRunner
|
|
70
6
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "automatick",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "State-machine engine for tick-based simulations in React",
|
|
5
5
|
"author": "jckr",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
|
-
"build": "tsup src/engine.ts src/sim.ts src/state.ts src/worker/workerRunner.ts src/worker/createSimWorker.ts src/worker/protocol.ts src/react/Simulation.tsx src/react/hooks.ts src/react/useSimulationCanvas.ts src/react/PerformanceOverlay.tsx src/react/SimulationContext.tsx src/react/EngineContext.tsx src/react/SimulationControls.tsx src/react/controlPrimitives.tsx src/react/stableCallback.ts --format esm,cjs --dts --clean --external react --external react-dom --external react/jsx-runtime",
|
|
32
|
+
"build": "tsup src/engine.ts src/sim.ts src/state.ts src/worker/workerRunner.ts src/worker/createSimWorker.ts src/worker/protocol.ts src/react/Simulation.tsx src/react/hooks.ts src/react/useSimulationCanvas.ts src/react/PerformanceOverlay.tsx src/react/SimulationContext.tsx src/react/EngineContext.tsx src/react/SimulationControls.tsx src/react/controlPrimitives.tsx src/react/stableCallback.ts --format esm,cjs --dts --clean --external react --external react-dom --external react/jsx-runtime && tsup src/engine.ts src/sim.ts --format esm --no-splitting --no-dts --outDir dist/standalone",
|
|
33
33
|
"test": "vitest run"
|
|
34
34
|
},
|
|
35
35
|
"exports": {
|