路线规划与送信执行
本页跟随一封从无锡寄往北京的信。你会运行一次经路线规划的投递,了解 canwu-routing 如何依据某个观察者掌握的信息选择路线,以及 canwu-transport 和 canwu-movement 如何逐段记录行程,包括失败和改道。
网络和时刻表数据由你的应用提供。canwu-routing 和 canwu-transport 是普通的 Rust 库,负责计算和校验数据;你的插件把这些数据存为领域记录,并通过规范化输入(canonical ingress)修改它们。两者的类型都由 canwu-api 重新导出。
通信示例为同一位寄信人规划两次投递,一次寄给无锡本地的邻居,一次寄往北京:
cargo run -p canwu-correspondence --example routed_correspondenceWuxi local delivery: 1 leg(s), arrival minute 30 wuxi/hub -> wuxi/delivery/recipient via HorseWuxi to Beijing: 2 leg(s), arrival minute 3060 wuxi/hub -> beijing/station via Rail beijing/station -> beijing/delivery/recipient via Horse寄信人知道的网络定义在 examples/support/mod.rs。本地信件只需一段 30 分钟的骑马路段。寄往北京时,路由器比较了直达列车(第 60 分钟发车,历时两天)、经南京中转的列车(三天),以及两条最后一段的骑马路线(两小时和三小时),最终选中直达列车和两小时的路线:60 + 2,880 + 120 = 第 3,060 分钟。从无锡出发的经路线规划通信完整讲解了这个示例;本页解释它底层的规划与执行契约。
“一封信怎样从无锡送到北京?”其实是两个问题:
- 规划:在这位观察者知道的网络、时刻表、风险和运力范围内,哪条路线能在限制条件下最早到达?
- 执行:信是否已经出发、到达中转站、交接给下一位承运人、因洪水改道,并最终送到收件人手里?
canwu-routing 回答第一个问题,canwu-transport 记录第二个问题的答案。收件人的知识只在信息扩展完成投递尝试时才会改变。
查看图表源码
flowchart LR
Snap["PlanningSnapshot<br/>观察者掌握的信息"] --> Plan["plan_route()<br/>RoutePlan"]
Plan --> Install["TransportExecution<br/>初始 ItineraryRevision"]
Install --> Book["CapacityBooking<br/>(可选)"]
Book --> Legs["路段:出发、到达<br/>中转站 Handoff"]
Legs -->|"路段失败"| Replan["ReplanPending"]
Replan -->|"新的规划快照"| Reroute["后继<br/>ItineraryRevision"]
Reroute --> Legs
Legs -->|"最后一段到达"| Done["ArrivalPending<br/>或 Settled"]
let request = RoutingRequest { origin: RoutingNodeRef::new("wuxi/hub"), destination: RoutingNodeRef::new("beijing/delivery/recipient"), departure_at: SimTime::EPOCH + SimDuration::hours(1), policy: RoutingPolicy { allowed_modes: [TransferMode::Rail, TransferMode::Horse] .into_iter() .collect(), ..RoutingPolicy::default() },};
let plan = plan_route(&planning_snapshot, &request)?;PlanningSnapshot 是某位观察者在某一时刻所知道的网络,记录了 observer、observed_at、knowledge_cut、topology_version、可选的 timetable_version 和可选的过期时间 valid_until。手握最新铁路时刻表的人和只听过驿站传闻的人会得到不同的计划,而这两份计划都符合各自掌握的信息。有两个辅助函数可以构建规划快照:
canwu_reference_world::planning_snapshot_from_world根据参考世界的领地和道路构建。canwu_correspondence::planning_snapshot_from_holder_knowledge只根据某个持有人账本中记录的内容构建,见按持有人知识规划路线。
得到的 RoutePlan 包含若干 legs(每段都有运输方式和计划出发、到达时间),以及 estimated_arrival_at 和 digest。近处的收件人可能只需要一小段路;去北京则可能要经过多个驿站、车站和保管权交接。
运输方式与时刻表
Section titled “运输方式与时刻表”铁路、空运、电报和驿站共用一套算法,差别都放在网络数据里:
- 铁路:
TransferMode::Rail、车站节点和发车班次。1900 年与 1940 年的铁路网是两个时刻表不同的内容包。 - 空运:
TransferMode::Air、机场和航班。 - 电报等信号:
TransferMode::Signal。电报局的营业时间、延迟、风险和截获规则来自数据或你自己扩展的策略。 - 驿站网络:中继站节点、
Horse或Foot连接,以及路段之间的保管权交接。
每条连接都有一个 TraversalModel:Fixed 表示固定时长,Departures 表示按班次发车,Piecewise 表示随时间变化的时长。默认算法 FifoDijkstraV1 假设晚出发就不会早到达(FIFO)。如果你的时变数据打破了这个前提,请在策略中设置 algorithm: RoutingAlgorithm::BoundedLabelCorrectingV1。
RoutingPolicy 还可以设置 max_expanded_nodes、max_transfers、max_risk_per_mille 和 max_arrival_at。NoKnownRoute、ExpansionBudgetExceeded 等失败都是确定性的结果,重放时会原样重现。
RoutingCache 以规划快照摘要和请求(含策略)为键缓存计划。它是上层应用自己的缓存,可以随时丢弃重建,重放也用不到它。
预计到达时间与投递期限
Section titled “预计到达时间与投递期限”RoutePlan.estimated_arrival_at 是执行层面的预估;投递尝试的 due_at 是信息生命周期中的期限。预计会晚到时,系统记录一次失败或创建新的投递尝试,原来的期限保持不变。
改道和投递重试是两种操作:
- 改道为同一次投递尝试创建新的不可变
ItineraryRevision。 - 投递重试创建新的投递尝试,可以换用新的路线和运输方式。
规划完成后,上层应用创建 TransportExecution,把计划安装为第一个行程修订,并关联它要完成的投递尝试:
let mut execution = TransportExecution::new( TransportExecutionId(7), Some(delivery_attempt_version.clone()),);execution.install_initial_itinerary(initial_revision)?;execution.begin_saga( delivery_attempt_version, delivery_completion_operation_key( TransportExecutionId(7), ItineraryRevisionId(1), 1, ),)?;delivery_attempt_version 是投递尝试的精确领域记录版本(DomainRecordVersionRef);initial_revision 是包含 RoutePlan、原因为 ItineraryRevisionReason::Initial 的 ItineraryRevision。begin_saga 把运输执行切换到 Executing 状态。
之后各路段依次执行:
start_current_leg(at)出发,complete_current_leg(at, endpoint)到达,每段都记录实际时间。- 到达中转站后,先用
record_handoff记录保管权由谁交给谁,再开始下一段。 - 洪水、战乱、恶劣天气或线路中断阻断路线时,领域系统调用
fail_current_leg(reason, at),运输执行进入ReplanPending。基于新的规划快照得到后继修订,用reroute(revision, at)安装,再用record_handoff记录保管权从失败路段交到新修订的第一段。路由器只负责规划,灾害是否发生由你的系统决定。 - 最后一段到达后,运输执行进入
ArrivalPending。completion_request()返回一个DeliveryCompletionRequest,其中带有稳定的操作键和证据。上层应用通过规范化输入提交它,完成信息扩展中的投递尝试。同一个键提交两次,投递尝试也只完成一次。
对运输执行 7、行程修订 1 和投递尝试版本 1,操作键为 transport/7/revision/1/delivery-completion/attempt-version/1。没有关联投递尝试的运输执行用 settle_arrival(at, endpoint) 结束,状态为 Settled。
人员群体、扣押与外部条件
Section titled “人员群体、扣押与外部条件”以下记录覆盖了策略游戏中常见的移动场景:
MovementSubjectRole::PersonsGroup表示人员群体:把一群人作为一个移动主体,其身份通常是应用的领域记录。它和货物一样带正整数数量,这里是人数;对这两种角色,requires_quantity()都返回true。HandoffKind::Seizure { by }记录扣押交接:行程之外的人夺走了保管权,例如盗匪或敌方驻军。计划交接使用HandoffKind::Planned。扣押交接与计划交接遵循相同的路段规则。- 终态扣押把当前失败的路段同时作为起止两端,保管权随之离开行程。每个运输执行只允许一次终态扣押;此后它不再接受交接、路段失败、改道、到达、成功结算或预订,只能由所有者以失败或取消关闭。
ItineraryRevisionReason::ExternalCondition { record, version, kind }用外部条件说明改道:引用应用自有的条件记录(例如洪水或封关记录)的某个正整数精确版本,并在kind中给出应用标签。初始行程和每次改道都会校验它。
运输层只记录这些事实。扣押或灾害是否发生、由谁授权,都由你的应用系统决定。
移动生命周期与运力池
Section titled “移动生命周期与运力池”canwu-transport 保存记录,移动生命周期扩展 canwu-movement 则通过一个 MovementPlugin 推进这些记录:上层应用用带请求标识的命令 apply_movement_operation_v1 下达移动命令,应用系统通过 movement_incident_v1 报告事故,插件在各路段到期时结算,分配运力池,并发布持有人相对的报告。插件自己不抽取随机数,危险是否发生由你的系统决定。移动命令、路段、事故、预订和报告的完整规则见设计页面的运行过程。
车次、航班、驿马和电报线路都可以用 CapacityBooking 预订。它是按时间窗口持久保存的记录,状态为 Requested、Confirmed、Consumed、Released、Expired、Cancelled 或 Failed 之一。预订可以在窗口开始前确认、失败或取消,但只能在窗口内消耗。
移动执行还可以使用运力池,见运力池。通信扩展目前只提供 CorrespondenceCapacityAdmission::Unconstrained,因此通信的路段不受运力限制。