跳转到内容

阶段边界与资源分配

本示例中,供给插件提供 10 单位粮食,需求插件在同一个每日边界中申请 6 单位。参伍完成分配后,同一边界的后续阶段记录守军实际得到的粮食。你会了解边界阶段如何为不同插件的工作排序,以及系统如何读取已结算的分配结果。

边界是一次原子结算:参伍按 14 个固定阶段运行已注册的系统,然后要么提交全部变化,要么全部回滚。各阶段见结算系统。

打开 phased_boundary.rs

浏览示例目录

在仓库根目录运行:

Terminal window
cargo run -p canwu-api --example phased_boundary

示例没有任何输出。两条断言都通过时程序以状态码 0 退出:需求得到 6 单位粮食,边界产生两个事件。

第 6 阶段声明供给和需求,参伍完成分配,第 7 阶段写入分配结果,然后提交. 查看图表源码.
查看图表源码
flowchart LR
  Offer["example-supply / offer<br/>第 6 阶段:提供 10 单位粮食"] --> Allocate["参伍分配<br/>第 6 阶段末"]
  Request["example-demand / request<br/>第 6 阶段:申请 6 单位粮食"] --> Allocate
  Allocate --> Apply["example-demand / apply<br/>第 7 阶段:读取分配结果并写入组件"]
  Apply --> Commit["校验并提交<br/>第 8、9 阶段"]

SupplyPlugin 在 BoundaryPhase::ReservationAndAllocation(第 6 阶段)注册一个名为 offer、按日执行的边界系统:

let mut contract = BoundarySystemContract::new(
"offer",
BoundaryPhase::ReservationAndAllocation,
SystemCadence::Daily,
);
contract.reservation_offers = vec![StateKey::new("logistics", "grain")];
registrar.register_boundary_system(contract, offer_grain)

offer_grain 为 grain_pool() 返回一个 10 单位的 ReservationOffer。这个池是领地 1 上、挂在 logistics/grain 状态键下的 grain 资源。契约的 reservation_offers 必须列出这个状态键。

DemandPlugin 在同一阶段注册 request,request_grain 返回:

ReservationRequest {
request: "daily-grain".to_owned(),
pool: grain_pool(),
quantity: 6,
priority: 10,
tie_break: "western-garrison".to_owned(),
}

第 6 阶段结束时,参伍逐个资源池进行分配:priority 高的申请先分;优先级相同时按 tie_break 排序,再按申请标识排序。每个申请最多拿到它的 quantity,池中剩多少就给多少。插件的注册顺序不影响分配结果。

DemandPlugin 还在 BoundaryPhase::DomainDeltaProposal(第 7 阶段)注册了 apply。它的契约声明要读取的预留、要写入的状态和要产生的事件:

apply.writes = vec![StateKey::new("garrison", "grain")];
apply.emits = vec!["grain_allocated".to_owned()];
apply.reservation_reads = vec![ReservationRef::new(
"example-demand",
"request",
"daily-grain",
)];

apply_grain 通过 view.reservation(&reservation) 读取结算结果,返回两条指令:SetComponent 把 allocation.granted 存到领地 1 上,Emit 记录一个 grain_allocated 事件。

let mut canwu = Canwu::demo(35)?;
canwu.register_plugin(&SupplyPlugin)?;
canwu.register_plugin(&DemandPlugin)?;
let receipt = canwu
.settle_boundary(BoundaryRequest::at(canwu.time()).with_cadence(SystemCadence::Daily))?;
assert_eq!(receipt.allocations[0].granted, 6);
assert_eq!(canwu.boundaries()[0].emissions.len(), 2);

settle_boundary 运行执行频率与请求相符的所有系统。回执和保存下来的 BoundaryRecord 都记录了供给、申请和分配结果,作为证据。两个事件分别是 SetComponent 产生的 daily_grant_changed 和 Emit 产生的 grain_allocated。

多个系统需要在同一模拟时间按固定顺序协作时,例如日结或回合结算,就使用边界系统。单个玩家动作用命令即可,见命令插件。

把第一条断言换成下面的循环,打印每项分配结果和事件日志:

for allocation in &receipt.allocations {
println!(
"requested={} granted={} remaining={} {:?}",
allocation.requested, allocation.granted, allocation.remaining_after, allocation.disposition
);
}
for event in canwu.events() {
println!("{} {}", event.kind.qualified_event_type(), event.summary);
}
requested=6 granted=6 remaining=4 Fulfilled
example-demand.daily_grant_changed Granted 6 grain to the western garrison
example-demand.grain_allocated The daily grain allocation settled

再把 request_grain 中的 quantity: 6 改为 quantity: 12。池中只有 10 单位,申请只能部分满足:

requested=12 granted=10 remaining=0 Partial
example-demand.daily_grant_changed Granted 10 grain to the western garrison
example-demand.grain_allocated The daily grain allocation settled