From bf03a186768944db22986c15a78bb64c7bc83e6b Mon Sep 17 00:00:00 2001 From: Suzumiya Date: Tue, 8 Sep 2026 13:29:08 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=B5=84=E6=BA=90=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=E6=9C=AC=E5=9C=B0=E8=BA=AB=E4=BB=BD=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 资源依赖图同时索引远端资源身份和 local-asset 本地资源身份 本地 canonical 引用与远端候选去重后再建立依赖连线 补充本地身份解析与候选不歧义回归测试 更新资源自由画板技术方案的依赖身份合同 --- .../src/project/resource_dependency_graph.rs | 101 +++++++++++++++++- 1 file changed, 96 insertions(+), 5 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/project/resource_dependency_graph.rs b/apps/ai-game-creator-shell/src-tauri/src/project/resource_dependency_graph.rs index 82ec4561c..4fbfeecb0 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/project/resource_dependency_graph.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/project/resource_dependency_graph.rs @@ -400,6 +400,12 @@ pub(crate) fn build_project_resource_graph( .push(resource_ids[0].clone()); } + let resources_by_manifest_asset_id = resource_ids_by_manifest_asset + .iter() + .filter(|(_, resource_ids)| resource_ids.len() == 1) + .map(|(asset_id, resource_ids)| (asset_id.clone(), resource_ids[0].clone())) + .collect::>(); + let mut unresolved_reference_resource_ids = BTreeSet::new(); let mut reference_edge_by_id = BTreeMap::::new(); for (asset_id, target_resource_ids) in &resource_ids_by_manifest_asset { @@ -418,16 +424,32 @@ pub(crate) fn build_project_resource_graph( .filter(|value| !value.is_empty()) .collect::>() { - let source_candidates = resources_by_external_id + let mut source_candidates = resources_by_external_id .get(external_reference_id) .map(Vec::as_slice) - .unwrap_or(&[]); + .unwrap_or(&[]) + .iter() + .collect::>(); + if let Some(referenced_asset_id) = external_reference_id + .strip_prefix("local-asset:") + .map(str::trim) + .filter(|value| !value.is_empty()) + { + if let Some(source_resource_id) = + resources_by_manifest_asset_id.get(referenced_asset_id) + { + source_candidates.insert(source_resource_id); + } + } if source_candidates.len() != 1 { unresolved_reference_resource_ids.insert(external_reference_id.to_string()); continue; } - let source_resource_id = &source_candidates[0]; - if !resource_by_id.contains_key(source_resource_id) + let source_resource_id = source_candidates + .iter() + .next() + .expect("a non-empty candidate set must have one resource"); + if !resource_by_id.contains_key(source_resource_id.as_str()) || !resource_by_id.contains_key(target_resource_id) { continue; @@ -438,7 +460,7 @@ pub(crate) fn build_project_resource_graph( ProjectResourceReferenceEdge { id, kind: "asset-reference".to_string(), - source_resource_id: source_resource_id.clone(), + source_resource_id: source_resource_id.to_string(), target_resource_id: target_resource_id.clone(), cyclic: false, }, @@ -835,6 +857,75 @@ mod tests { ); } + #[test] + fn graph_resolves_local_asset_reference_identities() { + let manifest = manifest( + Vec::new(), + vec![ + asset("source-1", None, &[], None), + asset( + "derivative-1", + Some("local-asset:derivative-1"), + &["local-asset:source-1"], + None, + ), + ], + ); + let graph = build_project_resource_graph( + &manifest, + vec![ + resource("asset:source-1", Some("source-1"), None), + resource("asset:derivative-1", Some("derivative-1"), None), + ], + &[], + false, + ); + + assert_eq!(graph.reference_edges.len(), 1); + assert_eq!( + graph.reference_edges[0].source_resource_id, + "asset:source-1" + ); + assert_eq!( + graph.reference_edges[0].target_resource_id, + "asset:derivative-1" + ); + assert!(graph.unresolved_reference_resource_ids.is_empty()); + assert!(graph + .connection_index + .iter() + .any(|index| index.resource_id == "asset:derivative-1" + && index.upstream_reference_resource_ids == vec!["asset:source-1"])); + } + + #[test] + fn graph_resolves_local_asset_identity_without_ambiguous_remote_duplicate() { + let manifest = manifest( + Vec::new(), + vec![ + asset("source-1", Some("external-source"), &[], None), + asset( + "derivative-1", + Some("local-asset:derivative-1"), + &["local-asset:source-1"], + None, + ), + ], + ); + let graph = build_project_resource_graph( + &manifest, + vec![ + resource("asset:source-1", Some("source-1"), None), + resource("asset:derivative-1", Some("derivative-1"), None), + ], + &[], + false, + ); + + assert_eq!(graph.reference_edges.len(), 1); + assert!(graph.unresolved_reference_resource_ids.is_empty()); + } + #[test] fn graph_aggregates_flows_filters_missing_resources_and_detects_cycles_iteratively() { let manifest = manifest(