Refactor Visject surface nodes cache to reuse between graphs

This commit is contained in:
Wojtek Figat
2023-08-03 15:44:02 +02:00
parent bb96f2cd6d
commit c0d32a99b0
4 changed files with 515 additions and 593 deletions

View File

@@ -724,8 +724,8 @@ namespace FlaxEditor
// Cleanup
Undo.Dispose();
Surface.VisualScriptSurface.NodesCache.Clear();
Surface.AnimGraphSurface.NodesCache.Clear();
foreach (var cache in Surface.VisjectSurface.NodesCache.Caches.ToArray())
cache.Clear();
Instance = null;
// Invoke new instance if need to open a project

View File

@@ -2,8 +2,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FlaxEditor.Content;
using FlaxEditor.Scripting;
using FlaxEditor.Surface.ContextMenu;
@@ -89,188 +87,7 @@ namespace FlaxEditor.Surface
}
};
internal static class NodesCache
{
private static readonly object _locker = new object();
private static int _version;
private static Task _task;
private static VisjectCM _taskContextMenu;
private static Dictionary<KeyValuePair<string, ushort>, GroupArchetype> _cache;
public static void Wait()
{
_task?.Wait();
}
public static void Clear()
{
Wait();
if (_cache != null && _cache.Count != 0)
{
OnCodeEditingTypesCleared();
}
}
public static void Get(VisjectCM contextMenu)
{
Wait();
lock (_locker)
{
if (_cache == null)
_cache = new Dictionary<KeyValuePair<string, ushort>, GroupArchetype>();
contextMenu.LockChildrenRecursive();
// Check if has cached groups
if (_cache.Count != 0)
{
// Check if context menu doesn't have the recent cached groups
if (!contextMenu.Groups.Any(g => g.Archetypes[0].Tag is int asInt && asInt == _version))
{
var groups = contextMenu.Groups.Where(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int).ToArray();
foreach (var g in groups)
contextMenu.RemoveGroup(g);
foreach (var g in _cache.Values)
contextMenu.AddGroup(g);
}
}
else
{
// Remove any old groups from context menu
var groups = contextMenu.Groups.Where(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int).ToArray();
foreach (var g in groups)
contextMenu.RemoveGroup(g);
// Register for scripting types reload
Editor.Instance.CodeEditing.TypesCleared += OnCodeEditingTypesCleared;
// Run caching on an async
_task = Task.Run(OnActiveContextMenuShowAsync);
_taskContextMenu = contextMenu;
}
contextMenu.UnlockChildrenRecursive();
}
}
private static void OnActiveContextMenuShowAsync()
{
Profiler.BeginEvent("Setup Anim Graph Context Menu (async)");
foreach (var scriptType in Editor.Instance.CodeEditing.All.Get())
{
if (!SurfaceUtils.IsValidVisualScriptType(scriptType))
continue;
// Skip Newtonsoft.Json stuff
var scriptTypeTypeName = scriptType.TypeName;
if (scriptTypeTypeName.StartsWith("Newtonsoft.Json."))
continue;
var scriptTypeName = scriptType.Name;
// Enum
if (scriptType.IsEnum)
{
// Create node archetype
var node = (NodeArchetype)Archetypes.Constants.Nodes[10].Clone();
node.DefaultValues[0] = Activator.CreateInstance(scriptType.Type);
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
node.Title = scriptTypeName;
node.Description = Editor.Instance.CodeDocs.GetTooltip(scriptType);
// Create group archetype
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 2);
if (!_cache.TryGetValue(groupKey, out var group))
{
group = new GroupArchetype
{
GroupID = groupKey.Value,
Name = groupKey.Key,
Color = new Color(243, 156, 18),
Tag = _version,
Archetypes = new List<NodeArchetype>(),
};
_cache.Add(groupKey, group);
}
// Add node to the group
((IList<NodeArchetype>)group.Archetypes).Add(node);
continue;
}
// Structure
if (scriptType.IsValueType)
{
if (scriptType.IsVoid)
continue;
// Create group archetype
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 4);
if (!_cache.TryGetValue(groupKey, out var group))
{
group = new GroupArchetype
{
GroupID = groupKey.Value,
Name = groupKey.Key,
Color = new Color(155, 89, 182),
Tag = _version,
Archetypes = new List<NodeArchetype>(),
};
_cache.Add(groupKey, group);
}
var tooltip = Editor.Instance.CodeDocs.GetTooltip(scriptType);
// Create Pack node archetype
var node = (NodeArchetype)Archetypes.Packing.Nodes[6].Clone();
node.DefaultValues[0] = scriptTypeTypeName;
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
node.Title = "Pack " + scriptTypeName;
node.Description = tooltip;
((IList<NodeArchetype>)group.Archetypes).Add(node);
// Create Unpack node archetype
node = (NodeArchetype)Archetypes.Packing.Nodes[13].Clone();
node.DefaultValues[0] = scriptTypeTypeName;
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
node.Title = "Unpack " + scriptTypeName;
node.Description = tooltip;
((IList<NodeArchetype>)group.Archetypes).Add(node);
}
}
// Add group to context menu (on a main thread)
FlaxEngine.Scripting.InvokeOnUpdate(() =>
{
lock (_locker)
{
_taskContextMenu.AddGroups(_cache.Values);
_taskContextMenu = null;
}
});
Profiler.EndEvent();
lock (_locker)
{
_task = null;
}
}
private static void OnCodeEditingTypesCleared()
{
Wait();
lock (_locker)
{
_cache.Clear();
_version++;
}
Editor.Instance.CodeEditing.TypesCleared -= OnCodeEditingTypesCleared;
}
}
private static NodesCache _nodesCache = new NodesCache(IterateNodesCache);
/// <summary>
/// The state machine editing context menu.
@@ -378,9 +195,7 @@ namespace FlaxEditor.Surface
// Check if show additional nodes in the current surface context
if (activeCM != _cmStateMachineMenu)
{
Profiler.BeginEvent("Setup Anim Graph Context Menu");
NodesCache.Get(activeCM);
Profiler.EndEvent();
_nodesCache.Get(activeCM);
base.OnShowPrimaryMenu(activeCM, location, startBox);
@@ -394,7 +209,86 @@ namespace FlaxEditor.Surface
private void OnActiveContextMenuVisibleChanged(Control activeCM)
{
NodesCache.Wait();
_nodesCache.Wait();
}
private static void IterateNodesCache(ScriptType scriptType, Dictionary<KeyValuePair<string, ushort>, GroupArchetype> cache, int version)
{
// Skip Newtonsoft.Json stuff
var scriptTypeTypeName = scriptType.TypeName;
if (scriptTypeTypeName.StartsWith("Newtonsoft.Json."))
return;
var scriptTypeName = scriptType.Name;
// Enum
if (scriptType.IsEnum)
{
// Create node archetype
var node = (NodeArchetype)Archetypes.Constants.Nodes[10].Clone();
node.DefaultValues[0] = Activator.CreateInstance(scriptType.Type);
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
node.Title = scriptTypeName;
node.Description = Editor.Instance.CodeDocs.GetTooltip(scriptType);
// Create group archetype
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 2);
if (!cache.TryGetValue(groupKey, out var group))
{
group = new GroupArchetype
{
GroupID = groupKey.Value,
Name = groupKey.Key,
Color = new Color(243, 156, 18),
Tag = version,
Archetypes = new List<NodeArchetype>(),
};
cache.Add(groupKey, group);
}
// Add node to the group
((IList<NodeArchetype>)group.Archetypes).Add(node);
return;
}
// Structure
if (scriptType.IsValueType)
{
if (scriptType.IsVoid)
return;
// Create group archetype
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 4);
if (!cache.TryGetValue(groupKey, out var group))
{
group = new GroupArchetype
{
GroupID = groupKey.Value,
Name = groupKey.Key,
Color = new Color(155, 89, 182),
Tag = version,
Archetypes = new List<NodeArchetype>(),
};
cache.Add(groupKey, group);
}
var tooltip = Editor.Instance.CodeDocs.GetTooltip(scriptType);
// Create Pack node archetype
var node = (NodeArchetype)Archetypes.Packing.Nodes[6].Clone();
node.DefaultValues[0] = scriptTypeTypeName;
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
node.Title = "Pack " + scriptTypeName;
node.Description = tooltip;
((IList<NodeArchetype>)group.Archetypes).Add(node);
// Create Unpack node archetype
node = (NodeArchetype)Archetypes.Packing.Nodes[13].Clone();
node.DefaultValues[0] = scriptTypeTypeName;
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
node.Title = "Unpack " + scriptTypeName;
node.Description = tooltip;
((IList<NodeArchetype>)group.Archetypes).Add(node);
}
}
/// <inheritdoc />
@@ -488,7 +382,7 @@ namespace FlaxEditor.Surface
_cmStateMachineTransitionMenu = null;
}
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
NodesCache.Wait();
_nodesCache.Wait();
base.OnDestroy();
}

