Skip to content

Commit

Permalink
feat: partial invalidation for preview pipelines (#273)
Browse files Browse the repository at this point in the history
* feat: partial invalidation for preview pipelines

This is currently significantly slower due to more precise change tracking, which causes more preview pipeline rebuilds.

* chore: fix proxy object leak

* chore: add ToString for NodeController
  • Loading branch information
bdunderscore authored Jun 22, 2024
1 parent 2415e8d commit 5099247
Show file tree
Hide file tree
Showing 8 changed files with 327 additions and 49 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

### Changed
- [#273] Preview system now calls `Refresh` to avoid double computation

### Removed

Expand Down
6 changes: 6 additions & 0 deletions Editor/PreviewSystem/IRenderFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ internal RenderGroup(ImmutableList<Renderer> renderers)
Renderers = renderers;
}

internal RenderGroup WithoutData()
{
return new RenderGroup(Renderers);
}

public static RenderGroup For(IEnumerable<Renderer> renderers)
{
return new(renderers.OrderBy(r => r.GetInstanceID()).ToImmutableList());
Expand Down Expand Up @@ -116,6 +121,7 @@ public Task<IRenderFilterNode> Instantiate(RenderGroup group, IEnumerable<(Rende
ComputeContext context);
}

[Flags]
public enum RenderAspects
{
/// <summary>
Expand Down
44 changes: 36 additions & 8 deletions Editor/PreviewSystem/Rendering/NodeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,27 @@ private class RefCount
private readonly RefCount _refCount;

private readonly ComputeContext _context;

internal RenderAspects WhatChanged = RenderAspects.Everything;

internal RenderGroup Group => _group;
internal Task OnInvalidate => _context.OnInvalidate;
internal bool IsInvalidated => OnInvalidate.IsCompleted;

internal ProxyObjectController GetProxyFor(Renderer r)
{
return _proxies.Find(p => p.Item1 == r).Item2;
}

private NodeController(
IRenderFilter filter,
RenderGroup group,
IRenderFilterNode node,
List<(Renderer, ProxyObjectController)> proxies,
RefCount refCount,
ComputeContext context
)
{
_filter = filter;
_group = group;
_node = node;
_proxies = proxies;
Expand Down Expand Up @@ -77,7 +81,7 @@ public static async Task<NodeController> Create(
context
);

return new NodeController(group, node, proxies, new RefCount(), context);
return new NodeController(filter, group, node, proxies, new RefCount(), context);
}

public async Task<NodeController> Refresh(
Expand All @@ -87,11 +91,24 @@ RenderAspects changes
{
ComputeContext context = new ComputeContext(() => _node.ToString());

var node = await _node.Refresh(
proxies.Select(p => (p.Item1, p.Item2.Renderer)),
context,
changes
);
IRenderFilterNode node;

if (changes == 0 && !IsInvalidated)
{
// Reuse the old node in its entirety
node = _node;
context = _context;
Debug.Log("=== Reusing node " + _node);
}
else
{
node = await _node.Refresh(
proxies.Select(p => (p.Item1, p.Item2.Renderer)),
context,
changes
);
Debug.Log("=== Refreshing node " + _node + " with changes " + changes + "; success? " + (node != null) + " same? " + (node == _node));
}

RefCount refCount;
if (node == _node)
Expand All @@ -108,8 +125,14 @@ RenderAspects changes
refCount = new RefCount();
}

var controller = new NodeController(_group, node, proxies, refCount, context);
var controller = new NodeController(_filter, _group, node, proxies, refCount, context);
controller.WhatChanged = changes | node.WhatChanged;

foreach (var proxy in proxies)
{
proxy.Item2.ChangeFlags |= node.WhatChanged;
}

return controller;
}

Expand All @@ -120,5 +143,10 @@ public void Dispose()
_node.Dispose();
}
}

public override string ToString()
{
return "Node(" + _filter + " for group including " + _group.Renderers[0].gameObject.name + ")";
}
}
}
127 changes: 127 additions & 0 deletions Editor/PreviewSystem/Rendering/ProxyObjectCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.Linq;
using nadena.dev.ndmf.rq;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;

namespace nadena.dev.ndmf.preview
{
internal class ProxyObjectCache : IDisposable
{
private static HashSet<int> _proxyObjectInstanceIds = new();

public static bool IsProxyObject(GameObject obj)
{
if (obj == null) return false;

return _proxyObjectInstanceIds.Contains(obj.GetInstanceID());
}

private class RendererState
{
public Renderer InactiveProxy;
public int ActiveProxyCount = 0;
}

private Dictionary<Renderer, RendererState> _renderers = new(new ObjectIdentityComparer<Renderer>());
private bool _cleanupPending = false;

public Renderer GetOrCreate(Renderer original, Func<Renderer> create)
{
if (!_renderers.TryGetValue(original, out var state))
{
state = new RendererState();
_renderers.Add(original, state);
}

Renderer proxy;
if (state.InactiveProxy != null)
{
proxy = state.InactiveProxy;
state.InactiveProxy = null;
state.ActiveProxyCount++;
return proxy;
}

Debug.Log("=== Creating new proxy for " + original.gameObject.name);
proxy = create();
if (proxy == null)
{
return null;
}

state.ActiveProxyCount++;
_proxyObjectInstanceIds.Add(proxy.gameObject.GetInstanceID());

return proxy;
}

public void ReturnProxy(Renderer original, Renderer proxy)
{
if (!_renderers.TryGetValue(original, out var state))
{
Debug.Log("ProxyObjectCache: Renderer not found in cache");
DestroyProxy(proxy);
return;
}

if (!_cleanupPending)
{
EditorApplication.delayCall += Cleanup;
_cleanupPending = true;
}

state.ActiveProxyCount--;
if (state.ActiveProxyCount > 0 && state.InactiveProxy == null)
{
state.InactiveProxy = proxy;
return;
}

DestroyProxy(proxy);

if (state.ActiveProxyCount == 0)
{
DestroyProxy(state.InactiveProxy);
_renderers.Remove(original);
}
}

private static void DestroyProxy(Renderer proxy)
{
if (proxy == null) return;

var gameObject = proxy.gameObject;
_proxyObjectInstanceIds.Remove(gameObject.GetInstanceID());
Object.DestroyImmediate(gameObject);
}

private void Cleanup()
{
_cleanupPending = false;

foreach (var entry in _renderers.Where(kv => kv.Key == null).ToList())
{
if (entry.Value.InactiveProxy != null)
{
Object.DestroyImmediate(entry.Value.InactiveProxy.gameObject);
}
_renderers.Remove(entry.Key);
}
}

public void Dispose()
{
foreach (var entry in _renderers)
{
if (entry.Value.InactiveProxy != null)
{
Object.DestroyImmediate(entry.Value.InactiveProxy.gameObject);
}
}
_renderers.Clear();
}
}
}
3 changes: 3 additions & 0 deletions Editor/PreviewSystem/Rendering/ProxyObjectCache.cs.meta

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

Loading

0 comments on commit 5099247

Please sign in to comment.