Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2e193f234
|
||
|
|
66804e0bd2
|
||
|
|
ee9b318172
|
||
|
|
29bf4830cf
|
@@ -17,6 +17,7 @@ use super::{
|
||||
use crate::policy::KHR_PORTABILITY_SUBSET_EXTENSION;
|
||||
use crate::shader_manifest::{triangle_shader_manifest, validate_shader_manifest};
|
||||
|
||||
#[cfg(test)]
|
||||
fn take_runtime_owners_in_dependency_order<Instance, Validation, Surface, Device, Swapchain>(
|
||||
instance: &mut Option<Instance>,
|
||||
validation: &mut Option<Validation>,
|
||||
@@ -31,28 +32,20 @@ fn take_runtime_owners_in_dependency_order<Instance, Validation, Surface, Device
|
||||
instance.take();
|
||||
}
|
||||
|
||||
fn take_runtime_owners_with_validation_snapshot<
|
||||
Instance,
|
||||
Validation,
|
||||
Surface,
|
||||
Device,
|
||||
Swapchain,
|
||||
Snapshot,
|
||||
Capture,
|
||||
>(
|
||||
instance: &mut Option<Instance>,
|
||||
validation: &mut Option<Validation>,
|
||||
fn take_runtime_children_with_validation_snapshot<Surface, Device, Swapchain, Validation, Snapshot, Capture>(
|
||||
surface: &mut Option<Surface>,
|
||||
device: &mut Option<Device>,
|
||||
swapchain: &mut Option<Swapchain>,
|
||||
validation: &Option<Validation>,
|
||||
capture: Capture,
|
||||
) -> Option<Snapshot>
|
||||
where
|
||||
Capture: FnOnce(&Validation) -> Snapshot,
|
||||
{
|
||||
let snapshot = validation.as_ref().map(capture);
|
||||
take_runtime_owners_in_dependency_order(instance, validation, surface, device, swapchain);
|
||||
snapshot
|
||||
swapchain.take();
|
||||
device.take();
|
||||
surface.take();
|
||||
validation.as_ref().map(capture)
|
||||
}
|
||||
|
||||
struct RollbackOnDrop<T, F>
|
||||
@@ -668,15 +661,16 @@ impl VulkanSmokeRenderer {
|
||||
})?;
|
||||
}
|
||||
self.destroy_device_owned_resources();
|
||||
let validation = take_runtime_owners_with_validation_snapshot(
|
||||
&mut self.instance,
|
||||
&mut self.validation,
|
||||
let validation = take_runtime_children_with_validation_snapshot(
|
||||
&mut self.surface,
|
||||
&mut self.device,
|
||||
&mut self.swapchain,
|
||||
&self.validation,
|
||||
VulkanValidationMessenger::report,
|
||||
)
|
||||
.unwrap_or_default();
|
||||
self.validation.take();
|
||||
self.instance.take();
|
||||
Ok(VulkanSmokeShutdownReport {
|
||||
renderer_report: self.report.clone(),
|
||||
swapchain_recreate_count: self.swapchain_recreate_count,
|
||||
@@ -690,14 +684,15 @@ impl VulkanSmokeRenderer {
|
||||
let _ = unsafe { device.device().device_wait_idle() };
|
||||
}
|
||||
self.destroy_device_owned_resources();
|
||||
let _ = take_runtime_owners_with_validation_snapshot(
|
||||
&mut self.instance,
|
||||
&mut self.validation,
|
||||
let _ = take_runtime_children_with_validation_snapshot(
|
||||
&mut self.surface,
|
||||
&mut self.device,
|
||||
&mut self.swapchain,
|
||||
&self.validation,
|
||||
VulkanValidationMessenger::report,
|
||||
);
|
||||
self.validation.take();
|
||||
self.instance.take();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -710,7 +705,7 @@ impl Drop for VulkanSmokeRenderer {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
take_runtime_owners_in_dependency_order, take_runtime_owners_with_validation_snapshot,
|
||||
take_runtime_children_with_validation_snapshot, take_runtime_owners_in_dependency_order,
|
||||
RollbackOnDrop,
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
@@ -850,7 +845,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn final_validation_snapshot_is_captured_before_validation_drop() {
|
||||
fn final_validation_snapshot_is_captured_after_surface_device_swapchain_drop() {
|
||||
let log = Rc::new(RefCell::new(Vec::new()));
|
||||
let mut instance = Some(tracker(TeardownStep::Instance, &log));
|
||||
let mut validation = Some(tracker(TeardownStep::Validation, &log));
|
||||
@@ -858,17 +853,18 @@ mod tests {
|
||||
let mut device = Some(tracker(TeardownStep::Device, &log));
|
||||
let mut swapchain = Some(tracker(TeardownStep::Swapchain, &log));
|
||||
|
||||
let snapshot = take_runtime_owners_with_validation_snapshot(
|
||||
&mut instance,
|
||||
&mut validation,
|
||||
let snapshot = take_runtime_children_with_validation_snapshot(
|
||||
&mut surface,
|
||||
&mut device,
|
||||
&mut swapchain,
|
||||
&validation,
|
||||
|_| {
|
||||
log.borrow_mut().push(TeardownStep::Snapshot);
|
||||
TeardownStep::Validation
|
||||
},
|
||||
);
|
||||
validation.take();
|
||||
instance.take();
|
||||
|
||||
assert_eq!(snapshot, Some(TeardownStep::Validation));
|
||||
assert_eq!(
|
||||
@@ -876,10 +872,10 @@ mod tests {
|
||||
.expect("all drop trackers released")
|
||||
.into_inner(),
|
||||
vec![
|
||||
TeardownStep::Snapshot,
|
||||
TeardownStep::Swapchain,
|
||||
TeardownStep::Device,
|
||||
TeardownStep::Surface,
|
||||
TeardownStep::Snapshot,
|
||||
TeardownStep::Validation,
|
||||
TeardownStep::Instance,
|
||||
]
|
||||
|
||||
+156
-58
@@ -32,11 +32,12 @@ use fparkan_runtime::{
|
||||
};
|
||||
use fparkan_vfs::DirectoryVfs;
|
||||
use serde::Serialize;
|
||||
use std::fmt::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
const ARCHIVE_INSPECT_SCHEMA: &str = "fparkan-archive-inspect-v1";
|
||||
const PROTOTYPE_INSPECT_SCHEMA: &str = "fparkan-prototype-inspect-v1";
|
||||
const MISSION_GRAPH_SCHEMA: &str = "fparkan-mission-graph-v1";
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ArchiveInspectOutput<'a> {
|
||||
@@ -48,6 +49,63 @@ struct ArchiveInspectOutput<'a> {
|
||||
lookup_order_valid: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct PrototypeInspectOutput {
|
||||
schema_version: &'static str,
|
||||
key: String,
|
||||
roots: usize,
|
||||
node_count: usize,
|
||||
edge_count: usize,
|
||||
prototype_requests: usize,
|
||||
resolved: usize,
|
||||
unit_references: usize,
|
||||
unit_components: usize,
|
||||
direct_references: usize,
|
||||
wear_requests: usize,
|
||||
wear: usize,
|
||||
materials: usize,
|
||||
textures: usize,
|
||||
lightmaps: usize,
|
||||
is_success: bool,
|
||||
failures: Vec<GraphFailureOutput>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct MissionGraphOutput {
|
||||
schema_version: &'static str,
|
||||
mission: String,
|
||||
objects: usize,
|
||||
paths: usize,
|
||||
clans: usize,
|
||||
extras: usize,
|
||||
roots: usize,
|
||||
node_count: usize,
|
||||
edge_count: usize,
|
||||
direct_references: usize,
|
||||
unit_references: usize,
|
||||
unit_components: usize,
|
||||
prototype_requests: usize,
|
||||
wear_requests: usize,
|
||||
wear: usize,
|
||||
materials: usize,
|
||||
textures: usize,
|
||||
lightmaps: usize,
|
||||
is_success: bool,
|
||||
failures: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct GraphFailureOutput {
|
||||
root_index: usize,
|
||||
edge: &'static str,
|
||||
requiredness: &'static str,
|
||||
message: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
archive: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
resource: Option<String>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
let result = run(&args);
|
||||
@@ -165,10 +223,13 @@ fn inspect_prototype(args: &[String]) -> Result<(), String> {
|
||||
let vfs = Arc::new(DirectoryVfs::new(root));
|
||||
let repository = CachedResourceRepository::new(vfs.clone());
|
||||
let roots = [resource_name(key.as_bytes())];
|
||||
let (graph, resolved, mut report) =
|
||||
let (mut graph, resolved, mut report) =
|
||||
build_prototype_graph_report(&repository, vfs.as_ref(), &roots);
|
||||
extend_graph_report_with_visual_dependencies(&repository, &mut report, &graph, &resolved);
|
||||
println!("{}", prototype_inspect_json(&key, &graph, &report));
|
||||
extend_graph_report_with_visual_dependencies(&repository, &mut report, &mut graph, &resolved);
|
||||
println!(
|
||||
"{}",
|
||||
prototype_inspect_json(&key, &graph, &report).map_err(|err| err.to_string())?
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -176,22 +237,26 @@ fn prototype_inspect_json(
|
||||
key: &str,
|
||||
graph: &fparkan_prototype::PrototypeGraph,
|
||||
report: &fparkan_prototype::PrototypeGraphReport,
|
||||
) -> String {
|
||||
format!(
|
||||
"{{\"schema_version\":\"fparkan-prototype-inspect-v1\",\"key\":{},\"roots\":{},\"prototype_requests\":{},\"resolved\":{},\"unit_references\":{},\"unit_components\":{},\"direct_references\":{},\"wear\":{},\"materials\":{},\"textures\":{},\"lightmaps\":{},\"failures\":{}}}",
|
||||
json_string(key),
|
||||
report.root_count,
|
||||
graph.prototype_requests.len(),
|
||||
report.resolved_count,
|
||||
report.unit_reference_count,
|
||||
report.unit_component_count,
|
||||
report.direct_reference_count,
|
||||
report.wear_resolved_count,
|
||||
report.material_resolved_count,
|
||||
report.texture_resolved_count,
|
||||
report.lightmap_resolved_count,
|
||||
report.failures.len()
|
||||
)
|
||||
) -> Result<String, serde_json::Error> {
|
||||
serde_json::to_string(&PrototypeInspectOutput {
|
||||
schema_version: PROTOTYPE_INSPECT_SCHEMA,
|
||||
key: key.to_string(),
|
||||
roots: report.root_count,
|
||||
node_count: graph.nodes.len(),
|
||||
edge_count: graph.edges.len(),
|
||||
prototype_requests: graph.prototype_requests.len(),
|
||||
resolved: report.resolved_count,
|
||||
unit_references: report.unit_reference_count,
|
||||
unit_components: report.unit_component_count,
|
||||
direct_references: report.direct_reference_count,
|
||||
wear_requests: report.wear_request_count,
|
||||
wear: report.wear_resolved_count,
|
||||
materials: report.material_resolved_count,
|
||||
textures: report.texture_resolved_count,
|
||||
lightmaps: report.lightmap_resolved_count,
|
||||
is_success: report.is_success(),
|
||||
failures: report.failures.iter().take(16).map(graph_failure_output).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
fn graph_mission(args: &[String]) -> Result<(), String> {
|
||||
@@ -213,22 +278,29 @@ fn graph_mission(args: &[String]) -> Result<(), String> {
|
||||
)
|
||||
.map_err(|err| err.to_string())?;
|
||||
println!(
|
||||
"{{\"schema_version\":\"fparkan-mission-graph-v1\",\"mission\":{},\"objects\":{},\"paths\":{},\"clans\":{},\"extras\":{},\"roots\":{},\"direct_references\":{},\"unit_references\":{},\"unit_components\":{},\"prototype_requests\":{},\"wear\":{},\"materials\":{},\"textures\":{},\"lightmaps\":{},\"failures\":{}}}",
|
||||
json_string(&mission),
|
||||
loaded.object_count,
|
||||
loaded.path_count,
|
||||
loaded.clan_count,
|
||||
loaded.extra_count,
|
||||
loaded.graph_root_count,
|
||||
loaded.graph_direct_reference_count,
|
||||
loaded.graph_unit_reference_count,
|
||||
loaded.graph_unit_component_count,
|
||||
loaded.graph_resolved_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
|
||||
"{}",
|
||||
serialize_json(&MissionGraphOutput {
|
||||
schema_version: MISSION_GRAPH_SCHEMA,
|
||||
mission: mission.clone(),
|
||||
objects: loaded.object_count,
|
||||
paths: loaded.path_count,
|
||||
clans: loaded.clan_count,
|
||||
extras: loaded.extra_count,
|
||||
roots: loaded.graph_root_count,
|
||||
node_count: loaded.graph_node_count,
|
||||
edge_count: loaded.graph_edge_count,
|
||||
direct_references: loaded.graph_direct_reference_count,
|
||||
unit_references: loaded.graph_unit_reference_count,
|
||||
unit_components: loaded.graph_unit_component_count,
|
||||
prototype_requests: loaded.graph_resolved_count,
|
||||
wear_requests: loaded.graph_wear_request_count,
|
||||
wear: loaded.graph_wear_resolved_count,
|
||||
materials: loaded.graph_material_resolved_count,
|
||||
textures: loaded.graph_texture_resolved_count,
|
||||
lightmaps: loaded.graph_lightmap_resolved_count,
|
||||
is_success: loaded.graph_failure_count == 0,
|
||||
failures: loaded.graph_failure_count,
|
||||
})?
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
@@ -290,30 +362,56 @@ fn parse_archive_path(args: &[String]) -> Result<PathBuf, String> {
|
||||
}
|
||||
}
|
||||
|
||||
fn json_string(value: &str) -> String {
|
||||
let mut out = String::with_capacity(value.len() + 2);
|
||||
out.push('"');
|
||||
for ch in value.chars() {
|
||||
match ch {
|
||||
'"' => out.push_str("\\\""),
|
||||
'\\' => out.push_str("\\\\"),
|
||||
'\n' => out.push_str("\\n"),
|
||||
'\r' => out.push_str("\\r"),
|
||||
'\t' => out.push_str("\\t"),
|
||||
c if c.is_control() => {
|
||||
let _ = write!(out, "\\u{:04x}", c as u32);
|
||||
}
|
||||
c => out.push(c),
|
||||
}
|
||||
}
|
||||
out.push('"');
|
||||
out
|
||||
}
|
||||
|
||||
fn serialize_json<T: Serialize>(value: &T) -> Result<String, String> {
|
||||
serde_json::to_string(value).map_err(|err| err.to_string())
|
||||
}
|
||||
|
||||
fn graph_failure_output(
|
||||
failure: &fparkan_prototype::PrototypeGraphFailure,
|
||||
) -> GraphFailureOutput {
|
||||
GraphFailureOutput {
|
||||
root_index: failure.root_index,
|
||||
edge: prototype_graph_edge_label(failure.edge),
|
||||
requiredness: prototype_graph_requiredness_label(failure.requiredness),
|
||||
message: failure.message.clone(),
|
||||
archive: failure
|
||||
.provenance
|
||||
.as_ref()
|
||||
.and_then(|provenance| provenance.archive.clone()),
|
||||
resource: failure.provenance.as_ref().and_then(|provenance| {
|
||||
provenance
|
||||
.resource
|
||||
.as_ref()
|
||||
.map(|raw| String::from_utf8_lossy(raw).into_owned())
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn prototype_graph_edge_label(edge: fparkan_prototype::PrototypeGraphEdge) -> &'static str {
|
||||
match edge {
|
||||
fparkan_prototype::PrototypeGraphEdge::MissionToUnitDat => "mission_to_unit_dat",
|
||||
fparkan_prototype::PrototypeGraphEdge::MissionToObjectsRegistry => {
|
||||
"mission_to_objects_registry"
|
||||
}
|
||||
fparkan_prototype::PrototypeGraphEdge::UnitDatToComponent => "unit_dat_to_component",
|
||||
fparkan_prototype::PrototypeGraphEdge::PrototypeToMesh => "prototype_to_mesh",
|
||||
fparkan_prototype::PrototypeGraphEdge::MeshToWear => "mesh_to_wear",
|
||||
fparkan_prototype::PrototypeGraphEdge::WearToMaterial => "wear_to_material",
|
||||
fparkan_prototype::PrototypeGraphEdge::MaterialToTexture => "material_to_texture",
|
||||
fparkan_prototype::PrototypeGraphEdge::WearToLightmap => "wear_to_lightmap",
|
||||
}
|
||||
}
|
||||
|
||||
fn prototype_graph_requiredness_label(
|
||||
requiredness: fparkan_prototype::PrototypeGraphRequiredness,
|
||||
) -> &'static str {
|
||||
match requiredness {
|
||||
fparkan_prototype::PrototypeGraphRequiredness::Required => "required",
|
||||
fparkan_prototype::PrototypeGraphRequiredness::Optional => "optional",
|
||||
fparkan_prototype::PrototypeGraphRequiredness::Fallback => "fallback",
|
||||
}
|
||||
}
|
||||
|
||||
fn usage() -> String {
|
||||
"usage: fparkan corpus discover|validate --root <path> [--format json] | archive inspect <file> [--format json] | prototype inspect --root <path> --key <key> [--format json] | mission graph --root <path> --mission <path> [--format json]".to_string()
|
||||
}
|
||||
@@ -367,11 +465,11 @@ mod tests {
|
||||
..fparkan_prototype::PrototypeGraphReport::default()
|
||||
};
|
||||
|
||||
let json = prototype_inspect_json("root", &graph, &report);
|
||||
let json = prototype_inspect_json("root", &graph, &report).expect("serialize");
|
||||
|
||||
assert_eq!(
|
||||
json,
|
||||
"{\"schema_version\":\"fparkan-prototype-inspect-v1\",\"key\":\"root\",\"roots\":1,\"prototype_requests\":1,\"resolved\":1,\"unit_references\":0,\"unit_components\":0,\"direct_references\":1,\"wear\":0,\"materials\":0,\"textures\":0,\"lightmaps\":0,\"failures\":0}"
|
||||
"{\"schema_version\":\"fparkan-prototype-inspect-v1\",\"key\":\"root\",\"roots\":1,\"node_count\":0,\"edge_count\":0,\"prototype_requests\":1,\"resolved\":1,\"unit_references\":0,\"unit_components\":0,\"direct_references\":1,\"wear_requests\":0,\"wear\":0,\"materials\":0,\"textures\":0,\"lightmaps\":0,\"is_success\":true,\"failures\":[]}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+811
-131
File diff suppressed because it is too large
Load Diff
@@ -179,6 +179,16 @@ pub enum PrototypeGraphNodeKind {
|
||||
Prototype,
|
||||
/// Mesh dependency.
|
||||
MeshResource,
|
||||
/// WEAR dependency.
|
||||
WearResource,
|
||||
/// MAT0 dependency.
|
||||
MaterialResource,
|
||||
/// TEXM texture dependency.
|
||||
TextureResource,
|
||||
/// TEXM lightmap dependency.
|
||||
LightmapResource,
|
||||
/// Effect dependency placeholder for later stages.
|
||||
EffectResource,
|
||||
/// Non-geometric prototype.
|
||||
NonGeometric,
|
||||
}
|
||||
@@ -197,6 +207,17 @@ pub struct PrototypeGraphNode {
|
||||
}
|
||||
|
||||
impl PrototypeGraphNode {
|
||||
/// Creates a typed resource node.
|
||||
#[must_use]
|
||||
pub fn resource(kind: PrototypeGraphNodeKind, resource: ResourceKey, id: PrototypeGraphNodeId) -> Self {
|
||||
Self {
|
||||
id,
|
||||
kind,
|
||||
key: None,
|
||||
resource: Some(resource),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a mesh resource node.
|
||||
#[must_use]
|
||||
pub const fn mesh(resource: ResourceKey, id: PrototypeGraphNodeId) -> Self {
|
||||
@@ -244,6 +265,16 @@ pub enum PrototypeGraphEdgeKind {
|
||||
UnitDatToComponent,
|
||||
/// Prototype to mesh dependency.
|
||||
PrototypeToMesh,
|
||||
/// Mesh resource to WEAR table.
|
||||
MeshToWear,
|
||||
/// WEAR table to MAT0 material.
|
||||
WearToMaterial,
|
||||
/// MAT0 material to TEXM texture.
|
||||
MaterialToTexture,
|
||||
/// WEAR table to TEXM lightmap.
|
||||
WearToLightmap,
|
||||
/// MAT0 material to effect dependency.
|
||||
MaterialToEffect,
|
||||
}
|
||||
|
||||
/// Prototype graph edge record.
|
||||
@@ -640,22 +671,20 @@ fn resolve_unit_dat_prototype_requests(
|
||||
};
|
||||
|
||||
if let Ok(unit) = decode_unit_dat(&bytes) {
|
||||
if !unit.records.is_empty() {
|
||||
let mut prototypes = Vec::with_capacity(unit.records.len());
|
||||
for record in &unit.records {
|
||||
let prototype = resolve_unit_component(repository, record)?.ok_or_else(|| {
|
||||
PrototypeError::Resource(ResourceError::Format(format!(
|
||||
"unit component {} did not resolve",
|
||||
String::from_utf8_lossy(cstr_bytes(&record.resource_raw))
|
||||
)))
|
||||
})?;
|
||||
prototypes.push(prototype);
|
||||
}
|
||||
return Ok(ResolvedPrototypeRequests {
|
||||
expected_count: unit.records.len(),
|
||||
prototypes,
|
||||
});
|
||||
let mut prototypes = Vec::with_capacity(unit.records.len());
|
||||
for record in &unit.records {
|
||||
let prototype = resolve_unit_component(repository, record)?.ok_or_else(|| {
|
||||
PrototypeError::Resource(ResourceError::Format(format!(
|
||||
"unit component {} did not resolve",
|
||||
String::from_utf8_lossy(cstr_bytes(&record.resource_raw))
|
||||
)))
|
||||
})?;
|
||||
prototypes.push(prototype);
|
||||
}
|
||||
return Ok(ResolvedPrototypeRequests {
|
||||
expected_count: unit.records.len(),
|
||||
prototypes,
|
||||
});
|
||||
}
|
||||
|
||||
let binding = decode_unit_dat_binding(&bytes)?;
|
||||
@@ -906,8 +935,31 @@ pub fn build_prototype_graph_report(
|
||||
}
|
||||
|
||||
fn graph_error_edge(edge: PrototypeGraphEdge, err: &PrototypeError) -> PrototypeGraphEdge {
|
||||
let _ = err;
|
||||
edge
|
||||
match err {
|
||||
PrototypeError::Decode(_)
|
||||
| PrototypeError::InvalidSize
|
||||
| PrototypeError::InvalidUnitDatMagic(_)
|
||||
if edge == PrototypeGraphEdge::MissionToUnitDat =>
|
||||
{
|
||||
PrototypeGraphEdge::MissionToUnitDat
|
||||
}
|
||||
PrototypeError::Resource(ResourceError::Format(message))
|
||||
if message.starts_with("missing unit DAT:") =>
|
||||
{
|
||||
PrototypeGraphEdge::MissionToUnitDat
|
||||
}
|
||||
PrototypeError::Resource(ResourceError::Format(message))
|
||||
if message.starts_with("unit component ") =>
|
||||
{
|
||||
PrototypeGraphEdge::UnitDatToComponent
|
||||
}
|
||||
PrototypeError::Resource(ResourceError::Format(message))
|
||||
if message.contains("explicit mesh reference missing:") =>
|
||||
{
|
||||
PrototypeGraphEdge::PrototypeToMesh
|
||||
}
|
||||
_ => edge,
|
||||
}
|
||||
}
|
||||
|
||||
fn provenance_for_root(root_index: usize, root: &ResourceName) -> PrototypeGraphProvenance {
|
||||
@@ -1581,6 +1633,69 @@ mod tests {
|
||||
assert!(err.to_string().contains("missing unit DAT"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_unit_dat_resolves_as_zero_component_root() {
|
||||
let mut vfs = MemoryVfs::default();
|
||||
let dat_path = resource_archive_path(b"UNITS/AUTO/empty.dat").expect("dat path");
|
||||
vfs.insert(dat_path, Arc::from(vec![0_u8; 8].into_boxed_slice()));
|
||||
let vfs = Arc::new(vfs);
|
||||
let repo = CachedResourceRepository::new(vfs.clone());
|
||||
let roots = [resource_name(b"UNITS/AUTO/empty.dat")];
|
||||
|
||||
let (graph, resolved, report) = build_prototype_graph_report(&repo, vfs.as_ref(), &roots);
|
||||
|
||||
assert_eq!(graph.roots.len(), 1);
|
||||
assert!(graph.prototype_requests.is_empty());
|
||||
assert!(resolved.is_empty());
|
||||
assert_eq!(report.unit_reference_count, 1);
|
||||
assert_eq!(report.unit_component_count, 0);
|
||||
assert_eq!(report.resolved_count, 0);
|
||||
assert!(report.failures.is_empty());
|
||||
assert!(report.is_success());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_unit_dat_failure_is_classified_on_unit_edge() {
|
||||
let mut vfs = MemoryVfs::default();
|
||||
let dat_path = resource_archive_path(b"UNITS/AUTO/bad.dat").expect("dat path");
|
||||
vfs.insert(dat_path, Arc::from([1_u8, 2, 3].to_vec().into_boxed_slice()));
|
||||
let vfs = Arc::new(vfs);
|
||||
let repo = CachedResourceRepository::new(vfs.clone());
|
||||
|
||||
let (_graph, _resolved, report) =
|
||||
build_prototype_graph_report(&repo, vfs.as_ref(), &[resource_name(b"UNITS/AUTO/bad.dat")]);
|
||||
|
||||
assert_eq!(report.failures.len(), 1);
|
||||
assert_eq!(report.failures[0].edge, PrototypeGraphEdge::MissionToUnitDat);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_unit_component_failure_is_classified_on_component_edge() {
|
||||
let mut vfs = MemoryVfs::default();
|
||||
let dat_path = resource_archive_path(b"UNITS/AUTO/compound.dat").expect("dat path");
|
||||
let objects_path = resource_archive_path(b"objects.rlb").expect("objects path");
|
||||
vfs.insert(
|
||||
dat_path,
|
||||
Arc::from(
|
||||
build_unit_dat(&[(b"objects.rlb".as_slice(), b"missing_component".as_slice())])
|
||||
.into_boxed_slice(),
|
||||
),
|
||||
);
|
||||
vfs.insert(objects_path, Arc::from(build_nres(&[]).into_boxed_slice()));
|
||||
let vfs = Arc::new(vfs);
|
||||
let repo = CachedResourceRepository::new(vfs.clone());
|
||||
|
||||
let (_graph, _resolved, report) = build_prototype_graph_report(
|
||||
&repo,
|
||||
vfs.as_ref(),
|
||||
&[resource_name(b"UNITS/AUTO/compound.dat")],
|
||||
);
|
||||
|
||||
assert_eq!(report.failures.len(), 1);
|
||||
assert_eq!(report.failures[0].edge, PrototypeGraphEdge::UnitDatToComponent);
|
||||
assert!(report.failures[0].message.contains("missing_component"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_dat_expands_components_in_order() {
|
||||
let mut vfs = MemoryVfs::default();
|
||||
@@ -1896,7 +2011,7 @@ mod tests {
|
||||
assert_eq!(report.failures[0].resource_raw, b"broken");
|
||||
assert_eq!(
|
||||
report.failures[0].edge,
|
||||
PrototypeGraphEdge::MissionToObjectsRegistry
|
||||
PrototypeGraphEdge::PrototypeToMesh
|
||||
);
|
||||
assert!(report.failures[0].message.contains("broken"));
|
||||
assert!(report.failures[0]
|
||||
@@ -1929,6 +2044,7 @@ mod tests {
|
||||
|
||||
assert_eq!(report.failures.len(), 1);
|
||||
assert_eq!(report.failures[0].resource_raw, b"broken");
|
||||
assert_eq!(report.failures[0].edge, PrototypeGraphEdge::PrototypeToMesh);
|
||||
assert!(report.failures[0]
|
||||
.message
|
||||
.contains("static.rlb:missing.msh"));
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
use fparkan_assets::{
|
||||
decode_mission_land_path, decode_mission_payload, decode_nres_payload,
|
||||
derive_mission_land_paths, extend_graph_report_with_visual_dependencies, prepare_terrain_world,
|
||||
AssetError as AssetPreparationError, AssetManager, BuildCategory, MissionAssetPlan,
|
||||
MissionDocument, MissionError, MissionTerrainPaths, NresError, TerrainFormatError,
|
||||
TerrainPreparationError, TerrainWorld, TmaProfile,
|
||||
AssetError as AssetPreparationError, AssetId, AssetManager, BuildCategory, MissionAssetPlan,
|
||||
MissionDocument, MissionError, MissionTerrainPaths, NresError, PreparedVisual,
|
||||
TerrainFormatError, TerrainPreparationError, TerrainWorld, TmaProfile,
|
||||
};
|
||||
use fparkan_path::{normalize_relative, NormalizedPath, PathError, PathPolicy};
|
||||
use fparkan_prototype::{
|
||||
@@ -149,6 +149,36 @@ pub struct MissionLoadTrace {
|
||||
pub transforms: Vec<PlacedTransformProfile>,
|
||||
}
|
||||
|
||||
/// Ordered mission property preserved for later runtime stages.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct MissionObjectProperty {
|
||||
/// Raw property value words.
|
||||
pub raw_value: [u32; 4],
|
||||
/// Raw property name bytes.
|
||||
pub name_raw: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Mission-derived object draft preserved for Stage 3 viewer/player work.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct MissionObjectDraft {
|
||||
/// Original mission object id.
|
||||
pub original_id: Option<OriginalObjectId>,
|
||||
/// Raw mission resource reference.
|
||||
pub resource_name_raw: Vec<u8>,
|
||||
/// Raw identity/clan word from the mission.
|
||||
pub identity_or_clan_raw: u32,
|
||||
/// Raw position vector.
|
||||
pub position: [f32; 3],
|
||||
/// Raw orientation vector.
|
||||
pub orientation_raw: [f32; 3],
|
||||
/// Raw scale vector.
|
||||
pub scale: [f32; 3],
|
||||
/// Prepared visuals reachable from this mission object.
|
||||
pub visual_ids: Vec<AssetId<PreparedVisual>>,
|
||||
/// Ordered mission properties.
|
||||
pub properties: Vec<MissionObjectProperty>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
struct MissionLoadOptions {
|
||||
fail_after_registered_objects: Option<usize>,
|
||||
@@ -187,6 +217,10 @@ pub struct LoadedMission {
|
||||
pub graph_unit_component_count: usize,
|
||||
/// Mission prototype graph root count.
|
||||
pub graph_root_count: usize,
|
||||
/// Total materialized graph node count after visual dependency expansion.
|
||||
pub graph_node_count: usize,
|
||||
/// Total materialized graph edge count after visual dependency expansion.
|
||||
pub graph_edge_count: usize,
|
||||
/// Mission asset plan visual count after dependency preparation.
|
||||
pub asset_visual_count: usize,
|
||||
/// Expanded prototype requests resolved to effective prototypes.
|
||||
@@ -254,6 +288,7 @@ struct LoadedMissionState {
|
||||
prototype_report: PrototypeGraphReport,
|
||||
mission_assets: MissionAssets,
|
||||
asset_plan: MissionAssetPlan,
|
||||
object_drafts: Vec<MissionObjectDraft>,
|
||||
}
|
||||
|
||||
/// Engine error.
|
||||
@@ -516,12 +551,12 @@ fn load_mission_with_options(
|
||||
.iter()
|
||||
.map(|object| resource_name(&object.resource_name.raw))
|
||||
.collect();
|
||||
let (prototype_graph, resolved_prototypes, mut prototype_report) =
|
||||
let (mut prototype_graph, resolved_prototypes, mut prototype_report) =
|
||||
build_prototype_graph_report(&repository, vfs.as_ref(), &graph_roots);
|
||||
extend_graph_report_with_visual_dependencies(
|
||||
&repository,
|
||||
&mut prototype_report,
|
||||
&prototype_graph,
|
||||
&mut prototype_graph,
|
||||
&resolved_prototypes,
|
||||
);
|
||||
if !prototype_report.is_success() {
|
||||
@@ -539,6 +574,28 @@ fn load_mission_with_options(
|
||||
source,
|
||||
})?;
|
||||
let mission_asset_plan = mission_assets.to_plan();
|
||||
let object_drafts: Vec<_> = mission
|
||||
.objects
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, object)| MissionObjectDraft {
|
||||
original_id: u32::try_from(index).ok().map(OriginalObjectId),
|
||||
resource_name_raw: object.resource_raw.clone(),
|
||||
identity_or_clan_raw: object.identity_or_clan_raw,
|
||||
position: object.position,
|
||||
orientation_raw: object.orientation,
|
||||
scale: object.scale,
|
||||
visual_ids: mission_assets.visuals_for_object(index).to_vec(),
|
||||
properties: object
|
||||
.properties
|
||||
.iter()
|
||||
.map(|property| MissionObjectProperty {
|
||||
raw_value: property.raw_value,
|
||||
name_raw: property.name_raw.clone(),
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.collect();
|
||||
trace.phases.push(MissionLoadPhase::Assets);
|
||||
|
||||
let mut new_runtime_world = new_world(WorldConfig);
|
||||
@@ -580,6 +637,8 @@ fn load_mission_with_options(
|
||||
graph_direct_reference_count: prototype_report.direct_reference_count,
|
||||
graph_unit_component_count: prototype_report.unit_component_count,
|
||||
graph_root_count: prototype_report.root_count,
|
||||
graph_node_count: prototype_graph.nodes.len(),
|
||||
graph_edge_count: prototype_graph.edges.len(),
|
||||
asset_visual_count: mission_asset_plan.visual_count,
|
||||
graph_resolved_count: prototype_report.resolved_count,
|
||||
graph_mesh_dependency_count: prototype_report.mesh_dependency_count,
|
||||
@@ -608,6 +667,7 @@ fn load_mission_with_options(
|
||||
prototype_report,
|
||||
mission_assets,
|
||||
asset_plan: mission_asset_plan,
|
||||
object_drafts,
|
||||
});
|
||||
Ok((summary, trace))
|
||||
}
|
||||
@@ -697,6 +757,15 @@ pub fn loaded_mission_assets(engine: &Engine) -> Option<&MissionAssets> {
|
||||
engine.loaded.as_ref().map(|state| &state.mission_assets)
|
||||
}
|
||||
|
||||
/// Returns mission-derived object drafts preserved for later runtime stages.
|
||||
#[must_use]
|
||||
pub fn loaded_mission_object_drafts(engine: &Engine) -> Option<&[MissionObjectDraft]> {
|
||||
engine
|
||||
.loaded
|
||||
.as_ref()
|
||||
.map(|state| state.object_drafts.as_slice())
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum SchedulerPresentation {
|
||||
Headless,
|
||||
|
||||
Reference in New Issue
Block a user