Skip to main content

Canwu

Struct Canwu 

Source
pub struct Canwu { /* private fields */ }
Expand description

Main in-process API. All returned world values are detached snapshots.

Implementations§

Source§

impl Canwu

Source

pub const fn version() -> &'static str

Source

pub fn new(seed: u64, scenario: Scenario) -> Result<Self, CanwuError>

Source

pub fn into_compacted(self) -> Result<CompactedCanwu, CanwuError>

Enters the explicit compact-journal interface without discarding evidence.

Source

pub fn new_with_plugins( seed: u64, scenario: Scenario, plugins: &[&dyn SimulationPlugin], ) -> Result<Self, CanwuError>

Source

pub fn new_with_manifest( seed: u64, scenario: Scenario, run_manifest: RunManifest, ) -> Result<Self, CanwuError>

Source

pub fn new_with_manifest_and_plugins( seed: u64, scenario: Scenario, run_manifest: RunManifest, plugins: &[&dyn SimulationPlugin], ) -> Result<Self, CanwuError>

Source

pub fn new_with_run_configuration( seed: u64, scenario: Scenario, run_manifest: RunManifest, run_configuration: RunConfiguration, ) -> Result<Self, CanwuError>

Source

pub fn new_with_run_configuration_and_plugins( seed: u64, scenario: Scenario, run_manifest: RunManifest, run_configuration: RunConfiguration, plugins: &[&dyn SimulationPlugin], ) -> Result<Self, CanwuError>

Source

pub fn demo(seed: u64) -> Result<Self, CanwuError>

Examples found in repository?
examples/phased_boundary.rs (line 150)
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}
More examples
Hide additional examples
examples/plugin.rs (line 89)
88fn main() -> Result<(), CanwuError> {
89    let mut canwu = Canwu::demo(35)?;
90    let ids = Canwu::demo_ids();
91    canwu.register_plugin(&StancePlugin)?;
92    canwu.submit(CommandEnvelope::new(
93        Issuer::Actor(ids.commander),
94        Command::Plugin {
95            plugin: "example-stance".to_owned(),
96            command: "set_stance".to_owned(),
97            payload: json!({ "army": ids.army, "stance": "hold" }),
98        },
99    ))?;
100    Ok(())
101}
examples/continuous_game_loop.rs (line 182)
181    fn new(verbose: bool) -> Result<Self, CanwuError> {
182        let authority = Canwu::demo(35)?;
183        let ids = Canwu::demo_ids();
184        let presentation = PresentationState::from_world(&authority.world(), ids.army);
185        Ok(Self {
186            authority,
187            ids,
188            presentation,
189            accumulated_sim_nanos: 0,
190            wall_elapsed: Duration::ZERO,
191            frame: 0,
192            reported_events: 0,
193            next_progress_milestone: 25,
194            canonical_boundaries: 0,
195            verbose,
196        })
197    }
examples/move_army.rs (line 4)
3fn main() -> Result<(), Box<dyn std::error::Error>> {
4    let mut canwu = Canwu::demo(35)?;
5    let ids = Canwu::demo_ids();
6
7    canwu.act(
8        ids.commander,
9        SemanticAction::MoveArmy {
10            army: ids.army,
11            destination: ids.eastern_territory,
12        },
13    )?;
14    let arrival_events = canwu.advance(SimDuration::days(1))?;
15
16    let commander = canwu.observe(ids.commander, &ObserveRequest::default())?;
17    let observer = canwu.observe(ids.observer, &ObserveRequest::default())?;
18    println!("time: {}", canwu.time());
19    println!("arrival events: {}", arrival_events.len());
20    println!(
21        "commander knows location: {:?}",
22        commander.known_armies[0].known_location
23    );
24    println!(
25        "observer still believes location: {:?}",
26        observer.known_armies[0].known_location
27    );
28
29    canwu.advance(SimDuration::days(3))?;
30    let observer = canwu.observe(ids.observer, &ObserveRequest::default())?;
31    println!(
32        "observer after report: {:?}",
33        observer.known_armies[0].known_location
34    );
35    Ok(())
36}
Source

pub fn demo_ids() -> DemoIds

