[update] 对 Hybrid Global Settings 工作流稍作完善

1. 将 hybrid settings 转移到 ProjectSettings 文件夹
2. 将 hybrid Settings 绘制到 编辑器 ProjectSettings 面板
3. 添加 文档 、Presets、Reset 功能
4. 在 installer 添加 settings 入口
5. 在 菜单栏添加 setting 入口,关于我们、以及文档入口
main
边上海 2022-10-09 03:25:56 +08:00
parent 47da01204b
commit caa0281e18
15 changed files with 415 additions and 100 deletions

View File

@ -1,12 +1,7 @@
using HybridCLR.Editor.Link; using HybridCLR.Editor.Link;
using HybridCLR.Editor.Meta;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using UnityEditor; using UnityEditor;
using UnityEngine; 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}"); Debug.Log($"[LinkGeneratorCommand] hotfix assembly count:{hotfixAssembles.Count}, ref type count:{refTypes.Count} output:{Application.dataPath}/{ls.outputLinkFile}");
var linkXmlWriter = new LinkXmlWriter(); var linkXmlWriter = new LinkXmlWriter();
linkXmlWriter.Write($"{Application.dataPath}/{ls.outputLinkFile}", refTypes); linkXmlWriter.Write($"{Application.dataPath}/{ls.outputLinkFile}", refTypes);
AssetDatabase.Refresh();
} }
} }
} }

View File

@ -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;
}

View File

@ -1,10 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Reflection;
using System.IO; using UnityEditor;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEditor ;
using UnityEngine; using UnityEngine;
@ -21,6 +17,19 @@ namespace HybridCLR.Editor.Installer
private void OnGUI() 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); string minCompatibleVersion = m_Controller.GetMinCompatibleVersion(m_Controller.Il2CppBranch);
GUI.enabled = true; GUI.enabled = true;
GUILayout.Space(10f); GUILayout.Space(10f);
@ -32,6 +41,7 @@ namespace HybridCLR.Editor.Installer
+ $"由于安装HybridCLR时需要从il2cpp_plus兼容版本{m_Controller.Il2CppBranch}而不是你项目版本拷贝il2cpp目录\n" + $"由于安装HybridCLR时需要从il2cpp_plus兼容版本{m_Controller.Il2CppBranch}而不是你项目版本拷贝il2cpp目录\n"
+ $"你必须同时安装兼容版本 {m_Controller.Il2CppBranch} 才能完成安装", EditorStyles.wordWrappedLabel); + $"你必须同时安装兼容版本 {m_Controller.Il2CppBranch} 才能完成安装", EditorStyles.wordWrappedLabel);
EditorGUILayout.LabelField("=============================================="); EditorGUILayout.LabelField("==============================================");
GUILayout.Space(10f); GUILayout.Space(10f);
EditorGUILayout.BeginVertical("box"); EditorGUILayout.BeginVertical("box");
@ -79,29 +89,29 @@ namespace HybridCLR.Editor.Installer
switch (err) switch (err)
{ {
case InstallErrorCode.Ok: case InstallErrorCode.Ok:
{ {
break; break;
} }
case InstallErrorCode.Il2CppInstallPathNotExists: case InstallErrorCode.Il2CppInstallPathNotExists:
{ {
EditorGUILayout.HelpBox("li2cpp 路径不存在", MessageType.Error); EditorGUILayout.HelpBox("li2cpp 路径不存在", MessageType.Error);
break; break;
} }
case InstallErrorCode.InvalidUnityInstallPath: case InstallErrorCode.InvalidUnityInstallPath:
{ {
EditorGUILayout.HelpBox($"Unity安装目录必须包含版本号否则无法识别版本", MessageType.Error); EditorGUILayout.HelpBox($"Unity安装目录必须包含版本号否则无法识别版本", MessageType.Error);
break; break;
} }
case InstallErrorCode.Il2CppInstallPathNotMatchIl2CppBranch: case InstallErrorCode.Il2CppInstallPathNotMatchIl2CppBranch:
{ {
EditorGUILayout.HelpBox($"il2cpp 版本不兼容,最小版本为 {m_Controller.GetMinCompatibleVersion(m_Controller.Il2CppBranch)}", MessageType.Error); EditorGUILayout.HelpBox($"il2cpp 版本不兼容,最小版本为 {m_Controller.GetMinCompatibleVersion(m_Controller.Il2CppBranch)}", MessageType.Error);
break; break;
} }
case InstallErrorCode.NotIl2CppPath: case InstallErrorCode.NotIl2CppPath:
{ {
EditorGUILayout.HelpBox($"当前选择的路径不是il2cpp目录必须类似 xxx/il2cpp", MessageType.Error); EditorGUILayout.HelpBox($"当前选择的路径不是il2cpp目录必须类似 xxx/il2cpp", MessageType.Error);
break; break;
} }
default: throw new Exception($"not support {err}"); default: throw new Exception($"not support {err}");
} }
} }

