automatick 0.0.1 → 0.0.2
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 +2 -0
- package/dist/chunk-IKR53C2U.js +12 -0
- package/dist/{chunk-YNLOTPDY.js → chunk-UU5V7HGM.js} +38 -2
- package/dist/engine.cjs +41 -2
- package/dist/engine.d.cts +34 -14
- package/dist/engine.d.ts +34 -14
- package/dist/engine.js +2 -1
- package/dist/react/EngineContext.d.cts +4 -3
- package/dist/react/EngineContext.d.ts +4 -3
- package/dist/react/Simulation.cjs +49 -5
- package/dist/react/Simulation.d.cts +1 -0
- package/dist/react/Simulation.d.ts +1 -0
- package/dist/react/Simulation.js +12 -4
- package/dist/react/SimulationContext.d.cts +1 -2
- package/dist/react/SimulationContext.d.ts +1 -2
- package/dist/react/hooks.d.cts +1 -1
- package/dist/react/hooks.d.ts +1 -1
- package/dist/react/useSimulationCanvas.cjs +2 -10
- package/dist/react/useSimulationCanvas.d.cts +4 -7
- package/dist/react/useSimulationCanvas.d.ts +4 -7
- package/dist/react/useSimulationCanvas.js +2 -10
- package/dist/sim.cjs +7 -2
- package/dist/sim.d.cts +26 -10
- package/dist/sim.d.ts +26 -10
- package/dist/sim.js +6 -5
- package/dist/state.cjs +18 -0
- package/dist/state.d.cts +16 -0
- package/dist/state.d.ts +16 -0
- package/dist/state.js +0 -0
- package/dist/worker/createSimWorker.cjs +2 -0
- package/dist/worker/createSimWorker.d.cts +1 -2
- package/dist/worker/createSimWorker.d.ts +1 -2
- package/dist/worker/createSimWorker.js +2 -0
- package/dist/worker/protocol.d.cts +2 -3
- package/dist/worker/protocol.d.ts +2 -3
- package/dist/worker/workerRunner.cjs +1 -4
- package/dist/worker/workerRunner.d.cts +3 -4
- package/dist/worker/workerRunner.d.ts +3 -4
- package/dist/worker/workerRunner.js +1 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -101,6 +101,8 @@ Useful when `step` is expensive (large grids, n-body simulations, fluid solvers)
|
|
|
101
101
|
| `automatick/react/performance` | `<PerformanceOverlay>` |
|
|
102
102
|
| `automatick/react/context` | `<SimulationContext>` |
|
|
103
103
|
|
|
104
|
+
All entry points use named exports. If you'd rather have a single namespace object, use `import * as Automatick from 'automatick'` and call `Automatick.createEngine(...)` — same for any subpath.
|
|
105
|
+
|
|
104
106
|
## License
|
|
105
107
|
|
|
106
108
|
MIT
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isInitFn
|
|
3
|
+
} from "./chunk-IKR53C2U.js";
|
|
4
|
+
|
|
1
5
|
// src/engine.ts
|
|
2
6
|
var PERF_BUFFER_SIZE = 120;
|
|
3
7
|
var SimulationEngine = class {
|
|
@@ -16,8 +20,16 @@ var SimulationEngine = class {
|
|
|
16
20
|
listeners = /* @__PURE__ */ new Set();
|
|
17
21
|
historyListeners = /* @__PURE__ */ new Set();
|
|
18
22
|
perfBuffer = [];
|
|
23
|
+
rafId = null;
|
|
24
|
+
rafCancel = null;
|
|
19
25
|
constructor(config) {
|
|
20
|
-
|
|
26
|
+
const init = config.init;
|
|
27
|
+
if (isInitFn(init)) {
|
|
28
|
+
this.initFn = init;
|
|
29
|
+
} else {
|
|
30
|
+
const seed = structuredClone(init);
|
|
31
|
+
this.initFn = () => structuredClone(seed);
|
|
32
|
+
}
|
|
21
33
|
this.stepFn = config.step;
|
|
22
34
|
this.shouldStopFn = config.shouldStop;
|
|
23
35
|
this.maxTime = config.maxTime;
|
|
@@ -25,6 +37,23 @@ var SimulationEngine = class {
|
|
|
25
37
|
this.ticksPerFrame = config.ticksPerFrame ?? 1;
|
|
26
38
|
this.params = { ...config.initialParams };
|
|
27
39
|
this.data = this.initFn(this.params);
|
|
40
|
+
if (config.render) {
|
|
41
|
+
this.listeners.add(config.render);
|
|
42
|
+
config.render(this.getSnapshot());
|
|
43
|
+
}
|
|
44
|
+
const autoFrame = config.autoFrame ?? true;
|
|
45
|
+
if (autoFrame) {
|
|
46
|
+
const raf = globalThis.requestAnimationFrame;
|
|
47
|
+
const caf = globalThis.cancelAnimationFrame;
|
|
48
|
+
if (typeof raf === "function" && typeof caf === "function") {
|
|
49
|
+
const loop = (now) => {
|
|
50
|
+
this.handleAnimationFrame(now);
|
|
51
|
+
this.rafId = raf(loop);
|
|
52
|
+
};
|
|
53
|
+
this.rafCancel = caf;
|
|
54
|
+
this.rafId = raf(loop);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
28
57
|
}
|
|
29
58
|
getSnapshot() {
|
|
30
59
|
return {
|
|
@@ -141,6 +170,11 @@ var SimulationEngine = class {
|
|
|
141
170
|
}
|
|
142
171
|
}
|
|
143
172
|
destroy() {
|
|
173
|
+
if (this.rafId !== null && this.rafCancel) {
|
|
174
|
+
this.rafCancel(this.rafId);
|
|
175
|
+
}
|
|
176
|
+
this.rafId = null;
|
|
177
|
+
this.rafCancel = null;
|
|
144
178
|
this.listeners.clear();
|
|
145
179
|
this.historyListeners.clear();
|
|
146
180
|
}
|
|
@@ -160,7 +194,9 @@ var SimulationEngine = class {
|
|
|
160
194
|
this.data = this.stepFn({
|
|
161
195
|
data: this.data,
|
|
162
196
|
params: this.params,
|
|
163
|
-
tick: this.tick
|
|
197
|
+
tick: this.tick,
|
|
198
|
+
status: this.status,
|
|
199
|
+
stepDurationMs: this.lastStepMs
|
|
164
200
|
});
|
|
165
201
|
const t1 = performance.now();
|
|
166
202
|
this.lastStepMs = t1 - t0;
|
package/dist/engine.cjs
CHANGED
|
@@ -24,6 +24,13 @@ __export(engine_exports, {
|
|
|
24
24
|
createEngine: () => createEngine
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(engine_exports);
|
|
27
|
+
|
|
28
|
+
// src/sim.ts
|
|
29
|
+
function isInitFn(init) {
|
|
30
|
+
return typeof init === "function";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/engine.ts
|
|
27
34
|
var PERF_BUFFER_SIZE = 120;
|
|
28
35
|
var SimulationEngine = class {
|
|
29
36
|
data;
|
|
@@ -41,8 +48,16 @@ var SimulationEngine = class {
|
|
|
41
48
|
listeners = /* @__PURE__ */ new Set();
|
|
42
49
|
historyListeners = /* @__PURE__ */ new Set();
|
|
43
50
|
perfBuffer = [];
|
|
51
|
+
rafId = null;
|
|
52
|
+
rafCancel = null;
|
|
44
53
|
constructor(config) {
|
|
45
|
-
|
|
54
|
+
const init = config.init;
|
|
55
|
+
if (isInitFn(init)) {
|
|
56
|
+
this.initFn = init;
|
|
57
|
+
} else {
|
|
58
|
+
const seed = structuredClone(init);
|
|
59
|
+
this.initFn = () => structuredClone(seed);
|
|
60
|
+
}
|
|
46
61
|
this.stepFn = config.step;
|
|
47
62
|
this.shouldStopFn = config.shouldStop;
|
|
48
63
|
this.maxTime = config.maxTime;
|
|
@@ -50,6 +65,23 @@ var SimulationEngine = class {
|
|
|
50
65
|
this.ticksPerFrame = config.ticksPerFrame ?? 1;
|
|
51
66
|
this.params = { ...config.initialParams };
|
|
52
67
|
this.data = this.initFn(this.params);
|
|
68
|
+
if (config.render) {
|
|
69
|
+
this.listeners.add(config.render);
|
|
70
|
+
config.render(this.getSnapshot());
|
|
71
|
+
}
|
|
72
|
+
const autoFrame = config.autoFrame ?? true;
|
|
73
|
+
if (autoFrame) {
|
|
74
|
+
const raf = globalThis.requestAnimationFrame;
|
|
75
|
+
const caf = globalThis.cancelAnimationFrame;
|
|
76
|
+
if (typeof raf === "function" && typeof caf === "function") {
|
|
77
|
+
const loop = (now) => {
|
|
78
|
+
this.handleAnimationFrame(now);
|
|
79
|
+
this.rafId = raf(loop);
|
|
80
|
+
};
|
|
81
|
+
this.rafCancel = caf;
|
|
82
|
+
this.rafId = raf(loop);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
53
85
|
}
|
|
54
86
|
getSnapshot() {
|
|
55
87
|
return {
|
|
@@ -166,6 +198,11 @@ var SimulationEngine = class {
|
|
|
166
198
|
}
|
|
167
199
|
}
|
|
168
200
|
destroy() {
|
|
201
|
+
if (this.rafId !== null && this.rafCancel) {
|
|
202
|
+
this.rafCancel(this.rafId);
|
|
203
|
+
}
|
|
204
|
+
this.rafId = null;
|
|
205
|
+
this.rafCancel = null;
|
|
169
206
|
this.listeners.clear();
|
|
170
207
|
this.historyListeners.clear();
|
|
171
208
|
}
|
|
@@ -185,7 +222,9 @@ var SimulationEngine = class {
|
|
|
185
222
|
this.data = this.stepFn({
|
|
186
223
|
data: this.data,
|
|
187
224
|
params: this.params,
|
|
188
|
-
tick: this.tick
|
|
225
|
+
tick: this.tick,
|
|
226
|
+
status: this.status,
|
|
227
|
+
stepDurationMs: this.lastStepMs
|
|
189
228
|
});
|
|
190
229
|
const t1 = performance.now();
|
|
191
230
|
this.lastStepMs = t1 - t0;
|
package/dist/engine.d.cts
CHANGED
|
@@ -1,26 +1,44 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SimInit } from './sim.cjs';
|
|
2
|
+
import { State, SimulationStatus } from './state.cjs';
|
|
2
3
|
|
|
3
|
-
type SimulationStatus = 'idle' | 'playing' | 'paused' | 'stopped';
|
|
4
4
|
type TickPerformance = {
|
|
5
5
|
tick: number;
|
|
6
6
|
stepMs: number;
|
|
7
7
|
drawMs?: number;
|
|
8
8
|
};
|
|
9
9
|
type EngineConfig<Data, Params> = {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Initial simulation state — value or `(params) => Data`. See `SimInit`.
|
|
12
|
+
* When a value is passed, the engine `structuredClone`s it on each (re)init.
|
|
13
|
+
*/
|
|
14
|
+
init: SimInit<Data, Params>;
|
|
15
|
+
step: (state: State<Data, Params>) => Data;
|
|
12
16
|
shouldStop?: (data: Data, params: Params) => boolean;
|
|
13
17
|
initialParams: Params;
|
|
14
18
|
maxTime?: number;
|
|
15
19
|
delayMs?: number;
|
|
16
20
|
ticksPerFrame?: number;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Optional render callback — sugar for the vanilla path. When provided, the
|
|
23
|
+
* engine calls it once with the initial state (right after init) and on
|
|
24
|
+
* every state emit thereafter, equivalent to `engine.subscribe(render)`
|
|
25
|
+
* followed by an initial paint. `subscribe` remains the lower-level
|
|
26
|
+
* primitive; React adapter and worker callers wire their own subscribers.
|
|
27
|
+
*/
|
|
28
|
+
render?: (snapshot: State<Data, Params>) => void;
|
|
29
|
+
/**
|
|
30
|
+
* Drive `handleAnimationFrame` from an internal `requestAnimationFrame` loop.
|
|
31
|
+
* Defaults to `true` so vanilla callers don't have to write the loop. The
|
|
32
|
+
* React adapter and worker host pass `false` because they either own the
|
|
33
|
+
* frame loop themselves or run in an environment that has none. The loop is
|
|
34
|
+
* created at construction and torn down by `destroy()`. If
|
|
35
|
+
* `globalThis.requestAnimationFrame` is missing (server, worker), the option
|
|
36
|
+
* is silently a no-op.
|
|
37
|
+
*
|
|
38
|
+
* Note: when `true`, the rAF closure pins this engine in memory — vanilla
|
|
39
|
+
* consumers must call `destroy()` to let it be garbage-collected.
|
|
40
|
+
*/
|
|
41
|
+
autoFrame?: boolean;
|
|
24
42
|
};
|
|
25
43
|
declare class SimulationEngine<Data, Params> {
|
|
26
44
|
private data;
|
|
@@ -38,14 +56,16 @@ declare class SimulationEngine<Data, Params> {
|
|
|
38
56
|
private readonly listeners;
|
|
39
57
|
private readonly historyListeners;
|
|
40
58
|
private readonly perfBuffer;
|
|
59
|
+
private rafId;
|
|
60
|
+
private rafCancel;
|
|
41
61
|
constructor(config: EngineConfig<Data, Params>);
|
|
42
|
-
getSnapshot():
|
|
62
|
+
getSnapshot(): State<Data, Params>;
|
|
43
63
|
getStatus(): SimulationStatus;
|
|
44
64
|
getPerformance(): readonly TickPerformance[];
|
|
45
65
|
setDelayMs(ms: number): void;
|
|
46
66
|
setTicksPerFrame(n: number): void;
|
|
47
67
|
recordDrawTime(tick: number, ms: number): void;
|
|
48
|
-
subscribe(listener: (snapshot:
|
|
68
|
+
subscribe(listener: (snapshot: State<Data, Params>) => void): () => void;
|
|
49
69
|
subscribeHistory(listener: (entry: {
|
|
50
70
|
tick: number;
|
|
51
71
|
data: Data;
|
|
@@ -69,4 +89,4 @@ declare class SimulationEngine<Data, Params> {
|
|
|
69
89
|
}
|
|
70
90
|
declare function createEngine<Data, Params>(config: EngineConfig<Data, Params>): SimulationEngine<Data, Params>;
|
|
71
91
|
|
|
72
|
-
export { type EngineConfig,
|
|
92
|
+
export { type EngineConfig, SimulationEngine, SimulationStatus, State, type TickPerformance, createEngine };
|
package/dist/engine.d.ts
CHANGED
|
@@ -1,26 +1,44 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SimInit } from './sim.js';
|
|
2
|
+
import { State, SimulationStatus } from './state.js';
|
|
2
3
|
|
|
3
|
-
type SimulationStatus = 'idle' | 'playing' | 'paused' | 'stopped';
|
|
4
4
|
type TickPerformance = {
|
|
5
5
|
tick: number;
|
|
6
6
|
stepMs: number;
|
|
7
7
|
drawMs?: number;
|
|
8
8
|
};
|
|
9
9
|
type EngineConfig<Data, Params> = {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Initial simulation state — value or `(params) => Data`. See `SimInit`.
|
|
12
|
+
* When a value is passed, the engine `structuredClone`s it on each (re)init.
|
|
13
|
+
*/
|
|
14
|
+
init: SimInit<Data, Params>;
|
|
15
|
+
step: (state: State<Data, Params>) => Data;
|
|
12
16
|
shouldStop?: (data: Data, params: Params) => boolean;
|
|
13
17
|
initialParams: Params;
|
|
14
18
|
maxTime?: number;
|
|
15
19
|
delayMs?: number;
|
|
16
20
|
ticksPerFrame?: number;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Optional render callback — sugar for the vanilla path. When provided, the
|
|
23
|
+
* engine calls it once with the initial state (right after init) and on
|
|
24
|
+
* every state emit thereafter, equivalent to `engine.subscribe(render)`
|
|
25
|
+
* followed by an initial paint. `subscribe` remains the lower-level
|
|
26
|
+
* primitive; React adapter and worker callers wire their own subscribers.
|
|
27
|
+
*/
|
|
28
|
+
render?: (snapshot: State<Data, Params>) => void;
|
|
29
|
+
/**
|
|
30
|
+
* Drive `handleAnimationFrame` from an internal `requestAnimationFrame` loop.
|
|
31
|
+
* Defaults to `true` so vanilla callers don't have to write the loop. The
|
|
32
|
+
* React adapter and worker host pass `false` because they either own the
|
|
33
|
+
* frame loop themselves or run in an environment that has none. The loop is
|
|
34
|
+
* created at construction and torn down by `destroy()`. If
|
|
35
|
+
* `globalThis.requestAnimationFrame` is missing (server, worker), the option
|
|
36
|
+
* is silently a no-op.
|
|
37
|
+
*
|
|
38
|
+
* Note: when `true`, the rAF closure pins this engine in memory — vanilla
|
|
39
|
+
* consumers must call `destroy()` to let it be garbage-collected.
|
|
40
|
+
*/
|
|
41
|
+
autoFrame?: boolean;
|
|
24
42
|
};
|
|
25
43
|
declare class SimulationEngine<Data, Params> {
|
|
26
44
|
private data;
|
|
@@ -38,14 +56,16 @@ declare class SimulationEngine<Data, Params> {
|
|
|
38
56
|
private readonly listeners;
|
|
39
57
|
private readonly historyListeners;
|
|
40
58
|
private readonly perfBuffer;
|
|
59
|
+
private rafId;
|
|
60
|
+
private rafCancel;
|
|
41
61
|
constructor(config: EngineConfig<Data, Params>);
|
|
42
|
-
getSnapshot():
|
|
62
|
+
getSnapshot(): State<Data, Params>;
|
|
43
63
|
getStatus(): SimulationStatus;
|
|
44
64
|
getPerformance(): readonly TickPerformance[];
|
|
45
65
|
setDelayMs(ms: number): void;
|
|
46
66
|
setTicksPerFrame(n: number): void;
|
|
47
67
|
recordDrawTime(tick: number, ms: number): void;
|
|
48
|
-
subscribe(listener: (snapshot:
|
|
68
|
+
subscribe(listener: (snapshot: State<Data, Params>) => void): () => void;
|
|
49
69
|
subscribeHistory(listener: (entry: {
|
|
50
70
|
tick: number;
|
|
51
71
|
data: Data;
|
|
@@ -69,4 +89,4 @@ declare class SimulationEngine<Data, Params> {
|
|
|
69
89
|
}
|
|
70
90
|
declare function createEngine<Data, Params>(config: EngineConfig<Data, Params>): SimulationEngine<Data, Params>;
|
|
71
91
|
|
|
72
|
-
export { type EngineConfig,
|
|
92
|
+
export { type EngineConfig, SimulationEngine, SimulationStatus, State, type TickPerformance, createEngine };
|
package/dist/engine.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { TickPerformance } from '../engine.cjs';
|
|
3
|
+
import { State } from '../state.cjs';
|
|
3
4
|
import '../sim.cjs';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -8,8 +9,8 @@ import '../sim.cjs';
|
|
|
8
9
|
* and to record draw timing back to the engine's performance buffer.
|
|
9
10
|
*/
|
|
10
11
|
type EngineContextValue = {
|
|
11
|
-
subscribe: (listener: (snapshot:
|
|
12
|
-
getSnapshot: () =>
|
|
12
|
+
subscribe: (listener: (snapshot: State<unknown, unknown>) => void) => () => void;
|
|
13
|
+
getSnapshot: () => State<unknown, unknown>;
|
|
13
14
|
recordDrawTime: (tick: number, ms: number) => void;
|
|
14
15
|
getPerformance: () => readonly TickPerformance[];
|
|
15
16
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { TickPerformance } from '../engine.js';
|
|
3
|
+
import { State } from '../state.js';
|
|
3
4
|
import '../sim.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -8,8 +9,8 @@ import '../sim.js';
|
|
|
8
9
|
* and to record draw timing back to the engine's performance buffer.
|
|
9
10
|
*/
|
|
10
11
|
type EngineContextValue = {
|
|
11
|
-
subscribe: (listener: (snapshot:
|
|
12
|
-
getSnapshot: () =>
|
|
12
|
+
subscribe: (listener: (snapshot: State<unknown, unknown>) => void) => () => void;
|
|
13
|
+
getSnapshot: () => State<unknown, unknown>;
|
|
13
14
|
recordDrawTime: (tick: number, ms: number) => void;
|
|
14
15
|
getPerformance: () => readonly TickPerformance[];
|
|
15
16
|
};
|
|
@@ -35,6 +35,11 @@ __export(Simulation_exports, {
|
|
|
35
35
|
module.exports = __toCommonJS(Simulation_exports);
|
|
36
36
|
var import_react4 = __toESM(require("react"), 1);
|
|
37
37
|
|
|
38
|
+
// src/sim.ts
|
|
39
|
+
function isInitFn(init) {
|
|
40
|
+
return typeof init === "function";
|
|
41
|
+
}
|
|
42
|
+
|
|
38
43
|
// src/engine.ts
|
|
39
44
|
var PERF_BUFFER_SIZE = 120;
|
|
40
45
|
var SimulationEngine = class {
|
|
@@ -53,8 +58,16 @@ var SimulationEngine = class {
|
|
|
53
58
|
listeners = /* @__PURE__ */ new Set();
|
|
54
59
|
historyListeners = /* @__PURE__ */ new Set();
|
|
55
60
|
perfBuffer = [];
|
|
61
|
+
rafId = null;
|
|
62
|
+
rafCancel = null;
|
|
56
63
|
constructor(config) {
|
|
57
|
-
|
|
64
|
+
const init = config.init;
|
|
65
|
+
if (isInitFn(init)) {
|
|
66
|
+
this.initFn = init;
|
|
67
|
+
} else {
|
|
68
|
+
const seed = structuredClone(init);
|
|
69
|
+
this.initFn = () => structuredClone(seed);
|
|
70
|
+
}
|
|
58
71
|
this.stepFn = config.step;
|
|
59
72
|
this.shouldStopFn = config.shouldStop;
|
|
60
73
|
this.maxTime = config.maxTime;
|
|
@@ -62,6 +75,23 @@ var SimulationEngine = class {
|
|
|
62
75
|
this.ticksPerFrame = config.ticksPerFrame ?? 1;
|
|
63
76
|
this.params = { ...config.initialParams };
|
|
64
77
|
this.data = this.initFn(this.params);
|
|
78
|
+
if (config.render) {
|
|
79
|
+
this.listeners.add(config.render);
|
|
80
|
+
config.render(this.getSnapshot());
|
|
81
|
+
}
|
|
82
|
+
const autoFrame = config.autoFrame ?? true;
|
|
83
|
+
if (autoFrame) {
|
|
84
|
+
const raf = globalThis.requestAnimationFrame;
|
|
85
|
+
const caf = globalThis.cancelAnimationFrame;
|
|
86
|
+
if (typeof raf === "function" && typeof caf === "function") {
|
|
87
|
+
const loop = (now) => {
|
|
88
|
+
this.handleAnimationFrame(now);
|
|
89
|
+
this.rafId = raf(loop);
|
|
90
|
+
};
|
|
91
|
+
this.rafCancel = caf;
|
|
92
|
+
this.rafId = raf(loop);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
65
95
|
}
|
|
66
96
|
getSnapshot() {
|
|
67
97
|
return {
|
|
@@ -178,6 +208,11 @@ var SimulationEngine = class {
|
|
|
178
208
|
}
|
|
179
209
|
}
|
|
180
210
|
destroy() {
|
|
211
|
+
if (this.rafId !== null && this.rafCancel) {
|
|
212
|
+
this.rafCancel(this.rafId);
|
|
213
|
+
}
|
|
214
|
+
this.rafId = null;
|
|
215
|
+
this.rafCancel = null;
|
|
181
216
|
this.listeners.clear();
|
|
182
217
|
this.historyListeners.clear();
|
|
183
218
|
}
|
|
@@ -197,7 +232,9 @@ var SimulationEngine = class {
|
|
|
197
232
|
this.data = this.stepFn({
|
|
198
233
|
data: this.data,
|
|
199
234
|
params: this.params,
|
|
200
|
-
tick: this.tick
|
|
235
|
+
tick: this.tick,
|
|
236
|
+
status: this.status,
|
|
237
|
+
stepDurationMs: this.lastStepMs
|
|
201
238
|
});
|
|
202
239
|
const t1 = performance.now();
|
|
203
240
|
this.lastStepMs = t1 - t0;
|
|
@@ -271,7 +308,9 @@ function LocalSimulation(props) {
|
|
|
271
308
|
initialParams,
|
|
272
309
|
maxTime: props.maxTime,
|
|
273
310
|
delayMs: props.delayMs,
|
|
274
|
-
ticksPerFrame: props.ticksPerFrame
|
|
311
|
+
ticksPerFrame: props.ticksPerFrame,
|
|
312
|
+
// The React adapter drives its own rAF loop tied to component lifecycle.
|
|
313
|
+
autoFrame: false
|
|
275
314
|
});
|
|
276
315
|
}
|
|
277
316
|
const engine = engineRef.current;
|
|
@@ -312,7 +351,9 @@ function WorkerSimulation(props) {
|
|
|
312
351
|
const [backend, setBackend] = import_react4.default.useState(
|
|
313
352
|
null
|
|
314
353
|
);
|
|
315
|
-
const [snapshot, setSnapshot] = import_react4.default.useState(
|
|
354
|
+
const [snapshot, setSnapshot] = import_react4.default.useState(
|
|
355
|
+
null
|
|
356
|
+
);
|
|
316
357
|
const propsRef = import_react4.default.useRef(props);
|
|
317
358
|
propsRef.current = props;
|
|
318
359
|
import_react4.default.useEffect(() => {
|
|
@@ -329,7 +370,8 @@ function WorkerSimulation(props) {
|
|
|
329
370
|
step: simModule.step,
|
|
330
371
|
shouldStop: simModule.shouldStop,
|
|
331
372
|
initialParams,
|
|
332
|
-
maxTime: p.maxTime
|
|
373
|
+
maxTime: p.maxTime,
|
|
374
|
+
autoFrame: false
|
|
333
375
|
});
|
|
334
376
|
if (cancelled) {
|
|
335
377
|
engine.destroy();
|
|
@@ -452,6 +494,8 @@ function SimulationProvider({
|
|
|
452
494
|
);
|
|
453
495
|
const engineValue = import_react4.default.useMemo(
|
|
454
496
|
() => ({
|
|
497
|
+
// TODO(#14): casts here bridge EngineContext's non-generic shape to
|
|
498
|
+
// the typed Backend<Data, Params>. Make EngineContext generic to drop them.
|
|
455
499
|
subscribe: (listener) => backend.subscribe(
|
|
456
500
|
listener
|
|
457
501
|
),
|
package/dist/react/Simulation.js
CHANGED
|
@@ -3,7 +3,8 @@ import {
|
|
|
3
3
|
} from "../chunk-SK5SHIWY.js";
|
|
4
4
|
import {
|
|
5
5
|
createEngine
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-UU5V7HGM.js";
|
|
7
|
+
import "../chunk-IKR53C2U.js";
|
|
7
8
|
import {
|
|
8
9
|
EngineContext
|
|
9
10
|
} from "../chunk-OA6FGXTP.js";
|
|
@@ -26,7 +27,9 @@ function LocalSimulation(props) {
|
|
|
26
27
|
initialParams,
|
|
27
28
|
maxTime: props.maxTime,
|
|
28
29
|
delayMs: props.delayMs,
|
|
29
|
-
ticksPerFrame: props.ticksPerFrame
|
|
30
|
+
ticksPerFrame: props.ticksPerFrame,
|
|
31
|
+
// The React adapter drives its own rAF loop tied to component lifecycle.
|
|
32
|
+
autoFrame: false
|
|
30
33
|
});
|
|
31
34
|
}
|
|
32
35
|
const engine = engineRef.current;
|
|
@@ -67,7 +70,9 @@ function WorkerSimulation(props) {
|
|
|
67
70
|
const [backend, setBackend] = React.useState(
|
|
68
71
|
null
|
|
69
72
|
);
|
|
70
|
-
const [snapshot, setSnapshot] = React.useState(
|
|
73
|
+
const [snapshot, setSnapshot] = React.useState(
|
|
74
|
+
null
|
|
75
|
+
);
|
|
71
76
|
const propsRef = React.useRef(props);
|
|
72
77
|
propsRef.current = props;
|
|
73
78
|
React.useEffect(() => {
|
|
@@ -84,7 +89,8 @@ function WorkerSimulation(props) {
|
|
|
84
89
|
step: simModule.step,
|
|
85
90
|
shouldStop: simModule.shouldStop,
|
|
86
91
|
initialParams,
|
|
87
|
-
maxTime: p.maxTime
|
|
92
|
+
maxTime: p.maxTime,
|
|
93
|
+
autoFrame: false
|
|
88
94
|
});
|
|
89
95
|
if (cancelled) {
|
|
90
96
|
engine.destroy();
|
|
@@ -207,6 +213,8 @@ function SimulationProvider({
|
|
|
207
213
|
);
|
|
208
214
|
const engineValue = React.useMemo(
|
|
209
215
|
() => ({
|
|
216
|
+
// TODO(#14): casts here bridge EngineContext's non-generic shape to
|
|
217
|
+
// the typed Backend<Data, Params>. Make EngineContext generic to drop them.
|
|
210
218
|
subscribe: (listener) => backend.subscribe(
|
|
211
219
|
listener
|
|
212
220
|
),
|
package/dist/react/hooks.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SimulationContextValue } from './SimulationContext.cjs';
|
|
2
2
|
import { SimModule } from '../sim.cjs';
|
|
3
3
|
import 'react';
|
|
4
|
-
import '../
|
|
4
|
+
import '../state.cjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Helper type: extract Data and Params from a SimModule type, or use them directly.
|
package/dist/react/hooks.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SimulationContextValue } from './SimulationContext.js';
|
|
2
2
|
import { SimModule } from '../sim.js';
|
|
3
3
|
import 'react';
|
|
4
|
-
import '../
|
|
4
|
+
import '../state.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Helper type: extract Data and Params from a SimModule type, or use them directly.
|
|
@@ -59,11 +59,7 @@ function useSimulationCanvas(draw) {
|
|
|
59
59
|
if (ctx) {
|
|
60
60
|
const snap = engineCtx.getSnapshot();
|
|
61
61
|
const t0 = performance.now();
|
|
62
|
-
drawRef.current(ctx,
|
|
63
|
-
data: snap.data,
|
|
64
|
-
params: snap.params,
|
|
65
|
-
tick: snap.tick
|
|
66
|
-
});
|
|
62
|
+
drawRef.current(ctx, snap);
|
|
67
63
|
engineCtx.recordDrawTime(snap.tick, performance.now() - t0);
|
|
68
64
|
}
|
|
69
65
|
}
|
|
@@ -74,11 +70,7 @@ function useSimulationCanvas(draw) {
|
|
|
74
70
|
const ctx = canvas2.getContext("2d");
|
|
75
71
|
if (!ctx) return;
|
|
76
72
|
const t0 = performance.now();
|
|
77
|
-
drawRef.current(ctx,
|
|
78
|
-
data: snap.data,
|
|
79
|
-
params: snap.params,
|
|
80
|
-
tick: snap.tick
|
|
81
|
-
});
|
|
73
|
+
drawRef.current(ctx, snap);
|
|
82
74
|
engineCtx.recordDrawTime(snap.tick, performance.now() - t0);
|
|
83
75
|
}
|
|
84
76
|
);
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { SimModule } from '../sim.cjs';
|
|
3
|
+
import { State } from '../state.cjs';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Draw callback signature for useSimulationCanvas.
|
|
6
|
-
* Called imperatively on every engine
|
|
7
|
+
* Called imperatively on every engine state emit — no React re-render involved.
|
|
7
8
|
*/
|
|
8
|
-
type CanvasDrawFn<Data, Params> = (ctx: CanvasRenderingContext2D, snapshot:
|
|
9
|
-
data: Data;
|
|
10
|
-
params: Params;
|
|
11
|
-
tick: number;
|
|
12
|
-
}) => void;
|
|
9
|
+
type CanvasDrawFn<Data, Params> = (ctx: CanvasRenderingContext2D, snapshot: State<Data, Params>) => void;
|
|
13
10
|
type InferDraw<T, FallbackParams> = T extends SimModule<infer D, infer P> ? CanvasDrawFn<D, P> : CanvasDrawFn<T, FallbackParams>;
|
|
14
11
|
/**
|
|
15
|
-
* Subscribe to simulation
|
|
12
|
+
* Subscribe to simulation state and draw to a canvas without React re-renders.
|
|
16
13
|
*
|
|
17
14
|
* Returns a ref to attach to a `<canvas>` element. The `draw` callback is called
|
|
18
15
|
* directly from the engine's subscription — it bypasses React's state/reconciliation
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { SimModule } from '../sim.js';
|
|
3
|
+
import { State } from '../state.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Draw callback signature for useSimulationCanvas.
|
|
6
|
-
* Called imperatively on every engine
|
|
7
|
+
* Called imperatively on every engine state emit — no React re-render involved.
|
|
7
8
|
*/
|
|
8
|
-
type CanvasDrawFn<Data, Params> = (ctx: CanvasRenderingContext2D, snapshot:
|
|
9
|
-
data: Data;
|
|
10
|
-
params: Params;
|
|
11
|
-
tick: number;
|
|
12
|
-
}) => void;
|
|
9
|
+
type CanvasDrawFn<Data, Params> = (ctx: CanvasRenderingContext2D, snapshot: State<Data, Params>) => void;
|
|
13
10
|
type InferDraw<T, FallbackParams> = T extends SimModule<infer D, infer P> ? CanvasDrawFn<D, P> : CanvasDrawFn<T, FallbackParams>;
|
|
14
11
|
/**
|
|
15
|
-
* Subscribe to simulation
|
|
12
|
+
* Subscribe to simulation state and draw to a canvas without React re-renders.
|
|
16
13
|
*
|
|
17
14
|
* Returns a ref to attach to a `<canvas>` element. The `draw` callback is called
|
|
18
15
|
* directly from the engine's subscription — it bypasses React's state/reconciliation
|
|
@@ -21,11 +21,7 @@ function useSimulationCanvas(draw) {
|
|
|
21
21
|
if (ctx) {
|
|
22
22
|
const snap = engineCtx.getSnapshot();
|
|
23
23
|
const t0 = performance.now();
|
|
24
|
-
drawRef.current(ctx,
|
|
25
|
-
data: snap.data,
|
|
26
|
-
params: snap.params,
|
|
27
|
-
tick: snap.tick
|
|
28
|
-
});
|
|
24
|
+
drawRef.current(ctx, snap);
|
|
29
25
|
engineCtx.recordDrawTime(snap.tick, performance.now() - t0);
|
|
30
26
|
}
|
|
31
27
|
}
|
|
@@ -36,11 +32,7 @@ function useSimulationCanvas(draw) {
|
|
|
36
32
|
const ctx = canvas2.getContext("2d");
|
|
37
33
|
if (!ctx) return;
|
|
38
34
|
const t0 = performance.now();
|
|
39
|
-
drawRef.current(ctx,
|
|
40
|
-
data: snap.data,
|
|
41
|
-
params: snap.params,
|
|
42
|
-
tick: snap.tick
|
|
43
|
-
});
|
|
35
|
+
drawRef.current(ctx, snap);
|
|
44
36
|
engineCtx.recordDrawTime(snap.tick, performance.now() - t0);
|
|
45
37
|
}
|
|
46
38
|
);
|
package/dist/sim.cjs
CHANGED
|
@@ -20,13 +20,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/sim.ts
|
|
21
21
|
var sim_exports = {};
|
|
22
22
|
__export(sim_exports, {
|
|
23
|
-
defineSim: () => defineSim
|
|
23
|
+
defineSim: () => defineSim,
|
|
24
|
+
isInitFn: () => isInitFn
|
|
24
25
|
});
|
|
25
26
|
module.exports = __toCommonJS(sim_exports);
|
|
27
|
+
function isInitFn(init) {
|
|
28
|
+
return typeof init === "function";
|
|
29
|
+
}
|
|
26
30
|
function defineSim(sim) {
|
|
27
31
|
return sim;
|
|
28
32
|
}
|
|
29
33
|
// Annotate the CommonJS export names for ESM import in node:
|
|
30
34
|
0 && (module.exports = {
|
|
31
|
-
defineSim
|
|
35
|
+
defineSim,
|
|
36
|
+
isInitFn
|
|
32
37
|
});
|
package/dist/sim.d.cts
CHANGED
|
@@ -1,15 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { State } from './state.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Initial state for a simulation. Either a value of type `Data`, or a function
|
|
5
|
+
* `(params) => Data`. The function form is required when the initial state
|
|
6
|
+
* depends on params; otherwise pass the value directly.
|
|
7
|
+
*
|
|
8
|
+
* `Data` itself must not be a function — it has to be structured-cloneable so
|
|
9
|
+
* it can cross the worker boundary and be safely re-emitted on resetWith.
|
|
10
|
+
* That's what makes this union unambiguous: a `function` value is always the
|
|
11
|
+
* `(params) => Data` branch.
|
|
12
|
+
*/
|
|
13
|
+
type SimInit<Data, Params> = ((params: Params) => Data) | Data;
|
|
14
|
+
/**
|
|
15
|
+
* Type guard that narrows a `SimInit` to its function branch. Defined as a
|
|
16
|
+
* user-typed predicate so we can do the narrowing without an `as` cast.
|
|
17
|
+
*/
|
|
18
|
+
declare function isInitFn<Data, Params>(init: SimInit<Data, Params>): init is (params: Params) => Data;
|
|
7
19
|
/** A simulation module: the pure business logic that automatick drives. */
|
|
8
20
|
type SimModule<Data, Params> = {
|
|
9
|
-
/**
|
|
10
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Initial simulation state — value or `(params) => Data`. When a value is
|
|
23
|
+
* passed, the engine takes a fresh `structuredClone` on every (re)init so
|
|
24
|
+
* mutations inside `step` never leak across resets.
|
|
25
|
+
*/
|
|
26
|
+
init: SimInit<Data, Params>;
|
|
11
27
|
/** Advance the simulation by one tick. Must be pure and synchronous. */
|
|
12
|
-
step: (
|
|
28
|
+
step: (state: State<Data, Params>) => Data;
|
|
13
29
|
/** Optional termination predicate. Checked after each step. If it returns true, the simulation stops. */
|
|
14
30
|
shouldStop?: (data: Data, params: Params) => boolean;
|
|
15
31
|
/** Default parameter values. Used at engine creation if no params override is provided. */
|
|
@@ -37,4 +53,4 @@ type SimData<M> = M extends SimModule<infer D, infer _P> ? D : never;
|
|
|
37
53
|
/** Extract the Params type from a SimModule. */
|
|
38
54
|
type SimParams<M> = M extends SimModule<infer _D, infer P> ? P : never;
|
|
39
55
|
|
|
40
|
-
export { type SimData, type
|
|
56
|
+
export { type SimData, type SimInit, type SimModule, type SimParams, defineSim, isInitFn };
|
package/dist/sim.d.ts
CHANGED
|
@@ -1,15 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { State } from './state.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Initial state for a simulation. Either a value of type `Data`, or a function
|
|
5
|
+
* `(params) => Data`. The function form is required when the initial state
|
|
6
|
+
* depends on params; otherwise pass the value directly.
|
|
7
|
+
*
|
|
8
|
+
* `Data` itself must not be a function — it has to be structured-cloneable so
|
|
9
|
+
* it can cross the worker boundary and be safely re-emitted on resetWith.
|
|
10
|
+
* That's what makes this union unambiguous: a `function` value is always the
|
|
11
|
+
* `(params) => Data` branch.
|
|
12
|
+
*/
|
|
13
|
+
type SimInit<Data, Params> = ((params: Params) => Data) | Data;
|
|
14
|
+
/**
|
|
15
|
+
* Type guard that narrows a `SimInit` to its function branch. Defined as a
|
|
16
|
+
* user-typed predicate so we can do the narrowing without an `as` cast.
|
|
17
|
+
*/
|
|
18
|
+
declare function isInitFn<Data, Params>(init: SimInit<Data, Params>): init is (params: Params) => Data;
|
|
7
19
|
/** A simulation module: the pure business logic that automatick drives. */
|
|
8
20
|
type SimModule<Data, Params> = {
|
|
9
|
-
/**
|
|
10
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Initial simulation state — value or `(params) => Data`. When a value is
|
|
23
|
+
* passed, the engine takes a fresh `structuredClone` on every (re)init so
|
|
24
|
+
* mutations inside `step` never leak across resets.
|
|
25
|
+
*/
|
|
26
|
+
init: SimInit<Data, Params>;
|
|
11
27
|
/** Advance the simulation by one tick. Must be pure and synchronous. */
|
|
12
|
-
step: (
|
|
28
|
+
step: (state: State<Data, Params>) => Data;
|
|
13
29
|
/** Optional termination predicate. Checked after each step. If it returns true, the simulation stops. */
|
|
14
30
|
shouldStop?: (data: Data, params: Params) => boolean;
|
|
15
31
|
/** Default parameter values. Used at engine creation if no params override is provided. */
|
|
@@ -37,4 +53,4 @@ type SimData<M> = M extends SimModule<infer D, infer _P> ? D : never;
|
|
|
37
53
|
/** Extract the Params type from a SimModule. */
|
|
38
54
|
type SimParams<M> = M extends SimModule<infer _D, infer P> ? P : never;
|
|
39
55
|
|
|
40
|
-
export { type SimData, type
|
|
56
|
+
export { type SimData, type SimInit, type SimModule, type SimParams, defineSim, isInitFn };
|
package/dist/sim.js
CHANGED
package/dist/state.cjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
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 __copyProps = (to, from, except, desc) => {
|
|
7
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
|
+
for (let key of __getOwnPropNames(from))
|
|
9
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
10
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
11
|
+
}
|
|
12
|
+
return to;
|
|
13
|
+
};
|
|
14
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
15
|
+
|
|
16
|
+
// src/state.ts
|
|
17
|
+
var state_exports = {};
|
|
18
|
+
module.exports = __toCommonJS(state_exports);
|
package/dist/state.d.cts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The unified engine state shape — passed to `step`, returned by `getSnapshot`,
|
|
3
|
+
* and delivered to the `render` callback. All three are operations on the
|
|
4
|
+
* engine's inner state, so they share one type.
|
|
5
|
+
*/
|
|
6
|
+
type State<Data, Params> = {
|
|
7
|
+
data: Data;
|
|
8
|
+
params: Params;
|
|
9
|
+
tick: number;
|
|
10
|
+
status: SimulationStatus;
|
|
11
|
+
/** Duration of the previous step in ms. `0` before the first step has run. */
|
|
12
|
+
stepDurationMs: number;
|
|
13
|
+
};
|
|
14
|
+
type SimulationStatus = 'idle' | 'playing' | 'paused' | 'stopped';
|
|
15
|
+
|
|
16
|
+
export type { SimulationStatus, State };
|
package/dist/state.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The unified engine state shape — passed to `step`, returned by `getSnapshot`,
|
|
3
|
+
* and delivered to the `render` callback. All three are operations on the
|
|
4
|
+
* engine's inner state, so they share one type.
|
|
5
|
+
*/
|
|
6
|
+
type State<Data, Params> = {
|
|
7
|
+
data: Data;
|
|
8
|
+
params: Params;
|
|
9
|
+
tick: number;
|
|
10
|
+
status: SimulationStatus;
|
|
11
|
+
/** Duration of the previous step in ms. `0` before the first step has run. */
|
|
12
|
+
stepDurationMs: number;
|
|
13
|
+
};
|
|
14
|
+
type SimulationStatus = 'idle' | 'playing' | 'paused' | 'stopped';
|
|
15
|
+
|
|
16
|
+
export type { SimulationStatus, State };
|
package/dist/state.js
ADDED
|
File without changes
|
|
@@ -78,6 +78,8 @@ self.onmessage = async (event) => {
|
|
|
78
78
|
shouldStop: sim.shouldStop,
|
|
79
79
|
initialParams: msg.params,
|
|
80
80
|
maxTime: msg.config.maxTime,
|
|
81
|
+
// Worker host owns its own setTimeout-driven loop; rAF wouldn't exist here anyway.
|
|
82
|
+
autoFrame: false,
|
|
81
83
|
});
|
|
82
84
|
postMessage({ kind: 'ready' });
|
|
83
85
|
emitSnapshot();
|
|
@@ -54,6 +54,8 @@ self.onmessage = async (event) => {
|
|
|
54
54
|
shouldStop: sim.shouldStop,
|
|
55
55
|
initialParams: msg.params,
|
|
56
56
|
maxTime: msg.config.maxTime,
|
|
57
|
+
// Worker host owns its own setTimeout-driven loop; rAF wouldn't exist here anyway.
|
|
58
|
+
autoFrame: false,
|
|
57
59
|
});
|
|
58
60
|
postMessage({ kind: 'ready' });
|
|
59
61
|
emitSnapshot();
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import '../sim.cjs';
|
|
1
|
+
import { State } from '../state.cjs';
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Wire protocol types for main ↔ worker communication.
|
|
@@ -38,7 +37,7 @@ type MainToWorkerMessage<Params> = {
|
|
|
38
37
|
/** Messages sent from the worker to the main thread. */
|
|
39
38
|
type WorkerToMainMessage<Data, Params> = {
|
|
40
39
|
kind: 'snapshot';
|
|
41
|
-
snapshot:
|
|
40
|
+
snapshot: State<Data, Params>;
|
|
42
41
|
} | {
|
|
43
42
|
kind: 'error';
|
|
44
43
|
error: {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import '../sim.js';
|
|
1
|
+
import { State } from '../state.js';
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Wire protocol types for main ↔ worker communication.
|
|
@@ -38,7 +37,7 @@ type MainToWorkerMessage<Params> = {
|
|
|
38
37
|
/** Messages sent from the worker to the main thread. */
|
|
39
38
|
type WorkerToMainMessage<Data, Params> = {
|
|
40
39
|
kind: 'snapshot';
|
|
41
|
-
snapshot:
|
|
40
|
+
snapshot: State<Data, Params>;
|
|
42
41
|
} | {
|
|
43
42
|
kind: 'error';
|
|
44
43
|
error: {
|
|
@@ -42,7 +42,6 @@ function createWorkerRunner(worker, config) {
|
|
|
42
42
|
status: "idle",
|
|
43
43
|
stepDurationMs: 0
|
|
44
44
|
};
|
|
45
|
-
let errorMessage = null;
|
|
46
45
|
function send(msg) {
|
|
47
46
|
worker.postMessage(serializeMainMessage(msg));
|
|
48
47
|
}
|
|
@@ -59,7 +58,6 @@ function createWorkerRunner(worker, config) {
|
|
|
59
58
|
emit();
|
|
60
59
|
break;
|
|
61
60
|
case "error":
|
|
62
|
-
errorMessage = msg.error.message;
|
|
63
61
|
currentSnapshot = { ...currentSnapshot, status: "stopped" };
|
|
64
62
|
emit();
|
|
65
63
|
break;
|
|
@@ -67,8 +65,7 @@ function createWorkerRunner(worker, config) {
|
|
|
67
65
|
break;
|
|
68
66
|
}
|
|
69
67
|
};
|
|
70
|
-
worker.onerror = (
|
|
71
|
-
errorMessage = event.message ?? "Unknown worker error";
|
|
68
|
+
worker.onerror = () => {
|
|
72
69
|
currentSnapshot = { ...currentSnapshot, status: "stopped" };
|
|
73
70
|
emit();
|
|
74
71
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { State } from '../state.cjs';
|
|
2
2
|
import { WorkerConfig } from './protocol.cjs';
|
|
3
|
-
import '../sim.cjs';
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Main-thread side of the worker runner.
|
|
@@ -14,8 +13,8 @@ type WorkerRunnerConfig<Params> = {
|
|
|
14
13
|
config: WorkerConfig;
|
|
15
14
|
};
|
|
16
15
|
type WorkerRunner<Data, Params> = {
|
|
17
|
-
getSnapshot: () =>
|
|
18
|
-
subscribe: (listener: (snapshot:
|
|
16
|
+
getSnapshot: () => State<Data, Params>;
|
|
17
|
+
subscribe: (listener: (snapshot: State<Data, Params>) => void) => () => void;
|
|
19
18
|
play: () => void;
|
|
20
19
|
pause: () => void;
|
|
21
20
|
stop: () => void;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { State } from '../state.js';
|
|
2
2
|
import { WorkerConfig } from './protocol.js';
|
|
3
|
-
import '../sim.js';
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Main-thread side of the worker runner.
|
|
@@ -14,8 +13,8 @@ type WorkerRunnerConfig<Params> = {
|
|
|
14
13
|
config: WorkerConfig;
|
|
15
14
|
};
|
|
16
15
|
type WorkerRunner<Data, Params> = {
|
|
17
|
-
getSnapshot: () =>
|
|
18
|
-
subscribe: (listener: (snapshot:
|
|
16
|
+
getSnapshot: () => State<Data, Params>;
|
|
17
|
+
subscribe: (listener: (snapshot: State<Data, Params>) => void) => () => void;
|
|
19
18
|
play: () => void;
|
|
20
19
|
pause: () => void;
|
|
21
20
|
stop: () => void;
|
|
@@ -16,7 +16,6 @@ function createWorkerRunner(worker, config) {
|
|
|
16
16
|
status: "idle",
|
|
17
17
|
stepDurationMs: 0
|
|
18
18
|
};
|
|
19
|
-
let errorMessage = null;
|
|
20
19
|
function send(msg) {
|
|
21
20
|
worker.postMessage(serializeMainMessage(msg));
|
|
22
21
|
}
|
|
@@ -33,7 +32,6 @@ function createWorkerRunner(worker, config) {
|
|
|
33
32
|
emit();
|
|
34
33
|
break;
|
|
35
34
|
case "error":
|
|
36
|
-
errorMessage = msg.error.message;
|
|
37
35
|
currentSnapshot = { ...currentSnapshot, status: "stopped" };
|
|
38
36
|
emit();
|
|
39
37
|
break;
|
|
@@ -41,8 +39,7 @@ function createWorkerRunner(worker, config) {
|
|
|
41
39
|
break;
|
|
42
40
|
}
|
|
43
41
|
};
|
|
44
|
-
worker.onerror = (
|
|
45
|
-
errorMessage = event.message ?? "Unknown worker error";
|
|
42
|
+
worker.onerror = () => {
|
|
46
43
|
currentSnapshot = { ...currentSnapshot, status: "stopped" };
|
|
47
44
|
emit();
|
|
48
45
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "automatick",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
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/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",
|
|
33
33
|
"test": "vitest run"
|
|
34
34
|
},
|
|
35
35
|
"exports": {
|