View File

@@ -1,8 +1,12 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
//#define DEBUG_SEARCH_TIME
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Scripting;
using FlaxEditor.Surface.ContextMenu;
using FlaxEditor.Surface.Elements;
using FlaxEditor.Surface.Undo;
@@ -13,6 +17,157 @@ namespace FlaxEditor.Surface
{
public partial class VisjectSurface
{
/// <summary>
/// Utility for easy nodes archetypes generation for Visject Surface based on scripting types.
/// </summary>
internal class NodesCache
{
public delegate void IterateType(ScriptType scriptType, Dictionary<KeyValuePair<string, ushort>, GroupArchetype> cache, int version);
internal static readonly List<NodesCache> Caches = new List<NodesCache>(8);
private readonly object _locker = new object();
private readonly IterateType _iterator;
private int _version;
private Task _task;
private VisjectCM _taskContextMenu;
private Dictionary<KeyValuePair<string, ushort>, GroupArchetype> _cache;
public NodesCache(IterateType iterator)
{
_iterator = iterator;
}
public void Wait()
{
if (_task != null)
{
Profiler.BeginEvent("Setup Context Menu");
_task.Wait();
Profiler.EndEvent();
}
}
public void Clear()
{
Wait();
if (_cache != null && _cache.Count != 0)
{
OnCodeEditingTypesCleared();
}
lock (_locker)
{
Caches.Remove(this);
}
}
public void Get(VisjectCM contextMenu)
{
Profiler.BeginEvent("Setup Context Menu");
Wait();
lock (_locker)
{
if (_cache == null)
{
if (!Caches.Contains(this))
Caches.Add(this);
_cache = new Dictionary<KeyValuePair<string, ushort>, GroupArchetype>();
}
contextMenu.LockChildrenRecursive();
// Check if has cached groups
if (_cache.Count != 0)
{
// Check if context menu doesn't have the recent cached groups
if (!contextMenu.Groups.Any(g => g.Archetypes[0].Tag is int asInt && asInt == _version))
{
var groups = contextMenu.Groups.Where(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int).ToArray();
foreach (var g in groups)
contextMenu.RemoveGroup(g);
foreach (var g in _cache.Values)
contextMenu.AddGroup(g);
}
}
else
{
// Remove any old groups from context menu
var groups = contextMenu.Groups.Where(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int).ToArray();
foreach (var g in groups)
contextMenu.RemoveGroup(g);
// Register for scripting types reload
Editor.Instance.CodeEditing.TypesCleared += OnCodeEditingTypesCleared;
// Run caching on an async
_task = Task.Run(OnActiveContextMenuShowAsync);
_taskContextMenu = contextMenu;
}
contextMenu.UnlockChildrenRecursive();
}
Profiler.EndEvent();
}
private void OnActiveContextMenuShowAsync()
{
Profiler.BeginEvent("Setup Context Menu (async)");
#if DEBUG_SEARCH_TIME
var searchStartTime = DateTime.Now;
#endif
foreach (var scriptType in Editor.Instance.CodeEditing.All.Get())
{
if (!SurfaceUtils.IsValidVisualScriptType(scriptType))
continue;
_iterator(scriptType, _cache, _version);
}
// Add group to context menu (on a main thread)
FlaxEngine.Scripting.InvokeOnUpdate(() =>
{
#if DEBUG_SEARCH_TIME
var addStartTime = DateTime.Now;
#endif
lock (_locker)
{
_taskContextMenu.AddGroups(_cache.Values);
_taskContextMenu = null;
}
#if DEBUG_SEARCH_TIME
Editor.LogError($"Added items to VisjectCM in: {(DateTime.Now - addStartTime).TotalMilliseconds} ms");
#endif
});
#if DEBUG_SEARCH_TIME
Editor.LogError($"Collected items in: {(DateTime.Now - searchStartTime).TotalMilliseconds} ms");
#endif
Profiler.EndEvent();
lock (_locker)
{
_task = null;
}
}
private void OnCodeEditingTypesCleared()
{
Wait();
lock (_locker)
{
_cache.Clear();
_version++;
}
Editor.Instance.CodeEditing.TypesCleared -= OnCodeEditingTypesCleared;
}
}
private ContextMenuButton _cmCopyButton;
private ContextMenuButton _cmDuplicateButton;
private ContextMenuButton _cmFormatNodesConnectionButton;

View File

@@ -4,15 +4,10 @@
//#define DEBUG_FIELDS_SEARCHING
//#define DEBUG_EVENTS_SEARCHING
#if DEBUG_INVOKE_METHODS_SEARCHING || DEBUG_FIELDS_SEARCHING || DEBUG_EVENTS_SEARCHING
#define DEBUG_SEARCH_TIME
#endif
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using FlaxEditor.Content;
using FlaxEditor.GUI.Drag;
using FlaxEditor.SceneGraph;
@@ -41,396 +36,7 @@ namespace FlaxEditor.Surface
Archetypes = new List<NodeArchetype>(),
};
internal static class NodesCache
{
private static readonly object _locker = new object();
private static int _version;
private static Task _task;
private static VisjectCM _taskContextMenu;
private static Dictionary<KeyValuePair<string, ushort>, GroupArchetype> _cache;
public static void Wait()
{
_task?.Wait();
}
public static void Clear()
{
Wait();
if (_cache != null && _cache.Count != 0)
{
OnCodeEditingTypesCleared();
}
}
public static void Get(VisjectCM contextMenu)
{
Wait();
lock (_locker)
{
if (_cache == null)
_cache = new Dictionary<KeyValuePair<string, ushort>, GroupArchetype>();
contextMenu.LockChildrenRecursive();
// Check if has cached groups
if (_cache.Count != 0)
{
// Check if context menu doesn't have the recent cached groups
if (!contextMenu.Groups.Any(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int asInt && asInt == _version))
{
var groups = contextMenu.Groups.Where(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int).ToArray();
foreach (var g in groups)
contextMenu.RemoveGroup(g);
foreach (var g in _cache.Values)
contextMenu.AddGroup(g);
}
}
else
{
// Remove any old groups from context menu
var groups = contextMenu.Groups.Where(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int).ToArray();
foreach (var g in groups)
contextMenu.RemoveGroup(g);
// Register for scripting types reload
Editor.Instance.CodeEditing.TypesCleared += OnCodeEditingTypesCleared;
// Run caching on an async
_task = Task.Run(OnActiveContextMenuShowAsync);
_taskContextMenu = contextMenu;
}
contextMenu.UnlockChildrenRecursive();
}
}
private static void OnActiveContextMenuShowAsync()
{
Profiler.BeginEvent("Setup Visual Script Context Menu (async)");
#if DEBUG_SEARCH_TIME
var searchStartTime = DateTime.Now;
var searchHitsCount = 0;
#endif
foreach (var scriptType in Editor.Instance.CodeEditing.All.Get())
{
if (!SurfaceUtils.IsValidVisualScriptType(scriptType))
continue;
// Skip Newtonsoft.Json stuff
var scriptTypeTypeName = scriptType.TypeName;
if (scriptTypeTypeName.StartsWith("Newtonsoft.Json."))
continue;
var scriptTypeName = scriptType.Name;
// Enum
if (scriptType.IsEnum)
{
// Create node archetype
var node = (NodeArchetype)Archetypes.Constants.Nodes[10].Clone();
node.DefaultValues[0] = Activator.CreateInstance(scriptType.Type);
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
node.Title = scriptTypeName;
node.Description = Editor.Instance.CodeDocs.GetTooltip(scriptType);
// Create group archetype
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 2);
if (!_cache.TryGetValue(groupKey, out var group))
{
group = new GroupArchetype
{
GroupID = groupKey.Value,
Name = groupKey.Key,
Color = new Color(243, 156, 18),
Tag = _version,
Archetypes = new List<NodeArchetype>(),
};
_cache.Add(groupKey, group);
}
// Add node to the group
((IList<NodeArchetype>)group.Archetypes).Add(node);
continue;
}
// Structure
if (scriptType.IsValueType)
{
if (scriptType.IsVoid)
continue;
// Create group archetype
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 4);
if (!_cache.TryGetValue(groupKey, out var group))
{
group = new GroupArchetype
{
GroupID = groupKey.Value,
Name = groupKey.Key,
Color = new Color(155, 89, 182),
Tag = _version,
Archetypes = new List<NodeArchetype>(),
};
_cache.Add(groupKey, group);
}
var tooltip = Editor.Instance.CodeDocs.GetTooltip(scriptType);
// Create Pack node archetype
var node = (NodeArchetype)Archetypes.Packing.Nodes[6].Clone();
node.DefaultValues[0] = scriptTypeTypeName;
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
node.Title = "Pack " + scriptTypeName;
node.Description = tooltip;
((IList<NodeArchetype>)group.Archetypes).Add(node);
// Create Unpack node archetype
node = (NodeArchetype)Archetypes.Packing.Nodes[13].Clone();
node.DefaultValues[0] = scriptTypeTypeName;
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
node.Title = "Unpack " + scriptTypeName;
node.Description = tooltip;
((IList<NodeArchetype>)group.Archetypes).Add(node);
}
foreach (var member in scriptType.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly))
{
if (member.IsGeneric)
continue;
if (member.IsMethod)
{
// Skip methods not declared in this type
if (member.Type is MethodInfo m && m.GetBaseDefinition().DeclaringType != m.DeclaringType)
continue;
var name = member.Name;
if (name == "ToString")
continue;
// Skip if searching by name doesn't return a match
var members = scriptType.GetMembers(name, MemberTypes.Method, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
if (!members.Contains(member))
continue;
// Check if method is valid for Visual Script usage
if (SurfaceUtils.IsValidVisualScriptInvokeMethod(member, out var parameters))
{
// Create node archetype
var node = (NodeArchetype)Archetypes.Function.Nodes[3].Clone();
node.DefaultValues[0] = scriptTypeTypeName;
node.DefaultValues[1] = name;
node.DefaultValues[2] = parameters.Length;
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
node.Title = SurfaceUtils.GetMethodDisplayName((string)node.DefaultValues[1]);
node.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member);
node.SubTitle = string.Format(" (in {0})", scriptTypeName);
node.Tag = member;
// Create group archetype
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 16);
if (!_cache.TryGetValue(groupKey, out var group))
{
group = new GroupArchetype
{
GroupID = groupKey.Value,
Name = groupKey.Key,
Color = new Color(109, 160, 24),
Tag = _version,
Archetypes = new List<NodeArchetype>(),
};
_cache.Add(groupKey, group);
}
// Add node to the group
((IList<NodeArchetype>)group.Archetypes).Add(node);
#if DEBUG_INVOKE_METHODS_SEARCHING
Editor.LogWarning(scriptTypeTypeName + " -> " + member.GetSignature());
searchHitsCount++;
#endif
}
}
else if (member.IsField)
{
var name = member.Name;
// Skip if searching by name doesn't return a match
var members = scriptType.GetMembers(name, MemberTypes.Field, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
if (!members.Contains(member))
continue;
// Check if field is valid for Visual Script usage
if (SurfaceUtils.IsValidVisualScriptField(member))
{
if (member.HasGet)
{
// Create node archetype
var node = (NodeArchetype)Archetypes.Function.Nodes[6].Clone();
node.DefaultValues[0] = scriptTypeTypeName;
node.DefaultValues[1] = name;
node.DefaultValues[2] = member.ValueType.TypeName;
node.DefaultValues[3] = member.IsStatic;
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
node.Title = "Get " + name;
node.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member);
node.SubTitle = string.Format(" (in {0})", scriptTypeName);
// Create group archetype
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 16);
if (!_cache.TryGetValue(groupKey, out var group))
{
group = new GroupArchetype
{
GroupID = groupKey.Value,
Name = groupKey.Key,
Color = new Color(109, 160, 24),
Tag = _version,
Archetypes = new List<NodeArchetype>(),
};
_cache.Add(groupKey, group);
}
// Add node to the group
((IList<NodeArchetype>)group.Archetypes).Add(node);
#if DEBUG_FIELDS_SEARCHING
Editor.LogWarning(scriptTypeTypeName + " -> Get " + member.GetSignature());
searchHitsCount++;
#endif
}
if (member.HasSet)
{
// Create node archetype
var node = (NodeArchetype)Archetypes.Function.Nodes[7].Clone();
node.DefaultValues[0] = scriptTypeTypeName;
node.DefaultValues[1] = name;
node.DefaultValues[2] = member.ValueType.TypeName;
node.DefaultValues[3] = member.IsStatic;
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
node.Title = "Set " + name;
node.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member);
node.SubTitle = string.Format(" (in {0})", scriptTypeName);
// Create group archetype
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 16);
if (!_cache.TryGetValue(groupKey, out var group))
{
group = new GroupArchetype
{
GroupID = groupKey.Value,
Name = groupKey.Key,
Color = new Color(109, 160, 24),
Tag = _version,
Archetypes = new List<NodeArchetype>(),
};
_cache.Add(groupKey, group);
}
// Add node to the group
((IList<NodeArchetype>)group.Archetypes).Add(node);
#if DEBUG_FIELDS_SEARCHING
Editor.LogWarning(scriptTypeTypeName + " -> Set " + member.GetSignature());
searchHitsCount++;
#endif
}
}
}
else if (member.IsEvent)
{
var name = member.Name;
// Skip if searching by name doesn't return a match
var members = scriptType.GetMembers(name, MemberTypes.Event, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
if (!members.Contains(member))
continue;
// Check if field is valid for Visual Script usage
if (SurfaceUtils.IsValidVisualScriptEvent(member))
{
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 16);
if (!_cache.TryGetValue(groupKey, out var group))
{
group = new GroupArchetype
{
GroupID = groupKey.Value,
Name = groupKey.Key,
Color = new Color(109, 160, 24),
Tag = _version,
Archetypes = new List<NodeArchetype>(),
};
_cache.Add(groupKey, group);
}
// Add Bind event node
var bindNode = (NodeArchetype)Archetypes.Function.Nodes[8].Clone();
bindNode.DefaultValues[0] = scriptTypeTypeName;
bindNode.DefaultValues[1] = name;
bindNode.Flags &= ~NodeFlags.NoSpawnViaGUI;
bindNode.Title = "Bind " + name;
bindNode.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member);
bindNode.SubTitle = string.Format(" (in {0})", scriptTypeName);
((IList<NodeArchetype>)group.Archetypes).Add(bindNode);
// Add Unbind event node
var unbindNode = (NodeArchetype)Archetypes.Function.Nodes[9].Clone();
unbindNode.DefaultValues[0] = scriptTypeTypeName;
unbindNode.DefaultValues[1] = name;
unbindNode.Flags &= ~NodeFlags.NoSpawnViaGUI;
unbindNode.Title = "Unbind " + name;
unbindNode.Description = bindNode.Description;
unbindNode.SubTitle = bindNode.SubTitle;
((IList<NodeArchetype>)group.Archetypes).Add(unbindNode);
#if DEBUG_EVENTS_SEARCHING
Editor.LogWarning(scriptTypeTypeName + " -> " + member.GetSignature());
searchHitsCount++;
#endif
}
}
}
}
// Add group to context menu (on a main thread)
FlaxEngine.Scripting.InvokeOnUpdate(() =>
{
#if DEBUG_SEARCH_TIME
var addStartTime = DateTime.Now;
#endif
lock (_locker)
{
_taskContextMenu.AddGroups(_cache.Values);
_taskContextMenu = null;
}
#if DEBUG_SEARCH_TIME
Editor.LogError($"Added items to VisjectCM in: {(DateTime.Now - addStartTime).TotalMilliseconds} ms");
#endif
});
#if DEBUG_SEARCH_TIME
Editor.LogError($"Collected {searchHitsCount} items in: {(DateTime.Now - searchStartTime).TotalMilliseconds} ms");
#endif
Profiler.EndEvent();
lock (_locker)
{
_task = null;
}
}
private static void OnCodeEditingTypesCleared()
{
Wait();
lock (_locker)
{
_cache.Clear();
_version++;
}
Editor.Instance.CodeEditing.TypesCleared -= OnCodeEditingTypesCleared;
}
}
private static NodesCache _nodesCache = new NodesCache(IterateNodesCache);
private DragActors _dragActors;
/// <summary>
@@ -547,9 +153,8 @@ namespace FlaxEditor.Surface
/// <inheritdoc />
protected override void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, Box startBox)
{
Profiler.BeginEvent("Setup Visual Script Context Menu");
// Update nodes for method overrides
Profiler.BeginEvent("Overrides");
activeCM.RemoveGroup(_methodOverridesGroupArchetype);
if (Script && !Script.WaitForLoaded(100))
{
@@ -595,11 +200,10 @@ namespace FlaxEditor.Surface
activeCM.AddGroup(_methodOverridesGroupArchetype, false);
}
Profiler.EndEvent();
// Update nodes for invoke methods (async)
NodesCache.Get(activeCM);
Profiler.EndEvent();
_nodesCache.Get(activeCM);
base.OnShowPrimaryMenu(activeCM, location, startBox);
@@ -608,7 +212,276 @@ namespace FlaxEditor.Surface
private void OnActiveContextMenuVisibleChanged(Control activeCM)
{
NodesCache.Wait();
_nodesCache.Wait();
}
private static void IterateNodesCache(ScriptType scriptType, Dictionary<KeyValuePair<string, ushort>, GroupArchetype> cache, int version)
{
// Skip Newtonsoft.Json stuff
var scriptTypeTypeName = scriptType.TypeName;
if (scriptTypeTypeName.StartsWith("Newtonsoft.Json."))
return;
var scriptTypeName = scriptType.Name;
// Enum
if (scriptType.IsEnum)
{
// Create node archetype
var node = (NodeArchetype)Archetypes.Constants.Nodes[10].Clone();
node.DefaultValues[0] = Activator.CreateInstance(scriptType.Type);
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
node.Title = scriptTypeName;
node.Description = Editor.Instance.CodeDocs.GetTooltip(scriptType);
// Create group archetype
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 2);
if (!cache.TryGetValue(groupKey, out var group))
{
group = new GroupArchetype
{
GroupID = groupKey.Value,
Name = groupKey.Key,
Color = new Color(243, 156, 18),
Tag = version,
Archetypes = new List<NodeArchetype>(),
};
cache.Add(groupKey, group);
}
// Add node to the group
((IList<NodeArchetype>)group.Archetypes).Add(node);
return;
}
// Structure
if (scriptType.IsValueType)
{
if (scriptType.IsVoid)
return;
// Create group archetype
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 4);
if (!cache.TryGetValue(groupKey, out var group))
{
group = new GroupArchetype
{
GroupID = groupKey.Value,
Name = groupKey.Key,
Color = new Color(155, 89, 182),
Tag = version,
Archetypes = new List<NodeArchetype>(),
};
cache.Add(groupKey, group);
}
var tooltip = Editor.Instance.CodeDocs.GetTooltip(scriptType);
// Create Pack node archetype
var node = (NodeArchetype)Archetypes.Packing.Nodes[6].Clone();
node.DefaultValues[0] = scriptTypeTypeName;
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
node.Title = "Pack " + scriptTypeName;
node.Description = tooltip;
((IList<NodeArchetype>)group.Archetypes).Add(node);
// Create Unpack node archetype
node = (NodeArchetype)Archetypes.Packing.Nodes[13].Clone();
node.DefaultValues[0] = scriptTypeTypeName;
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
node.Title = "Unpack " + scriptTypeName;
node.Description = tooltip;
((IList<NodeArchetype>)group.Archetypes).Add(node);
}
foreach (var member in scriptType.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly))
{
if (member.IsGeneric)
continue;
if (member.IsMethod)
{
// Skip methods not declared in this type
if (member.Type is MethodInfo m && m.GetBaseDefinition().DeclaringType != m.DeclaringType)
continue;
var name = member.Name;
if (name == "ToString")
continue;
// Skip if searching by name doesn't return a match
var members = scriptType.GetMembers(name, MemberTypes.Method, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
if (!members.Contains(member))
continue;
// Check if method is valid for Visual Script usage
if (SurfaceUtils.IsValidVisualScriptInvokeMethod(member, out var parameters))
{
// Create node archetype
var node = (NodeArchetype)Archetypes.Function.Nodes[3].Clone();
node.DefaultValues[0] = scriptTypeTypeName;
node.DefaultValues[1] = name;
node.DefaultValues[2] = parameters.Length;
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
node.Title = SurfaceUtils.GetMethodDisplayName((string)node.DefaultValues[1]);
node.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member);
node.SubTitle = string.Format(" (in {0})", scriptTypeName);
node.Tag = member;
// Create group archetype
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 16);
if (!cache.TryGetValue(groupKey, out var group))
{
group = new GroupArchetype
{
GroupID = groupKey.Value,
Name = groupKey.Key,
Color = new Color(109, 160, 24),
Tag = version,
Archetypes = new List<NodeArchetype>(),
};
cache.Add(groupKey, group);
}
// Add node to the group
((IList<NodeArchetype>)group.Archetypes).Add(node);
#if DEBUG_INVOKE_METHODS_SEARCHING
Editor.LogWarning(scriptTypeTypeName + " -> " + member.GetSignature());
#endif
}
}
else if (member.IsField)
{
var name = member.Name;
// Skip if searching by name doesn't return a match
var members = scriptType.GetMembers(name, MemberTypes.Field, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
if (!members.Contains(member))
continue;
// Check if field is valid for Visual Script usage
if (SurfaceUtils.IsValidVisualScriptField(member))
{
if (member.HasGet)
{
// Create node archetype
var node = (NodeArchetype)Archetypes.Function.Nodes[6].Clone();
node.DefaultValues[0] = scriptTypeTypeName;
node.DefaultValues[1] = name;
node.DefaultValues[2] = member.ValueType.TypeName;
node.DefaultValues[3] = member.IsStatic;
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
node.Title = "Get " + name;
node.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member);
node.SubTitle = string.Format(" (in {0})", scriptTypeName);
// Create group archetype
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 16);
if (!cache.TryGetValue(groupKey, out var group))
{
group = new GroupArchetype
{
GroupID = groupKey.Value,
Name = groupKey.Key,
Color = new Color(109, 160, 24),
Tag = version,
Archetypes = new List<NodeArchetype>(),
};
cache.Add(groupKey, group);
}
// Add node to the group
((IList<NodeArchetype>)group.Archetypes).Add(node);
#if DEBUG_FIELDS_SEARCHING
Editor.LogWarning(scriptTypeTypeName + " -> Get " + member.GetSignature());
#endif
}
if (member.HasSet)
{
// Create node archetype
var node = (NodeArchetype)Archetypes.Function.Nodes[7].Clone();
node.DefaultValues[0] = scriptTypeTypeName;
node.DefaultValues[1] = name;
node.DefaultValues[2] = member.ValueType.TypeName;
node.DefaultValues[3] = member.IsStatic;
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
node.Title = "Set " + name;
node.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member);
node.SubTitle = string.Format(" (in {0})", scriptTypeName);
// Create group archetype
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 16);
if (!cache.TryGetValue(groupKey, out var group))
{
group = new GroupArchetype
{
GroupID = groupKey.Value,
Name = groupKey.Key,
Color = new Color(109, 160, 24),
Tag = version,
Archetypes = new List<NodeArchetype>(),
};
cache.Add(groupKey, group);
}
// Add node to the group
((IList<NodeArchetype>)group.Archetypes).Add(node);
#if DEBUG_FIELDS_SEARCHING
Editor.LogWarning(scriptTypeTypeName + " -> Set " + member.GetSignature());
#endif
}
}
}
else if (member.IsEvent)
{
var name = member.Name;
// Skip if searching by name doesn't return a match
var members = scriptType.GetMembers(name, MemberTypes.Event, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
if (!members.Contains(member))
continue;
// Check if field is valid for Visual Script usage
if (SurfaceUtils.IsValidVisualScriptEvent(member))
{
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 16);
if (!cache.TryGetValue(groupKey, out var group))
{
group = new GroupArchetype
{
GroupID = groupKey.Value,
Name = groupKey.Key,
Color = new Color(109, 160, 24),
Tag = version,
Archetypes = new List<NodeArchetype>(),
};
cache.Add(groupKey, group);
}
// Add Bind event node
var bindNode = (NodeArchetype)Archetypes.Function.Nodes[8].Clone();
bindNode.DefaultValues[0] = scriptTypeTypeName;
bindNode.DefaultValues[1] = name;
bindNode.Flags &= ~NodeFlags.NoSpawnViaGUI;
bindNode.Title = "Bind " + name;
bindNode.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member);
bindNode.SubTitle = string.Format(" (in {0})", scriptTypeName);
((IList<NodeArchetype>)group.Archetypes).Add(bindNode);
// Add Unbind event node
var unbindNode = (NodeArchetype)Archetypes.Function.Nodes[9].Clone();
unbindNode.DefaultValues[0] = scriptTypeTypeName;
unbindNode.DefaultValues[1] = name;
unbindNode.Flags &= ~NodeFlags.NoSpawnViaGUI;
unbindNode.Title = "Unbind " + name;
unbindNode.Description = bindNode.Description;
unbindNode.SubTitle = bindNode.SubTitle;
((IList<NodeArchetype>)group.Archetypes).Add(unbindNode);
#if DEBUG_EVENTS_SEARCHING
Editor.LogWarning(scriptTypeTypeName + " -> " + member.GetSignature());
#endif
}
}
}
}
/// <inheritdoc />
@@ -686,7 +559,7 @@ namespace FlaxEditor.Surface
{
if (IsDisposing)
return;
NodesCache.Wait();
_nodesCache.Wait();
base.OnDestroy();
}