diff --git a/Editor/Commands/LinkGeneratorCommand.cs b/Editor/Commands/LinkGeneratorCommand.cs index 200b660..b9cae1d 100644 --- a/Editor/Commands/LinkGeneratorCommand.cs +++ b/Editor/Commands/LinkGeneratorCommand.cs @@ -1,12 +1,7 @@ using HybridCLR.Editor.Link; -using HybridCLR.Editor.Meta; using System; using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Reflection; -using System.Text; -using System.Threading.Tasks; using UnityEditor; using UnityEngine; @@ -56,6 +51,7 @@ namespace HybridCLR.Editor.Commands Debug.Log($"[LinkGeneratorCommand] hotfix assembly count:{hotfixAssembles.Count}, ref type count:{refTypes.Count} output:{Application.dataPath}/{ls.outputLinkFile}"); var linkXmlWriter = new LinkXmlWriter(); linkXmlWriter.Write($"{Application.dataPath}/{ls.outputLinkFile}", refTypes); + AssetDatabase.Refresh(); } } } diff --git a/Editor/HybridCLRGlobalSettings.cs b/Editor/HybridCLRGlobalSettings.cs deleted file mode 100644 index 6b4c145..0000000 --- a/Editor/HybridCLRGlobalSettings.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Reflection; -using UnityEditorInternal; -using UnityEngine; - -[CreateAssetMenu(fileName = "HybridCLRGlobalSettings", menuName = "HybridCLR/GlobalSettings")] -public class HybridCLRGlobalSettings : ScriptableObject -{ - [Header("开启HybridCLR插件")] - public bool enable = true; - - [Header("从gitee clone插件代码")] - public bool cloneFromGitee = true; // false 则从github上拉取 - - [Header("热更新Assembly Definition Modules")] - public AssemblyDefinitionAsset[] hotUpdateAssemblyDefinitions; - - [Header("热更新dlls")] - public string[] hotUpdateAssemblies; - - [Header("自动扫描生成的link.xml路径")] - public string outputLinkFile = "HybridCLR/link.xml"; - - [Header("自动扫描生成的AOTGenericReferences.cs路径")] - public string outputAOTGenericReferenceFile = "Main/AOTGenericReferences.cs"; - - [Header("AOT泛型实例化搜索迭代次数")] - public int maxGenericReferenceIteration = 4; - - [Header("预留MonoPInvokeCallbackAttribute函数个数")] - public int ReversePInvokeWrapperCount = 10; - - [Header("MethodBridge泛型搜索迭代次数")] - public int maxMethodBridgeGenericIteration = 4; -} diff --git a/Editor/Installer/InstallerWindow.cs b/Editor/Installer/InstallerWindow.cs index 0383d1c..00765df 100644 --- a/Editor/Installer/InstallerWindow.cs +++ b/Editor/Installer/InstallerWindow.cs @@ -1,12 +1,8 @@ using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using UnityEditor ; +using System.Reflection; +using UnityEditor; using UnityEngine; - + namespace HybridCLR.Editor.Installer { @@ -21,6 +17,19 @@ namespace HybridCLR.Editor.Installer private void OnGUI() { + var rect = new Rect + { + x = EditorGUIUtility.currentViewWidth - 24, + y = 5, + width = 24, + height = 24 + }; + var content = EditorGUIUtility.IconContent("Settings"); + content.tooltip = "点击打开HybridCLR Settings"; + if (GUI.Button(rect, content, GUI.skin.GetStyle("IconButton"))) + { + SettingsService.OpenProjectSettings("Project/HybridCLR Settings"); + } string minCompatibleVersion = m_Controller.GetMinCompatibleVersion(m_Controller.Il2CppBranch); GUI.enabled = true; GUILayout.Space(10f); @@ -32,6 +41,7 @@ namespace HybridCLR.Editor.Installer + $"由于安装HybridCLR时需要从il2cpp_plus兼容版本{m_Controller.Il2CppBranch}(而不是你项目版本)拷贝il2cpp目录,\n" + $"你必须同时安装兼容版本 {m_Controller.Il2CppBranch} 才能完成安装", EditorStyles.wordWrappedLabel); EditorGUILayout.LabelField("=============================================="); + GUILayout.Space(10f); EditorGUILayout.BeginVertical("box"); @@ -56,7 +66,7 @@ namespace HybridCLR.Editor.Installer } GUI.enabled = true; EditorGUILayout.EndHorizontal(); - + } @@ -79,29 +89,29 @@ namespace HybridCLR.Editor.Installer switch (err) { case InstallErrorCode.Ok: - { - break; - } + { + break; + } case InstallErrorCode.Il2CppInstallPathNotExists: - { - EditorGUILayout.HelpBox("li2cpp 路径不存在", MessageType.Error); - break; - } + { + EditorGUILayout.HelpBox("li2cpp 路径不存在", MessageType.Error); + break; + } case InstallErrorCode.InvalidUnityInstallPath: - { - EditorGUILayout.HelpBox($"Unity安装目录必须包含版本号,否则无法识别版本", MessageType.Error); - break; - } + { + EditorGUILayout.HelpBox($"Unity安装目录必须包含版本号,否则无法识别版本", MessageType.Error); + break; + } case InstallErrorCode.Il2CppInstallPathNotMatchIl2CppBranch: - { - EditorGUILayout.HelpBox($"il2cpp 版本不兼容,最小版本为 {m_Controller.GetMinCompatibleVersion(m_Controller.Il2CppBranch)}", MessageType.Error); - break; - } + { + EditorGUILayout.HelpBox($"il2cpp 版本不兼容,最小版本为 {m_Controller.GetMinCompatibleVersion(m_Controller.Il2CppBranch)}", MessageType.Error); + break; + } case InstallErrorCode.NotIl2CppPath: - { - EditorGUILayout.HelpBox($"当前选择的路径不是il2cpp目录(必须类似 xxx/il2cpp)", MessageType.Error); - break; - } + { + EditorGUILayout.HelpBox($"当前选择的路径不是il2cpp目录(必须类似 xxx/il2cpp)", MessageType.Error); + break; + } default: throw new Exception($"not support {err}"); } } diff --git a/Editor/Settings.meta b/Editor/Settings.meta new file mode 100644 index 0000000..f73ef75 --- /dev/null +++ b/Editor/Settings.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3708ee1d4035cb14abaa4d64a8ec8148 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Settings/HybridCLRGlobalSettings.cs b/Editor/Settings/HybridCLRGlobalSettings.cs new file mode 100644 index 0000000..0b7f900 --- /dev/null +++ b/Editor/Settings/HybridCLRGlobalSettings.cs @@ -0,0 +1,35 @@ +using UnityEditorInternal; +using UnityEngine; +namespace HybridCLR.Editor +{ + [FilePath("ProjectSettings/HybridCLRGlobalSettings.asset")] + public class HybridCLRGlobalSettings : ScriptableSingleton + { + [Header("开启HybridCLR插件")] + public bool enable = true; + + [Header("从gitee clone插件代码")] + public bool cloneFromGitee = true; // false 则从github上拉取 + + [Header("热更新Assembly Definition Modules")] + public AssemblyDefinitionAsset[] hotUpdateAssemblyDefinitions; + + [Header("热更新dlls")] + public string[] hotUpdateAssemblies; + + [Header("自动扫描生成的link.xml路径")] + public string outputLinkFile = "HybridCLRData/Generated/link.xml"; + + [Header("自动扫描生成的AOTGenericReferences.cs路径")] + public string outputAOTGenericReferenceFile = "HybridCLRData/Generated/AOTGenericReferences.cs"; + + [Header("AOT泛型实例化搜索迭代次数")] + public int maxGenericReferenceIteration = 4; + + [Header("预留MonoPInvokeCallbackAttribute函数个数")] + public int ReversePInvokeWrapperCount = 10; + + [Header("MethodBridge泛型搜索迭代次数")] + public int maxMethodBridgeGenericIteration = 4; + } +} diff --git a/Editor/HybridCLRGlobalSettings.cs.meta b/Editor/Settings/HybridCLRGlobalSettings.cs.meta similarity index 100% rename from Editor/HybridCLRGlobalSettings.cs.meta rename to Editor/Settings/HybridCLRGlobalSettings.cs.meta diff --git a/Editor/Settings/HybridCLRSettingProvider.cs b/Editor/Settings/HybridCLRSettingProvider.cs new file mode 100644 index 0000000..dd01c83 --- /dev/null +++ b/Editor/Settings/HybridCLRSettingProvider.cs @@ -0,0 +1,142 @@ +using System; +using System.Reflection; +using UnityEditor; +using UnityEditor.Presets; +using UnityEngine; +using UnityEngine.UIElements; +namespace HybridCLR.Editor +{ + public class HybridCLRSettingsProvider : SettingsProvider + { + private static SerializedObject m_SerializedObject; + private SerializedProperty m_Enable; + private SerializedProperty m_CloneFromGitee; + private SerializedProperty m_HotUpdateAssemblyDefinitions; + private SerializedProperty m_HotUpdateAssemblies; + private SerializedProperty m_OutputLinkFile; + private SerializedProperty m_OutputAOTGenericReferenceFile; + private SerializedProperty m_MaxGenericReferenceIteration; + private SerializedProperty m_ReversePInvokeWrapperCount; + private SerializedProperty m_MaxMethodBridgeGenericIteration; + private GUIStyle buttonStyle; + public HybridCLRSettingsProvider() : base("Project/HybridCLR Settings", SettingsScope.Project) { } + public override void OnActivate(string searchContext, VisualElement rootElement) + { + HybridCLRGlobalSettings.Instance.Save(); + var setting = HybridCLRGlobalSettings.Instance; + setting.hideFlags &= ~HideFlags.NotEditable; + m_SerializedObject ??= new SerializedObject(setting); + m_Enable = m_SerializedObject.FindProperty("enable"); + m_CloneFromGitee = m_SerializedObject.FindProperty("cloneFromGitee"); + m_HotUpdateAssemblyDefinitions = m_SerializedObject.FindProperty("hotUpdateAssemblyDefinitions"); + m_HotUpdateAssemblies = m_SerializedObject.FindProperty("hotUpdateAssemblies"); + m_OutputLinkFile = m_SerializedObject.FindProperty("outputLinkFile"); + m_OutputAOTGenericReferenceFile = m_SerializedObject.FindProperty("outputAOTGenericReferenceFile"); + m_MaxGenericReferenceIteration = m_SerializedObject.FindProperty("maxGenericReferenceIteration"); + m_ReversePInvokeWrapperCount = m_SerializedObject.FindProperty("ReversePInvokeWrapperCount"); + m_MaxMethodBridgeGenericIteration = m_SerializedObject.FindProperty("maxMethodBridgeGenericIteration"); + } + public override void OnTitleBarGUI() + { + base.OnTitleBarGUI(); + var rect = GUILayoutUtility.GetLastRect(); + buttonStyle ??= GUI.skin.GetStyle("IconButton"); + + #region 绘制官方网站跳转按钮 + var w = rect.x + rect.width; + rect.x = w - 58; + rect.y += 6; + rect.width = rect.height = 18; + var content = EditorGUIUtility.IconContent("_Help"); + content.tooltip = "点击访问 HybridCLR 官方文档"; + if (GUI.Button(rect, content, buttonStyle)) + { + Application.OpenURL("https://focus-creative-games.github.io/hybridclr/"); + } + #endregion + + #region 绘制 Preset + rect.x += 19; + content = EditorGUIUtility.IconContent("Preset.Context"); + content.tooltip = "点击存储或加载 Preset ."; + if (GUI.Button(rect, content, buttonStyle)) + { + var target = HybridCLRGlobalSettings.Instance; + var receiver = ScriptableObject.CreateInstance(); + receiver.Init(target); + PresetSelector.ShowSelector(target, null, true, receiver); + } + #endregion + #region 绘制 Reset + rect.x += 19; + content = EditorGUIUtility.IconContent("_Popup"); + content.tooltip = "Reset"; + if (GUI.Button(rect, content, buttonStyle)) + { + GenericMenu menu = new GenericMenu(); + menu.AddItem(new GUIContent("Reset"), false, () => + { + Undo.RecordObject(HybridCLRGlobalSettings.Instance, "Capture Value for Reset"); + var dv = ScriptableObject.CreateInstance(); + var json = EditorJsonUtility.ToJson(dv); + EditorJsonUtility.FromJsonOverwrite(json,HybridCLRGlobalSettings.Instance); + HybridCLRGlobalSettings.Instance.Save(); + }); + menu.ShowAsContext(); + } + #endregion + } + public override void OnGUI(string searchContext) + { + using (CreateSettingsWindowGUIScope()) + { + if (m_SerializedObject == null || !m_SerializedObject.targetObject) + { + m_SerializedObject = null; + m_SerializedObject = new SerializedObject(HybridCLRGlobalSettings.Instance); + } + m_SerializedObject.Update(); + EditorGUI.BeginChangeCheck(); + EditorGUILayout.PropertyField(m_Enable); + EditorGUILayout.PropertyField(m_CloneFromGitee); + EditorGUILayout.PropertyField(m_HotUpdateAssemblyDefinitions); + EditorGUILayout.PropertyField(m_HotUpdateAssemblies); + EditorGUILayout.PropertyField(m_OutputLinkFile); + EditorGUILayout.PropertyField(m_OutputAOTGenericReferenceFile); + EditorGUILayout.PropertyField(m_MaxGenericReferenceIteration); + EditorGUILayout.PropertyField(m_ReversePInvokeWrapperCount); + EditorGUILayout.PropertyField(m_MaxMethodBridgeGenericIteration); + if (EditorGUI.EndChangeCheck()) + { + m_SerializedObject.ApplyModifiedProperties(); + HybridCLRGlobalSettings.Instance.Save(); + } + } + } + private IDisposable CreateSettingsWindowGUIScope() + { + var unityEditorAssembly = Assembly.GetAssembly(typeof(EditorWindow)); + var type = unityEditorAssembly.GetType("UnityEditor.SettingsWindow+GUIScope"); + return Activator.CreateInstance(type) as IDisposable; + } + public override void OnDeactivate() + { + base.OnDeactivate(); + HybridCLRGlobalSettings.Instance.Save(); + m_SerializedObject = null; + } + [SettingsProvider] + public static SettingsProvider CreateMyCustomSettingsProvider() + { + if (HybridCLRGlobalSettings.Instance) + { + var provider = new HybridCLRSettingsProvider + { + keywords = GetSearchKeywordsFromSerializedObject(m_SerializedObject ??= new SerializedObject(HybridCLRGlobalSettings.Instance)) + }; + return provider; + } + return null; + } + } +} \ No newline at end of file diff --git a/Editor/Settings/HybridCLRSettingProvider.cs.meta b/Editor/Settings/HybridCLRSettingProvider.cs.meta new file mode 100644 index 0000000..0a21f37 --- /dev/null +++ b/Editor/Settings/HybridCLRSettingProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d2bd1694fedc8b54c88bb9f6c67907d3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Settings/MenuProvider.cs b/Editor/Settings/MenuProvider.cs new file mode 100644 index 0000000..e02c35b --- /dev/null +++ b/Editor/Settings/MenuProvider.cs @@ -0,0 +1,13 @@ +using UnityEditor; +using UnityEngine; + +public static class MenuProvider +{ + [MenuItem("HybridCLR/Settings", priority = 200)] + public static void OpenSettings() => SettingsService.OpenProjectSettings("Project/HybridCLR Settings"); + [MenuItem("HybridCLR/About us", priority = 400)] + public static void AboutUs() => Application.OpenURL("https://focus-creative-games.github.io/hybridclr/about/"); + + [MenuItem("HybridCLR/Documents", priority = 399)] + public static void OpenDoc() => Application.OpenURL("https://focus-creative-games.github.io/hybridclr/"); +} diff --git a/Editor/Settings/MenuProvider.cs.meta b/Editor/Settings/MenuProvider.cs.meta new file mode 100644 index 0000000..a4474b2 --- /dev/null +++ b/Editor/Settings/MenuProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a475a5f281298b84da32373694704c68 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Settings/ScriptableSignleton.cs b/Editor/Settings/ScriptableSignleton.cs new file mode 100644 index 0000000..6ac2ef9 --- /dev/null +++ b/Editor/Settings/ScriptableSignleton.cs @@ -0,0 +1,94 @@ +using System; +using System.IO; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; + +namespace HybridCLR.Editor +{ + public class ScriptableSingleton : ScriptableObject where T : ScriptableObject + { + private static T s_Instance; + public static T Instance + { + get + { + if (!s_Instance) + { + CreateAndLoad(); + } + return s_Instance; + } + } + private static void CreateAndLoad() + { + string filePath = GetFilePath(); + if (!string.IsNullOrEmpty(filePath)) + { + var arr = InternalEditorUtility.LoadSerializedFileAndForget(filePath); + s_Instance = arr.Length > 0 ? arr[0] as T : CreateInstance(); + } + else + { + Debug.LogError($"{nameof(ScriptableSingleton)}: 请指定单例存档路径! "); + } + } + + public void Save(bool saveAsText=true) + { + if (!s_Instance) + { + Debug.LogError("Cannot save ScriptableSingleton: no instance!"); + return; + } + + string filePath = GetFilePath(); + if (!string.IsNullOrEmpty(filePath)) + { + string directoryName = Path.GetDirectoryName(filePath); + if (!Directory.Exists(directoryName)) + { + Directory.CreateDirectory(directoryName); + } + UnityEngine.Object[] obj = new T[1] { s_Instance }; + InternalEditorUtility.SaveToSerializedFileAndForget(obj, filePath, saveAsText); + } + } + protected static string GetFilePath() + { + Type typeFromHandle = typeof(T); + object[] customAttributes = typeFromHandle.GetCustomAttributes(inherit: true); + object[] array = customAttributes; + foreach (object obj in array) + { + if (obj is FilePathAttribute) + { + FilePathAttribute filePathAttribute = obj as FilePathAttribute; + return filePathAttribute.filepath; + } + } + return string.Empty; + } + } + [AttributeUsage(AttributeTargets.Class)] + public class FilePathAttribute : Attribute + { + internal string filepath; + /// + /// 单例存放路径 + /// + /// 相对 Project 路径 + public FilePathAttribute(string path) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentException("Invalid relative path (it is empty)"); + } + if (path[0] == '/') + { + path = path.Substring(1); + } + filepath = path; + } + } +} \ No newline at end of file diff --git a/Editor/Settings/ScriptableSignleton.cs.meta b/Editor/Settings/ScriptableSignleton.cs.meta new file mode 100644 index 0000000..fa1923b --- /dev/null +++ b/Editor/Settings/ScriptableSignleton.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 851c44a8da67a9742a7ea68815383f27 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Settings/SettingsPresetReceiver.cs b/Editor/Settings/SettingsPresetReceiver.cs new file mode 100644 index 0000000..3636c49 --- /dev/null +++ b/Editor/Settings/SettingsPresetReceiver.cs @@ -0,0 +1,37 @@ +using UnityEditor; +using UnityEditor.Presets; +using UnityEngine; + +namespace HybridCLR.Editor +{ + public class SettingsPresetReceiver : PresetSelectorReceiver + { + private Object m_Target; + private Preset m_InitialValue; + + internal void Init(Object target) + { + m_Target = target; + m_InitialValue = new Preset(target); + } + public override void OnSelectionChanged(Preset selection) + { + if (selection != null) + { + Undo.RecordObject(m_Target, "Apply Preset " + selection.name); + selection.ApplyTo(m_Target); + } + else + { + Undo.RecordObject(m_Target, "Cancel Preset"); + m_InitialValue.ApplyTo(m_Target); + } + SettingsService.RepaintAllSettingsWindow(); + } + public override void OnSelectionClosed(Preset selection) + { + OnSelectionChanged(selection); + Object.DestroyImmediate(this); + } + } +} \ No newline at end of file diff --git a/Editor/Settings/SettingsPresetReceiver.cs.meta b/Editor/Settings/SettingsPresetReceiver.cs.meta new file mode 100644 index 0000000..5047283 --- /dev/null +++ b/Editor/Settings/SettingsPresetReceiver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d8373e0cb30c4894db7cd4d0b77a7a48 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/SettingsUtil.cs b/Editor/SettingsUtil.cs index cda0a48..b98e0a4 100644 --- a/Editor/SettingsUtil.cs +++ b/Editor/SettingsUtil.cs @@ -2,17 +2,16 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; -using System.Threading.Tasks; using UnityEditor; using UnityEditorInternal; using UnityEngine; + namespace HybridCLR.Editor { public static class SettingsUtil { - public static bool Enable => GlobalSettings.enable; + public static bool Enable => HybridCLRGlobalSettings.Instance.enable; public static string PackageName { get; } = "com.focus-creative-games.hybridclr_unity"; @@ -64,7 +63,7 @@ namespace HybridCLR.Editor { get { - var gs = GlobalSettings; + var gs = HybridCLRGlobalSettings.Instance; var hotfixAssNames = (gs.hotUpdateAssemblyDefinitions ?? Array.Empty()).Select(ad => JsonUtility.FromJson(ad.text)); var hotfixAssembles = new List(); @@ -76,35 +75,8 @@ namespace HybridCLR.Editor return hotfixAssembles.ToList(); } } - public static List HotUpdateAssemblyFiles => HotUpdateAssemblyNames.Select(dll => dll + ".dll").ToList(); - public static T GetSingletonAssets() where T : ScriptableObject, new() - { - string assetType = typeof(T).Name; - string[] globalAssetPaths = AssetDatabase.FindAssets($"t:{assetType}"); - if (globalAssetPaths == null || globalAssetPaths.Length == 0) - { - string defaultNewAssetPath = $"Assets/{typeof(T).Name}.asset"; - Debug.LogWarning($"没找到 {assetType} asset,自动创建创建一个:{defaultNewAssetPath}."); - - var newAsset = ScriptableObject.CreateInstance(); - AssetDatabase.CreateAsset(newAsset, defaultNewAssetPath); - return newAsset; - } - if (globalAssetPaths.Length > 1) - { - foreach (var assetPath in globalAssetPaths) - { - Debug.LogError($"不能有多个 {assetType}. 路径: {AssetDatabase.GUIDToAssetPath(assetPath)}"); - } - throw new Exception($"不能有多个 {assetType}"); - } - string assPath = AssetDatabase.GUIDToAssetPath(globalAssetPaths[0]); - //Debug.Log($"find asset:{assPath}"); - return AssetDatabase.LoadAssetAtPath(assPath); - } - - public static HybridCLRGlobalSettings GlobalSettings => GetSingletonAssets(); + public static HybridCLRGlobalSettings GlobalSettings => HybridCLRGlobalSettings.Instance; } }