Seneral / Node_Editor_Framework

A flexible and modular Node Editor Framework for creating node based displays and editors in Unity
https://nodeeditor.seneral.dev
MIT License
2k stars 415 forks source link

assembly.FullName.Contains ("Assembly") not working with Assembly Definition File #159

Closed No3371 closed 5 years ago

No3371 commented 6 years ago

Assembly Definition File create assembly dll file with custom name.

No3371 commented 6 years ago

Removing all the .Where() will results in insane amount of GC. To address this, I've come up with 2 ideas:

  1. Provide an interface for users to set a list of assembly name string filter to use by the .Where()
  2. Or do a lot of caching in ReflectionUtility, and don't re-setup if it's just a simple onFocus(). Will provide working code later.
Seneral commented 6 years ago

Ya, makes sense. Will see what you come up with, thanks! It could also work by adding some text file to read the custom assemblies from. Or, in hopes that it does not change too often, simply actively exclude assemblies we know are NOT user made.

Seneral commented 6 years ago

Any news? Otherwise, at it is quite an edge-case, I would say you would indeed need to edit the code at that point...

SugoiDev commented 6 years ago

I think this issue should remain open so someone can take a look at it in the future. I don't think this is an edge-case.

Custom assemblies are very much in use with Unity. Newer Unity components all use custom assemblies and in time asset store packages will also become custom assemblies.

