Skip to content

Commit

Permalink
fix: unpacking generated assets breaks references
Browse files Browse the repository at this point in the history
Also implement support for subassets.
  • Loading branch information
bdunderscore committed Nov 28, 2024
1 parent 5b22855 commit dfc712e
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 37 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
126 changes: 89 additions & 37 deletions Editor/UI/GeneratedAssetsEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ class AssetInfo
{
public readonly UnityEngine.Object Asset;
public readonly HashSet<AssetInfo> IncomingReferences = new HashSet<AssetInfo>();
public readonly HashSet<AssetInfo> OutgoingReferences = new HashSet<AssetInfo>();
public readonly Dictionary<AssetInfo, List<string>> OutgoingReferences = new();

public AssetInfo Root;

Expand Down Expand Up @@ -134,7 +134,12 @@ public void PopulateReferences(Dictionary<UnityEngine.Object, AssetInfo> 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);
}
}
Expand All @@ -145,6 +150,30 @@ public void PopulateReferences(Dictionary<UnityEngine.Object, AssetInfo> 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.
Expand Down Expand Up @@ -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();
}


Expand Down Expand Up @@ -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<UnityEngine.Object>(_assets.Keys);

foreach (var info in _assets.Values)
try
{
info.PopulateReferences(_assets);
}
AssetDatabase.StartAssetEditing();

var queue = new Queue<UnityEngine.Object>();
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<UnityEngine.Object>();
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);
Expand Down Expand Up @@ -318,17 +365,22 @@ private void ProcessSingleAsset(string directory, Object next)
private static Dictionary<Object, AssetInfo> GetContainedAssets(GeneratedAssets bundle)
{
string path = AssetDatabase.GetAssetPath(bundle);
var rawAssets = AssetDatabase.LoadAllAssetsAtPath(path);
Dictionary<Object, AssetInfo> infos = new Dictionary<Object, AssetInfo>(rawAssets.Length);
var rawAssets = new List<UnityEngine.Object>(AssetDatabase.LoadAllAssetsAtPath(path));

foreach (var subcontainer in bundle.SubAssets)
{
rawAssets.AddRange(AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(subcontainer)));
}

Dictionary<Object, AssetInfo> infos = new Dictionary<Object, AssetInfo>(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;
}
}
Expand Down

0 comments on commit dfc712e

Please sign in to comment.