@mica-js/simulator 0.2.0 → 0.2.1

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.
Files changed (2) hide show
  1. package/README.md +79 -43
  2. package/package.json +3 -3
package/README.md CHANGED
@@ -2,7 +2,27 @@
2
2
 
3
3
  在一条虚拟时钟上运行原理图里的全部器件实例:`loadSchematic` 建实例,`advance` 推进时间,`onPublish` / `deliver` 与画布互投载荷。
4
4
 
5
- 依赖方向:`@mica-js/simulator` → `@mica-js/device` → `@mica-js/ir` → `@mica-js/core`。
5
+ 依赖方向:`@mica-js/simulator` → `@mica-js/device`,两者都建立在 `@mica-js/core`(及其底层的 `@mica-js/ir`)之上。
6
+
7
+ ## 架构
8
+
9
+ ```mermaid
10
+ flowchart TD
11
+ sch[".sch 原理图"] --> exec["DeviceSimExecutor"]
12
+ devices["IDeviceProvider<br/>器件描述 + 代码"] --> exec
13
+
14
+ exec --> scheduler["Scheduler<br/>一条虚拟时钟"]
15
+ exec --> instances["DeviceInstance × 符号"]
16
+
17
+ hostFactory["DeviceSimHostFactory<br/>每次运行时调用一次"] --> pinHost["pinHost"]
18
+ hostFactory --> topology["topology"]
19
+ pinHost --> instances
20
+ topology --> instances
21
+
22
+ exec --> onPublish["onPublish<br/>器件 → widget"]
23
+ exec --> deliver["deliver<br/>widget → 器件"]
24
+ circuit["createCircuitHost()<br/>可选电气接线"] -.-> hostFactory
25
+ ```
6
26
 
7
27
  ## 用法
8
28
 
@@ -15,22 +35,18 @@ import { createDeviceSimExecutor } from '@mica-js/simulator';
15
35
  import { createWidgetHost } from '@mica-js/widget';
16
36
 