A good start here is to ignore all assemblies with names starting with Unity. (Llot's of new packages use names like Unity.someComponent), UnityEngine. and UnityEditor. (all including the dot).

For newer .net, we also could ignore assemblies that are dynamic (check against Assembly.IsDynamic).

This will probably reduce the pressure by a lot.

As a last measure, if this is called more than once per type (I didn't study the code yet), it can be cached to a Dictionary<Type, Type[]> to improve query performance even further, at the cost of some memory.

No3371 commented 6 years ago

Yeah, I have working code, which is basically a whitelist string array and some List and List for all the stuff that need reflection in NodeEditorFramework, and use static constructor and [InitializeOnLoad] attribute to achieve best experience... not elegant enough though. I will post the code after my project deadline.

No3371 commented 6 years ago

Also I don't consider this a edge-class either due to Assembly Definition Files is a major feature after 2017 version (in my opinion).

Seneral commented 6 years ago

Alright, alright:) No promises as to when I can get to it, currently in final essay phase. No problem though since you have code, thanks!

No3371 commented 5 years ago

This suddenly comes to my mind. Here's the RefelctionUtility I modified last year, ugly but works, have to edit the assemblyFilters array to the assembly names desired.

using UnityEngine;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;

namespace NodeEditorFramework.Utilities 
{
    public static class ReflectionUtility 
    {
        public static string[] assemblyFilters =  new string[3] { "Story", "NodeEditor" };

        static readonly Assembly[] currentDomainAssembliesCache;

        public static Assembly[]  CurrentDomainAssembliesCache
        {
            get { return currentDomainAssembliesCache; }
        }

        static readonly List<Type> nodeTypes;

        public static List<Type> NodeTypes { get { return nodeTypes; }}
        static readonly List<Type> nodeCanvasTypes;

        public static List<Type> NodeCanvasTypes { get { return nodeCanvasTypes; }}
        static readonly List<Type> connectionPortStyleTypes;

        public static List<Type> ConnectionPortStyleTypes { get { return connectionPortStyleTypes; }}
        static readonly List<Type> importExportType;

        public static List<Type> ImportExportType { get { return importExportType; }}
        static readonly List<MethodInfo> usedByInputSystem;

        public static List<MethodInfo> UsedByInputSystem { get { return usedByInputSystem; }}
        static ReflectionUtility ()
        {
            DateTime time = DateTime.Now;
            currentDomainAssembliesCache = AppDomain.CurrentDomain.GetAssemblies().Where(a => assemblyFilters.Any(f => a.FullName.Contains(f))).ToArray();
            // Debug.Log("GetAssemblies:" + (DateTime.Now - time).TotalMilliseconds + "ms");

            nodeTypes = new List<Type>(50);
            nodeCanvasTypes = new List<Type>(10);
            connectionPortStyleTypes = new List<Type>(10);
            importExportType = new List<Type>(3);
            usedByInputSystem = new List<MethodInfo>(50);
            foreach (Assembly assembly in currentDomainAssembliesCache)
            {
                time = DateTime.Now;
                foreach (Type type in assembly.GetTypes())
                {
                    if (IsValidType<Node>(type)) nodeTypes.Add(type);
                    if (IsValidType<NodeCanvas>(type, typeof(NodeCanvasTypeAttribute))) nodeCanvasTypes.Add(type);
                    if (IsValidType<ConnectionPortStyle>(type)) connectionPortStyleTypes.Add(type);
                    if (IsValidType<NodeEditorFramework.IO.ImportExportFormat>(type)) importExportType.Add(type);
                    foreach (MethodInfo method in type.GetMethods (BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)) 
                    {
                        if (method.GetCustomAttributes(true).Any(t => t.GetType() == typeof(EventHandlerAttribute) || t.GetType() == typeof(HotkeyAttribute) || t.GetType() == typeof(ContextEntryAttribute) || t.GetType() == typeof(ContextFillerAttribute))) usedByInputSystem.Add(method);
                    }   
                }
                // Debug.Log("Types in assembly " + assembly.FullName + ":" + (DateTime.Now - time).TotalMilliseconds + "ms");
            }
        }

        public class ReflectionSearchIgnoreAttribute : Attribute
        {
            public ReflectionSearchIgnoreAttribute () { }
        }

        static bool IsValidType<T> (Type type, Type hasAttr = null)
        {
            if (type.IsClass
            && !type.IsAbstract
            && type.IsSubclassOf(typeof(T))
            && ((hasAttr == null)? true : (type.GetCustomAttributes (hasAttr, false).Length > 0))
            && !(type.GetCustomAttributes (typeof(ReflectionSearchIgnoreAttribute), false).Length > 0))
            return true;
            else return false;
        }

        /// <summary>
        /// Gets all non-abstract types extending the given base type
        /// </summary>
        public static Type[] getSubTypes (Type baseType) 
        {
            return CurrentDomainAssembliesCache
                .SelectMany ((Assembly assembly) => assembly.GetTypes ()
                    .Where ((Type T) => 
                        (T.IsClass && !T.IsAbstract) 
                        && T.IsSubclassOf (baseType)
                        && !T.GetCustomAttributes (typeof(ReflectionSearchIgnoreAttribute), false).Any ())
                ).ToArray ();
        }

        /// <summary>
        /// Gets all non-abstract types extending the given base type and with the given attribute
        /// </summary>
        public static Type[] getSubTypes (Type baseType, Type hasAttribute) 
        {
            return CurrentDomainAssembliesCache
                .SelectMany ((Assembly assembly) => assembly.GetTypes ()
                    .Where ((Type T) => 
                        (T.IsClass && !T.IsAbstract) 
                        && T.IsSubclassOf (baseType)
                        && T.GetCustomAttributes (hasAttribute, false).Any ()
                        && !T.GetCustomAttributes (typeof(ReflectionSearchIgnoreAttribute), false).Any ())
                ).ToArray ();
        }

        /// <summary>
        /// Returns all fields that should be serialized in the given type
        /// </summary>
        public static FieldInfo[] getSerializedFields (Type type) 
        {
            return type.GetFields (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
                .Where ((FieldInfo field) => 
                    (field.IsPublic && !field.GetCustomAttributes (typeof(NonSerializedAttribute), true).Any ())
                    || field.GetCustomAttributes (typeof(SerializeField), true).Any ()
                    && !field.GetCustomAttributes (typeof(ReflectionSearchIgnoreAttribute), false).Any ())
                .ToArray ();
        }

        /// <summary>
        /// Returns all fields that should be serialized in the given type, minus the fields declared in or above the given base type
        /// </summary>
        public static FieldInfo[] getSerializedFields (Type type, Type hiddenBaseType) 
        {
            return type.GetFields (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
                .Where ((FieldInfo field) => 
                    (hiddenBaseType == null || !field.DeclaringType.IsAssignableFrom (hiddenBaseType))
                    && ((field.IsPublic && !field.GetCustomAttributes (typeof(NonSerializedAttribute), true).Any ()) 
                        || field.GetCustomAttributes (typeof(SerializeField), true).Any ()
                        && !field.GetCustomAttributes (typeof(ReflectionSearchIgnoreAttribute), false).Any ()))
                .ToArray ();
        }

        /// <summary>
        /// Gets all fields in the classType of the specified fieldType
        /// </summary>
        public static FieldInfo[] getFieldsOfType (Type classType, Type fieldType) 
        {
            return classType.GetFields (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
                .Where ((FieldInfo field) => 
                    field.FieldType == fieldType || field.FieldType.IsSubclassOf (fieldType)
                    && !field.GetCustomAttributes (typeof(ReflectionSearchIgnoreAttribute), false).Any ())
                .ToArray ();
        }
    }
}
No3371 commented 5 years ago

To adapt to this change to reflection utility and support adf, you have to modify places also using refelctions.

Take NodeEditorInputSystem for example: 圖片

All the others that have to be modified like this are located inside CoreExtension folder.