@mica-js/simulator 0.0.0-stage → 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DigitalEcho
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,88 @@
1
- # Temporary Holding Version
1
+ # @mica-js/simulator
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 在一条虚拟时钟上运行原理图里的全部器件实例:`loadSchematic` 建实例,`advance` 推进时间,`onPublish` / `deliver` 与画布互投载荷。
4
+
5
+ 依赖方向:`@mica-js/simulator` → `@mica-js/device` → `@mica-js/ir` → `@mica-js/core`。
6
+
7
+ ## 用法
8
+
9
+ 推荐直接挂到 `WidgetHost` 上:宿主按 `bindings` 完成双向路由,应用侧不再手写转发。
10
+
11
+ ```ts
12
+ import { createPowerRuntime, timePs } from '@mica-js/core';
13
+ import { parseSchematic } from '@mica-js/core/schematic';
14
+ import { createDeviceSimExecutor } from '@mica-js/simulator';
15
+ import { createWidgetHost } from '@mica-js/widget';
16
+
17
+ const executor = createDeviceSimExecutor({
18
+ devices: provider, // IDeviceProvider:器件描述 + 代码,原理图里只写 device id
19
+ onDiagnostic: (diagnostic) => console.warn(diagnostic.code, diagnostic.message),
20
+ // 引脚与连接需要宿主侧接线;工厂每次建运行时调用一次,拿到运行时自己的调度器。
21
+ host: ({ scheduler, schematic }) => {
22
+ const power = createPowerRuntime(scheduler, board, { instanceId: 'power', pinStates });
23
+ return {
24
+ pinHost: power,
25
+ topology: power,
26
+ instanceOptions: (symbol) =>
27
+ symbol.device === 'mica:teaching-mcu' ? { updateResources: ['firmware'] } : undefined,
28
+ dispose: () => power.destroy(),
29
+ };
30
+ },
31
+ });
32
+
33
+ // 器件发布 → 绑定的 widget;widget 发布 → 绑定的器件端点。
34
+ const host = createWidgetHost({ provider, assets, simulation: executor });
35
+
36
+ await executor.loadSchematic(parseSchematic(schText)); // 文本由调用方解析
37
+ const report = executor.advance(timePs(100_000)); // 100 ns,同步完成
38
+ ```
39
+
40
+ `DeviceSimExecutor` 结构上满足 `@mica-js/widget` 的 `WidgetHostSimulation`,不需要适配层;宿主之外也可以自己订阅 `onPublish` / 调用 `deliver`。
41
+
42
+ ## 宿主接线(`host`)
43
+
44
+ 执行器不拥有电路:它只把宿主建的 `pinHost` 与 `topology` 透传给每个实例。`@mica-js/core` 的 `createPowerRuntime` 同时满足 `DevicePinHost` 与 `TopologyRuntime`,所以一块板只需要一个运行时。
45
+
46
+ | 字段 | 说明 |
47
+ | ----------------- | ------------------------------------------------------------------------------------ |
48
+ | `pinHost` | 逐实例的 `pinHost`;`b.pin.*` / `b.observeDigital` 依赖它 |
49
+ | `topology` | 逐实例的 `connections.topology`;`b.connection.apply` 依赖它 |
50
+ | `instanceOptions` | 逐符号补充实例选项(目前是 `updateResources` / `messaging`);身份、属性与接线不开放 |
51
+ | `dispose` | 实例全部销毁后调用,用来释放电源/拓扑运行时 |
52
+
53
+ 工厂在每次 `loadSchematic` 和 `reset()` 建运行时各调用一次;每次运行时都有**自己的新调度器**,所以工厂里的实例 id 不会跨运行时冲突。工厂或 `dispose` 抛错时:构建阶段直接让 `loadSchematic` / `reset` 抛错(旧运行时原样保留),释放阶段转成 `simulator/dispose-failed` 诊断。
54
+
55
+ 不配置 `host` 时实例没有 `pinHost` 也没有拓扑,用到引脚或连接的器件会在加载时报器件层错误(例如 `device/connection-unavailable`),并被包成 `simulator/instance-create-failed`。
56
+
57
+ ## 接口
58
+
59
+ | 成员 | 说明 |
60
+ | -------------------------- | ------------------------------------------------------------------- |
61
+ | `getState()` | `'idle'`、`'ready'`、`'faulted'`、`'disposed'`;只有 `ready` 能推进 |
62
+ | `getSimTime()` | 当前虚拟时间;首次加载前为 `0` |
63
+ | `getSchematic()` | 当前运行时的来源文档;首次加载前是空图 |
64
+ | `getTopics()` | 每个实例的端口,`symbol` + `port` 与画布绑定的端点一一对应 |
65
+ | `loadSchematic(document)` | 按符号建实例并跑 `initialize`,时钟归零;构建失败时旧运行时原样保留 |
66
+ | `advance(delta)` | 同步推进 `delta` 皮秒,返回 `{ from, to, executed, reason }` |
67
+ | `onPublish(listener)` | 器件发布 `(target, payload, simTime)`;返回取消订阅函数 |
68
+ | `deliver(target, payload)` | 排入下一次 `advance` 的开头,同一时刻多次调用按调用顺序被器件看到 |
69
+ | `reset()` | 丢弃实例、按当前原理图重建,时钟归零;同步 |
70
+ | `dispose()` | 销毁全部实例并释放运行时;同步、幂等 |
71
+
72
+ `DeviceTopic` 的 `direction` / `type` 与 `DevicePortDescriptor` 同构,宿主可以直接用 `getTopics()` 生成画布的绑定候选。
73
+
74
+ ## 语义
75
+
76
+ - **除 `loadSchematic` 之外全是同步的。** 加载之所以返回 promise,只因为器件模块要经 provider 取;模块随后被缓存,`reset()` 直接用缓存重建实例,不会再让出线程。仿真与 `WidgetHost` 跑在同一个 JS 线程上,没有 worker,也没有消息通道。
77
+ - **先建后换。** `loadSchematic` 在新调度器上建好实例、跑完 `initialize` 后才替换旧运行时,因此器件解析失败或工厂抛错时,正在跑的仿真不受影响,时钟也不回退。宿主接线工厂也在这个阶段构建:它抛错同样保留旧运行时。
78
+ - **`advance` 是同步的。** 整个时间片在调用内跑完,宿主可以在帧循环里直接调用并立刻拿到报告;推进失败时 `getState()` 变成 `'faulted'` 并原样抛出底层 `SchedulerError`。
79
+ - **`deliver` 收 `unknown`,延迟到时间片开头。** 宿主转发的是 widget 发布的原始载荷,端口类型由器件层校验;同一时刻多次投递按调用顺序被器件看到,UI 线程不会把载荷插进两次 `advance` 之间。载荷被器件层拒绝时 `advance` 抛出,但不会把执行器置为 `'faulted'`(故障只由调度器决定),宿主应当把这次输入记为被拒绝。
80
+ - **`simTime` 由执行器负责。** 器件发布时的时间戳取自调度器,画布侧不需要传时间。
81
+ - **诊断不中断仿真。** `onDiagnostic` 抛错会被吞掉;只有 `advance` 的调度器故障才会把状态置为 `'faulted'`。
82
+
83
+ 诊断码以 `simulator/` 开头:`device-load-failed`、`instance-create-failed`、`advance-failed`、`publish-failed`、`dispose-failed`。宿主调用的参数错误抛 `SimulatorError`(`code` 见 `SimulatorErrorCode`)。
84
+
85
+ ## 边界
86
+
87
+ - **电路定义归宿主所有。** 执行器只透传 `pinHost` / `topology`,不解析板级电源、上拉或网络拓扑;引脚与电气观测(`sampleDigital`、`connectionState`)由宿主读自己的运行时。
88
+ - 器件代码在宿主 realm 里求值(与 `loadDeviceModule` 相同,等同可信扩展),沙箱化未做。
@@ -0,0 +1,209 @@
1
+ import { DeviceInstanceOptions } from '@mica-js/device';
2
+ import { DevicePinHost } from '@mica-js/device';
3
+ import { IDeviceProvider } from '@mica-js/device';
4
+ import { Scheduler } from '@mica-js/core';
5
+ import { SchedulerOptions } from '@mica-js/core';
6
+ import { SchematicDocument } from '@mica-js/core/schematic';
7
+ import { SchematicSymbol } from '@mica-js/core/schematic';
8
+ import { TimePs } from '@mica-js/core';
9
+ import { TopicPayload } from '@mica-js/device';
10
+ import { TopicPayloadType } from '@mica-js/device';
11
+ import { TopologyRuntime } from '@mica-js/core';
12
+
13
+ export declare type AdvanceReason = 'until' | 'empty' | 'entry-limit' | 'paused';
14
+
15
+ /** Result of one {@link DeviceSimExecutor.advance} call. */
16
+ export declare interface AdvanceReport {
17
+ readonly from: TimePs;
18
+ /** Virtual time after the call; equals {@link DeviceSimExecutor.getSimTime}. */
19
+ readonly to: TimePs;
20
+ /** Entries the scheduler executed during this call. */
21
+ readonly executed: number;
22
+ readonly reason: AdvanceReason;
23
+ }
24
+
25
+ /**
26
+ * Creates an executor for one schematic at a time.
27
+ *
28
+ * Instances are provisioned by `@mica-js/device` on a private scheduler; pins, nets and the
29
+ * electrical network are not wired yet, so a device whose IR touches pins or connections fails to
30
+ * load with the device layer's own error.
31
+ */
32
+ export declare function createDeviceSimExecutor(options: DeviceSimExecutorOptions): DeviceSimExecutor;
33
+
34
+ /** One payload a device published while the simulation was running. */
35
+ export declare interface DevicePublish {
36
+ readonly target: DeviceTopicTarget;
37
+ readonly payload: TopicPayload;
38
+ /** Virtual time captured when the device published. */
39
+ readonly simTime: TimePs;
40
+ }
41
+
42
+ /**
43
+ * Runs the devices of one schematic on a single virtual clock.
44
+ *
45
+ * The executor owns the scheduler, the device instances and the topic routing; the host owns the
46
+ * clock: it calls {@link advance} with the slice of time it wants simulated and dispatches widget
47
+ * input through {@link deliver}. The executor itself never leaves the caller's thread — there is
48
+ * no worker and no message channel — so a host can wire it to `WidgetHost` and read a publish
49
+ * while the stack that produced it is still running.
50
+ *
51
+ * Every method except `loadSchematic` is synchronous: once a schematic is loaded, a host can
52
+ * advance, deliver, reset and dispose from plain event handlers.
53
+ */
54
+ export declare interface DeviceSimExecutor {
55
+ getState(): SimulationState;
56
+ /** Current virtual time; `0` before the first `loadSchematic`. */
57
+ getSimTime(): TimePs;
58
+ /** Schematic the current runtime was built from; the empty sheet before the first load. */
59
+ getSchematic(): SchematicDocument;
60
+ /** Endpoints of the running instances, in schematic order; empty while `idle`. */
61
+ getTopics(): readonly DeviceTopic[];
62
+ /**
63
+ * Builds a runtime for `document` and mounts one device instance per symbol.
64
+ *
65
+ * Asynchronous only because device modules are fetched through the provider: everything after
66
+ * that runs synchronously. The new runtime is built before the old one is dropped, so a rejected
67
+ * load leaves the previous simulation untouched. Resolves once every `initialize` entry has run,
68
+ * with the clock back at 0.
69
+ *
70
+ * @throws {SimulatorError} when a device cannot be loaded or instantiated.
71
+ */
72
+ loadSchematic(document: SchematicDocument): Promise<void>;
73
+ /**
74
+ * Advances the virtual clock by `delta` picoseconds and runs everything scheduled up to it.
75
+ *
76
+ * The whole slice runs inside the call, so the host can drive it from a frame loop and read
77
+ * {@link AdvanceReport} right away. Payloads queued by {@link deliver} are consumed at the start
78
+ * of the slice.
79
+ *
80
+ * @throws {SimulatorError} when the executor is idle, disposed or `delta` is not positive.
81
+ * @throws when a device faults; `getState()` is then `'faulted'`.
82
+ */
83
+ advance(delta: TimePs): AdvanceReport;
84
+ /** Subscribes to device publishes; returns the unsubscribe function. */
85
+ onPublish(listener: (publish: DevicePublish) => void): () => void;
86
+ /**
87
+ * Queues one payload for a device input port.
88
+ *
89
+ * The payload is `unknown` on purpose: the host forwards what a widget published, and the device
90
+ * layer rejects a payload that does not match the port type.
91
+ *
92
+ * Delivery is deferred to the next {@link advance}, so a host can post several events for the
93
+ * same instant and have the devices observe them in call order.
94
+ *
95
+ * @throws {SimulatorError} when the symbol or port is unknown, or the port is an output.
96
+ */
97
+ deliver(target: DeviceTopicTarget, payload: unknown): void;
98
+ /**
99
+ * Drops the running instances and rebuilds them from the current schematic at time 0.
100
+ *
101
+ * Synchronous: the devices were already loaded by `loadSchematic`, so this is a fresh set of
102
+ * instances, not a reload.
103
+ */
104
+ reset(): void;
105
+ /** Destroys every instance and releases the runtime. Idempotent. */
106
+ dispose(): void;
107
+ }
108
+
109
+ export declare interface DeviceSimExecutorOptions {
110
+ /** Device descriptions and code; the schematic only names device ids. */
111
+ readonly devices: IDeviceProvider;
112
+ readonly scheduler?: SchedulerOptions;
113
+ readonly onDiagnostic?: (diagnostic: SimulationDiagnostic) => void;
114
+ /**
115
+ * Host-owned pin/connection wiring, built once per runtime with that runtime's scheduler.
116
+ *
117
+ * Without it the instances get neither `pinHost` nor a topology, so a device whose IR touches
118
+ * pins or connections fails to load with the device layer's own error. A factory that throws
119
+ * rejects `loadSchematic` and `reset`; the running runtime is then left untouched.
120
+ */
121
+ readonly host?: DeviceSimHostFactory;
122
+ }
123
+
124
+ /**
125
+ * Host-owned wiring handed to every instance of one runtime.
126
+ *
127
+ * The executor builds it through {@link DeviceSimHostFactory} on every `loadSchematic` and
128
+ * `reset()`, so the pin host and the topology always belong to the scheduler that is running.
129
+ */
130
+ export declare interface DeviceSimHost {
131
+ /** Electrical/pin host passed to every instance as `pinHost`. */
132
+ readonly pinHost?: DevicePinHost;
133
+ /** Topology passed to every instance as `connections.topology`. */
134
+ readonly topology?: TopologyRuntime;
135
+ /** Adds per-symbol options such as `updateResources`; omitted fields keep the defaults. */
136
+ instanceOptions?(symbol: SchematicSymbol): DeviceSimInstanceOptions | undefined;
137
+ /** Releases what the factory created; runs once every instance of the runtime is destroyed. */
138
+ dispose?(): void;
139
+ }
140
+
141
+ export declare interface DeviceSimHostContext {
142
+ /** Scheduler of the runtime being built; a new one for every load and every reset. */
143
+ readonly scheduler: Scheduler;
144
+ readonly schematic: SchematicDocument;
145
+ }
146
+
147
+ /** Builds the wiring for one runtime. */
148
+ export declare type DeviceSimHostFactory = (context: DeviceSimHostContext) => DeviceSimHost;
149
+
150
+ /**
151
+ * Extra instance options a host may add per symbol.
152
+ *
153
+ * Identity, the symbol's properties and the pin/connection hosts stay with the executor, so a
154
+ * host cannot rebind one instance to another runtime's wiring.
155
+ */
156
+ export declare type DeviceSimInstanceOptions = Omit<DeviceInstanceOptions, 'instanceId' | 'onTopic' | 'pinHost' | 'connections' | 'properties'>;
157
+
158
+ /**
159
+ * One logical endpoint of the running schematic.
160
+ *
161
+ * `symbol` + `port` is the same pair a canvas binding refers to, and `direction` / `type` match
162
+ * `DevicePortDescriptor`, so a host can build binding candidates without touching the devices.
163
+ */
164
+ export declare interface DeviceTopic {
165
+ /** Symbol id inside the schematic (`SchematicSymbol.id`). */
166
+ readonly symbol: string;
167
+ /** Port key declared by the device description. */
168
+ readonly port: string;
169
+ readonly direction: 'input' | 'output';
170
+ readonly type: TopicPayloadType;
171
+ readonly title?: string;
172
+ }
173
+
174
+ export declare interface DeviceTopicTarget {
175
+ readonly symbol: string;
176
+ readonly port: string;
177
+ }
178
+
179
+ /** A problem the executor survives; the simulation keeps running unless stated otherwise. */
180
+ export declare interface SimulationDiagnostic {
181
+ readonly code: SimulationDiagnosticCode;
182
+ readonly message: string;
183
+ /** Symbol the problem belongs to, when it is attributable to one. */
184
+ readonly symbol?: string;
185
+ readonly cause?: unknown;
186
+ }
187
+
188
+ export declare type SimulationDiagnosticCode = 'simulator/device-load-failed' | 'simulator/instance-create-failed' | 'simulator/advance-failed' | 'simulator/publish-failed' | 'simulator/dispose-failed';
189
+
190
+ /** Lifecycle of one executor. `ready` is the only state that can advance. */
191
+ export declare type SimulationState = 'idle' | 'ready' | 'faulted' | 'disposed';
192
+
193
+ /** Error raised by the executor's host-facing calls. */
194
+ export declare class SimulatorError extends Error {
195
+ readonly name = "SimulatorError";
196
+ readonly code: SimulatorErrorCode;
197
+ readonly symbol: string | undefined;
198
+ constructor(code: SimulatorErrorCode, message: string, options?: SimulatorErrorOptions);
199
+ }
200
+
201
+ export declare type SimulatorErrorCode = 'simulator/disposed' | 'simulator/not-ready' | 'simulator/invalid-delta' | 'simulator/unknown-target' | 'simulator/unknown-port' | 'simulator/device-load-failed' | 'simulator/instance-create-failed';
202
+
203
+ export declare interface SimulatorErrorOptions {
204
+ /** Symbol the failure belongs to, when it is attributable to one. */
205
+ readonly symbol?: string;
206
+ readonly cause?: unknown;
207
+ }
208
+
209
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,282 @@
1
+ import { createScheduler, timePs } from "@mica-js/core";
2
+ import { createDeviceInstance, loadDeviceModule } from "@mica-js/device";
3
+ //#region src/errors.ts
4
+ /** Error raised by the executor's host-facing calls. */
5
+ var SimulatorError = class extends Error {
6
+ name = "SimulatorError";
7
+ code;
8
+ symbol;
9
+ constructor(code, message, options = {}) {
10
+ super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
11
+ this.code = code;
12
+ this.symbol = options.symbol;
13
+ }
14
+ };
15
+ //#endregion
16
+ //#region src/create-device-sim-executor.ts
17
+ var EMPTY_SCHEMATIC = Object.freeze({
18
+ version: 1,
19
+ sheet: Object.freeze({
20
+ symbols: Object.freeze([]),
21
+ wires: Object.freeze([]),
22
+ junctions: Object.freeze([])
23
+ })
24
+ });
25
+ var EMPTY_TOPICS = Object.freeze([]);
26
+ /**
27
+ * Creates an executor for one schematic at a time.
28
+ *
29
+ * Instances are provisioned by `@mica-js/device` on a private scheduler; pins, nets and the
30
+ * electrical network are not wired yet, so a device whose IR touches pins or connections fails to
31
+ * load with the device layer's own error.
32
+ */
33
+ function createDeviceSimExecutor(options) {
34
+ return new DeviceSimExecutorImpl(options);
35
+ }
36
+ var DeviceSimExecutorImpl = class {
37
+ #options;
38
+ #publishListeners = /* @__PURE__ */ new Set();
39
+ #pending = [];
40
+ /** Loaded device modules, so `reset` can rebuild instances without touching the provider. */
41
+ #modules = /* @__PURE__ */ new Map();
42
+ #runtime;
43
+ #schematic = EMPTY_SCHEMATIC;
44
+ #state = "idle";
45
+ constructor(options) {
46
+ this.#options = options;
47
+ }
48
+ getState() {
49
+ return this.#state;
50
+ }
51
+ getSimTime() {
52
+ return this.#runtime?.scheduler.now ?? timePs(0);
53
+ }
54
+ getSchematic() {
55
+ return this.#schematic;
56
+ }
57
+ getTopics() {
58
+ return this.#runtime?.topics ?? EMPTY_TOPICS;
59
+ }
60
+ async loadSchematic(document) {
61
+ this.#assertNotDisposed();
62
+ await this.#loadModules(document);
63
+ const runtime = this.#buildRuntime(document);
64
+ this.#teardown();
65
+ this.#runtime = runtime;
66
+ this.#schematic = document;
67
+ this.#state = "ready";
68
+ }
69
+ advance(delta) {
70
+ const runtime = this.#requireRuntime();
71
+ if (!Number.isSafeInteger(delta) || delta <= 0) throw new SimulatorError("simulator/invalid-delta", `advance expects a positive whole number of picoseconds, received ${String(delta)}.`);
72
+ const { scheduler } = runtime;
73
+ const from = scheduler.now;
74
+ this.#flush(runtime);
75
+ try {
76
+ const result = scheduler.start({ until: from + delta });
77
+ return Object.freeze({
78
+ from,
79
+ to: result.timePs,
80
+ executed: result.executed,
81
+ reason: result.reason
82
+ });
83
+ } catch (cause) {
84
+ this.#state = "faulted";
85
+ this.#report({
86
+ code: "simulator/advance-failed",
87
+ message: `Advancing ${delta} ps failed.`,
88
+ cause
89
+ });
90
+ throw cause;
91
+ }
92
+ }
93
+ onPublish(listener) {
94
+ if (this.#state === "disposed") return () => {};
95
+ this.#publishListeners.add(listener);
96
+ return () => {
97
+ this.#publishListeners.delete(listener);
98
+ };
99
+ }
100
+ deliver(target, payload) {
101
+ const instance = this.#requireRuntime().instances.get(target.symbol);
102
+ if (instance === void 0) throw new SimulatorError("simulator/unknown-target", `The schematic has no symbol "${target.symbol}".`, { symbol: target.symbol });
103
+ const port = instance.description.ports.get(target.port);
104
+ if (port === void 0) throw new SimulatorError("simulator/unknown-port", `Device ${JSON.stringify(instance.deviceId)} has no port "${target.port}".`, { symbol: target.symbol });
105
+ if (port.direction !== "input") throw new SimulatorError("simulator/unknown-port", `Port "${target.port}" of symbol "${target.symbol}" is an output, not an input.`, { symbol: target.symbol });
106
+ this.#pending.push(Object.freeze({
107
+ target: Object.freeze({ ...target }),
108
+ payload
109
+ }));
110
+ }
111
+ reset() {
112
+ this.#assertNotDisposed();
113
+ if (this.#runtime === void 0) {
114
+ this.#pending.length = 0;
115
+ return;
116
+ }
117
+ const runtime = this.#buildRuntime(this.#schematic);
118
+ this.#teardown();
119
+ this.#runtime = runtime;
120
+ this.#state = "ready";
121
+ }
122
+ dispose() {
123
+ if (this.#state === "disposed") return;
124
+ this.#state = "disposed";
125
+ this.#publishListeners.clear();
126
+ this.#teardown();
127
+ this.#modules.clear();
128
+ }
129
+ /** Loads the devices the schematic names; already loaded ids are reused. */
130
+ async #loadModules(document) {
131
+ for (const symbol of document.sheet.symbols) {
132
+ if (this.#modules.has(symbol.device)) continue;
133
+ try {
134
+ this.#modules.set(symbol.device, await loadDeviceModule(symbol.device, this.#options.devices));
135
+ } catch (cause) {
136
+ throw new SimulatorError("simulator/device-load-failed", `Device ${JSON.stringify(symbol.device)} of symbol "${symbol.id}" could not be loaded.`, {
137
+ symbol: symbol.id,
138
+ cause
139
+ });
140
+ }
141
+ }
142
+ }
143
+ /** Mounts every symbol of the schematic and runs the `initialize` pass on a fresh scheduler. */
144
+ #buildRuntime(document) {
145
+ const scheduler = this.#options.scheduler === void 0 ? createScheduler() : createScheduler(this.#options.scheduler);
146
+ const host = this.#options.host?.({
147
+ scheduler,
148
+ schematic: document
149
+ });
150
+ const instances = /* @__PURE__ */ new Map();
151
+ const topics = [];
152
+ try {
153
+ for (const symbol of document.sheet.symbols) {
154
+ const instance = this.#createInstance(symbol, scheduler, host);
155
+ instances.set(symbol.id, instance);
156
+ collectTopics(symbol, instance, topics);
157
+ }
158
+ scheduler.start({ until: scheduler.now });
159
+ return {
160
+ scheduler,
161
+ instances,
162
+ topics: Object.freeze(topics),
163
+ host
164
+ };
165
+ } catch (error) {
166
+ for (const instance of instances.values()) destroyQuietly(instance, (diagnostic) => this.#report(diagnostic));
167
+ disposeHostQuietly(host, (diagnostic) => this.#report(diagnostic));
168
+ throw error;
169
+ }
170
+ }
171
+ #createInstance(symbol, scheduler, host) {
172
+ const loaded = this.#modules.get(symbol.device);
173
+ if (loaded === void 0) throw new SimulatorError("simulator/device-load-failed", `Device ${JSON.stringify(symbol.device)} of symbol "${symbol.id}" was not loaded.`, { symbol: symbol.id });
174
+ const additions = host?.instanceOptions?.(symbol);
175
+ try {
176
+ return createDeviceInstance(loaded, scheduler, {
177
+ ...additions,
178
+ instanceId: symbol.id,
179
+ ...symbol.properties === void 0 ? {} : { properties: symbol.properties },
180
+ ...host?.pinHost === void 0 ? {} : { pinHost: host.pinHost },
181
+ ...host?.topology === void 0 ? {} : { connections: { topology: host.topology } },
182
+ onTopic: (message) => {
183
+ this.#publish(scheduler, symbol.id, message.port, message.payload);
184
+ }
185
+ });
186
+ } catch (cause) {
187
+ throw new SimulatorError("simulator/instance-create-failed", `Symbol "${symbol.id}" could not be instantiated with device ${JSON.stringify(symbol.device)}.`, {
188
+ symbol: symbol.id,
189
+ cause
190
+ });
191
+ }
192
+ }
193
+ #publish(scheduler, symbol, port, payload) {
194
+ const publish = Object.freeze({
195
+ target: Object.freeze({
196
+ symbol,
197
+ port
198
+ }),
199
+ payload,
200
+ simTime: scheduler.now
201
+ });
202
+ for (const listener of [...this.#publishListeners]) try {
203
+ listener(publish);
204
+ } catch (cause) {
205
+ this.#report({
206
+ code: "simulator/publish-failed",
207
+ message: "A publish listener threw.",
208
+ symbol,
209
+ cause
210
+ });
211
+ }
212
+ }
213
+ /** Hands the payloads queued by `deliver` to their instances at the slice's start time. */
214
+ #flush(runtime) {
215
+ if (this.#pending.length === 0) return;
216
+ const deliveries = this.#pending.splice(0);
217
+ const simTime = runtime.scheduler.now;
218
+ for (const delivery of deliveries) {
219
+ const instance = runtime.instances.get(delivery.target.symbol);
220
+ if (instance === void 0) continue;
221
+ instance.deliver(delivery.target.port, delivery.payload, { simTime });
222
+ }
223
+ }
224
+ #teardown() {
225
+ const runtime = this.#runtime;
226
+ this.#runtime = void 0;
227
+ this.#pending.length = 0;
228
+ if (runtime === void 0) return;
229
+ for (const instance of runtime.instances.values()) destroyQuietly(instance, (diagnostic) => this.#report(diagnostic));
230
+ disposeHostQuietly(runtime.host, (diagnostic) => this.#report(diagnostic));
231
+ }
232
+ #requireRuntime() {
233
+ this.#assertNotDisposed();
234
+ const runtime = this.#runtime;
235
+ if (runtime === void 0) throw new SimulatorError("simulator/not-ready", "Load a schematic before advancing the simulation.");
236
+ return runtime;
237
+ }
238
+ #assertNotDisposed() {
239
+ if (this.#state === "disposed") throw new SimulatorError("simulator/disposed", "The executor is disposed.");
240
+ }
241
+ #report(diagnostic) {
242
+ try {
243
+ this.#options.onDiagnostic?.(diagnostic);
244
+ } catch {}
245
+ }
246
+ };
247
+ function collectTopics(symbol, instance, into) {
248
+ for (const [port, descriptor] of instance.description.ports) into.push(Object.freeze({
249
+ symbol: symbol.id,
250
+ port,
251
+ direction: descriptor.direction,
252
+ type: descriptor.type,
253
+ ...descriptor.title === void 0 ? {} : { title: descriptor.title }
254
+ }));
255
+ }
256
+ function destroyQuietly(instance, report) {
257
+ try {
258
+ instance.destroy();
259
+ } catch (cause) {
260
+ report({
261
+ code: "simulator/dispose-failed",
262
+ message: `Device instance "${instance.instanceId}" failed to destroy.`,
263
+ symbol: instance.instanceId,
264
+ cause
265
+ });
266
+ }
267
+ }
268
+ function disposeHostQuietly(host, report) {
269
+ try {
270
+ host?.dispose?.();
271
+ } catch (cause) {
272
+ report({
273
+ code: "simulator/dispose-failed",
274
+ message: "The host wiring failed to dispose.",
275
+ cause
276
+ });
277
+ }
278
+ }
279
+ //#endregion
280
+ export { SimulatorError, createDeviceSimExecutor };
281
+
282
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/errors.ts","../src/create-device-sim-executor.ts"],"sourcesContent":["export type SimulatorErrorCode =\n | 'simulator/disposed'\n | 'simulator/not-ready'\n | 'simulator/invalid-delta'\n | 'simulator/unknown-target'\n | 'simulator/unknown-port'\n | 'simulator/device-load-failed'\n | 'simulator/instance-create-failed';\n\nexport interface SimulatorErrorOptions {\n /** Symbol the failure belongs to, when it is attributable to one. */\n readonly symbol?: string;\n readonly cause?: unknown;\n}\n\n/** Error raised by the executor's host-facing calls. */\nexport class SimulatorError extends Error {\n override readonly name = 'SimulatorError';\n readonly code: SimulatorErrorCode;\n readonly symbol: string | undefined;\n\n constructor(code: SimulatorErrorCode, message: string, options: SimulatorErrorOptions = {}) {\n super(message, options.cause === undefined ? undefined : { cause: options.cause });\n this.code = code;\n this.symbol = options.symbol;\n }\n}\n","import { createScheduler, timePs, type Scheduler, type TimePs } from '@mica-js/core';\nimport type { SchematicDocument, SchematicSymbol } from '@mica-js/core/schematic';\nimport {\n createDeviceInstance,\n loadDeviceModule,\n type DeviceInstance,\n type LoadedDeviceModule,\n type TopicPayload,\n} from '@mica-js/device';\nimport { SimulatorError } from './errors';\nimport type {\n AdvanceReport,\n DeviceSimExecutor,\n DeviceSimExecutorOptions,\n DeviceSimHost,\n DevicePublish,\n DeviceTopic,\n DeviceTopicTarget,\n SimulationDiagnostic,\n SimulationState,\n} from './types';\n\nconst EMPTY_SCHEMATIC: SchematicDocument = Object.freeze({\n version: 1,\n sheet: Object.freeze({\n symbols: Object.freeze([]),\n wires: Object.freeze([]),\n junctions: Object.freeze([]),\n }),\n});\n\nconst EMPTY_TOPICS: readonly DeviceTopic[] = Object.freeze([]);\n\n/** One loaded schematic: its scheduler, the instances keyed by symbol id and the topic index. */\ninterface Runtime {\n readonly scheduler: Scheduler;\n readonly instances: ReadonlyMap<string, DeviceInstance>;\n readonly topics: readonly DeviceTopic[];\n /** Wiring the instances were mounted against; released with the runtime. */\n readonly host: DeviceSimHost | undefined;\n}\n\ninterface PendingDelivery {\n readonly target: DeviceTopicTarget;\n readonly payload: unknown;\n}\n\n/**\n * Creates an executor for one schematic at a time.\n *\n * Instances are provisioned by `@mica-js/device` on a private scheduler; pins, nets and the\n * electrical network are not wired yet, so a device whose IR touches pins or connections fails to\n * load with the device layer's own error.\n */\nexport function createDeviceSimExecutor(options: DeviceSimExecutorOptions): DeviceSimExecutor {\n return new DeviceSimExecutorImpl(options);\n}\n\nclass DeviceSimExecutorImpl implements DeviceSimExecutor {\n readonly #options: DeviceSimExecutorOptions;\n readonly #publishListeners = new Set<(publish: DevicePublish) => void>();\n readonly #pending: PendingDelivery[] = [];\n /** Loaded device modules, so `reset` can rebuild instances without touching the provider. */\n readonly #modules = new Map<string, LoadedDeviceModule>();\n #runtime: Runtime | undefined;\n #schematic: SchematicDocument = EMPTY_SCHEMATIC;\n #state: SimulationState = 'idle';\n\n constructor(options: DeviceSimExecutorOptions) {\n this.#options = options;\n }\n\n getState(): SimulationState {\n return this.#state;\n }\n\n getSimTime(): TimePs {\n return this.#runtime?.scheduler.now ?? timePs(0);\n }\n\n getSchematic(): SchematicDocument {\n return this.#schematic;\n }\n\n getTopics(): readonly DeviceTopic[] {\n return this.#runtime?.topics ?? EMPTY_TOPICS;\n }\n\n async loadSchematic(document: SchematicDocument): Promise<void> {\n this.#assertNotDisposed();\n await this.#loadModules(document);\n // Build first: a rejected load must leave the running simulation untouched.\n const runtime = this.#buildRuntime(document);\n this.#teardown();\n this.#runtime = runtime;\n this.#schematic = document;\n this.#state = 'ready';\n }\n\n advance(delta: TimePs): AdvanceReport {\n const runtime = this.#requireRuntime();\n if (!Number.isSafeInteger(delta) || delta <= 0) {\n throw new SimulatorError(\n 'simulator/invalid-delta',\n `advance expects a positive whole number of picoseconds, received ${String(delta)}.`,\n );\n }\n const { scheduler } = runtime;\n const from = scheduler.now;\n this.#flush(runtime);\n try {\n const result = scheduler.start({ until: from + delta });\n return Object.freeze({\n from,\n to: result.timePs,\n executed: result.executed,\n reason: result.reason,\n });\n } catch (cause) {\n this.#state = 'faulted';\n this.#report({\n code: 'simulator/advance-failed',\n message: `Advancing ${delta} ps failed.`,\n cause,\n });\n throw cause;\n }\n }\n\n onPublish(listener: (publish: DevicePublish) => void): () => void {\n if (this.#state === 'disposed') {\n return () => {};\n }\n this.#publishListeners.add(listener);\n return () => {\n this.#publishListeners.delete(listener);\n };\n }\n\n deliver(target: DeviceTopicTarget, payload: unknown): void {\n const runtime = this.#requireRuntime();\n const instance = runtime.instances.get(target.symbol);\n if (instance === undefined) {\n throw new SimulatorError(\n 'simulator/unknown-target',\n `The schematic has no symbol \"${target.symbol}\".`,\n { symbol: target.symbol },\n );\n }\n const port = instance.description.ports.get(target.port);\n if (port === undefined) {\n throw new SimulatorError(\n 'simulator/unknown-port',\n `Device ${JSON.stringify(instance.deviceId)} has no port \"${target.port}\".`,\n { symbol: target.symbol },\n );\n }\n if (port.direction !== 'input') {\n throw new SimulatorError(\n 'simulator/unknown-port',\n `Port \"${target.port}\" of symbol \"${target.symbol}\" is an output, not an input.`,\n { symbol: target.symbol },\n );\n }\n this.#pending.push(Object.freeze({ target: Object.freeze({ ...target }), payload }));\n }\n\n reset(): void {\n this.#assertNotDisposed();\n if (this.#runtime === undefined) {\n this.#pending.length = 0;\n return;\n }\n // The modules are cached, so a reset never yields: it swaps in freshly built instances.\n const runtime = this.#buildRuntime(this.#schematic);\n this.#teardown();\n this.#runtime = runtime;\n this.#state = 'ready';\n }\n\n dispose(): void {\n if (this.#state === 'disposed') {\n return;\n }\n this.#state = 'disposed';\n this.#publishListeners.clear();\n this.#teardown();\n this.#modules.clear();\n }\n\n /** Loads the devices the schematic names; already loaded ids are reused. */\n async #loadModules(document: SchematicDocument): Promise<void> {\n for (const symbol of document.sheet.symbols) {\n if (this.#modules.has(symbol.device)) {\n continue;\n }\n try {\n this.#modules.set(\n symbol.device,\n await loadDeviceModule(symbol.device, this.#options.devices),\n );\n } catch (cause) {\n throw new SimulatorError(\n 'simulator/device-load-failed',\n `Device ${JSON.stringify(symbol.device)} of symbol \"${symbol.id}\" could not be loaded.`,\n { symbol: symbol.id, cause },\n );\n }\n }\n }\n\n /** Mounts every symbol of the schematic and runs the `initialize` pass on a fresh scheduler. */\n #buildRuntime(document: SchematicDocument): Runtime {\n const scheduler =\n this.#options.scheduler === undefined\n ? createScheduler()\n : createScheduler(this.#options.scheduler);\n const host = this.#options.host?.({ scheduler, schematic: document });\n const instances = new Map<string, DeviceInstance>();\n const topics: DeviceTopic[] = [];\n try {\n for (const symbol of document.sheet.symbols) {\n const instance = this.#createInstance(symbol, scheduler, host);\n instances.set(symbol.id, instance);\n collectTopics(symbol, instance, topics);\n }\n // Device factories only queue `initialize`; run it so the runtime is ready to advance.\n scheduler.start({ until: scheduler.now });\n return { scheduler, instances, topics: Object.freeze(topics), host };\n } catch (error) {\n for (const instance of instances.values()) {\n destroyQuietly(instance, (diagnostic) => this.#report(diagnostic));\n }\n disposeHostQuietly(host, (diagnostic) => this.#report(diagnostic));\n throw error;\n }\n }\n\n #createInstance(\n symbol: SchematicSymbol,\n scheduler: Scheduler,\n host: DeviceSimHost | undefined,\n ): DeviceInstance {\n const loaded = this.#modules.get(symbol.device);\n if (loaded === undefined) {\n throw new SimulatorError(\n 'simulator/device-load-failed',\n `Device ${JSON.stringify(symbol.device)} of symbol \"${symbol.id}\" was not loaded.`,\n { symbol: symbol.id },\n );\n }\n const additions = host?.instanceOptions?.(symbol);\n try {\n return createDeviceInstance(loaded, scheduler, {\n ...additions,\n instanceId: symbol.id,\n ...(symbol.properties === undefined ? {} : { properties: symbol.properties }),\n ...(host?.pinHost === undefined ? {} : { pinHost: host.pinHost }),\n ...(host?.topology === undefined ? {} : { connections: { topology: host.topology } }),\n onTopic: (message) => {\n this.#publish(scheduler, symbol.id, message.port, message.payload);\n },\n });\n } catch (cause) {\n throw new SimulatorError(\n 'simulator/instance-create-failed',\n `Symbol \"${symbol.id}\" could not be instantiated with device ${JSON.stringify(symbol.device)}.`,\n { symbol: symbol.id, cause },\n );\n }\n }\n\n #publish(scheduler: Scheduler, symbol: string, port: string, payload: TopicPayload): void {\n const publish: DevicePublish = Object.freeze({\n target: Object.freeze({ symbol, port }),\n payload,\n simTime: scheduler.now,\n });\n for (const listener of [...this.#publishListeners]) {\n try {\n listener(publish);\n } catch (cause) {\n this.#report({\n code: 'simulator/publish-failed',\n message: 'A publish listener threw.',\n symbol,\n cause,\n });\n }\n }\n }\n\n /** Hands the payloads queued by `deliver` to their instances at the slice's start time. */\n #flush(runtime: Runtime): void {\n if (this.#pending.length === 0) {\n return;\n }\n const deliveries = this.#pending.splice(0);\n const simTime = runtime.scheduler.now;\n for (const delivery of deliveries) {\n const instance = runtime.instances.get(delivery.target.symbol);\n if (instance === undefined) {\n continue; // The target was validated when it was queued; a reload dropped the instance.\n }\n // `deliver` accepts `unknown` because hosts forward widget payloads; the device layer\n // captures and validates the value against the port type.\n instance.deliver(delivery.target.port, delivery.payload as TopicPayload, { simTime });\n }\n }\n\n #teardown(): void {\n const runtime = this.#runtime;\n this.#runtime = undefined;\n this.#pending.length = 0;\n if (runtime === undefined) {\n return;\n }\n for (const instance of runtime.instances.values()) {\n destroyQuietly(instance, (diagnostic) => this.#report(diagnostic));\n }\n // The pin host and the topology outlive the instances they serve, so they go last.\n disposeHostQuietly(runtime.host, (diagnostic) => this.#report(diagnostic));\n }\n\n #requireRuntime(): Runtime {\n this.#assertNotDisposed();\n const runtime = this.#runtime;\n if (runtime === undefined) {\n throw new SimulatorError(\n 'simulator/not-ready',\n 'Load a schematic before advancing the simulation.',\n );\n }\n return runtime;\n }\n\n #assertNotDisposed(): void {\n if (this.#state === 'disposed') {\n throw new SimulatorError('simulator/disposed', 'The executor is disposed.');\n }\n }\n\n #report(diagnostic: SimulationDiagnostic): void {\n try {\n this.#options.onDiagnostic?.(diagnostic);\n } catch {\n // A throwing diagnostic sink must not take the simulation down.\n }\n }\n}\n\nfunction collectTopics(\n symbol: SchematicSymbol,\n instance: DeviceInstance,\n into: DeviceTopic[],\n): void {\n for (const [port, descriptor] of instance.description.ports) {\n into.push(\n Object.freeze({\n symbol: symbol.id,\n port,\n direction: descriptor.direction,\n type: descriptor.type,\n ...(descriptor.title === undefined ? {} : { title: descriptor.title }),\n }),\n );\n }\n}\n\nfunction destroyQuietly(\n instance: DeviceInstance,\n report: (diagnostic: SimulationDiagnostic) => void,\n): void {\n try {\n instance.destroy();\n } catch (cause) {\n report({\n code: 'simulator/dispose-failed',\n message: `Device instance \"${instance.instanceId}\" failed to destroy.`,\n symbol: instance.instanceId,\n cause,\n });\n }\n}\n\nfunction disposeHostQuietly(\n host: DeviceSimHost | undefined,\n report: (diagnostic: SimulationDiagnostic) => void,\n): void {\n try {\n host?.dispose?.();\n } catch (cause) {\n report({\n code: 'simulator/dispose-failed',\n message: 'The host wiring failed to dispose.',\n cause,\n });\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,41 @@
1
1
  {
2
2
  "name": "@mica-js/simulator",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Mica simulator: runs the device instances of a schematic on one virtual clock",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/x-botlab/mica-js.git",
9
+ "directory": "packages/simulator"
10
+ },
11
+ "type": "module",
12
+ "sideEffects": false,
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "dependencies": {
26
+ "@mica-js/core": "0.2.0",
27
+ "@mica-js/device": "0.2.0"
28
+ },
29
+ "devDependencies": {
30
+ "typescript": "^6.0.3",
31
+ "vite": "^8.3.4",
32
+ "vite-plugin-dts": "^5.1.2",
33
+ "vitest": "^5.0.5"
34
+ },
35
+ "scripts": {
36
+ "build": "vite build",
37
+ "test": "vitest run",
38
+ "test:watch": "vitest",
39
+ "typecheck": "tsc --noEmit"
40
+ }
6
41
  }