feat(stage2): materialize visual dependency graph

This commit is contained in:
2026-07-03 17:03:40 +04:00
parent ee9b318172
commit 66804e0bd2
4 changed files with 532 additions and 67 deletions
+2 -2
View File
@@ -165,9 +165,9 @@ fn inspect_prototype(args: &[String]) -> Result<(), String> {
let vfs = Arc::new(DirectoryVfs::new(root)); let vfs = Arc::new(DirectoryVfs::new(root));
let repository = CachedResourceRepository::new(vfs.clone()); let repository = CachedResourceRepository::new(vfs.clone());
let roots = [resource_name(key.as_bytes())]; 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); build_prototype_graph_report(&repository, vfs.as_ref(), &roots);
extend_graph_report_with_visual_dependencies(&repository, &mut report, &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));
Ok(()) Ok(())
} }
+497 -63
View File
@@ -20,7 +20,7 @@
)] )]
//! Asset manager ports and transactional preparation models. //! Asset manager ports and transactional preparation models.
use fparkan_material::{decode_wear, resolve_material, MaterialError, WEAR_KIND}; use fparkan_material::{decode_wear, resolve_material, MaterialError, 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, MshError};
@@ -601,22 +601,71 @@ fn prepared_visual_signature(proto: &EffectivePrototype) -> PreparedVisualSignat
pub fn extend_graph_report_with_visual_dependencies<R: ResourceRepository>( pub fn extend_graph_report_with_visual_dependencies<R: ResourceRepository>(
repository: &R, repository: &R,
report: &mut PrototypeGraphReport, report: &mut PrototypeGraphReport,
graph: &PrototypeGraph, graph: &mut PrototypeGraph,
prototypes: &[EffectivePrototype], prototypes: &[EffectivePrototype],
) { ) {
let texture_archive = parse_path(TEXTURES_ARCHIVE).ok(); let material_archive = parse_path("material.lib")
let lightmap_archive = parse_path(LIGHTMAP_ARCHIVE).ok(); .expect("static material archive path must satisfy host-compatible normalization");
let mut next_node = graph
.nodes
.iter()
.map(|node| node.id.0)
.max()
.map_or(0, |value| value.saturating_add(1));
let mut next_edge = graph
.edges
.iter()
.map(|edge| edge.id.0)
.max()
.map_or(0, |value| value.saturating_add(1));
for (prototype_index, prototype) in prototypes.iter().enumerate() { for (prototype_index, prototype) in prototypes.iter().enumerate() {
let PrototypeGeometry::Mesh(mesh) = &prototype.geometry else { let PrototypeGeometry::Mesh(mesh) = &prototype.geometry else {
continue; continue;
}; };
report.mesh_dependency_count += prototype.dependencies.len();
report.wear_request_count += 1; report.wear_request_count += 1;
let Some(prototype_node_id) = prototype_node_id(graph, prototype_index) else {
continue;
};
let Some(mesh_node_id) = prototype_mesh_node_id(graph, prototype_node_id) else {
continue;
};
let mesh_parent_edge = mesh_edge_id(graph, prototype_node_id);
let root_index = root_index_for_prototype(graph, prototype_index);
match resolve_wear_table(repository, mesh) { match resolve_wear_table(repository, mesh) {
Ok(table) => { Ok(table) => {
report.wear_resolved_count += 1; report.wear_resolved_count += 1;
let wear_key = match wear_resource_key(mesh) {
Ok(key) => key,
Err(message) => {
push_visual_failure(
report,
graph,
prototype_index,
mesh.name.0.clone(),
PrototypeGraphEdge::MeshToWear,
PrototypeGraphRequiredness::Required,
&message.to_string(),
);
continue;
}
};
let wear_node_id = push_graph_resource_node(
graph,
PrototypeGraphNodeKind::WearResource,
wear_key.clone(),
&mut next_node,
);
let wear_edge_id = push_graph_edge(
graph,
mesh_node_id,
wear_node_id,
fparkan_prototype::PrototypeGraphEdgeKind::MeshToWear,
PrototypeGraphRequiredness::Required,
Some(provenance_for_resource(root_index, mesh_parent_edge, &wear_key)),
&mut next_edge,
);
report.material_slot_count += table.entries.len(); report.material_slot_count += table.entries.len();
for (material_index, _entry) in table.entries.iter().enumerate() { for (material_index, _entry) in table.entries.iter().enumerate() {
let Ok(material_index) = u16::try_from(material_index) else { let Ok(material_index) = u16::try_from(material_index) else {
@@ -634,14 +683,59 @@ pub fn extend_graph_report_with_visual_dependencies<R: ResourceRepository>(
match resolve_material(repository, &table, material_index) { match resolve_material(repository, &table, material_index) {
Ok(material) => { Ok(material) => {
report.material_resolved_count += 1; report.material_resolved_count += 1;
let material_key = ResourceKey {
archive: material_archive.clone(),
name: material.name.clone(),
type_id: Some(MAT0_KIND),
};
let material_node_id = push_graph_resource_node(
graph,
PrototypeGraphNodeKind::MaterialResource,
material_key.clone(),
&mut next_node,
);
let material_edge_id = push_graph_edge(
graph,
wear_node_id,
material_node_id,
fparkan_prototype::PrototypeGraphEdgeKind::WearToMaterial,
PrototypeGraphRequiredness::Required,
Some(provenance_for_resource(
root_index,
Some(wear_edge_id),
&material_key,
)),
&mut next_edge,
);
for texture in material.document.texture_requests() { for texture in material.document.texture_requests() {
report.texture_request_count += 1; report.texture_request_count += 1;
match resolve_texm_from_candidates( match resolve_texture(repository, &texture) {
repository, Ok(()) => {
&texture, report.texture_resolved_count += 1;
[texture_archive.as_ref(), lightmap_archive.as_ref()], if let Ok(texture_key) =
) { texm_resource_key(TEXTURES_ARCHIVE, &texture)
Ok(()) => report.texture_resolved_count += 1, {
let texture_node_id = push_graph_resource_node(
graph,
PrototypeGraphNodeKind::TextureResource,
texture_key.clone(),
&mut next_node,
);
push_graph_edge(
graph,
material_node_id,
texture_node_id,
fparkan_prototype::PrototypeGraphEdgeKind::MaterialToTexture,
PrototypeGraphRequiredness::Required,
Some(provenance_for_resource(
root_index,
Some(material_edge_id),
&texture_key,
)),
&mut next_edge,
);
}
}
Err(message) => { Err(message) => {
let message = message.to_string(); let message = message.to_string();
push_visual_failure( push_visual_failure(
@@ -670,12 +764,33 @@ pub fn extend_graph_report_with_visual_dependencies<R: ResourceRepository>(
} }
for lightmap in &table.lightmaps { for lightmap in &table.lightmaps {
report.lightmap_request_count += 1; report.lightmap_request_count += 1;
match resolve_texm_from_candidates( match resolve_lightmap(repository, &lightmap.lightmap) {
repository, Ok(()) => {
&lightmap.lightmap, report.lightmap_resolved_count += 1;
[lightmap_archive.as_ref(), texture_archive.as_ref()], if let Ok(lightmap_key) =
) { texm_resource_key(LIGHTMAP_ARCHIVE, &lightmap.lightmap)
Ok(()) => report.lightmap_resolved_count += 1, {
let lightmap_node_id = push_graph_resource_node(
graph,
PrototypeGraphNodeKind::LightmapResource,
lightmap_key.clone(),
&mut next_node,
);
push_graph_edge(
graph,
wear_node_id,
lightmap_node_id,
fparkan_prototype::PrototypeGraphEdgeKind::WearToLightmap,
PrototypeGraphRequiredness::Required,
Some(provenance_for_resource(
root_index,
Some(wear_edge_id),
&lightmap_key,
)),
&mut next_edge,
);
}
}
Err(message) => { Err(message) => {
let message = message.to_string(); let message = message.to_string();
push_visual_failure( push_visual_failure(
@@ -704,6 +819,89 @@ pub fn extend_graph_report_with_visual_dependencies<R: ResourceRepository>(
} }
} }
fn push_graph_resource_node(
graph: &mut PrototypeGraph,
kind: PrototypeGraphNodeKind,
resource: ResourceKey,
next_node: &mut u32,
) -> fparkan_prototype::PrototypeGraphNodeId {
let id = fparkan_prototype::PrototypeGraphNodeId(*next_node);
*next_node = (*next_node).saturating_add(1);
graph
.nodes
.push(fparkan_prototype::PrototypeGraphNode::resource(kind, resource, id));
id
}
fn push_graph_edge(
graph: &mut PrototypeGraph,
from: fparkan_prototype::PrototypeGraphNodeId,
to: fparkan_prototype::PrototypeGraphNodeId,
kind: fparkan_prototype::PrototypeGraphEdgeKind,
requiredness: PrototypeGraphRequiredness,
provenance: Option<PrototypeGraphProvenance>,
next_edge: &mut u32,
) -> fparkan_prototype::PrototypeGraphEdgeId {
let id = fparkan_prototype::PrototypeGraphEdgeId(*next_edge);
*next_edge = (*next_edge).saturating_add(1);
graph.edges.push(fparkan_prototype::PrototypeGraphEdgeInstance {
id,
from,
to,
kind,
requiredness,
provenance,
});
id
}
fn prototype_mesh_node_id(
graph: &PrototypeGraph,
prototype_node: fparkan_prototype::PrototypeGraphNodeId,
) -> Option<fparkan_prototype::PrototypeGraphNodeId> {
graph
.edges
.iter()
.find(|edge| {
edge.from == prototype_node
&& matches!(
edge.kind,
fparkan_prototype::PrototypeGraphEdgeKind::PrototypeToMesh
)
})
.map(|edge| edge.to)
}
fn provenance_for_resource(
root_index: usize,
parent_edge: Option<fparkan_prototype::PrototypeGraphEdgeId>,
resource: &ResourceKey,
) -> PrototypeGraphProvenance {
PrototypeGraphProvenance {
root_index,
parent_edge,
archive: Some(resource.archive.display_lossy().to_string()),
resource: Some(resource.name.0.clone()),
span: None,
}
}
fn wear_resource_key(mesh: &ResourceKey) -> Result<ResourceKey, AssetError> {
Ok(ResourceKey {
archive: mesh.archive.clone(),
name: sibling_name(mesh, "wea")?,
type_id: Some(WEAR_KIND),
})
}
fn texm_resource_key(archive: &str, name: &ResourceName) -> Result<ResourceKey, AssetError> {
Ok(ResourceKey {
archive: parse_path(archive)?,
name: name.clone(),
type_id: None,
})
}
/// Validates a prototype visual without resolving cross-resource dependencies. /// Validates a prototype visual without resolving cross-resource dependencies.
/// ///
/// This is useful for tests and API callers that only need a stable visual id. /// This is useful for tests and API callers that only need a stable visual id.
@@ -913,52 +1111,6 @@ fn resolve_wear_table<R: ResourceRepository>(
decode_wear(&bytes).map_err(AssetError::Material) decode_wear(&bytes).map_err(AssetError::Material)
} }
fn resolve_texm_from_candidates<'a, R: ResourceRepository>(
repository: &R,
texture: &ResourceName,
candidates: impl IntoIterator<Item = Option<&'a NormalizedPath>>,
) -> Result<(), AssetError> {
let mut missing_archive = false;
for path in candidates.into_iter().flatten() {
let key = ResourceKey {
archive: path.to_owned(),
name: texture.clone(),
type_id: None,
};
let archive = match repository.open_archive(path) {
Ok(archive) => archive,
Err(ResourceError::MissingArchive { .. }) => {
missing_archive = true;
continue;
}
Err(err) => return Err(map_resource_error("texm", &key, err)),
};
let Some(handle) = repository
.find(archive, texture)
.map_err(|err| map_resource_error("texm", &key, err))?
else {
continue;
};
let bytes = repository
.read(handle)
.map_err(|err| map_resource_error("texm", &key, err))?
.into_owned();
decode_texm(Arc::from(bytes)).map_err(AssetError::Texture)?;
return Ok(());
}
if missing_archive {
Err(AssetError::MissingDependency(format!(
"texm archive missing for {}",
String::from_utf8_lossy(&texture.0)
)))
} else {
Err(AssetError::MissingDependency(format!(
"missing texm {}",
String::from_utf8_lossy(&texture.0)
)))
}
}
fn push_visual_failure( fn push_visual_failure(
report: &mut PrototypeGraphReport, report: &mut PrototypeGraphReport,
graph: &PrototypeGraph, graph: &PrototypeGraph,
@@ -1298,6 +1450,185 @@ mod tests {
assert_ne!(prepared_visual_signature(&first), prepared_visual_signature(&second)); assert_ne!(prepared_visual_signature(&first), prepared_visual_signature(&second));
} }
#[test]
fn graph_materializes_visual_dependency_nodes_and_edges() {
let mesh_key = ResourceKey {
archive: parse_path("static.rlb").expect("archive"),
name: resource_name(b"tree.msh"),
type_id: Some(0x4853_454D),
};
let mat0 = mat0_with_texture(b"TEX_A");
let texm = texm_payload();
let lightmap_texm = texm_payload();
let repo = repository_with_archives_meta(&[
(
"static.rlb",
&[TestNresEntry {
name: b"tree.wea",
payload: b"1\n0 MAT_A\n\nLIGHTMAPS\n1\n0 LM_A\n",
type_id: WEAR_KIND,
attr2: 0,
}],
),
(
"material.lib",
&[TestNresEntry {
name: b"MAT_A",
payload: &mat0,
type_id: MAT0_KIND,
attr2: 0,
}],
),
(
TEXTURES_ARCHIVE,
&[TestNresEntry {
name: b"TEX_A",
payload: &texm,
type_id: 0,
attr2: 0,
}],
),
(
LIGHTMAP_ARCHIVE,
&[TestNresEntry {
name: b"LM_A",
payload: &lightmap_texm,
type_id: 0,
attr2: 0,
}],
),
]);
let prototype = EffectivePrototype {
key: fparkan_prototype::PrototypeKey(resource_name(b"tree")),
geometry: PrototypeGeometry::Mesh(mesh_key.clone()),
source: fparkan_prototype::PrototypeSource::DirectArchive,
dependencies: vec![mesh_key.clone()],
};
let mut graph = prototype_graph_for_mesh(&prototype);
let mut report = PrototypeGraphReport {
root_count: 1,
direct_reference_count: 1,
resolved_count: 1,
mesh_dependency_count: 1,
..PrototypeGraphReport::default()
};
extend_graph_report_with_visual_dependencies(
&repo,
&mut report,
&mut graph,
std::slice::from_ref(&prototype),
);
assert!(graph
.nodes
.iter()
.any(|node| node.kind == PrototypeGraphNodeKind::WearResource));
assert!(graph
.nodes
.iter()
.any(|node| node.kind == PrototypeGraphNodeKind::MaterialResource));
assert!(graph
.nodes
.iter()
.any(|node| node.kind == PrototypeGraphNodeKind::TextureResource));
assert!(graph
.nodes
.iter()
.any(|node| node.kind == PrototypeGraphNodeKind::LightmapResource));
assert!(graph
.edges
.iter()
.any(|edge| edge.kind == fparkan_prototype::PrototypeGraphEdgeKind::MeshToWear));
assert!(graph
.edges
.iter()
.any(|edge| edge.kind == fparkan_prototype::PrototypeGraphEdgeKind::WearToMaterial));
assert!(graph
.edges
.iter()
.any(|edge| edge.kind == fparkan_prototype::PrototypeGraphEdgeKind::MaterialToTexture));
assert!(graph
.edges
.iter()
.any(|edge| edge.kind == fparkan_prototype::PrototypeGraphEdgeKind::WearToLightmap));
assert_eq!(report.wear_request_count, 1);
assert_eq!(report.wear_resolved_count, 1);
assert_eq!(report.material_slot_count, 1);
assert_eq!(report.material_resolved_count, 1);
assert_eq!(report.texture_request_count, 1);
assert_eq!(report.texture_resolved_count, 1);
assert_eq!(report.lightmap_request_count, 1);
assert_eq!(report.lightmap_resolved_count, 1);
assert!(report.failures.is_empty());
}
#[test]
fn graph_report_uses_strict_texture_archive_policy() {
let mesh_key = ResourceKey {
archive: parse_path("static.rlb").expect("archive"),
name: resource_name(b"tree.msh"),
type_id: Some(0x4853_454D),
};
let mat0 = mat0_with_texture(b"TEX_A");
let texm = texm_payload();
let repo = repository_with_archives_meta(&[
(
"static.rlb",
&[TestNresEntry {
name: b"tree.wea",
payload: b"1\n0 MAT_A\n",
type_id: WEAR_KIND,
attr2: 0,
}],
),
(
"material.lib",
&[TestNresEntry {
name: b"MAT_A",
payload: &mat0,
type_id: MAT0_KIND,
attr2: 0,
}],
),
(
LIGHTMAP_ARCHIVE,
&[TestNresEntry {
name: b"TEX_A",
payload: &texm,
type_id: 0,
attr2: 0,
}],
),
]);
let prototype = EffectivePrototype {
key: fparkan_prototype::PrototypeKey(resource_name(b"tree")),
geometry: PrototypeGeometry::Mesh(mesh_key.clone()),
source: fparkan_prototype::PrototypeSource::DirectArchive,
dependencies: vec![mesh_key.clone()],
};
let mut graph = prototype_graph_for_mesh(&prototype);
let mut report = PrototypeGraphReport {
root_count: 1,
direct_reference_count: 1,
resolved_count: 1,
mesh_dependency_count: 1,
..PrototypeGraphReport::default()
};
extend_graph_report_with_visual_dependencies(
&repo,
&mut report,
&mut graph,
std::slice::from_ref(&prototype),
);
assert_eq!(report.texture_request_count, 1);
assert_eq!(report.texture_resolved_count, 0);
assert_eq!(report.failures.len(), 1);
assert_eq!(report.failures[0].edge, PrototypeGraphEdge::MaterialToTexture);
}
#[test] #[test]
#[ignore = "requires licensed corpus"] #[ignore = "requires licensed corpus"]
fn prepares_real_unit_asset_plan() { fn prepares_real_unit_asset_plan() {
@@ -1375,6 +1706,25 @@ mod tests {
CachedResourceRepository::new(Arc::new(vfs)) CachedResourceRepository::new(Arc::new(vfs))
} }
#[derive(Clone, Copy)]
struct TestNresEntry<'a> {
name: &'a [u8],
payload: &'a [u8],
type_id: u32,
attr2: u32,
}
fn repository_with_archives_meta(
archives: &[(&str, &[TestNresEntry<'_>])],
) -> CachedResourceRepository {
let mut vfs = MemoryVfs::default();
for (archive, entries) in archives {
let path = parse_path(archive).expect("archive path");
vfs.insert(path, Arc::from(build_nres_with_meta(entries).into_boxed_slice()));
}
CachedResourceRepository::new(Arc::new(vfs))
}
fn texm_payload() -> Vec<u8> { fn texm_payload() -> Vec<u8> {
let mut out = Vec::new(); let mut out = Vec::new();
out.extend_from_slice(&0x6d78_6554_u32.to_le_bytes()); out.extend_from_slice(&0x6d78_6554_u32.to_le_bytes());
@@ -1389,6 +1739,58 @@ mod tests {
out out
} }
fn mat0_with_texture(texture: &[u8]) -> Vec<u8> {
let mut bytes = vec![0; 4 + 34];
bytes[0..2].copy_from_slice(&1_u16.to_le_bytes());
let len = texture.len().min(16);
bytes[22..22 + len].copy_from_slice(&texture[..len]);
bytes
}
fn prototype_graph_for_mesh(prototype: &EffectivePrototype) -> PrototypeGraph {
let root_node = fparkan_prototype::PrototypeGraphNode::root(
fparkan_prototype::PrototypeKey(prototype.key.0.clone()),
false,
fparkan_prototype::PrototypeGraphNodeId(0),
);
let prototype_node = fparkan_prototype::PrototypeGraphNode::prototype(
prototype.key.clone(),
fparkan_prototype::PrototypeGraphNodeId(1),
);
let mesh_key = match &prototype.geometry {
PrototypeGeometry::Mesh(mesh) => mesh.clone(),
PrototypeGeometry::NonGeometric => panic!("mesh prototype expected"),
};
let mesh_node = fparkan_prototype::PrototypeGraphNode::mesh(
mesh_key,
fparkan_prototype::PrototypeGraphNodeId(2),
);
PrototypeGraph {
roots: vec![prototype.key.clone()],
prototype_requests: vec![prototype.key.clone()],
root_prototype_request_spans: std::iter::once(0..1).collect(),
nodes: vec![root_node, prototype_node, mesh_node],
edges: vec![
fparkan_prototype::PrototypeGraphEdgeInstance {
id: fparkan_prototype::PrototypeGraphEdgeId(0),
from: fparkan_prototype::PrototypeGraphNodeId(0),
to: fparkan_prototype::PrototypeGraphNodeId(1),
kind: fparkan_prototype::PrototypeGraphEdgeKind::MissionToRoot,
requiredness: PrototypeGraphRequiredness::Required,
provenance: None,
},
fparkan_prototype::PrototypeGraphEdgeInstance {
id: fparkan_prototype::PrototypeGraphEdgeId(1),
from: fparkan_prototype::PrototypeGraphNodeId(1),
to: fparkan_prototype::PrototypeGraphNodeId(2),
kind: fparkan_prototype::PrototypeGraphEdgeKind::PrototypeToMesh,
requiredness: PrototypeGraphRequiredness::Required,
provenance: None,
},
],
}
}
fn build_nres(entries: &[(&[u8], &[u8])]) -> Vec<u8> { fn build_nres(entries: &[(&[u8], &[u8])]) -> Vec<u8> {
let mut out = vec![0; 16]; let mut out = vec![0; 16];
let mut offsets = Vec::with_capacity(entries.len()); let mut offsets = Vec::with_capacity(entries.len());
@@ -1421,6 +1823,38 @@ mod tests {
out out
} }
fn build_nres_with_meta(entries: &[TestNresEntry<'_>]) -> Vec<u8> {
let mut out = vec![0; 16];
let mut offsets = Vec::with_capacity(entries.len());
for entry in entries {
offsets.push(u32::try_from(out.len()).expect("offset"));
out.extend_from_slice(entry.payload);
let padding = (8 - (out.len() % 8)) % 8;
out.resize(out.len() + padding, 0);
}
let mut order: Vec<usize> = (0..entries.len()).collect();
order.sort_by(|left, right| entries[*left].name.cmp(entries[*right].name));
for (idx, entry) in entries.iter().enumerate() {
push_u32(&mut out, entry.type_id);
push_u32(&mut out, 0);
push_u32(&mut out, entry.attr2);
push_u32(&mut out, u32::try_from(entry.payload.len()).expect("payload"));
push_u32(&mut out, 0);
let mut name_raw = [0; 36];
let len = name_raw.len().saturating_sub(1).min(entry.name.len());
name_raw[..len].copy_from_slice(&entry.name[..len]);
out.extend_from_slice(&name_raw);
push_u32(&mut out, offsets[idx]);
push_u32(&mut out, u32::try_from(order[idx]).expect("sort index"));
}
out[0..4].copy_from_slice(b"NRes");
out[4..8].copy_from_slice(&0x100_u32.to_le_bytes());
out[8..12].copy_from_slice(&u32::try_from(entries.len()).expect("count").to_le_bytes());
let total_size = u32::try_from(out.len()).expect("total size");
out[12..16].copy_from_slice(&total_size.to_le_bytes());
out
}
fn push_u32(out: &mut Vec<u8>, value: u32) { fn push_u32(out: &mut Vec<u8>, value: u32) {
out.extend_from_slice(&value.to_le_bytes()); out.extend_from_slice(&value.to_le_bytes());
} }
+31
View File
@@ -179,6 +179,16 @@ pub enum PrototypeGraphNodeKind {
Prototype, Prototype,
/// Mesh dependency. /// Mesh dependency.
MeshResource, 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. /// Non-geometric prototype.
NonGeometric, NonGeometric,
} }
@@ -197,6 +207,17 @@ pub struct PrototypeGraphNode {
} }
impl 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. /// Creates a mesh resource node.
#[must_use] #[must_use]
pub const fn mesh(resource: ResourceKey, id: PrototypeGraphNodeId) -> Self { pub const fn mesh(resource: ResourceKey, id: PrototypeGraphNodeId) -> Self {
@@ -244,6 +265,16 @@ pub enum PrototypeGraphEdgeKind {
UnitDatToComponent, UnitDatToComponent,
/// Prototype to mesh dependency. /// Prototype to mesh dependency.
PrototypeToMesh, 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. /// Prototype graph edge record.
+2 -2
View File
@@ -516,12 +516,12 @@ fn load_mission_with_options(
.iter() .iter()
.map(|object| resource_name(&object.resource_name.raw)) .map(|object| resource_name(&object.resource_name.raw))
.collect(); .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); build_prototype_graph_report(&repository, vfs.as_ref(), &graph_roots);
extend_graph_report_with_visual_dependencies( extend_graph_report_with_visual_dependencies(
&repository, &repository,
&mut prototype_report, &mut prototype_report,
&prototype_graph, &mut prototype_graph,
&resolved_prototypes, &resolved_prototypes,
); );
if !prototype_report.is_success() { if !prototype_report.is_success() {