From 528c4a8c6da25d28068d8796ef91082f2d61a62b Mon Sep 17 00:00:00 2001 From: ApsisRS contributors Date: Sat, 26 Sep 2026 22:53:51 +0800 Subject: [PATCH] docs: record the simulation layer design --- docs/design/README.md | 4 ++ docs/design/sim.md | 85 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 docs/design/sim.md diff --git a/docs/design/README.md b/docs/design/README.md index 6a574f4..3f9ebfa 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -4,3 +4,7 @@ 分层边界。单个 crate 的内部设计写在对应 crate 的模块文档中。 文档内容需自包含,仅引用本仓库内的代码或可公开访问的标准。 + +## 各层设计 + +- [仿真层](sim.md) diff --git a/docs/design/sim.md b/docs/design/sim.md new file mode 100644 index 0000000..ee6c820 --- /dev/null +++ b/docs/design/sim.md @@ -0,0 +1,85 @@ +# 仿真层设计 + +本文件记录仿真层(`sim`)的跨 crate 决策。各 crate 的内部设计见其模块文档。 + +## 组成与依赖 + +```text +apsis-event 事件类型与事件队列 +apsis-sim-engine 离散事件引擎 +apsis-scenario 场景定义与文本加载 +apsis-output 仿真输出记录 +apsis-sim 仿真门面(整合上述四个) +``` + +依赖方向: + +```text +apsis-event -> apsis-sim-engine -> apsis-sim -> apsis-rs +apsis-scenario -----------------> apsis-sim +apsis-output -------------------> apsis-sim +``` + +`apsis-sim` 同时依赖 ops 层(`apsis-scheduler`、`apsis-schedule`、 +`apsis-task`、`apsis-resource`),因为它要规划场景并读取计划。其余 crate +只依赖 foundation。 + +## 事件顺序 + +同一时刻的多个事件必须按可复现的顺序投递,否则同一输入会产生不同输出, +无法与参考实现对拍。约定: + +- 每个事件在入队时获得一个递增的序号。 +- 事件先按时间排序,时间相同则按序号排序,即入队顺序。 +- 处理函数在处理某事件时新调度的事件,排在该时刻已有事件之后。 + +该顺序由 `apsis-event` 保证,`apsis-sim-engine` 与 `apsis-sim` 直接沿用。 +序列号由队列分配,调用方无法制造两个同号事件。 + +## 时间原点 + +事件时间是从仿真原点起算的秒数,与日历无关;需要日历的场景由上层换算。 + +场景的 `epoch` 是时钟起点。时间线中的事件时间相对场景原点,`apsis-sim` +在投递前加上 `epoch`。若不偏移,引擎会在投递首个事件时把时钟回拨。 + +时间限制按同一绝对时间轴比较:时间晚于上限的事件留在队列中而不执行,因此 +调用方可以查看运行结束时还剩什么。时钟停在最后一个已执行事件上,而不是停在 +上限上。 + +## 事件限制是累计的 + +`apsis-sim-engine` 的事件上限是引擎整个生命周期的总数,而非单次 `run` +的数量。达到上限后再次 `run` 不执行任何事件。这样"上限"始终表示一个不会 +被绕过的界,调用方要运行剩余事件需显式提高上限。 + +## 时间线语义 + +`apsis-sim` 把计划展开为时间线上的起止事件:每个已放置任务贡献一个开始 +事件和一个结束事件,二者是独立事件,因此引擎能按时间顺序把它们与其他事件 +正确排序,重叠任务的先后也由时钟决定。 + +时间线是计划本身,不是物理模型。开始/结束事件触发时调用什么物理计算由处理 +函数决定,门面只负责排序与记录。 + +## 场景文本格式 + +`apsis-scenario` 自带一套零依赖的行式文本格式,用于存储与重新加载。 +解析分三遍(资源、任务、优先级约束),因此 `require` 可以引用文件中稍后 +定义的资源,`precedence` 可以引用任意顺序定义的任务。每处失败都带行号。 + +`Scenario::to_text` 的输出必须能被 `Scenario::parse` 读回为相等场景, +即文本形式是往返稳定的;这一点由测试保证。 + +任务的"无窗口"(两端为无穷)无法写入文本,故写为一个包含该任务的有限区间, +读回后任务仍可用。 + +## 确定性 + +仿真结果必须可复现,这是对拍的前提。确定性来自三点: + +- 事件队列表述的同刻顺序规则; +- 规划器对同权任务的稳定排序(见 ops 层); +- 记录器保留插入顺序,`Trace::in_time_order` 为稳定排序。 + +`integrate_sim` 示例断言同一次准备出的仿真重复运行得到相同轨迹。