// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using System.Reflection; using FlaxEditor.Scripting; using FlaxEditor.Surface; using FlaxEngine; using MethodInfo = System.Reflection.MethodInfo; namespace FlaxEditor.Modules.SourceCodeEditing { /// /// Cached collection of custom nodes types including cached node archetypes for each one of them. /// /// [HideInEditor] public class CachedCustomAnimGraphNodesCollection : CachedTypesCollection { private List _archetypes; /// /// Gets a value indicating whether this instance has any type from game scripts (or editor scripts) - those can be reloaded at runtime so prevent crashes. /// public bool HasTypeFromGameScripts { get; protected set; } /// public CachedCustomAnimGraphNodesCollection(int capacity, ScriptType type, Func checkFunc, Func checkAssembly) : base(capacity, type, checkFunc, checkAssembly) { } /// /// Gets the cached archetypes. /// /// The archetypes collection. public List GetArchetypes() { Get(); return _archetypes; } /// protected override void Search() { base.Search(); HasTypeFromGameScripts = false; if (_list.Count == 0) { _archetypes?.Clear(); return; } if (_archetypes == null) _archetypes = new List(_list.Capacity); _archetypes.Clear(); for (int i = _list.Count - 1; i >= 0 && _list.Count > 0; i--) { var nodeType = _list[i].Type; var methods = nodeType.GetMethods(BindingFlags.Static | BindingFlags.Public); for (int j = 0; j < methods.Length; j++) { var arch = GetArchetype(methods[j]); if (arch != null) { // Cache type _archetypes.Add(arch); } } } } private NodeArchetype GetArchetype(MethodBase method) { // Validate method signature if (!method.IsStatic || !method.IsPublic || method as MethodInfo == null || ((MethodInfo)method).ReturnType != typeof(NodeArchetype) || method.GetParameters().Length != 0 || method.IsGenericMethod) return null; // Invoke method try { var arch = (NodeArchetype)method.Invoke(null, null); // Validate archetype if (arch.Tag != null || string.IsNullOrEmpty(arch.Title)) { Debug.LogWarning(string.Format("Method {0} from {1} returned invalid node archetype. Tag must be null and title must be specified.", method, method.DeclaringType)); return null; } if (arch.DefaultValues == null || arch.DefaultValues.Length < 2 || string.IsNullOrEmpty(arch.DefaultValues[0] as string) || string.IsNullOrEmpty(arch.DefaultValues[1] as string)) { Debug.LogWarning(string.Format("Method {0} from {1} returned invalid node archetype. Default values are invalid. DefaultValues[0] must specify the C# runtime controller typename. DefaultValues[1] must specify the node group name.", method, method.DeclaringType)); return null; } // Validate node type var typeName = Surface.Archetypes.Custom.GetNodeTypeName(arch); var type = Scripting.TypeUtils.GetType(typeName).Type; if (type == null) { Debug.LogWarning(string.Format("Method {0} from {1} returned invalid node archetype. Failed to find node logic defined in type {2}.", method, method.DeclaringType, typeName)); return null; } // Check if type comes from scripts that can be reloaded at runtime HasTypeFromGameScripts |= FlaxEngine.Scripting.IsTypeFromGameScripts(method.DeclaringType) || FlaxEngine.Scripting.IsTypeFromGameScripts(type); return arch; } catch (Exception ex) { Debug.LogWarning("Failed to get the custom node archetype."); Debug.LogWarning(ex); } return null; } /// public override void ClearTypes() { base.ClearTypes(); _archetypes?.Clear(); } } }