Add headless runtime tooling and Campaign.win analysis
This commit is contained in:
parent
57bf0666e0
commit
27172e3786
37 changed files with 11867 additions and 302 deletions
111
crates/rrt-runtime/src/persistence.rs
Normal file
111
crates/rrt-runtime/src/persistence.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
use std::path::Path;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{RuntimeState, RuntimeSummary};
|
||||
|
||||
pub const SNAPSHOT_FORMAT_VERSION: u32 = 1;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
pub struct RuntimeSnapshotSource {
|
||||
#[serde(default)]
|
||||
pub source_fixture_id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct RuntimeSnapshotDocument {
|
||||
pub format_version: u32,
|
||||
pub snapshot_id: String,
|
||||
#[serde(default)]
|
||||
pub source: RuntimeSnapshotSource,
|
||||
pub state: RuntimeState,
|
||||
}
|
||||
|
||||
impl RuntimeSnapshotDocument {
|
||||
pub fn summary(&self) -> RuntimeSummary {
|
||||
RuntimeSummary::from_state(&self.state)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate_runtime_snapshot_document(
|
||||
document: &RuntimeSnapshotDocument,
|
||||
) -> Result<(), String> {
|
||||
if document.format_version != SNAPSHOT_FORMAT_VERSION {
|
||||
return Err(format!(
|
||||
"unsupported snapshot format_version {} (expected {})",
|
||||
document.format_version, SNAPSHOT_FORMAT_VERSION
|
||||
));
|
||||
}
|
||||
if document.snapshot_id.trim().is_empty() {
|
||||
return Err("snapshot_id must not be empty".to_string());
|
||||
}
|
||||
document.state.validate()
|
||||
}
|
||||
|
||||
pub fn load_runtime_snapshot_document(
|
||||
path: &Path,
|
||||
) -> Result<RuntimeSnapshotDocument, Box<dyn std::error::Error>> {
|
||||
let text = std::fs::read_to_string(path)?;
|
||||
Ok(serde_json::from_str(&text)?)
|
||||
}
|
||||
|
||||
pub fn save_runtime_snapshot_document(
|
||||
path: &Path,
|
||||
document: &RuntimeSnapshotDocument,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
validate_runtime_snapshot_document(document)
|
||||
.map_err(|err| format!("invalid runtime snapshot document: {err}"))?;
|
||||
let bytes = serde_json::to_vec_pretty(document)?;
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
std::fs::write(path, bytes)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{CalendarPoint, RuntimeServiceState};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
fn snapshot() -> RuntimeSnapshotDocument {
|
||||
RuntimeSnapshotDocument {
|
||||
format_version: SNAPSHOT_FORMAT_VERSION,
|
||||
snapshot_id: "snapshot-smoke".to_string(),
|
||||
source: RuntimeSnapshotSource {
|
||||
source_fixture_id: Some("fixture-smoke".to_string()),
|
||||
description: Some("test snapshot".to_string()),
|
||||
},
|
||||
state: RuntimeState {
|
||||
calendar: CalendarPoint {
|
||||
year: 1830,
|
||||
month_slot: 0,
|
||||
phase_slot: 0,
|
||||
tick_slot: 0,
|
||||
},
|
||||
world_flags: BTreeMap::new(),
|
||||
companies: Vec::new(),
|
||||
event_runtime_records: Vec::new(),
|
||||
service_state: RuntimeServiceState::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validates_snapshot_document() {
|
||||
let document = snapshot();
|
||||
assert!(validate_runtime_snapshot_document(&document).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrips_snapshot_json() {
|
||||
let document = snapshot();
|
||||
let value = serde_json::to_string_pretty(&document).expect("snapshot should serialize");
|
||||
let reparsed: RuntimeSnapshotDocument =
|
||||
serde_json::from_str(&value).expect("snapshot should deserialize");
|
||||
assert_eq!(document, reparsed);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue