路线、运输与移动系统
参伍用三个 crate 模拟出行。canwu-routing 根据某个观察者掌握的信息规划路线,canwu-transport 记录途中实际发生的事,canwu-movement 则是在结算边界中推进这些记录的模拟插件。游戏里的军队、信使、货物或信件要走多段路线,途中可能失败、交接或等待紧缺运力时,就会用到它们。
crate 与所有权
Section titled “crate 与所有权”| crate | 负责什么 | 发布情况 |
|---|---|---|
canwu-routing |
基于 PlanningSnapshot 做确定性的、随时间变化的路线规划。它只做计算:不读取模拟状态,不抽随机数,也不安排后续工作。 |
已发布到 crates.io,类型由 canwu-api 重新导出 |
canwu-transport |
普通记录及其状态转移规则:移动命令、运输执行、行程修订、路段、交接(含扣押)、运力预订、运力池、纯函数形式的预订分配 allocate_capacity_bookings,以及投递 saga 记录 |
已发布到 crates.io,类型由 canwu-api 重新导出 |
canwu-movement |
移动生命周期扩展 MovementPlugin:准入移动命令,在各路段到期时结算,分配运力池,发布持有人相对的移动报告 |
已发布到 crates.io,需要单独添加依赖 |
查看图表源码
flowchart TB
App["你的应用<br/>网络数据、事故、<br/>权限记录"] -- "注册" --> Movement["canwu-movement<br/>MovementPlugin"]
App --> Api["canwu-api<br/>对外 API"]
Movement --> Api
Corr["canwu-correspondence<br/>自行维护运输执行"] --> Api
Api --> Sim["canwu-sim<br/>私有运行时"]
Api --> Transport["canwu-transport<br/>记录库"]
Api --> Routing["canwu-routing<br/>路线规划器"]
Transport --> Routing
Transport --> Found["canwu-core、canwu-time"]
Routing --> Found
为什么运输层只是记录库
Section titled “为什么运输层只是记录库”所有模拟插件都建立在 canwu-api 之上,而 canwu-api 为了重新导出运输类型,依赖 canwu-transport。如果把插件放进 canwu-transport,它就得反过来依赖 canwu-api,Cargo 会因为依赖成环而拒绝构建。所以 canwu-transport 只做普通的库:它的值通过 TransportExecution::reroute、TransportExecution::record_handoff、CapacityBooking::transition 等方法自行检查状态转移,从不接触模拟状态。负责生命周期的插件放在上一层的 canwu-movement 中。
这样拆分还有一个好处:同一套记录可以由不同的所有者推进。canwu-movement 是通用的生命周期实现;canwu-correspondence 把信件的运输执行保存在自己的插件状态里;自己管理生命周期的上层应用也可以直接调用 allocate_capacity_bookings,得到相同的分配顺序。
参考世界的移动插件是另一回事
Section titled “参考世界的移动插件是另一回事”移动军队教程使用参考世界的 order_movement_v1 命令。这是一个可替换的小示例:它在示例世界中查找一条直达路线,预留要移动的军队或人物,再安排出发和到达两条调度输入(movement_transition_v1)。canwu-movement 则是面向 canwu-transport 记录的通用生命周期,支持多段行程、改道、运力池和报告,与参考世界没有任何关联。
参伍把一次行程拆成规划和执行两部分。规划回答“这个观察者根据自己知道的信息会选哪条路线”。plan_route(&snapshot, &request) 只读取这两个参数,返回一个 RoutePlan 值,因此相同的快照和请求总是得到相同的计划或相同的错误。执行记录从一份计划出发,之后只通过各自的状态转移方法改变。
canwu-movement 从不规划路线。Order 操作在 MovementOrder 中携带 RoutePlan,Reroute 操作携带一个新的 ItineraryRevision,其中的计划由你的代码算出。
规划类型(canwu-routing):
| 类型 | 表示什么 |
|---|---|
PlanningSnapshot |
某个观察者在某一时刻所知的网络:observer、observed_at、可选的 valid_until、knowledge_cut、topology_version、可选的 timetable_version,以及 RoutingNetwork |
RoutingNetwork |
带版本的 RoutingEndpoint 与 RoutingConnection 列表。RoutingNetwork::new 会排序,并拒绝重复项和无效连接。 |
RoutingConnection |
一条有向连接,带 TransferMode、TraversalModel(Fixed、Departures、Recurring 或 Piecewise)、可选的可用时间窗口、risk_per_mille 和 resource_cost |
RoutingRequest、RoutingPolicy |
起点、终点和出发时间。策略选择 FifoDijkstraV1 或 BoundedLabelCorrectingV1,并规定允许的交通方式和搜索上限。 |
RoutePlan |
选中的 RouteLeg 列表及其计划时间、RouteCost、预计到达时间、算法与策略版本、快照摘要,以及计划自身的摘要 |
RoutingCache |
可选的上层应用缓存,以快照摘要和请求为键保存计划 |
执行记录(canwu-transport):
| 类型 | 表示什么 |
|---|---|
MovementOrder |
已准入的移动意图:按实体排序的 MovementSubject(各带一个 MovementSubjectRole)、起点和终点、RoutePlan、MovementInitiative、下令时间,以及预期的位置修订号 |
TransportExecution |
一次进行中的行程:TransportExecutionState、各次行程修订、路段、交接、预订,以及可选的投递 saga |
ItineraryRevision |
一份不可变的行程,带计划、前驱修订、ItineraryRevisionReason 和被取代的时间。改道会追加一份新修订。 |
LegExecution |
某个计划路段的实际情况:LegExecutionStatus,以及实际出发、到达或失败的时间 |
Handoff |
路段之间的保管权交接,类型为 HandoffKind::Planned 或 HandoffKind::Seizure { by } |
CapacityBooking |
在某个时间窗口内占用运力的预订,带 CapacityBookingStatus |
TransportCapacityPoolV1 |
一个时间窗口内一定数量的可互换运力,带 booked、consumed 两个计数器,以及每次变化都会递增的 revision |
DeliverySaga、DeliveryCompletionRequest |
把到达结果连回信息投递尝试的记录,带稳定的操作键 |
移动插件(canwu-movement):
| 类型 | 表示什么 |
|---|---|
MovementPlugin |
模拟插件本身。MovementPlugin::new(evidence_kinds) 声明它可以读取哪些应用记录种类,这份列表就是它读取证据的全部范围。 |
MovementState |
整个移动运行时,保存为一条领域记录:运输执行、移动命令、运力池、预订绑定、操作结果和报告头 |
MovementCommandV1、MovementOperation |
来自某个持有人的命令载荷,带操作键和一个操作:Order、StartLeg、CompleteLeg、FailLeg、DelayLeg、Reroute、RecordHandoff、RequestBooking、RevokeBooking、Cancel 或 OfferPool |
MovementIncidentV1 |
应用报告的 FailLeg、DelayLeg、Reroute、RecordHandoff、RevokeBooking 或 Reconcile,引用一条证据记录的当前版本 |
MovementOperationOutcomeV1 |
某个操作键的持久结果:Applied,或带 MovementErrorCode 的 Rejected |
MovementReportV1 |
某个持有人看到的一次运输执行,以知识形式发布 |
查看图表源码
flowchart TB
Plan["上层应用或应用插件<br/>用 plan_route 得到 RoutePlan"] --> Cmd["命令<br/>apply_movement_operation_v1"]
Cmd --> Admit["命令处理器检查<br/>持有人、操作键、大小、配额"]
Admit --> OpQ["内部输入<br/>movement_operation_v1"]
AppSys["应用系统<br/>判定发生事故"] --> Inc["公开输入<br/>movement_incident_v1"]
OpQ --> P7["第 7 阶段<br/>movement_lifecycle_apply_v1"]
Inc --> P7
Due["内部输入<br/>movement_leg_due_v1"] --> P7
P7 -- "安排下一个到期路段" --> Due
P7 --> P8["第 8 阶段<br/>movement_lifecycle_validate_v1"]
P8 --> P13["第 13 阶段<br/>movement_report_publish_v1"]
P13 --> Know["持有人知识<br/>movement_report"]
- 规划。上层应用或应用插件构建
PlanningSnapshot,调用plan_route,把得到的RoutePlan放进MovementOrder。 - 提交。用
movement_command(&MovementCommandV1)构建领域命令apply_movement_operation_v1,作为CommandRequest通过enqueue_command提交。用submit直接应用的命令会被插件以MixedCommandIngress拒绝。MovementCommandV1::holder必须是签发者本人对应的持有人:签发者是角色时就是该角色,否则是命令主体。命令处理器检查载荷大小、操作键、持有人和配额,然后把内部输入movement_operation_v1排入队列。只有这个处理器能生成这种输入。 - 报告事故。应用系统判定某个路段失败、某个已出发的路段要滞留、某条路线封闭、有人夺走保管权或某次投递已经结束时,发送公开输入
movement_incident_v1。上层应用用movement_incident_ingress构建它;边界系统先在plugin_ingress_targets中列出movement_incident_target(),再用BoundaryDirective::SchedulePluginIngress安排它。 - 第 7 阶段
DomainDeltaProposal,事件驱动。movement_lifecycle_apply_v1按准入顺序应用已准入的操作和事故。随后它让窗口已结束的已确认预订过期,对每个运力池运行一次分配,处理到期路段,退休已关闭的运输执行,并清理过期的操作结果。它写入运行时记录,并为需要的移动命令安排下一条movement_leg_due_v1。 - 第 8 阶段
InvariantValidation。movement_lifecycle_validate_v1检查暂存的运行时:移动命令与运输执行一一对应、路段连续、每个运输执行只有一份生效行程、预订与运力池计数一致、到期工作有效、已出发路段的到达时间正确、各集合没有超限。检查失败时整个边界回滚。 - 第 13 阶段
PerspectiveAndReportMaterialization。本边界准入过任何移动输入时,movement_report_publish_v1把有变化的报告发布给对应持有人,并为延迟观察者安排movement_report_wake_v1。
结算系统页面列出了这些系统与其他扩展系统的相对位置。
路段靠内部调度输入推进。移动命令在计划出发时间安排第一次出发;准入时如果已经过了这个时间,就在准入时出发。出发后,按路段的计划时长安排到达;到达后,再安排下一段出发。每条移动命令在 pending_due 中只记下自己等待的那一条输入,其他到期输入都已过时,不起作用。所以显式的 StartLeg、FailLeg、DelayLeg 或 Reroute 会取代旧的计时;被取代而尚未到期的输入由插件撤回(见下)。
移动命令的所有者或操作者,或者应用报告的事故,可以对当前路段发送 DelayLeg,为它设置路段滞留(leg hold):路段已出发时滞留它的到达,尚未出发(Planned、Booked 或 Waiting,带待处理的出发到期)时滞留它的出发,都不产生行程修订。until 是一个绝对时刻,不早于结算它的边界,也不早于该路段的排定时刻(scheduled due):已出发的路段是实际出发时间加计划时长,尚未出发的路段是它首次被滞留时待处理的出发时刻,所以反复滞留不会让路段比首次滞留时更早出发。要延长或缩短滞留,再发送一次 DelayLeg;把 until 设为排定时刻,就解除了滞留。只保留最新的一次滞留。带 retime_later_legs 的滞留在结束时把实际经过的部分(最多为滞留的延长量)加到移动命令的行程顺延量(schedule shift,schedule_shift)上,后续路段的计划出发和预订窗口随之顺延,保持原有间隔;不带它时后续路段保持计划时间,紧随到达出发。改道会把顺延量清零。滞留在它的到期结算、StartLeg 或 CompleteLeg 显式作用于该路段、路段失败,或修订被改道或取消关闭时结束:CompleteLeg 仍会让滞留中的路段立即到达,FailLeg 仍会让它失败。路段已出发时,Reroute 仍会被拒绝。几种边界情况:滞留与它所取代的到期在同一个边界中结算时,以滞留为准;未通过检查的 DelayLeg 会被拒绝,并记下一条结果;滞留期间预订过期的后续路段,到出发时以 capacity_unavailable 失败,除非先被替换。
一次操作或滞留取代的到期输入如果尚未到期,插件会在同一边界里用 CancelPluginIngress 撤回它(撤回是该边界的一条 PluginCancellation 输入记录),所以被取代的输入不会累积,不会唤醒本来为空的边界,pending_ingress 也不再列出它们;在到期当刻被取代的输入照旧作为过时输入被跳过。
到期工作从不让边界失败。到期的出发或到达无法应用时,当前路段以 due_settlement_failed 为原因失败,所有者或操作者可以改道或取消。最后一段到达时,没有投递尝试的运输执行直接结算;带有 delivery_attempt 的运输执行进入 ArrivalPending,等待一条引用该投递尝试记录的 Reconcile 事故。
插件在第 7 阶段用 allocate_capacity_bookings 分配运力池,与内核第 6 阶段的资源分配相互独立。OfferPool 操作提供运力池,RequestBooking 为一个尚未出发的路段申请运力,预订窗口必须落在该路段按行程顺延量(以及正在生效、会顺延它的滞留)顺延后的计划窗口内;申请可以用 replaces 指名要替换的同一路段的一项更早预订(预订替换,每项预订只能被替换一次),待处理的原预订随即被取消,已确认的被释放;原预订离开运输执行,由替换它的预订接替。本边界申请的每项预订要么全额确认,要么失败,结果记录为 CapacityBookingAllocationEvidenceV1。分配时依次按优先级(从高到低)、窗口开始时间、平局决胜键、准入序号和预订标识排序。已经确认的运力始终归属原预订,后来的申请拿不走。
出发的路段消耗自己已确认的预订。运力池的保管人,或引用当前证据的应用事故,可以用 RevokeBooking 撤销一项已确认的预订(预订撤销):预订变为 Revoked,记下时间和原因,运力回到运力池,供之后的申请使用或由 OfferPool 收回。预订失败、过期或被撤销且未被替换的路段以 capacity_unavailable 失败,等待改道。已确认但窗口尚未开始的预订会把出发推迟到窗口开始。
运输执行只保留仍与其路段有关的预订(在持预订):已申请或已确认的、正在途中的路段消耗掉的,以及生效行程中尚未出发的路段已失去(失败、过期或被撤销)而尚未替换的。其余预订均已了结(已了结预订),在了结它的那一步离开运输执行,其绑定也离开运行时:被替换的在替换申请中离开,消耗它的路段到达或失败时离开,或在释放、取消、改道或关闭运输执行的那一步离开。已消耗的运力仍由运力池计入(MovementState::retired_consumption)。预订上限只计在持预订:每个运输执行同时最多 64 项,整个运行时最多 16,384 项,超出时申请以 LimitExceeded 被拒绝。
每个操作键都有一个持久结果,可以用 MovementState::operation_outcome 查询。命令的操作键按持有人区分,事故的操作键按证据记录种类区分,所以任何来源都占用不了别人的键。完全相同的重试不产生变化。命令用同一个键提交不同内容时,会在准入时以 IdempotencyConflict 被拒绝;其他冲突的复用(例如来自事故的)会另记一条 Conflict 拒绝结果。
周期性班次、连接条件与路由索引
Section titled “周期性班次、连接条件与路由索引”三项路由功能让大网络上的规划保持便宜:
- 周期性班次(
TraversalModel::Recurring { period, offsets, duration })描述一张重复的时刻表:从纪元起每隔一个period,在列出的每个偏移时刻(1 到 4,096 个,严格递增,都在一个周期之内)各发一班,每班耗时duration。六小时一班的渡船只需一个模式,不必为每次航行写一个班次,十四个月的时刻表也就不再在每份快照里为每条连接占 1,700 个班次。某时刻之后的下一班按整数分钟精确算出,连接的可用窗口限定航季。TraversalModel::next_departure(at)对每种按班次的模型回答同一个问题。 - 连接条件(
EdgeConditionOverlay)是作用于一条连接的半开时间窗口,附带一种效果:Closed表示封闭,旅行者等窗口结束后的第一班;Adjusted则按比例放大窗口内出发的时长和资源成本(向上取整),并在给出时替换风险。洪水、封冻的河流、泥泞季节都是条件,不必重建网络。你先构建一个校验过的EdgeConditionSet(最多 65,536 条,每条连接最多 1,024 条,ID 唯一;同一连接上的封闭会合并,同一连接上的两条调整不得重叠,否则以RoutingError::InvalidConditions拒绝),再传给RoutingIndex::plan_route_with_conditions。条件按出发时刻应用,不影响已经在途的通行。指向网络中不存在的连接的条目不起作用,所以一个条件集可以服务多个观察者的网络;unmatched_conditions会列出这些条目,拼错的 ID 也就能查出来。计划把条件集的摘要记在RoutePlan.edge_conditions_digest中(没有应用条件时为空)。 - 路由索引(
RoutingIndex::new(snapshot))把规划快照编译一次:网络只校验一次、只哈希一次,邻接表是稠密的,放在Arc后共享,所以索引复制起来很便宜,而且是Send + Sync。为每个网络版本保留一个索引,用reobserve(topology_version, timetable_version, observer, observed_at, valid_until, knowledge_cut)把它移到新的读取切面(只哈希切面,版本与索引不符时拒绝),然后通过它规划:一次查询只剩搜索本身的开销,约为冷启动plan_route的百分之一。plan_route的签名不变,每次调用临时编译一个借用的索引,它本身也比以前更快。通过索引得到的计划和摘要,与在同一快照上调用plan_route完全相同。RoutingCache::index_key(index, conditions, request)不哈希网络就能给缓存的计划生成键。
plan_route 和 RoutingIndex::new 会拒绝 RoutingNetwork::new 也会拒绝的手工拼装的网络(重复 ID、未知端点、畸形的通行模型),所以请在把连接载荷当作知识发布之前先校验。路线在每个节点都尽早出发,不会主动等待;一条调整可能让一条连接不再满足 FIFO,这与 Piecewise 连接同属一类。日历月不是固定周期,请改用班次,或为每种月长各写一个模式。
知识与可见性
Section titled “知识与可见性”规划只读取快照。PlanningSnapshot 写明观察者和 knowledge_cut,RoutePlan.planning_snapshot_digest 则把每份计划绑定到它所依据的快照。有两个辅助函数可以构建快照:
canwu_correspondence::planning_snapshot_from_holder_knowledge构建持有人规划快照,只收录某个持有人知识账本中断言的端点和连接,同时返回一个KnowledgeReadCutDigest,作为规划读取了哪些事实的证据。见按持有人知识规划路线。canwu_reference_world::planning_snapshot_from_world根据参考世界完整的路线表构建快照,输入是受信任的WorldSnapshot。它适合示例和工具;为某个角色规划时,应当从该角色自己的知识出发。
要在受信任的工具中显示一次派发的计划路线,请读取 canwu-correspondence 规划的路段;它们是真值。受信任的上层应用适配器用 typed_domain_record(&run, &correspondence_operation_ref(key)) 读取它们,再用 decode_payload::<CorrespondenceOperationRecord>() 解码载荷:route_plan.legs 记录每个路段的连接、节点、交通方式,以及计划出发和到达时间。派发不会向发送者或承运人发布任何内容,所以这次读取的结果绝不能原样展示给角色;CanwuViewer 也没有读取领域记录的方法。要得到角色的视角,请从该角色自己的知识出发重新规划(对 planning_knowledge_query() 的查询结果调用 planning_snapshot_from_knowledge_result,再调用 plan_route)。只有当角色所知与承运人规划时所依据的信息相同时,结果才与插件的计划一致。
移动报告是持有人相对的。每份报告都是一条 canwu.movement/movement_report 知识记录,载荷为 MovementReportV1:
| 持有人 | 角色 | 报告内容 |
|---|---|---|
| 下令的持有人(须为人物) | Owner |
当前阶段和各路段,外加详情:运输执行状态、修订原因、失败、交接、在持的预订,当前路段上的滞留(MovementLegHoldV1:种类、排定时刻、until、原因、设置时间和是否顺延后续路段)、行程顺延量,以及各预订的撤销时间和原因 |
移动命令中的 operator |
Operator |
与所有者相同 |
remote_observers 中的每一项 |
DelayedRemote |
按授权延迟回溯后的阶段、路段和最后已知端点,不含详情 |
| 其他人 | 无 | 收不到任何报告 |
插件只在报告摘要变化时发布,而且只发给在世的人物持有人,所以作为所有者的机构收不到报告。observed_as_of 是报告所反映的最新事实的时间。延迟的远方观察者只能从路段的出发或到达时间得知滞留。所有者和操作者的报告置信度按千分制记为 1,000,并引用运行时记录的版本;延迟报告的置信度记为 700,不引用任何证据,因此发布时间不会透露运行时最近何时变化。
客户端通过 viewer_for_actor 得到持有人的角色相对视图,再用 query_knowledge 读取报告,schema 取自 movement_report_knowledge_schema_id()。movement_state(&canwu) 返回整个运行时,只应在受信任的上层应用代码中使用。
随机性、持久化与重放
Section titled “随机性、持久化与重放”这三个 crate 都不抽随机数。连接上的 risk_per_mille 是规划成本:路由器沿路线把它累加,再用 max_risk_per_mille 限制总和,没有任何代码据此掷骰。渡口是否遭洪水、盗匪是否出现、驻军是否扣押信使,都由你的系统决定,通常是在自己声明的随机流上做一次操作定址随机抽样,再把结果作为事故报告。canwu-correspondence 的做法不同:它在自己的随机流 canwu-correspondence / operation-resolution / 1 上抽取事故,概率 probability_per_mille 由你的应用提供。
持久化的内容:
MovementState是一条领域记录,种类为canwu.movement/runtime,ID 为canwu.movement:runtime。MovementState::into_initial_record可以为场景生成这条记录,例如在运行开始前装好运力池。激活时,插件最多接受一个规范编码的运行时根记录。根记录中已出发的路段,到达的到期时间必须是它的排定时刻(出发时间加计划时长),或者带一个until等于该到期时间的滞留;尚未出发而被滞留的路段按同样的方式检查:待处理的出发到期必须等于滞留的until,until不早于滞留记下的排定时刻。不满足这一点的根记录在载入时被拒绝,违反这条规则的恢复后运行时也一样。- 待处理的路段到期输入和报告唤醒都是排队中的规范化输入,所以快照会带上它们,载入后的运行可以从行程中途继续。
- 已关闭的运输执行会从运行时退休。它的移动命令和运输执行标识仍然保留、不会复用,已消耗的运力也仍计入运力池。已发布的报告留在持有人的知识中,领域记录历史保留运行时的旧版本。
- 运输值是 serde 数据,保存在其所有者的记录中。
TRANSPORT_SEMANTIC_VERSION为canwu-transport.v5。HandoffKind::Planned类型的交接在 JSON 中省略类型字段,所以旧的交接记录保持原有编码。 RoutePlan记录ROUTING_ALGORITHM_VERSION(canwu-routing.v2:对每个有效网络,路线、成本和拒绝结果与 v1 相同,但快照摘要和计划摘要由RoutingNetwork::digest组合而成,与 v1 不同)、策略版本、快照摘要,以及所应用的连接条件的摘要。RoutingCache是派生数据,随时可以丢弃重建,重放也用不到它。
精确重放把记录下来的命令、事故和到期输入重新送入同样的系统,得到同样的运行时。恢复和重放都要求插件名称、版本和语义哈希(MOVEMENT_SEMANTIC_HASH)一致。
上层应用需要提供什么
Section titled “上层应用需要提供什么”- 网络和时刻表数据,以及每次规划所用的规划快照。
- 每条移动命令中的
RoutePlan,以及每次改道中新的ItineraryRevision。 - 传给
MovementPlugin::new的记录种类:权限记录、投递尝试、外部条件记录和事故证据。 - 权限依据记录。所有者要移动自己以外的对象时,须在
authority_basis中引用一条已声明种类记录的当前版本,该记录的引用以movement_grantee(MOVEMENT_GRANTEE_ROLE)指明所有者,以movement_subject(MOVEMENT_SUBJECT_ROLE)指明其他每个对象。 - 事故、危险、敌对行为及其背后的随机抽样,通过
movement_incident_v1报告。扣押交接和投递核对只接受这条路径。 - 运力:谁用
OfferPool提供哪个运力池,以及每项预订的优先级和平局决胜键。 - 位置真值和保管权的含义。到达后更新军队位置或信件持有人,是你的世界模型的职责。
- 展示:把报告变成通知、地图或预计到达时间。
RoutingPolicy::default() 把 max_expanded_nodes 设为 10,000,max_transfers 设为 64,路线累计风险上限 max_risk_per_mille 设为 1,000。单条连接的 risk_per_mille 不能超过 1,000。NoKnownRoute、ExpansionBudgetExceeded、SearchHorizonExceeded 等搜索失败都是确定性结果,重放会得到相同的失败。一个周期性模式最多列出 MAX_RECURRING_OFFSETS(4,096)个偏移;一个条件集最多 MAX_EDGE_CONDITIONS(65,536)条,每条连接最多 MAX_EDGE_CONDITIONS_PER_CONNECTION(1,024)条,调整的 duration_per_mille 取值 1 到 MAX_CONDITION_PER_MILLE(1,000,000)。Departures 的班次数没有上限,RoutingCache 也不限大小。
MovementLimitsV1::CURRENT 限定移动运行时:
| 限额 | 值 |
|---|---|
命令或事故载荷(operation_bytes) |
8,192 字节 |
操作键、标签和原因(text_bytes) |
256 字节 |
| 每条移动命令的对象数 | 32 |
| 每条移动命令的报告授权数(含所有者和操作者) | 16,因此最多 14 个远方观察者 |
| 每个运输执行的路段、行程修订和交接数 | 64、8、16 |
| 每个运输执行和整个运行时同时在持的预订数 | 64、16,384 |
| 每个运行时、每个所有者的移动命令数 | 1,024、256 |
| 每个运行时的运力池数 | 256 |
| 每个边界的报告数和报告持有人数 | 1,024、64 |
远方观察者的延迟上限(max_report_delay_minutes) |
365 天 |
| 每个运行时、每个持有人的操作结果数 | 16,384、1,024 |
为事故保留的结果余量(incident_outcome_headroom) |
1,024 |
| 不再被活跃运输执行需要的操作结果的保留时长 | 7 天 |
超出移动命令、运输执行或运力池限额的操作会以 LimitExceeded 被拒绝。操作结果预算用完后,命令处理器在准入时拒绝新命令,仍然进入结算的操作会发出 canwu.movement.operation_dropped.v1。超过知识载荷上限的报告不会发布,插件改为发出 canwu.movement.report_withheld.v1。超出单个边界预算的报告等待同一模拟时间的报告唤醒。已关闭的运输执行在每个在世观察者都收到最终报告后退休,最迟不超过关闭后 365 天。
从同一个寄信人的知识出发,规划一封本地信和一封寄往北京的信:
cargo run -p canwu-correspondence --example routed_correspondence在一条带快车连接的 512 节点铁路线上测量路由器耗时。程序打印节点数、连接数、十次规划的耗时(毫秒)和选中的路线:256 个路段,第 766 分钟到达。
cargo run -p canwu-routing --example routing_scale通过对外 API 驱动移动插件。这个测试覆盖按优先级分配运力池、因缺少运力而失败的路段、伏击事故、引用外部条件绕开洪水的改道、扣押、投递核对、持有人相对的报告、退休、行程中途的保存与载入,以及精确重放:
cargo test -p canwu-movement --test gap_g19_movement_lifecycle_plugin让已出发的路段滞留,缩短和解除滞留,让滞留中的路段失败,并检查在封存过和未封存的运行中,事故证据和投递尝试都必须是当前版本:
cargo test -p canwu-movement --test gap_g64_movement_delay_leg单独检查运输记录:纯函数运力分配、人员群体、扣押交接和外部条件:
cargo test -p canwu-transport --test gap_g20_transport_capacity_pool_allocation --test gap_g21_g23_transport_records完整讲解见路线规划与送信执行(规划与记录契约)和从无锡发出、按路线投递的通信(逐步讲解信件示例)。移动军队运行的是参考世界的移动插件。
- 结算系统:阶段、资源分配与回滚
- 随机性:第一方扩展在哪里抽样,哪些抽样留给你
- 模型归属:与移动相关的各项职责归哪一层
- 仓库架构文档中的 Routing and transport execution 一节
canwu-movementREADME:操作规则与错误码- 路线与运输提案:所有权边界与里程碑