phased_boundary/
phased_boundary.rs1#![allow(clippy::unnecessary_wraps)]
2
3use canwu_api::{
4 BoundaryContext, BoundaryDirective, BoundaryPhase, BoundaryProposal, BoundaryRequest,
5 BoundarySystemContract, Canwu, CanwuError, EntityRef, ErrorCode, ReservationOffer,
6 ReservationPoolKey, ReservationRef, ReservationRequest, SimulationPlugin, SimulationView,
7 StateKey, StateVisibility, SystemCadence, TerritoryId,
8};
9use serde_json::Value;
10
11fn grain_pool() -> ReservationPoolKey {
12 ReservationPoolKey::new(
13 StateKey::new("logistics", "grain"),
14 EntityRef::Territory(TerritoryId::new(1)),
15 "grain",
16 )
17}
18
19fn offer_grain(
20 _view: &SimulationView<'_>,
21 _context: &BoundaryContext,
22) -> Result<BoundaryProposal, CanwuError> {
23 Ok(BoundaryProposal {
24 offers: vec![ReservationOffer {
25 pool: grain_pool(),
26 capacity: 10,
27 }],
28 ..BoundaryProposal::default()
29 })
30}
31
32fn request_grain(
33 _view: &SimulationView<'_>,
34 _context: &BoundaryContext,
35) -> Result<BoundaryProposal, CanwuError> {
36 Ok(BoundaryProposal {
37 requests: vec![ReservationRequest {
38 request: "daily-grain".to_owned(),
39 pool: grain_pool(),
40 quantity: 6,
41 priority: 10,
42 tie_break: "western-garrison".to_owned(),
43 }],
44 ..BoundaryProposal::default()
45 })
46}
47
48fn apply_grain(
49 view: &SimulationView<'_>,
50 _context: &BoundaryContext,
51) -> Result<BoundaryProposal, CanwuError> {
52 let reservation = ReservationRef::new("example-demand", "request", "daily-grain");
53 let allocation = view.reservation(&reservation)?.ok_or_else(|| {
54 CanwuError::new(
55 ErrorCode::InvalidBoundary,
56 "the declared grain reservation did not produce allocation evidence",
57 )
58 })?;
59 let territory = EntityRef::Territory(TerritoryId::new(1));
60 Ok(BoundaryProposal {
61 directives: vec![
62 BoundaryDirective::SetComponent {
63 state: StateKey::new("garrison", "grain"),
64 entity: territory.clone(),
65 component: "daily_grant".to_owned(),
66 value: Value::from(allocation.granted),
67 summary: format!(
68 "Granted {} grain to the western garrison",
69 allocation.granted
70 ),
71 },
72 BoundaryDirective::Emit {
73 event_type: "grain_allocated".to_owned(),
74 summary: "The daily grain allocation settled".to_owned(),
75 affected: vec![territory],
76 },
77 ],
78 ..BoundaryProposal::default()
79 })
80}
81
82struct SupplyPlugin;
83
84impl SimulationPlugin for SupplyPlugin {
85 fn name(&self) -> &'static str {
86 "example-supply"
87 }
88
89 fn version(&self) -> &'static str {
90 "1.0.0"
91 }
92
93 fn semantic_hash(&self) -> &'static str {
94 "5fac3fb17e5f6fc4a3c85354908d84405bb569b5816631a91f70b759c156da07"
95 }
96
97 fn register(&self, registrar: &mut canwu_api::PluginRegistrar<'_>) -> Result<(), CanwuError> {
98 let mut contract = BoundarySystemContract::new(
99 "offer",
100 BoundaryPhase::ReservationAndAllocation,
101 SystemCadence::Daily,
102 );
103 contract.reservation_offers = vec![StateKey::new("logistics", "grain")];
104 registrar.register_boundary_system(contract, offer_grain)
105 }
106}
107
108struct DemandPlugin;
109
110impl SimulationPlugin for DemandPlugin {
111 fn name(&self) -> &'static str {
112 "example-demand"
113 }
114
115 fn version(&self) -> &'static str {
116 "1.0.0"
117 }
118
119 fn semantic_hash(&self) -> &'static str {
120 "331b865359d4ac4a14c9bf90da665b82b4aa65ee63c195e331d8f1fa181e7029"
121 }
122
123 fn register(&self, registrar: &mut canwu_api::PluginRegistrar<'_>) -> Result<(), CanwuError> {
124 let mut request = BoundarySystemContract::new(
125 "request",
126 BoundaryPhase::ReservationAndAllocation,
127 SystemCadence::Daily,
128 );
129 request.reservation_requests = vec![StateKey::new("logistics", "grain")];
130 registrar.register_boundary_system(request, request_grain)?;
131
132 let mut apply = BoundarySystemContract::new(
133 "apply",
134 BoundaryPhase::DomainDeltaProposal,
135 SystemCadence::Daily,
136 );
137 apply.writes = vec![StateKey::new("garrison", "grain")];
138 apply.emits = vec!["grain_allocated".to_owned()];
139 apply.reservation_reads = vec![ReservationRef::new(
140 "example-demand",
141 "request",
142 "daily-grain",
143 )];
144 apply.visibility = StateVisibility::SameBoundary;
145 registrar.register_boundary_system(apply, apply_grain)
146 }
147}
148
149fn main() -> Result<(), CanwuError> {
150 let mut canwu = Canwu::demo(35)?;
151 canwu.register_plugin(&SupplyPlugin)?;
152 canwu.register_plugin(&DemandPlugin)?;
153
154 let receipt = canwu
155 .settle_boundary(BoundaryRequest::at(canwu.time()).with_cadence(SystemCadence::Daily))?;
156 assert_eq!(receipt.allocations[0].granted, 6);
157 assert_eq!(canwu.boundaries()[0].emissions.len(), 2);
158 Ok(())
159}