Examples found in repository?
examples/plugin.rs (line 90)
88fn main() -> Result<(), CanwuError> {
89    let mut canwu = Canwu::demo(35)?;
90    let ids = Canwu::demo_ids();
91    canwu.register_plugin(&StancePlugin)?;
92    canwu.submit(CommandEnvelope::new(
93        Issuer::Actor(ids.commander),
94        Command::Plugin {
95            plugin: "example-stance".to_owned(),
96            command: "set_stance".to_owned(),
97            payload: json!({ "army": ids.army, "stance": "hold" }),
98        },
99    ))?;
100    Ok(())
101}
More examples
Hide additional examples
examples/continuous_game_loop.rs (line 183)
181    fn new(verbose: bool) -> Result<Self, CanwuError> {
182        let authority = Canwu::demo(35)?;
183        let ids = Canwu::demo_ids();
184        let presentation = PresentationState::from_world(&authority.world(), ids.army);
185        Ok(Self {
186            authority,
187            ids,
188            presentation,
189            accumulated_sim_nanos: 0,
190            wall_elapsed: Duration::ZERO,
191            frame: 0,
192            reported_events: 0,
193            next_progress_milestone: 25,
194            canonical_boundaries: 0,
195            verbose,
196        })
197    }
198
199    fn run_phase(&mut self, phase: SpeedPhase, profile: FrameProfile) -> Result<(), CanwuError> {
200        let mut remaining_millis = phase.wall_millis;
201        let mut pattern_index = 0;
202        let mut first_frame = true;
203
204        while remaining_millis > 0 {
205            let frame_millis = profile.frame_millis[pattern_index % profile.frame_millis.len()]
206                .min(remaining_millis);
207            remaining_millis -= frame_millis;
208            self.render_frame(
209                Duration::from_millis(frame_millis),
210                phase.speed,
211                first_frame || remaining_millis == 0,
212            )?;
213            first_frame = false;
214            pattern_index += 1;
215        }
216        Ok(())
217    }
218
219    fn render_frame(
220        &mut self,
221        wall_dt: Duration,
222        speed: GameSpeed,
223        phase_edge: bool,
224    ) -> Result<(), CanwuError> {
225        self.frame += 1;
226        self.wall_elapsed += wall_dt;
227
228        let converted_wall_nanos = wall_dt.as_nanos() * BASE_SIM_SECONDS_PER_WALL_SECOND;
229        self.accumulated_sim_nanos += converted_wall_nanos * speed.multiplier();
230
231        let mut authority_changed = false;
232        while self.accumulated_sim_nanos >= SIMULATION_MINUTE_NANOS {
233            let receipts = self.authority.advance_canonical(SIMULATION_QUANTUM)?;
234            self.canonical_boundaries += receipts.len();
235            authority_changed |= !receipts.is_empty();
236            self.accumulated_sim_nanos -= SIMULATION_MINUTE_NANOS;
237        }
238
239        if authority_changed {
240            self.refresh_presentation_from_authority();
241        }
242
243        let progress = self.presentation.render(self.presentation_time());
244        let crossed_milestone = self.crossed_progress_milestone(progress);
245        let has_new_events = self.reported_events < self.authority.events().len();
246
247        if self.verbose && (phase_edge || crossed_milestone || has_new_events) {
248            self.print_frame(speed);
249        }
250        self.report_new_events();
251        Ok(())
252    }
253
254    /// This host never backdates mid-quantum input. It deterministically rounds
255    /// up to the next representable Canwu minute; another host may choose a
256    /// different policy, but it must apply the same policy across render FPS.
257    fn submit_player_command(&mut self) -> Result<(), CanwuError> {
258        let captured_at = self.presentation_time();
259        let due_at = if self.accumulated_sim_nanos == 0 {
260            self.authority.time()
261        } else {
262            self.authority
263                .time()
264                .checked_add(SIMULATION_QUANTUM)
265                .expect("the scripted command time must remain representable")
266        };
267        let envelope = CommandEnvelope::new(
268            Issuer::Actor(self.ids.commander),
269            Command::MoveArmy {
270                army: self.ids.army,
271                destination: self.ids.eastern_territory,
272            },
273        )
274        .at_time(due_at);
275        let request = CommandRequest::new(
276            CommandRequestId::new(1),
277            self.authority.revision(),
278            envelope,
279        );
280        let ingress = self.authority.enqueue_command(due_at, 0, request)?;
281
282        if self.verbose {
283            println!(
284                "INPUT: queued commander move as {:?}, captured_at={captured_at}, quantized_due_at={due_at}",
285                ingress.ingress_id,
286            );
287        }
288
289        // This drains ingress due at the current canonical time only. The
290        // mid-quantum command remains queued until normal accumulation reaches
291        // its quantized minute; future scheduled work is never jumped to.
292        let receipts = self.authority.advance_canonical(SimDuration::ZERO)?;
293        self.canonical_boundaries += receipts.len();
294        self.refresh_presentation_from_authority();
295        self.presentation.render(self.presentation_time());
296        if self.verbose {
297            self.print_frame(GameSpeed::X1);
298        }
299        self.report_new_events();
300        Ok(())
301    }
302
303    fn refresh_presentation_from_authority(&mut self) {
304        self.presentation
305            .refresh_from_authority(&self.authority.world(), self.ids.army);
306    }
307
308    fn presentation_time(&self) -> PresentationTime {
309        PresentationTime {
310            canonical: self.authority.time(),
311            sub_minute: Duration::from_nanos(
312                u64::try_from(self.accumulated_sim_nanos)
313                    .expect("the sub-minute accumulator must fit a Duration"),
314            ),
315        }
316    }
317
318    fn crossed_progress_milestone(&mut self, progress: Option<f64>) -> bool {
319        let Some(progress) = progress else {
320            return false;
321        };
322        let mut crossed = false;
323        while self.next_progress_milestone <= 100
324            && progress * 100.0 + f64::EPSILON >= f64::from(self.next_progress_milestone)
325        {
326            crossed = true;
327            self.next_progress_milestone += 25;
328        }
329        crossed
330    }
331
332    fn print_frame(&self, speed: GameSpeed) {
333        println!(
334            "frame={:>5} speed={:<6} wall={:>7.3}s visual_time={} canwu_time={} {}",
335            self.frame,
336            speed.label(),
337            self.wall_elapsed.as_secs_f64(),
338            self.presentation_time(),
339            self.authority.time(),
340            self.presentation.describe(),
341        );
342    }
343
344    fn report_new_events(&mut self) {
345        let events = self.authority.events();
346        if self.verbose {
347            for event in &events[self.reported_events..] {
348                println!(
349                    "EVENT: at={} type={} {}",
350                    event.timestamp,
351                    event.kind.qualified_event_type(),
352                    event.summary,
353                );
354            }
355        }
356        self.reported_events = events.len();
357    }
358
359    fn outcome(self, profile: FrameProfile) -> Result<RunOutcome, CanwuError> {
360        Ok(RunOutcome {
361            profile: profile.name,
362            frames: self.frame,
363            time: self.authority.time(),
364            world: self.authority.world(),
365            events: self.authority.events().to_vec(),
366            commands: self.authority.commands().to_vec(),
367            authoritative_state_hash: self.authority.authoritative_state_hash()?,
368            checkpoint_hash: self.authority.checkpoint_hash().to_owned(),
369            canonical_boundaries: self.canonical_boundaries,
370        })
371    }
372}
373
374struct RunOutcome {
375    profile: &'static str,
376    frames: u64,
377    time: SimTime,
378    world: WorldSnapshot,
379    events: Vec<SimEvent>,
380    commands: Vec<CommandRecord>,
381    authoritative_state_hash: String,
382    checkpoint_hash: String,
383    canonical_boundaries: usize,
384}
385
386fn run(profile: FrameProfile, verbose: bool) -> Result<RunOutcome, CanwuError> {
387    let mut host = GameHost::new(verbose)?;
388    if verbose {
389        println!("\n{} render loop", profile.name);
390    }
391
392    host.run_phase(SPEED_SCHEDULE[0], profile)?;
393    host.submit_player_command()?;
394    host.run_phase(SPEED_SCHEDULE[1], profile)?;
395    host.run_phase(SPEED_SCHEDULE[2], profile)?;
396
397    let time_before_pause = host.authority.time();
398    let remainder_before_pause = host.accumulated_sim_nanos;
399    let presentation_before_pause = host.presentation_time();
400    let frames_before_pause = host.frame;
401    assert!(
402        remainder_before_pause > 0,
403        "the pause fixture must preserve a fractional simulation minute"
404    );
405    host.run_phase(SPEED_SCHEDULE[3], profile)?;
406    assert!(
407        host.frame > frames_before_pause,
408        "pause must keep rendering"
409    );
410    assert_eq!(
411        host.authority.time(),
412        time_before_pause,
413        "pause must not advance Canwu"
414    );
415    assert_eq!(
416        host.accumulated_sim_nanos, remainder_before_pause,
417        "pause must not add desired simulation time"
418    );
419    assert_eq!(
420        host.presentation_time(),
421        presentation_before_pause,
422        "pause must freeze presentation time"
423    );
424
425    host.run_phase(SPEED_SCHEDULE[4], profile)?;
426    assert_eq!(
427        host.accumulated_sim_nanos, 0,
428        "the scripted schedule should end on a simulation-minute boundary"
429    );
430    host.outcome(profile)
431}
432
433fn std_duration(duration: SimDuration) -> Duration {
434    let minutes = u64::try_from(duration.as_minutes())
435        .expect("presentation interpolation requires a non-negative duration");
436    Duration::from_secs(
437        minutes
438            .checked_mul(60)
439            .expect("presentation duration must remain representable"),
440    )
441}
442
443fn assert_fps_independent(left: &RunOutcome, right: &RunOutcome) {
444    assert_ne!(
445        left.frames, right.frames,
446        "the render segmentations must differ"
447    );
448    assert_eq!(left.time, right.time, "Canwu time changed with render FPS");
449    assert_eq!(
450        left.world, right.world,
451        "world state changed with render FPS"
452    );
453    assert_eq!(left.events, right.events, "events changed with render FPS");
454    assert_eq!(
455        left.commands, right.commands,
456        "command ordering changed with render FPS"
457    );
458    assert_eq!(
459        left.authoritative_state_hash, right.authoritative_state_hash,
460        "authoritative state hash changed with render FPS"
461    );
462    assert_eq!(
463        left.checkpoint_hash, right.checkpoint_hash,
464        "checkpoint hash changed with render FPS"
465    );
466    assert_eq!(
467        left.canonical_boundaries, right.canonical_boundaries,
468        "canonical boundary count changed with render FPS"
469    );
470}
471
472fn assert_expected_outcome(outcome: &RunOutcome) {
473    let ids = Canwu::demo_ids();
474    let command_at = SimTime::from_minutes(1);
475    let arrival_at = SimTime::from_minutes(1_081);
476    let final_time = SimTime::from_minutes(1_082);
477
478    assert_eq!(outcome.time, final_time, "the scripted final time changed");
479    let army = outcome
480        .world
481        .army(ids.army)
482        .expect("the demo army must remain present");
483    assert_eq!(
484        army.location, ids.eastern_territory,
485        "the scheduled arrival must execute"
486    );
487    assert!(
488        army.transit.is_none(),
489        "the army must no longer be in authoritative transit"
490    );
491
492    assert_eq!(
493        outcome.commands.len(),
494        1,
495        "the move command must be accepted"
496    );
497    let command = &outcome.commands[0];
498    assert_eq!(command.accepted_at, command_at);
499    assert_eq!(command.envelope.expected_time, Some(command_at));
500    assert!(matches!(
501        &command.envelope.command,
502        Command::MoveArmy { army, destination }
503            if *army == ids.army && *destination == ids.eastern_territory
504    ));
505
506    let event_timeline = outcome
507        .events
508        .iter()
509        .map(|event| (event.kind.qualified_event_type(), event.timestamp))
510        .collect::<Vec<_>>();
511    assert_eq!(
512        event_timeline,
513        vec![
514            ("move_ordered".to_owned(), command_at),
515            ("army_arrived".to_owned(), arrival_at),
516            ("knowledge_updated".to_owned(), arrival_at),
517            ("report_dispatched".to_owned(), arrival_at),
518        ],
519        "the expected command and scheduled-arrival event timeline changed"
520    );
521    assert_eq!(
522        outcome.canonical_boundaries, 2,
523        "the command and arrival should settle at two canonical boundaries"
524    );
525}
examples/move_army.rs (line 5)
3fn main() -> Result<(), Box<dyn std::error::Error>> {
4    let mut canwu = Canwu::demo(35)?;
5    let ids = Canwu::demo_ids();
6
7    canwu.act(
8        ids.commander,
9        SemanticAction::MoveArmy {
10            army: ids.army,
11            destination: ids.eastern_territory,
12        },
13    )?;
14    let arrival_events = canwu.advance(SimDuration::days(1))?;
15
16    let commander = canwu.observe(ids.commander, &ObserveRequest::default())?;
17    let observer = canwu.observe(ids.observer, &ObserveRequest::default())?;
18    println!("time: {}", canwu.time());
19    println!("arrival events: {}", arrival_events.len());
20    println!(
21        "commander knows location: {:?}",
22        commander.known_armies[0].known_location
23    );
24    println!(
25        "observer still believes location: {:?}",
26        observer.known_armies[0].known_location
27    );
28
29    canwu.advance(SimDuration::days(3))?;
30    let observer = canwu.observe(ids.observer, &ObserveRequest::default())?;
31    println!(
32        "observer after report: {:?}",
33        observer.known_armies[0].known_location
34    );
35    Ok(())
36}
Source

