Внедрение кода в метод через его Атрибут с использованием ткачества Mono.Cecil и IL в Unity3D - PullRequest
0 голосов
/ 29 мая 2019

Я пытаюсь внедрить систему галочек для MenuItems (как это делают переключатели) ( пример ) ...

В течение последних часов я исследовал, как я могу сделать это, используя самый понятный способ для пользователей, которые будут использовать мой API.

Я решил использовать атрибут для внедрения кода в метод, имеющий атрибут MenuItem, и обнаружил, что есть несколько способов (AOP, DI, IoC ...) сделать это (но любая из библиотек). импортированные в Unity3D работали: PostSharp, KingAOP ...), один из этих методов IL-ткачество , эта библиотека уже реализует его.

Но при импорте библиотеки в мой проект возникает следующая ошибка:

PrecompiledAssemblyException: Multiple precompiled assemblies with the same name Mono.Cecil.dll included for the current platform. Only one assembly with the same name is allowed per platform. Assembly path: {0}
UnityEditor.Scripting.ScriptCompilation.EditorBuildRules.CreateTargetAssemblies (System.Collections.Generic.IEnumerable`1[T] customScriptAssemblies, System.Collections.Generic.IEnumerable`1[T] precompiledAssemblies) (at C:/buildslave/unity/build/Editor/Mono/Scripting/ScriptCompilation/EditorBuildRules.cs:221)
UnityEditor.Scripting.ScriptCompilation.EditorCompilation.UpdateCustomTargetAssemblies () (at C:/buildslave/unity/build/Editor/Mono/Scripting/ScriptCompilation/EditorCompilation.cs:672)
UnityEditor.Scripting.ScriptCompilation.EditorCompilation.SetAllCustomScriptAssemblyJsonContents (System.String[] paths, System.String[] contents, System.String[] guids) (at C:/buildslave/unity/build/Editor/Mono/Scripting/ScriptCompilation/EditorCompilation.cs:892)
UnityEditor.Scripting.ScriptCompilation.EditorCompilationInterface.SetAllCustomScriptAssemblyJsonContents (System.String[] allAssemblyJsonPaths, System.String[] allAssemblyJsonContents, System.String[] guids) (at C:/buildslave/unity/build/Editor/Mono/Scripting/ScriptCompilation/EditorCompilationInterface.cs:241)

Я уже открыл для этого вопрос.

Что я мог сделать? Я не знаю точно, что я делаю, поэтому я не могу проверить, вызвана ли проблема этим или потому, что я сделал это неправильно.

Я не знаю точно, как подключить мой новый созданный Компонент ...

Вот что я получил на данный момент:

using Mono.Cecil;
using Mono.Cecil.Cil;
using System;
using UnityEditor;
using Weaver;
using Weaver.Extensions;

namespace UnityEngine.Core.Editor
{
    public class RadioMenuComponent : WeaverComponent
    {
        public override string addinName => "Radio Menu";

        private TypeReference m_InvokerTypeReference;
        private MethodReference m_PerfomActionMethodRef;

        public override void VisitModule(ModuleDefinition moduleDefinition)
        {
            // Get 'Radio Menu Invoker' type
            Type invokerType = typeof(RadioMenuInvoker);
            // Import the 'Radio Menu Invoker' type
            m_InvokerTypeReference = moduleDefinition.Import(invokerType);
            // Get the type def by resolving
            TypeDefinition invokerTypeDef = m_InvokerTypeReference.Resolve();
            // Get our start sample
            m_PerfomActionMethodRef = invokerTypeDef.GetMethod("PerformAction", 1);

            // Import everything
            moduleDefinition.Import(typeof(RadioMenuAttribute));
            moduleDefinition.Import(m_PerfomActionMethodRef);
        }

        public override void VisitMethod(MethodDefinition methodDefinition)
        {
            // This isn't executing... When it's supposed to be executed?
            Debug.Log(methodDefinition.FullName);

            // Check if we have our attribute
            CustomAttribute customAttribute = methodDefinition.GetCustomAttribute<RadioMenuAttribute>();
            if (customAttribute == null)
                return;

            // Remove the attribute
            methodDefinition.CustomAttributes.Remove(customAttribute);

            MethodBody body = methodDefinition.Body;
            ILProcessor bodyProcessor = body.GetILProcessor();

            // Start of method
            {
                Instruction _02 = Instruction.Create(OpCodes.Call, methodDefinition.Module.Import(m_PerfomActionMethodRef));
                bodyProcessor.InsertBefore(body.Instructions[0], _02);
             }
        }

