From c0d32a99b0e7b52fa8c02168c4166277b22fa242 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 3 Aug 2023 15:44:02 +0200 Subject: [PATCH] Refactor Visject surface nodes cache to reuse between graphs --- Source/Editor/Editor.cs | 4 +- Source/Editor/Surface/AnimGraphSurface.cs | 272 +++---- .../Surface/VisjectSurface.ContextMenu.cs | 155 ++++ Source/Editor/Surface/VisualScriptSurface.cs | 677 +++++++----------- 4 files changed, 515 insertions(+), 593 deletions(-) diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index d46bf48ed..6f2111e9c 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -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 diff --git a/Source/Editor/Surface/AnimGraphSurface.cs b/Source/Editor/Surface/AnimGraphSurface.cs index 3cc07813c..d7746662f 100644 --- a/Source/Editor/Surface/AnimGraphSurface.cs +++ b/Source/Editor/Surface/AnimGraphSurface.cs @@ -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, 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, 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(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(), - }; - _cache.Add(groupKey, group); - } - - // Add node to the group - ((IList)group.Archetypes).Add(node); - continue; - } - - // Structure - if (scriptType.IsValueType) - { - if (scriptType.IsVoid) - continue; - - // Create group archetype - var groupKey = new KeyValuePair(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(), - }; - _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)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)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); /// /// 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, 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(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(), + }; + cache.Add(groupKey, group); + } + + // Add node to the group + ((IList)group.Archetypes).Add(node); + return; + } + + // Structure + if (scriptType.IsValueType) + { + if (scriptType.IsVoid) + return; + + // Create group archetype + var groupKey = new KeyValuePair(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(), + }; + 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)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)group.Archetypes).Add(node); + } } /// @@ -488,7 +382,7 @@ namespace FlaxEditor.Surface _cmStateMachineTransitionMenu = null; } ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; - NodesCache.Wait(); + _nodesCache.Wait(); base.OnDestroy(); } diff --git a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs index 659131e8b..3ca7ddfc7 100644 --- a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs +++ b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs @@ -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 { + /// + /// Utility for easy nodes archetypes generation for Visject Surface based on scripting types. + /// + internal class NodesCache + { + public delegate void IterateType(ScriptType scriptType, Dictionary, GroupArchetype> cache, int version); + + internal static readonly List Caches = new List(8); + + private readonly object _locker = new object(); + private readonly IterateType _iterator; + private int _version; + private Task _task; + private VisjectCM _taskContextMenu; + private Dictionary, 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, 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; diff --git a/Source/Editor/Surface/VisualScriptSurface.cs b/Source/Editor/Surface/VisualScriptSurface.cs index 2cb82b351..902582311 100644 --- a/Source/Editor/Surface/VisualScriptSurface.cs +++ b/Source/Editor/Surface/VisualScriptSurface.cs @@ -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(), }; - 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, 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, 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(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(), - }; - _cache.Add(groupKey, group); - } - - // Add node to the group - ((IList)group.Archetypes).Add(node); - continue; - } - - // Structure - if (scriptType.IsValueType) - { - if (scriptType.IsVoid) - continue; - - // Create group archetype - var groupKey = new KeyValuePair(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(), - }; - _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)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)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(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(), - }; - _cache.Add(groupKey, group); - } - - // Add node to the group - ((IList)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(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(), - }; - _cache.Add(groupKey, group); - } - - // Add node to the group - ((IList)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(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(), - }; - _cache.Add(groupKey, group); - } - - // Add node to the group - ((IList)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(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(), - }; - _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)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)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; /// @@ -547,9 +153,8 @@ namespace FlaxEditor.Surface /// 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, 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(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(), + }; + cache.Add(groupKey, group); + } + + // Add node to the group + ((IList)group.Archetypes).Add(node); + return; + } + + // Structure + if (scriptType.IsValueType) + { + if (scriptType.IsVoid) + return; + + // Create group archetype + var groupKey = new KeyValuePair(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(), + }; + 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)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)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(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(), + }; + cache.Add(groupKey, group); + } + + // Add node to the group + ((IList)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(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(), + }; + cache.Add(groupKey, group); + } + + // Add node to the group + ((IList)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(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(), + }; + cache.Add(groupKey, group); + } + + // Add node to the group + ((IList)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(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(), + }; + 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)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)group.Archetypes).Add(unbindNode); + +#if DEBUG_EVENTS_SEARCHING + Editor.LogWarning(scriptTypeTypeName + " -> " + member.GetSignature()); +#endif + } + } + } } /// @@ -686,7 +559,7 @@ namespace FlaxEditor.Surface { if (IsDisposing) return; - NodesCache.Wait(); + _nodesCache.Wait(); base.OnDestroy(); }