pub const fn time(&self) -> SimTime

Examples found in repository?
examples/phased_boundary.rs (line 155)
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}
More examples
Hide additional examples
examples/move_army.rs (line 18)
3fn main() -> Result<(), Box<dyn std::error::Error>> {
4    let mut canwu = Canwu::demo(35)?;
5    let ids = Canwu::demo_ids();
6
7    canwu.act(
8        ids.commander,
9        SemanticAction::MoveArmy {
10            army: ids.army,
11            destination: ids.eastern_territory,
12        },
13    )?;
14    let arrival_events = canwu.advance(SimDuration::days(1))?;
15
16    let commander = canwu.observe(ids.commander, &ObserveRequest::default())?;
17    let observer = canwu.observe(ids.observer, &ObserveRequest::default())?;
18    println!("time: {}", canwu.time());
19    println!("arrival events: {}", arrival_events.len());
20    println!(
21        "commander knows location: {:?}",
22        commander.known_armies[0].known_location
23    );
24    println!(
25        "observer still believes location: {:?}",
26        observer.known_armies[0].known_location
27    );
28
29    canwu.advance(SimDuration::days(3))?;
30    let observer = canwu.observe(ids.observer, &ObserveRequest::default())?;
31    println!(
32        "observer after report: {:?}",
33        observer.known_armies[0].known_location
34    );
35    Ok(())
36}
examples/continuous_game_loop.rs (line 260)
257    fn submit_player_command(&mut self) -> Result<(), CanwuError> {
258        let captured_at = self.presentation_time();
259        let due_at = if self.accumulated_sim_nanos == 0 {
260            self.authority.time()
261        } else {
262            self.authority
263                .time()
264                .checked_add(SIMULATION_QUANTUM)
265                .expect("the scripted command time must remain representable")
266        };
267        let envelope = CommandEnvelope::new(
268            Issuer::Actor(self.ids.commander),
269            Command::MoveArmy {
270                army: self.ids.army,
271                destination: self.ids.eastern_territory,
272            },
273        )
274        .at_time(due_at);
275        let request = CommandRequest::new(
276            CommandRequestId::new(1),
277            self.authority.revision(),
278            envelope,
279        );
280        let ingress = self.authority.enqueue_command(due_at, 0, request)?;
281
282        if self.verbose {
283            println!(
284                "INPUT: queued commander move as {:?}, captured_at={captured_at}, quantized_due_at={due_at}",
285                ingress.ingress_id,
286            );
287        }
288
289        // This drains ingress due at the current canonical time only. The
290        // mid-quantum command remains queued until normal accumulation reaches
291        // its quantized minute; future scheduled work is never jumped to.
292        let receipts = self.authority.advance_canonical(SimDuration::ZERO)?;
293        self.canonical_boundaries += receipts.len();
294        self.refresh_presentation_from_authority();
295        self.presentation.render(self.presentation_time());
296        if self.verbose {
297            self.print_frame(GameSpeed::X1);
298        }
299        self.report_new_events();
300        Ok(())
301    }
302
303    fn refresh_presentation_from_authority(&mut self) {
304        self.presentation
305            .refresh_from_authority(&self.authority.world(), self.ids.army);
306    }
307
308    fn presentation_time(&self) -> PresentationTime {
309        PresentationTime {
310            canonical: self.authority.time(),
311            sub_minute: Duration::from_nanos(
312                u64::try_from(self.accumulated_sim_nanos)
313                    .expect("the sub-minute accumulator must fit a Duration"),
314            ),
315        }
316    }
317
318    fn crossed_progress_milestone(&mut self, progress: Option<f64>) -> bool {
319        let Some(progress) = progress else {
320            return false;
321        };
322        let mut crossed = false;
323        while self.next_progress_milestone <= 100
324            && progress * 100.0 + f64::EPSILON >= f64::from(self.next_progress_milestone)
325        {
326            crossed = true;
327            self.next_progress_milestone += 25;
328        }
329        crossed
330    }
331
332    fn print_frame(&self, speed: GameSpeed) {
333        println!(
334            "frame={:>5} speed={:<6} wall={:>7.3}s visual_time={} canwu_time={} {}",
335            self.frame,
336            speed.label(),
337            self.wall_elapsed.as_secs_f64(),
338            self.presentation_time(),
339            self.authority.time(),
340            self.presentation.describe(),
341        );
342    }
343
344    fn report_new_events(&mut self) {
345        let events = self.authority.events();
346        if self.verbose {
347            for event in &events[self.reported_events..] {
348                println!(
349                    "EVENT: at={} type={} {}",
350                    event.timestamp,
351                    event.kind.qualified_event_type(),
352                    event.summary,
353                );
354            }
355        }
356        self.reported_events = events.len();
357    }
358
359    fn outcome(self, profile: FrameProfile) -> Result<RunOutcome, CanwuError> {
360        Ok(RunOutcome {
361            profile: profile.name,
362            frames: self.frame,
363            time: self.authority.time(),
364            world: self.authority.world(),
365            events: self.authority.events().to_vec(),
366            commands: self.authority.commands().to_vec(),
367            authoritative_state_hash: self.authority.authoritative_state_hash()?,
368            checkpoint_hash: self.authority.checkpoint_hash().to_owned(),
369            canonical_boundaries: self.canonical_boundaries,
370        })
371    }
372}
373
374struct RunOutcome {
375    profile: &'static str,
376    frames: u64,
377    time: SimTime,
378    world: WorldSnapshot,
379    events: Vec<SimEvent>,
380    commands: Vec<CommandRecord>,
381    authoritative_state_hash: String,
382    checkpoint_hash: String,
383    canonical_boundaries: usize,
384}
385
386fn run(profile: FrameProfile, verbose: bool) -> Result<RunOutcome, CanwuError> {
387    let mut host = GameHost::new(verbose)?;
388    if verbose {
389        println!("\n{} render loop", profile.name);
390    }
391
392    host.run_phase(SPEED_SCHEDULE[0], profile)?;
393    host.submit_player_command()?;
394    host.run_phase(SPEED_SCHEDULE[1], profile)?;
395    host.run_phase(SPEED_SCHEDULE[2], profile)?;
396
397    let time_before_pause = host.authority.time();
398    let remainder_before_pause = host.accumulated_sim_nanos;
399    let presentation_before_pause = host.presentation_time();
400    let frames_before_pause = host.frame;
401    assert!(
402        remainder_before_pause > 0,
403        "the pause fixture must preserve a fractional simulation minute"
404    );
405    host.run_phase(SPEED_SCHEDULE[3], profile)?;
406    assert!(
407        host.frame > frames_before_pause,
408        "pause must keep rendering"
409    );
410    assert_eq!(
411        host.authority.time(),
412        time_before_pause,
413        "pause must not advance Canwu"
414    );
415    assert_eq!(
416        host.accumulated_sim_nanos, remainder_before_pause,
417        "pause must not add desired simulation time"
418    );
419    assert_eq!(
420        host.presentation_time(),
421        presentation_before_pause,
422        "pause must freeze presentation time"
423    );
424
425    host.run_phase(SPEED_SCHEDULE[4], profile)?;
426    assert_eq!(
427        host.accumulated_sim_nanos, 0,
428        "the scripted schedule should end on a simulation-minute boundary"
429    );
430    host.outcome(profile)
431}
Source