8
Editor/Settings.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3708ee1d4035cb14abaa4d64a8ec8148
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,35 @@
using UnityEditorInternal;
using UnityEngine;
namespace HybridCLR.Editor
{
[FilePath("ProjectSettings/HybridCLRGlobalSettings.asset")]
public class HybridCLRGlobalSettings : ScriptableSingleton<HybridCLRGlobalSettings>
{
[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;
}
}

View File

@ -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<SettingsPresetReceiver>();
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<HybridCLRGlobalSettings>();
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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d2bd1694fedc8b54c88bb9f6c67907d3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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/");
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a475a5f281298b84da32373694704c68
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,94 @@
using System;
using System.IO;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
namespace HybridCLR.Editor
{
public class ScriptableSingleton<T> : 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<T>();
}
else
{
Debug.LogError($"{nameof(ScriptableSingleton<T>)}: 请指定单例存档路径! ");
}
}
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;
/// <summary>
/// 单例存放路径
/// </summary>
/// <param name="path">相对 Project 路径</param>
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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 851c44a8da67a9742a7ea68815383f27
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d8373e0cb30c4894db7cd4d0b77a7a48
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -2,17 +2,16 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEditor; using UnityEditor;
using UnityEditorInternal; using UnityEditorInternal;
using UnityEngine; using UnityEngine;
namespace HybridCLR.Editor namespace HybridCLR.Editor
{ {
public static class SettingsUtil 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"; public static string PackageName { get; } = "com.focus-creative-games.hybridclr_unity";
@ -64,7 +63,7 @@ namespace HybridCLR.Editor
{ {
get get
{ {
var gs = GlobalSettings; var gs = HybridCLRGlobalSettings.Instance;
var hotfixAssNames = (gs.hotUpdateAssemblyDefinitions ?? Array.Empty<AssemblyDefinitionAsset>()).Select(ad => JsonUtility.FromJson<AssemblyDefinitionData>(ad.text)); var hotfixAssNames = (gs.hotUpdateAssemblyDefinitions ?? Array.Empty<AssemblyDefinitionAsset>()).Select(ad => JsonUtility.FromJson<AssemblyDefinitionData>(ad.text));
var hotfixAssembles = new List<string>(); var hotfixAssembles = new List<string>();
@ -76,35 +75,8 @@ namespace HybridCLR.Editor
return hotfixAssembles.ToList(); return hotfixAssembles.ToList();
} }
} }
public static List<string> HotUpdateAssemblyFiles => HotUpdateAssemblyNames.Select(dll => dll + ".dll").ToList(); public static List<string> HotUpdateAssemblyFiles => HotUpdateAssemblyNames.Select(dll => dll + ".dll").ToList();
public static T GetSingletonAssets<T>() where T : ScriptableObject, new() public static HybridCLRGlobalSettings GlobalSettings => HybridCLRGlobalSettings.Instance;
{
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<T>();
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<T>(assPath);
}
public static HybridCLRGlobalSettings GlobalSettings => GetSingletonAssets<HybridCLRGlobalSettings>();
} }
} }