feat(stage2): persist prepared mission assets and reports
This commit is contained in:
@@ -17,6 +17,7 @@ use super::{
|
|||||||
use crate::policy::KHR_PORTABILITY_SUBSET_EXTENSION;
|
use crate::policy::KHR_PORTABILITY_SUBSET_EXTENSION;
|
||||||
use crate::shader_manifest::{triangle_shader_manifest, validate_shader_manifest};
|
use crate::shader_manifest::{triangle_shader_manifest, validate_shader_manifest};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
fn take_runtime_owners_in_dependency_order<Instance, Validation, Surface, Device, Swapchain>(
|
fn take_runtime_owners_in_dependency_order<Instance, Validation, Surface, Device, Swapchain>(
|
||||||
instance: &mut Option<Instance>,
|
instance: &mut Option<Instance>,
|
||||||
validation: &mut Option<Validation>,
|
validation: &mut Option<Validation>,
|
||||||
|
|||||||
+154
-56
@@ -32,11 +32,12 @@ use fparkan_runtime::{
|
|||||||
};
|
};
|
||||||
use fparkan_vfs::DirectoryVfs;
|
use fparkan_vfs::DirectoryVfs;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::fmt::Write;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
const ARCHIVE_INSPECT_SCHEMA: &str = "fparkan-archive-inspect-v1";
|
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)]
|
#[derive(Serialize)]
|
||||||
struct ArchiveInspectOutput<'a> {
|
struct ArchiveInspectOutput<'a> {
|
||||||
@@ -48,6 +49,63 @@ struct ArchiveInspectOutput<'a> {
|
|||||||
lookup_order_valid: Option<bool>,
|
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() {
|
fn main() {
|
||||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||||
let result = run(&args);
|
let result = run(&args);
|
||||||
@@ -168,7 +226,10 @@ fn inspect_prototype(args: &[String]) -> Result<(), String> {
|
|||||||
let (mut graph, resolved, mut report) =
|
let (mut graph, resolved, mut report) =
|
||||||
build_prototype_graph_report(&repository, vfs.as_ref(), &roots);
|
build_prototype_graph_report(&repository, vfs.as_ref(), &roots);
|
||||||
extend_graph_report_with_visual_dependencies(&repository, &mut report, &mut graph, &resolved);
|
extend_graph_report_with_visual_dependencies(&repository, &mut report, &mut graph, &resolved);
|
||||||
println!("{}", prototype_inspect_json(&key, &graph, &report));
|
println!(
|
||||||
|
"{}",
|
||||||
|
prototype_inspect_json(&key, &graph, &report).map_err(|err| err.to_string())?
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,22 +237,26 @@ fn prototype_inspect_json(
|
|||||||
key: &str,
|
key: &str,
|
||||||
graph: &fparkan_prototype::PrototypeGraph,
|
graph: &fparkan_prototype::PrototypeGraph,
|
||||||
report: &fparkan_prototype::PrototypeGraphReport,
|
report: &fparkan_prototype::PrototypeGraphReport,
|
||||||
) -> String {
|
) -> Result<String, serde_json::Error> {
|
||||||
format!(
|
serde_json::to_string(&PrototypeInspectOutput {
|
||||||
"{{\"schema_version\":\"fparkan-prototype-inspect-v1\",\"key\":{},\"roots\":{},\"prototype_requests\":{},\"resolved\":{},\"unit_references\":{},\"unit_components\":{},\"direct_references\":{},\"wear\":{},\"materials\":{},\"textures\":{},\"lightmaps\":{},\"failures\":{}}}",
|
schema_version: PROTOTYPE_INSPECT_SCHEMA,
|
||||||
json_string(key),
|
key: key.to_string(),
|
||||||
report.root_count,
|
roots: report.root_count,
|
||||||
graph.prototype_requests.len(),
|
node_count: graph.nodes.len(),
|
||||||
report.resolved_count,
|
edge_count: graph.edges.len(),
|
||||||
report.unit_reference_count,
|
prototype_requests: graph.prototype_requests.len(),
|
||||||
report.unit_component_count,
|
resolved: report.resolved_count,
|
||||||
report.direct_reference_count,
|
unit_references: report.unit_reference_count,
|
||||||
report.wear_resolved_count,
|
unit_components: report.unit_component_count,
|
||||||
report.material_resolved_count,
|
direct_references: report.direct_reference_count,
|
||||||
report.texture_resolved_count,
|
wear_requests: report.wear_request_count,
|
||||||
report.lightmap_resolved_count,
|
wear: report.wear_resolved_count,
|
||||||
report.failures.len()
|
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> {
|
fn graph_mission(args: &[String]) -> Result<(), String> {
|
||||||
@@ -213,22 +278,29 @@ fn graph_mission(args: &[String]) -> Result<(), String> {
|
|||||||
)
|
)
|
||||||
.map_err(|err| err.to_string())?;
|
.map_err(|err| err.to_string())?;
|
||||||
println!(
|
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),
|
serialize_json(&MissionGraphOutput {
|
||||||
loaded.object_count,
|
schema_version: MISSION_GRAPH_SCHEMA,
|
||||||
loaded.path_count,
|
mission: mission.clone(),
|
||||||
loaded.clan_count,
|
objects: loaded.object_count,
|
||||||
loaded.extra_count,
|
paths: loaded.path_count,
|
||||||
loaded.graph_root_count,
|
clans: loaded.clan_count,
|
||||||
loaded.graph_direct_reference_count,
|
extras: loaded.extra_count,
|
||||||
loaded.graph_unit_reference_count,
|
roots: loaded.graph_root_count,
|
||||||
loaded.graph_unit_component_count,
|
node_count: loaded.graph_node_count,
|
||||||
loaded.graph_resolved_count,
|
edge_count: loaded.graph_edge_count,
|
||||||
loaded.graph_wear_resolved_count,
|
direct_references: loaded.graph_direct_reference_count,
|
||||||
loaded.graph_material_resolved_count,
|
unit_references: loaded.graph_unit_reference_count,
|
||||||
loaded.graph_texture_resolved_count,
|
unit_components: loaded.graph_unit_component_count,
|
||||||
loaded.graph_lightmap_resolved_count,
|
prototype_requests: loaded.graph_resolved_count,
|
||||||
loaded.graph_failure_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(())
|
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> {
|
fn serialize_json<T: Serialize>(value: &T) -> Result<String, String> {
|
||||||
serde_json::to_string(value).map_err(|err| err.to_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 {
|
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()
|
"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()
|
..fparkan_prototype::PrototypeGraphReport::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let json = prototype_inspect_json("root", &graph, &report);
|
let json = prototype_inspect_json("root", &graph, &report).expect("serialize");
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
json,
|
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\":[]}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,12 @@
|
|||||||
)]
|
)]
|
||||||
//! Asset manager ports and transactional preparation models.
|
//! Asset manager ports and transactional preparation models.
|
||||||
|
|
||||||
use fparkan_material::{decode_wear, resolve_material, MaterialError, MAT0_KIND, WEAR_KIND};
|
use fparkan_material::{
|
||||||
|
decode_wear, resolve_material, Mat0Document, MaterialError, WearTable, MAT0_KIND, WEAR_KIND,
|
||||||
|
};
|
||||||
use fparkan_mission_format::{decode_tma, decode_tma_land_path};
|
use fparkan_mission_format::{decode_tma, decode_tma_land_path};
|
||||||
pub use fparkan_mission_format::{LpString, MissionDocument, MissionError, TmaProfile};
|
pub use fparkan_mission_format::{LpString, MissionDocument, MissionError, TmaProfile};
|
||||||
use fparkan_msh::{decode_msh, validate_msh, MshError};
|
use fparkan_msh::{decode_msh, validate_msh, ModelAsset, MshError};
|
||||||
use fparkan_nres::{decode as decode_nres, ReadProfile};
|
use fparkan_nres::{decode as decode_nres, ReadProfile};
|
||||||
pub use fparkan_nres::{NresDocument, NresError};
|
pub use fparkan_nres::{NresDocument, NresError};
|
||||||
use fparkan_path::{normalize_relative, NormalizedPath, PathError, PathPolicy, ResourceName};
|
use fparkan_path::{normalize_relative, NormalizedPath, PathError, PathPolicy, ResourceName};
|
||||||
@@ -36,7 +38,7 @@ use fparkan_resource::{ResourceError, ResourceKey, ResourceRepository};
|
|||||||
pub use fparkan_terrain::{TerrainError, TerrainWorld};
|
pub use fparkan_terrain::{TerrainError, TerrainWorld};
|
||||||
use fparkan_terrain_format::{decode_build_dat, decode_land_map, decode_land_msh};
|
use fparkan_terrain_format::{decode_build_dat, decode_land_map, decode_land_msh};
|
||||||
pub use fparkan_terrain_format::{BuildCategory, TerrainFormatError};
|
pub use fparkan_terrain_format::{BuildCategory, TerrainFormatError};
|
||||||
use fparkan_texm::{decode_texm, TexmError};
|
use fparkan_texm::{decode_texm, TexmDocument, TexmError};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
@@ -217,6 +219,10 @@ pub struct PreparedVisual {
|
|||||||
pub id: AssetId<PreparedVisual>,
|
pub id: AssetId<PreparedVisual>,
|
||||||
/// Optional mesh resource backing the visual.
|
/// Optional mesh resource backing the visual.
|
||||||
pub mesh: Option<ResourceKey>,
|
pub mesh: Option<ResourceKey>,
|
||||||
|
/// Prepared model backing the visual, when geometry is present.
|
||||||
|
pub model_id: Option<AssetId<PreparedModel>>,
|
||||||
|
/// Prepared WEAR table backing the visual, when geometry is present.
|
||||||
|
pub wear_id: Option<AssetId<PreparedWear>>,
|
||||||
/// Number of validated model nodes.
|
/// Number of validated model nodes.
|
||||||
pub model_nodes: usize,
|
pub model_nodes: usize,
|
||||||
/// Number of validated material slots on the model.
|
/// Number of validated material slots on the model.
|
||||||
@@ -227,19 +233,77 @@ pub struct PreparedVisual {
|
|||||||
pub material_count: usize,
|
pub material_count: usize,
|
||||||
/// Typed material IDs available from the resolved visual.
|
/// Typed material IDs available from the resolved visual.
|
||||||
pub material_ids: Vec<AssetId<PreparedMaterial>>,
|
pub material_ids: Vec<AssetId<PreparedMaterial>>,
|
||||||
|
/// Typed texture IDs available from the resolved visual.
|
||||||
|
pub texture_ids: Vec<AssetId<PreparedTexture>>,
|
||||||
|
/// Typed lightmap IDs available from the resolved visual.
|
||||||
|
pub lightmap_ids: Vec<AssetId<PreparedTexture>>,
|
||||||
/// Number of texture phase requests decoded as TEXM.
|
/// Number of texture phase requests decoded as TEXM.
|
||||||
pub texture_count: usize,
|
pub texture_count: usize,
|
||||||
/// Number of lightmap requests decoded as TEXM.
|
/// Number of lightmap requests decoded as TEXM.
|
||||||
pub lightmap_count: usize,
|
pub lightmap_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// CPU-side validated model ready for a renderer upload path.
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct PreparedModel {
|
||||||
|
/// Stable id derived from the visual source.
|
||||||
|
pub id: AssetId<PreparedModel>,
|
||||||
|
/// Source mesh resource.
|
||||||
|
pub source: ResourceKey,
|
||||||
|
/// Fully validated model payload.
|
||||||
|
pub validated: ModelAsset,
|
||||||
|
/// Mesh dependencies that led to this prepared model.
|
||||||
|
pub mesh_dependencies: Vec<ResourceKey>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CPU-side WEAR table resolved for a visual.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct PreparedWear {
|
||||||
|
/// Stable id derived from the visual source.
|
||||||
|
pub id: AssetId<PreparedWear>,
|
||||||
|
/// Source WEAR resource.
|
||||||
|
pub source: ResourceKey,
|
||||||
|
/// Decoded WEAR table.
|
||||||
|
pub table: WearTable,
|
||||||
|
}
|
||||||
|
|
||||||
/// CPU-side data needed before a material can be handed to a renderer.
|
/// CPU-side data needed before a material can be handed to a renderer.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct PreparedMaterial {
|
pub struct PreparedMaterial {
|
||||||
/// Stable id derived from the visual and material selector.
|
/// Stable id derived from the visual and material selector.
|
||||||
pub id: AssetId<PreparedMaterial>,
|
pub id: AssetId<PreparedMaterial>,
|
||||||
/// Parsed material key.
|
/// Source MAT0 resource.
|
||||||
|
pub source: ResourceKey,
|
||||||
|
/// Parsed material key retained for compatibility with older callers.
|
||||||
pub name: ResourceName,
|
pub name: ResourceName,
|
||||||
|
/// Decoded MAT0 payload.
|
||||||
|
pub mat0: Mat0Document,
|
||||||
|
/// Texture requests declared by MAT0 phases.
|
||||||
|
pub texture_requests: Vec<ResourceName>,
|
||||||
|
/// Lightmap requests associated with the owning WEAR table.
|
||||||
|
pub lightmap_requests: Vec<ResourceName>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Texture usage role inside a prepared visual.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum PreparedTextureUsage {
|
||||||
|
/// Standard diffuse/albedo texture.
|
||||||
|
Diffuse,
|
||||||
|
/// Lightmap texture.
|
||||||
|
Lightmap,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CPU-side TEXM texture ready for a renderer upload path.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct PreparedTexture {
|
||||||
|
/// Stable id derived from the texture source and usage.
|
||||||
|
pub id: AssetId<PreparedTexture>,
|
||||||
|
/// Source TEXM resource.
|
||||||
|
pub source: ResourceKey,
|
||||||
|
/// Decoded TEXM payload.
|
||||||
|
pub texm: TexmDocument,
|
||||||
|
/// Usage role in the prepared visual.
|
||||||
|
pub usage: PreparedTextureUsage,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PreparedVisual {
|
impl PreparedVisual {
|
||||||
@@ -251,8 +315,16 @@ impl PreparedVisual {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Immutable prepared mission assets for rendering and game setup.
|
/// Immutable prepared mission assets for rendering and game setup.
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct MissionAssets {
|
pub struct MissionAssets {
|
||||||
|
/// Mesh-backed models prepared for reachable visuals.
|
||||||
|
pub models: Vec<PreparedModel>,
|
||||||
|
/// WEAR tables prepared for reachable visuals.
|
||||||
|
pub wears: Vec<PreparedWear>,
|
||||||
|
/// MAT0 materials prepared for reachable visuals.
|
||||||
|
pub materials: Vec<PreparedMaterial>,
|
||||||
|
/// TEXM textures prepared for reachable visuals.
|
||||||
|
pub textures: Vec<PreparedTexture>,
|
||||||
/// Visuals prepared for all reachable prototype requests.
|
/// Visuals prepared for all reachable prototype requests.
|
||||||
pub visuals: Vec<PreparedVisual>,
|
pub visuals: Vec<PreparedVisual>,
|
||||||
/// Visual ids available for each mission object index.
|
/// Visual ids available for each mission object index.
|
||||||
@@ -286,32 +358,41 @@ impl MissionAssets {
|
|||||||
self.visuals.iter().find(|visual| visual.id == id)
|
self.visuals.iter().find(|visual| visual.id == id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Finds a prepared model by id.
|
||||||
|
#[must_use]
|
||||||
|
pub fn model_by_id(&self, id: AssetId<PreparedModel>) -> Option<&PreparedModel> {
|
||||||
|
self.models.iter().find(|model| model.id == id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds a prepared material by id.
|
||||||
|
#[must_use]
|
||||||
|
pub fn material_by_id(&self, id: AssetId<PreparedMaterial>) -> Option<&PreparedMaterial> {
|
||||||
|
self.materials.iter().find(|material| material.id == id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds a prepared texture by id.
|
||||||
|
#[must_use]
|
||||||
|
pub fn texture_by_id(&self, id: AssetId<PreparedTexture>) -> Option<&PreparedTexture> {
|
||||||
|
self.textures.iter().find(|texture| texture.id == id)
|
||||||
|
}
|
||||||
|
|
||||||
/// Converts mission assets into a coarse mission plan.
|
/// Converts mission assets into a coarse mission plan.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn to_plan(&self) -> MissionAssetPlan {
|
pub fn to_plan(&self) -> MissionAssetPlan {
|
||||||
let visual_count = self.visuals.len();
|
|
||||||
let model_count = self
|
|
||||||
.visuals
|
|
||||||
.iter()
|
|
||||||
.filter(|visual| visual.mesh.is_some())
|
|
||||||
.count();
|
|
||||||
let material_count = self
|
|
||||||
.visuals
|
|
||||||
.iter()
|
|
||||||
.map(|visual| visual.material_count)
|
|
||||||
.sum();
|
|
||||||
let texture_count = self.visuals.iter().map(|visual| visual.texture_count).sum();
|
|
||||||
let lightmap_count = self
|
|
||||||
.visuals
|
|
||||||
.iter()
|
|
||||||
.map(|visual| visual.lightmap_count)
|
|
||||||
.sum();
|
|
||||||
MissionAssetPlan {
|
MissionAssetPlan {
|
||||||
visual_count,
|
visual_count: self.visuals.len(),
|
||||||
model_count,
|
model_count: self.models.len(),
|
||||||
material_count,
|
material_count: self.materials.len(),
|
||||||
texture_count,
|
texture_count: self
|
||||||
lightmap_count,
|
.textures
|
||||||
|
.iter()
|
||||||
|
.filter(|texture| texture.usage == PreparedTextureUsage::Diffuse)
|
||||||
|
.count(),
|
||||||
|
lightmap_count: self
|
||||||
|
.textures
|
||||||
|
.iter()
|
||||||
|
.filter(|texture| texture.usage == PreparedTextureUsage::Lightmap)
|
||||||
|
.count(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -510,7 +591,14 @@ pub fn prepare_mission_assets_with_repository<R: ResourceRepository>(
|
|||||||
}
|
}
|
||||||
let mut visual_index_by_id: HashMap<AssetId<PreparedVisual>, PreparedVisualSignature> =
|
let mut visual_index_by_id: HashMap<AssetId<PreparedVisual>, PreparedVisualSignature> =
|
||||||
HashMap::new();
|
HashMap::new();
|
||||||
let mut material_signature_by_id: HashMap<AssetId<PreparedMaterial>, Vec<u8>> = HashMap::new();
|
let mut model_ids = HashSet::new();
|
||||||
|
let mut wear_ids = HashSet::new();
|
||||||
|
let mut material_ids = HashSet::new();
|
||||||
|
let mut texture_ids = HashSet::new();
|
||||||
|
let mut models = Vec::new();
|
||||||
|
let mut wears = Vec::new();
|
||||||
|
let mut materials = Vec::new();
|
||||||
|
let mut textures = Vec::new();
|
||||||
let mut visuals = Vec::new();
|
let mut visuals = Vec::new();
|
||||||
let mut prototype_visual_ids = Vec::with_capacity(prototypes.len());
|
let mut prototype_visual_ids = Vec::with_capacity(prototypes.len());
|
||||||
|
|
||||||
@@ -526,18 +614,34 @@ pub fn prepare_mission_assets_with_repository<R: ResourceRepository>(
|
|||||||
Some(_) => {}
|
Some(_) => {}
|
||||||
None => {
|
None => {
|
||||||
visual_index_by_id.insert(visual_id, signature);
|
visual_index_by_id.insert(visual_id, signature);
|
||||||
let visual = prepare_visual_with_repository_internal(
|
let bundle = prepare_visual_with_repository_internal(repository, proto)?;
|
||||||
repository,
|
if bundle.visual.id != visual_id {
|
||||||
proto,
|
|
||||||
Some(&mut material_signature_by_id),
|
|
||||||
)?;
|
|
||||||
if visual.id != visual_id {
|
|
||||||
// Defensive check. stable IDs are deterministic for the same inputs.
|
// Defensive check. stable IDs are deterministic for the same inputs.
|
||||||
return Err(AssetError::InvalidPrototype(
|
return Err(AssetError::InvalidPrototype(
|
||||||
"prepared visual id changed during preparation".to_string(),
|
"prepared visual id changed during preparation".to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
visuals.push(visual);
|
if let Some(model) = bundle.model {
|
||||||
|
if model_ids.insert(model.id) {
|
||||||
|
models.push(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(wear) = bundle.wear {
|
||||||
|
if wear_ids.insert(wear.id) {
|
||||||
|
wears.push(wear);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for material in bundle.materials {
|
||||||
|
if material_ids.insert(material.id) {
|
||||||
|
materials.push(material);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for texture in bundle.textures {
|
||||||
|
if texture_ids.insert(texture.id) {
|
||||||
|
textures.push(texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visuals.push(bundle.visual);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
prototype_visual_ids.push(visual_id);
|
prototype_visual_ids.push(visual_id);
|
||||||
@@ -562,6 +666,10 @@ pub fn prepare_mission_assets_with_repository<R: ResourceRepository>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(MissionAssets {
|
Ok(MissionAssets {
|
||||||
|
models,
|
||||||
|
wears,
|
||||||
|
materials,
|
||||||
|
textures,
|
||||||
visuals,
|
visuals,
|
||||||
object_visuals,
|
object_visuals,
|
||||||
})
|
})
|
||||||
@@ -919,11 +1027,15 @@ pub fn prepare_visual(proto: &EffectivePrototype) -> Result<PreparedVisual, Asse
|
|||||||
Ok(PreparedVisual {
|
Ok(PreparedVisual {
|
||||||
id: AssetId::new(id),
|
id: AssetId::new(id),
|
||||||
mesh,
|
mesh,
|
||||||
|
model_id: None,
|
||||||
|
wear_id: None,
|
||||||
model_nodes: 0,
|
model_nodes: 0,
|
||||||
model_slots: 0,
|
model_slots: 0,
|
||||||
model_batches: 0,
|
model_batches: 0,
|
||||||
material_count: 0,
|
material_count: 0,
|
||||||
material_ids: Vec::new(),
|
material_ids: Vec::new(),
|
||||||
|
texture_ids: Vec::new(),
|
||||||
|
lightmap_ids: Vec::new(),
|
||||||
texture_count: 0,
|
texture_count: 0,
|
||||||
lightmap_count: 0,
|
lightmap_count: 0,
|
||||||
})
|
})
|
||||||
@@ -939,16 +1051,29 @@ pub fn prepare_visual_with_repository<R: ResourceRepository>(
|
|||||||
repository: &R,
|
repository: &R,
|
||||||
proto: &EffectivePrototype,
|
proto: &EffectivePrototype,
|
||||||
) -> Result<PreparedVisual, AssetError> {
|
) -> Result<PreparedVisual, AssetError> {
|
||||||
prepare_visual_with_repository_internal(repository, proto, None)
|
Ok(prepare_visual_with_repository_internal(repository, proto)?.visual)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PreparedVisualBundle {
|
||||||
|
visual: PreparedVisual,
|
||||||
|
model: Option<PreparedModel>,
|
||||||
|
wear: Option<PreparedWear>,
|
||||||
|
materials: Vec<PreparedMaterial>,
|
||||||
|
textures: Vec<PreparedTexture>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_visual_with_repository_internal<R: ResourceRepository>(
|
fn prepare_visual_with_repository_internal<R: ResourceRepository>(
|
||||||
repository: &R,
|
repository: &R,
|
||||||
proto: &EffectivePrototype,
|
proto: &EffectivePrototype,
|
||||||
mut material_signature_by_id: Option<&mut HashMap<AssetId<PreparedMaterial>, Vec<u8>>>,
|
) -> Result<PreparedVisualBundle, AssetError> {
|
||||||
) -> Result<PreparedVisual, AssetError> {
|
|
||||||
let PrototypeGeometry::Mesh(mesh_key) = &proto.geometry else {
|
let PrototypeGeometry::Mesh(mesh_key) = &proto.geometry else {
|
||||||
return prepare_visual(proto);
|
return Ok(PreparedVisualBundle {
|
||||||
|
visual: prepare_visual(proto)?,
|
||||||
|
model: None,
|
||||||
|
wear: None,
|
||||||
|
materials: Vec::new(),
|
||||||
|
textures: Vec::new(),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
let nres = decode_nres(
|
let nres = decode_nres(
|
||||||
@@ -958,6 +1083,13 @@ fn prepare_visual_with_repository_internal<R: ResourceRepository>(
|
|||||||
.map_err(AssetError::Nres)?;
|
.map_err(AssetError::Nres)?;
|
||||||
let msh_document = decode_msh(&nres).map_err(AssetError::Msh)?;
|
let msh_document = decode_msh(&nres).map_err(AssetError::Msh)?;
|
||||||
let model = validate_msh(&msh_document).map_err(AssetError::Msh)?;
|
let model = validate_msh(&msh_document).map_err(AssetError::Msh)?;
|
||||||
|
let model_id = AssetId::new(stable_model_id(proto));
|
||||||
|
let prepared_model = PreparedModel {
|
||||||
|
id: model_id,
|
||||||
|
source: mesh_key.clone(),
|
||||||
|
validated: model.clone(),
|
||||||
|
mesh_dependencies: proto.dependencies.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
let wear_name = sibling_name(mesh_key, "wea")?;
|
let wear_name = sibling_name(mesh_key, "wea")?;
|
||||||
let wear_key = ResourceKey {
|
let wear_key = ResourceKey {
|
||||||
@@ -967,11 +1099,26 @@ fn prepare_visual_with_repository_internal<R: ResourceRepository>(
|
|||||||
};
|
};
|
||||||
let wear = decode_wear(&read_key(repository, &wear_key, Some("wear"))?)
|
let wear = decode_wear(&read_key(repository, &wear_key, Some("wear"))?)
|
||||||
.map_err(AssetError::Material)?;
|
.map_err(AssetError::Material)?;
|
||||||
|
let wear_id = AssetId::new(stable_wear_id(proto));
|
||||||
|
let prepared_wear = PreparedWear {
|
||||||
|
id: wear_id,
|
||||||
|
source: wear_key.clone(),
|
||||||
|
table: wear.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
let mut material_count = 0;
|
let mut material_count = 0;
|
||||||
let mut material_ids = Vec::with_capacity(wear.entries.len());
|
let mut material_ids = Vec::with_capacity(wear.entries.len());
|
||||||
|
let mut prepared_materials = Vec::with_capacity(wear.entries.len());
|
||||||
|
let mut prepared_textures = Vec::new();
|
||||||
|
let mut texture_ids = Vec::new();
|
||||||
|
let mut lightmap_ids = Vec::new();
|
||||||
let mut texture_count = 0;
|
let mut texture_count = 0;
|
||||||
let mut lightmap_count = 0;
|
let mut lightmap_count = 0;
|
||||||
|
let lightmap_requests: Vec<_> = wear
|
||||||
|
.lightmaps
|
||||||
|
.iter()
|
||||||
|
.map(|lightmap| lightmap.lightmap.clone())
|
||||||
|
.collect();
|
||||||
for material_index in 0..wear.entries.len() {
|
for material_index in 0..wear.entries.len() {
|
||||||
let material_index = u16::try_from(material_index).map_err(|_| {
|
let material_index = u16::try_from(material_index).map_err(|_| {
|
||||||
AssetError::InvalidPrototype("material index does not fit archive format".to_string())
|
AssetError::InvalidPrototype("material index does not fit archive format".to_string())
|
||||||
@@ -981,42 +1128,64 @@ fn prepare_visual_with_repository_internal<R: ResourceRepository>(
|
|||||||
material_count += 1;
|
material_count += 1;
|
||||||
let material_id = AssetId::new(stable_material_id(proto, material_index, &material.name));
|
let material_id = AssetId::new(stable_material_id(proto, material_index, &material.name));
|
||||||
material_ids.push(material_id);
|
material_ids.push(material_id);
|
||||||
if let Some(registry) = material_signature_by_id.as_deref_mut() {
|
let material_key = ResourceKey {
|
||||||
match registry.get(&material_id) {
|
archive: parse_path("material.lib")?,
|
||||||
Some(existing_name) => {
|
name: material.name.clone(),
|
||||||
if existing_name != &material.name.0 {
|
type_id: Some(MAT0_KIND),
|
||||||
return Err(AssetError::InvalidPrototype(
|
};
|
||||||
"stable material id collision between unrelated materials".to_string(),
|
let texture_requests = material.document.texture_requests();
|
||||||
));
|
prepared_materials.push(PreparedMaterial {
|
||||||
}
|
id: material_id,
|
||||||
}
|
source: material_key,
|
||||||
None => {
|
name: material.name.clone(),
|
||||||
registry.insert(material_id, material.name.0.clone());
|
mat0: material.document.clone(),
|
||||||
}
|
texture_requests: texture_requests.clone(),
|
||||||
}
|
lightmap_requests: lightmap_requests.clone(),
|
||||||
}
|
});
|
||||||
|
|
||||||
for texture in material.document.texture_requests() {
|
for texture in texture_requests {
|
||||||
resolve_texture(repository, &texture)?;
|
let prepared_texture = prepare_texture(
|
||||||
|
repository,
|
||||||
|
&texture,
|
||||||
|
PreparedTextureUsage::Diffuse,
|
||||||
|
)?;
|
||||||
|
texture_ids.push(prepared_texture.id);
|
||||||
|
prepared_textures.push(prepared_texture);
|
||||||
texture_count += 1;
|
texture_count += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for lightmap in &wear.lightmaps {
|
for lightmap in &wear.lightmaps {
|
||||||
resolve_lightmap(repository, &lightmap.lightmap)?;
|
let prepared_lightmap = prepare_texture(
|
||||||
|
repository,
|
||||||
|
&lightmap.lightmap,
|
||||||
|
PreparedTextureUsage::Lightmap,
|
||||||
|
)?;
|
||||||
|
lightmap_ids.push(prepared_lightmap.id);
|
||||||
|
prepared_textures.push(prepared_lightmap);
|
||||||
lightmap_count += 1;
|
lightmap_count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(PreparedVisual {
|
Ok(PreparedVisualBundle {
|
||||||
id: AssetId::new(stable_visual_id(proto)),
|
visual: PreparedVisual {
|
||||||
mesh: Some(mesh_key.clone()),
|
id: AssetId::new(stable_visual_id(proto)),
|
||||||
model_nodes: model.node_count,
|
mesh: Some(mesh_key.clone()),
|
||||||
model_slots: model.slots.len(),
|
model_id: Some(model_id),
|
||||||
model_batches: model.batches.len(),
|
wear_id: Some(wear_id),
|
||||||
material_count,
|
model_nodes: model.node_count,
|
||||||
material_ids,
|
model_slots: model.slots.len(),
|
||||||
texture_count,
|
model_batches: model.batches.len(),
|
||||||
lightmap_count,
|
material_count,
|
||||||
|
material_ids,
|
||||||
|
texture_ids,
|
||||||
|
lightmap_ids,
|
||||||
|
texture_count,
|
||||||
|
lightmap_count,
|
||||||
|
},
|
||||||
|
model: Some(prepared_model),
|
||||||
|
wear: Some(prepared_wear),
|
||||||
|
materials: prepared_materials,
|
||||||
|
textures: prepared_textures,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1248,6 +1417,32 @@ fn resolve_lightmap<R: ResourceRepository>(
|
|||||||
resolve_texm(repository, name, LIGHTMAP_ARCHIVE, "lightmap")
|
resolve_texm(repository, name, LIGHTMAP_ARCHIVE, "lightmap")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prepare_texture<R: ResourceRepository>(
|
||||||
|
repository: &R,
|
||||||
|
name: &ResourceName,
|
||||||
|
usage: PreparedTextureUsage,
|
||||||
|
) -> Result<PreparedTexture, AssetError> {
|
||||||
|
let (archive, label) = match usage {
|
||||||
|
PreparedTextureUsage::Diffuse => (TEXTURES_ARCHIVE, "texture"),
|
||||||
|
PreparedTextureUsage::Lightmap => (LIGHTMAP_ARCHIVE, "lightmap"),
|
||||||
|
};
|
||||||
|
let key = ResourceKey {
|
||||||
|
archive: parse_path(archive)?,
|
||||||
|
name: name.clone(),
|
||||||
|
type_id: None,
|
||||||
|
};
|
||||||
|
let Some(bytes) = read_optional_key(repository, &key, Some(label))? else {
|
||||||
|
return Err(AssetError::MissingDependency(format!("{label} {name:?}")));
|
||||||
|
};
|
||||||
|
let texm = decode_texm(bytes).map_err(AssetError::Texture)?;
|
||||||
|
Ok(PreparedTexture {
|
||||||
|
id: AssetId::new(stable_texture_id(&key, usage)),
|
||||||
|
source: key,
|
||||||
|
texm,
|
||||||
|
usage,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn resolve_texm<R: ResourceRepository>(
|
fn resolve_texm<R: ResourceRepository>(
|
||||||
repository: &R,
|
repository: &R,
|
||||||
name: &ResourceName,
|
name: &ResourceName,
|
||||||
@@ -1323,6 +1518,17 @@ fn stable_visual_id(proto: &EffectivePrototype) -> u64 {
|
|||||||
hasher.finish()
|
hasher.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn stable_model_id(proto: &EffectivePrototype) -> u64 {
|
||||||
|
stable_visual_id(proto)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stable_wear_id(proto: &EffectivePrototype) -> u64 {
|
||||||
|
let mut hasher = StableHasher::default();
|
||||||
|
stable_visual_id(proto).hash(&mut hasher);
|
||||||
|
b"wear".hash(&mut hasher);
|
||||||
|
hasher.finish()
|
||||||
|
}
|
||||||
|
|
||||||
fn stable_material_id(
|
fn stable_material_id(
|
||||||
proto: &EffectivePrototype,
|
proto: &EffectivePrototype,
|
||||||
material_index: u16,
|
material_index: u16,
|
||||||
@@ -1335,6 +1541,17 @@ fn stable_material_id(
|
|||||||
hasher.finish()
|
hasher.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn stable_texture_id(key: &ResourceKey, usage: PreparedTextureUsage) -> u64 {
|
||||||
|
let mut hasher = StableHasher::default();
|
||||||
|
key.archive.identity_bytes().hash(&mut hasher);
|
||||||
|
key.name.0.hash(&mut hasher);
|
||||||
|
match usage {
|
||||||
|
PreparedTextureUsage::Diffuse => 0_u8.hash(&mut hasher),
|
||||||
|
PreparedTextureUsage::Lightmap => 1_u8.hash(&mut hasher),
|
||||||
|
}
|
||||||
|
hasher.finish()
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_path(value: &str) -> Result<NormalizedPath, AssetError> {
|
fn parse_path(value: &str) -> Result<NormalizedPath, AssetError> {
|
||||||
normalize_relative(value.as_bytes(), PathPolicy::HostCompatible)
|
normalize_relative(value.as_bytes(), PathPolicy::HostCompatible)
|
||||||
.map_err(|err| AssetError::InvalidPrototype(format!("{err}")))
|
.map_err(|err| AssetError::InvalidPrototype(format!("{err}")))
|
||||||
|
|||||||
@@ -23,9 +23,9 @@
|
|||||||
use fparkan_assets::{
|
use fparkan_assets::{
|
||||||
decode_mission_land_path, decode_mission_payload, decode_nres_payload,
|
decode_mission_land_path, decode_mission_payload, decode_nres_payload,
|
||||||
derive_mission_land_paths, extend_graph_report_with_visual_dependencies, prepare_terrain_world,
|
derive_mission_land_paths, extend_graph_report_with_visual_dependencies, prepare_terrain_world,
|
||||||
AssetError as AssetPreparationError, AssetManager, BuildCategory, MissionAssetPlan,
|
AssetError as AssetPreparationError, AssetId, AssetManager, BuildCategory, MissionAssetPlan,
|
||||||
MissionDocument, MissionError, MissionTerrainPaths, NresError, TerrainFormatError,
|
MissionDocument, MissionError, MissionTerrainPaths, NresError, PreparedVisual,
|
||||||
TerrainPreparationError, TerrainWorld, TmaProfile,
|
TerrainFormatError, TerrainPreparationError, TerrainWorld, TmaProfile,
|
||||||
};
|
};
|
||||||
use fparkan_path::{normalize_relative, NormalizedPath, PathError, PathPolicy};
|
use fparkan_path::{normalize_relative, NormalizedPath, PathError, PathPolicy};
|
||||||
use fparkan_prototype::{
|
use fparkan_prototype::{
|
||||||
@@ -149,6 +149,36 @@ pub struct MissionLoadTrace {
|
|||||||
pub transforms: Vec<PlacedTransformProfile>,
|
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)]
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||||
struct MissionLoadOptions {
|
struct MissionLoadOptions {
|
||||||
fail_after_registered_objects: Option<usize>,
|
fail_after_registered_objects: Option<usize>,
|
||||||
@@ -187,6 +217,10 @@ pub struct LoadedMission {
|
|||||||
pub graph_unit_component_count: usize,
|
pub graph_unit_component_count: usize,
|
||||||
/// Mission prototype graph root count.
|
/// Mission prototype graph root count.
|
||||||
pub graph_root_count: usize,
|
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.
|
/// Mission asset plan visual count after dependency preparation.
|
||||||
pub asset_visual_count: usize,
|
pub asset_visual_count: usize,
|
||||||
/// Expanded prototype requests resolved to effective prototypes.
|
/// Expanded prototype requests resolved to effective prototypes.
|
||||||
@@ -254,6 +288,7 @@ struct LoadedMissionState {
|
|||||||
prototype_report: PrototypeGraphReport,
|
prototype_report: PrototypeGraphReport,
|
||||||
mission_assets: MissionAssets,
|
mission_assets: MissionAssets,
|
||||||
asset_plan: MissionAssetPlan,
|
asset_plan: MissionAssetPlan,
|
||||||
|
object_drafts: Vec<MissionObjectDraft>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Engine error.
|
/// Engine error.
|
||||||
@@ -539,6 +574,28 @@ fn load_mission_with_options(
|
|||||||
source,
|
source,
|
||||||
})?;
|
})?;
|
||||||
let mission_asset_plan = mission_assets.to_plan();
|
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);
|
trace.phases.push(MissionLoadPhase::Assets);
|
||||||
|
|
||||||
let mut new_runtime_world = new_world(WorldConfig);
|
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_direct_reference_count: prototype_report.direct_reference_count,
|
||||||
graph_unit_component_count: prototype_report.unit_component_count,
|
graph_unit_component_count: prototype_report.unit_component_count,
|
||||||
graph_root_count: prototype_report.root_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,
|
asset_visual_count: mission_asset_plan.visual_count,
|
||||||
graph_resolved_count: prototype_report.resolved_count,
|
graph_resolved_count: prototype_report.resolved_count,
|
||||||
graph_mesh_dependency_count: prototype_report.mesh_dependency_count,
|
graph_mesh_dependency_count: prototype_report.mesh_dependency_count,
|
||||||
@@ -608,6 +667,7 @@ fn load_mission_with_options(
|
|||||||
prototype_report,
|
prototype_report,
|
||||||
mission_assets,
|
mission_assets,
|
||||||
asset_plan: mission_asset_plan,
|
asset_plan: mission_asset_plan,
|
||||||
|
object_drafts,
|
||||||
});
|
});
|
||||||
Ok((summary, trace))
|
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)
|
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)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
enum SchedulerPresentation {
|
enum SchedulerPresentation {
|
||||||
Headless,
|
Headless,
|
||||||
|
|||||||
Reference in New Issue
Block a user