pub const fn run_manifest(&self) -> &RunManifest

Source

pub const fn run_configuration(&self) -> &RunConfigurationSnapshot

Source

pub fn revision(&self) -> u64

Returns the persisted authoritative transaction revision.

Accepted commands, persisted expected rejections, and completed settlement boundaries each advance it exactly once. Failed work, exact retries, bare clock movement, queued but unadmitted ingress, and plugin setup do not advance it; combine it with command expected-time guards.

Examples found in repository?
examples/continuous_game_loop.rs (line 277)
257    fn submit_player_command(&mut self) -> Result<(), CanwuError> {
258        let captured_at = self.presentation_time();
259        let due_at = if self.accumulated_sim_nanos == 0 {
260            self.authority.time()
261        } else {
262            self.authority
263                .time()
264                .checked_add(SIMULATION_QUANTUM)
265                .expect("the scripted command time must remain representable")
266        };
267        let envelope = CommandEnvelope::new(
268            Issuer::Actor(self.ids.commander),
269            Command::MoveArmy {
270                army: self.ids.army,
271                destination: self.ids.eastern_territory,
272            },
273        )
274        .at_time(due_at);
275        let request = CommandRequest::new(
276            CommandRequestId::new(1),
277            self.authority.revision(),
278            envelope,
279        );
280        let ingress = self.authority.enqueue_command(due_at, 0, request)?;
281
282        if self.verbose {
283            println!(
284                "INPUT: queued commander move as {:?}, captured_at={captured_at}, quantized_due_at={due_at}",
285                ingress.ingress_id,
286            );
287        }
288
289        // This drains ingress due at the current canonical time only. The
290        // mid-quantum command remains queued until normal accumulation reaches
291        // its quantized minute; future scheduled work is never jumped to.
292        let receipts = self.authority.advance_canonical(SimDuration::ZERO)?;
293        self.canonical_boundaries += receipts.len();
294        self.refresh_presentation_from_authority();
295        self.presentation.render(self.presentation_time());
296        if self.verbose {
297            self.print_frame(GameSpeed::X1);
298        }
299        self.report_new_events();
300        Ok(())
301    }
Source

pub fn run_manifest_hash(&self) -> &str

Source

pub fn checkpoint_hash(&self) -> &str

Examples found in repository?
examples/continuous_game_loop.rs (line 368)
359    fn outcome(self, profile: FrameProfile) -> Result<RunOutcome, CanwuError> {
360        Ok(RunOutcome {
361            profile: profile.name,
362            frames: self.frame,
363            time: self.authority.time(),
364            world: self.authority.world(),
365            events: self.authority.events().to_vec(),
366            commands: self.authority.commands().to_vec(),
367            authoritative_state_hash: self.authority.authoritative_state_hash()?,
368            checkpoint_hash: self.authority.checkpoint_hash().to_owned(),
369            canonical_boundaries: self.canonical_boundaries,
370        })
371    }
Source

pub fn authoritative_state_hash(&self) -> Result<String, CanwuError>

Examples found in repository?
examples/continuous_game_loop.rs (line 367)
359    fn outcome(self, profile: FrameProfile) -> Result<RunOutcome, CanwuError> {
360        Ok(RunOutcome {
361            profile: profile.name,
362            frames: self.frame,
363            time: self.authority.time(),
364            world: self.authority.world(),
365            events: self.authority.events().to_vec(),
366            commands: self.authority.commands().to_vec(),
367            authoritative_state_hash: self.authority.authoritative_state_hash()?,
368            checkpoint_hash: self.authority.checkpoint_hash().to_owned(),
369            canonical_boundaries: self.canonical_boundaries,
370        })
371    }
Source

pub fn world(&self) -> WorldSnapshot

