Skip to content

Commit

Permalink
feat: add API for declaring custom preview controls (#305)
Browse files Browse the repository at this point in the history
Closes: #302, #301
  • Loading branch information
bdunderscore authored Aug 4, 2024
1 parent 6142778 commit f1f0a2f
Show file tree
Hide file tree
Showing 10 changed files with 214 additions and 60 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- [#297] Added UI for turning preview on/off at a plugin or pass level
- [#301] [#302] Added API for changing the controls used to manipulate preview enable/disable state


### Fixed
- [#298] Fixed issue where the scene view was sometimes not refreshed when the pipeline build completes
Expand Down
17 changes: 9 additions & 8 deletions Editor/API/Solver/PluginResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,23 @@ internal class ConcretePass
internal IPass InstantiatedPass { get; }
internal ImmutableList<Type> DeactivatePlugins { get; }
internal ImmutableList<Type> ActivatePlugins { get; }
internal bool HasPreviews { get; set; }
internal ImmutableList<IRenderFilter> RenderFilters { get; }
internal bool HasPreviews => RenderFilters.Any();

public void Execute(BuildContext context)
{
InstantiatedPass.Execute(context);
}

internal ConcretePass(IPluginInternal plugin, IPass pass, ImmutableList<Type> deactivatePlugins,
ImmutableList<Type> activatePlugins)
ImmutableList<Type> activatePlugins, ImmutableList<IRenderFilter> renderFilters)
{
Plugin = plugin;
Description = pass.DisplayName;
InstantiatedPass = pass;
DeactivatePlugins = deactivatePlugins;
ActivatePlugins = activatePlugins;
RenderFilters = renderFilters;
}
}

Expand Down Expand Up @@ -199,8 +201,7 @@ ImmutableList<ConcretePass> ToConcretePasses(BuildPhase phase, IEnumerable<Solve
}

var concretePass = new ConcretePass(pass.Plugin, pass.Pass, toDeactivate.ToImmutableList(),
toActivate.ToImmutableList());
concretePass.HasPreviews = pass.RenderFilters.Count > 0;
toActivate.ToImmutableList(), pass.RenderFilters.ToImmutableList());

concrete.Add(concretePass);
}
Expand All @@ -213,7 +214,8 @@ ImmutableList<ConcretePass> ToConcretePasses(BuildPhase phase, IEnumerable<Solve

concrete.Add(new ConcretePass(InternalPasses.Instance, cleanup,
activeExtensions.ToImmutableList(),
ImmutableList<Type>.Empty
ImmutableList<Type>.Empty,
ImmutableList<IRenderFilter>.Empty
));
}

Expand All @@ -230,9 +232,8 @@ internal PreviewSession PreviewSession
{
if (!PreviewPrefs.instance.IsPreviewPluginEnabled(pass.Plugin.QualifiedName)) continue;

if (PreviewPrefs.instance.IsPreviewPassEnabled(pass.Pass.QualifiedName))
foreach (var filter in pass.RenderFilters)
session.AddMutator(new SequencePoint(), filter);
foreach (var filter in pass.RenderFilters)
session.AddMutator(new SequencePoint(), filter);
}

return session;
Expand Down
2 changes: 0 additions & 2 deletions Editor/GlobalInit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ static GlobalInit()
var oldSession = PreviewSession.Current;
PreviewSession.Current = resolver.PreviewSession;
oldSession.Dispose();

SceneView.RepaintAll();
};
};
}
Expand Down
11 changes: 11 additions & 0 deletions Editor/PreviewSystem/ComputeContext/PublishedValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public sealed class PublishedValue<T>
{
private T _value;

public event Action<T> OnChange;

public T Value
{
get => _value;
Expand All @@ -21,9 +23,18 @@ public T Value
if (ReferenceEquals(_value, value)) return;
_value = value;
_listeners.Fire(null);
var listeners = OnChange;
OnChange = default;

listeners?.Invoke(value);
}
}

public void SetWithoutNotify(T value)
{
_value = value;
}

