Skip to content

Commit

Permalink
feat: add DeferPostprocessAsset API
Browse files Browse the repository at this point in the history
Closes: #225
  • Loading branch information
bdunderscore committed Apr 27, 2024
1 parent 5617be4 commit 9dd311d
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 5 deletions.
69 changes: 65 additions & 4 deletions Editor/API/BuildContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using JetBrains.Annotations;
using nadena.dev.ndmf.reporting;
using nadena.dev.ndmf.runtime;
using nadena.dev.ndmf.ui;
Expand Down Expand Up @@ -52,37 +53,50 @@ public sealed partial class BuildContext
internal readonly ObjectRegistry _registry;
internal readonly ErrorReport _report;

[PublicAPI]
public ObjectRegistry ObjectRegistry => _registry;
[PublicAPI]
public ErrorReport ErrorReport => _report;

/// <summary>
/// The root GameObject of the avatar being built.
/// </summary>

[PublicAPI]
public GameObject AvatarRootObject => _avatarRootObject;

/// <summary>
/// The root Transform of the avatar being built.
/// </summary>
[PublicAPI]
public Transform AvatarRootTransform => _avatarRootTransform;

/// <summary>
/// An asset container that can be used to store generated assets. NDMF will automatically add any objects
/// referenced by the avatar to this container when the build completes, but in some cases it can be necessary
/// to manually save assets (e.g. when using AnimatorController builtins).
/// </summary>
[PublicAPI]
public UnityObject AssetContainer { get; private set; }

public bool Successful => !_report.Errors.Any(e => e.TheError.Severity >= ErrorSeverity.Error);

private Dictionary<Type, object> _state = new Dictionary<Type, object>();
private Dictionary<Type, IExtensionContext> _extensions = new Dictionary<Type, IExtensionContext>();
private Dictionary<Type, IExtensionContext> _activeExtensions = new Dictionary<Type, IExtensionContext>();
private readonly Dictionary<Type, object> _state = new Dictionary<Type, object>();
private readonly Dictionary<Type, IExtensionContext> _extensions = new Dictionary<Type, IExtensionContext>();
private readonly Dictionary<Type, IExtensionContext> _activeExtensions = new Dictionary<Type, IExtensionContext>();

private readonly List<(UnityEngine.Object, PostprocessAssetDelegate)> _assetPostprocessors
= new List<(UnityEngine.Object, PostprocessAssetDelegate)>();

public delegate void PostprocessAssetDelegate(UnityEngine.Object asset);

[PublicAPI]
public T GetState<T>() where T : new()
{
return GetState(_ => new T());
}

[PublicAPI]
public T GetState<T>(Func<BuildContext, T> init)
{
if (_state.TryGetValue(typeof(T), out var value))
Expand All @@ -95,6 +109,7 @@ public T GetState<T>(Func<BuildContext, T> init)
return (T)value;
}

[PublicAPI]
public T Extension<T>() where T : IExtensionContext
{
if (!_activeExtensions.TryGetValue(typeof(T), out var value))
Expand All @@ -105,6 +120,7 @@ public T Extension<T>() where T : IExtensionContext
return (T)value;
}

[PublicAPI]
public BuildContext(GameObject obj, string assetRootPath, bool isClone = true)
{
BuildEvent.Dispatch(new BuildEvent.BuildStarted(obj));
Expand Down Expand Up @@ -200,13 +216,44 @@ internal static string FilterAvatarName(string avatarName)
return avatarName;
}

[PublicAPI]
public bool IsTemporaryAsset(UnityObject obj)
{
return !EditorUtility.IsPersistent(obj)
|| AssetDatabase.GetAssetPath(obj) == AssetDatabase.GetAssetPath(AssetContainer);
}