Examples found in repository?
examples/continuous_game_loop.rs (line 184)
181    fn new(verbose: bool) -> Result<Self, CanwuError> {
182        let authority = Canwu::demo(35)?;
183        let ids = Canwu::demo_ids();
184        let presentation = PresentationState::from_world(&authority.world(), ids.army);
185        Ok(Self {
186            authority,
187            ids,
188            presentation,
189            accumulated_sim_nanos: 0,
190            wall_elapsed: Duration::ZERO,
191            frame: 0,
192            reported_events: 0,
193            next_progress_milestone: 25,
194            canonical_boundaries: 0,
195            verbose,
196        })
197    }
198
199    fn run_phase(&mut self, phase: SpeedPhase, profile: FrameProfile) -> Result<(), CanwuError> {
200        let mut remaining_millis = phase.wall_millis;
201        let mut pattern_index = 0;
202        let mut first_frame = true;
203
204        while remaining_millis > 0 {
205            let frame_millis = profile.frame_millis[pattern_index % profile.frame_millis.len()]
206                .min(remaining_millis);
207            remaining_millis -= frame_millis;
208            self.render_frame(
209                Duration::from_millis(frame_millis),
210                phase.speed,
211                first_frame || remaining_millis == 0,
212            )?;
213            first_frame = false;
214            pattern_index += 1;
215        }
216        Ok(())
217    }
218
219    fn render_frame(
220        &mut self,
221        wall_dt: Duration,
222        speed: GameSpeed,
223        phase_edge: bool,
224    ) -> Result<(), CanwuError> {
225        self.frame += 1;
226        self.wall_elapsed += wall_dt;
227
228        let converted_wall_nanos = wall_dt.as_nanos() * BASE_SIM_SECONDS_PER_WALL_SECOND;
229        self.accumulated_sim_nanos += converted_wall_nanos * speed.multiplier();
230
231        let mut authority_changed = false;
232        while self.accumulated_sim_nanos >= SIMULATION_MINUTE_NANOS {
233            let receipts = self.authority.advance_canonical(SIMULATION_QUANTUM)?;
234            self.canonical_boundaries += receipts.len();
235            authority_changed |= !receipts.is_empty();
236            self.accumulated_sim_nanos -= SIMULATION_MINUTE_NANOS;
237        }
238
239        if authority_changed {
240            self.refresh_presentation_from_authority();
241        }
242
243        let progress = self.presentation.render(self.presentation_time());
244        let crossed_milestone = self.crossed_progress_milestone(progress);
245        let has_new_events = self.reported_events < self.authority.events().len();
246
247        if self.verbose && (phase_edge || crossed_milestone || has_new_events) {
248            self.print_frame(speed);
249        }
250        self.report_new_events();
251        Ok(())
252    }
253
254    /// This host never backdates mid-quantum input. It deterministically rounds
255    /// up to the next representable Canwu minute; another host may choose a
256    /// different policy, but it must apply the same policy across render FPS.
257    fn submit_player_command(&mut self) -> Result<(), CanwuError> {
258        let captured_at = self.presentation_time();
259        let due_at = if self.accumulated_sim_nanos == 0 {
260            self.authority.time()
261        } else {
262            self.authority
263                .time()
264                .checked_add(SIMULATION_QUANTUM)
265                .expect("the scripted command time must remain representable")
266        };
267        let envelope = CommandEnvelope::new(
268            Issuer::Actor(self.ids.commander),
269            Command::MoveArmy {
270                army: self.ids.army,
271                destination: self.ids.eastern_territory,
272            },
273        )
274        .at_time(due_at);
275        let request = CommandRequest::new(
276            CommandRequestId::new(1),
277            self.authority.revision(),
278            envelope,
279        );
280        let ingress = self.authority.enqueue_command(due_at, 0, request)?;
281
282        if self.verbose {
283            println!(
284                "INPUT: queued commander move as {:?}, captured_at={captured_at}, quantized_due_at={due_at}",
285                ingress.ingress_id,
286            );
287        }
288
289        // This drains ingress due at the current canonical time only. The
290        // mid-quantum command remains queued until normal accumulation reaches
291        // its quantized minute; future scheduled work is never jumped to.
292        let receipts = self.authority.advance_canonical(SimDuration::ZERO)?;
293        self.canonical_boundaries += receipts.len();
294        self.refresh_presentation_from_authority();
295        self.presentation.render(self.presentation_time());
296        if self.verbose {
297            self.print_frame(GameSpeed::X1);
298        }
299        self.report_new_events();
300        Ok(())
301    }
302
303    fn refresh_presentation_from_authority(&mut self) {
304        self.presentation
305            .refresh_from_authority(&self.authority.world(), self.ids.army);
306    }
307
308    fn presentation_time(&self) -> PresentationTime {
309        PresentationTime {
310            canonical: self.authority.time(),
311            sub_minute: Duration::from_nanos(
312                u64::try_from(self.accumulated_sim_nanos)
313                    .expect("the sub-minute accumulator must fit a Duration"),
314            ),
315        }
316    }
317
318    fn crossed_progress_milestone(&mut self, progress: Option<f64>) -> bool {
319        let Some(progress) = progress else {
320            return false;
321        };
322        let mut crossed = false;
323        while self.next_progress_milestone <= 100
324            && progress * 100.0 + f64::EPSILON >= f64::from(self.next_progress_milestone)
325        {
326            crossed = true;
327            self.next_progress_milestone += 25;
328        }
329        crossed
330    }
331
332    fn print_frame(&self, speed: GameSpeed) {
333        println!(
334            "frame={:>5} speed={:<6} wall={:>7.3}s visual_time={} canwu_time={} {}",
335            self.frame,
336            speed.label(),
337            self.wall_elapsed.as_secs_f64(),
338            self.presentation_time(),
339            self.authority.time(),
340            self.presentation.describe(),
341        );
342    }
343
344    fn report_new_events(&mut self) {
345        let events = self.authority.events();
346        if self.verbose {
347            for event in &events[self.reported_events..] {
348                println!(
349                    "EVENT: at={} type={} {}",
350                    event.timestamp,
351                    event.kind.qualified_event_type(),
352                    event.summary,
353                );
354            }
355        }
356        self.reported_events = events.len();
357    }
358
359    fn outcome(self, profile: FrameProfile) -> Result<RunOutcome, CanwuError> {
360        Ok(RunOutcome {
361            profile: profile.name,
362            frames: self.frame,
363            time: self.authority.time(),
364            world: self.authority.world(),
365            events: self.authority.events().to_vec(),
366            commands: self.authority.commands().to_vec(),
367            authoritative_state_hash: self.authority.authoritative_state_hash()?,
368            checkpoint_hash: self.authority.checkpoint_hash().to_owned(),
369            canonical_boundaries: self.canonical_boundaries,
370        })
371    }
Source

pub fn knowledge(&self) -> &KnowledgeSnapshot

Source

pub fn events(&self) -> &[SimEvent]