17
37
  const executor = createDeviceSimExecutor({
18
- devices: provider, // IDeviceProvider:器件描述 + 代码,原理图里只写 device id
38
+ devices: provider, // IDeviceProvider:原理图里只写 device id
19
39
  onDiagnostic: (diagnostic) => console.warn(diagnostic.code, diagnostic.message),
20
- // 引脚与连接需要宿主侧接线;工厂每次建运行时调用一次,拿到运行时自己的调度器。
21
- host: ({ scheduler, schematic }) => {
40
+ host: ({ scheduler }) => {
22
41
  const power = createPowerRuntime(scheduler, board, { instanceId: 'power', pinStates });
23
42
  return {
24
43
  pinHost: power,
25
44
  topology: power,
26
- instanceOptions: (symbol) =>
27
- symbol.device === 'mica:teaching-mcu' ? { updateResources: ['firmware'] } : undefined,
28
45
  dispose: () => power.destroy(),
29
46
  };
30
47
  },
31
48
  });
32
49
 
33
- // 器件发布 → 绑定的 widget;widget 发布 → 绑定的器件端点。
34
50
  const host = createWidgetHost({ provider, assets, simulation: executor });
35
51
 
36
52
  await executor.loadSchematic(parseSchematic(schText)); // 文本由调用方解析
@@ -39,52 +55,74 @@ const report = executor.advance(timePs(100_000)); // 100 ns,同步完成
39
55
 
40
56
  `DeviceSimExecutor` 结构上满足 `@mica-js/widget` 的 `WidgetHostSimulation`,不需要适配层;宿主之外也可以自己订阅 `onPublish` / 调用 `deliver`。
41
57
 
42
- ## 宿主接线(`host`)
58
+ ## 宿主接线
43
59
 
44
- 执行器不拥有电路:它只把宿主建的 `pinHost` 与 `topology` 透传给每个实例。`@mica-js/core` 的 `createPowerRuntime` 同时满足 `DevicePinHost` 与 `TopologyRuntime`,所以一块板只需要一个运行时。
60
+ 执行器不拥有电路:它只把宿主建的 `pinHost` 与 `topology` 透传给每个实例。
45
61
 
46
- | 字段 | 说明 |
47
- | ----------------- | ------------------------------------------------------------------------------------ |
48
- | `pinHost` | 逐实例的 `pinHost`;`b.pin.*` / `b.observeDigital` 依赖它 |
49
- | `topology` | 逐实例的 `connections.topology`;`b.connection.apply` 依赖它 |
50
- | `instanceOptions` | 逐符号补充实例选项(目前是 `updateResources` / `messaging`);身份、属性与接线不开放 |
51
- | `dispose` | 实例全部销毁后调用,用来释放电源/拓扑运行时 |
62
+ | 字段 | 说明 |
63
+ | ----------------- | ----------------------------------------------------------------------------- |
64
+ | `pinHost` | 逐实例的 `pinHost`;`b.pin.*` / `b.observeDigital` 依赖它 |
65
+ | `topology` | 逐实例的 `connections.topology`;`b.connection.apply` 依赖它 |
66
+ | `instanceOptions` | 逐符号补充实例选项(`updateResources` / `messaging`);身份、属性与接线不开放 |
67
+ | `dispose` | 实例全部销毁后调用,用来释放电源 / 拓扑运行时 |
52
68
 
53
- 工厂在每次 `loadSchematic` 和 `reset()` 建运行时各调用一次;每次运行时都有**自己的新调度器**,所以工厂里的实例 id 不会跨运行时冲突。工厂或 `dispose` 抛错时:构建阶段直接让 `loadSchematic` / `reset` 抛错(旧运行时原样保留),释放阶段转成 `simulator/dispose-failed` 诊断。
54
-
55
- 不配置 `host` 时实例没有 `pinHost` 也没有拓扑,用到引脚或连接的器件会在加载时报器件层错误(例如 `device/connection-unavailable`),并被包成 `simulator/instance-create-failed`。
69
+ - 工厂在每次 `loadSchematic` 和 `reset()` 建运行时各调用一次;每次运行时都有**自己的新调度器**,所以工厂里的实例 id 不会跨运行时冲突。
70
+ - `DeviceSimHostContext` 提供 `scheduler`、`schematic` 与按 device id 索引的 `modules`,接线宿主直接读引脚几何而不必重复加载器件。
71
+ - `@mica-js/core` 的 `createPowerRuntime` 同时满足 `DevicePinHost` 与 `TopologyRuntime`,所以一块板只需要一个运行时。
72
+ - 工厂或 `dispose` 抛错时:构建阶段直接让 `loadSchematic` / `reset` 抛错(旧运行时原样保留),释放阶段转成 `simulator/dispose-failed` 诊断。
73
+ - 不配置 `host` 时实例没有 `pinHost` 也没有拓扑,用到引脚或连接的器件会在加载时报器件层错误,并被包成 `simulator/instance-create-failed`。
56
74
 
57
75
  ## 接口
58
76
 
59
- | 成员 | 说明 |
60
- | -------------------------- | --------------------------------------------------------------------------------------------- |
61
- | `getState()` | `'idle'`、`'ready'`、`'faulted'`、`'disposed'`;只有 `ready` 能推进 |
62
- | `getSimTime()` | 当前虚拟时间;首次加载前为 `0` |
63
- | `getSchematic()` | 当前运行时的来源文档;首次加载前是空图 |
64
- | `getTopics()` | 每个实例的端口,`symbol`(id,绑定用的就是它)+ `designator`(位号,未设置时等于 id)+ `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()` 生成画布的绑定候选。`symbol` 是原理图里的符号 id(绑定存的就是它),`designator` 是给人看的位号(`designatorOf`:符号自己的 `designator`,没写时回退到 id),两者在界面上不要混用。
77
+ | 成员 | 说明 |
78
+ | -------------------------- | ---------------------------------------------------------------------- |
79
+ | `getState()` | `'idle'` / `'ready'` / `'faulted'` / `'disposed'`;只有 `ready` 能推进 |
80
+ | `getSimTime()` | 当前虚拟时间;首次加载前为 `0` |
81
+ | `getSchematic()` | 当前运行时的来源文档;首次加载前是空图 |
82
+ | `getTopics()` | 每个实例的 `symbol`(绑定用的就是它)、`designator`(位号)与 `port` |
83
+ | `loadSchematic(document)` | 按符号建实例并跑 `initialize`,时钟归零;构建失败时旧运行时原样保留 |
84
+ | `advance(delta)` | 同步推进 `delta` 皮秒,返回 `{ from, to, executed, reason }` |
85
+ | `onPublish(listener)` | 器件发布 `(target, payload, simTime)`;返回取消订阅函数 |
86
+ | `deliver(target, payload)` | 排入下一次 `advance` 的开头,同一时刻多次调用按调用顺序被器件看到 |
87
+ | `reset()` | 丢弃实例、按当前原理图重建,时钟归零;同步 |
88
+ | `dispose()` | 销毁全部实例并释放运行时;同步、幂等 |
89
+
90
+ `DeviceTopic` 的 `direction` / `type` 与 `DevicePortDescriptor` 同构,宿主可以直接用 `getTopics()` 生成画布的绑定候选。`symbol` 是原理图里的符号 id(绑定存的就是它),`designator` 是给人看的位号(符号自己的 `designator`,没写时回退到 id),两者在界面上不要混用。
73
91
 
74
92
  ## 语义
75
93
 
76
- - **除 `loadSchematic` 之外全是同步的。** 加载之所以返回 promise,只因为器件模块要经 provider 取;模块随后被缓存,`reset()` 直接用缓存重建实例,不会再让出线程。仿真与 `WidgetHost` 跑在同一个 JS 线程上,没有 worker,也没有消息通道。
77
- - **先建后换。** `loadSchematic` 在新调度器上建好实例、跑完 `initialize` 后才替换旧运行时,因此器件解析失败或工厂抛错时,正在跑的仿真不受影响,时钟也不回退。宿主接线工厂也在这个阶段构建:它抛错同样保留旧运行时。
94
+ - **除 `loadSchematic` 之外全是同步的。** 加载之所以返回 promise,只因为器件模块要经 provider 取;模块随后被缓存,`reset()` 直接用缓存重建实例。仿真与 `WidgetHost` 跑在同一个 JS 线程上,没有 worker,也没有消息通道。
95
+ - **先建后换。** `loadSchematic` 在新调度器上建好实例、跑完 `initialize` 后才替换旧运行时,因此器件解析失败或工厂抛错时,正在跑的仿真不受影响,时钟也不回退。
78
96
  - **`advance` 是同步的。** 整个时间片在调用内跑完,宿主可以在帧循环里直接调用并立刻拿到报告;推进失败时 `getState()` 变成 `'faulted'` 并原样抛出底层 `SchedulerError`。
79
- - **`deliver` 收 `unknown`,延迟到时间片开头。** 宿主转发的是 widget 发布的原始载荷,端口类型由器件层校验;同一时刻多次投递按调用顺序被器件看到,UI 线程不会把载荷插进两次 `advance` 之间。载荷被器件层拒绝时 `advance` 抛出,但不会把执行器置为 `'faulted'`(故障只由调度器决定),宿主应当把这次输入记为被拒绝。
97
+ - **`deliver` 收 `unknown`,延迟到时间片开头。** 端口类型由器件层校验;同一时刻多次投递按调用顺序被器件看到。载荷被器件层拒绝时 `advance` 抛出,但不会把执行器置为 `'faulted'`(故障只由调度器决定),宿主应当把这次输入记为被拒绝。
80
98
  - **`simTime` 由执行器负责。** 器件发布时的时间戳取自调度器,画布侧不需要传时间。
81
99
  - **诊断不中断仿真。** `onDiagnostic` 抛错会被吞掉;只有 `advance` 的调度器故障才会把状态置为 `'faulted'`。
82
100
 
83
- 诊断码以 `simulator/` 开头:`device-load-failed`、`instance-create-failed`、`advance-failed`、`publish-failed`、`dispose-failed`。宿主调用的参数错误抛 `SimulatorError`(`code` 见 `SimulatorErrorCode`)。
101
+ ```mermaid
102
+ sequenceDiagram
103
+ participant App as 宿主
104
+ participant Sim as DeviceSimExecutor
105
+ participant Sched as Scheduler
106
+ participant Dev as DeviceInstance
107
+
108
+ App->>Sim: deliver(target, payload)
109
+ Sim->>Sim: 入队待投递载荷
110
+ App->>Sim: advance(delta)
111
+ Sim->>Dev: deliver(port, payload, meta)
112
+ Sim->>Sched: start({ until: now + delta })
113
+ Sched->>Dev: initialize / 事件入口
114
+ Dev-->>Sim: api.publish / IR 输出
115
+ Sim-->>App: AdvanceReport
116
+ ```
117
+
118
+ ## 错误
119
+
120
+ - 诊断码以 `simulator/` 开头:`device-load-failed`、`instance-create-failed`、`advance-failed`、`publish-failed`、`dispose-failed`。
121
+ - 宿主调用的参数错误抛 `SimulatorError`,`code` 为 `simulator/disposed`、`simulator/not-ready`、`simulator/invalid-delta`、`simulator/unknown-target`、`simulator/unknown-port`、`simulator/device-load-failed` 或 `simulator/instance-create-failed`。
84
122
 
85
123
  ## 电气层
86
124
 
87
- 原理图里的导线就是电路:`createCircuitHost()` 返回一个宿主接线工厂,用 `deriveSchematicTopology` 把每个器件引脚按合成符号的几何位置落到网,折成 `TopologyDefinition` 交给 `createPowerRuntime` 解算,再把结果作为 `pinHost` / `topology` 交给执行器。执行器自身不解析电路,这条路径是可选的一层便利封装。
125
+ 原理图里的导线就是电路。`createCircuitHost()` 返回一个宿主接线工厂,用 `deriveSchematicTopology` 把每个器件引脚按合成符号的几何位置落到网,折成 `TopologyDefinition` 交给 `createPowerRuntime` 解算,再把结果作为 `pinHost` / `topology` 交给执行器。
88
126
 
89
127
  ```ts
90
128
  const executor = createDeviceSimExecutor({
@@ -94,15 +132,13 @@ const executor = createDeviceSimExecutor({
94
132
  ```
95
133
 
96
134
  - **网 → 外部导线。** 每张网一条 `wireId`,成员是网里的全部引脚;带地符号的网标 `globalGnd`。
97
- - **电源符号 → 电源实例。** `builtin:power` / `builtin:ground` 属于合成实例 `power`:供电网的引脚按 `supplyVoltage`(默认 5 V)模拟输出,接地网的引脚按逻辑低电压强驱动。地是 0 V 参考,不是悬空——所以「触点接地」真的能把引脚拉低。
98
- - **器件引脚 → 拓扑引脚。** 引脚种类取自器件描述(`signal` / `power` / `gnd`);器件描述里的 `connections` 变成可开关的内部连接,`connection.apply` 直接作用在这张网上。引脚几何来自 `DeviceSimHostContext.modules`,所以工厂不会重复加载器件。
135
+ - **电源符号 → 电源实例。** `builtin:power` / `builtin:ground` 属于合成实例 `power`:供电网的引脚按 `supplyVoltage`(默认 5 V)模拟输出,接地网的引脚按逻辑低电压强驱动。
136
+ - **器件引脚 → 拓扑引脚。** 引脚种类取自器件描述(`signal` / `power` / `gnd`);描述里的 `connections` 变成可开关的内部连接,`connection.apply` 直接作用在这张网上。
99
137
  - **引脚初值一条不少。** `PowerOptions.pinStates` 要求每个已声明引脚一条,默认高阻、无上下拉;器件在 `initialize` 里 `pin.configure` 之后由器件层覆盖。
100
138
  - **生命周期跟着运行时走。** 实例先销毁(退订、停观察者),工厂的 `dispose` 最后销毁电气网络;构建失败时同样按这个顺序回收。
101
139
 
102
- 按钮 + 计数器 + 数码管的完整样例见 `apps/playground/public/devices/`,`apps/playground/scripts/build-device-demo.mjs` 负责在改动布局后重算导线端点。
103
-
104
140
  ## 边界
105
141
 
106
- - **电路定义归宿主所有。** 执行器只透传 `pinHost` / `topology`,不解析板级电源、上拉或网络拓扑;引脚与电气观测(`sampleDigital`、`connectionState`)由宿主读自己的运行时。`createCircuitHost` 是可选的实现之一,手写电路(例如 playground 的仿真调试页)可以直接实现 `DeviceSimHostFactory`。
142
+ - **电路定义归宿主所有。** 执行器只透传 `pinHost` / `topology`,不解析板级电源、上拉或网络拓扑;引脚与电气观测由宿主读自己的运行时。`createCircuitHost` 是可选的实现之一,手写电路可以直接实现 `DeviceSimHostFactory`。
107
143
  - **模拟量只有理想电压源。** `createCircuitHost` 的引脚模型固定为 `ideal-voltage`(0–5 V,低 0.8 V、高 2 V),没有电流、RC 或器件间负载;需要更细的电气行为时要往 `@mica-js/core` 的电气层加模型,或换一个自定义宿主。
108
- - 器件代码在宿主 realm 里求值(与 `loadDeviceModule` 相同,等同可信扩展),沙箱化未做。
144
+ - **器件代码在宿主 realm 里求值**(与 `loadDeviceModule` 相同,等同可信扩展),不做沙箱化。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mica-js/simulator",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Mica simulator: runs the device instances of a schematic on one virtual clock",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -23,8 +23,8 @@
23
23
  "access": "public"
24
24
  },
25
25
  "dependencies": {
26
- "@mica-js/core": "0.2.0",
27
- "@mica-js/device": "0.2.0"
26
+ "@mica-js/core": "0.3.0",
27
+ "@mica-js/device": "0.2.1"
28
28
  },
29
29
  "devDependencies": {
30
30
  "typescript": "^6.0.3",