/// <summary>
/// Processes a Unity asset after the build completes, but only if it is still referenced. This can be used to
/// e.g. compress textures only if they are not replaced by other NDMF plugins.
///
/// Postprocess callbacks are run in order of registration. Note that the set of assets to be postprocessed is
/// determined prior to invoking callbacks; as such, if you change an asset to add or remove references to
/// assets during a postprocess callback, this will not impact which subsequent callbacks are invoked.
/// </summary>
/// <param name="asset">The asset to postprocess</param>
/// <param name="postprocess">The postprocess callback</param>
/// <returns></returns>
[PublicAPI]
public void DeferPostprocessAsset(
UnityEngine.Object asset,
PostprocessAssetDelegate postprocess
)
{
_assetPostprocessors.Add((asset, postprocess));
}

/// <summary>
/// No-op. Retained for API compatibility.
/// </summary>
[Obsolete("Serialize() was not meant to be public in the first place")]
[PublicAPI]
public void Serialize()
{

}

internal void SerializeInternal()
{
if (string.IsNullOrEmpty(AssetDatabase.GetAssetPath(AssetContainer)))
{
Expand Down Expand Up @@ -257,6 +304,16 @@ public void Serialize()
}
}

foreach (var pair in _assetPostprocessors)
{
var (asset, postprocess) = pair;

if (_savedObjects.Contains(asset))
{
postprocess(asset);
}
}

// SaveAssets to make sub-assets visible on the Project window
AssetDatabase.SaveAssets();

Expand All @@ -282,11 +339,13 @@ public void Serialize()
}
}

[PublicAPI]
public void DeactivateExtensionContext<T>() where T : IExtensionContext
{
DeactivateExtensionContext(typeof(T));
}

[PublicAPI]
public void DeactivateExtensionContext(Type t)
{
using (new ExecutionScope(this))
Expand Down Expand Up @@ -371,11 +430,13 @@ internal void RunPass(ConcretePass pass)
}
}

[PublicAPI]
public T ActivateExtensionContext<T>() where T : IExtensionContext
{
return (T)ActivateExtensionContext(typeof(T));
}

[PublicAPI]
public IExtensionContext ActivateExtensionContext(Type ty)
{
using (new ExecutionScope(this))
Expand Down Expand Up @@ -439,7 +500,7 @@ internal void Finish()
_activeExtensions.Remove(kvp.Key);
}

Serialize();
SerializeInternal();
sw.Stop();

BuildEvent.Dispatch(new BuildEvent.BuildEnded(sw.ElapsedMilliseconds, true));
Expand Down
50 changes: 50 additions & 0 deletions UnitTests~/DeferPostprocessTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.Collections.Generic;
using nadena.dev.ndmf;
using NUnit.Framework;
using UnityEngine;

namespace UnitTests
{
public class DeferPostprocessTest : TestBase
{
[Test]
public void Test()
{
var root = CreateRoot("root");

var meshRenderer = root.AddComponent<MeshRenderer>();
var material = new Material(Shader.Find("Standard"));
var tex = new Texture2D(1, 1);
var tex2 = new Texture2D(1, 1);

material.mainTexture = tex;
meshRenderer.material = material;

var log = new List<string>();

BuildContext bc = CreateContext(root);
bc.DeferPostprocessAsset(tex, obj =>
{
Assert.AreSame(tex, obj);
log.Add("1");
});
bc.DeferPostprocessAsset(tex2, _ =>
{
log.Add("2");
});
bc.DeferPostprocessAsset(material, obj =>
{
Assert.AreSame(material, obj);
log.Add("3");
});
bc.DeferPostprocessAsset(tex, _ =>
{
log.Add("4");
});

bc.SerializeInternal();

Assert.AreEqual(new List<string> { "1", "3", "4" }, log);
}
}
}
3 changes: 3 additions & 0 deletions UnitTests~/DeferPostprocessTest.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion UnitTests~/SerializationSweepTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public void testSerialization()
testScriptable3.ref2 = testScriptable4;

BuildContext bc = CreateContext(root);
bc.Serialize();
bc.SerializeInternal();

var path = AssetDatabase.GetAssetPath(testScriptable1);
Assert.IsFalse(string.IsNullOrEmpty(path));
Expand Down

0 comments on commit 9dd311d

Please sign in to comment.