Skip to main content

phased_boundary/
phased_boundary.rs

1#![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}