        public class RadioMenuInvoker
        {
            public static void PerformAction(RadioMenuAttribute radioMenu)
            {
                bool enabled = radioMenu.Enabled;

                // Set checkmark on menu item
                Menu.SetChecked(radioMenu.Path, enabled);

                // Saving editor state
                EditorPrefs.SetBool(radioMenu.Path, enabled);

                radioMenu.Enabled = enabled;

                // Perform your logic here...
            }
        }
    }

}

Я не уверен (потому что я не проверял), но мне нужно знать, вызываются ли где-нибудь унаследованные классы из WeaverComponent класса ...

using System;
using System.Collections.Generic;

namespace UnityEngine.Core.Editor
{
    using Extensions;

    // [InitializeOnLoad]
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
    public class RadioMenuAttribute : Attribute
    {
        private static Dictionary<string, HashSet<RadioMenuAttribute>> m_menus = new Dictionary<string, HashSet<RadioMenuAttribute>>();

        public string Path { get; private set; }
        public bool Enabled { get; set; }

        private RadioMenuAttribute()
        {
        }

        public RadioMenuAttribute(string name, string path)
        {
            Path = path;

            m_menus.AddOnce(name, new HashSet<RadioMenuAttribute>());
            m_menus[name].Add(this);
        }

        static RadioMenuAttribute()
        {
            // Find here all methods with 'RadioMenuAttribute'

            // Test 1 (Debug Length)
            //var attrs = typeof(UnityEditor.Editor).Assembly.GetCustomAttributes<RadioMenuAttribute>();

            //foreach (var attr in attrs)
            //{
            //    attr.Enabled = EditorPrefs.GetBool(attr.Path, false);

            //    // Delaying until first editor tick so that the menu
            //    // will be populated before setting check state, and
            //    // re-apply correct action
            //}

            //EditorApplication.delayCall += () =>
            //{
            //    foreach (var attr in attrs)
            //        PerformAction(attr);
            //};

            // Test 2 (Inject this code to all methods that contains 'RadioMenuAttribute')

            /*

                 // Toggling action
                 PerformAction( !CheckmarkMenuItem.enabled_);

             */
        }

    }
}
using UnityEditor;
using UnityEngine.Core.Editor;

namespace UnitedTeamworkAssociation.UST_SDK.Utilities.SWW.Editor
{
    using Wins;

    public static class MenuItems
    {
        [MenuItem("Source Tools/Open Steam Workshop Wrapper Window...")]
        public static void StartVectorEditor()
        {
            EditorWindow.GetWindow<SteamWorkshopWrapperEditorWindow>("Steam Workshop Wrapper", false, typeof(SceneView));
        }

        #region "Settings"

        public const string BrowseSettings = "BROWSE_SETTINGS",
                    BrowseSettings_Opt1 = "Source Tools/Settings/Browse items in Editor browser...",
                    BrowseSettings_Opt2 = "Source Tools/Settings/Browse items in Default browser...";

        [RadioMenu(BrowseSettings, BrowseSettings_Opt1)]
        [MenuItem(BrowseSettings_Opt1)]
        public static void Setting_BrowseInEditor()
        {
        }

        [RadioMenu(BrowseSettings, BrowseSettings_Opt2)]
        [MenuItem(BrowseSettings_Opt2)]
        public static void Setting_BrowseInApp()
        {
        }

        #endregion "Settings"
    }
}

Мой главный вопрос здесь. Как я могу ввести следующий код:

RadioMenuInvoker.PerformAction(xxx);

Кому:

        [RadioMenu(BrowseSettings, BrowseSettings_Opt1)]
        [MenuItem(BrowseSettings_Opt1)]
        public static void Setting_BrowseInEditor()
        {
               // Logic here...
        }

Для вывода следующего:

        [MenuItem(BrowseSettings_Opt1)]
        public static void Setting_BrowseInEditor()
        {
                RadioMenuInvoker.PerformAction(xxx);
                // Where xxx is a direct reference to the attibute?
               // Logic here...
        }

Но, как вы видите, я новичок в IL и Mono.Cecil ... Итак, я не могу понять это без помощи.

...