From dfc712e49196107ca28c73bec797d88e7fd2333f Mon Sep 17 00:00:00 2001 From: bd_ Date: Thu, 28 Nov 2024 14:37:16 -0800 Subject: [PATCH] fix: unpacking generated assets breaks references Also implement support for subassets. --- CHANGELOG.md | 1 + Editor/UI/GeneratedAssetsEditor.cs | 126 ++++++++++++++++++++--------- 2 files changed, 90 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e528ad..f8d8035 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 grows. ### Fixed +- [#XXX] Unpacking generated asset containers can break inter-asset references ### Changed diff --git a/Editor/UI/GeneratedAssetsEditor.cs b/Editor/UI/GeneratedAssetsEditor.cs index 239cb3f..665ebc6 100644 --- a/Editor/UI/GeneratedAssetsEditor.cs +++ b/Editor/UI/GeneratedAssetsEditor.cs @@ -99,7 +99,7 @@ class AssetInfo { public readonly UnityEngine.Object Asset; public readonly HashSet IncomingReferences = new HashSet(); - public readonly HashSet OutgoingReferences = new HashSet(); + public readonly Dictionary> OutgoingReferences = new(); public AssetInfo Root; @@ -134,7 +134,12 @@ public void PopulateReferences(Dictionary assets) var value = prop.objectReferenceValue; if (value != null && assets.TryGetValue(value, out var target)) { - OutgoingReferences.Add(target); + if (!OutgoingReferences.TryGetValue(target, out var fixups)) + { + fixups = new(); + OutgoingReferences[target] = fixups; + } + fixups.Add(prop.propertyPath); target.IncomingReferences.Add(this); } } @@ -145,6 +150,30 @@ public void PopulateReferences(Dictionary assets) } } + public void ApplyFixups() + { + if (OutgoingReferences.Count == 0) return; + + var so = new SerializedObject(Asset); + + foreach (var (target, fixups) in OutgoingReferences) + { + foreach (var fixup in fixups) + { + var prop = so.FindProperty(fixup); + if (prop == null) + { + Debug.LogWarning($"Failed to find property {fixup} on {Asset.name}"); + continue; + } + + prop.objectReferenceValue = target.Asset; + } + } + + so.ApplyModifiedPropertiesWithoutUndo(); + } + public void ForceAssignRoot() { // First, see if we're reachable only from one root. @@ -184,15 +213,7 @@ public void ForceAssignRoot() public static void Unpack(GeneratedAssets bundle) { - try - { - AssetDatabase.StartAssetEditing(); - new GeneratedAssetBundleExtractor(bundle).Extract(); - } - finally - { - AssetDatabase.StopAssetEditing(); - } + new GeneratedAssetBundleExtractor(bundle).Extract(); } @@ -223,43 +244,69 @@ private bool TryAssignRoot(AssetInfo info) internal void Extract() { string path = AssetDatabase.GetAssetPath(Bundle); + var directory = System.IO.Path.GetDirectoryName(path); _unassigned = new HashSet(_assets.Keys); - foreach (var info in _assets.Values) + try { - info.PopulateReferences(_assets); - } + AssetDatabase.StartAssetEditing(); - var queue = new Queue(); - while (_unassigned.Count > 0) - { - // Bootstrap - if (queue.Count == 0) + foreach (var info in _assets.Values) { - _unassigned.Where(o => TryAssignRoot(_assets[o])).ToList().ForEach(o => { queue.Enqueue(o); }); + info.PopulateReferences(_assets); + } + var queue = new Queue(); + while (_unassigned.Count > 0) + { + // Bootstrap if (queue.Count == 0) { - _assets[_unassigned.First()].ForceAssignRoot(); - queue.Enqueue(_unassigned.First()); - } - } + _unassigned.Where(o => TryAssignRoot(_assets[o])).ToList().ForEach(o => { queue.Enqueue(o); }); - while (queue.Count > 0) - { - var next = queue.Dequeue(); - ProcessSingleAsset(directory, next); - _unassigned.Remove(next); + if (queue.Count == 0) + { + _assets[_unassigned.First()].ForceAssignRoot(); + queue.Enqueue(_unassigned.First()); + } + } - foreach (var outgoingReference in _assets[next].OutgoingReferences) + while (queue.Count > 0) { - if (_unassigned.Contains(outgoingReference.Asset) && TryAssignRoot(outgoingReference)) + var next = queue.Dequeue(); + ProcessSingleAsset(directory, next); + _unassigned.Remove(next); + + foreach (var outgoingReference in _assets[next].OutgoingReferences.Keys) { - queue.Enqueue(outgoingReference.Asset); + if (_unassigned.Contains(outgoingReference.Asset) && TryAssignRoot(outgoingReference)) + { + queue.Enqueue(outgoingReference.Asset); + } } } } + + // The above movements can break some inter-asset references. Fix them now before we save assets. + foreach (var asset in _assets.Values) + { + asset.ApplyFixups(); + } + } + finally + { + AssetDatabase.StopAssetEditing(); + AssetDatabase.SaveAssets(); + } + + foreach (var subcontainer in Bundle.SubAssets) + { + var remaining = AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(subcontainer)) + .Where(asset => asset != subcontainer).ToList(); + if (remaining.Count > 0) Debug.Log($"Failed to extract: " + string.Join(", ", remaining.Select(o => o.name + " " + o.GetType().Name))); + + AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(subcontainer)); } AssetDatabase.DeleteAsset(path); @@ -318,17 +365,22 @@ private void ProcessSingleAsset(string directory, Object next) private static Dictionary GetContainedAssets(GeneratedAssets bundle) { string path = AssetDatabase.GetAssetPath(bundle); - var rawAssets = AssetDatabase.LoadAllAssetsAtPath(path); - Dictionary infos = new Dictionary(rawAssets.Length); + var rawAssets = new List(AssetDatabase.LoadAllAssetsAtPath(path)); + + foreach (var subcontainer in bundle.SubAssets) + { + rawAssets.AddRange(AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(subcontainer))); + } + + Dictionary infos = new Dictionary(rawAssets.Count); foreach (var asset in rawAssets) { - if (!(asset is GeneratedAssets)) + if (!(asset is GeneratedAssets or SubAssetContainer)) { infos.Add(asset, new AssetInfo(asset)); } } - - + return infos; } }