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.
@@ -1,124 +1,6 @@
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
- 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
- // 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
- 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.2",
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": {