feat: implement FParkan architecture foundation
Add the modular fparkan workspace, domain crates, adapters, apps, xtask policy/CI, acceptance evidence, and licensed corpus gates for the macOS-focused roadmap foundation.
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
#![forbid(unsafe_code)]
|
||||
#![allow(clippy::print_stderr, clippy::print_stdout)]
|
||||
//! `FParkan` headless runtime entrypoint.
|
||||
|
||||
use fparkan_runtime::{
|
||||
create, load_mission, step_headless, EngineConfig, EngineMode, EngineServices, MissionRequest,
|
||||
};
|
||||
use fparkan_vfs::DirectoryVfs;
|
||||
use fparkan_world::InputSnapshot;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn main() {
|
||||
if let Err(err) = run() {
|
||||
eprintln!("{err}");
|
||||
std::process::exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
fn run() -> Result<(), String> {
|
||||
let raw_args: Vec<String> = std::env::args().skip(1).collect();
|
||||
let args = Args::parse(&raw_args)?;
|
||||
let services = if let Some(root) = &args.root {
|
||||
EngineServices::new(Arc::new(DirectoryVfs::new(root)))
|
||||
} else {
|
||||
EngineServices::default()
|
||||
};
|
||||
let mut engine = create(
|
||||
EngineConfig {
|
||||
mode: EngineMode::Headless,
|
||||
},
|
||||
services,
|
||||
)
|
||||
.map_err(|err| format!("{err}"))?;
|
||||
if let Some(mission) = args.mission {
|
||||
let loaded = load_mission(&mut engine, MissionRequest { key: mission })
|
||||
.map_err(|err| format!("{err}"))?;
|
||||
println!(
|
||||
"mission objects={} areals={} surfaces={} graph_roots={} components={} wear={} material_slots={} textures={} lightmaps={} graph_failures={}",
|
||||
loaded.object_count,
|
||||
loaded.areal_count,
|
||||
loaded.surface_count,
|
||||
loaded.graph_root_count,
|
||||
loaded.graph_unit_component_count,
|
||||
loaded.graph_wear_resolved_count,
|
||||
loaded.graph_material_resolved_count,
|
||||
loaded.graph_texture_resolved_count,
|
||||
loaded.graph_lightmap_resolved_count,
|
||||
loaded.graph_failure_count
|
||||
);
|
||||
}
|
||||
let mut last = None;
|
||||
for _ in 0..args.ticks {
|
||||
last = Some(step_headless(&mut engine, InputSnapshot).map_err(|err| format!("{err}"))?);
|
||||
}
|
||||
if let Some(frame) = last {
|
||||
println!(
|
||||
"tick={} hash={:02x?}",
|
||||
frame.snapshot.tick.0, frame.snapshot.hash.0
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct Args {
|
||||
root: Option<PathBuf>,
|
||||
mission: Option<String>,
|
||||
ticks: u64,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
fn parse(args: &[String]) -> Result<Self, String> {
|
||||
let mut parsed = Self {
|
||||
root: None,
|
||||
mission: None,
|
||||
ticks: 1,
|
||||
};
|
||||
let mut iter = args.iter();
|
||||
while let Some(arg) = iter.next() {
|
||||
match arg.as_str() {
|
||||
"--root" => {
|
||||
parsed.root = Some(
|
||||
iter.next()
|
||||
.map(PathBuf::from)
|
||||
.ok_or_else(|| "--root requires a path".to_string())?,
|
||||
);
|
||||
}
|
||||
"--mission" => {
|
||||
parsed.mission = Some(
|
||||
iter.next()
|
||||
.cloned()
|
||||
.ok_or_else(|| "--mission requires a path".to_string())?,
|
||||
);
|
||||
}
|
||||
"--ticks" => {
|
||||
parsed.ticks = iter
|
||||
.next()
|
||||
.ok_or_else(|| "--ticks requires a value".to_string())?
|
||||
.parse()
|
||||
.map_err(|_| "--ticks must be an integer".to_string())?;
|
||||
}
|
||||
_ => return Err(usage()),
|
||||
}
|
||||
}
|
||||
if parsed.mission.is_some() && parsed.root.is_none() {
|
||||
return Err("--mission requires --root".to_string());
|
||||
}
|
||||
Ok(parsed)
|
||||
}
|
||||
}
|
||||
|
||||
fn usage() -> String {
|
||||
"usage: fparkan-headless [--root <path> --mission <path>] [--ticks <n>]".to_string()
|
||||
}
|
||||
Reference in New Issue
Block a user