public PublishedValue(T value)
{
_value = value;
Expand Down
20 changes: 20 additions & 0 deletions Editor/PreviewSystem/IRenderFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,26 @@ public Task<IRenderFilterNode> Instantiate(
ComputeContext context
);

/// <summary>
/// Evaluate whether the filter as a whole should be enabled. Returns true by default.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public bool IsEnabled(ComputeContext context)
{
return true;
}

/// <summary>
/// Returns a set of control nodes that can be used to modify the behavior of this filter. By default, returns an
/// empty set.
/// </summary>
/// <returns></returns>
public IEnumerable<TogglablePreviewNode> GetPreviewControlNodes()
{
yield break;
}

// Allow for future expansion
[ExcludeFromDocs]
[UsedImplicitly]
Expand Down
8 changes: 5 additions & 3 deletions Editor/PreviewSystem/Rendering/ProxyPipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Threading.Tasks;
using UnityEditor;
using UnityEngine;
using Debug = System.Diagnostics.Debug;

#endregion

Expand Down Expand Up @@ -106,8 +107,7 @@ private async Task Build(ProxyObjectCache proxyCache, IEnumerable<IRenderFilter>
var context = new ComputeContext();
_ctx = context; // prevent GC

List<IRenderFilter> filterList = filters.ToList();
List<StageDescriptor> priorStages = priorPipeline?._stages;
var filterList = filters.Where(f => f.IsEnabled(context)).ToList();

Dictionary<Renderer, Task<NodeController>> nodeTasks = new();

Expand Down Expand Up @@ -194,7 +194,7 @@ await Task.WhenAll(_stages.SelectMany(s => s.NodeTasks))
.ContinueWith(result =>
{
_completedBuild.TrySetResult(null);
EditorApplication.delayCall += SceneView.RepaintAll;
EditorApplication.delayCall += () => { EditorApplication.delayCall += SceneView.RepaintAll; };
});

foreach (var stage in _stages)
Expand All @@ -211,6 +211,8 @@ await Task.WhenAll(_stages.SelectMany(s => s.NodeTasks))
public void OnFrame()
{
if (!IsReady) return;

Debug.WriteLine("OnFrame");

foreach (var pair in _proxies)
{
Expand Down
43 changes: 43 additions & 0 deletions Editor/PreviewSystem/TogglablePreviewNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using nadena.dev.ndmf.preview.UI;

namespace nadena.dev.ndmf.preview
{
/// <summary>
/// Declares a previewable aspect of this plugin.
/// </summary>
public sealed class TogglablePreviewNode
{
/// <summary>
/// The name that will be shown to the user. Will be re-invoked on language change.
/// </summary>
public Func<string> DisplayName { get; }

public PublishedValue<bool> IsEnabled { get; }

private TogglablePreviewNode(Func<string> displayName, bool initialState)
{
DisplayName = displayName;
IsEnabled = new PublishedValue<bool>(initialState);
}

/// <summary>
/// Creates a togglable preview node. Must not be invoked during static initialization.
/// </summary>
/// <param name="displayName">A function which returns the localized display name for this switch</param>
/// <param name="qualifiedName">If not null, a name which will be used to save this configuration</param>
/// <param name="initialState">The initial state for this node; defaults to true</param>
public static TogglablePreviewNode Create(Func<string> displayName, string qualifiedName = null,
bool initialState = true)
{
if (qualifiedName != null) initialState = PreviewPrefs.instance.GetNodeState(qualifiedName, initialState);

var node = new TogglablePreviewNode(displayName, initialState);

if (qualifiedName != null)
node.IsEnabled.OnChange += value => PreviewPrefs.instance.SetNodeState(qualifiedName, value);

return node;
}
}
}
3 changes: 3 additions & 0 deletions Editor/PreviewSystem/TogglablePreviewNode.cs.meta

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

47 changes: 29 additions & 18 deletions Editor/PreviewSystem/UI/PreviewPrefs.cs
Original file line number Diff line number Diff line change
@@ -1,51 +1,62 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using UnityEditor;
using UnityEngine;

namespace nadena.dev.ndmf.preview.UI
{
internal class PreviewPrefs : ScriptableSingleton<PreviewPrefs>
{
[SerializeField] private List<string> _disabledPreviewPasses = new();
private ImmutableHashSet<string> _disabledPreviewPassesSet;

[SerializeField] private List<string> _disabledPreviewPlugins = new();
private ImmutableHashSet<string> _disabledPreviewPluginsSet;

[Serializable]
private struct KeyValue
{
public string key;
public bool value;
}

[SerializeField] private List<KeyValue> _savedNodeStates = new();
private Dictionary<string, bool> _nodeStates = new();

public event Action OnPreviewConfigChanged;

private void OnValidate()
{
_disabledPreviewPassesSet = _disabledPreviewPasses.ToImmutableHashSet();
if (_disabledPreviewPlugins == null)
_disabledPreviewPlugins = new List<string>();
if (_savedNodeStates == null)
_savedNodeStates = new List<KeyValue>();

_disabledPreviewPluginsSet = _disabledPreviewPlugins.ToImmutableHashSet();
_nodeStates = _savedNodeStates.ToDictionary(kv => kv.key, kv => kv.value);
}

public bool IsPreviewPassEnabled(string qualifiedName)
public bool GetNodeState(string qualifiedName, bool defaultValue)
{
if (_disabledPreviewPassesSet == null) OnValidate();
if (_nodeStates == null) OnValidate();

return !_disabledPreviewPassesSet.Contains(qualifiedName);
return _nodeStates.TryGetValue(qualifiedName, out var value) ? value : defaultValue;
}

public bool IsPreviewPluginEnabled(string qualifiedName)
public void SetNodeState(string qualifiedName, bool value)
{
if (_disabledPreviewPluginsSet == null) OnValidate();
if (_nodeStates == null) OnValidate();

return !_disabledPreviewPluginsSet.Contains(qualifiedName);
_nodeStates[qualifiedName] = value;
_savedNodeStates = _nodeStates.Select(kv => new KeyValue { key = kv.Key, value = kv.Value }).ToList();

EditorUtility.SetDirty(this);
}

public void SetPreviewPassEnabled(string qualifiedName, bool enabled)
public bool IsPreviewPluginEnabled(string qualifiedName)
{
if (enabled)
_disabledPreviewPasses.Remove(qualifiedName);
else
_disabledPreviewPasses.Add(qualifiedName);

OnValidate();
if (_disabledPreviewPluginsSet == null) OnValidate();

OnPreviewConfigChanged?.Invoke();
return !_disabledPreviewPluginsSet.Contains(qualifiedName);
}

public void SetPreviewPluginEnabled(string qualifiedName, bool enabled)
Expand Down
Loading

0 comments on commit f1f0a2f

Please sign in to comment.