Examples found in repository?
examples/continuous_game_loop.rs (line 245)
219    fn render_frame(
220        &mut self,
221        wall_dt: Duration,
222        speed: GameSpeed,
223        phase_edge: bool,
224    ) -> Result<(), CanwuError> {
225        self.frame += 1;
226        self.wall_elapsed += wall_dt;
227
228        let converted_wall_nanos = wall_dt.as_nanos() * BASE_SIM_SECONDS_PER_WALL_SECOND;
229        self.accumulated_sim_nanos += converted_wall_nanos * speed.multiplier();
230
231        let mut authority_changed = false;
232        while self.accumulated_sim_nanos >= SIMULATION_MINUTE_NANOS {
233            let receipts = self.authority.advance_canonical(SIMULATION_QUANTUM)?;
234            self.canonical_boundaries += receipts.len();
235            authority_changed |= !receipts.is_empty();
236            self.accumulated_sim_nanos -= SIMULATION_MINUTE_NANOS;
237        }
238
239        if authority_changed {
240            self.refresh_presentation_from_authority();
241        }
242
243        let progress = self.presentation.render(self.presentation_time());
244        let crossed_milestone = self.crossed_progress_milestone(progress);
245        let has_new_events = self.reported_events < self.authority.events().len();
246
247        if self.verbose && (phase_edge || crossed_milestone || has_new_events) {
248            self.print_frame(speed);
249        }
250        self.report_new_events();
251        Ok(())
252    }
253
254    /// This host never backdates mid-quantum input. It deterministically rounds
255    /// up to the next representable Canwu minute; another host may choose a
256    /// different policy, but it must apply the same policy across render FPS.
257    fn submit_player_command(&mut self) -> Result<(), CanwuError> {
258        let captured_at = self.presentation_time();
259        let due_at = if self.accumulated_sim_nanos == 0 {
260            self.authority.time()
261        } else {
262            self.authority
263                .time()
264                .checked_add(SIMULATION_QUANTUM)
265                .expect("the scripted command time must remain representable")
266        };
267        let envelope = CommandEnvelope::new(
268            Issuer::Actor(self.ids.commander),
269            Command::MoveArmy {
270                army: self.ids.army,
271                destination: self.ids.eastern_territory,
272            },
273        )
274        .at_time(due_at);
275        let request = CommandRequest::new(
276            CommandRequestId::new(1),
277            self.authority.revision(),
278            envelope,
279        );
280        let ingress = self.authority.enqueue_command(due_at, 0, request)?;
281
282        if self.verbose {
283            println!(
284                "INPUT: queued commander move as {:?}, captured_at={captured_at}, quantized_due_at={due_at}",
285                ingress.ingress_id,
286            );
287        }
288
289        // This drains ingress due at the current canonical time only. The
290        // mid-quantum command remains queued until normal accumulation reaches
291        // its quantized minute; future scheduled work is never jumped to.
292        let receipts = self.authority.advance_canonical(SimDuration::ZERO)?;
293        self.canonical_boundaries += receipts.len();
294        self.refresh_presentation_from_authority();
295        self.presentation.render(self.presentation_time());
296        if self.verbose {
297            self.print_frame(GameSpeed::X1);
298        }
299        self.report_new_events();
300        Ok(())
301    }
302
303    fn refresh_presentation_from_authority(&mut self) {
304        self.presentation
305            .refresh_from_authority(&self.authority.world(), self.ids.army);
306    }
307
308    fn presentation_time(&self) -> PresentationTime {
309        PresentationTime {
310            canonical: self.authority.time(),
311            sub_minute: Duration::from_nanos(
312                u64::try_from(self.accumulated_sim_nanos)
313                    .expect("the sub-minute accumulator must fit a Duration"),
314            ),
315        }
316    }
317
318    fn crossed_progress_milestone(&mut self, progress: Option<f64>) -> bool {
319        let Some(progress) = progress else {
320            return false;
321        };
322        let mut crossed = false;
323        while self.next_progress_milestone <= 100
324            && progress * 100.0 + f64::EPSILON >= f64::from(self.next_progress_milestone)
325        {
326            crossed = true;
327            self.next_progress_milestone += 25;
328        }
329        crossed
330    }
331
332    fn print_frame(&self, speed: GameSpeed) {
333        println!(
334            "frame={:>5} speed={:<6} wall={:>7.3}s visual_time={} canwu_time={} {}",
335            self.frame,
336            speed.label(),
337            self.wall_elapsed.as_secs_f64(),
338            self.presentation_time(),
339            self.authority.time(),
340            self.presentation.describe(),
341        );
342    }
343
344    fn report_new_events(&mut self) {
345        let events = self.authority.events();
346        if self.verbose {
347            for event in &events[self.reported_events..] {
348                println!(
349                    "EVENT: at={} type={} {}",
350                    event.timestamp,
351                    event.kind.qualified_event_type(),
352                    event.summary,
353                );
354            }
355        }
356        self.reported_events = events.len();
357    }
358
359    fn outcome(self, profile: FrameProfile) -> Result<RunOutcome, CanwuError> {
360        Ok(RunOutcome {
361            profile: profile.name,
362            frames: self.frame,
363            time: self.authority.time(),
364            world: self.authority.world(),
365            events: self.authority.events().to_vec(),
366            commands: self.authority.commands().to_vec(),
367            authoritative_state_hash: self.authority.authoritative_state_hash()?,
368            checkpoint_hash: self.authority.checkpoint_hash().to_owned(),
369            canonical_boundaries: self.canonical_boundaries,
370        })
371    }
Source

pub fn commands(&self) -> &[CommandRecord]

Examples found in repository?
examples/continuous_game_loop.rs (line 366)
359    fn outcome(self, profile: FrameProfile) -> Result<RunOutcome, CanwuError> {
360        Ok(RunOutcome {
361            profile: profile.name,
362            frames: self.frame,
363            time: self.authority.time(),
364            world: self.authority.world(),
365            events: self.authority.events().to_vec(),
366            commands: self.authority.commands().to_vec(),
367            authoritative_state_hash: self.authority.authoritative_state_hash()?,
368            checkpoint_hash: self.authority.checkpoint_hash().to_owned(),
369            canonical_boundaries: self.canonical_boundaries,
370        })
371    }
Source

pub fn boundaries(&self) -> &[BoundaryRecord]

Examples found in repository?
examples/phased_boundary.rs (line 157)
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}
Source

pub fn command_attempts(&self) -> &[CommandAttemptRecord]

Source

pub fn ingress_log(&self) -> &[IngressRecord]

Source

pub fn domain_record( &self, reference: &DomainRecordRef, ) -> Option<&DomainRecord>

Source

pub fn typed_domain_record<T: DomainRecordType>( &self, reference: &TypedDomainRecordRef<T>, ) -> Option<&DomainRecord>

Source

pub fn domain_records(&self) -> impl Iterator<Item = &DomainRecord>

Source

pub fn random_draws(&self) -> &[RandomDrawRecord]

Source

pub fn boundary_head_hash(&self) -> Option<&str>

Source

pub const fn schema(&self) -> &SchemaRegistry

Source

pub fn plugin_descriptors(&self) -> impl Iterator<Item = &PluginDescriptor>

Source

pub fn replay_journal(&self) -> ReplayJournal

Source

pub fn evidence_cursor(&self) -> Result<EvidenceCursor, CanwuError>

Source

pub fn checkpoint(&self) -> Result<SimulationCheckpoint, CanwuError>

Source

pub fn journal_segment_since( &self, start: EvidenceCursor, ) -> Result<EvidenceJournalSegment, CanwuError>

Source

pub fn checkpoint_journal(&self) -> Result<CheckpointJournal, CanwuError>

Source

pub fn checkpoint_journal_json(&self) -> Result<String, CanwuError>

Source

pub fn register_plugin<P: SimulationPlugin + ?Sized>( &mut self, plugin: &P, ) -> Result<(), CanwuError>

Examples found in repository?
examples/phased_boundary.rs (line 151)
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}
More examples
Hide additional examples
examples/plugin.rs (line 91)
88fn main() -> Result<(), CanwuError> {
89    let mut canwu = Canwu::demo(35)?;
90    let ids = Canwu::demo_ids();
91    canwu.register_plugin(&StancePlugin)?;
92    canwu.submit(CommandEnvelope::new(
93        Issuer::Actor(ids.commander),
94        Command::Plugin {
95            plugin: "example-stance".to_owned(),
96            command: "set_stance".to_owned(),
97            payload: json!({ "army": ids.army, "stance": "hold" }),
98        },
99    ))?;
100    Ok(())
101}
Source

pub fn submit( &mut self, command: CommandEnvelope, ) -> Result<CommandReceipt, CanwuError>

