pub enum EventKind {
MoveOrdered {
army: ArmyId,
from: TerritoryId,
to: TerritoryId,
arrival_at: SimTime,
},
ArmyArrived {
army: ArmyId,
territory: TerritoryId,
},
ReportDispatched {
recipient: PersonId,
army: ArmyId,
arrives_at: SimTime,
},
KnowledgeUpdated {
recipient: PersonId,
army: ArmyId,
known_location: TerritoryId,
},
DebugFieldChanged {
entity: EntityRef,
field: String,
old_value: String,
new_value: String,
},
Plugin {
plugin: String,
event_type: String,
},
}Variants§
Implementations§
Source§impl EventKind
impl EventKind
pub const fn event_type(&self) -> &'static str
Sourcepub fn qualified_event_type(&self) -> String
pub fn qualified_event_type(&self) -> String
Returns the stable, display-oriented type identity for this event.
Built-in event kinds retain the same snake-case labels returned by
Self::event_type. Plugin events are qualified with their registered
plugin name and event type, separated by a dot. The two plugin-provided
components are preserved as registered; no case folding or additional
normalization is applied.
Examples found in repository?
examples/continuous_game_loop.rs (line 351)
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}Trait Implementations§
Source§impl<'de> Deserialize<'de> for EventKind
impl<'de> Deserialize<'de> for EventKind
Source§fn deserialize<__D>(
__deserializer: __D,
) -> Result<EventKind, <__D as Deserializer<'de>>::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(
__deserializer: __D,
) -> Result<EventKind, <__D as Deserializer<'de>>::Error>where
__D: Deserializer<'de>,
Deserialize this value from the given Serde deserializer. Read more
impl Eq for EventKind
Source§impl Serialize for EventKind
impl Serialize for EventKind
Source§fn serialize<__S>(
&self,
__serializer: __S,
) -> Result<<__S as Serializer>::Ok, <__S as Serializer>::Error>where
__S: Serializer,
fn serialize<__S>(
&self,
__serializer: __S,
) -> Result<<__S as Serializer>::Ok, <__S as Serializer>::Error>where
__S: Serializer,
Serialize this value into the given Serde serializer. Read more
impl StructuralPartialEq for EventKind
Auto Trait Implementations§
impl Freeze for EventKind
impl RefUnwindSafe for EventKind
impl Send for EventKind
impl Sync for EventKind
impl Unpin for EventKind
impl UnsafeUnpin for EventKind
impl UnwindSafe for EventKind
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more