Phased boundary and allocation
In this example a supply plugin offers 10 grain and a demand plugin asks for 6 in the same daily boundary. Canwu settles the allocation, and a later phase of that boundary records how much grain the garrison received. You will learn how boundary phases order work between plugins and how a system reads the settled allocation.
A boundary is one atomic settlement step: Canwu runs the registered systems in 14 fixed phases, then commits all of their changes or rolls all of them back. Settlement system lists the phases.
Open phased_boundary.rs
Browse the example folder
Run it
Section titled “Run it”From the repository root:
cargo run -p canwu-api --example phased_boundaryThe example prints nothing. It exits with status 0 when both of its assertions pass: the request was granted 6 grain, and the boundary emitted two events.
How the plugins collaborate
Section titled “How the plugins collaborate”View diagram source
flowchart LR
Offer["example-supply / offer<br/>phase 6: offer 10 grain"] --> Allocate["Canwu allocates<br/>end of phase 6"]
Request["example-demand / request<br/>phase 6: request 6 grain"] --> Allocate
Allocate --> Apply["example-demand / apply<br/>phase 7: read grant, write component"]
Apply --> Commit["Validate and commit<br/>phases 8 and 9"]
1. Offer supply in phase 6
Section titled “1. Offer supply in phase 6”SupplyPlugin registers a boundary system named offer in BoundaryPhase::ReservationAndAllocation (phase 6) with a daily cadence:
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 returns a ReservationOffer of 10 units for the pool grain_pool(): resource grain at territory 1 under the logistics/grain state key. The contract’s reservation_offers must name that state key.
2. Request grain in the same phase
Section titled “2. Request grain in the same phase”DemandPlugin registers request in the same phase. request_grain returns:
ReservationRequest { request: "daily-grain".to_owned(), pool: grain_pool(), quantity: 6, priority: 10, tie_break: "western-garrison".to_owned(),}At the end of phase 6 Canwu allocates each pool. Requests with higher priority go first; equal priorities are ordered by tie_break, then by request identity. Each request gets as much as remains, up to its quantity. The result is the same regardless of the order in which plugins registered.
3. Read the grant in phase 7
Section titled “3. Read the grant in phase 7”DemandPlugin also registers apply in BoundaryPhase::DomainDeltaProposal (phase 7). Its contract declares the reservation it reads, the state it writes, and the event it emits:
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 reads the settled result with view.reservation(&reservation) and returns two directives: SetComponent, which stores allocation.granted on territory 1, and Emit, which records a grain_allocated event.
4. Settle the boundary
Section titled “4. Settle the boundary”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 runs every system whose cadence matches the request. The receipt and the stored BoundaryRecord keep the offers, requests, and allocations as evidence. The two emissions are the daily_grant_changed event from SetComponent and the grain_allocated event from Emit.
When to use a boundary
Section titled “When to use a boundary”Use boundary systems when several systems must act at the same simulation time in a set order, such as daily settlement or turn resolution. A single player action fits a command; see Command plugin.
Try next
Section titled “Try next”Replace the first assertion with a loop that prints each allocation and the event log:
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 Fulfilledexample-demand.daily_grant_changed Granted 6 grain to the western garrisonexample-demand.grain_allocated The daily grain allocation settledNow change quantity: 6 to quantity: 12 in request_grain. The pool holds 10, so the request is partly filled:
requested=12 granted=10 remaining=0 Partialexample-demand.daily_grant_changed Granted 10 grain to the western garrisonexample-demand.grain_allocated The daily grain allocation settled