Examples found in repository?
examples/plugin.rs (lines 92-99)
88fn main() -> Result<(), CanwuError> {
89    let mut canwu = Canwu::demo(35)?;
90    let ids = Canwu::demo_ids();
91    canwu.register_plugin(&StancePlugin)?;
92    canwu.submit(CommandEnvelope::new(
93        Issuer::Actor(ids.commander),
94        Command::Plugin {
95            plugin: "example-stance".to_owned(),
96            command: "set_stance".to_owned(),
97            payload: json!({ "army": ids.army, "stance": "hold" }),
98        },
99    ))?;
100    Ok(())
101}
Source

pub fn process_command( &mut self, request: CommandRequest, ) -> Result<CommandOutcome, CanwuError>

Source

pub fn enqueue_command( &mut self, due_at: SimTime, priority: i32, request: CommandRequest, ) -> Result<IngressReceipt, CanwuError>

Examples found in repository?
examples/continuous_game_loop.rs (line 280)
257    fn submit_player_command(&mut self) -> Result<(), CanwuError> {
258        let captured_at = self.presentation_time();
259        let due_at = if self.accumulated_sim_nanos == 0 {
260            self.authority.time()
261        } else {
262            self.authority
263                .time()
264                .checked_add(SIMULATION_QUANTUM)
265                .expect("the scripted command time must remain representable")
266        };
267        let envelope = CommandEnvelope::new(
268            Issuer::Actor(self.ids.commander),
269            Command::MoveArmy {
270                army: self.ids.army,
271                destination: self.ids.eastern_territory,
272            },
273        )
274        .at_time(due_at);
275        let request = CommandRequest::new(
276            CommandRequestId::new(1),
277            self.authority.revision(),
278            envelope,
279        );
280        let ingress = self.authority.enqueue_command(due_at, 0, request)?;
281
282        if self.verbose {
283            println!(
284                "INPUT: queued commander move as {:?}, captured_at={captured_at}, quantized_due_at={due_at}",
285                ingress.ingress_id,
286            );
287        }
288
289        // This drains ingress due at the current canonical time only. The
290        // mid-quantum command remains queued until normal accumulation reaches
291        // its quantized minute; future scheduled work is never jumped to.
292        let receipts = self.authority.advance_canonical(SimDuration::ZERO)?;
293        self.canonical_boundaries += receipts.len();
294        self.refresh_presentation_from_authority();
295        self.presentation.render(self.presentation_time());
296        if self.verbose {
297            self.print_frame(GameSpeed::X1);
298        }
299        self.report_new_events();
300        Ok(())
301    }
Source

pub fn enqueue_plugin_ingress( &mut self, request: PluginIngressRequest, ) -> Result<IngressReceipt, CanwuError>

Source

pub fn schedule_calendar_boundary( &mut self, due_at: SimTime, cadences: Vec<SystemCadence>, ) -> Result<IngressReceipt, CanwuError>

Source

pub fn advance_canonical( &mut self, duration: SimDuration, ) -> Result<Vec<BoundaryReceipt>, CanwuError>

Examples found in repository?
examples/continuous_game_loop.rs (line 233)
219    fn render_frame(
220        &mut self,
221        wall_dt: Duration,
222        speed: GameSpeed,
223        phase_edge: bool,
224    ) -> Result<(), CanwuError> {
225        self.frame += 1;
226        self.wall_elapsed += wall_dt;
227
228        let converted_wall_nanos = wall_dt.as_nanos() * BASE_SIM_SECONDS_PER_WALL_SECOND;
229        self.accumulated_sim_nanos += converted_wall_nanos * speed.multiplier();
230
231        let mut authority_changed = false;
232        while self.accumulated_sim_nanos >= SIMULATION_MINUTE_NANOS {
233            let receipts = self.authority.advance_canonical(SIMULATION_QUANTUM)?;
234            self.canonical_boundaries += receipts.len();
235            authority_changed |= !receipts.is_empty();
236            self.accumulated_sim_nanos -= SIMULATION_MINUTE_NANOS;
237        }
238
239        if authority_changed {
240            self.refresh_presentation_from_authority();
241        }
242
243        let progress = self.presentation.render(self.presentation_time());
244        let crossed_milestone = self.crossed_progress_milestone(progress);
245        let has_new_events = self.reported_events < self.authority.events().len();
246
247        if self.verbose && (phase_edge || crossed_milestone || has_new_events) {
248            self.print_frame(speed);
249        }
250        self.report_new_events();
251        Ok(())
252    }
253
254    /// This host never backdates mid-quantum input. It deterministically rounds
255    /// up to the next representable Canwu minute; another host may choose a
256    /// different policy, but it must apply the same policy across render FPS.
257    fn submit_player_command(&mut self) -> Result<(), CanwuError> {
258        let captured_at = self.presentation_time();
259        let due_at = if self.accumulated_sim_nanos == 0 {
260            self.authority.time()
261        } else {
262            self.authority
263                .time()
264                .checked_add(SIMULATION_QUANTUM)
265                .expect("the scripted command time must remain representable")
266        };
267        let envelope = CommandEnvelope::new(
268            Issuer::Actor(self.ids.commander),
269            Command::MoveArmy {
270                army: self.ids.army,
271                destination: self.ids.eastern_territory,
272            },
273        )
274        .at_time(due_at);
275        let request = CommandRequest::new(
276            CommandRequestId::new(1),
277            self.authority.revision(),
278            envelope,
279        );
280        let ingress = self.authority.enqueue_command(due_at, 0, request)?;
281
282        if self.verbose {
283            println!(
284                "INPUT: queued commander move as {:?}, captured_at={captured_at}, quantized_due_at={due_at}",
285                ingress.ingress_id,
286            );
287        }
288
289        // This drains ingress due at the current canonical time only. The
290        // mid-quantum command remains queued until normal accumulation reaches
291        // its quantized minute; future scheduled work is never jumped to.
292        let receipts = self.authority.advance_canonical(SimDuration::ZERO)?;
293        self.canonical_boundaries += receipts.len();
294        self.refresh_presentation_from_authority();
295        self.presentation.render(self.presentation_time());
296        if self.verbose {
297            self.print_frame(GameSpeed::X1);
298        }
299        self.report_new_events();
300        Ok(())
301    }
Source

pub fn step_canonical(&mut self) -> Result<Option<BoundaryReceipt>, CanwuError>

Source

pub fn advance( &mut self, duration: SimDuration, ) -> Result<Vec<SimEvent>, CanwuError>

Examples found in repository?
examples/move_army.rs (line 14)
3fn main() -> Result<(), Box<dyn std::error::Error>> {
4    let mut canwu = Canwu::demo(35)?;
5    let ids = Canwu::demo_ids();
6
7    canwu.act(
8        ids.commander,
9        SemanticAction::MoveArmy {
10            army: ids.army,
11            destination: ids.eastern_territory,
12        },
13    )?;
14    let arrival_events = canwu.advance(SimDuration::days(1))?;
15
16    let commander = canwu.observe(ids.commander, &ObserveRequest::default())?;
17    let observer = canwu.observe(ids.observer, &ObserveRequest::default())?;
18    println!("time: {}", canwu.time());
19    println!("arrival events: {}", arrival_events.len());
20    println!(
21        "commander knows location: {:?}",
22        commander.known_armies[0].known_location
23    );
24    println!(
25        "observer still believes location: {:?}",
26        observer.known_armies[0].known_location
27    );
28
29    canwu.advance(SimDuration::days(3))?;
30    let observer = canwu.observe(ids.observer, &ObserveRequest::default())?;
31    println!(
32        "observer after report: {:?}",
33        observer.known_armies[0].known_location
34    );
35    Ok(())
36}
Source

