Skip to content

Commit

Permalink
feat: pass ObjectRegistry to render filter nodes (#288)
Browse files Browse the repository at this point in the history
* feat: pass ObjectRegistry to render filter nodes

* chore: remove debug logs

* chore: update CHANGELOG

* chore: add NodeController test

* feat: make ActiveRegistry an AsyncLocal

* test: add NodeControllerTest

* chore: adjust CHANGELOG.md
  • Loading branch information
bdunderscore authored Jul 28, 2024
1 parent cd8ea99 commit b5a6505
Show file tree
Hide file tree
Showing 9 changed files with 288 additions and 86 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- [#287] Added PublishedValue class
- [#288] Added support for passing ObjectRegistry to IRenderFilter

### Fixed
- [#283] Cached proxy objects are visible after exiting play mode
Expand Down
150 changes: 99 additions & 51 deletions Editor/API/ObjectRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Linq;
using System.Threading;
using nadena.dev.ndmf.rq;
using nadena.dev.ndmf.runtime;
using UnityEngine;
using Object = UnityEngine.Object;
Expand All @@ -23,9 +25,9 @@ namespace nadena.dev.ndmf
/// </summary>
public sealed class ObjectRegistryScope : IDisposable
{
private readonly ObjectRegistry _oldRegistry;
private readonly IObjectRegistry _oldRegistry;

public ObjectRegistryScope(ObjectRegistry registry)
public ObjectRegistryScope(IObjectRegistry registry)
{
_oldRegistry = ObjectRegistry.ActiveRegistry;
ObjectRegistry.ActiveRegistry = registry;
Expand All @@ -43,61 +45,109 @@ internal struct Entry
public ObjectReference Reference;
}

public interface IObjectRegistry
{
/// <summary>
/// Returns the ObjectReference for the given object.
/// </summary>
/// <param name="obj"></param>
/// <param name="create">If true, an ObjectReference will be created if one does not already exist</param>
/// <returns>The ObjectReference, or null if create is false and no reference exists</returns>
public ObjectReference GetReference(UnityObject obj, bool create = true);

/// <summary>
/// Record that a particular object (asset or scene object) was replaced by a clone or transformed version.
/// This will be used to track the original object in error reports.
/// </summary>
/// <param name="oldObject"></param>
/// <param name="newObject"></param>
/// <returns>The ObjectReference for the objects in question</returns>
public ObjectReference RegisterReplacedObject(UnityObject oldObject, UnityObject newObject);

/// <summary>
/// Record that a particular object (asset or scene object) was replaced by a clone or transformed version.
/// This will be used to track the original object in error reports.
/// </summary>
/// <param name="oldObject"></param>
/// <param name="newObject"></param>
/// <returns>The ObjectReference for the objects in question</returns>
public ObjectReference RegisterReplacedObject(ObjectReference oldObject, UnityObject newObject);
}

/// <summary>
/// The ObjectRegistry tracks the original position of objects on the avatar; this is used to be able to identify
/// the source of errors after objects have been moved within the hierarchy.
/// </summary>
public sealed class ObjectRegistry
public sealed class ObjectRegistry : IObjectRegistry
{
// Reference hash code => objects
private readonly Dictionary<int, List<Entry>> _obj2ref =
new Dictionary<int, List<Entry>>();
private readonly Dictionary<Object, ObjectReference> _obj2ref = new(new ObjectIdentityComparer<UnityObject>());

static internal ObjectRegistry ActiveRegistry;
internal readonly Transform AvatarRoot;
private static readonly AsyncLocal<IObjectRegistry> _activeRegistry = new();

public static IObjectRegistry ActiveRegistry
{
get => _activeRegistry.Value;
set => _activeRegistry.Value = value;
}
private readonly ObjectRegistry _parent;

internal static ObjectRegistry Merge(Transform avatarRoot, IEnumerable<ObjectRegistry> inputs)
{
var newRegistry = new ObjectRegistry(null);

foreach (var kvp in inputs.SelectMany(FlattenEntries)) newRegistry._obj2ref[kvp.Key] = kvp.Value;

return newRegistry;

IEnumerable<KeyValuePair<Object, ObjectReference>> FlattenEntries(ObjectRegistry registry)
{
while (registry != null)
{
foreach (var kvp in registry._obj2ref) yield return kvp;

public ObjectRegistry(Transform avatarRoot)
registry = registry._parent;
}
}
}

public ObjectRegistry(Transform avatarRoot, ObjectRegistry parent = null)
{
AvatarRoot = avatarRoot;
_parent = parent;
}

/// <summary>
/// Returns the ObjectReference for the given object, using the ambient ObjectRegistry.
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static ObjectReference GetReference(UnityObject obj)
{
if (obj == null) return null;

return ActiveRegistry?._GetReference(obj) ?? new ObjectReference(obj, null);
return ActiveRegistry?.GetReference(obj) ?? new ObjectReference(obj, null);
}

private ObjectReference _GetReference(UnityObject obj)
ObjectReference IObjectRegistry.GetReference(UnityObject obj, bool create)
{
if (obj == null) return null;
if (!_obj2ref.TryGetValue(RuntimeHelpers.GetHashCode(obj), out var refs))
{
_obj2ref[RuntimeHelpers.GetHashCode(obj)] = refs = new List<Entry>();
}
var objref = _obj2ref.GetValueOrDefault(obj);

foreach (var r in refs)
if (objref != null || !create)
{
if (r.Object == obj) return r.Reference;
return objref;
}

string path = null;
if (obj is GameObject go)
{
path = RuntimeUtil.RelativePath(ActiveRegistry.AvatarRoot.gameObject, go);
}
else if (obj is Component c)
{
path = RuntimeUtil.RelativePath(ActiveRegistry.AvatarRoot.gameObject, c.gameObject);
}
if (AvatarRoot == null)
path = "<unknown>";
else if (obj is GameObject go)
path = RuntimeUtil.RelativePath(AvatarRoot?.gameObject, go);
else if (obj is Component c) path = RuntimeUtil.RelativePath(AvatarRoot?.gameObject, c.gameObject);

var objref = new ObjectReference(obj, path);

refs.Add(new Entry()
{
Object = obj,
Reference = objref
});
objref = new ObjectReference(obj, path);
_obj2ref[obj] = objref;

return objref;
}
Expand All @@ -111,7 +161,14 @@ private ObjectReference _GetReference(UnityObject obj)
/// <returns>The ObjectReference for the objects in question</returns>
public static ObjectReference RegisterReplacedObject(UnityObject oldObject, UnityObject newObject)
{
return RegisterReplacedObject(GetReference(oldObject), newObject);
return ActiveRegistry?.RegisterReplacedObject(GetReference(oldObject), newObject);
}

ObjectReference IObjectRegistry.RegisterReplacedObject(UnityObject oldObject, UnityObject newObject)
{
// We made the mistake of creating a bunch of static wrappers that conflict with the names we want on the
// interface; work around this by using the interface explicitly here.
return ((IObjectRegistry)this).RegisterReplacedObject(GetReference(oldObject), newObject);
}

/// <summary>
Expand All @@ -123,30 +180,21 @@ public static ObjectReference RegisterReplacedObject(UnityObject oldObject, Unit
/// <returns>The ObjectReference for the objects in question</returns>
public static ObjectReference RegisterReplacedObject(ObjectReference oldObject, UnityObject newObject)
{
if (ActiveRegistry == null) return oldObject;
return ActiveRegistry?.RegisterReplacedObject(oldObject, newObject) ?? oldObject;
}

ObjectReference IObjectRegistry.RegisterReplacedObject(ObjectReference oldObject, UnityObject newObject)
{
if (oldObject == null) throw new NullReferenceException("oldObject must not be null");
if (newObject == null) throw new NullReferenceException("newObject must not be null");

if (!ActiveRegistry._obj2ref.TryGetValue(RuntimeHelpers.GetHashCode(newObject), out var refs))
{
ActiveRegistry._obj2ref[RuntimeHelpers.GetHashCode(newObject)] = refs = new List<Entry>();
}
var self = (IObjectRegistry)this;

foreach (var r in refs)
{
if (r.Object == newObject)
{
throw new ArgumentException(
"RegisterReplacedObject must be called before GetReference is called on the new object");
}
}
if (self.GetReference(newObject, false) != null)
throw new ArgumentException(
"RegisterReplacedObject must be called before GetReference is called on the new object");

refs.Add(new Entry()
{
Object = newObject,
Reference = oldObject
});
_obj2ref[newObject] = oldObject;

return oldObject;
}
Expand Down
28 changes: 20 additions & 8 deletions Editor/PreviewSystem/IRenderFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,18 @@ public interface IRenderFilter
/// <param name="context">A compute context that is used to track which values your code depended on in
/// configuring this node. Changing these values will triger a recomputation of this node.</param>
/// <returns></returns>
public Task<IRenderFilterNode> Instantiate(RenderGroup group, IEnumerable<(Renderer, Renderer)> proxyPairs,
ComputeContext context);
public Task<IRenderFilterNode> Instantiate(
RenderGroup group,
IEnumerable<(Renderer, Renderer)> proxyPairs,
ComputeContext context
);

// Allow for future expansion
[ExcludeFromDocs]
[UsedImplicitly]
void __please_enable_dotnet_80_or_higher_for_default_methods()
{
}
}

[Flags]
Expand Down Expand Up @@ -147,12 +157,6 @@ public enum RenderAspects

public interface IRenderFilterNode : IDisposable
{
/// <summary>
/// Indicates which static aspects of a renderer this node examines. Changes to these aspects will trigger a
/// rebuild or partial update of this node.
/// </summary>
public RenderAspects Reads { get; }

/// <summary>
/// Indicates which aspects of a renderer this node changed, relative to the node prior to the last Update
/// call. This may trigger updates of downstream nodes.
Expand Down Expand Up @@ -182,6 +186,7 @@ public interface IRenderFilterNode : IDisposable
/// </summary>
/// <param name="proxyPairs"></param>
/// <param name="context"></param>
/// <param name="renderFilterContext"></param>
/// <param name="updatedAspects"></param>
/// <returns></returns>
public Task<IRenderFilterNode> Refresh(
Expand Down Expand Up @@ -210,5 +215,12 @@ public void OnFrame(Renderer original, Renderer proxy)
void IDisposable.Dispose()
{
}

// Allow for future expansion
[ExcludeFromDocs]
[UsedImplicitly]
void __please_enable_dotnet_80_or_higher_for_default_methods()
{
}
}
}
Loading

0 comments on commit b5a6505

Please sign in to comment.