pub fn settle_boundary( &mut self, request: BoundaryRequest, ) -> Result<BoundaryReceipt, CanwuError>

Examples found in repository?
examples/phased_boundary.rs (line 155)
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}
Source

pub fn wait( &mut self, duration: SimDuration, ) -> Result<Vec<SimEvent>, CanwuError>

Source

pub fn step(&mut self) -> Result<Vec<SimEvent>, CanwuError>

Source

pub fn snapshot(&self) -> SimulationSnapshot

Source

pub fn snapshot_json(&self) -> Result<String, CanwuError>

Source

pub fn from_snapshot_json(json: &str) -> Result<Self, CanwuError>

Source

pub fn from_snapshot_json_with_plugins( json: &str, plugins: &[&dyn SimulationPlugin], ) -> Result<Self, CanwuError>

Source

pub fn from_checkpoint_and_journal( checkpoint: SimulationCheckpoint, segments: Vec<EvidenceJournalSegment>, ) -> Result<Self, CanwuError>

Source

pub fn from_checkpoint_journal( bundle: CheckpointJournal, ) -> Result<Self, CanwuError>

Source

pub fn from_checkpoint_journal_with_plugins( bundle: CheckpointJournal, plugins: &[&dyn SimulationPlugin], ) -> Result<Self, CanwuError>

Source

pub fn from_checkpoint_journal_json(json: &str) -> Result<Self, CanwuError>

Source

pub fn from_checkpoint_journal_json_with_plugins( json: &str, plugins: &[&dyn SimulationPlugin], ) -> Result<Self, CanwuError>

Source

pub fn replay( seed: u64, scenario: Scenario, commands: &[CommandRecord], final_time: SimTime, ) -> Result<Self, CanwuError>

Source

pub fn replay_with_plugins( seed: u64, scenario: Scenario, plugins: &[&dyn SimulationPlugin], commands: &[CommandRecord], final_time: SimTime, ) -> Result<Self, CanwuError>

Source

pub fn replay_with_boundaries( seed: u64, scenario: Scenario, plugins: &[&dyn SimulationPlugin], commands: &[CommandRecord], boundaries: &[BoundaryRecord], final_time: SimTime, ) -> Result<Self, CanwuError>

Source

pub fn replay_with_run_manifest( seed: u64, scenario: Scenario, run_manifest: RunManifest, plugins: &[&dyn SimulationPlugin], commands: &[CommandRecord], boundaries: &[BoundaryRecord], final_time: SimTime, ) -> Result<Self, CanwuError>

Source

pub fn replay_with_run_configuration( seed: u64, scenario: Scenario, run_manifest: RunManifest, run_configuration: RunConfiguration, plugins: &[&dyn SimulationPlugin], commands: &[CommandRecord], command_attempts: &[CommandAttemptRecord], boundaries: &[BoundaryRecord], final_time: SimTime, ) -> Result<Self, CanwuError>

Source

pub fn replay_from_journal( scenario: Scenario, plugins: &[&dyn SimulationPlugin], journal: &ReplayJournal, ) -> Result<Self, CanwuError>

Source

pub fn fork(&self) -> Self

Source

pub fn diff(&self, other: &Self) -> WorldDiff

Source

pub fn query(&self, query: &Query) -> QueryResult

Source

pub fn query_as( &self, actor: PersonId, query: &Query, ) -> Result<QueryResult, CanwuError>

Source

pub fn viewer_context( &self, actor: PersonId, ) -> Result<ViewerContext, CanwuError>

Builds an authorized viewer context from the persisted run policy.

Callers cannot select a stronger observation policy through an observation request; the run configuration is the input-control boundary for actor-relative versus research projections.

Source

pub fn observe( &self, actor: PersonId, request: &ObserveRequest, ) -> Result<AgentContext, CanwuError>

Examples found in repository?
examples/move_army.rs (line 16)
3fn main() -> Result<(), Box<dyn std::error::Error>> {
4    let mut canwu = Canwu::demo(35)?;
5    let ids = Canwu::demo_ids();
6
7    canwu.act(
8        ids.commander,
9        SemanticAction::MoveArmy {
10            army: ids.army,
11            destination: ids.eastern_territory,
12        },
13    )?;
14    let arrival_events = canwu.advance(SimDuration::days(1))?;
15
16    let commander = canwu.observe(ids.commander, &ObserveRequest::default())?;
17    let observer = canwu.observe(ids.observer, &ObserveRequest::default())?;
18    println!("time: {}", canwu.time());
19    println!("arrival events: {}", arrival_events.len());
20    println!(
21        "commander knows location: {:?}",
22        commander.known_armies[0].known_location
23    );
24    println!(
25        "observer still believes location: {:?}",
26        observer.known_armies[0].known_location
27    );
28
29    canwu.advance(SimDuration::days(3))?;
30    let observer = canwu.observe(ids.observer, &ObserveRequest::default())?;
31    println!(
32        "observer after report: {:?}",
33        observer.known_armies[0].known_location
34    );
35    Ok(())
36}
Source

pub fn observe_with_viewer( &self, viewer: &ViewerContext, request: &ObserveRequest, ) -> Result<AgentContext, CanwuError>

Projects the simulation for a previously authorized viewer context.

The context controls only the player-facing projection. Plugin system subscriptions and state read permissions remain enforced by the simulation runtime and are not widened by this method.

Source

pub fn inspect( &self, actor: PersonId, entity: &EntityRef, detail: DetailLevel, ) -> Result<Inspection, CanwuError>

Source

pub fn available_actions( &self, actor: PersonId, ) -> Result<Vec<AvailableAction>, CanwuError>

Source

pub fn act( &mut self, actor: PersonId, action: SemanticAction, ) -> Result<CommandReceipt, CanwuError>

Examples found in repository?
examples/move_army.rs (lines 7-13)
3fn main() -> Result<(), Box<dyn std::error::Error>> {
4    let mut canwu = Canwu::demo(35)?;
5    let ids = Canwu::demo_ids();
6
7    canwu.act(
8        ids.commander,
9        SemanticAction::MoveArmy {
10            army: ids.army,
11            destination: ids.eastern_territory,
12        },
13    )?;
14    let arrival_events = canwu.advance(SimDuration::days(1))?;
15
16    let commander = canwu.observe(ids.commander, &ObserveRequest::default())?;
17    let observer = canwu.observe(ids.observer, &ObserveRequest::default())?;
18    println!("time: {}", canwu.time());
19    println!("arrival events: {}", arrival_events.len());
20    println!(
21        "commander knows location: {:?}",
22        commander.known_armies[0].known_location
23    );
24    println!(
25        "observer still believes location: {:?}",
26        observer.known_armies[0].known_location
27    );
28
29    canwu.advance(SimDuration::days(3))?;
30    let observer = canwu.observe(ids.observer, &ObserveRequest::default())?;
31    println!(
32        "observer after report: {:?}",
33        observer.known_armies[0].known_location
34    );
35    Ok(())
36}
Source

pub fn explain(&self, request: &ExplanationRequest) -> Explanation

Source

pub fn describe_capabilities(&self) -> CapabilityDescription

Auto Trait Implementations§

§

impl Freeze for Canwu

§

impl RefUnwindSafe for Canwu

§

impl Send for Canwu

§

impl Sync for Canwu

§

impl Unpin for Canwu

§

impl UnsafeUnpin for Canwu

§

impl UnwindSafe for Canwu

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.