diff --git a/Editor/AlicizaX.Framework.Editor.asmdef b/Editor/AlicizaX.Framework.Editor.asmdef
index 1a0c8d0..c1109f3 100644
--- a/Editor/AlicizaX.Framework.Editor.asmdef
+++ b/Editor/AlicizaX.Framework.Editor.asmdef
@@ -2,11 +2,9 @@
"name": "AlicizaX.Framework.Editor",
"rootNamespace": "AlicizaX.Framework.Editor",
"references": [
- "GUID:acfef7cabed3b0a42b25edb1cd4fa259",
"GUID:1619e00706139ce488ff80c0daeea8e7",
"GUID:e34a5702dd353724aa315fb8011f08c3",
- "GUID:4d1926c9df5b052469a1c63448b7609a",
- "GUID:75b6f2078d190f14dbda4a5b747d709c"
+ "GUID:4d1926c9df5b052469a1c63448b7609a"
],
"includePlatforms": [
"Editor"
diff --git a/Editor/Inspector.meta b/Editor/Inspector.meta
new file mode 100644
index 0000000..6b953a8
--- /dev/null
+++ b/Editor/Inspector.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 6a6566b690eb46c3acc8685a8ccabfeb
+timeCreated: 1737362727
\ No newline at end of file
diff --git a/Editor/Inspector/GameFrameworkInspector.cs b/Editor/Inspector/GameFrameworkInspector.cs
new file mode 100644
index 0000000..8c68f1c
--- /dev/null
+++ b/Editor/Inspector/GameFrameworkInspector.cs
@@ -0,0 +1,58 @@
+using UnityEditor;
+
+namespace AlicizaX.Editor
+{
+ ///
+ /// 游戏框架 Inspector 抽象类。
+ ///
+ public abstract class GameFrameworkInspector : UnityEditor.Editor
+ {
+ protected const string NoneOptionName = "";
+ private bool m_IsCompiling = false;
+
+ ///
+ /// 绘制事件。
+ ///
+ public override void OnInspectorGUI()
+ {
+ if (m_IsCompiling && !EditorApplication.isCompiling)
+ {
+ m_IsCompiling = false;
+ OnCompileComplete();
+ }
+ else if (!m_IsCompiling && EditorApplication.isCompiling)
+ {
+ m_IsCompiling = true;
+ OnCompileStart();
+ }
+ }
+
+ ///
+ /// 编译开始事件。
+ ///
+ protected virtual void OnCompileStart()
+ {
+ }
+
+ ///
+ /// 编译完成事件。
+ ///
+ protected virtual void OnCompileComplete()
+ {
+ }
+
+ protected bool IsPrefabInHierarchy(UnityEngine.Object obj)
+ {
+ if (obj == null)
+ {
+ return false;
+ }
+
+#if UNITY_2018_3_OR_NEWER
+ return PrefabUtility.GetPrefabAssetType(obj) != PrefabAssetType.Regular;
+#else
+ return PrefabUtility.GetPrefabType(obj) != PrefabType.Prefab;
+#endif
+ }
+ }
+}
\ No newline at end of file
diff --git a/Editor/Inspector/GameFrameworkInspector.cs.meta b/Editor/Inspector/GameFrameworkInspector.cs.meta
new file mode 100644
index 0000000..5052ceb
--- /dev/null
+++ b/Editor/Inspector/GameFrameworkInspector.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b5bb373d9bcd4bd45861670fa5208e05
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Inspector/InspectorEditor.cs b/Editor/Inspector/InspectorEditor.cs
new file mode 100644
index 0000000..e366d03
--- /dev/null
+++ b/Editor/Inspector/InspectorEditor.cs
@@ -0,0 +1,27 @@
+using System.Text.RegularExpressions;
+using UnityEngine;
+using UnityEditor;
+
+
+namespace AlicizaX.Editor
+{
+ public class InspectorEditor : UnityEditor.Editor where T : Object
+ {
+ public T Target { get; private set; }
+ public PropertyCollection Properties { get; private set; }
+
+ public virtual void OnEnable()
+ {
+ Target = target as T;
+ Properties = EditorDrawing.GetAllProperties(serializedObject);
+ }
+
+ public override void OnInspectorGUI()
+ {
+ string name = Target.GetType().Name;
+ string spacedName = Regex.Replace(name, "(\\B[A-Z])", " $1");
+ EditorDrawing.DrawInspectorHeader(new GUIContent(spacedName), Target);
+ EditorGUILayout.Space();
+ }
+ }
+}
diff --git a/Editor/Inspector/InspectorEditor.cs.meta b/Editor/Inspector/InspectorEditor.cs.meta
new file mode 100644
index 0000000..52a1561
--- /dev/null
+++ b/Editor/Inspector/InspectorEditor.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: ecc870911d5f4da592d757183a315b49
+timeCreated: 1758265131
\ No newline at end of file
diff --git a/Editor/Inspector/MemoryPoolComponentInspector.cs b/Editor/Inspector/MemoryPoolComponentInspector.cs
new file mode 100644
index 0000000..e172f8e
--- /dev/null
+++ b/Editor/Inspector/MemoryPoolComponentInspector.cs
@@ -0,0 +1,150 @@
+using AlicizaX;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using UnityEditor;
+using UnityEngine;
+
+namespace AlicizaX.Editor
+{
+ [CustomEditor(typeof(MemoryPoolSetting))]
+ internal sealed class MemoryPoolComponentInspector : GameFrameworkInspector
+ {
+ private readonly Dictionary> m_ReferencePoolInfos = new Dictionary>(StringComparer.Ordinal);
+ private readonly HashSet m_OpenedItems = new HashSet();
+
+ private SerializedProperty m_EnableStrictCheck = null;
+
+ private bool m_ShowFullClassName = false;
+
+ public override void OnInspectorGUI()
+ {
+ base.OnInspectorGUI();
+
+ serializedObject.Update();
+
+ MemoryPoolSetting t = (MemoryPoolSetting)target;
+
+ if (EditorApplication.isPlaying && IsPrefabInHierarchy(t.gameObject))
+ {
+ bool enableStrictCheck = EditorGUILayout.Toggle("Enable Strict Check", t.EnableStrictCheck);
+ if (enableStrictCheck != t.EnableStrictCheck)
+ {
+ t.EnableStrictCheck = enableStrictCheck;
+ }
+
+ EditorGUILayout.LabelField("Reference Pool Count", MemoryPool.Count.ToString());
+ m_ShowFullClassName = EditorGUILayout.Toggle("Show Full Class Name", m_ShowFullClassName);
+ m_ReferencePoolInfos.Clear();
+ MemoryPoolInfo[] referencePoolInfos = MemoryPool.GetAllMemoryPoolInfos();
+ foreach (MemoryPoolInfo referencePoolInfo in referencePoolInfos)
+ {
+ string assemblyName = referencePoolInfo.Type.Assembly.GetName().Name;
+ List results = null;
+ if (!m_ReferencePoolInfos.TryGetValue(assemblyName, out results))
+ {
+ results = new List();
+ m_ReferencePoolInfos.Add(assemblyName, results);
+ }
+
+ results.Add(referencePoolInfo);
+ }
+
+ foreach (KeyValuePair> assemblyReferencePoolInfo in m_ReferencePoolInfos)
+ {
+ bool lastState = m_OpenedItems.Contains(assemblyReferencePoolInfo.Key);
+ bool currentState = EditorGUILayout.Foldout(lastState, assemblyReferencePoolInfo.Key);
+ if (currentState != lastState)
+ {
+ if (currentState)
+ {
+ m_OpenedItems.Add(assemblyReferencePoolInfo.Key);
+ }
+ else
+ {
+ m_OpenedItems.Remove(assemblyReferencePoolInfo.Key);
+ }
+ }
+
+ if (currentState)
+ {
+ EditorGUILayout.BeginVertical("box");
+ {
+ var label = "Unused\tUsing.\tAcquire\tRelease\tAdd\tRemove";
+ EditorGUILayout.LabelField(m_ShowFullClassName ? "Full Class Name" : "Class Name", label);
+ assemblyReferencePoolInfo.Value.Sort(Comparison);
+ foreach (MemoryPoolInfo referencePoolInfo in assemblyReferencePoolInfo.Value)
+ {
+ DrawReferencePoolInfo(referencePoolInfo);
+ }
+
+ if (GUILayout.Button("Export CSV Data"))
+ {
+ string exportFileName = EditorUtility.SaveFilePanel("Export CSV Data", string.Empty, Utility.Text.Format("Reference Pool Data - {0}.csv", assemblyReferencePoolInfo.Key), string.Empty);
+ if (!string.IsNullOrEmpty(exportFileName))
+ {
+ try
+ {
+ int index = 0;
+ string[] data = new string[assemblyReferencePoolInfo.Value.Count + 1];
+ data[index++] = "Class Name,Full Class Name,Unused,Using,Acquire,Release,Add,Remove";
+ foreach (MemoryPoolInfo referencePoolInfo in assemblyReferencePoolInfo.Value)
+ {
+ data[index++] = Utility.Text.Format("{0},{1},{2},{3},{4},{5},{6},{7}", referencePoolInfo.Type.Name, referencePoolInfo.Type.FullName, referencePoolInfo.UnusedMemoryCount, referencePoolInfo.UsingMemoryCount, referencePoolInfo.AcquireMemoryCount, referencePoolInfo.ReleaseMemoryCount, referencePoolInfo.AddMemoryCount, referencePoolInfo.RemoveMemoryCount);
+ }
+
+ File.WriteAllLines(exportFileName, data, Encoding.UTF8);
+ Debug.Log(Utility.Text.Format("Export reference pool CSV data to '{0}' success.", exportFileName));
+ }
+ catch (Exception exception)
+ {
+ Debug.LogError(Utility.Text.Format("Export reference pool CSV data to '{0}' failure, exception is '{1}'.", exportFileName, exception));
+ }
+ }
+ }
+ }
+ EditorGUILayout.EndVertical();
+
+ EditorGUILayout.Separator();
+ }
+ }
+ }
+ else
+ {
+ EditorGUILayout.PropertyField(m_EnableStrictCheck);
+ }
+
+ serializedObject.ApplyModifiedProperties();
+
+ Repaint();
+ }
+
+ private void OnEnable()
+ {
+ m_EnableStrictCheck = serializedObject.FindProperty("m_EnableStrictCheck");
+ }
+
+ private void DrawReferencePoolInfo(MemoryPoolInfo referencePoolInfo)
+ {
+#if UNITY_6000_0_OR_NEWER
+ EditorGUILayout.LabelField(m_ShowFullClassName ? referencePoolInfo.Type.FullName : referencePoolInfo.Type.Name, Utility.Text.Format("{0,-12}\t{1,-12}\t{2,-12}\t{3,-12}\t{4}\t{5}", referencePoolInfo.UnusedMemoryCount, referencePoolInfo.UsingMemoryCount, referencePoolInfo.AcquireMemoryCount, referencePoolInfo.ReleaseMemoryCount, referencePoolInfo.AddMemoryCount, referencePoolInfo.RemoveMemoryCount));
+#else
+ EditorGUILayout.LabelField(m_ShowFullClassName ? referencePoolInfo.Type.FullName :
+ referencePoolInfo.Type.Name, Utility.Text.Format("{0}\t{1}\t{2}\t{3}\t{4}\t{5}",referencePoolInfo.UnusedMemoryCount, referencePoolInfo.UsingMemoryCount, referencePoolInfo.AcquireMemoryCount, referencePoolInfo.ReleaseMemoryCount, referencePoolInfo.AddMemoryCount, referencePoolInfo.RemoveMemoryCount));
+#endif
+ }
+
+ private int Comparison(MemoryPoolInfo a, MemoryPoolInfo b)
+ {
+ if (m_ShowFullClassName)
+ {
+ return a.Type.FullName.CompareTo(b.Type.FullName);
+ }
+ else
+ {
+ return a.Type.Name.CompareTo(b.Type.Name);
+ }
+ }
+ }
+}
diff --git a/Editor/Inspector/MemoryPoolComponentInspector.cs.meta b/Editor/Inspector/MemoryPoolComponentInspector.cs.meta
new file mode 100644
index 0000000..defecb2
--- /dev/null
+++ b/Editor/Inspector/MemoryPoolComponentInspector.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 705a7441224da3d4cbc6593bdc58f167
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Inspector/RootModuleInspector.cs b/Editor/Inspector/RootModuleInspector.cs
new file mode 100644
index 0000000..831b0e4
--- /dev/null
+++ b/Editor/Inspector/RootModuleInspector.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Collections.Generic;
+using AlicizaX;
+using UnityEditor;
+using UnityEngine;
+
+namespace AlicizaX.Editor
+{
+ [CustomEditor(typeof(RootModule))]
+ internal sealed class RootModuleInspector : GameFrameworkInspector
+ {
+ private static readonly float[] GameSpeed = new float[] { 0f, 0.01f, 0.1f, 0.25f, 0.5f, 1f, 1.5f, 2f, 4f, 8f };
+ private static readonly string[] GameSpeedForDisplay = new string[] { "0x", "0.01x", "0.1x", "0.25x", "0.5x", "1x", "1.5x", "2x", "4x", "8x" };
+
+ private SerializedProperty _frameRate = null;
+ private SerializedProperty _gameSpeed = null;
+ private SerializedProperty _runInBackground = null;
+ private SerializedProperty _neverSleep = null;
+
+ public override void OnInspectorGUI()
+ {
+ base.OnInspectorGUI();
+
+ serializedObject.Update();
+
+ RootModule t = (RootModule)target;
+
+
+ EditorGUILayout.BeginVertical("box");
+ {
+ int frameRate = EditorGUILayout.IntSlider("Frame Rate", _frameRate.intValue, 1, 120);
+ if (frameRate != _frameRate.intValue)
+ {
+ if (EditorApplication.isPlaying)
+ {
+ t.FrameRate = frameRate;
+ }
+ else
+ {
+ _frameRate.intValue = frameRate;
+ }
+ }
+ }
+ EditorGUILayout.EndVertical();
+
+
+ EditorGUILayout.BeginVertical("box");
+ {
+ float gameSpeed = EditorGUILayout.Slider("Game Speed", _gameSpeed.floatValue, 0f, 8f);
+ int selectedGameSpeed = GUILayout.SelectionGrid(GetSelectedGameSpeed(gameSpeed), GameSpeedForDisplay, 5);
+ if (selectedGameSpeed >= 0)
+ {
+ gameSpeed = GetGameSpeed(selectedGameSpeed);
+ }
+
+ if (Math.Abs(gameSpeed - _gameSpeed.floatValue) > 0.01f)
+ {
+ if (EditorApplication.isPlaying)
+ {
+ t.GameSpeed = gameSpeed;
+ }
+ else
+ {
+ _gameSpeed.floatValue = gameSpeed;
+ }
+ }
+ }
+ EditorGUILayout.EndVertical();
+
+ bool runInBackground = EditorGUILayout.Toggle("Run in Background", _runInBackground.boolValue);
+ if (runInBackground != _runInBackground.boolValue)
+ {
+ if (EditorApplication.isPlaying)
+ {
+ t.RunInBackground = runInBackground;
+ }
+ else
+ {
+ _runInBackground.boolValue = runInBackground;
+ }
+ }
+
+ bool neverSleep = EditorGUILayout.Toggle("Never Sleep", _neverSleep.boolValue);
+ if (neverSleep != _neverSleep.boolValue)
+ {
+ if (EditorApplication.isPlaying)
+ {
+ t.NeverSleep = neverSleep;
+ }
+ else
+ {
+ _neverSleep.boolValue = neverSleep;
+ }
+ }
+
+ serializedObject.ApplyModifiedProperties();
+ }
+
+ private void OnEnable()
+ {
+ _frameRate = serializedObject.FindProperty("frameRate");
+ _gameSpeed = serializedObject.FindProperty("gameSpeed");
+ _runInBackground = serializedObject.FindProperty("runInBackground");
+ _neverSleep = serializedObject.FindProperty("neverSleep");
+ }
+
+ private float GetGameSpeed(int selectedGameSpeed)
+ {
+ if (selectedGameSpeed < 0)
+ {
+ return GameSpeed[0];
+ }
+
+ if (selectedGameSpeed >= GameSpeed.Length)
+ {
+ return GameSpeed[GameSpeed.Length - 1];
+ }
+
+ return GameSpeed[selectedGameSpeed];
+ }
+
+ private int GetSelectedGameSpeed(float gameSpeed)
+ {
+ for (int i = 0; i < GameSpeed.Length; i++)
+ {
+ if (Mathf.Approximately(gameSpeed, GameSpeed[i]))
+ {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+ }
+}
diff --git a/Editor/Inspector/RootModuleInspector.cs.meta b/Editor/Inspector/RootModuleInspector.cs.meta
new file mode 100644
index 0000000..4d4704f
--- /dev/null
+++ b/Editor/Inspector/RootModuleInspector.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 55195676d06c1cd418e6f3201eae7176
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Localization/LocalizationComponentInspector.cs b/Editor/Localization/LocalizationComponentInspector.cs
new file mode 100644
index 0000000..b65182c
--- /dev/null
+++ b/Editor/Localization/LocalizationComponentInspector.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Linq;
+using AlicizaX.Editor;
+using AlicizaX.Localization.Runtime;
+using UnityEditor;
+
+namespace AlicizaX.Localization.Editor
+{
+ [CustomEditor(typeof(LocalizationComponent))]
+ internal sealed class LocalizationComponentInspector : GameFrameworkInspector
+ {
+ private string[] _languageNames = new[] { "None" };
+ private SerializedProperty _language;
+ private int languageIndex = -1;
+
+ private void OnEnable()
+ {
+ _language = serializedObject.FindProperty("_language");
+ _languageNames = LocalizationConfiguration.Instance.LanguageTypeNames.ToArray();
+ var languageName = EditorPrefs.GetString(LocalizationComponent.PrefsKey, "None");
+ _language.stringValue = languageName;
+ serializedObject.ApplyModifiedProperties();
+ languageIndex = _languageNames.ToList().FindIndex(t => t.Equals(languageName));
+ }
+
+ public override void OnInspectorGUI()
+ {
+ base.OnInspectorGUI();
+ serializedObject.Update();
+ LocalizationComponent t = (LocalizationComponent)target;
+ EditorGUI.BeginDisabledGroup(EditorApplication.isPlayingOrWillChangePlaymode);
+ {
+ if (EditorApplication.isPlaying && IsPrefabInHierarchy(t.gameObject))
+ {
+ int index = UnityEditor.EditorPrefs.GetInt(LocalizationComponent.PrefsKey, 0);
+ int selectedIndex = EditorGUILayout.Popup("Language", index, _languageNames);
+ }
+ else
+ {
+ int selectedIndex = EditorGUILayout.Popup("Play Mode", languageIndex, _languageNames);
+ if (selectedIndex != languageIndex)
+ {
+ languageIndex = selectedIndex;
+ _language.stringValue = _languageNames[languageIndex];
+ EditorPrefs.SetString(LocalizationComponent.PrefsKey, _language.stringValue);
+ }
+ }
+ }
+ serializedObject.ApplyModifiedProperties();
+ }
+ }
+}
diff --git a/Editor/Localization/LocalizationComponentInspector.cs.meta b/Editor/Localization/LocalizationComponentInspector.cs.meta
new file mode 100644
index 0000000..4bbb56a
--- /dev/null
+++ b/Editor/Localization/LocalizationComponentInspector.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 93a2512d8bf54c0a8032fe6f4a7eb52b
+timeCreated: 1760164418
\ No newline at end of file
diff --git a/Editor/Misc.meta b/Editor/Misc.meta
new file mode 100644
index 0000000..4e4a533
--- /dev/null
+++ b/Editor/Misc.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: c6d5d730968b49849be897d89a8b9049
+timeCreated: 1736410513
\ No newline at end of file
diff --git a/Editor/Misc/EventScriptingDefineSymbols.cs b/Editor/Misc/EventScriptingDefineSymbols.cs
new file mode 100644
index 0000000..a8cb412
--- /dev/null
+++ b/Editor/Misc/EventScriptingDefineSymbols.cs
@@ -0,0 +1,46 @@
+using AlicizaX.Editor;
+using UnityEditor;
+using UnityEngine;
+
+namespace AlicizaX.Editor
+{
+ internal static class EventScriptingDefineSymbols
+ {
+ private const string MenuPath = "Tools/AlicizaX/Event/Strict Check";
+ private const string DefineSymbol = "Event_StrictCheck";
+
+ [MenuItem(MenuPath)]
+ private static void ToggleStrictCheck()
+ {
+ bool enabled = IsEnabled();
+ SetEnabled(!enabled);
+ }
+
+ [MenuItem(MenuPath, true)]
+ private static bool ToggleStrictCheckValidate()
+ {
+ Menu.SetChecked(MenuPath, IsEnabled());
+ return true;
+ }
+
+ private static bool IsEnabled()
+ {
+ return ScriptingDefineSymbols.HasScriptingDefineSymbol(EditorUserBuildSettings.selectedBuildTargetGroup, DefineSymbol);
+ }
+
+ private static void SetEnabled(bool enabled)
+ {
+ var targetGroup = EditorUserBuildSettings.selectedBuildTargetGroup;
+ if (enabled)
+ {
+ ScriptingDefineSymbols.AddScriptingDefineSymbol(targetGroup, DefineSymbol);
+ }
+ else
+ {
+ ScriptingDefineSymbols.RemoveScriptingDefineSymbol(targetGroup, DefineSymbol);
+ }
+
+ Debug.Log($"[EventKit] Strict Check {(enabled ? "Enabled" : "Disabled")}");
+ }
+ }
+}
diff --git a/Editor/Misc/EventScriptingDefineSymbols.cs.meta b/Editor/Misc/EventScriptingDefineSymbols.cs.meta
new file mode 100644
index 0000000..1ed3286
--- /dev/null
+++ b/Editor/Misc/EventScriptingDefineSymbols.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: a0f666b24d9c41c4a355f9a9a643928b
+timeCreated: 1756782190
\ No newline at end of file
diff --git a/Editor/Misc/LogScriptingDefineSymbols.cs b/Editor/Misc/LogScriptingDefineSymbols.cs
new file mode 100644
index 0000000..57f5687
--- /dev/null
+++ b/Editor/Misc/LogScriptingDefineSymbols.cs
@@ -0,0 +1,172 @@
+using UnityEditor;
+
+namespace AlicizaX.Editor
+{
+ ///
+ /// 日志脚本宏定义。
+ ///
+ public static class LogScriptingDefineSymbols
+ {
+ private const string EnableLogScriptingDefineSymbol = "ENABLE_LOG";
+ private const string EnableDebugAndAboveLogScriptingDefineSymbol = "ENABLE_DEBUG_AND_ABOVE_LOG";
+ private const string EnableInfoAndAboveLogScriptingDefineSymbol = "ENABLE_INFO_AND_ABOVE_LOG";
+ private const string EnableWarningAndAboveLogScriptingDefineSymbol = "ENABLE_WARNING_AND_ABOVE_LOG";
+ private const string EnableErrorAndAboveLogScriptingDefineSymbol = "ENABLE_ERROR_AND_ABOVE_LOG";
+ private const string EnableFatalAndAboveLogScriptingDefineSymbol = "ENABLE_FATAL_AND_ABOVE_LOG";
+ private const string EnableDebugLogScriptingDefineSymbol = "ENABLE_DEBUG_LOG";
+ private const string EnableInfoLogScriptingDefineSymbol = "ENABLE_INFO_LOG";
+ private const string EnableWarningLogScriptingDefineSymbol = "ENABLE_WARNING_LOG";
+ private const string EnableErrorLogScriptingDefineSymbol = "ENABLE_ERROR_LOG";
+ private const string EnableFatalLogScriptingDefineSymbol = "ENABLE_FATAL_LOG";
+
+ private static readonly string[] AboveLogScriptingDefineSymbols = new string[]
+ {
+ EnableDebugAndAboveLogScriptingDefineSymbol,
+ EnableInfoAndAboveLogScriptingDefineSymbol,
+ EnableWarningAndAboveLogScriptingDefineSymbol,
+ EnableErrorAndAboveLogScriptingDefineSymbol,
+ EnableFatalAndAboveLogScriptingDefineSymbol
+ };
+
+ private static readonly string[] SpecifyLogScriptingDefineSymbols = new string[]
+ {
+ EnableDebugLogScriptingDefineSymbol,
+ EnableInfoLogScriptingDefineSymbol,
+ EnableWarningLogScriptingDefineSymbol,
+ EnableErrorLogScriptingDefineSymbol,
+ EnableFatalLogScriptingDefineSymbol
+ };
+
+ ///
+ /// 禁用所有日志脚本宏定义。
+ ///
+ [MenuItem("Tools/AlicizaX/Scripting Define Symbols/Disable All Logs", false, 30)]
+ public static void DisableAllLogs()
+ {
+ ScriptingDefineSymbols.RemoveScriptingDefineSymbol(EnableLogScriptingDefineSymbol);
+
+ foreach (string specifyLogScriptingDefineSymbol in SpecifyLogScriptingDefineSymbols)
+ {
+ ScriptingDefineSymbols.RemoveScriptingDefineSymbol(specifyLogScriptingDefineSymbol);
+ }
+
+ foreach (string aboveLogScriptingDefineSymbol in AboveLogScriptingDefineSymbols)
+ {
+ ScriptingDefineSymbols.RemoveScriptingDefineSymbol(aboveLogScriptingDefineSymbol);
+ }
+ }
+
+ ///
+ /// 开启所有日志脚本宏定义。
+ ///
+ [MenuItem("Tools/AlicizaX/Scripting Define Symbols/Enable All Logs", false, 31)]
+ public static void EnableAllLogs()
+ {
+ DisableAllLogs();
+ ScriptingDefineSymbols.AddScriptingDefineSymbol(EnableLogScriptingDefineSymbol);
+ }
+
+ ///
+ /// 开启调试及以上级别的日志脚本宏定义。
+ ///
+ [MenuItem("Tools/AlicizaX/Scripting Define Symbols/Enable Debug And Above Logs", false, 32)]
+ public static void EnableDebugAndAboveLogs()
+ {
+ SetAboveLogScriptingDefineSymbol(EnableDebugAndAboveLogScriptingDefineSymbol);
+ }
+
+ ///
+ /// 开启信息及以上级别的日志脚本宏定义。
+ ///
+ [MenuItem("Tools/AlicizaX/Scripting Define Symbols/Enable Info And Above Logs", false, 33)]
+ public static void EnableInfoAndAboveLogs()
+ {
+ SetAboveLogScriptingDefineSymbol(EnableInfoAndAboveLogScriptingDefineSymbol);
+ }
+
+ ///
+ /// 开启警告及以上级别的日志脚本宏定义。
+ ///
+ [MenuItem("Tools/AlicizaX/Scripting Define Symbols/Enable Warning And Above Logs", false, 34)]
+ public static void EnableWarningAndAboveLogs()
+ {
+ SetAboveLogScriptingDefineSymbol(EnableWarningAndAboveLogScriptingDefineSymbol);
+ }
+
+ ///
+ /// 开启错误及以上级别的日志脚本宏定义。
+ ///
+ [MenuItem("Tools/AlicizaX/Scripting Define Symbols/Enable Error And Above Logs", false, 35)]
+ public static void EnableErrorAndAboveLogs()
+ {
+ SetAboveLogScriptingDefineSymbol(EnableErrorAndAboveLogScriptingDefineSymbol);
+ }
+
+ ///
+ /// 开启严重错误及以上级别的日志脚本宏定义。
+ ///
+ [MenuItem("Tools/AlicizaX/Scripting Define Symbols/Enable Fatal And Above Logs", false, 36)]
+ public static void EnableFatalAndAboveLogs()
+ {
+ SetAboveLogScriptingDefineSymbol(EnableFatalAndAboveLogScriptingDefineSymbol);
+ }
+
+ ///
+ /// 设置日志脚本宏定义。
+ ///
+ /// 要设置的日志脚本宏定义。
+ public static void SetAboveLogScriptingDefineSymbol(string aboveLogScriptingDefineSymbol)
+ {
+ if (string.IsNullOrEmpty(aboveLogScriptingDefineSymbol))
+ {
+ return;
+ }
+
+ foreach (string i in AboveLogScriptingDefineSymbols)
+ {
+ if (i == aboveLogScriptingDefineSymbol)
+ {
+ DisableAllLogs();
+ ScriptingDefineSymbols.AddScriptingDefineSymbol(aboveLogScriptingDefineSymbol);
+ return;
+ }
+ }
+ }
+
+ ///
+ /// 设置日志脚本宏定义。
+ ///
+ /// 要设置的日志脚本宏定义。
+ public static void SetSpecifyLogScriptingDefineSymbols(string[] specifyLogScriptingDefineSymbols)
+ {
+ if (specifyLogScriptingDefineSymbols == null || specifyLogScriptingDefineSymbols.Length <= 0)
+ {
+ return;
+ }
+
+ bool removed = false;
+ foreach (string specifyLogScriptingDefineSymbol in specifyLogScriptingDefineSymbols)
+ {
+ if (string.IsNullOrEmpty(specifyLogScriptingDefineSymbol))
+ {
+ continue;
+ }
+
+ foreach (string i in SpecifyLogScriptingDefineSymbols)
+ {
+ if (i == specifyLogScriptingDefineSymbol)
+ {
+ if (!removed)
+ {
+ removed = true;
+ DisableAllLogs();
+ }
+
+ ScriptingDefineSymbols.AddScriptingDefineSymbol(specifyLogScriptingDefineSymbol);
+ break;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Editor/Misc/LogScriptingDefineSymbols.cs.meta b/Editor/Misc/LogScriptingDefineSymbols.cs.meta
new file mode 100644
index 0000000..46c0c6c
--- /dev/null
+++ b/Editor/Misc/LogScriptingDefineSymbols.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0b5eed5f56efa7e4cb245bee9b064c21
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Misc/OpenAssetLogLine.cs b/Editor/Misc/OpenAssetLogLine.cs
new file mode 100644
index 0000000..c1f7e62
--- /dev/null
+++ b/Editor/Misc/OpenAssetLogLine.cs
@@ -0,0 +1,108 @@
+using System;
+using UnityEditor;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using UnityEditor.Callbacks;
+using UnityEditorInternal;
+using UnityEngine;
+
+namespace AlicizaX.Editor
+{
+ public static class OpenAssetLogLine
+ {
+ [OnOpenAsset(0)]
+ private static bool OnOpenAsset(int instanceID, int line)
+ {
+ if (line <= 0)
+ {
+ return false;
+ }
+ // 获取资源路径
+ string assetPath = AssetDatabase.GetAssetPath(instanceID);
+
+ // 判断资源类型
+ if (!assetPath.EndsWith(".cs"))
+ {
+ return false;
+ }
+
+ bool autoFirstMatch = assetPath.Contains("Log.cs");
+
+ var stackTrace = GetStackTrace();
+ if (!string.IsNullOrEmpty(stackTrace) && stackTrace.Contains("Log.cs"))
+
+ {
+ if (!autoFirstMatch)
+ {
+ var fullPath = Application.dataPath.Substring(0, Application.dataPath.LastIndexOf("Assets", StringComparison.Ordinal));
+ fullPath = $"{fullPath}{assetPath}";
+ // 跳转到目标代码的特定行
+ InternalEditorUtility.OpenFileAtLineExternal(fullPath.Replace('/', '\\'), line);
+ return true;
+ }
+
+ // 使用正则表达式匹配at的哪个脚本的哪一行
+ var matches = Regex.Match(stackTrace, @"\(at (.+)\)",
+ RegexOptions.IgnoreCase);
+ while (matches.Success)
+ {
+ var pathLine = matches.Groups[1].Value;
+
+ if (!pathLine.Contains("Log.cs"))
+ {
+ var splitIndex = pathLine.LastIndexOf(":", StringComparison.Ordinal);
+ // 脚本路径
+ var path = pathLine.Substring(0, splitIndex);
+ // 行号
+ line = Convert.ToInt32(pathLine.Substring(splitIndex + 1));
+ var fullPath = Application.dataPath.Substring(0, Application.dataPath.LastIndexOf("Assets", StringComparison.Ordinal));
+ fullPath = $"{fullPath}{path}";
+ // 跳转到目标代码的特定行
+ InternalEditorUtility.OpenFileAtLineExternal(fullPath.Replace('/', '\\'), line);
+ break;
+ }
+
+ matches = matches.NextMatch();
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// 获取当前日志窗口选中的日志的堆栈信息。
+ ///
+ /// 选中日志的堆栈信息实例。
+ private static string GetStackTrace()
+ {
+ // 通过反射获取ConsoleWindow类
+ var consoleWindowType = typeof(EditorWindow).Assembly.GetType("UnityEditor.ConsoleWindow");
+ // 获取窗口实例
+ var fieldInfo = consoleWindowType.GetField("ms_ConsoleWindow",
+ BindingFlags.Static |
+ BindingFlags.NonPublic);
+ if (fieldInfo != null)
+ {
+ var consoleInstance = fieldInfo.GetValue(null);
+ if (consoleInstance != null)
+ if (EditorWindow.focusedWindow == (EditorWindow)consoleInstance)
+ {
+ // 获取m_ActiveText成员
+ fieldInfo = consoleWindowType.GetField("m_ActiveText",
+ BindingFlags.Instance |
+ BindingFlags.NonPublic);
+ // 获取m_ActiveText的值
+ if (fieldInfo != null)
+ {
+ var activeText = fieldInfo.GetValue(consoleInstance).ToString();
+ return activeText;
+ }
+ }
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/Editor/Misc/OpenAssetLogLine.cs.meta b/Editor/Misc/OpenAssetLogLine.cs.meta
new file mode 100644
index 0000000..4dd64f0
--- /dev/null
+++ b/Editor/Misc/OpenAssetLogLine.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: bb8c486fbd51ec64fab3749895432f7d
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Misc/OpenFolder.cs b/Editor/Misc/OpenFolder.cs
new file mode 100644
index 0000000..22ba4a9
--- /dev/null
+++ b/Editor/Misc/OpenFolder.cs
@@ -0,0 +1,84 @@
+using AlicizaX;
+using System.Diagnostics;
+using UnityEditor;
+using UnityEngine;
+
+namespace AlicizaX.Editor
+{
+ ///
+ /// 打开文件夹相关的实用函数。
+ ///
+ public static class OpenFolder
+ {
+ ///
+ /// 打开 Data Path 文件夹。
+ ///
+ [MenuItem("Tools/AlicizaX/Open Folder/Data Path", false, 10)]
+ public static void OpenFolderDataPath()
+ {
+ Execute(Application.dataPath);
+ }
+
+ ///
+ /// 打开 Persistent Data Path 文件夹。
+ ///
+ [MenuItem("Tools/AlicizaX/Open Folder/Persistent Data Path", false, 11)]
+ public static void OpenFolderPersistentDataPath()
+ {
+ Execute(Application.persistentDataPath);
+ }
+
+ ///
+ /// 打开 Streaming Assets Path 文件夹。
+ ///
+ [MenuItem("Tools/AlicizaX/Open Folder/Streaming Assets Path", false, 12)]
+ public static void OpenFolderStreamingAssetsPath()
+ {
+ Execute(Application.streamingAssetsPath);
+ }
+
+ ///
+ /// 打开 Temporary Cache Path 文件夹。
+ ///
+ [MenuItem("Tools/AlicizaX/Open Folder/Temporary Cache Path", false, 13)]
+ public static void OpenFolderTemporaryCachePath()
+ {
+ Execute(Application.temporaryCachePath);
+ }
+
+#if UNITY_2018_3_OR_NEWER
+
+ ///
+ /// 打开 Console Log Path 文件夹。
+ ///
+ [MenuItem("Tools/AlicizaX/Open Folder/Console Log Path", false, 14)]
+ public static void OpenFolderConsoleLogPath()
+ {
+ Execute(System.IO.Path.GetDirectoryName(Application.consoleLogPath));
+ }
+
+#endif
+
+ ///
+ /// 打开指定路径的文件夹。
+ ///
+ /// 要打开的文件夹的路径。
+ public static void Execute(string folder)
+ {
+ folder = Utility.Text.Format("\"{0}\"", folder);
+ switch (Application.platform)
+ {
+ case RuntimePlatform.WindowsEditor:
+ Process.Start("Explorer.exe", folder.Replace('/', '\\'));
+ break;
+
+ case RuntimePlatform.OSXEditor:
+ Process.Start("open", folder);
+ break;
+
+ default:
+ throw new GameFrameworkException(Utility.Text.Format("Not support open folder on '{0}' platform.", Application.platform));
+ }
+ }
+ }
+}
diff --git a/Editor/Misc/OpenFolder.cs.meta b/Editor/Misc/OpenFolder.cs.meta
new file mode 100644
index 0000000..e948d83
--- /dev/null
+++ b/Editor/Misc/OpenFolder.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 62e84f6e8237ea540b348d07a13891c7
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Misc/ScriptableSingleton.cs b/Editor/Misc/ScriptableSingleton.cs
new file mode 100644
index 0000000..0bf222c
--- /dev/null
+++ b/Editor/Misc/ScriptableSingleton.cs
@@ -0,0 +1,98 @@
+using System;
+using System.IO;
+using System.Linq;
+using UnityEditorInternal;
+using UnityEngine;
+
+namespace AlicizaX.Editor.Setting
+{
+ public class ScriptableSingleton : ScriptableObject where T : ScriptableObject
+ {
+ private static T s_Instance;
+
+ public static T Instance
+ {
+ get
+ {
+ if (!s_Instance)
+ {
+ LoadOrCreate();
+ }
+
+ return s_Instance;
+ }
+ }
+
+ public static T LoadOrCreate()
+ {
+ string filePath = GetFilePath();
+ if (!string.IsNullOrEmpty(filePath))
+ {
+ var arr = InternalEditorUtility.LoadSerializedFileAndForget(filePath);
+ s_Instance = arr.Length > 0 ? arr[0] as T : s_Instance ?? CreateInstance();
+ }
+ else
+ {
+ Debug.LogError($"save location of {nameof(ScriptableSingleton)} is invalid");
+ }
+
+ return s_Instance;
+ }
+
+ public static 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()
+ {
+ return typeof(T).GetCustomAttributes(inherit: true)
+ .Where(v => v is FilePathAttribute)
+ .Cast()
+ .FirstOrDefault()
+ ?.filepath;
+ }
+ }
+
+ [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;
+ }
+ }
+}
diff --git a/Editor/Misc/ScriptableSingleton.cs.meta b/Editor/Misc/ScriptableSingleton.cs.meta
new file mode 100644
index 0000000..6a3bddc
--- /dev/null
+++ b/Editor/Misc/ScriptableSingleton.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 9da8cbe84b4045d093d326334bbf4c7f
+timeCreated: 1739529382
\ No newline at end of file
diff --git a/Editor/Misc/ScriptingDefineSymbols.cs b/Editor/Misc/ScriptingDefineSymbols.cs
new file mode 100644
index 0000000..23caeba
--- /dev/null
+++ b/Editor/Misc/ScriptingDefineSymbols.cs
@@ -0,0 +1,159 @@
+using System.Collections.Generic;
+using UnityEditor;
+using UnityEditor.Build;
+
+namespace AlicizaX.Editor
+{
+ ///
+ /// 脚本宏定义。
+ ///
+ public static class ScriptingDefineSymbols
+ {
+ private static readonly BuildTargetGroup[] BuildTargetGroups = new BuildTargetGroup[]
+ {
+ BuildTargetGroup.Standalone,
+ BuildTargetGroup.iOS,
+ BuildTargetGroup.Android,
+ BuildTargetGroup.WSA,
+ BuildTargetGroup.WebGL
+ };
+
+ ///
+ /// 检查指定平台是否存在指定的脚本宏定义。
+ ///
+ /// 要检查脚本宏定义的平台。
+ /// 要检查的脚本宏定义。
+ /// 指定平台是否存在指定的脚本宏定义。
+ public static bool HasScriptingDefineSymbol(BuildTargetGroup buildTargetGroup, string scriptingDefineSymbol)
+ {
+ if (string.IsNullOrEmpty(scriptingDefineSymbol))
+ {
+ return false;
+ }
+
+ string[] scriptingDefineSymbols = GetScriptingDefineSymbols(buildTargetGroup);
+ foreach (string i in scriptingDefineSymbols)
+ {
+ if (i == scriptingDefineSymbol)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// 为指定平台增加指定的脚本宏定义。
+ ///
+ /// 要增加脚本宏定义的平台。
+ /// 要增加的脚本宏定义。
+ public static void AddScriptingDefineSymbol(BuildTargetGroup buildTargetGroup, string scriptingDefineSymbol)
+ {
+ if (string.IsNullOrEmpty(scriptingDefineSymbol))
+ {
+ return;
+ }
+
+ if (HasScriptingDefineSymbol(buildTargetGroup, scriptingDefineSymbol))
+ {
+ return;
+ }
+
+ List scriptingDefineSymbols = new List(GetScriptingDefineSymbols(buildTargetGroup))
+ {
+ scriptingDefineSymbol
+ };
+
+ SetScriptingDefineSymbols(buildTargetGroup, scriptingDefineSymbols.ToArray());
+ }
+
+ ///
+ /// 为指定平台移除指定的脚本宏定义。
+ ///
+ /// 要移除脚本宏定义的平台。
+ /// 要移除的脚本宏定义。
+ public static void RemoveScriptingDefineSymbol(BuildTargetGroup buildTargetGroup, string scriptingDefineSymbol)
+ {
+ if (string.IsNullOrEmpty(scriptingDefineSymbol))
+ {
+ return;
+ }
+
+ if (!HasScriptingDefineSymbol(buildTargetGroup, scriptingDefineSymbol))
+ {
+ return;
+ }
+
+ List scriptingDefineSymbols = new List(GetScriptingDefineSymbols(buildTargetGroup));
+ while (scriptingDefineSymbols.Contains(scriptingDefineSymbol))
+ {
+ scriptingDefineSymbols.Remove(scriptingDefineSymbol);
+ }
+
+ SetScriptingDefineSymbols(buildTargetGroup, scriptingDefineSymbols.ToArray());
+ }
+
+ ///
+ /// 为所有平台增加指定的脚本宏定义。
+ ///
+ /// 要增加的脚本宏定义。
+ public static void AddScriptingDefineSymbol(string scriptingDefineSymbol)
+ {
+ if (string.IsNullOrEmpty(scriptingDefineSymbol))
+ {
+ return;
+ }
+
+ foreach (BuildTargetGroup buildTargetGroup in BuildTargetGroups)
+ {
+ AddScriptingDefineSymbol(buildTargetGroup, scriptingDefineSymbol);
+ }
+ }
+
+ ///
+ /// 为所有平台移除指定的脚本宏定义。
+ ///
+ /// 要移除的脚本宏定义。
+ public static void RemoveScriptingDefineSymbol(string scriptingDefineSymbol)
+ {
+ if (string.IsNullOrEmpty(scriptingDefineSymbol))
+ {
+ return;
+ }
+
+ foreach (BuildTargetGroup buildTargetGroup in BuildTargetGroups)
+ {
+ RemoveScriptingDefineSymbol(buildTargetGroup, scriptingDefineSymbol);
+ }
+ }
+
+ ///
+ /// 获取指定平台的脚本宏定义。
+ ///
+ /// 要获取脚本宏定义的平台。
+ /// 平台的脚本宏定义。
+ public static string[] GetScriptingDefineSymbols(BuildTargetGroup buildTargetGroup)
+ {
+#if UNITY_6000_0_OR_NEWER
+ return PlayerSettings.GetScriptingDefineSymbols(NamedBuildTarget.FromBuildTargetGroup(buildTargetGroup)).Split(';');
+#else
+ return PlayerSettings.GetScriptingDefineSymbolsForGroup(buildTargetGroup).Split(';');
+#endif
+ }
+
+ ///
+ /// 设置指定平台的脚本宏定义。
+ ///
+ /// 要设置脚本宏定义的平台。
+ /// 要设置的脚本宏定义。
+ public static void SetScriptingDefineSymbols(BuildTargetGroup buildTargetGroup, string[] scriptingDefineSymbols)
+ {
+#if UNITY_6000_0_OR_NEWER
+ PlayerSettings.SetScriptingDefineSymbols(NamedBuildTarget.FromBuildTargetGroup(buildTargetGroup), string.Join(";", scriptingDefineSymbols));
+#else
+ PlayerSettings.SetScriptingDefineSymbolsForGroup(buildTargetGroup, string.Join(";", scriptingDefineSymbols));
+#endif
+ }
+ }
+}
diff --git a/Editor/Misc/ScriptingDefineSymbols.cs.meta b/Editor/Misc/ScriptingDefineSymbols.cs.meta
new file mode 100644
index 0000000..a7eca4a
--- /dev/null
+++ b/Editor/Misc/ScriptingDefineSymbols.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 99eaa7f830bac2c469f9aab52406b8ed
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Misc/TypeUtil.cs b/Editor/Misc/TypeUtil.cs
new file mode 100644
index 0000000..0e4ce9d
--- /dev/null
+++ b/Editor/Misc/TypeUtil.cs
@@ -0,0 +1,71 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using AlicizaX;
+
+namespace AlicizaX.Editor
+{
+ ///
+ /// 类型相关的实用函数。
+ ///
+ public static class TypeUtil
+ {
+ private static readonly string[] RuntimeAssemblyNames = Utility.Assembly.GetAssemblies().Where(m => !m.FullName.Contains("Editor")).Select(m => m.FullName).ToArray();
+
+ private static readonly string[] RuntimeOrEditorAssemblyNames = Utility.Assembly.GetAssemblies().Select(m => m.FullName).ToArray();
+
+ ///
+ /// 在运行时程序集中获取指定基类的所有子类的名称。
+ ///
+ /// 基类类型。
+ /// 指定基类的所有子类的名称。
+ public static string[] GetRuntimeTypeNames(System.Type typeBase)
+ {
+ return GetTypeNames(typeBase, RuntimeAssemblyNames);
+ }
+
+ ///
+ /// 在运行时或编辑器程序集中获取指定基类的所有子类的名称。
+ ///
+ /// 基类类型。
+ /// 指定基类的所有子类的名称。
+ internal static string[] GetRuntimeOrEditorTypeNames(System.Type typeBase)
+ {
+ return GetTypeNames(typeBase, RuntimeOrEditorAssemblyNames);
+ }
+
+ private static string[] GetTypeNames(System.Type typeBase, string[] assemblyNames)
+ {
+ var typeNames = new List();
+ foreach (var assemblyName in assemblyNames)
+ {
+ Assembly assembly = null;
+ try
+ {
+ assembly = Assembly.Load(assemblyName);
+ }
+ catch
+ {
+ continue;
+ }
+
+ if (assembly == null)
+ {
+ continue;
+ }
+
+ var types = assembly.GetTypes();
+ foreach (var type in types)
+ {
+ if (type.IsClass && !type.IsAbstract && typeBase.IsAssignableFrom(type))
+ {
+ typeNames.Add(type.FullName);
+ }
+ }
+ }
+
+ typeNames.Sort();
+ return typeNames.ToArray();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Editor/Misc/TypeUtil.cs.meta b/Editor/Misc/TypeUtil.cs.meta
new file mode 100644
index 0000000..8064749
--- /dev/null
+++ b/Editor/Misc/TypeUtil.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 82f73088e6e538649abbb7e35e3d6bf5
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/ObjectPool.meta b/Editor/ObjectPool.meta
new file mode 100644
index 0000000..564dfc0
--- /dev/null
+++ b/Editor/ObjectPool.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: f8f1cd19221bf3c48b13c9b505adc436
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/ObjectPool/ObjectPoolComponentInspector.cs b/Editor/ObjectPool/ObjectPoolComponentInspector.cs
new file mode 100644
index 0000000..23cd7c6
--- /dev/null
+++ b/Editor/ObjectPool/ObjectPoolComponentInspector.cs
@@ -0,0 +1,141 @@
+using AlicizaX;
+using AlicizaX.ObjectPool;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using UnityEditor;
+using UnityEngine;
+
+namespace AlicizaX.Editor
+{
+ [CustomEditor(typeof(ObjectPoolComponent))]
+ internal sealed class ObjectPoolComponentInspector : GameFrameworkInspector
+ {
+ private readonly HashSet m_OpenedItems = new HashSet();
+
+ public override void OnInspectorGUI()
+ {
+ base.OnInspectorGUI();
+
+ if (!EditorApplication.isPlaying)
+ {
+ EditorGUILayout.HelpBox("Available during runtime only.", MessageType.Info);
+ return;
+ }
+
+ ObjectPoolComponent t = (ObjectPoolComponent)target;
+
+ if (IsPrefabInHierarchy(t.gameObject))
+ {
+ EditorGUILayout.LabelField("Object Pool Count", t.Count.ToString());
+
+ ObjectPoolBase[] objectPools = t.GetAllObjectPools(true);
+ foreach (ObjectPoolBase objectPool in objectPools)
+ {
+ DrawObjectPool(objectPool);
+ }
+ }
+
+ Repaint();
+ }
+
+
+
+ private void DrawObjectPool(ObjectPoolBase objectPool)
+ {
+ bool lastState = m_OpenedItems.Contains(objectPool.FullName);
+ bool currentState = EditorGUILayout.Foldout(lastState, objectPool.FullName);
+ if (currentState != lastState)
+ {
+ if (currentState)
+ {
+ m_OpenedItems.Add(objectPool.FullName);
+ }
+ else
+ {
+ m_OpenedItems.Remove(objectPool.FullName);
+ }
+ }
+
+ if (currentState)
+ {
+ EditorGUILayout.BeginVertical("box");
+ {
+ EditorGUILayout.LabelField("Name", objectPool.Name);
+ EditorGUILayout.LabelField("Type", objectPool.ObjectType.FullName);
+ EditorGUILayout.LabelField("Auto Release Interval", objectPool.AutoReleaseInterval.ToString());
+ EditorGUILayout.LabelField("Capacity", objectPool.Capacity.ToString());
+ EditorGUILayout.LabelField("Used Count", objectPool.Count.ToString());
+ EditorGUILayout.LabelField("Can Release Count", objectPool.CanReleaseCount.ToString());
+ EditorGUILayout.LabelField("Expire Time", objectPool.ExpireTime.ToString());
+ EditorGUILayout.LabelField("Priority", objectPool.Priority.ToString());
+ ObjectInfo[] objectInfos = objectPool.GetAllObjectInfos();
+ if (objectInfos.Length > 0)
+ {
+ EditorGUILayout.LabelField("Name", objectPool.AllowMultiSpawn ? "Locked\tCount\tFlag\tPriority\tLast Use Time" : "Locked\tIn Use\tFlag\tPriority\tLast Use Time");
+ foreach (ObjectInfo objectInfo in objectInfos)
+ {
+#if UNITY_6000_0_OR_NEWER
+
+ EditorGUILayout.LabelField(string.IsNullOrEmpty(objectInfo.Name) ? "" : objectInfo.Name,
+ objectPool.AllowMultiSpawn
+ ? Utility.Text.Format("{0,-12}\t{1,-12}\t{2,-12}\t{3,-12}\t{4:yyyy-MM-dd HH:mm:ss,-12}", objectInfo.Locked, objectInfo.SpawnCount, objectInfo.CustomCanReleaseFlag, objectInfo.Priority, objectInfo.LastUseTime.ToLocalTime())
+ : Utility.Text.Format("{0,-12}\t{1,-12}\t{2,-12}\t{3,-12}\t{4:yyyy-MM-dd HH:mm:ss,-12}", objectInfo.Locked, objectInfo.IsInUse, objectInfo.CustomCanReleaseFlag, objectInfo.Priority, objectInfo.LastUseTime.ToLocalTime()));
+#else
+ EditorGUILayout.LabelField(string.IsNullOrEmpty(objectInfo.Name) ? "" : objectInfo.Name,
+ objectPool.AllowMultiSpawn
+ ? Utility.Text.Format("{0}\t{1}\t{2}\t{3}\t{4:yyyy-MM-dd HH:mm:ss}", objectInfo.Locked, objectInfo.SpawnCount, objectInfo.CustomCanReleaseFlag, objectInfo.Priority, objectInfo.LastUseTime.ToLocalTime())
+ : Utility.Text.Format("{0}\t{1}\t{2}\t{3}\t{4:yyyy-MM-dd HH:mm:ss}", objectInfo.Locked, objectInfo.IsInUse, objectInfo.CustomCanReleaseFlag, objectInfo.Priority, objectInfo.LastUseTime.ToLocalTime()));
+#endif
+ }
+
+ if (GUILayout.Button("Release"))
+ {
+ objectPool.Release();
+ }
+
+ if (GUILayout.Button("Release All Unused"))
+ {
+ objectPool.ReleaseAllUnused();
+ }
+
+ if (GUILayout.Button("Export CSV Data"))
+ {
+ string exportFileName = EditorUtility.SaveFilePanel("Export CSV Data", string.Empty, Utility.Text.Format("Object Pool Data - {0}.csv", objectPool.Name), string.Empty);
+ if (!string.IsNullOrEmpty(exportFileName))
+ {
+ try
+ {
+ int index = 0;
+ string[] data = new string[objectInfos.Length + 1];
+ data[index++] = Utility.Text.Format("Name,Locked,{0},Custom Can Release Flag,Priority,Last Use Time", objectPool.AllowMultiSpawn ? "Count" : "In Use");
+ foreach (ObjectInfo objectInfo in objectInfos)
+ {
+ data[index++] = objectPool.AllowMultiSpawn
+ ? Utility.Text.Format("{0},{1},{2},{3},{4},{5:yyyy-MM-dd HH:mm:ss}", objectInfo.Name, objectInfo.Locked, objectInfo.SpawnCount, objectInfo.CustomCanReleaseFlag, objectInfo.Priority, objectInfo.LastUseTime.ToLocalTime())
+ : Utility.Text.Format("{0},{1},{2},{3},{4},{5:yyyy-MM-dd HH:mm:ss}", objectInfo.Name, objectInfo.Locked, objectInfo.IsInUse, objectInfo.CustomCanReleaseFlag, objectInfo.Priority, objectInfo.LastUseTime.ToLocalTime());
+ }
+
+ File.WriteAllLines(exportFileName, data, Encoding.UTF8);
+ Debug.Log(Utility.Text.Format("Export object pool CSV data to '{0}' success.", exportFileName));
+ }
+ catch (Exception exception)
+ {
+ Debug.LogError(Utility.Text.Format("Export object pool CSV data to '{0}' failure, exception is '{1}'.", exportFileName, exception));
+ }
+ }
+ }
+ }
+ else
+ {
+ GUILayout.Label("Object Pool is Empty ...");
+ }
+ }
+ EditorGUILayout.EndVertical();
+
+ EditorGUILayout.Separator();
+ }
+ }
+ }
+}
diff --git a/Editor/ObjectPool/ObjectPoolComponentInspector.cs.meta b/Editor/ObjectPool/ObjectPoolComponentInspector.cs.meta
new file mode 100644
index 0000000..26a3673
--- /dev/null
+++ b/Editor/ObjectPool/ObjectPoolComponentInspector.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: eb0d4111d9ba0ad469b2361c7731666a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Resource/Inspector/ResourceComponentInspector.cs b/Editor/Resource/Inspector/ResourceComponentInspector.cs
index 9202a7a..c4f56e8 100644
--- a/Editor/Resource/Inspector/ResourceComponentInspector.cs
+++ b/Editor/Resource/Inspector/ResourceComponentInspector.cs
@@ -18,6 +18,7 @@ namespace AlicizaX.Resource.Editor
"HostPlayMode (联机运行模式)",
"WebPlayMode (WebGL运行模式)"
};
+
private SerializedProperty _milliseconds = null;
private SerializedProperty _minUnloadUnusedAssetsInterval = null;
private SerializedProperty _maxUnloadUnusedAssetsInterval = null;
@@ -30,6 +31,7 @@ namespace AlicizaX.Resource.Editor
private SerializedProperty _failedTryAgain = null;
private SerializedProperty _packageName = null;
private SerializedProperty _decryptionServices = null;
+ private SerializedProperty _playMode = null;
private int _packageNameIndex = 0;
private string[] _packageNames;
@@ -37,6 +39,7 @@ namespace AlicizaX.Resource.Editor
private int m_DecryptionSelectIndex;
private int _playModeIndex = 0;
+
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
@@ -58,6 +61,7 @@ namespace AlicizaX.Resource.Editor
if (selectedIndex != _playModeIndex)
{
_playModeIndex = selectedIndex;
+ _playMode.enumValueIndex = _playModeIndex;
EditorPrefs.SetInt(ResourceComponent.PrefsKey, selectedIndex);
}
}
@@ -230,7 +234,6 @@ namespace AlicizaX.Resource.Editor
private void OnEnable()
{
-
_milliseconds = serializedObject.FindProperty("milliseconds");
_minUnloadUnusedAssetsInterval = serializedObject.FindProperty("minUnloadUnusedAssetsInterval");
_maxUnloadUnusedAssetsInterval = serializedObject.FindProperty("maxUnloadUnusedAssetsInterval");
@@ -243,8 +246,11 @@ namespace AlicizaX.Resource.Editor
_failedTryAgain = serializedObject.FindProperty("failedTryAgain");
_packageName = serializedObject.FindProperty("packageName");
_decryptionServices = serializedObject.FindProperty("_decryptionServices");
+ _playMode = serializedObject.FindProperty("_playMode");
RefreshDecryptionServices();
RefreshTypeNames();
+ _playModeIndex = EditorPrefs.GetInt(ResourceComponent.PrefsKey, 0);
+ _playMode.enumValueIndex = _playModeIndex;
}
diff --git a/Editor/Utility.meta b/Editor/Utility.meta
new file mode 100644
index 0000000..a7ae566
--- /dev/null
+++ b/Editor/Utility.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 3a0cecfd631d46f09c53e7e2374100d0
+timeCreated: 1758265178
\ No newline at end of file
diff --git a/Editor/Utility/EditorDrawing.cs b/Editor/Utility/EditorDrawing.cs
new file mode 100644
index 0000000..4f537bd
--- /dev/null
+++ b/Editor/Utility/EditorDrawing.cs
@@ -0,0 +1,1866 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEditor;
+using UnityEditor.IMGUI.Controls;
+using Object = UnityEngine.Object;
+using UnityEditorInternal;
+
+namespace AlicizaX.Editor
+{
+ public class MultiToolbarItem
+ {
+ public GUIContent content;
+ public SerializedProperty property;
+
+ public MultiToolbarItem(GUIContent content, SerializedProperty toggleProperty)
+ {
+ this.content = content;
+ property = toggleProperty;
+ }
+ }
+
+ public static class EditorDrawing
+ {
+ public static class Styles
+ {
+ public static Color? labelColor = null;
+
+ public static GUIStyle borderBoxHeaderStyle
+ {
+ get
+ {
+ GUIStyle style = new();
+ style.margin = new RectOffset(3, 3, 2, 2);
+ style.padding = new RectOffset(5, 5, 2, 5);
+ return style;
+ }
+ }
+
+ public static GUIStyle borderBoxStyle
+ {
+ get
+ {
+ GUIStyle style = new();
+ style.margin = new RectOffset(3, 3, 2, 2);
+ style.padding = new RectOffset(5, 5, 5, 5);
+ return style;
+ }
+ }
+
+ public static GUIStyle miniBoldLabelMiddleLeft
+ {
+ get
+ {
+ GUIStyle style = new(EditorStyles.miniBoldLabel);
+ style.alignment = TextAnchor.MiddleLeft;
+
+ if (labelColor.HasValue)
+ style.normal.textColor = labelColor.Value;
+
+ return style;
+ }
+ }
+
+ public static GUIStyle boldLabelMiddleLeft
+ {
+ get
+ {
+ GUIStyle style = new(EditorStyles.boldLabel);
+ style.alignment = TextAnchor.MiddleLeft;
+
+ if (labelColor.HasValue)
+ style.normal.textColor = labelColor.Value;
+
+ return style;
+ }
+ }
+
+ public static GUIStyle miniBoldLabelCenter
+ {
+ get
+ {
+ GUIStyle style = new(EditorStyles.miniBoldLabel);
+ style.alignment = TextAnchor.MiddleLeft;
+
+ if (labelColor.HasValue)
+ style.normal.textColor = labelColor.Value;
+
+ return style;
+ }
+ }
+
+ public static GUIStyle miniBoldLabelFoldout
+ {
+ get
+ {
+ GUIStyle style = new(EditorStyles.foldout);
+ style.font = EditorStyles.miniBoldLabel.font;
+ style.fontStyle = EditorStyles.miniBoldLabel.fontStyle;
+ style.fontSize = EditorStyles.miniBoldLabel.fontSize;
+ return style;
+ }
+ }
+
+ public static GUIStyle miniBoldButton
+ {
+ get
+ {
+ GUIStyle style = new(GUI.skin.button);
+ style.font = EditorStyles.miniBoldLabel.font;
+ style.fontStyle = EditorStyles.miniBoldLabel.fontStyle;
+ style.fontSize = EditorStyles.miniBoldLabel.fontSize;
+ return style;
+ }
+ }
+
+ public static GUIStyle RichLabel
+ {
+ get => new(EditorStyles.label)
+ {
+ richText = true
+ };
+ }
+
+ public static GUIStyle RichHelpBox
+ {
+ get => new(EditorStyles.helpBox)
+ {
+ richText = true
+ };
+ }
+
+ public static Texture2D TransparentCheckerTexture
+ {
+ get
+ {
+ if (EditorGUIUtility.isProSkin)
+ {
+ return EditorGUIUtility.LoadRequired("Previews/Textures/textureCheckerDark.png") as Texture2D;
+ }
+
+ return EditorGUIUtility.LoadRequired("Previews/Textures/textureChecker.png") as Texture2D;
+ }
+ }
+ }
+
+ public static GUIStyle CenterStyle(GUIStyle style)
+ {
+ return new GUIStyle(style)
+ {
+ alignment = TextAnchor.MiddleCenter
+ };
+ }
+
+ public static GUIStyle CenterStyleLeft(GUIStyle style)
+ {
+ return new GUIStyle(style)
+ {
+ alignment = TextAnchor.MiddleLeft
+ };
+ }
+
+ public class BorderBoxScope : GUI.Scope
+ {
+ public BorderBoxScope(bool roundedBox = true)
+ {
+ BeginBorderLayout(roundedBox);
+ }
+
+ public BorderBoxScope(GUIContent title, float headerHeight = 18f, bool roundedBox = true)
+ {
+ BeginHeaderBorderLayout(title, headerHeight, roundedBox);
+ }
+
+ protected override void CloseScope()
+ {
+ EndBorderHeaderLayout();
+ }
+ }
+
+ public class ToggleBorderBoxScope : GUI.Scope
+ {
+ private readonly bool disableContent;
+
+ public ToggleBorderBoxScope(GUIContent title, SerializedProperty toggle, float headerHeight = 18f, bool roundedBox = true, bool disableContent = true)
+ {
+ this.disableContent = disableContent;
+ toggle.boolValue = BeginToggleBorderLayout(title, toggle.boolValue, headerHeight, roundedBox);
+ if (disableContent) GUI.enabled = toggle.boolValue;
+ }
+
+ protected override void CloseScope()
+ {
+ if (disableContent) GUI.enabled = true;
+ EndBorderHeaderLayout();
+ }
+ }
+
+ public class IconSizeScope : GUI.Scope
+ {
+ private Vector2 prevIconSize;
+
+ public IconSizeScope(Vector2 iconSize)
+ {
+ prevIconSize = EditorGUIUtility.GetIconSize();
+ EditorGUIUtility.SetIconSize(iconSize);
+ }
+
+ public IconSizeScope(float iconSize)
+ {
+ prevIconSize = EditorGUIUtility.GetIconSize();
+ EditorGUIUtility.SetIconSize(new Vector2(iconSize, iconSize));
+ }
+
+ public IconSizeScope(float x, float y)
+ {
+ prevIconSize = EditorGUIUtility.GetIconSize();
+ EditorGUIUtility.SetIconSize(new Vector2(x, y));
+ }
+
+ protected override void CloseScope()
+ {
+ EditorGUIUtility.SetIconSize(prevIconSize);
+ }
+ }
+
+ public class BackgroundColorScope : GUI.Scope
+ {
+ private Color prevColor;
+
+ public BackgroundColorScope(Color backgroundColor)
+ {
+ prevColor = GUI.backgroundColor;
+ GUI.backgroundColor = backgroundColor;
+ }
+
+ public BackgroundColorScope(string htmlColor)
+ {
+ prevColor = GUI.backgroundColor;
+ if (ColorUtility.TryParseHtmlString(htmlColor, out Color bgColor))
+ GUI.backgroundColor = bgColor;
+ }
+
+ protected override void CloseScope()
+ {
+ GUI.backgroundColor = prevColor;
+ }
+ }
+
+ ///
+ /// Set custom icon size.
+ ///
+ public static Vector2 SetIconSize(float iconSize)
+ {
+ Vector2 prevIconSize = EditorGUIUtility.GetIconSize();
+ EditorGUIUtility.SetIconSize(new Vector2(iconSize, iconSize));
+ return prevIconSize;
+ }
+
+ ///
+ /// Reset custom icon size.
+ ///
+ public static void ResetIconSize()
+ {
+ EditorGUIUtility.SetIconSize(Vector2.zero);
+ }
+
+ ///
+ /// Set the header label text color.
+ ///
+ public static void SetLabelColor(Color color)
+ {
+ Styles.labelColor = color;
+ }
+
+ ///
+ /// Set the header label text color.
+ ///
+ public static void SetLabelColor(string htmlColor)
+ {
+ if (ColorUtility.TryParseHtmlString(htmlColor, out Color color))
+ Styles.labelColor = color;
+ }
+
+ ///
+ /// Reset the header label text color.
+ ///
+ public static void ResetLabelColor()
+ {
+ Styles.labelColor = null;
+ }
+
+ ///
+ /// Get GUIContent with specified icon.
+ ///
+ public static GUIContent IconContent(string iconName, float iconSize = 16f)
+ {
+ SetIconSize(iconSize);
+ return EditorGUIUtility.TrIconContent(iconName);
+ }
+
+ ///
+ /// Get GUIContent with specified icon and text.
+ ///
+ public static GUIContent IconTextContent(string text, string iconName, float iconSize = 16f)
+ {
+ SetIconSize(iconSize);
+ return EditorGUIUtility.TrTextContentWithIcon(" " + text, iconName);
+ }
+
+ ///
+ /// Get GUIContent with specified icon and text.
+ ///
+ public static GUIContent IconTextContent(string text, Texture2D texture, float iconSize = 16f)
+ {
+ SetIconSize(iconSize);
+ return EditorGUIUtility.TrTextContentWithIcon(" " + text, texture);
+ }
+
+ ///
+ /// Classic HelpBox but with Rich Text.
+ ///
+ public static void RichHelpBox(string message, MessageType type)
+ {
+ GUIContent content = new(message);
+ content.image = GetHelpIcon(type);
+ EditorGUILayout.LabelField(GUIContent.none, content, Styles.RichHelpBox);
+ }
+
+ private static Texture2D GetHelpIcon(MessageType type)
+ {
+ Type editorGUIUtilityType = typeof(EditorGUIUtility);
+ MethodInfo method = editorGUIUtilityType.GetMethod("GetHelpIcon",
+ BindingFlags.NonPublic | BindingFlags.Static
+ );
+
+ if (method != null)
+ {
+ return method.Invoke(null, new object[] { type }) as Texture2D;
+ }
+
+ Debug.LogError("GetHelpIcon method not found!");
+ return null;
+ }
+
+ ///
+ /// Draw custom border box.
+ ///
+ public static void DrawBorderBox(Rect rect, RectOffset border, Color color)
+ {
+ if (Event.current.type != EventType.Repaint)
+ return;
+
+ Color orgColor = GUI.color;
+
+ GUI.color *= color;
+ GUI.DrawTexture(new Rect(rect.x, rect.y, rect.width, border.top), EditorGUIUtility.whiteTexture); //top
+ GUI.DrawTexture(new Rect(rect.x, rect.yMax - border.bottom, rect.width, border.bottom), EditorGUIUtility.whiteTexture); //bottom
+ GUI.DrawTexture(new Rect(rect.x, rect.y + border.left, border.left, rect.height - 2 * border.left), EditorGUIUtility.whiteTexture); //left
+ GUI.DrawTexture(new Rect(rect.xMax - border.right, rect.y + border.right, border.right, rect.height - 2 * border.right), EditorGUIUtility.whiteTexture); //right
+
+ GUI.color = orgColor;
+ }
+
+ ///
+ /// Draw the heading at the top of the inspector.
+ ///
+ public static void DrawInspectorHeader(GUIContent title, Object script = null)
+ {
+ GUIStyle headerStyle = new(EditorStyles.boldLabel)
+ {
+ fontSize = 13,
+ alignment = TextAnchor.MiddleCenter
+ };
+
+ headerStyle.normal.textColor = Color.white;
+ title.text = title.text.ToUpper();
+
+ using (new IconSizeScope(13))
+ {
+ Rect rect = GUILayoutUtility.GetRect(1, 30);
+ ColorUtility.TryParseHtmlString("#181818", out Color color);
+
+ EditorGUI.DrawRect(rect, color);
+ //EditorGUI.DrawRect(new Rect(rect.x, rect.y, rect.width, 1), Color.white.Alpha(0.4f));
+ //EditorGUI.DrawRect(new Rect(rect.x, rect.yMax - 1, rect.width, 1), Color.white.Alpha(0.4f));
+ //DrawCorners(rect, Vector2.one * 5, 1, Color.white.Alpha(0.75f));
+
+ Texture2D mask = AssetDatabase.LoadAssetAtPath("Packages/com.alicizax.unity.framework/Editor/Localization/EditorIcons/mask.png");
+ GUI.DrawTexture(new Rect(rect.x, rect.yMax - 1, rect.width, 1), mask);
+
+ if (script != null)
+ {
+ MonoScript monoScript = null;
+
+ if (script is MonoBehaviour monoBehaviour)
+ monoScript = MonoScript.FromMonoBehaviour(monoBehaviour);
+ else if (script is ScriptableObject scriptableObject)
+ monoScript = MonoScript.FromScriptableObject(scriptableObject);
+
+
+ Event e = Event.current;
+
+ Rect pingRect = rect;
+ Rect docsIconRect = rect;
+ Rect saveableIconRect = rect;
+
+ docsIconRect.xMin = docsIconRect.xMax - EditorGUIUtility.singleLineHeight - 3f;
+ docsIconRect.y += 7f;
+ docsIconRect.width = EditorGUIUtility.singleLineHeight;
+ docsIconRect.height = EditorGUIUtility.singleLineHeight;
+
+ saveableIconRect.xMin = saveableIconRect.xMax - (EditorGUIUtility.singleLineHeight * 2) - 3f;
+ saveableIconRect.y += 6f;
+ saveableIconRect.width = EditorGUIUtility.singleLineHeight;
+ saveableIconRect.height = EditorGUIUtility.singleLineHeight;
+
+
+ if (pingRect.Contains(e.mousePosition))
+ {
+ if (e.type == EventType.MouseDown && e.button == 0)
+ {
+ EditorGUIUtility.PingObject(monoScript);
+ }
+ }
+
+ saveableIconRect = docsIconRect;
+ saveableIconRect.y -= 1f;
+
+ // draw saveable icon
+ // bool flag1 = typeof(UHFPS.Runtime.ISaveable).IsAssignableFrom(script.GetType());
+ // bool flag2 = typeof(UHFPS.Runtime.IRuntimeSaveable).IsAssignableFrom(script.GetType());
+ // bool flag3 = typeof(UHFPS.Runtime.ISaveableCustom).IsAssignableFrom(script.GetType());
+ //
+ // if (flag1 || flag2 || flag3)
+ // {
+ // GUIContent saveableIcon = EditorGUIUtility.TrIconContent("CacheServerConnected", "Script is Saveable");
+ // EditorGUI.LabelField(saveableIconRect, saveableIcon);
+ // }
+ }
+
+ EditorGUI.LabelField(rect, title, headerStyle);
+ }
+ }
+
+ private static void DrawCorners(Rect rect, Vector2 cornerSize, Color cornerColor)
+ {
+ EditorGUI.DrawRect(new Rect(rect.x, rect.y, cornerSize.x, cornerSize.y), cornerColor);
+
+ // Draw top-right corner
+ EditorGUI.DrawRect(new Rect(rect.xMax - cornerSize.x, rect.y, cornerSize.x, cornerSize.y), cornerColor);
+
+ // Draw bottom-left corner
+ EditorGUI.DrawRect(new Rect(rect.x, rect.yMax - cornerSize.y, cornerSize.x, cornerSize.y), cornerColor);
+
+ // Draw bottom-right corner
+ EditorGUI.DrawRect(new Rect(rect.xMax - cornerSize.x, rect.yMax - cornerSize.y, cornerSize.x, cornerSize.y), cornerColor);
+ }
+
+ private static void DrawCorners(Rect rect, Vector2 cornerSize, float thickness, Color cornerColor)
+ {
+ // Draw top-left corner
+ EditorGUI.DrawRect(new Rect(rect.x, rect.y, cornerSize.x, thickness), cornerColor); // Horizontal part of L
+ EditorGUI.DrawRect(new Rect(rect.x, rect.y, thickness, cornerSize.y), cornerColor); // Vertical part of L
+
+ // Draw top-right corner
+ EditorGUI.DrawRect(new Rect(rect.xMax - cornerSize.x, rect.y, cornerSize.x, thickness), cornerColor); // Horizontal part of L
+ EditorGUI.DrawRect(new Rect(rect.xMax - thickness, rect.y, thickness, cornerSize.y), cornerColor); // Vertical part of L
+
+ // Draw bottom-left corner
+ EditorGUI.DrawRect(new Rect(rect.x, rect.yMax - thickness, cornerSize.x, thickness), cornerColor); // Horizontal part of L
+ EditorGUI.DrawRect(new Rect(rect.x, rect.yMax - cornerSize.y, thickness, cornerSize.y), cornerColor); // Vertical part of L
+
+ // Draw bottom-right corner
+ EditorGUI.DrawRect(new Rect(rect.xMax - cornerSize.x, rect.yMax - thickness, cornerSize.x, thickness), cornerColor); // Horizontal part of L
+ EditorGUI.DrawRect(new Rect(rect.xMax - thickness, rect.yMax - cornerSize.y, thickness, cornerSize.y), cornerColor); // Vertical part of L
+ }
+
+
+ ///
+ /// Draw classic prefix label.
+ ///
+ public static void DrawPrefixLabel(string title, string text, GUIStyle style)
+ {
+ EditorGUILayout.BeginHorizontal();
+ EditorGUILayout.PrefixLabel(title);
+ EditorGUILayout.LabelField(text, style);
+ EditorGUILayout.EndHorizontal();
+ }
+
+ ///
+ /// Draw min-max slider from MinMax property.
+ ///
+ public static void DrawMinMaxSlider(SerializedProperty minMaxProperty, float minLimit, float maxLimit)
+ {
+ SerializedProperty minProp = minMaxProperty.FindPropertyRelative("min");
+ SerializedProperty maxProp = minMaxProperty.FindPropertyRelative("max");
+
+ float minValue = minProp.floatValue;
+ float maxValue = maxProp.floatValue;
+ EditorGUILayout.MinMaxSlider(minMaxProperty.displayName, ref minValue, ref maxValue, minLimit, maxLimit);
+
+ minProp.floatValue = minValue;
+ maxProp.floatValue = maxValue;
+ }
+
+ ///
+ /// Draw a property field with a label after the field.
+ ///
+ public static void DrawPropertyAndLabel(SerializedProperty property, GUIContent content)
+ {
+ DrawPropertyAndLabel(property, content, EditorStyles.boldLabel);
+ }
+
+ ///
+ /// Draw a property field with a label after the field.
+ ///
+ public static void DrawPropertyAndLabel(SerializedProperty property, GUIContent content, GUIStyle labelStyle)
+ {
+ Rect rect = EditorGUILayout.GetControlRect();
+
+ float labelWidth = labelStyle.CalcSize(content).x;
+ rect.xMax -= labelWidth + 2f;
+
+ EditorGUI.PropertyField(rect, property);
+
+ rect.xMin = rect.xMax + 2f;
+ rect.xMax += labelWidth + 2f;
+ EditorGUI.LabelField(rect, content, labelStyle);
+ }
+
+ ///
+ /// Draw horizontal separator.
+ ///
+ public static void Separator(int height = 1)
+ {
+ Rect rect = EditorGUILayout.GetControlRect(false, height);
+ rect.height = height;
+ EditorGUI.DrawRect(rect, new Color(0.5f, 0.5f, 0.5f, 1));
+ }
+
+ ///
+ /// Draw horizontal (spaced) separator.
+ ///
+ public static void SeparatorSpaced(float space, int height = 1)
+ {
+ EditorGUILayout.Space(space);
+ Separator(height);
+ EditorGUILayout.Space(space);
+ }
+
+ ///
+ /// Draw a large sprite selection similar to the EditorGUI.ObjectField sprite selection.
+ ///
But with the correct, not stretched image.
+ ///
+ public static Object DrawLargeSpriteSelector(Rect rect, Object obj)
+ {
+ Rect borderRect = rect;
+ borderRect.height -= 1;
+ borderRect.width -= 1;
+ GUI.Box(borderRect, GUIContent.none, new GUIStyle("HelpBox"));
+
+ Rect iconRect = rect;
+ iconRect.height -= 10;
+ iconRect.width -= 10;
+ iconRect.x += 5f;
+ iconRect.y += 5f;
+
+ DrawTransparentTextureWithChecker(iconRect, obj);
+
+ Rect mageSelectRect = rect;
+ mageSelectRect.yMax -= 15f;
+
+ Event e = Event.current;
+ if (mageSelectRect.Contains(e.mousePosition))
+ {
+ if (e.type == EventType.MouseDown && e.button == 0 && obj != null)
+ {
+ EditorGUIUtility.PingObject(obj);
+ e.Use();
+ }
+ }
+
+ Rect buttonRect = rect;
+ buttonRect.height = 15f;
+ buttonRect.xMin = rect.width - rect.width * 0.7f;
+ buttonRect.y = rect.height - 16f;
+ buttonRect.x -= 1f;
+
+ if (GUI.Button(buttonRect, "Select", EditorStyles.objectFieldThumb))
+ {
+ EditorGUIUtility.ShowObjectPicker(obj, false, "", GUIUtility.GetControlID(FocusType.Passive));
+ }
+ else if (Event.current.commandName == "ObjectSelectorUpdated")
+ {
+ return EditorGUIUtility.GetObjectPickerObject();
+ }
+
+ return obj;
+ }
+
+ ///
+ /// Draw classic object field with picker.
+ ///
+ public static bool ObjectField(Rect rect, GUIContent text, GUIContent tooltip = null)
+ {
+ using (new IconSizeScope(12f))
+ {
+ GUI.Box(rect, text, EditorStyles.objectField);
+
+ GUIStyle buttonStyle = new GUIStyle("ObjectFieldButton") { richText = true };
+ Rect buttonRect = buttonStyle.margin.Remove(new Rect(rect.xMax - 19, rect.y, 19, rect.height));
+
+ return GUI.Button(buttonRect, tooltip ?? new GUIContent(), buttonStyle);
+ }
+ }
+
+ ///
+ /// Draw a multi selection toolbar.
+ ///
+ public static void DrawMultiToolbar(Rect rect, MultiToolbarItem[] contents, float buttonWidth = 100)
+ {
+ GUIStyle style = new GUIStyle(GUI.skin.button);
+ string name = style.name;
+
+ GUIStyle midStyle = GUI.skin.FindStyle(name + "mid") ?? style;
+ GUIStyle firstStyle = GUI.skin.FindStyle(name + "left") ?? midStyle;
+ GUIStyle lastStyle = GUI.skin.FindStyle(name + "right") ?? midStyle;
+
+ Rect toolbarRect = rect;
+ toolbarRect.width = buttonWidth;
+
+ for (int i = 0; i < contents.Length; i++)
+ {
+ if (i == 0)
+ {
+ // first
+ contents[i].property.boolValue = GUI.Toggle(toolbarRect, contents[i].property.boolValue, contents[i].content, firstStyle);
+ toolbarRect.x += buttonWidth;
+ }
+ else if (i == contents.Length - 1)
+ {
+ // last
+ contents[i].property.boolValue = GUI.Toggle(toolbarRect, contents[i].property.boolValue, contents[i].content, lastStyle);
+ toolbarRect.x += buttonWidth;
+ }
+ else
+ {
+ // mid
+ contents[i].property.boolValue = GUI.Toggle(toolbarRect, contents[i].property.boolValue, contents[i].content, midStyle);
+ toolbarRect.x += buttonWidth;
+ }
+ }
+ }
+
+ ///
+ /// Draw multi selection popup menu.
+ ///
+ public static void DrawMultiSelectPopup(Rect rect, string[] content, string[] selected, Action onItemSelect, int maxDisplay = 3)
+ {
+ GenericMenu menu = new GenericMenu();
+ List selectedArr = new List(selected);
+
+ for (int i = 0; i < content.Length; i++)
+ {
+ string name = content[i];
+ bool on = selected.Any(x => x.Equals(name));
+
+ menu.AddItem(new GUIContent(name), on, data =>
+ {
+ if (selectedArr.Contains(name))
+ selectedArr.Remove(name);
+ else selectedArr.Add(name);
+ onItemSelect?.Invoke(selectedArr.ToArray());
+ },
+ name);
+ }
+
+ string popupTitle = "Nothing";
+ if (selected.Length > 0)
+ {
+ popupTitle = string.Join(", ", selected.Take(maxDisplay));
+
+ if (selected.Length > maxDisplay)
+ popupTitle += " ...";
+ }
+
+ if (GUI.Button(rect, popupTitle, EditorStyles.popup))
+ {
+ menu.DropDown(rect);
+ }
+ }
+
+ ///
+ /// Draw multi selection popup menu.
+ ///
+ public static void DrawMultiSelectPopup(Rect rect, GUIContent title, string[] content, string[] selected, Action onItemSelect, int maxDisplay = 3)
+ {
+ GenericMenu menu = new GenericMenu();
+ GUIContent guiTitle = new GUIContent(title);
+ List selectedArr = new List(selected);
+
+ for (int i = 0; i < content.Length; i++)
+ {
+ string name = content[i];
+ bool on = selected.Any(x => x.Equals(name));
+
+ menu.AddItem(new GUIContent(name), on, data =>
+ {
+ if (selectedArr.Contains(name))
+ selectedArr.Remove(name);
+ else selectedArr.Add(name);
+ onItemSelect?.Invoke(selectedArr.ToArray());
+ },
+ name);
+ }
+
+ if (selected.Length > 0)
+ {
+ guiTitle.text = string.Join(", ", selected.Take(maxDisplay));
+
+ if (selected.Length > maxDisplay)
+ guiTitle.text += " ...";
+ }
+
+ if (GUI.Button(rect, guiTitle, EditorStyles.popup))
+ {
+ menu.DropDown(rect);
+ }
+ }
+
+ ///
+ /// Draw string selection popup menu.
+ ///
+ public static void DrawStringSelectPopup(Rect rect, string[] content, string selected, Action onItemSelect)
+ {
+ GenericMenu menu = new GenericMenu();
+
+ for (int i = 0; i < content.Length; i++)
+ {
+ string name = content[i];
+ bool on = name.Equals(selected);
+
+ menu.AddItem(new GUIContent(name), on, data =>
+ {
+ string selection = (string)data;
+ string result = selected;
+
+ if (selected != selection)
+ result = selection;
+
+ onItemSelect?.Invoke(result);
+ },
+ name);
+ }
+
+ string popupTitle = string.IsNullOrEmpty(selected) ? "Nothing" : selected;
+ if (GUI.Button(rect, popupTitle, EditorStyles.popup))
+ {
+ menu.DropDown(rect);
+ }
+ }
+
+ ///
+ /// Draw string selection popup menu.
+ ///
+ public static void DrawStringSelectPopup(Rect rect, GUIContent title, string[] content, string selected, Action onItemSelect)
+ {
+ GenericMenu menu = new GenericMenu();
+ GUIContent guiTitle = new GUIContent(title);
+
+ for (int i = 0; i < content.Length; i++)
+ {
+ string name = content[i];
+ bool on = name.Equals(selected);
+
+ menu.AddItem(new GUIContent(name), on, data =>
+ {
+ string selection = (string)data;
+ string result = selected;
+
+ if (selected != selection)
+ result = selection;
+
+ onItemSelect?.Invoke(result);
+ },
+ name);
+ }
+
+ if (!string.IsNullOrEmpty(selected))
+ guiTitle.text = selected;
+
+ if (GUI.Button(rect, guiTitle, EditorStyles.popup))
+ {
+ menu.DropDown(rect);
+ }
+ }
+
+ ///
+ /// Draw string selection popup menu.
+ ///
+ public static void DrawStringSelectPopup(GUIContent label, GUIContent title, string[] content, string selected, Action onItemSelect)
+ {
+ Rect rect = EditorGUILayout.GetControlRect();
+ rect = EditorGUI.PrefixLabel(rect, label);
+ DrawStringSelectPopup(rect, title, content, selected, onItemSelect);
+ }
+
+ ///
+ /// Draw string selection popup menu.
+ ///
+ public static void DrawStringSelectPopup(GUIContent title, string[] content, string selected, Action onItemSelect)
+ {
+ Rect rect = EditorGUILayout.GetControlRect();
+ DrawStringSelectPopup(rect, title, content, selected, onItemSelect);
+ }
+
+ ///
+ /// Draw custom class property list.
+ ///
+ public static void DrawList(SerializedProperty listProperty, GUIContent title, float headerHeight = 18f, bool roundedBox = true)
+ {
+ if (BeginFoldoutBorderLayout(listProperty, title, headerHeight, roundedBox))
+ {
+ for (int i = 0; i < listProperty.arraySize; i++)
+ {
+ SerializedProperty listItem = listProperty.GetArrayElementAtIndex(i);
+ var properties = GetAllProperties(listItem);
+ string elementName = "Element " + i;
+
+ var firstProperty = properties.First();
+ if (firstProperty.Value.propertyType == SerializedPropertyType.String)
+ {
+ string stringValue = firstProperty.Value.stringValue;
+ if (!string.IsNullOrEmpty(stringValue)) elementName = stringValue;
+ }
+
+ if (BeginFoldoutBorderLayout(listItem, new GUIContent(elementName), out Rect itemHeaderRect))
+ {
+ properties.DrawAll(true);
+ EndBorderHeaderLayout();
+ }
+
+ GUIContent minus = EditorGUIUtility.TrIconContent("Toolbar Minus");
+ Rect minusRect = itemHeaderRect;
+ minusRect.xMin = minusRect.xMax - EditorGUIUtility.singleLineHeight;
+ minusRect.x -= 3f;
+ minusRect.y += 3f;
+
+ if (GUI.Button(minusRect, minus, EditorStyles.iconButton))
+ {
+ listProperty.DeleteArrayElementAtIndex(i);
+ }
+ }
+
+ if (listProperty.arraySize > 0)
+ EditorGUILayout.Space(2f);
+
+ if (GUILayout.Button("Add"))
+ {
+ listProperty.arraySize++;
+ }
+
+ EndBorderHeaderLayout();
+ }
+ }
+
+ public static int BeginDrawCustomList(SerializedProperty listProperty, GUIContent title)
+ {
+ int arraySize = listProperty.arraySize;
+
+ EditorGUILayout.BeginVertical(GUI.skin.box);
+
+ Rect labelRect = EditorGUILayout.GetControlRect();
+ EditorGUI.LabelField(labelRect, title, EditorStyles.boldLabel);
+
+ Rect countLabelRect = labelRect;
+ countLabelRect.xMin = countLabelRect.xMax - 25f;
+
+ GUI.enabled = false;
+ EditorGUI.IntField(countLabelRect, arraySize);
+ GUI.enabled = true;
+
+ EditorGUILayout.Space(2f);
+
+ return arraySize;
+ }
+
+ public static void EndDrawCustomList(GUIContent buttonTitle, bool buttonEnabled, Action onClick)
+ {
+ EditorGUILayout.Space(2f);
+ Separator();
+ EditorGUILayout.Space(2f);
+
+ EditorGUILayout.BeginHorizontal();
+ {
+ GUILayout.FlexibleSpace();
+ float width = GUI.skin.button.CalcSize(buttonTitle).x + 10f;
+ Rect moduleButtonRect = EditorGUILayout.GetControlRect(GUILayout.Width(width), GUILayout.Height(20f));
+
+ using (new EditorGUI.DisabledGroupScope(Application.isPlaying || !buttonEnabled))
+ {
+ if (GUI.Button(moduleButtonRect, buttonTitle))
+ {
+ onClick?.Invoke();
+ }
+ }
+
+ GUILayout.FlexibleSpace();
+ }
+ EditorGUILayout.EndHorizontal();
+
+ EditorGUILayout.EndVertical();
+ }
+
+ ///
+ /// Setup reorderable list for easy drawing.
+ ///
+ public static void SetupReorderableList(ReorderableList reorderableList)
+ {
+ SerializedProperty properties = reorderableList.serializedProperty;
+
+ reorderableList.elementHeightCallback += (int index) =>
+ {
+ SerializedProperty element = properties.GetArrayElementAtIndex(index);
+
+ int fieldCount = element.CountProperty();
+ if (element.isExpanded) return (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing) * (fieldCount + 1);
+
+ return EditorGUIUtility.singleLineHeight;
+ };
+
+ reorderableList.drawElementCallback += (Rect rect, int index, bool isActive, bool isFocused) =>
+ {
+ SerializedProperty element = properties.GetArrayElementAtIndex(index);
+
+ rect.y += 1f;
+ rect.height = EditorGUIUtility.singleLineHeight;
+ Rect propertyRect = new(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight);
+ propertyRect.xMin += 12f;
+
+ EditorGUI.PropertyField(propertyRect, element);
+
+ if (element.isExpanded)
+ {
+ EditorGUI.indentLevel++;
+
+ SerializedProperty childProperty = element.Copy();
+ SerializedProperty endProperty = element.GetEndProperty();
+
+ childProperty.NextVisible(true);
+ while (!SerializedProperty.EqualContents(childProperty, endProperty))
+ {
+ rect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
+ EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), childProperty);
+ childProperty.NextVisible(false);
+ }
+
+ EditorGUI.indentLevel--;
+ }
+ };
+ }
+
+
+ ///
+ /// Get count of properties.
+ ///
+ public static int CountProperty(this SerializedProperty property)
+ {
+ SerializedProperty copy = property.Copy();
+ SerializedProperty endProperty = property.GetEndProperty();
+ int count = 0;
+
+ copy.NextVisible(true);
+ while (!SerializedProperty.EqualContents(copy, endProperty))
+ {
+ count++;
+ copy.NextVisible(false);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Draw header by specified rect.
+ ///
+ public static void DrawHeader(Rect headerRect, GUIContent title, GUIStyle labelStyle = null)
+ {
+ Color headerColor = new Color(0.1f, 0.1f, 0.1f, 0.4f);
+ EditorGUI.DrawRect(headerRect, headerColor);
+
+ Rect labelRect = new Rect(headerRect.x + 4f, headerRect.y - 1f, headerRect.width - 4f, headerRect.height);
+ EditorGUI.LabelField(labelRect, title, labelStyle ?? Styles.miniBoldLabelCenter);
+ }
+
+ ///
+ /// Draw toggle header by specified rect.
+ ///
+ public static bool DrawToggleHeader(Rect headerRect, GUIContent title, bool toggle)
+ {
+ Color headerColor = new Color(0.1f, 0.1f, 0.1f, 0.4f);
+ EditorGUI.DrawRect(headerRect, headerColor);
+
+ Rect toggleRect = new Rect(headerRect.x + 4f, headerRect.y, EditorGUIUtility.singleLineHeight, headerRect.height);
+ toggle = GUI.Toggle(toggleRect, toggle, new GUIContent("", "Enabled"), EditorStyles.toggle);
+
+ Rect labelRect = new Rect(toggleRect.xMax, headerRect.y - 1f, headerRect.width - toggleRect.xMax, headerRect.height);
+ EditorGUI.LabelField(labelRect, title, Styles.miniBoldLabelCenter);
+
+ return toggle;
+ }
+
+ ///
+ /// Draw foldout header by specified rect.
+ ///
+ public static bool DrawFoldoutHeader(Rect headerRect, GUIContent title, bool expanded)
+ {
+ // Constants
+ Color headerColor = new Color(0.1f, 0.1f, 0.1f, 0.4f);
+ float singleLineHeight = EditorGUIUtility.singleLineHeight;
+
+ // Draw header background
+ EditorGUI.DrawRect(headerRect, headerColor);
+
+ // Define and draw foldout toggle
+ Rect foldoutRect = new Rect(headerRect.x + 4f, headerRect.y, singleLineHeight, headerRect.height);
+ GUI.Toggle(foldoutRect, expanded, GUIContent.none, EditorStyles.foldout);
+
+ // Define and draw title label
+ Rect labelRect = new Rect(foldoutRect.xMax, headerRect.y - 1f, headerRect.width - foldoutRect.xMax + 4f, headerRect.height);
+ EditorGUI.LabelField(labelRect, title, Styles.miniBoldLabelCenter);
+
+ // Handle mouse events for foldout interaction
+ headerRect.xMax -= singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
+ Event e = Event.current;
+ if (headerRect.Contains(e.mousePosition) && e.type == EventType.MouseDown && e.button == 0)
+ {
+ expanded = !expanded;
+ e.Use();
+ }
+
+ return expanded;
+ }
+
+ ///
+ /// Draw foldout header by specified rect.
+ ///
+ public static bool DrawFoldoutHeader(Rect headerRect, GUIContent title, float minusWidth, bool expanded)
+ {
+ // Constants
+ Color headerColor = new Color(0.1f, 0.1f, 0.1f, 0.4f);
+ float singleLineHeight = EditorGUIUtility.singleLineHeight;
+
+ // Draw header background
+ EditorGUI.DrawRect(headerRect, headerColor);
+
+ // Draw foldout
+ Rect foldoutRect = new Rect(headerRect.x + 4f, headerRect.y, singleLineHeight, headerRect.height);
+ GUI.Toggle(foldoutRect, expanded, GUIContent.none, EditorStyles.foldout);
+
+ // Draw title
+ Rect labelRect = new Rect(foldoutRect.xMax, headerRect.y - 1f, headerRect.width - foldoutRect.xMax, headerRect.height);
+ EditorGUI.LabelField(labelRect, title, Styles.miniBoldLabelCenter);
+
+ // Handle mouse events
+ headerRect.xMax -= singleLineHeight + EditorGUIUtility.standardVerticalSpacing + minusWidth;
+ Event e = Event.current;
+ if (headerRect.Contains(e.mousePosition) && e.type == EventType.MouseDown && e.button == 0)
+ {
+ expanded = !expanded;
+ e.Use();
+ }
+
+ return expanded;
+ }
+
+ ///
+ /// Draw foldout toggle header by specified rect.
+ ///
+ public static void DrawFoldoutToggleHeader(Rect headerRect, GUIContent title, ref bool expanded, ref bool toggle, bool canExpand = true)
+ {
+ // Constants
+ float singleLineHeight = EditorGUIUtility.singleLineHeight;
+ Color headerColor = new Color(0.1f, 0.1f, 0.1f, 0.4f);
+
+ // Draw header background
+ EditorGUI.DrawRect(headerRect, headerColor);
+
+ // Set up initial positions
+ Rect foldoutRect = headerRect;
+ foldoutRect.width = singleLineHeight;
+ foldoutRect.x += 4f;
+
+ // If expandable, draw foldout and adjust for toggle position
+ if (canExpand)
+ {
+ GUI.Toggle(foldoutRect, expanded, GUIContent.none, EditorStyles.foldout);
+ foldoutRect.x += singleLineHeight;
+ }
+
+ // Draw toggle
+ Rect toggleRect = new Rect(foldoutRect.x, headerRect.y, singleLineHeight, headerRect.height);
+ toggle = GUI.Toggle(toggleRect, toggle, new GUIContent("", "Enabled"), EditorStyles.toggle);
+
+ // Draw title
+ Rect labelRect = new Rect(toggleRect.xMax, headerRect.y - 1f, headerRect.width - toggleRect.xMax, headerRect.height);
+ EditorGUI.LabelField(labelRect, title, Styles.miniBoldLabelCenter);
+
+ // Handle events
+ headerRect.xMax -= singleLineHeight + 2f;
+ Event e = Event.current;
+ if (canExpand && headerRect.Contains(e.mousePosition) && e.type == EventType.MouseDown && e.button == 0)
+ {
+ expanded = !expanded;
+ e.Use();
+ }
+ }
+
+ ///
+ /// Draw header with border.
+ ///
+ public static Rect DrawHeaderWithBorder(ref Rect rect, GUIContent title, float headerHeight = 18f, bool roundedBox = true)
+ {
+ GUI.Box(rect, GUIContent.none, new GUIStyle(roundedBox ? "HelpBox" : "Tooltip"));
+ rect.x += 1;
+ rect.y += 1;
+ rect.height -= 1;
+ rect.width -= 2;
+
+ Rect headerRect = rect;
+ headerRect.height = headerHeight + EditorGUIUtility.standardVerticalSpacing;
+
+ rect.y += headerRect.height;
+ rect.height -= headerRect.height;
+
+ Rect titleRect = headerRect;
+ titleRect.x += 2f;
+
+ using (new IconSizeScope(14))
+ {
+ EditorGUI.DrawRect(headerRect, new Color(0.1f, 0.1f, 0.1f, 0.4f));
+ EditorGUI.LabelField(titleRect, title, EditorStyles.miniBoldLabel);
+ }
+
+ return headerRect;
+ }
+
+ ///
+ /// Draw header with border.
+ ///
+ public static Rect DrawHeaderWithBorder(GUIContent title, float headerHeight = 18f, bool roundedBox = true)
+ {
+ // Calculate the rectangle for the header based on the control rect
+ Rect rect = EditorGUILayout.GetControlRect(false, headerHeight);
+
+ // Adjust the rect for the border drawing
+ Rect borderRect = rect;
+ borderRect.height = headerHeight + EditorGUIUtility.standardVerticalSpacing;
+
+ // Draw the border box
+ GUI.Box(borderRect, GUIContent.none, new GUIStyle(roundedBox ? "HelpBox" : "Tooltip"));
+
+ // Adjust the rect for the title inside the header
+ Rect titleRect = rect;
+ titleRect.x += 4f;
+ titleRect.y += 2f;
+ titleRect.height = headerHeight;
+
+ // Draw the title background
+ EditorGUI.DrawRect(borderRect, new Color(0.1f, 0.1f, 0.1f, 0.4f));
+
+ // Draw the title text
+ using (new IconSizeScope(14))
+ {
+ EditorGUI.LabelField(titleRect, title, EditorStyles.miniBoldLabel);
+ }
+
+ // Return the rect for further custom drawing if needed
+ return rect;
+ }
+
+ ///
+ /// Begin a bordered vertical group;
+ ///
+ public static void BeginBorderLayout(bool roundedBox = true)
+ {
+ Rect drawingRect = EditorGUILayout.BeginVertical(Styles.borderBoxStyle);
+ GUI.Box(drawingRect, GUIContent.none, new GUIStyle(roundedBox ? "HelpBox" : "Tooltip"));
+ }
+
+ ///
+ /// Begin a bordered vertical header group;
+ ///
+ public static Rect BeginHeaderBorderLayout(GUIContent title, float headerHeight = 18f, bool roundedBox = true)
+ {
+ Rect headerRect = EditorGUILayout.GetControlRect(false, headerHeight + 4f);
+ Rect drawingRect = EditorGUILayout.BeginVertical(Styles.borderBoxHeaderStyle);
+ drawingRect.yMin -= headerHeight + 6f;
+
+ GUI.Box(drawingRect, GUIContent.none, new GUIStyle(roundedBox ? "HelpBox" : "Tooltip"));
+ DrawHeader(headerRect, title);
+ return headerRect;
+ }
+
+ ///
+ /// Begin a bordered vertical foldout group.
+ ///
+ public static bool BeginFoldoutBorderLayout(GUIContent title, ref bool expanded, float headerHeight = 18f, bool roundedBox = true)
+ {
+ Rect headerRect = EditorGUILayout.GetControlRect(false, headerHeight + 4f);
+ Rect boxRect = headerRect;
+ bool foldoutResult = expanded;
+
+ if (expanded)
+ {
+ Rect drawingRect = EditorGUILayout.BeginVertical(Styles.borderBoxHeaderStyle);
+ boxRect.yMax = drawingRect.yMax;
+ }
+
+ GUI.Box(boxRect, GUIContent.none, new GUIStyle(roundedBox ? "HelpBox" : "Tooltip"));
+ expanded = DrawFoldoutHeader(headerRect, title, expanded);
+ return foldoutResult;
+ }
+
+ ///
+ /// Begin a bordered vertical foldout group.
+ ///
+ public static bool BeginFoldoutBorderLayout(GUIContent title, bool expanded, float headerHeight = 18f, bool roundedBox = true)
+ {
+ Rect headerRect = EditorGUILayout.GetControlRect(false, headerHeight + 4f);
+ Rect boxRect = headerRect;
+
+ if (expanded)
+ {
+ Rect drawingRect = EditorGUILayout.BeginVertical(Styles.borderBoxHeaderStyle);
+ boxRect.yMax = drawingRect.yMax;
+ }
+
+ GUI.Box(boxRect, GUIContent.none, new GUIStyle(roundedBox ? "HelpBox" : "Tooltip"));
+ return DrawFoldoutHeader(headerRect, title, expanded);
+ }
+
+ ///
+ /// Begin a bordered vertical foldout group.
+ ///
+ public static bool BeginFoldoutBorderLayout(GUIContent title, ref bool expanded, out Rect headerRect, float headerHeight = 18f, bool roundedBox = true)
+ {
+ headerRect = EditorGUILayout.GetControlRect(false, headerHeight + 4f);
+ Rect boxRect = headerRect;
+ bool foldoutResult = expanded;
+
+ if (expanded)
+ {
+ Rect drawingRect = EditorGUILayout.BeginVertical(Styles.borderBoxHeaderStyle);
+ boxRect.yMax = drawingRect.yMax;
+ }
+
+ GUI.Box(boxRect, GUIContent.none, new GUIStyle(roundedBox ? "HelpBox" : "Tooltip"));
+ expanded = DrawFoldoutHeader(headerRect, title, expanded);
+ return foldoutResult;
+ }
+
+ ///
+ /// Begin a bordered vertical foldout group.
+ ///
+ public static bool BeginFoldoutBorderLayout(SerializedProperty foldoutProperty, GUIContent title, float headerHeight = 18f, bool roundedBox = true)
+ {
+ Rect headerRect = EditorGUILayout.GetControlRect(false, headerHeight + 4f);
+ Rect boxRect = headerRect;
+ bool foldoutResult = foldoutProperty.isExpanded;
+
+ if (foldoutResult)
+ {
+ Rect drawingRect = EditorGUILayout.BeginVertical(Styles.borderBoxHeaderStyle);
+ boxRect.yMax = drawingRect.yMax;
+ }
+
+ GUI.Box(boxRect, GUIContent.none, new GUIStyle(roundedBox ? "HelpBox" : "Tooltip"));
+ foldoutProperty.isExpanded = DrawFoldoutHeader(headerRect, title, foldoutProperty.isExpanded);
+ return foldoutResult;
+ }
+
+ ///
+ /// Begin a bordered vertical foldout group.
+ ///
+ public static bool BeginFoldoutBorderLayout(SerializedProperty foldoutProperty, GUIContent title, out Rect headerRect, float headerHeight = 18f, bool roundedBox = true)
+ {
+ headerRect = EditorGUILayout.GetControlRect(false, headerHeight + 4f);
+ Rect boxRect = headerRect;
+ bool foldoutResult = foldoutProperty.isExpanded;
+
+ if (foldoutResult)
+ {
+ Rect drawingRect = EditorGUILayout.BeginVertical(Styles.borderBoxHeaderStyle);
+ boxRect.yMax = drawingRect.yMax;
+ }
+
+ GUI.Box(boxRect, GUIContent.none, new GUIStyle(roundedBox ? "HelpBox" : "Tooltip"));
+ foldoutProperty.isExpanded = DrawFoldoutHeader(headerRect, title, foldoutProperty.isExpanded);
+ return foldoutResult;
+ }
+
+ ///
+ /// Begin a bordered vertical foldout group.
+ ///
+ public static bool BeginFoldoutBorderLayout(SerializedProperty foldoutProperty, GUIContent title, out Rect headerRect, float minusWidth, float headerHeight = 18f, bool roundedBox = true)
+ {
+ headerRect = EditorGUILayout.GetControlRect(false, headerHeight + 4f);
+ Rect boxRect = headerRect;
+ bool foldoutResult = foldoutProperty.isExpanded;
+
+ if (foldoutResult)
+ {
+ Rect drawingRect = EditorGUILayout.BeginVertical(Styles.borderBoxHeaderStyle);
+ boxRect.yMax = drawingRect.yMax;
+ }
+
+ GUI.Box(boxRect, GUIContent.none, new GUIStyle(roundedBox ? "HelpBox" : "Tooltip"));
+ foldoutProperty.isExpanded = DrawFoldoutHeader(headerRect, title, minusWidth, foldoutProperty.isExpanded);
+ return foldoutResult;
+ }
+
+ ///
+ /// Begin a bordered vertical toggle group.
+ ///
+ public static bool BeginToggleBorderLayout(GUIContent title, bool toggle, float headerHeight = 18f, bool roundedBox = true)
+ {
+ Rect headerRect = EditorGUILayout.GetControlRect(false, headerHeight + 4f);
+ Rect drawingRect = EditorGUILayout.BeginVertical(Styles.borderBoxHeaderStyle);
+ drawingRect.yMin -= headerHeight + 6f;
+
+ GUI.Box(drawingRect, GUIContent.none, new GUIStyle(roundedBox ? "HelpBox" : "Tooltip"));
+ return DrawToggleHeader(headerRect, title, toggle);
+ }
+
+ ///
+ /// Begin a bordered vertical toggle group.
+ ///
+ public static bool BeginToggleBorderLayout(GUIContent title, bool toggle, out Rect headerRect, float headerHeight = 18f, bool roundedBox = true)
+ {
+ headerRect = EditorGUILayout.GetControlRect(false, headerHeight + 4f);
+ Rect drawingRect = EditorGUILayout.BeginVertical(Styles.borderBoxHeaderStyle);
+ drawingRect.yMin -= headerHeight + 6f;
+
+ GUI.Box(drawingRect, GUIContent.none, new GUIStyle(roundedBox ? "HelpBox" : "Tooltip"));
+ return DrawToggleHeader(headerRect, title, toggle);
+ }
+
+ ///
+ /// Begin a bordered vertical foldout toggle group.
+ ///
+ public static bool BeginFoldoutToggleBorderLayout(GUIContent title, ref bool expanded, ref bool toggle, float headerHeight = 18f, bool roundedBox = true, bool canExpand = true)
+ {
+ Rect headerRect = EditorGUILayout.GetControlRect(false, headerHeight + 4f);
+ Rect boxRect = headerRect;
+ bool foldoutResult = canExpand && expanded;
+
+ if (foldoutResult)
+ {
+ Rect drawingRect = EditorGUILayout.BeginVertical(Styles.borderBoxHeaderStyle);
+ boxRect.yMax = drawingRect.yMax;
+ }
+
+ GUI.Box(boxRect, GUIContent.none, new GUIStyle(roundedBox ? "HelpBox" : "Tooltip"));
+ DrawFoldoutToggleHeader(headerRect, title, ref expanded, ref toggle, canExpand);
+ return foldoutResult;
+ }
+
+ ///
+ /// Begin a bordered vertical foldout toggle group.
+ ///
+ public static bool BeginFoldoutToggleBorderLayout(GUIContent title, ref bool expanded, ref bool toggle, out Rect headerRect, float headerHeight = 18f, bool roundedBox = true, bool canExpand = true)
+ {
+ headerRect = EditorGUILayout.GetControlRect(false, headerHeight + 4f);
+ Rect boxRect = headerRect;
+ bool foldoutResult = canExpand && expanded;
+
+ if (foldoutResult)
+ {
+ Rect drawingRect = EditorGUILayout.BeginVertical(Styles.borderBoxHeaderStyle);
+ boxRect.yMax = drawingRect.yMax;
+ }
+
+ GUI.Box(boxRect, GUIContent.none, new GUIStyle(roundedBox ? "HelpBox" : "Tooltip"));
+ DrawFoldoutToggleHeader(headerRect, title, ref expanded, ref toggle, canExpand);
+ return foldoutResult;
+ }
+
+ ///
+ /// Begin a bordered vertical foldout toggle group.
+ ///
+ public static bool BeginFoldoutToggleBorderLayout(SerializedProperty foldoutProperty, GUIContent title, ref bool toggle, float headerHeight = 18f, bool roundedBox = true, bool canExpand = true)
+ {
+ Rect headerRect = EditorGUILayout.GetControlRect(false, headerHeight + 4f);
+ Rect boxRect = headerRect;
+
+ bool expanded = canExpand && foldoutProperty.isExpanded;
+ bool foldoutResult = expanded;
+
+ if (foldoutResult)
+ {
+ Rect drawingRect = EditorGUILayout.BeginVertical(Styles.borderBoxHeaderStyle);
+ boxRect.yMax = drawingRect.yMax;
+ }
+
+ GUI.Box(boxRect, GUIContent.none, new GUIStyle(roundedBox ? "HelpBox" : "Tooltip"));
+ DrawFoldoutToggleHeader(headerRect, title, ref expanded, ref toggle, canExpand);
+ foldoutProperty.isExpanded = expanded;
+ return foldoutResult;
+ }
+
+ ///
+ /// Begin a bordered vertical foldout toggle group.
+ ///
+ public static bool BeginFoldoutToggleBorderLayout(GUIContent title, SerializedProperty toggleProperty, float headerHeight = 18f, bool roundedBox = true, bool canExpand = true)
+ {
+ Rect headerRect = EditorGUILayout.GetControlRect(false, headerHeight + 4f);
+ Rect boxRect = headerRect;
+
+ bool expanded = canExpand && toggleProperty.isExpanded;
+ bool toggle = toggleProperty.boolValue;
+ bool foldoutResult = expanded;
+
+ if (foldoutResult)
+ {
+ Rect drawingRect = EditorGUILayout.BeginVertical(Styles.borderBoxHeaderStyle);
+ boxRect.yMax = drawingRect.yMax;
+ }
+
+ GUI.Box(boxRect, GUIContent.none, new GUIStyle(roundedBox ? "HelpBox" : "Tooltip"));
+ DrawFoldoutToggleHeader(headerRect, title, ref expanded, ref toggle, canExpand);
+ toggleProperty.boolValue = toggle;
+ toggleProperty.isExpanded = expanded;
+ return foldoutResult;
+ }
+
+ ///
+ /// Begin a bordered vertical foldout toggle group.
+ ///
+ public static bool BeginFoldoutToggleBorderLayout(GUIContent title, SerializedProperty toggleProperty, out Rect headerRect, float headerHeight = 18f, bool roundedBox = true, bool canExpand = true)
+ {
+ headerRect = EditorGUILayout.GetControlRect(false, headerHeight + 4f);
+ Rect boxRect = headerRect;
+
+ bool expanded = canExpand && toggleProperty.isExpanded;
+ bool toggle = toggleProperty.boolValue;
+ bool foldoutResult = expanded;
+
+ if (foldoutResult)
+ {
+ Rect drawingRect = EditorGUILayout.BeginVertical(Styles.borderBoxHeaderStyle);
+ boxRect.yMax = drawingRect.yMax;
+ }
+
+ GUI.Box(boxRect, GUIContent.none, new GUIStyle(roundedBox ? "HelpBox" : "Tooltip"));
+ DrawFoldoutToggleHeader(headerRect, title, ref expanded, ref toggle, canExpand);
+ toggleProperty.boolValue = toggle;
+ toggleProperty.isExpanded = expanded;
+ return foldoutResult;
+ }
+
+ ///
+ /// Begin a bordered vertical foldout toggle group.
+ ///
+ public static bool BeginFoldoutToggleBorderLayout(SerializedProperty foldoutProperty, GUIContent title, ref bool toggle, out Rect headerRect, float headerHeight = 18f, bool roundedBox = true, bool canExpand = true)
+ {
+ headerRect = EditorGUILayout.GetControlRect(false, headerHeight + 4f);
+ Rect boxRect = headerRect;
+
+ bool expanded = canExpand && foldoutProperty.isExpanded;
+ bool foldoutResult = expanded;
+
+ if (foldoutResult)
+ {
+ Rect drawingRect = EditorGUILayout.BeginVertical(Styles.borderBoxHeaderStyle);
+ boxRect.yMax = drawingRect.yMax;
+ }
+
+ GUI.Box(boxRect, GUIContent.none, new GUIStyle(roundedBox ? "HelpBox" : "Tooltip"));
+ DrawFoldoutToggleHeader(headerRect, title, ref expanded, ref toggle, canExpand);
+ foldoutProperty.isExpanded = expanded;
+ return foldoutResult;
+ }
+
+ ///
+ /// End a bordered vertical group.
+ ///
+ public static void EndBorderHeaderLayout()
+ {
+ EditorGUILayout.EndVertical();
+ }
+
+ ///
+ /// Draw all class property childs inside bordered foldout.
+ ///
+ public static Rect DrawClassBorderFoldout(SerializedProperty classProperty, GUIContent title, float headerHeight = 18f, bool roundedBox = true)
+ {
+ var classChildrens = classProperty.GetVisibleChildrens();
+ if (BeginFoldoutBorderLayout(classProperty, title, out Rect headerRect, headerHeight, roundedBox))
+ {
+ foreach (var child in classChildrens)
+ {
+ EditorGUI.BeginChangeCheck();
+ {
+ bool isArray = IsArray(child);
+
+ if (isArray) EditorGUI.indentLevel++;
+ {
+ EditorGUILayout.PropertyField(child, true);
+ }
+ if (isArray) EditorGUI.indentLevel--;
+ }
+ if (EditorGUI.EndChangeCheck())
+ {
+ classProperty.serializedObject.ApplyModifiedProperties();
+ }
+ }
+
+ EndBorderHeaderLayout();
+ }
+
+ return headerRect;
+ }
+
+ ///
+ /// Draw all class property childs inside bordered foldout.
+ ///
+ public static Rect DrawClassBorderFoldout(SerializedProperty classProperty, GUIContent title, ref bool expanded, float headerHeight = 18f, bool roundedBox = true)
+ {
+ var classChildrens = classProperty.GetVisibleChildrens();
+ if (BeginFoldoutBorderLayout(title, ref expanded, out Rect headerRect, headerHeight, roundedBox))
+ {
+ foreach (var child in classChildrens)
+ {
+ EditorGUI.BeginChangeCheck();
+ {
+ bool isArray = IsArray(child);
+
+ if (isArray) EditorGUI.indentLevel++;
+ {
+ EditorGUILayout.PropertyField(child, true);
+ }
+ if (isArray) EditorGUI.indentLevel--;
+ }
+ if (EditorGUI.EndChangeCheck())
+ {
+ classProperty.serializedObject.ApplyModifiedProperties();
+ }
+ }
+
+ EndBorderHeaderLayout();
+ }
+
+ return headerRect;
+ }
+
+ ///
+ /// Draw scriptable property childs inside bordered foldout.
+ ///
+ public static Rect DrawScriptableBorderFoldout(SerializedProperty scriptableProperty, GUIContent title, float headerHeight = 18f, bool roundedBox = true)
+ {
+ SerializedObject serializedObject = scriptableProperty.propertyType == SerializedPropertyType.ObjectReference
+ ? new SerializedObject(scriptableProperty.objectReferenceValue)
+ : scriptableProperty.serializedObject;
+
+ SerializedProperty iterator = serializedObject.GetIterator();
+ HasIteratorChilds(iterator);
+
+ if (BeginFoldoutBorderLayout(scriptableProperty, title, out Rect headerRect, headerHeight, roundedBox))
+ {
+ do
+ {
+ EditorGUI.BeginChangeCheck();
+ {
+ SerializedProperty child = serializedObject.FindProperty(iterator.name);
+ bool isArray = IsArray(child);
+
+ if (isArray) EditorGUI.indentLevel++;
+ {
+ EditorGUILayout.PropertyField(child, true);
+ }
+ if (isArray) EditorGUI.indentLevel--;
+ }
+ if (EditorGUI.EndChangeCheck())
+ {
+ serializedObject.ApplyModifiedProperties();
+ }
+ } while (iterator.NextVisible(false));
+
+ EndBorderHeaderLayout();
+ }
+
+ return headerRect;
+ }
+
+ ///
+ /// Draw scriptable property childs inside bordered foldout toggle.
+ ///
+ public static Rect DrawScriptableBorderFoldoutToggle(SerializedProperty scriptableProperty, GUIContent title, ref bool toggle, float headerHeight = 18f, bool roundedBox = true)
+ {
+ SerializedObject serializedObject = scriptableProperty.propertyType == SerializedPropertyType.ObjectReference
+ ? new SerializedObject(scriptableProperty.objectReferenceValue)
+ : scriptableProperty.serializedObject;
+
+ SerializedProperty iterator = serializedObject.GetIterator();
+ bool hasChilds = HasIteratorChilds(iterator);
+
+ if (BeginFoldoutToggleBorderLayout(scriptableProperty, title, ref toggle, out Rect headerRect, headerHeight, roundedBox, canExpand: hasChilds))
+ {
+ if (hasChilds)
+ {
+ do
+ {
+ EditorGUI.BeginChangeCheck();
+ {
+ SerializedProperty child = serializedObject.FindProperty(iterator.name);
+ bool isArray = IsArray(child);
+
+ if (isArray) EditorGUI.indentLevel++;
+ {
+ EditorGUILayout.PropertyField(child, true);
+ }
+ if (isArray) EditorGUI.indentLevel--;
+ }
+ if (EditorGUI.EndChangeCheck())
+ {
+ serializedObject.ApplyModifiedProperties();
+ }
+ } while (iterator.NextVisible(false));
+ }
+
+ EndBorderHeaderLayout();
+ }
+
+ return headerRect;
+ }
+
+ ///
+ /// Draw scriptable property childs inside bordered foldout toggle.
+ ///
+ public static Rect DrawScriptableBorderFoldoutToggle(SerializedProperty scriptableProperty, GUIContent title, ref bool expanded, ref bool toggle, float headerHeight = 18f, bool roundedBox = true)
+ {
+ SerializedObject serializedObject = scriptableProperty.propertyType == SerializedPropertyType.ObjectReference
+ ? new SerializedObject(scriptableProperty.objectReferenceValue)
+ : scriptableProperty.serializedObject;
+
+ SerializedProperty iterator = serializedObject.GetIterator();
+ bool hasChilds = HasIteratorChilds(iterator);
+
+ if (BeginFoldoutToggleBorderLayout(title, ref expanded, ref toggle, out Rect headerRect, headerHeight, roundedBox, canExpand: hasChilds))
+ {
+ if (hasChilds)
+ {
+ do
+ {
+ EditorGUI.BeginChangeCheck();
+ {
+ SerializedProperty child = serializedObject.FindProperty(iterator.name);
+ bool isArray = IsArray(child);
+
+ if (isArray) EditorGUI.indentLevel++;
+ {
+ EditorGUILayout.PropertyField(child, true);
+ }
+ if (isArray) EditorGUI.indentLevel--;
+ }
+ if (EditorGUI.EndChangeCheck())
+ {
+ serializedObject.ApplyModifiedProperties();
+ }
+ } while (iterator.NextVisible(false));
+ }
+
+ EndBorderHeaderLayout();
+ }
+
+ return headerRect;
+ }
+
+ ///
+ /// Create a new texture with the given dimensions and color.
+ ///
+ public static Texture2D MakeTexture(int width, int height, Color color)
+ {
+ Color[] pix = new Color[width * height];
+
+ for (int i = 0; i < pix.Length; i++)
+ pix[i] = color;
+
+ Texture2D result = new Texture2D(width, height);
+ result.SetPixels(pix);
+ result.Apply();
+
+ return result;
+ }
+
+ ///
+ /// Draw texture with a transprent background.
+ ///
+ public static void DrawTransparentTexture(Rect rect, Texture image)
+ {
+ Color guiColor = GUI.color;
+ GUI.color = Color.clear;
+ EditorGUI.DrawTextureTransparent(rect, image, ScaleMode.ScaleToFit);
+ GUI.color = guiColor;
+ }
+
+ ///
+ /// Draw texture with a transprent background.
+ ///
+ public static void DrawTransparentTextureWithChecker(Rect rect, Object spriteObj)
+ {
+ GUI.DrawTexture(rect, Styles.TransparentCheckerTexture, ScaleMode.ScaleToFit, alphaBlend: false);
+ if (spriteObj != null) GUI.DrawTexture(rect, ((Sprite)spriteObj).texture, ScaleMode.ScaleToFit, alphaBlend: true);
+ }
+
+ public static PropertyCollection GetAllProperties(SerializedProperty classProperty)
+ {
+ PropertyCollection properties = new();
+
+ SerializedProperty currentProperty = classProperty.Copy();
+ SerializedProperty nextSiblingProperty = classProperty.Copy();
+ nextSiblingProperty.NextVisible(false);
+
+ if (currentProperty.NextVisible(true))
+ {
+ do
+ {
+ if (SerializedProperty.EqualContents(currentProperty, nextSiblingProperty))
+ break;
+
+ properties.Add(currentProperty.name, currentProperty.Copy());
+ } while (currentProperty.NextVisible(false));
+ }
+
+ return properties;
+ }
+
+ public static PropertyCollection GetAllProperties(SerializedObject serializedObject)
+ {
+ PropertyCollection properties = new();
+
+ SerializedProperty property = serializedObject.GetIterator();
+ SerializedProperty currentProperty = property.Copy();
+ SerializedProperty nextSiblingProperty = property.Copy();
+
+ if (currentProperty.NextVisible(true))
+ {
+ do
+ {
+ if (SerializedProperty.EqualContents(currentProperty, nextSiblingProperty))
+ break;
+
+ properties.Add(currentProperty.name, currentProperty.Copy());
+ } while (currentProperty.NextVisible(false));
+ }
+
+ return properties;
+ }
+
+ public static bool HasIteratorChilds(SerializedProperty iterator)
+ {
+ return iterator != null && iterator.NextVisible(true) && iterator.NextVisible(false);
+ }
+
+ public static bool IsArray(SerializedProperty property)
+ {
+ return property.isArray && property.propertyType != SerializedPropertyType.String;
+ }
+
+ public static float GetPropertyChildHeight(SerializedProperty iterator)
+ {
+ SerializedProperty ite = iterator.Copy();
+ float totalHeight = EditorGUI.GetPropertyHeight(ite, true);
+
+ while (ite.NextVisible(false))
+ {
+ totalHeight += EditorGUIUtility.standardVerticalSpacing;
+ totalHeight += EditorGUI.GetPropertyHeight(ite, true);
+ }
+
+ return totalHeight;
+ }
+ }
+
+ public static class SerializedPropertyE
+ {
+ public static void DrawRelative(this SerializedProperty property, string propertyName)
+ {
+ SerializedProperty relative = property.FindPropertyRelative(propertyName);
+ EditorGUILayout.PropertyField(relative);
+ }
+
+ public static void DrawRelative(this SerializedProperty property, string propertyName, GUIContent label)
+ {
+ SerializedProperty relative = property.FindPropertyRelative(propertyName);
+ EditorGUILayout.PropertyField(relative, label);
+ }
+
+ public static SerializedProperty Relative(this SerializedProperty property, string propertyName)
+ {
+ return property.FindPropertyRelative(propertyName);
+ }
+
+ public static SerializedProperty Find(this SerializedProperty property, string path)
+ {
+ if (property == null)
+ throw new ArgumentNullException(nameof(property));
+
+ if (string.IsNullOrEmpty(path))
+ throw new ArgumentException("Path cannot be null or empty", nameof(path));
+
+ string[] elements = path.Split('.');
+ SerializedProperty currentProperty = property;
+
+ foreach (var element in elements)
+ {
+ currentProperty = currentProperty.FindPropertyRelative(element);
+ if (currentProperty == null)
+ throw new ArgumentException($"Property not found in path: {path}", nameof(path));
+ }
+
+ return currentProperty;
+ }
+ }
+
+ public static class AdvancedDropdownExtensions
+ {
+ public static void Show(this AdvancedDropdown dropdown, Rect buttonRect, float maxHeight)
+ {
+ dropdown.Show(buttonRect);
+ SetMaxHeightForOpenedPopup(buttonRect, maxHeight);
+ }
+
+ private static void SetMaxHeightForOpenedPopup(Rect buttonRect, float maxHeight)
+ {
+ var window = EditorWindow.focusedWindow;
+
+ if (window == null)
+ {
+ Debug.LogWarning("EditorWindow.focusedWindow was null.");
+ return;
+ }
+
+ if (!string.Equals(window.GetType().Namespace, typeof(AdvancedDropdown).Namespace))
+ {
+ Debug.LogWarning("EditorWindow.focusedWindow " + EditorWindow.focusedWindow.GetType().FullName + " was not in expected namespace.");
+ return;
+ }
+
+ var position = window.position;
+ if (position.height <= maxHeight)
+ {
+ return;
+ }
+
+ position.height = maxHeight;
+ window.minSize = position.size;
+ window.maxSize = position.size;
+ window.position = position;
+ window.ShowAsDropDown(GUIUtility.GUIToScreenRect(buttonRect), position.size);
+ }
+ }
+}
diff --git a/Editor/Utility/EditorDrawing.cs.meta b/Editor/Utility/EditorDrawing.cs.meta
new file mode 100644
index 0000000..fb659d9
--- /dev/null
+++ b/Editor/Utility/EditorDrawing.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: cb3982e0339e441fbb4134b5be9094f0
+timeCreated: 1758265207
\ No newline at end of file
diff --git a/Editor/Utility/EditorUtils.cs b/Editor/Utility/EditorUtils.cs
new file mode 100644
index 0000000..ce737f4
--- /dev/null
+++ b/Editor/Utility/EditorUtils.cs
@@ -0,0 +1,519 @@
+using System.Collections.Generic;
+using System.Reflection;
+using System;
+using UnityEngine;
+using UnityEditor;
+
+namespace AlicizaX.Editor
+{
+ public static class EditorUtils
+ {
+ public static class Styles
+ {
+ public static GUIStyle IconButton => GUI.skin.FindStyle("IconButton");
+ public static readonly GUIContent PlusIcon = EditorGUIUtility.TrIconContent("Toolbar Plus", "Add Item");
+ public static readonly GUIContent MinusIcon = EditorGUIUtility.TrIconContent("Toolbar Minus", "Remove Item");
+ public static readonly GUIContent TrashIcon = EditorGUIUtility.TrIconContent("TreeEditor.Trash", "Remove Item");
+ public static readonly GUIContent RefreshIcon = EditorGUIUtility.TrIconContent("Refresh", "Refresh");
+ public static readonly GUIContent Linked = EditorGUIUtility.TrIconContent("Linked");
+ public static readonly GUIContent UnLinked = EditorGUIUtility.TrIconContent("Unlinked");
+ public static readonly GUIContent Database = EditorGUIUtility.TrIconContent("Package Manager");
+ public static readonly GUIContent GreenLight = EditorGUIUtility.TrIconContent("greenLight");
+ public static readonly GUIContent OrangeLight = EditorGUIUtility.TrIconContent("orangeLight");
+ public static readonly GUIContent RedLight = EditorGUIUtility.TrIconContent("redLight");
+
+ public static GUIStyle RichLabel => new GUIStyle(EditorStyles.label)
+ {
+ richText = true
+ };
+ }
+
+ public class BoxGroupScope : GUI.Scope
+ {
+ public BoxGroupScope(string icon, string title, float height = 22)
+ {
+ GUIContent iconTitle = EditorGUIUtility.TrTextContentWithIcon(" " + title, icon);
+ EditorGUILayout.BeginVertical(GUI.skin.box);
+
+ Rect headerRect = GUILayoutUtility.GetRect(1, height);
+ EditorGUI.DrawRect(headerRect, new Color(0.1f, 0.1f, 0.1f, 0.4f));
+
+ headerRect.x += EditorGUIUtility.standardVerticalSpacing;
+ EditorGUI.LabelField(headerRect, iconTitle, EditorStyles.boldLabel);
+
+ EditorGUILayout.Space(EditorGUIUtility.standardVerticalSpacing);
+ }
+
+ public BoxGroupScope(string title, float height = 22)
+ {
+ EditorGUILayout.BeginVertical(GUI.skin.box);
+
+ Rect headerRect = GUILayoutUtility.GetRect(1, height);
+ EditorGUI.DrawRect(headerRect, new Color(0.1f, 0.1f, 0.1f, 0.4f));
+
+ headerRect.x += EditorGUIUtility.standardVerticalSpacing;
+ EditorGUI.LabelField(headerRect, title, EditorStyles.boldLabel);
+
+ EditorGUILayout.Space(EditorGUIUtility.standardVerticalSpacing);
+ }
+
+ protected override void CloseScope()
+ {
+ EditorGUILayout.EndVertical();
+ }
+ }
+
+ public struct PopupArray
+ {
+ public List contents;
+ public List selected;
+
+ public PopupArray(string[] contents, string[] selected)
+ {
+ this.contents = new List(contents);
+ this.selected = new List(selected);
+ }
+ }
+
+ public sealed class PopupElement
+ {
+ public Action onSelect;
+ public PopupArray popupArray;
+ public string name;
+
+ public PopupElement(PopupArray popupArray, string name, Action onSelect)
+ {
+ this.popupArray = popupArray;
+ this.name = name;
+ this.onSelect = onSelect;
+ }
+ }
+
+ public static void DrawOutline(Rect rect, RectOffset border)
+ {
+ Color color = new Color(0.6f, 0.6f, 0.6f, 1.333f);
+ if (EditorGUIUtility.isProSkin)
+ {
+ color.r = 0.12f;
+ color.g = 0.12f;
+ color.b = 0.12f;
+ }
+
+ if (Event.current.type != EventType.Repaint)
+ return;
+
+ Color orgColor = GUI.color;
+ GUI.color *= color;
+ GUI.DrawTexture(new Rect(rect.x, rect.y, rect.width, border.top), EditorGUIUtility.whiteTexture); //top
+ GUI.DrawTexture(new Rect(rect.x, rect.yMax - border.bottom, rect.width, border.bottom), EditorGUIUtility.whiteTexture); //bottom
+ GUI.DrawTexture(new Rect(rect.x, rect.y + 1, border.left, rect.height - 2 * border.left), EditorGUIUtility.whiteTexture); //left
+ GUI.DrawTexture(new Rect(rect.xMax - border.right, rect.y + 1, border.right, rect.height - 2 * border.right), EditorGUIUtility.whiteTexture); //right
+
+ GUI.color = orgColor;
+ }
+
+ public static void DrawOutline(Rect rect, RectOffset border, Color color)
+ {
+ if (Event.current.type != EventType.Repaint)
+ return;
+
+ Color orgColor = GUI.color;
+ GUI.color *= color;
+ GUI.DrawTexture(new Rect(rect.x, rect.y, rect.width, border.top), EditorGUIUtility.whiteTexture); //top
+ GUI.DrawTexture(new Rect(rect.x, rect.yMax - border.bottom, rect.width, border.bottom), EditorGUIUtility.whiteTexture); //bottom
+ GUI.DrawTexture(new Rect(rect.x, rect.y + 1, border.left, rect.height - 2 * border.left), EditorGUIUtility.whiteTexture); //left
+ GUI.DrawTexture(new Rect(rect.xMax - border.right, rect.y + 1, border.right, rect.height - 2 * border.right), EditorGUIUtility.whiteTexture); //right
+
+ GUI.color = orgColor;
+ }
+
+ public static Rect DrawHeader(float height, string title)
+ {
+ Rect rect = GUILayoutUtility.GetRect(0, height);
+ EditorGUI.DrawRect(rect, new Color(0.1f, 0.1f, 0.1f, 0.4f));
+
+ var labelRect = rect;
+ labelRect.x += 3f;
+
+ EditorGUI.LabelField(labelRect, title, EditorStyles.boldLabel);
+ return rect;
+ }
+
+ public static void DrawHeader(Rect rect, string title, float labelX, float labelY, GUIStyle labelStyle)
+ {
+ EditorGUI.DrawRect(rect, new Color(0.1f, 0.1f, 0.1f, 0.4f));
+
+ var labelRect = rect;
+ labelRect.x += labelX;
+ labelRect.y += labelY;
+
+ EditorGUI.LabelField(labelRect, title, labelStyle);
+ }
+
+ public static Rect DrawHeaderWithBorder(GUIContent title, float height, ref Rect rect, bool rounded)
+ {
+ GUI.Box(rect, GUIContent.none, new GUIStyle(rounded ? "HelpBox" : "Tooltip"));
+
+ Rect headerRect = rect;
+ headerRect.height = height;
+ EditorGUI.DrawRect(headerRect, new Color(0.1f, 0.1f, 0.1f, 0.4f));
+
+ Rect labelRect = headerRect;
+ labelRect.y += height / 2;
+ EditorGUI.LabelField(labelRect, title, EditorStyles.miniBoldLabel);
+
+ rect.x += 1;
+ rect.y += 1;
+ rect.height -= 1;
+ rect.width -= 2;
+ rect.y += height;
+ rect.height -= height;
+ return rect;
+ }
+
+ public static Rect DrawHeaderWithBorder(GUIContent title, float height, ref Rect rect, GUIStyle boxStyle)
+ {
+ GUI.Box(rect, GUIContent.none, boxStyle);
+ rect.x += 1;
+ rect.y += 1;
+ rect.height -= 1;
+ rect.width -= 2;
+
+ var headerRect = rect;
+ headerRect.height = height + EditorGUIUtility.standardVerticalSpacing;
+
+ rect.y += headerRect.height;
+ rect.height -= headerRect.height;
+
+ EditorGUI.DrawRect(headerRect, new Color(0.1f, 0.1f, 0.1f, 0.4f));
+
+ var labelRect = headerRect;
+ labelRect.y += EditorGUIUtility.standardVerticalSpacing;
+ labelRect.x += 2f;
+
+ EditorGUI.LabelField(labelRect, title, EditorStyles.miniBoldLabel);
+
+ return headerRect;
+ }
+
+ public static Rect DrawHeaderWithBorder(GUIContent title, float height, ref Rect rect, RectOffset border)
+ {
+ DrawOutline(rect, border);
+ rect.x += 1;
+ rect.y += 1;
+ rect.height -= 1;
+ rect.width -= 2;
+
+ var headerRect = rect;
+ headerRect.height = height + EditorGUIUtility.standardVerticalSpacing;
+
+ rect.y += headerRect.height;
+ rect.height -= headerRect.height;
+
+ EditorGUI.DrawRect(headerRect, new Color(0.1f, 0.1f, 0.1f, 0.4f));
+
+ var labelRect = headerRect;
+ labelRect.y += EditorGUIUtility.standardVerticalSpacing;
+ labelRect.x += 2f;
+
+ EditorGUI.LabelField(labelRect, title, EditorStyles.miniBoldLabel);
+
+ return headerRect;
+ }
+
+ public static bool DrawBoxFoldoutHeader(string title, bool state, float height = 22)
+ {
+ Rect rect = GUILayoutUtility.GetRect(1, height);
+ EditorGUI.DrawRect(rect, new Color(0.1f, 0.1f, 0.1f, 0.4f));
+
+ rect.x += EditorGUIUtility.standardVerticalSpacing;
+ Rect foldoutRect = EditorGUI.IndentedRect(rect);
+ state = GUI.Toggle(foldoutRect, state, GUIContent.none, EditorStyles.foldout);
+
+ rect.x += EditorGUIUtility.singleLineHeight - EditorGUIUtility.standardVerticalSpacing * 2;
+ EditorGUI.LabelField(rect, new GUIContent(title), EditorStyles.boldLabel);
+
+ return state;
+ }
+
+ public static bool DrawFoldoutHeader(float height, string title, bool state)
+ {
+ Rect rect = GUILayoutUtility.GetRect(1, height);
+
+ rect.x += EditorGUIUtility.standardVerticalSpacing;
+ Rect foldoutRect = EditorGUI.IndentedRect(rect);
+ state = GUI.Toggle(foldoutRect, state, GUIContent.none, EditorStyles.foldout);
+
+ rect.x += EditorGUIUtility.singleLineHeight - EditorGUIUtility.standardVerticalSpacing * 2;
+ EditorGUI.LabelField(rect, new GUIContent(title), EditorStyles.boldLabel);
+
+ return state;
+ }
+
+ public static void DrawFoldoutToggleHeader(Rect rect, string title, ref bool isExpanded, ref bool isEnabled, bool showFoldout = true, bool toggleDisable = false)
+ {
+ Color headerColor = new Color(0.1f, 0.1f, 0.1f, 0f);
+
+ var expandRect = rect;
+ expandRect.xMin += EditorGUIUtility.singleLineHeight * 2;
+
+ var foldoutRect = rect;
+ foldoutRect.width = EditorGUIUtility.singleLineHeight;
+
+ var toggleRect = rect;
+ toggleRect.width = EditorGUIUtility.singleLineHeight;
+ toggleRect.x += EditorGUIUtility.singleLineHeight;
+
+ var labelRect = rect;
+ labelRect.xMin += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing * 2;
+
+ if (showFoldout)
+ {
+ // events
+ var e = Event.current;
+ if (expandRect.Contains(e.mousePosition))
+ {
+ if (e.type == EventType.MouseDown && e.button == 0)
+ {
+ isExpanded = !isExpanded;
+ e.Use();
+ }
+ }
+
+ // foldout
+ isExpanded = GUI.Toggle(foldoutRect, isExpanded, GUIContent.none, EditorStyles.foldout);
+ }
+
+ // background
+ EditorGUI.DrawRect(rect, headerColor);
+
+ // toggle
+ using (new EditorGUI.DisabledGroupScope(toggleDisable))
+ {
+ isEnabled = GUI.Toggle(toggleRect, isEnabled, new GUIContent("", "Extension Enabled State"), EditorStyles.toggle);
+ }
+
+ // title
+ EditorGUI.LabelField(labelRect, new GUIContent(title), EditorStyles.boldLabel);
+ }
+
+ private static void PopupSelect(object data)
+ {
+ PopupElement element = (PopupElement)data;
+ PopupArray array = element.popupArray;
+ string name = element.name;
+
+ if (array.selected.Contains(name))
+ array.selected.Remove(name);
+ else array.selected.Add(name);
+
+ element.onSelect?.Invoke(array.selected.ToArray());
+ }
+
+ public static void DrawMultiSelectionPopup(Rect rect, string title, PopupArray popupArray, Action onSelect)
+ {
+ GenericMenu menu = new GenericMenu();
+
+ for (int i = 0; i < popupArray.contents.Count; i++)
+ {
+ string name = popupArray.contents[i];
+ bool on = popupArray.selected.Contains(name);
+
+ PopupElement element = new PopupElement(popupArray, name, onSelect);
+ menu.AddItem(new GUIContent(name), on, PopupSelect, element);
+ }
+
+ if(GUI.Button(rect, title, EditorStyles.popup))
+ {
+ menu.ShowAsContext();
+ }
+ }
+
+ public static void DrawRelativeProperties(SerializedProperty root, float width)
+ {
+ var childrens = root.GetVisibleChildrens();
+
+ foreach (var childProperty in childrens)
+ {
+ float height = EditorGUI.GetPropertyHeight(childProperty, true);
+
+ Rect rect = GUILayoutUtility.GetRect(1f, height);
+ rect.xMin += width;
+ EditorGUI.PropertyField(rect, childProperty, true);
+ EditorGUILayout.Space(EditorGUIUtility.standardVerticalSpacing);
+ }
+ }
+
+ public static IEnumerable GetVisibleChildrens(this SerializedProperty serializedProperty)
+ {
+ SerializedProperty currentProperty = serializedProperty.Copy();
+ SerializedProperty nextSiblingProperty = serializedProperty.Copy();
+ {
+ nextSiblingProperty.NextVisible(false);
+ }
+
+ if (currentProperty.NextVisible(true))
+ {
+ do
+ {
+ if (SerializedProperty.EqualContents(currentProperty, nextSiblingProperty))
+ break;
+
+ yield return currentProperty;
+ }
+ while (currentProperty.NextVisible(false));
+ }
+ }
+
+ public static void TrHelpIconText(string message, string icon, bool rich = false)
+ {
+ GUIStyle style = new GUIStyle(EditorStyles.helpBox)
+ {
+ richText = rich
+ };
+
+ EditorGUILayout.LabelField(GUIContent.none, EditorGUIUtility.TrTextContentWithIcon(" " + message, icon), style, new GUILayoutOption[0]);
+ }
+
+ public static void TrHelpIconText(Rect rect, string message, string icon, bool rich = false)
+ {
+ GUIStyle style = new GUIStyle(EditorStyles.helpBox)
+ {
+ richText = rich
+ };
+
+ EditorGUI.LabelField(rect, GUIContent.none, EditorGUIUtility.TrTextContentWithIcon(" " + message, icon), style);
+ }
+
+ public static void TrHelpIconText(string message, MessageType messageType, bool rich = false, bool space = true)
+ {
+ string icon = string.Empty;
+
+ GUIStyle style = new GUIStyle(EditorStyles.helpBox)
+ {
+ richText = rich
+ };
+
+ switch (messageType)
+ {
+ case MessageType.Info:
+ icon = "console.infoicon.sml";
+ break;
+ case MessageType.Warning:
+ icon = "console.warnicon.sml";
+ break;
+ case MessageType.Error:
+ icon = "console.erroricon.sml";
+ break;
+ }
+
+ if (!string.IsNullOrEmpty(icon))
+ {
+ string text = space ? " " + message : message;
+ EditorGUILayout.LabelField(GUIContent.none, EditorGUIUtility.TrTextContentWithIcon(text, icon), style, new GUILayoutOption[0]);
+ }
+ else
+ {
+ EditorGUILayout.LabelField(GUIContent.none, EditorGUIUtility.TrTextContent(message), style, new GUILayoutOption[0]);
+ }
+ }
+
+ public static void TrHelpIconText(Rect rect, string message, MessageType messageType, bool rich = false, bool space = true)
+ {
+ string icon = string.Empty;
+
+ GUIStyle style = new GUIStyle(EditorStyles.helpBox)
+ {
+ richText = rich
+ };
+
+ switch (messageType)
+ {
+ case MessageType.Info:
+ icon = "console.infoicon.sml";
+ break;
+ case MessageType.Warning:
+ icon = "console.warnicon.sml";
+ break;
+ case MessageType.Error:
+ icon = "console.erroricon.sml";
+ break;
+ }
+
+ if (!string.IsNullOrEmpty(icon))
+ {
+ string text = space ? " " + message : message;
+ EditorGUI.LabelField(rect, GUIContent.none, EditorGUIUtility.TrTextContentWithIcon(text, icon), style);
+ }
+ else
+ {
+ EditorGUI.LabelField(rect, GUIContent.none, EditorGUIUtility.TrTextContent(message), style);
+ }
+ }
+
+ public static void TrIconText(string message, string icon, GUIStyle style, bool rich = false, bool space = true)
+ {
+ style.richText = rich;
+ string text = space ? " " + message : message;
+ EditorGUILayout.LabelField(GUIContent.none, EditorGUIUtility.TrTextContentWithIcon(text, icon), style, new GUILayoutOption[0]);
+ }
+
+ public static void TrIconText(string message, MessageType messageType, GUIStyle style, bool rich = false, bool space = true)
+ {
+ string icon = string.Empty;
+ style.richText = rich;
+
+ switch (messageType)
+ {
+ case MessageType.Info:
+ icon = "console.infoicon.sml";
+ break;
+ case MessageType.Warning:
+ icon = "console.warnicon.sml";
+ break;
+ case MessageType.Error:
+ icon = "console.erroricon.sml";
+ break;
+ }
+
+ if (!string.IsNullOrEmpty(icon))
+ {
+ string text = space ? " " + message : message;
+ EditorGUILayout.LabelField(GUIContent.none, EditorGUIUtility.TrTextContentWithIcon(text, icon), style, new GUILayoutOption[0]);
+ }
+ else
+ {
+ EditorGUILayout.LabelField(GUIContent.none, EditorGUIUtility.TrTextContent(message), style, new GUILayoutOption[0]);
+ }
+ }
+
+ public static void TrIconText(Rect rect, string message, MessageType messageType, GUIStyle style, bool rich = false, bool space = true)
+ {
+ string icon = string.Empty;
+ style.richText = rich;
+
+ switch (messageType)
+ {
+ case MessageType.Info:
+ icon = "console.infoicon.sml";
+ break;
+ case MessageType.Warning:
+ icon = "console.warnicon.sml";
+ break;
+ case MessageType.Error:
+ icon = "console.erroricon.sml";
+ break;
+ }
+
+ if (!string.IsNullOrEmpty(icon))
+ {
+ string text = space ? " " + message : message;
+ EditorGUI.LabelField(rect, GUIContent.none, EditorGUIUtility.TrTextContentWithIcon(text, icon), style);
+ }
+ else
+ {
+ EditorGUI.LabelField(rect, GUIContent.none, EditorGUIUtility.TrTextContent(message), style);
+ }
+ }
+ }
+}
diff --git a/Editor/Utility/EditorUtils.cs.meta b/Editor/Utility/EditorUtils.cs.meta
new file mode 100644
index 0000000..e00cb1d
--- /dev/null
+++ b/Editor/Utility/EditorUtils.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: b87483fd614d43e781d70a25494f0616
+timeCreated: 1758265230
\ No newline at end of file
diff --git a/Editor/Utility/PropertyCollection.cs b/Editor/Utility/PropertyCollection.cs
new file mode 100644
index 0000000..2d72061
--- /dev/null
+++ b/Editor/Utility/PropertyCollection.cs
@@ -0,0 +1,207 @@
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEditor;
+using System.Linq;
+using System;
+
+namespace AlicizaX.Editor
+{
+ public class PropertyCollection : Dictionary
+ {
+ public SerializedProperty GetRelative(string propertyPath)
+ {
+ string[] paths = propertyPath.Split(new char[] { '.' });
+ if (TryGetValue(paths[0], out SerializedProperty pathProperty))
+ {
+ for (int i = 1; i < paths.Length; i++)
+ {
+ pathProperty = pathProperty.FindPropertyRelative(paths[i]);
+ }
+
+ return pathProperty;
+ }
+
+ return null;
+ }
+
+ public void DrawRelative(string propertyPath)
+ {
+ string[] paths = propertyPath.Split(new char[] { '.' });
+ if (TryGetValue(paths[0], out SerializedProperty pathProperty))
+ {
+ for (int i = 1; i < paths.Length; i++)
+ {
+ pathProperty = pathProperty.FindPropertyRelative(paths[i]);
+ }
+
+ EditorGUILayout.PropertyField(pathProperty);
+ }
+ }
+
+ public void DrawRelative(string propertyPath, GUIContent label)
+ {
+ string[] paths = propertyPath.Split(new char[] { '.' });
+ if (TryGetValue(paths[0], out SerializedProperty pathProperty))
+ {
+ for (int i = 1; i < paths.Length; i++)
+ {
+ pathProperty = pathProperty.FindPropertyRelative(paths[i]);
+ }
+
+ EditorGUILayout.PropertyField(pathProperty, label);
+ }
+ }
+
+ public void Draw(string propertyName, int indent = 0)
+ {
+ if (TryGetValue(propertyName, out SerializedProperty property))
+ {
+ if (indent > 0) EditorGUI.indentLevel += indent;
+ EditorGUILayout.PropertyField(property);
+ if (indent > 0) EditorGUI.indentLevel -= indent;
+ }
+ }
+
+ public void DrawBacking(string propertyName)
+ {
+ propertyName = $"<{propertyName}>k__BackingField";
+ if (TryGetValue(propertyName, out SerializedProperty property))
+ EditorGUILayout.PropertyField(property);
+ }
+
+ public bool DrawGetBool(string propertyName)
+ {
+ if (TryGetValue(propertyName, out SerializedProperty property))
+ {
+ EditorGUILayout.PropertyField(property);
+ return property.boolValue;
+ }
+
+ return false;
+ }
+
+ public bool DrawToggleLeft(string propertyName)
+ {
+ if (TryGetValue(propertyName, out SerializedProperty property))
+ {
+ GUIContent label = new(property.displayName, property.tooltip);
+ return property.boolValue = EditorGUILayout.ToggleLeft(label, property.boolValue);
+ }
+
+ return false;
+ }
+
+ public void DrawAll(bool indentDropdowns = false, int skip = 0)
+ {
+ foreach (var property in this.Skip(skip))
+ {
+ bool shouldIndent = property.Value.propertyType == SerializedPropertyType.Generic;
+
+ if (indentDropdowns && shouldIndent) EditorGUI.indentLevel++;
+ EditorGUILayout.PropertyField(property.Value);
+ if (indentDropdowns && shouldIndent) EditorGUI.indentLevel--;
+ }
+ }
+
+ public void DrawAllExcept(bool indentDropdowns = false, int skip = 0, params string[] except)
+ {
+ foreach (var property in this.Skip(skip))
+ {
+ if (except.Contains(property.Key))
+ continue;
+
+ bool shouldIndent = property.Value.propertyType == SerializedPropertyType.Generic;
+
+ if (indentDropdowns && shouldIndent) EditorGUI.indentLevel++;
+ EditorGUILayout.PropertyField(property.Value);
+ if (indentDropdowns && shouldIndent) EditorGUI.indentLevel--;
+ }
+ }
+
+ public void DrawAllPredicate(bool indentDropdowns, int skip, Predicate predicate)
+ {
+ foreach (var property in this.Skip(skip))
+ {
+ if (!predicate(property.Key))
+ continue;
+
+ bool shouldIndent = property.Value.propertyType == SerializedPropertyType.Generic;
+
+ if (indentDropdowns && shouldIndent) EditorGUI.indentLevel++;
+ EditorGUILayout.PropertyField(property.Value);
+ if (indentDropdowns && shouldIndent) EditorGUI.indentLevel--;
+ }
+ }
+
+ public bool BoolValue(string propertyName)
+ {
+ if (TryGetValue(propertyName, out SerializedProperty property))
+ return property.boolValue;
+
+ return false;
+ }
+
+ public void DrawArray(string propertyName)
+ {
+ if (TryGetValue(propertyName, out SerializedProperty property))
+ {
+ if (property.isArray) EditorGUI.indentLevel++;
+ EditorGUILayout.PropertyField(property);
+ if (property.isArray) EditorGUI.indentLevel--;
+ }
+ }
+
+ public void Draw(string propertyName, bool includeChildren)
+ {
+ if (TryGetValue(propertyName, out SerializedProperty property))
+ EditorGUILayout.PropertyField(property, includeChildren);
+ }
+
+ public void Draw(string propertyName, GUIContent label)
+ {
+ if (TryGetValue(propertyName, out SerializedProperty property))
+ EditorGUILayout.PropertyField(property, label);
+ }
+
+ public bool DrawGetBool(string propertyName, GUIContent label)
+ {
+ if (TryGetValue(propertyName, out SerializedProperty property))
+ {
+ EditorGUILayout.PropertyField(property, label);
+ return property.boolValue;
+ }
+
+ return false;
+ }
+
+ public void Draw(string propertyName, GUIContent label, bool includeChildren)
+ {
+ if (TryGetValue(propertyName, out SerializedProperty property))
+ EditorGUILayout.PropertyField(property, label, includeChildren);
+ }
+
+ public void Draw(Rect rect, string propertyName)
+ {
+ if (TryGetValue(propertyName, out SerializedProperty property))
+ EditorGUI.PropertyField(rect, property);
+ }
+
+ public void Draw(Rect rect, string propertyName, bool includeChildren)
+ {
+ if (TryGetValue(propertyName, out SerializedProperty property))
+ EditorGUI.PropertyField(rect, property, includeChildren);
+ }
+
+ public void Draw(Rect rect, string propertyName, GUIContent label)
+ {
+ if (TryGetValue(propertyName, out SerializedProperty property))
+ EditorGUI.PropertyField(rect, property, label);
+ }
+
+ public void Draw(Rect rect, string propertyName, GUIContent label, bool includeChildren)
+ {
+ if (TryGetValue(propertyName, out SerializedProperty property))
+ EditorGUI.PropertyField(rect, property, label, includeChildren);
+ }
+ }
+}
diff --git a/Editor/Utility/PropertyCollection.cs.meta b/Editor/Utility/PropertyCollection.cs.meta
new file mode 100644
index 0000000..2bc6525
--- /dev/null
+++ b/Editor/Utility/PropertyCollection.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: c59762160e44443096c548c7ea024d00
+timeCreated: 1758265184
\ No newline at end of file
diff --git a/Plugins/EventSourceGenerator.dll b/Plugins/EventSourceGenerator.dll
new file mode 100644
index 0000000..b800e52
Binary files /dev/null and b/Plugins/EventSourceGenerator.dll differ
diff --git a/Plugins/EventSourceGenerator.dll.meta b/Plugins/EventSourceGenerator.dll.meta
new file mode 100644
index 0000000..d0ee933
--- /dev/null
+++ b/Plugins/EventSourceGenerator.dll.meta
@@ -0,0 +1,52 @@
+fileFormatVersion: 2
+guid: e0f9fe588fe13b14ca28cddf735a2f0b
+labels:
+- RoslynAnalyzer
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 3
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 1
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ Any:
+ enabled: 1
+ settings:
+ Exclude Editor: 1
+ Exclude Linux64: 0
+ Exclude OSXUniversal: 0
+ Exclude Win: 0
+ Exclude Win64: 0
+ Editor:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ DefaultValueInitialized: true
+ OS: AnyOS
+ Linux64:
+ enabled: 1
+ settings:
+ CPU: AnyCPU
+ OSXUniversal:
+ enabled: 1
+ settings:
+ CPU: AnyCPU
+ Win:
+ enabled: 1
+ settings:
+ CPU: AnyCPU
+ Win64:
+ enabled: 1
+ settings:
+ CPU: AnyCPU
+ WindowsStoreApps:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Plugins/ICSharpCode.SharpZipLib.dll b/Plugins/ICSharpCode.SharpZipLib.dll
new file mode 100644
index 0000000..fe643eb
Binary files /dev/null and b/Plugins/ICSharpCode.SharpZipLib.dll differ
diff --git a/Plugins/ICSharpCode.SharpZipLib.dll.meta b/Plugins/ICSharpCode.SharpZipLib.dll.meta
new file mode 100644
index 0000000..79161a0
--- /dev/null
+++ b/Plugins/ICSharpCode.SharpZipLib.dll.meta
@@ -0,0 +1,83 @@
+fileFormatVersion: 2
+guid: de67d260a82e87742b9c3c297379ffb3
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ : Linux
+ second:
+ enabled: 0
+ settings:
+ CPU: x86
+ - first:
+ : LinuxUniversal
+ second:
+ enabled: 0
+ settings:
+ CPU: None
+ - first:
+ : OSXIntel
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ - first:
+ : OSXIntel64
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ - first:
+ Any:
+ second:
+ enabled: 1
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ DefaultValueInitialized: true
+ OS: AnyOS
+ - first:
+ Standalone: Linux64
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ - first:
+ Standalone: OSXUniversal
+ second:
+ enabled: 0
+ settings:
+ CPU: None
+ - first:
+ Standalone: Win
+ second:
+ enabled: 1
+ settings:
+ CPU: AnyCPU
+ - first:
+ Standalone: Win64
+ second:
+ enabled: 1
+ settings:
+ CPU: AnyCPU
+ - first:
+ Windows Store Apps: WindowsStoreApps
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Plugins/System.Buffers.dll b/Plugins/System.Buffers.dll
new file mode 100644
index 0000000..c0970c0
Binary files /dev/null and b/Plugins/System.Buffers.dll differ
diff --git a/Plugins/System.Buffers.dll.meta b/Plugins/System.Buffers.dll.meta
new file mode 100644
index 0000000..608cd5b
--- /dev/null
+++ b/Plugins/System.Buffers.dll.meta
@@ -0,0 +1,33 @@
+fileFormatVersion: 2
+guid: 80b599a6cb4f1c94dbb15cbb8b52b5e7
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 1
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ - first:
+ Windows Store Apps: WindowsStoreApps
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Plugins/System.Memory.dll b/Plugins/System.Memory.dll
new file mode 100644
index 0000000..953a9d2
Binary files /dev/null and b/Plugins/System.Memory.dll differ
diff --git a/Plugins/System.Memory.dll.meta b/Plugins/System.Memory.dll.meta
new file mode 100644
index 0000000..8be3b77
--- /dev/null
+++ b/Plugins/System.Memory.dll.meta
@@ -0,0 +1,33 @@
+fileFormatVersion: 2
+guid: e69802479db3f6540b50e7515c7e7ef9
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 1
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ - first:
+ Windows Store Apps: WindowsStoreApps
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Plugins/System.Runtime.CompilerServices.Unsafe.dll b/Plugins/System.Runtime.CompilerServices.Unsafe.dll
new file mode 100644
index 0000000..491a80a
Binary files /dev/null and b/Plugins/System.Runtime.CompilerServices.Unsafe.dll differ
diff --git a/Plugins/System.Runtime.CompilerServices.Unsafe.dll.meta b/Plugins/System.Runtime.CompilerServices.Unsafe.dll.meta
new file mode 100644
index 0000000..2833d64
--- /dev/null
+++ b/Plugins/System.Runtime.CompilerServices.Unsafe.dll.meta
@@ -0,0 +1,33 @@
+fileFormatVersion: 2
+guid: 8f0fcff9f03d0ec498f1e227c9c6878a
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 1
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ - first:
+ Windows Store Apps: WindowsStoreApps
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Plugins/XLog.dll b/Plugins/XLog.dll
new file mode 100644
index 0000000..a51d185
Binary files /dev/null and b/Plugins/XLog.dll differ
diff --git a/Plugins/XLog.dll.meta b/Plugins/XLog.dll.meta
new file mode 100644
index 0000000..b22e5a5
--- /dev/null
+++ b/Plugins/XLog.dll.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: d54020f8ad5378144bdeef5bb9b7a769
\ No newline at end of file
diff --git a/Runtime/ABase.meta b/Runtime/ABase.meta
new file mode 100644
index 0000000..848d79e
--- /dev/null
+++ b/Runtime/ABase.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 74d41a49b8f6d8442b0484a9d0f1a99d
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/ABase/Base.meta b/Runtime/ABase/Base.meta
new file mode 100644
index 0000000..62ddfd3
--- /dev/null
+++ b/Runtime/ABase/Base.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 17ad22f068c740e7a51303c78a742268
+timeCreated: 1736324891
\ No newline at end of file
diff --git a/Runtime/ABase/Base/DataStruct.meta b/Runtime/ABase/Base/DataStruct.meta
new file mode 100644
index 0000000..6159a22
--- /dev/null
+++ b/Runtime/ABase/Base/DataStruct.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: a40ddcd0fd47a4c06bb194117de12597
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/ABase/Base/DataStruct/GameFrameworkLinkedList.cs b/Runtime/ABase/Base/DataStruct/GameFrameworkLinkedList.cs
new file mode 100644
index 0000000..35361ef
--- /dev/null
+++ b/Runtime/ABase/Base/DataStruct/GameFrameworkLinkedList.cs
@@ -0,0 +1,446 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace AlicizaX
+{
+ ///
+ /// 游戏框架链表类。
+ ///
+ /// 指定链表的元素类型。
+ public sealed class GameFrameworkLinkedList : ICollection, IEnumerable, ICollection, IEnumerable
+ {
+ private readonly LinkedList m_LinkedList;
+ private readonly Queue> m_CachedNodes;
+
+ ///
+ /// 初始化游戏框架链表类的新实例。
+ ///
+ public GameFrameworkLinkedList()
+ {
+ m_LinkedList = new LinkedList();
+ m_CachedNodes = new Queue>();
+ }
+
+ ///
+ /// 获取链表中实际包含的结点数量。
+ ///
+ public int Count
+ {
+ get
+ {
+ return m_LinkedList.Count;
+ }
+ }
+
+ ///
+ /// 获取链表结点缓存数量。
+ ///
+ public int CachedNodeCount
+ {
+ get
+ {
+ return m_CachedNodes.Count;
+ }
+ }
+
+ ///
+ /// 获取链表的第一个结点。
+ ///
+ public LinkedListNode First
+ {
+ get
+ {
+ return m_LinkedList.First;
+ }
+ }
+
+ ///
+ /// 获取链表的最后一个结点。
+ ///
+ public LinkedListNode Last
+ {
+ get
+ {
+ return m_LinkedList.Last;
+ }
+ }
+
+ ///
+ /// 获取一个值,该值指示 ICollection`1 是否为只读。
+ ///
+ public bool IsReadOnly
+ {
+ get
+ {
+ return ((ICollection)m_LinkedList).IsReadOnly;
+ }
+ }
+
+ ///
+ /// 获取可用于同步对 ICollection 的访问的对象。
+ ///
+ public object SyncRoot
+ {
+ get
+ {
+ return ((ICollection)m_LinkedList).SyncRoot;
+ }
+ }
+
+ ///
+ /// 获取一个值,该值指示是否同步对 ICollection 的访问(线程安全)。
+ ///
+ public bool IsSynchronized
+ {
+ get
+ {
+ return ((ICollection)m_LinkedList).IsSynchronized;
+ }
+ }
+
+ ///
+ /// 在链表中指定的现有结点后添加包含指定值的新结点。
+ ///
+ /// 指定的现有结点。
+ /// 指定值。
+ /// 包含指定值的新结点。
+ public LinkedListNode AddAfter(LinkedListNode node, T value)
+ {
+ LinkedListNode newNode = AcquireNode(value);
+ m_LinkedList.AddAfter(node, newNode);
+ return newNode;
+ }
+
+ ///
+ /// 在链表中指定的现有结点后添加指定的新结点。
+ ///
+ /// 指定的现有结点。
+ /// 指定的新结点。
+ public void AddAfter(LinkedListNode node, LinkedListNode newNode)
+ {
+ m_LinkedList.AddAfter(node, newNode);
+ }
+
+ ///
+ /// 在链表中指定的现有结点前添加包含指定值的新结点。
+ ///
+ /// 指定的现有结点。
+ /// 指定值。
+ /// 包含指定值的新结点。
+ public LinkedListNode AddBefore(LinkedListNode node, T value)
+ {
+ LinkedListNode newNode = AcquireNode(value);
+ m_LinkedList.AddBefore(node, newNode);
+ return newNode;
+ }
+
+ ///
+ /// 在链表中指定的现有结点前添加指定的新结点。
+ ///
+ /// 指定的现有结点。
+ /// 指定的新结点。
+ public void AddBefore(LinkedListNode node, LinkedListNode newNode)
+ {
+ m_LinkedList.AddBefore(node, newNode);
+ }
+
+ ///
+ /// 在链表的开头处添加包含指定值的新结点。
+ ///
+ /// 指定值。
+ /// 包含指定值的新结点。
+ public LinkedListNode AddFirst(T value)
+ {
+ LinkedListNode node = AcquireNode(value);
+ m_LinkedList.AddFirst(node);
+ return node;
+ }
+
+ ///
+ /// 在链表的开头处添加指定的新结点。
+ ///
+ /// 指定的新结点。
+ public void AddFirst(LinkedListNode node)
+ {
+ m_LinkedList.AddFirst(node);
+ }
+
+ ///
+ /// 在链表的结尾处添加包含指定值的新结点。
+ ///
+ /// 指定值。
+ /// 包含指定值的新结点。
+ public LinkedListNode AddLast(T value)
+ {
+ LinkedListNode node = AcquireNode(value);
+ m_LinkedList.AddLast(node);
+ return node;
+ }
+
+ ///
+ /// 在链表的结尾处添加指定的新结点。
+ ///
+ /// 指定的新结点。
+ public void AddLast(LinkedListNode node)
+ {
+ m_LinkedList.AddLast(node);
+ }
+
+ ///
+ /// 从链表中移除所有结点。
+ ///
+ public void Clear()
+ {
+ LinkedListNode current = m_LinkedList.First;
+ while (current != null)
+ {
+ ReleaseNode(current);
+ current = current.Next;
+ }
+
+ m_LinkedList.Clear();
+ }
+
+ ///
+ /// 清除链表结点缓存。
+ ///
+ public void ClearCachedNodes()
+ {
+ m_CachedNodes.Clear();
+ }
+
+ ///
+ /// 确定某值是否在链表中。
+ ///
+ /// 指定值。
+ /// 某值是否在链表中。
+ public bool Contains(T value)
+ {
+ return m_LinkedList.Contains(value);
+ }
+
+ ///
+ /// 从目标数组的指定索引处开始将整个链表复制到兼容的一维数组。
+ ///
+ /// 一维数组,它是从链表复制的元素的目标。数组必须具有从零开始的索引。
+ /// array 中从零开始的索引,从此处开始复制。
+ public void CopyTo(T[] array, int index)
+ {
+ m_LinkedList.CopyTo(array, index);
+ }
+
+ ///
+ /// 从特定的 ICollection 索引开始,将数组的元素复制到一个数组中。
+ ///
+ /// 一维数组,它是从 ICollection 复制的元素的目标。数组必须具有从零开始的索引。
+ /// array 中从零开始的索引,从此处开始复制。
+ public void CopyTo(Array array, int index)
+ {
+ ((ICollection)m_LinkedList).CopyTo(array, index);
+ }
+
+ ///
+ /// 查找包含指定值的第一个结点。
+ ///
+ /// 要查找的指定值。
+ /// 包含指定值的第一个结点。
+ public LinkedListNode Find(T value)
+ {
+ return m_LinkedList.Find(value);
+ }
+
+ ///
+ /// 查找包含指定值的最后一个结点。
+ ///
+ /// 要查找的指定值。
+ /// 包含指定值的最后一个结点。
+ public LinkedListNode FindLast(T value)
+ {
+ return m_LinkedList.FindLast(value);
+ }
+
+ ///
+ /// 从链表中移除指定值的第一个匹配项。
+ ///
+ /// 指定值。
+ /// 是否移除成功。
+ public bool Remove(T value)
+ {
+ LinkedListNode node = m_LinkedList.Find(value);
+ if (node != null)
+ {
+ m_LinkedList.Remove(node);
+ ReleaseNode(node);
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// 从链表中移除指定的结点。
+ ///
+ /// 指定的结点。
+ public void Remove(LinkedListNode node)
+ {
+ m_LinkedList.Remove(node);
+ ReleaseNode(node);
+ }
+
+ ///
+ /// 移除位于链表开头处的结点。
+ ///
+ public void RemoveFirst()
+ {
+ LinkedListNode first = m_LinkedList.First;
+ if (first == null)
+ {
+ throw new GameFrameworkException("First is invalid.");
+ }
+
+ m_LinkedList.RemoveFirst();
+ ReleaseNode(first);
+ }
+
+ ///
+ /// 移除位于链表结尾处的结点。
+ ///
+ public void RemoveLast()
+ {
+ LinkedListNode last = m_LinkedList.Last;
+ if (last == null)
+ {
+ throw new GameFrameworkException("Last is invalid.");
+ }
+
+ m_LinkedList.RemoveLast();
+ ReleaseNode(last);
+ }
+
+ ///
+ /// 返回循环访问集合的枚举数。
+ ///
+ /// 循环访问集合的枚举数。
+ public Enumerator GetEnumerator()
+ {
+ return new Enumerator(m_LinkedList);
+ }
+
+ private LinkedListNode AcquireNode(T value)
+ {
+ LinkedListNode node = null;
+ if (m_CachedNodes.Count > 0)
+ {
+ node = m_CachedNodes.Dequeue();
+ node.Value = value;
+ }
+ else
+ {
+ node = new LinkedListNode(value);
+ }
+
+ return node;
+ }
+
+ private void ReleaseNode(LinkedListNode node)
+ {
+ node.Value = default(T);
+ m_CachedNodes.Enqueue(node);
+ }
+
+ ///
+ /// 将值添加到 ICollection`1 的结尾处。
+ ///
+ /// 要添加的值。
+ void ICollection.Add(T value)
+ {
+ AddLast(value);
+ }
+
+ ///
+ /// 返回循环访问集合的枚举数。
+ ///
+ /// 循环访问集合的枚举数。
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ ///
+ /// 返回循环访问集合的枚举数。
+ ///
+ /// 循环访问集合的枚举数。
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ ///
+ /// 循环访问集合的枚举数。
+ ///
+ [StructLayout(LayoutKind.Auto)]
+ public struct Enumerator : IEnumerator, IEnumerator
+ {
+ private LinkedList.Enumerator m_Enumerator;
+
+ internal Enumerator(LinkedList linkedList)
+ {
+ if (linkedList == null)
+ {
+ throw new GameFrameworkException("Linked list is invalid.");
+ }
+
+ m_Enumerator = linkedList.GetEnumerator();
+ }
+
+ ///
+ /// 获取当前结点。
+ ///
+ public T Current
+ {
+ get
+ {
+ return m_Enumerator.Current;
+ }
+ }
+
+ ///
+ /// 获取当前的枚举数。
+ ///
+ object IEnumerator.Current
+ {
+ get
+ {
+ return m_Enumerator.Current;
+ }
+ }
+
+ ///
+ /// 清理枚举数。
+ ///
+ public void Dispose()
+ {
+ m_Enumerator.Dispose();
+ }
+
+ ///
+ /// 获取下一个结点。
+ ///
+ /// 返回下一个结点。
+ public bool MoveNext()
+ {
+ return m_Enumerator.MoveNext();
+ }
+
+ ///
+ /// 重置枚举数。
+ ///
+ void IEnumerator.Reset()
+ {
+ ((IEnumerator)m_Enumerator).Reset();
+ }
+ }
+ }
+}
diff --git a/Runtime/ABase/Base/DataStruct/GameFrameworkLinkedList.cs.meta b/Runtime/ABase/Base/DataStruct/GameFrameworkLinkedList.cs.meta
new file mode 100644
index 0000000..d89ec26
--- /dev/null
+++ b/Runtime/ABase/Base/DataStruct/GameFrameworkLinkedList.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8662d2b0651bb46308fa9ea5a24a9f08
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/ABase/Base/DataStruct/GameFrameworkLinkedListRange.cs b/Runtime/ABase/Base/DataStruct/GameFrameworkLinkedListRange.cs
new file mode 100644
index 0000000..4799dbf
--- /dev/null
+++ b/Runtime/ABase/Base/DataStruct/GameFrameworkLinkedListRange.cs
@@ -0,0 +1,210 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace AlicizaX
+{
+ ///
+ /// 游戏框架链表范围。
+ ///
+ /// 指定链表范围的元素类型。
+ [StructLayout(LayoutKind.Auto)]
+ public readonly struct GameFrameworkLinkedListRange : IEnumerable, IEnumerable
+ {
+ private readonly LinkedListNode m_First;
+ private readonly LinkedListNode m_Terminal;
+
+ ///
+ /// 初始化游戏框架链表范围的新实例。
+ ///
+ /// 链表范围的开始结点。
+ /// 链表范围的终结标记结点。
+ public GameFrameworkLinkedListRange(LinkedListNode first, LinkedListNode terminal)
+ {
+ if (first == null || terminal == null || first == terminal)
+ {
+ throw new GameFrameworkException("Range is invalid.");
+ }
+
+ m_First = first;
+ m_Terminal = terminal;
+ }
+
+ ///
+ /// 获取链表范围是否有效。
+ ///
+ public bool IsValid
+ {
+ get
+ {
+ return m_First != null && m_Terminal != null && m_First != m_Terminal;
+ }
+ }
+
+ ///
+ /// 获取链表范围的开始结点。
+ ///
+ public LinkedListNode First
+ {
+ get
+ {
+ return m_First;
+ }
+ }
+
+ ///
+ /// 获取链表范围的终结标记结点。
+ ///
+ public LinkedListNode Terminal
+ {
+ get
+ {
+ return m_Terminal;
+ }
+ }
+
+ ///
+ /// 获取链表范围的结点数量。
+ ///
+ public int Count
+ {
+ get
+ {
+ if (!IsValid)
+ {
+ return 0;
+ }
+
+ int count = 0;
+ for (LinkedListNode current = m_First; current != null && current != m_Terminal; current = current.Next)
+ {
+ count++;
+ }
+
+ return count;
+ }
+ }
+
+ ///
+ /// 检查是否包含指定值。
+ ///
+ /// 要检查的值。
+ /// 是否包含指定值。
+ public bool Contains(T value)
+ {
+ for (LinkedListNode current = m_First; current != null && current != m_Terminal; current = current.Next)
+ {
+ if (current.Value.Equals(value))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// 返回循环访问集合的枚举数。
+ ///
+ /// 循环访问集合的枚举数。
+ public Enumerator GetEnumerator()
+ {
+ return new Enumerator(this);
+ }
+
+ ///
+ /// 返回循环访问集合的枚举数。
+ ///
+ /// 循环访问集合的枚举数。
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ ///
+ /// 返回循环访问集合的枚举数。
+ ///
+ /// 循环访问集合的枚举数。
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ ///
+ /// 循环访问集合的枚举数。
+ ///
+ [StructLayout(LayoutKind.Auto)]
+ public struct Enumerator : IEnumerator, IEnumerator
+ {
+ private readonly GameFrameworkLinkedListRange m_GameFrameworkLinkedListRange;
+ private LinkedListNode m_Current;
+ private T m_CurrentValue;
+
+ internal Enumerator(GameFrameworkLinkedListRange range)
+ {
+ if (!range.IsValid)
+ {
+ throw new GameFrameworkException("Range is invalid.");
+ }
+
+ m_GameFrameworkLinkedListRange = range;
+ m_Current = m_GameFrameworkLinkedListRange.m_First;
+ m_CurrentValue = default(T);
+ }
+
+ ///
+ /// 获取当前结点。
+ ///
+ public T Current
+ {
+ get
+ {
+ return m_CurrentValue;
+ }
+ }
+
+ ///
+ /// 获取当前的枚举数。
+ ///
+ object IEnumerator.Current
+ {
+ get
+ {
+ return m_CurrentValue;
+ }
+ }
+
+ ///
+ /// 清理枚举数。
+ ///
+ public void Dispose()
+ {
+ }
+
+ ///
+ /// 获取下一个结点。
+ ///
+ /// 返回下一个结点。
+ public bool MoveNext()
+ {
+ if (m_Current == null || m_Current == m_GameFrameworkLinkedListRange.m_Terminal)
+ {
+ return false;
+ }
+
+ m_CurrentValue = m_Current.Value;
+ m_Current = m_Current.Next;
+ return true;
+ }
+
+ ///
+ /// 重置枚举数。
+ ///
+ void IEnumerator.Reset()
+ {
+ m_Current = m_GameFrameworkLinkedListRange.m_First;
+ m_CurrentValue = default(T);
+ }
+ }
+ }
+}
diff --git a/Runtime/ABase/Base/DataStruct/GameFrameworkLinkedListRange.cs.meta b/Runtime/ABase/Base/DataStruct/GameFrameworkLinkedListRange.cs.meta
new file mode 100644
index 0000000..72d94f7
--- /dev/null
+++ b/Runtime/ABase/Base/DataStruct/GameFrameworkLinkedListRange.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7edbebe0b1fd848418e2f1d863b611e1
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/ABase/Base/DataStruct/GameFrameworkMultiDictionary.cs b/Runtime/ABase/Base/DataStruct/GameFrameworkMultiDictionary.cs
new file mode 100644
index 0000000..0b68d74
--- /dev/null
+++ b/Runtime/ABase/Base/DataStruct/GameFrameworkMultiDictionary.cs
@@ -0,0 +1,276 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace AlicizaX
+{
+ ///
+ /// 游戏框架多值字典类。
+ ///
+ /// 指定多值字典的主键类型。
+ /// 指定多值字典的值类型。
+ public sealed class GameFrameworkMultiDictionary : IEnumerable>>, IEnumerable
+ {
+ private readonly GameFrameworkLinkedList m_LinkedList;
+ private readonly Dictionary> m_Dictionary;
+
+ ///
+ /// 初始化游戏框架多值字典类的新实例。
+ ///
+ public GameFrameworkMultiDictionary()
+ {
+ m_LinkedList = new GameFrameworkLinkedList();
+ m_Dictionary = new Dictionary>();
+ }
+
+ ///
+ /// 获取多值字典中实际包含的主键数量。
+ ///
+ public int Count
+ {
+ get
+ {
+ return m_Dictionary.Count;
+ }
+ }
+
+ ///
+ /// 获取多值字典中指定主键的范围。
+ ///
+ /// 指定的主键。
+ /// 指定主键的范围。
+ public GameFrameworkLinkedListRange this[TKey key]
+ {
+ get
+ {
+ GameFrameworkLinkedListRange range = default(GameFrameworkLinkedListRange);
+ m_Dictionary.TryGetValue(key, out range);
+ return range;
+ }
+ }
+
+ ///
+ /// 清理多值字典。
+ ///
+ public void Clear()
+ {
+ m_Dictionary.Clear();
+ m_LinkedList.Clear();
+ }
+
+ ///
+ /// 检查多值字典中是否包含指定主键。
+ ///
+ /// 要检查的主键。
+ /// 多值字典中是否包含指定主键。
+ public bool Contains(TKey key)
+ {
+ return m_Dictionary.ContainsKey(key);
+ }
+
+ ///
+ /// 检查多值字典中是否包含指定值。
+ ///
+ /// 要检查的主键。
+ /// 要检查的值。
+ /// 多值字典中是否包含指定值。
+ public bool Contains(TKey key, TValue value)
+ {
+ GameFrameworkLinkedListRange range = default(GameFrameworkLinkedListRange);
+ if (m_Dictionary.TryGetValue(key, out range))
+ {
+ return range.Contains(value);
+ }
+
+ return false;
+ }
+
+ ///
+ /// 尝试获取多值字典中指定主键的范围。
+ ///
+ /// 指定的主键。
+ /// 指定主键的范围。
+ /// 是否获取成功。
+ public bool TryGetValue(TKey key, out GameFrameworkLinkedListRange range)
+ {
+ return m_Dictionary.TryGetValue(key, out range);
+ }
+
+ ///
+ /// 向指定的主键增加指定的值。
+ ///
+ /// 指定的主键。
+ /// 指定的值。
+ public void Add(TKey key, TValue value)
+ {
+ GameFrameworkLinkedListRange range = default(GameFrameworkLinkedListRange);
+ if (m_Dictionary.TryGetValue(key, out range))
+ {
+ m_LinkedList.AddBefore(range.Terminal, value);
+ }
+ else
+ {
+ LinkedListNode first = m_LinkedList.AddLast(value);
+ LinkedListNode terminal = m_LinkedList.AddLast(default(TValue));
+ m_Dictionary.Add(key, new GameFrameworkLinkedListRange(first, terminal));
+ }
+ }
+
+ ///
+ /// 从指定的主键中移除指定的值。
+ ///
+ /// 指定的主键。
+ /// 指定的值。
+ /// 是否移除成功。
+ public bool Remove(TKey key, TValue value)
+ {
+ GameFrameworkLinkedListRange range = default(GameFrameworkLinkedListRange);
+ if (m_Dictionary.TryGetValue(key, out range))
+ {
+ for (LinkedListNode current = range.First; current != null && current != range.Terminal; current = current.Next)
+ {
+ if (current.Value.Equals(value))
+ {
+ if (current == range.First)
+ {
+ LinkedListNode next = current.Next;
+ if (next == range.Terminal)
+ {
+ m_LinkedList.Remove(next);
+ m_Dictionary.Remove(key);
+ }
+ else
+ {
+ m_Dictionary[key] = new GameFrameworkLinkedListRange(next, range.Terminal);
+ }
+ }
+
+ m_LinkedList.Remove(current);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// 从指定的主键中移除所有的值。
+ ///
+ /// 指定的主键。
+ /// 是否移除成功。
+ public bool RemoveAll(TKey key)
+ {
+ GameFrameworkLinkedListRange range = default(GameFrameworkLinkedListRange);
+ if (m_Dictionary.TryGetValue(key, out range))
+ {
+ m_Dictionary.Remove(key);
+
+ LinkedListNode current = range.First;
+ while (current != null)
+ {
+ LinkedListNode next = current != range.Terminal ? current.Next : null;
+ m_LinkedList.Remove(current);
+ current = next;
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// 返回循环访问集合的枚举数。
+ ///
+ /// 循环访问集合的枚举数。
+ public Enumerator GetEnumerator()
+ {
+ return new Enumerator(m_Dictionary);
+ }
+
+ ///
+ /// 返回循环访问集合的枚举数。
+ ///
+ /// 循环访问集合的枚举数。
+ IEnumerator>> IEnumerable>>.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ ///
+ /// 返回循环访问集合的枚举数。
+ ///
+ /// 循环访问集合的枚举数。
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ ///
+ /// 循环访问集合的枚举数。
+ ///
+ [StructLayout(LayoutKind.Auto)]
+ public struct Enumerator : IEnumerator>>, IEnumerator
+ {
+ private Dictionary>.Enumerator m_Enumerator;
+
+ internal Enumerator(Dictionary> dictionary)
+ {
+ if (dictionary == null)
+ {
+ throw new GameFrameworkException("Dictionary is invalid.");
+ }
+
+ m_Enumerator = dictionary.GetEnumerator();
+ }
+
+ ///
+ /// 获取当前结点。
+ ///
+ public KeyValuePair> Current
+ {
+ get
+ {
+ return m_Enumerator.Current;
+ }
+ }
+
+ ///
+ /// 获取当前的枚举数。
+ ///
+ object IEnumerator.Current
+ {
+ get
+ {
+ return m_Enumerator.Current;
+ }
+ }
+
+ ///
+ /// 清理枚举数。
+ ///
+ public void Dispose()
+ {
+ m_Enumerator.Dispose();
+ }
+
+ ///
+ /// 获取下一个结点。
+ ///
+ /// 返回下一个结点。
+ public bool MoveNext()
+ {
+ return m_Enumerator.MoveNext();
+ }
+
+ ///
+ /// 重置枚举数。
+ ///
+ void IEnumerator.Reset()
+ {
+ ((IEnumerator>>)m_Enumerator).Reset();
+ }
+ }
+ }
+}
diff --git a/Runtime/ABase/Base/DataStruct/GameFrameworkMultiDictionary.cs.meta b/Runtime/ABase/Base/DataStruct/GameFrameworkMultiDictionary.cs.meta
new file mode 100644
index 0000000..8e90a86
--- /dev/null
+++ b/Runtime/ABase/Base/DataStruct/GameFrameworkMultiDictionary.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ce20200ce38864c44b02e0c1ebc7fc3a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/ABase/Base/DataStruct/GameFrameworkSerializer.cs b/Runtime/ABase/Base/DataStruct/GameFrameworkSerializer.cs
new file mode 100644
index 0000000..54c79b7
--- /dev/null
+++ b/Runtime/ABase/Base/DataStruct/GameFrameworkSerializer.cs
@@ -0,0 +1,199 @@
+using System.Collections.Generic;
+using System.IO;
+
+namespace AlicizaX
+{
+ ///
+ /// 游戏框架序列化器基类。
+ ///
+ /// 要序列化的数据类型。
+ public abstract class GameFrameworkSerializer
+ {
+ private readonly Dictionary _serializeCallbacks;
+ private readonly Dictionary _deserializeCallbacks;
+ private readonly Dictionary _tryGetValueCallbacks;
+ private byte _latestSerializeCallbackVersion;
+
+ ///
+ /// 初始化游戏框架序列化器基类的新实例。
+ ///
+ [UnityEngine.Scripting.Preserve]
+ public GameFrameworkSerializer()
+ {
+ _serializeCallbacks = new Dictionary();
+ _deserializeCallbacks = new Dictionary();
+ _tryGetValueCallbacks = new Dictionary();
+ _latestSerializeCallbackVersion = 0;
+ }
+
+ ///
+ /// 序列化回调函数。
+ ///
+ /// 目标流。
+ /// 要序列化的数据。
+ /// 是否序列化数据成功。
+ public delegate bool SerializeCallback(Stream stream, T data);
+
+ ///
+ /// 反序列化回调函数。
+ ///
+ /// 指定流。
+ /// 反序列化的数据。
+ public delegate T DeserializeCallback(Stream stream);
+
+ ///
+ /// 尝试从指定流获取指定键的值回调函数。
+ ///
+ /// 指定流。
+ /// 指定键。
+ /// 指定键的值。
+ /// 是否从指定流获取指定键的值成功。
+ public delegate bool TryGetValueCallback(Stream stream, string key, out object value);
+
+ ///
+ /// 注册序列化回调函数。
+ ///
+ /// 序列化回调函数的版本。
+ /// 序列化回调函数。
+ public void RegisterSerializeCallback(byte version, SerializeCallback callback)
+ {
+ if (callback == null)
+ {
+ throw new GameFrameworkException("Serialize callback is invalid.");
+ }
+
+ _serializeCallbacks[version] = callback;
+ if (version > _latestSerializeCallbackVersion)
+ {
+ _latestSerializeCallbackVersion = version;
+ }
+ }
+
+ ///
+ /// 注册反序列化回调函数。
+ ///
+ /// 反序列化回调函数的版本。
+ /// 反序列化回调函数。
+ public void RegisterDeserializeCallback(byte version, DeserializeCallback callback)
+ {
+ if (callback == null)
+ {
+ throw new GameFrameworkException("Deserialize callback is invalid.");
+ }
+
+ _deserializeCallbacks[version] = callback;
+ }
+
+ ///
+ /// 注册尝试从指定流获取指定键的值回调函数。
+ ///
+ /// 尝试从指定流获取指定键的值回调函数的版本。
+ /// 尝试从指定流获取指定键的值回调函数。
+ public void RegisterTryGetValueCallback(byte version, TryGetValueCallback callback)
+ {
+ if (callback == null)
+ {
+ throw new GameFrameworkException("Try get value callback is invalid.");
+ }
+
+ _tryGetValueCallbacks[version] = callback;
+ }
+
+ ///
+ /// 序列化数据到目标流中。
+ ///
+ /// 目标流。
+ /// 要序列化的数据。
+ /// 是否序列化数据成功。
+ public bool Serialize(Stream stream, T data)
+ {
+ if (_serializeCallbacks.Count <= 0)
+ {
+ throw new GameFrameworkException("No serialize callback registered.");
+ }
+
+ return Serialize(stream, data, _latestSerializeCallbackVersion);
+ }
+
+ ///
+ /// 序列化数据到目标流中。
+ ///
+ /// 目标流。
+ /// 要序列化的数据。
+ /// 序列化回调函数的版本。
+ /// 是否序列化数据成功。
+ public bool Serialize(Stream stream, T data, byte version)
+ {
+ byte[] header = GetHeader();
+ stream.WriteByte(header[0]);
+ stream.WriteByte(header[1]);
+ stream.WriteByte(header[2]);
+ stream.WriteByte(version);
+ if (!_serializeCallbacks.TryGetValue(version, out var callback))
+ {
+ throw new GameFrameworkException(Utility.Text.Format("Serialize callback '{0}' is not exist.", version));
+ }
+
+ return callback(stream, data);
+ }
+
+ ///
+ /// 从指定流反序列化数据。
+ ///
+ /// 指定流。
+ /// 反序列化的数据。
+ public T Deserialize(Stream stream)
+ {
+ byte[] header = GetHeader();
+ byte header0 = (byte)stream.ReadByte();
+ byte header1 = (byte)stream.ReadByte();
+ byte header2 = (byte)stream.ReadByte();
+ if (header0 != header[0] || header1 != header[1] || header2 != header[2])
+ {
+ throw new GameFrameworkException(Utility.Text.Format("Header is invalid, need '{0}{1}{2}', current '{3}{4}{5}'.", (char)header[0], (char)header[1], (char)header[2], (char)header0, (char)header1, (char)header2));
+ }
+
+ byte version = (byte)stream.ReadByte();
+ if (!_deserializeCallbacks.TryGetValue(version, out var callback))
+ {
+ throw new GameFrameworkException(Utility.Text.Format("Deserialize callback '{0}' is not exist.", version));
+ }
+
+ return callback(stream);
+ }
+
+ ///
+ /// 尝试从指定流获取指定键的值。
+ ///
+ /// 指定流。
+ /// 指定键。
+ /// 指定键的值。
+ /// 是否从指定流获取指定键的值成功。
+ public bool TryGetValue(Stream stream, string key, out object value)
+ {
+ value = null;
+ byte[] header = GetHeader();
+ byte header0 = (byte)stream.ReadByte();
+ byte header1 = (byte)stream.ReadByte();
+ byte header2 = (byte)stream.ReadByte();
+ if (header0 != header[0] || header1 != header[1] || header2 != header[2])
+ {
+ return false;
+ }
+
+ byte version = (byte)stream.ReadByte();
+ if (!_tryGetValueCallbacks.TryGetValue(version, out var callback))
+ {
+ return false;
+ }
+
+ return callback(stream, key, out value);
+ }
+
+ ///
+ /// 获取数据头标识。
+ ///
+ /// 数据头标识。
+ protected abstract byte[] GetHeader();
+ }
+}
diff --git a/Runtime/ABase/Base/DataStruct/GameFrameworkSerializer.cs.meta b/Runtime/ABase/Base/DataStruct/GameFrameworkSerializer.cs.meta
new file mode 100644
index 0000000..68af999
--- /dev/null
+++ b/Runtime/ABase/Base/DataStruct/GameFrameworkSerializer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2c8469a7db4474e1da10e383bc09e77f
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/ABase/Base/DataStruct/ObjectDontDestroyOnLoad.cs b/Runtime/ABase/Base/DataStruct/ObjectDontDestroyOnLoad.cs
new file mode 100644
index 0000000..b051eb6
--- /dev/null
+++ b/Runtime/ABase/Base/DataStruct/ObjectDontDestroyOnLoad.cs
@@ -0,0 +1,15 @@
+using UnityEngine;
+
+namespace AlicizaX
+{
+ ///
+ /// 标记物体对象为不可销毁
+ ///
+ public sealed class ObjectDontDestroyOnLoad : MonoBehaviour
+ {
+ private void Awake()
+ {
+ DontDestroyOnLoad(this);
+ }
+ }
+}
diff --git a/Runtime/ABase/Base/DataStruct/ObjectDontDestroyOnLoad.cs.meta b/Runtime/ABase/Base/DataStruct/ObjectDontDestroyOnLoad.cs.meta
new file mode 100644
index 0000000..7786d97
--- /dev/null
+++ b/Runtime/ABase/Base/DataStruct/ObjectDontDestroyOnLoad.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: eeb79f91247b409d97a8599f33d97808
+timeCreated: 1730723224
\ No newline at end of file
diff --git a/Runtime/ABase/Base/DataStruct/SingletonManager.cs b/Runtime/ABase/Base/DataStruct/SingletonManager.cs
new file mode 100644
index 0000000..3e00c04
--- /dev/null
+++ b/Runtime/ABase/Base/DataStruct/SingletonManager.cs
@@ -0,0 +1,130 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace AlicizaX
+{
+ ///
+ /// 所有单例的统一管理器。
+ ///
+ [DefaultExecutionOrder(-9999)]
+ public sealed class SingletonManager : MonoBehaviour
+ {
+ private static SingletonManager _instance;
+ private readonly Dictionary _singletons = new();
+
+ public static SingletonManager Instance
+ {
+ get
+ {
+ if (_instance == null)
+ {
+ // 查找是否已有实例
+ _instance = FindFirstObjectByType();
+ if (_instance == null)
+ {
+ var obj = new GameObject("[SingletonManagers]");
+ _instance = obj.AddComponent();
+ DontDestroyOnLoad(obj);
+ }
+ }
+
+ return _instance;
+ }
+ }
+
+ ///
+ /// 注册一个单例。
+ ///
+ internal void Register(T instance) where T : Component
+ {
+ var type = typeof(T);
+ if (!_singletons.ContainsKey(type))
+ {
+ _singletons[type] = instance;
+ instance.transform.SetParent(transform, false);
+ }
+ }
+
+ ///
+ /// 注销单例。
+ ///
+ internal void Unregister(T instance) where T : Component
+ {
+ var type = typeof(T);
+ if (_singletons.TryGetValue(type, out var current) && current == instance)
+ _singletons.Remove(type);
+ }
+
+ ///
+ /// 获取一个已注册的单例(若不存在则返回 null)
+ ///
+ public T Get() where T : Component
+ {
+ _singletons.TryGetValue(typeof(T), out var result);
+ return result as T;
+ }
+ }
+
+
+ ///
+ /// 泛型单例基类。
+ /// 自动注册到 SingletonManager。
+ /// 支持动态创建与销毁。
+ ///
+ public abstract class Singleton : MonoBehaviour where T : Component
+ {
+ private static T _instance;
+ private static readonly object _lock = new();
+
+ public static T Instance
+ {
+ get
+ {
+ if (_instance != null) return _instance;
+
+ lock (_lock)
+ {
+ if (_instance != null) return _instance;
+
+ // 检查 SingletonManager 是否存在
+ var manager = SingletonManager.Instance;
+
+ // 查找场景中是否已经存在该类型
+ _instance = manager.Get();
+ if (_instance == null)
+ {
+ var obj = new GameObject(typeof(T).Name);
+ _instance = obj.AddComponent();
+ manager.Register(_instance);
+ }
+
+ return _instance;
+ }
+ }
+ }
+
+ protected virtual void Awake()
+ {
+ if (_instance == null)
+ {
+ _instance = this as T;
+ SingletonManager.Instance.Register(_instance);
+ }
+ else if (_instance != this)
+ {
+ Destroy(gameObject);
+ return;
+ }
+ }
+
+ protected virtual void OnDestroy()
+ {
+ if (_instance == this)
+ {
+ SingletonManager.Instance.Unregister(this as T);
+ _instance = null;
+ }
+ }
+ }
+}
diff --git a/Runtime/ABase/Base/DataStruct/SingletonManager.cs.meta b/Runtime/ABase/Base/DataStruct/SingletonManager.cs.meta
new file mode 100644
index 0000000..f50a1c4
--- /dev/null
+++ b/Runtime/ABase/Base/DataStruct/SingletonManager.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: e7535f150dae4dbeb5604b01e21e92ed
+timeCreated: 1760162930
\ No newline at end of file
diff --git a/Runtime/ABase/Base/DataStruct/TypeNamePair.cs b/Runtime/ABase/Base/DataStruct/TypeNamePair.cs
new file mode 100644
index 0000000..76aa787
--- /dev/null
+++ b/Runtime/ABase/Base/DataStruct/TypeNamePair.cs
@@ -0,0 +1,122 @@
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace AlicizaX
+{
+ ///
+ /// 类型和名称的组合值。
+ ///
+ [StructLayout(LayoutKind.Auto)]
+ public readonly struct TypeNamePair : IEquatable
+ {
+ private readonly Type m_Type;
+ private readonly string m_Name;
+
+ ///
+ /// 初始化类型和名称的组合值的新实例。
+ ///
+ /// 类型。
+ public TypeNamePair(Type type) : this(type, string.Empty)
+ {
+ }
+
+ ///
+ /// 初始化类型和名称的组合值的新实例。
+ ///
+ /// 类型。
+ /// 名称。
+ public TypeNamePair(Type type, string name)
+ {
+ if (type == null)
+ {
+ throw new GameFrameworkException("Type is invalid.");
+ }
+
+ m_Type = type;
+ m_Name = name ?? string.Empty;
+ }
+
+ ///
+ /// 获取类型。
+ ///
+ public Type Type
+ {
+ get { return m_Type; }
+ }
+
+ ///
+ /// 获取名称。
+ ///
+ public string Name
+ {
+ get { return m_Name; }
+ }
+
+ ///
+ /// 获取类型和名称的组合值字符串。
+ ///
+ /// 类型和名称的组合值字符串。
+ public override string ToString()
+ {
+ if (m_Type == null)
+ {
+ throw new GameFrameworkException("Type is invalid.");
+ }
+
+ string typeName = m_Type.FullName;
+ return (string.IsNullOrEmpty(m_Name) ? typeName : Utility.Text.Format("{0}.{1}", typeName, m_Name)) ?? string.Empty;
+ }
+
+ ///
+ /// 获取对象的哈希值。
+ ///
+ /// 对象的哈希值。
+ public override int GetHashCode()
+ {
+ return m_Type.GetHashCode() ^ m_Name.GetHashCode();
+ }
+
+ ///
+ /// 比较对象是否与自身相等。
+ ///
+ /// 要比较的对象。
+ /// 被比较的对象是否与自身相等。
+ public override bool Equals(object obj)
+ {
+ return obj is TypeNamePair pair && Equals(pair);
+ }
+
+ ///
+ /// 比较对象是否与自身相等。
+ ///
+ /// 要比较的对象。
+ /// 被比较的对象是否与自身相等。
+ public bool Equals(TypeNamePair value)
+ {
+ return m_Type == value.m_Type && m_Name == value.m_Name;
+ }
+
+ ///
+ /// 判断两个对象是否相等。
+ ///
+ /// 值 a。
+ /// 值 b。
+ /// 两个对象是否相等。
+ public static bool operator ==(TypeNamePair a, TypeNamePair b)
+ {
+ return a.Equals(b);
+ }
+
+ ///
+ /// 判断两个对象是否不相等。
+ ///
+ /// 值 a。
+ /// 值 b。
+ /// 两个对象是否不相等。
+ public static bool operator !=(TypeNamePair a, TypeNamePair b)
+ {
+ return !(a == b);
+ }
+ }
+}
diff --git a/Runtime/ABase/Base/DataStruct/TypeNamePair.cs.meta b/Runtime/ABase/Base/DataStruct/TypeNamePair.cs.meta
new file mode 100644
index 0000000..db673ad
--- /dev/null
+++ b/Runtime/ABase/Base/DataStruct/TypeNamePair.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 794a4497a986043dead37960f38aeb52
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/ABase/Base/Exception.meta b/Runtime/ABase/Base/Exception.meta
new file mode 100644
index 0000000..98fde13
--- /dev/null
+++ b/Runtime/ABase/Base/Exception.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 6d2a87a6a5a0464580b7732d8291d278
+timeCreated: 1736424688
\ No newline at end of file
diff --git a/Runtime/ABase/Base/Exception/GameFrameworkException.cs b/Runtime/ABase/Base/Exception/GameFrameworkException.cs
new file mode 100644
index 0000000..2d2294e
--- /dev/null
+++ b/Runtime/ABase/Base/Exception/GameFrameworkException.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace AlicizaX
+{
+ ///
+ /// 游戏框架异常类。
+ ///
+ [Serializable]
+ public class GameFrameworkException : Exception
+ {
+ ///
+ /// 初始化游戏框架异常类的新实例。
+ ///
+ public GameFrameworkException()
+ : base()
+ {
+ }
+
+ ///
+ /// 使用指定错误消息初始化游戏框架异常类的新实例。
+ ///
+ /// 描述错误的消息。
+ public GameFrameworkException(string message)
+ : base(message)
+ {
+ }
+
+ ///
+ /// 使用指定错误消息和对作为此异常原因的内部异常的引用来初始化游戏框架异常类的新实例。
+ ///
+ /// 解释异常原因的错误消息。
+ /// 导致当前异常的异常。如果 innerException 参数不为空引用,则在处理内部异常的 catch 块中引发当前异常。
+ public GameFrameworkException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+
+ ///
+ /// 用序列化数据初始化游戏框架异常类的新实例。
+ ///
+ /// 存有有关所引发异常的序列化的对象数据。
+ /// 包含有关源或目标的上下文信息。
+ protected GameFrameworkException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+ }
+}
diff --git a/Runtime/ABase/Base/Exception/GameFrameworkException.cs.meta b/Runtime/ABase/Base/Exception/GameFrameworkException.cs.meta
new file mode 100644
index 0000000..67e4ca5
--- /dev/null
+++ b/Runtime/ABase/Base/Exception/GameFrameworkException.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ae8e4ebd442c64fbdbbd8bfec15f1962
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/ABase/Base/Exception/GameFrameworkGuard.cs b/Runtime/ABase/Base/Exception/GameFrameworkGuard.cs
new file mode 100644
index 0000000..5b83252
--- /dev/null
+++ b/Runtime/ABase/Base/Exception/GameFrameworkGuard.cs
@@ -0,0 +1,55 @@
+using System;
+
+namespace AlicizaX
+{
+ ///
+ /// 游戏框架异常静态方法
+ ///
+ public static class GameFrameworkGuard
+ {
+ ///
+ /// 确保指定的值不为null。
+ ///
+ /// 要检查的值。
+ /// 值的名称。
+ /// 当值为null时引发。
+ public static void NotNullOrEmpty(string value, string name)
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ throw new ArgumentNullException(name, " can not be null.");
+ }
+ }
+
+ ///
+ /// 确保指定的值不为null。
+ ///
+ /// 值的类型。
+ /// 要检查的值。
+ /// 值的名称。
+ /// 当值为null时引发。
+ public static void NotNull(T value, string name) where T : class
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(name, " can not be null.");
+ }
+ }
+
+ ///
+ /// 检查值是否在指定范围内,如果不在范围内则抛出 ArgumentOutOfRangeException 异常。
+ ///
+ /// 要检查的值。
+ /// 允许的最小值。
+ /// 允许的最大值。
+ /// 值的名称。
+ /// 当值不在指定范围内时抛出。
+ public static void NotRange(int value, int min, int max, string name)
+ {
+ if (value > max || value < min)
+ {
+ throw new ArgumentOutOfRangeException(name, "value must between " + min + " and " + max);
+ }
+ }
+ }
+}
diff --git a/Runtime/ABase/Base/Exception/GameFrameworkGuard.cs.meta b/Runtime/ABase/Base/Exception/GameFrameworkGuard.cs.meta
new file mode 100644
index 0000000..e49307a
--- /dev/null
+++ b/Runtime/ABase/Base/Exception/GameFrameworkGuard.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: ca46f6f46a474facaebe8c44a049485c
+timeCreated: 1702542639
\ No newline at end of file
diff --git a/Runtime/ABase/Base/MemoryPool.meta b/Runtime/ABase/Base/MemoryPool.meta
new file mode 100644
index 0000000..6ff5479
--- /dev/null
+++ b/Runtime/ABase/Base/MemoryPool.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 68c2b9f042834f45893675afb0ba2ecf
+timeCreated: 1742458629
\ No newline at end of file
diff --git a/Runtime/ABase/Base/MemoryPool/IMemory.cs b/Runtime/ABase/Base/MemoryPool/IMemory.cs
new file mode 100644
index 0000000..40b44df
--- /dev/null
+++ b/Runtime/ABase/Base/MemoryPool/IMemory.cs
@@ -0,0 +1,13 @@
+namespace AlicizaX
+{
+ ///
+ /// 内存对象Interface。
+ ///
+ public interface IMemory
+ {
+ ///
+ /// 清理内存对象回收入池。
+ ///
+ void Clear();
+ }
+}
diff --git a/Runtime/ABase/Base/MemoryPool/IMemory.cs.meta b/Runtime/ABase/Base/MemoryPool/IMemory.cs.meta
new file mode 100644
index 0000000..0385e9b
--- /dev/null
+++ b/Runtime/ABase/Base/MemoryPool/IMemory.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 29d6300317e70b24381120ac8f5b0a92
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/ABase/Base/MemoryPool/MemoryPool.MemoryCollection.cs b/Runtime/ABase/Base/MemoryPool/MemoryPool.MemoryCollection.cs
new file mode 100644
index 0000000..f550e88
--- /dev/null
+++ b/Runtime/ABase/Base/MemoryPool/MemoryPool.MemoryCollection.cs
@@ -0,0 +1,156 @@
+using System;
+using System.Collections.Generic;
+
+namespace AlicizaX
+{
+ public static partial class MemoryPool
+ {
+ ///
+ /// 内存池收集器。
+ ///
+ private sealed class MemoryCollection
+ {
+ private readonly Queue _memories;
+ private readonly Type _memoryType;
+ private int _usingMemoryCount;
+ private int _acquireMemoryCount;
+ private int _releaseMemoryCount;
+ private int _addMemoryCount;
+ private int _removeMemoryCount;
+
+ public MemoryCollection(Type memoryType)
+ {
+ _memories = new Queue();
+ _memoryType = memoryType;
+ _usingMemoryCount = 0;
+ _acquireMemoryCount = 0;
+ _releaseMemoryCount = 0;
+ _addMemoryCount = 0;
+ _removeMemoryCount = 0;
+ }
+
+ public Type MemoryType => _memoryType;
+
+ public int UnusedMemoryCount => _memories.Count;
+
+ public int UsingMemoryCount => _usingMemoryCount;
+
+ public int AcquireMemoryCount => _acquireMemoryCount;
+
+ public int ReleaseMemoryCount => _releaseMemoryCount;
+
+ public int AddMemoryCount => _addMemoryCount;
+
+ public int RemoveMemoryCount => _removeMemoryCount;
+
+ public T Acquire() where T : class, IMemory, new()
+ {
+ if (typeof(T) != _memoryType)
+ {
+ throw new Exception("Type is invalid.");
+ }
+
+ _usingMemoryCount++;
+ _acquireMemoryCount++;
+ lock (_memories)
+ {
+ if (_memories.Count > 0)
+ {
+ return (T)_memories.Dequeue();
+ }
+ }
+
+ _addMemoryCount++;
+ return new T();
+ }
+
+ public IMemory Acquire()
+ {
+ _usingMemoryCount++;
+ _acquireMemoryCount++;
+ lock (_memories)
+ {
+ if (_memories.Count > 0)
+ {
+ return _memories.Dequeue();
+ }
+ }
+
+ _addMemoryCount++;
+ return (IMemory)Activator.CreateInstance(_memoryType);
+ }
+
+ public void Release(IMemory memory)
+ {
+ memory.Clear();
+ lock (_memories)
+ {
+ if (_enableStrictCheck && _memories.Contains(memory))
+ {
+ throw new Exception("The memory has been released.");
+ }
+
+ _memories.Enqueue(memory);
+ }
+
+ _releaseMemoryCount++;
+ _usingMemoryCount--;
+ }
+
+ public void Add(int count) where T : class, IMemory, new()
+ {
+ if (typeof(T) != _memoryType)
+ {
+ throw new Exception("Type is invalid.");
+ }
+
+ lock (_memories)
+ {
+ _addMemoryCount += count;
+ while (count-- > 0)
+ {
+ _memories.Enqueue(new T());
+ }
+ }
+ }
+
+ public void Add(int count)
+ {
+ lock (_memories)
+ {
+ _addMemoryCount += count;
+ while (count-- > 0)
+ {
+ _memories.Enqueue((IMemory)Activator.CreateInstance(_memoryType));
+ }
+ }
+ }
+
+ public void Remove(int count)
+ {
+ lock (_memories)
+ {
+ if (count > _memories.Count)
+ {
+ count = _memories.Count;
+ }
+
+ _removeMemoryCount += count;
+ while (count-- > 0)
+ {
+ _memories.Dequeue();
+ }
+ }
+ }
+
+ public void RemoveAll()
+ {
+ lock (_memories)
+ {
+ _removeMemoryCount += _memories.Count;
+ _memories.Clear();
+ }
+ }
+ }
+ }
+}
diff --git a/Runtime/ABase/Base/MemoryPool/MemoryPool.MemoryCollection.cs.meta b/Runtime/ABase/Base/MemoryPool/MemoryPool.MemoryCollection.cs.meta
new file mode 100644
index 0000000..3cab765
--- /dev/null
+++ b/Runtime/ABase/Base/MemoryPool/MemoryPool.MemoryCollection.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e4ec5f33991c55f41bceefbdb8089ae2
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/ABase/Base/MemoryPool/MemoryPool.cs b/Runtime/ABase/Base/MemoryPool/MemoryPool.cs
new file mode 100644
index 0000000..704429c
--- /dev/null
+++ b/Runtime/ABase/Base/MemoryPool/MemoryPool.cs
@@ -0,0 +1,207 @@
+using System;
+using System.Collections.Generic;
+
+namespace AlicizaX
+{
+ ///
+ /// 内存池。
+ ///
+ public static partial class MemoryPool
+ {
+ private static readonly Dictionary _memoryCollections = new Dictionary();
+ private static bool _enableStrictCheck = false;
+
+ ///
+ /// 获取或设置是否开启强制检查。
+ ///
+ public static bool EnableStrictCheck
+ {
+ get => _enableStrictCheck;
+ set => _enableStrictCheck = value;
+ }
+
+ ///
+ /// 获取内存池的数量。
+ ///
+ // ReSharper disable once InconsistentlySynchronizedField
+ public static int Count => _memoryCollections.Count;
+
+ ///
+ /// 获取所有内存池的信息。
+ ///
+ /// 所有内存池的信息。
+ public static MemoryPoolInfo[] GetAllMemoryPoolInfos()
+ {
+ int index = 0;
+ MemoryPoolInfo[] results = null;
+
+ lock (_memoryCollections)
+ {
+ results = new MemoryPoolInfo[_memoryCollections.Count];
+ foreach (KeyValuePair memoryCollection in _memoryCollections)
+ {
+ results[index++] = new MemoryPoolInfo(memoryCollection.Key, memoryCollection.Value.UnusedMemoryCount, memoryCollection.Value.UsingMemoryCount, memoryCollection.Value.AcquireMemoryCount, memoryCollection.Value.ReleaseMemoryCount, memoryCollection.Value.AddMemoryCount, memoryCollection.Value.RemoveMemoryCount);
+ }
+ }
+
+ return results;
+ }
+
+ ///
+ /// 清除所有内存池。
+ ///
+ public static void ClearAll()
+ {
+ lock (_memoryCollections)
+ {
+ foreach (KeyValuePair memoryCollection in _memoryCollections)
+ {
+ memoryCollection.Value.RemoveAll();
+ }
+
+ _memoryCollections.Clear();
+ }
+ }
+
+ ///
+ /// 从内存池获取内存对象。
+ ///
+ /// 内存对象类型。
+ /// 内存对象。
+ public static T Acquire() where T : class, IMemory, new()
+ {
+ return GetMemoryCollection(typeof(T)).Acquire();
+ }
+
+ ///
+ /// 从内存池获取内存对象。
+ ///
+ /// 内存对象类型。
+ /// 内存对象。
+ public static IMemory Acquire(Type memoryType)
+ {
+ InternalCheckMemoryType(memoryType);
+ return GetMemoryCollection(memoryType).Acquire();
+ }
+
+ ///
+ /// 将内存对象归还内存池。
+ ///
+ /// 内存对象。
+ public static void Release(IMemory memory)
+ {
+ if (memory == null)
+ {
+ throw new Exception("Memory is invalid.");
+ }
+
+ Type memoryType = memory.GetType();
+ InternalCheckMemoryType(memoryType);
+ GetMemoryCollection(memoryType).Release(memory);
+ }
+
+ ///
+ /// 向内存池中追加指定数量的内存对象。
+ ///
+ /// 内存对象类型。
+ /// 追加数量。
+ public static void Add(int count) where T : class, IMemory, new()
+ {
+ GetMemoryCollection(typeof(T)).Add(count);
+ }
+
+ ///
+ /// 向内存池中追加指定数量的内存对象。
+ ///
+ /// 内存对象类型。
+ /// 追加数量。
+ public static void Add(Type memoryType, int count)
+ {
+ InternalCheckMemoryType(memoryType);
+ GetMemoryCollection(memoryType).Add(count);
+ }
+
+ ///
+ /// 从内存池中移除指定数量的内存对象。
+ ///
+ /// 内存对象类型。
+ /// 移除数量。
+ public static void Remove(int count) where T : class, IMemory
+ {
+ GetMemoryCollection(typeof(T)).Remove(count);
+ }
+
+ ///
+ /// 从内存池中移除指定数量的内存对象。
+ ///
+ /// 内存对象类型。
+ /// 移除数量。
+ public static void Remove(Type memoryType, int count)
+ {
+ InternalCheckMemoryType(memoryType);
+ GetMemoryCollection(memoryType).Remove(count);
+ }
+
+ ///
+ /// 从内存池中移除所有的内存对象。
+ ///
+ /// 内存对象类型。
+ public static void RemoveAll() where T : class, IMemory
+ {
+ GetMemoryCollection(typeof(T)).RemoveAll();
+ }
+
+ ///
+ /// 从内存池中移除所有的内存对象。
+ ///
+ /// 内存对象类型。
+ public static void RemoveAll(Type memoryType)
+ {
+ InternalCheckMemoryType(memoryType);
+ GetMemoryCollection(memoryType).RemoveAll();
+ }
+
+ private static void InternalCheckMemoryType(Type memoryType)
+ {
+ if (!_enableStrictCheck)
+ {
+ return;
+ }
+
+ if (memoryType == null)
+ {
+ throw new Exception("Memory type is invalid.");
+ }
+
+ if (!memoryType.IsClass || memoryType.IsAbstract)
+ {
+ throw new Exception("Memory type is not a non-abstract class type.");
+ }
+
+ if (!typeof(IMemory).IsAssignableFrom(memoryType))
+ {
+ throw new Exception(string.Format("Memory type '{0}' is invalid.", memoryType.FullName));
+ }
+ }
+
+ private static MemoryCollection GetMemoryCollection(Type memoryType)
+ {
+ if (memoryType == null)
+ {
+ throw new Exception("MemoryType is invalid.");
+ }
+
+ MemoryCollection memoryCollection = null;
+ lock (_memoryCollections)
+ {
+ if (!_memoryCollections.TryGetValue(memoryType, out memoryCollection))
+ {
+ memoryCollection = new MemoryCollection(memoryType);
+ _memoryCollections.Add(memoryType, memoryCollection);
+ }
+ }
+
+ return memoryCollection;
+ }
+ }
+}
diff --git a/Runtime/ABase/Base/MemoryPool/MemoryPool.cs.meta b/Runtime/ABase/Base/MemoryPool/MemoryPool.cs.meta
new file mode 100644
index 0000000..422a910
--- /dev/null
+++ b/Runtime/ABase/Base/MemoryPool/MemoryPool.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f253bd3093bf78d45ad11b73c134e9ff
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/ABase/Base/MemoryPool/MemoryPoolExtension.cs b/Runtime/ABase/Base/MemoryPool/MemoryPoolExtension.cs
new file mode 100644
index 0000000..612bb83
--- /dev/null
+++ b/Runtime/ABase/Base/MemoryPool/MemoryPoolExtension.cs
@@ -0,0 +1,57 @@
+using System;
+
+namespace AlicizaX
+{
+ ///
+ /// 内存池对象基类。
+ ///
+ public abstract class MemoryObject : IMemory
+ {
+ ///
+ /// 清理内存对象回收入池。
+ ///
+ public virtual void Clear()
+ {
+ }
+
+ ///
+ /// 从内存池中初始化。
+ ///
+ public abstract void InitFromPool();
+
+ ///
+ /// 回收到内存池。
+ ///
+ public abstract void RecycleToPool();
+ }
+
+ public static partial class MemoryPool
+ {
+ ///
+ /// 从内存池获取内存对象。
+ ///
+ /// 内存对象类型。
+ /// 内存对象。
+ public static T Alloc() where T : MemoryObject, new()
+ {
+ T memory = Acquire();
+ memory.InitFromPool();
+ return memory;
+ }
+
+ ///
+ /// 将内存对象归还内存池。
+ ///
+ /// 内存对象。
+ public static void Dealloc(MemoryObject memory)
+ {
+ if (memory == null)
+ {
+ throw new Exception("Memory is invalid.");
+ }
+
+ memory.RecycleToPool();
+ Release(memory);
+ }
+ }
+}
diff --git a/Runtime/ABase/Base/MemoryPool/MemoryPoolExtension.cs.meta b/Runtime/ABase/Base/MemoryPool/MemoryPoolExtension.cs.meta
new file mode 100644
index 0000000..2d4491f
--- /dev/null
+++ b/Runtime/ABase/Base/MemoryPool/MemoryPoolExtension.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: ad13ef73c22340058c4420733a22b580
+timeCreated: 1701273442
\ No newline at end of file
diff --git a/Runtime/ABase/Base/MemoryPool/MemoryPoolInfo.cs b/Runtime/ABase/Base/MemoryPool/MemoryPoolInfo.cs
new file mode 100644
index 0000000..85591b1
--- /dev/null
+++ b/Runtime/ABase/Base/MemoryPool/MemoryPoolInfo.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace AlicizaX
+{
+ ///
+ /// 内存池信息。
+ ///
+ [StructLayout(LayoutKind.Auto)]
+ public struct MemoryPoolInfo
+ {
+ private readonly Type _type;
+ private readonly int _unusedMemoryCount;
+ private readonly int _usingMemoryCount;
+ private readonly int _acquireMemoryCount;
+ private readonly int _releaseMemoryCount;
+ private readonly int _addMemoryCount;
+ private readonly int _removeMemoryCount;
+
+ ///
+ /// 初始化内存池信息的新实例。
+ ///
+ /// 内存池类型。
+ /// 未使用内存对象数量。
+ /// 正在使用内存对象数量。
+ /// 获取内存对象数量。
+ /// 归还内存对象数量。
+ /// 增加内存对象数量。
+ /// 移除内存对象数量。
+ public MemoryPoolInfo(Type type, int unusedMemoryCount, int usingMemoryCount, int acquireMemoryCount, int releaseMemoryCount, int addMemoryCount, int removeMemoryCount)
+ {
+ _type = type;
+ _unusedMemoryCount = unusedMemoryCount;
+ _usingMemoryCount = usingMemoryCount;
+ _acquireMemoryCount = acquireMemoryCount;
+ _releaseMemoryCount = releaseMemoryCount;
+ _addMemoryCount = addMemoryCount;
+ _removeMemoryCount = removeMemoryCount;
+ }
+
+ ///
+ /// 获取内存池类型。
+ ///
+ public Type Type
+ {
+ get
+ {
+ return _type;
+ }
+ }
+
+ ///
+ /// 获取未使用内存对象数量。
+ ///
+ public int UnusedMemoryCount
+ {
+ get
+ {
+ return _unusedMemoryCount;
+ }
+ }
+
+ ///
+ /// 获取正在使用内存对象数量。
+ ///
+ public int UsingMemoryCount
+ {
+ get
+ {
+ return _usingMemoryCount;
+ }
+ }
+
+ ///
+ /// 获取获取内存对象数量。
+ ///
+ public int AcquireMemoryCount
+ {
+ get
+ {
+ return _acquireMemoryCount;
+ }
+ }
+
+ ///
+ /// 获取归还内存对象数量。
+ ///
+ public int ReleaseMemoryCount
+ {
+ get
+ {
+ return _releaseMemoryCount;
+ }
+ }
+
+ ///
+ /// 获取增加内存对象数量。
+ ///
+ public int AddMemoryCount
+ {
+ get
+ {
+ return _addMemoryCount;
+ }
+ }
+
+ ///
+ /// 获取移除内存对象数量。
+ ///
+ public int RemoveMemoryCount
+ {
+ get
+ {
+ return _removeMemoryCount;
+ }
+ }
+ }
+}
diff --git a/Runtime/ABase/Base/MemoryPool/MemoryPoolInfo.cs.meta b/Runtime/ABase/Base/MemoryPool/MemoryPoolInfo.cs.meta
new file mode 100644
index 0000000..f91a6b4
--- /dev/null
+++ b/Runtime/ABase/Base/MemoryPool/MemoryPoolInfo.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 444aa83c59c8f0445894e785975b5463
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/ABase/Base/MemoryPool/MemoryPoolSetting.cs b/Runtime/ABase/Base/MemoryPool/MemoryPoolSetting.cs
new file mode 100644
index 0000000..bc60dab
--- /dev/null
+++ b/Runtime/ABase/Base/MemoryPool/MemoryPoolSetting.cs
@@ -0,0 +1,78 @@
+using UnityEngine;
+
+namespace AlicizaX
+{
+ ///
+ /// 内存强制检查类型。
+ ///
+ public enum MemoryStrictCheckType : byte
+ {
+ ///
+ /// 总是启用。
+ ///
+ AlwaysEnable = 0,
+
+ ///
+ /// 仅在开发模式时启用。
+ ///
+ OnlyEnableWhenDevelopment,
+
+ ///
+ /// 仅在编辑器中启用。
+ ///
+ OnlyEnableInEditor,
+
+ ///
+ /// 总是禁用。
+ ///
+ AlwaysDisable,
+ }
+
+ ///
+ /// 内存池模块。
+ ///
+ [DisallowMultipleComponent]
+ public sealed class MemoryPoolSetting : MonoBehaviour
+ {
+ [SerializeField]
+ private MemoryStrictCheckType m_EnableStrictCheck = MemoryStrictCheckType.OnlyEnableWhenDevelopment;
+
+ ///
+ /// 获取或设置是否开启强制检查。
+ ///
+ public bool EnableStrictCheck
+ {
+ get => MemoryPool.EnableStrictCheck;
+ set
+ {
+ MemoryPool.EnableStrictCheck = value;
+ if (value)
+ {
+ Log.Info("Strict checking is enabled for the Memory Pool. It will drastically affect the performance.");
+ }
+ }
+ }
+
+ private void Start()
+ {
+ switch (m_EnableStrictCheck)
+ {
+ case MemoryStrictCheckType.AlwaysEnable:
+ EnableStrictCheck = true;
+ break;
+
+ case MemoryStrictCheckType.OnlyEnableWhenDevelopment:
+ EnableStrictCheck = Debug.isDebugBuild;
+ break;
+
+ case MemoryStrictCheckType.OnlyEnableInEditor:
+ EnableStrictCheck = Application.isEditor;
+ break;
+
+ default:
+ EnableStrictCheck = false;
+ break;
+ }
+ }
+ }
+}
diff --git a/Runtime/ABase/Base/MemoryPool/MemoryPoolSetting.cs.meta b/Runtime/ABase/Base/MemoryPool/MemoryPoolSetting.cs.meta
new file mode 100644
index 0000000..dd7ae56
--- /dev/null
+++ b/Runtime/ABase/Base/MemoryPool/MemoryPoolSetting.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 61474d279eb27214d9178822796f3b88
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/ABase/Base/Module.meta b/Runtime/ABase/Base/Module.meta
new file mode 100644
index 0000000..b324d54
--- /dev/null
+++ b/Runtime/ABase/Base/Module.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: aeb86cc258fa4378ba3b259a5d38e4df
+timeCreated: 1736424998
\ No newline at end of file
diff --git a/Runtime/ABase/Base/Module/IModule.cs b/Runtime/ABase/Base/Module/IModule.cs
new file mode 100644
index 0000000..5c390bf
--- /dev/null
+++ b/Runtime/ABase/Base/Module/IModule.cs
@@ -0,0 +1,42 @@
+namespace AlicizaX
+{
+ public interface IModule
+ {
+ protected internal void Dispose();
+ }
+
+ public interface IModuleAwake
+ {
+ protected internal void Awake();
+ }
+
+ public interface IExecuteSystem
+ {
+ abstract int Priority { get; }
+ }
+
+ public interface IModuleUpdate : IExecuteSystem
+ {
+ protected internal void Update(float elapseSeconds, float realElapseSeconds);
+ }
+
+ public interface IModuleLateUpdate : IExecuteSystem
+ {
+ protected internal void LateUpdate();
+ }
+
+ public interface IModuleFixedUpdate : IExecuteSystem
+ {
+ protected internal void FixedUpdate();
+ }
+
+ public interface IModuleDrawGizmos : IExecuteSystem
+ {
+ protected internal void DrawGizmos();
+ }
+
+ public interface IModuleGUI : IExecuteSystem
+ {
+ protected internal void OnGUI();
+ }
+}
diff --git a/Runtime/ABase/Base/Module/IModule.cs.meta b/Runtime/ABase/Base/Module/IModule.cs.meta
new file mode 100644
index 0000000..e93bf72
--- /dev/null
+++ b/Runtime/ABase/Base/Module/IModule.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 0ba192d5e9084d67b16b53e843e103b3
+timeCreated: 1736424999
\ No newline at end of file
diff --git a/Runtime/ABase/Base/Module/ModuleSystem.cs b/Runtime/ABase/Base/Module/ModuleSystem.cs
new file mode 100644
index 0000000..2898c70
--- /dev/null
+++ b/Runtime/ABase/Base/Module/ModuleSystem.cs
@@ -0,0 +1,303 @@
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+
+namespace AlicizaX
+{
+ public static class ModuleSystem
+ {
+ internal const int DesignModuleCount = 32;
+
+ private static readonly Dictionary _moduleMaps = new Dictionary(DesignModuleCount);
+ private static readonly GameFrameworkLinkedList _modules = new GameFrameworkLinkedList();
+
+ // Update systems
+ private static readonly GameFrameworkLinkedList _updateModules = new GameFrameworkLinkedList();
+ private static readonly IModuleUpdate[] _updateExecuteArray = new IModuleUpdate[DesignModuleCount];
+ private static int _updateExecuteCount;
+
+ // LateUpdate systems
+ private static readonly GameFrameworkLinkedList _lateUpdateModules = new GameFrameworkLinkedList();
+ private static readonly IModuleLateUpdate[] _lateUpdateExecuteArray = new IModuleLateUpdate[DesignModuleCount];
+ private static int _lateUpdateExecuteCount;
+
+ // FixedUpdate systems
+ private static readonly GameFrameworkLinkedList _fixedUpdateModules = new GameFrameworkLinkedList();
+ private static readonly IModuleFixedUpdate[] _fixedUpdateExecuteArray = new IModuleFixedUpdate[DesignModuleCount];
+ private static int _fixedUpdateExecuteCount;
+
+ // Gizmos systems
+ private static readonly GameFrameworkLinkedList _gizmosUpdateModules = new GameFrameworkLinkedList();
+ private static readonly IModuleDrawGizmos[] _gizmosUpdateExecuteArray = new IModuleDrawGizmos[DesignModuleCount];
+ private static int _gizmosExecuteCount;
+
+ // GUI systems
+ private static readonly GameFrameworkLinkedList _guiUpdateModules = new GameFrameworkLinkedList();
+ private static readonly IModuleGUI[] _guiUpdateExecuteArray = new IModuleGUI[DesignModuleCount];
+ private static int _guiExecuteCount;
+
+ // Dirty flags
+ private static bool _isExecuteListDirty;
+ private static bool _isLateExecuteListDirty;
+ private static bool _isFixedExecuteListDirty;
+ private static bool _isGizmosExecuteListDirty;
+ private static bool _isGUIExecuteListDirty;
+
+ internal static void Dispose()
+ {
+ // Clear all modules and execute arrays
+ _updateModules.Clear();
+ Array.Clear(_updateExecuteArray, 0, _updateExecuteArray.Length);
+ _updateExecuteCount = 0;
+
+ _lateUpdateModules.Clear();
+ Array.Clear(_lateUpdateExecuteArray, 0, _lateUpdateExecuteArray.Length);
+ _lateUpdateExecuteCount = 0;
+
+ _fixedUpdateModules.Clear();
+ Array.Clear(_fixedUpdateExecuteArray, 0, _fixedUpdateExecuteArray.Length);
+ _fixedUpdateExecuteCount = 0;
+
+ _gizmosUpdateModules.Clear();
+ Array.Clear(_gizmosUpdateExecuteArray, 0, _gizmosUpdateExecuteArray.Length);
+ _gizmosExecuteCount = 0;
+
+ _guiUpdateModules.Clear();
+ Array.Clear(_guiUpdateExecuteArray, 0, _guiUpdateExecuteArray.Length);
+ _guiExecuteCount = 0;
+
+ // Dispose all modules
+ for (var current = _modules.Last; current != null; current = current.Previous)
+ {
+ current.Value.Dispose();
+ }
+
+ _modules.Clear();
+ _moduleMaps.Clear();
+ }
+
+ #region Public System
+
+ public static T RegisterModule() where T : IModule where TImple : class, T, new()
+ {
+ Type interfaceType = typeof(T);
+ Type implType = typeof(TImple);
+ if (!interfaceType.IsInterface)
+ {
+ throw new GameFrameworkException(Utility.Text.Format("You must register module by interface, but '{0}' is not.", interfaceType.FullName));
+ }
+
+ if (!implType.IsClass || implType.IsInterface || implType.IsAbstract)
+ {
+ throw new GameFrameworkException(Utility.Text.Format("You must register module by Class and not Interface and Abstract, but '{0}' is not.", implType.FullName));
+ }
+
+ if (!typeof(IModule).IsAssignableFrom(interfaceType))
+ {
+ throw new GameFrameworkException(Utility.Text.Format("Module must implement IModule.", interfaceType.FullName));
+ }
+
+ if (GetModule(interfaceType) != null)
+ {
+ Log.Error("Already Register {0}", interfaceType.FullName);
+ return default;
+ }
+
+ TImple impl = new TImple();
+ return (T)SetModuleInstance(interfaceType, impl);
+ }
+
+ public static T GetModule() where T : class
+ {
+ Type interfaceType = typeof(T);
+ if (!interfaceType.IsInterface)
+ {
+ throw new GameFrameworkException(Utility.Text.Format("You must get module by interface, but '{0}' is not.", interfaceType.FullName));
+ }
+
+ return GetModule(interfaceType) as T;
+ }
+
+ public static IModule GetModule(Type moduleType)
+ {
+ return _moduleMaps.TryGetValue(moduleType, out var ret) ? ret : default;
+ }
+
+ private static IModule SetModuleInstance(Type interfaceType, TImpl impl) where TImpl : class, IModule
+ {
+ _moduleMaps[interfaceType] = impl;
+ _modules.AddLast(impl);
+
+ if (impl is IModuleAwake awakeSystem)
+ {
+ awakeSystem.Awake();
+ }
+
+ CheckSystemLife(impl);
+ return impl;
+ }
+
+ #endregion
+
+ private static void CheckSystemLife(IModule module)
+ {
+ if (module is IModuleUpdate updateSystem)
+ {
+ BindSystemLife(updateSystem, _updateModules, ref _isExecuteListDirty);
+ }
+
+ if (module is IModuleLateUpdate lateUpdate)
+ {
+ BindSystemLife(lateUpdate, _lateUpdateModules, ref _isLateExecuteListDirty);
+ }
+
+ if (module is IModuleFixedUpdate fixedUpdate)
+ {
+ BindSystemLife(fixedUpdate, _fixedUpdateModules, ref _isFixedExecuteListDirty);
+ }
+
+ if (module is IModuleDrawGizmos drawGizmosUpdate)
+ {
+ BindSystemLife(drawGizmosUpdate, _gizmosUpdateModules, ref _isGizmosExecuteListDirty);
+ }
+
+ if (module is IModuleGUI guiUpdate)
+ {
+ BindSystemLife(guiUpdate, _guiUpdateModules, ref _isGUIExecuteListDirty);
+ }
+ }
+
+ private static void BindSystemLife(T system, GameFrameworkLinkedList updateModule, ref bool executeDirty) where T : IExecuteSystem
+ {
+ var current = updateModule.First;
+ while (current != null && system.Priority <= current.Value.Priority)
+ {
+ current = current.Next;
+ }
+
+ if (current != null)
+ {
+ updateModule.AddBefore(current, system);
+ }
+ else
+ {
+ updateModule.AddLast(system);
+ }
+
+ executeDirty = true;
+ }
+
+ #region BuildExecuteList
+
+ private static void BuildExecuteList()
+ {
+ if (!_isExecuteListDirty) return;
+ _isExecuteListDirty = false;
+ _updateExecuteCount = 0;
+ foreach (var module in _updateModules)
+ {
+ if (_updateExecuteCount >= _updateExecuteArray.Length) break;
+ _updateExecuteArray[_updateExecuteCount++] = module;
+ }
+ }
+
+ private static void BuildLateExecuteList()
+ {
+ if (!_isLateExecuteListDirty) return;
+ _isLateExecuteListDirty = false;
+ _lateUpdateExecuteCount = 0;
+ foreach (var module in _lateUpdateModules)
+ {
+ if (_lateUpdateExecuteCount >= _lateUpdateExecuteArray.Length) break;
+ _lateUpdateExecuteArray[_lateUpdateExecuteCount++] = module;
+ }
+ }
+
+ private static void BuildFixedExecuteList()
+ {
+ if (!_isFixedExecuteListDirty) return;
+ _isFixedExecuteListDirty = false;
+ _fixedUpdateExecuteCount = 0;
+ foreach (var module in _fixedUpdateModules)
+ {
+ if (_fixedUpdateExecuteCount >= _fixedUpdateExecuteArray.Length) break;
+ _fixedUpdateExecuteArray[_fixedUpdateExecuteCount++] = module;
+ }
+ }
+
+ private static void BuildGizmosExecuteList()
+ {
+ if (!_isGizmosExecuteListDirty) return;
+ _isGizmosExecuteListDirty = false;
+ _gizmosExecuteCount = 0;
+ foreach (var module in _gizmosUpdateModules)
+ {
+ if (_gizmosExecuteCount >= _gizmosUpdateExecuteArray.Length) break;
+ _gizmosUpdateExecuteArray[_gizmosExecuteCount++] = module;
+ }
+ }
+
+ private static void BuildGUIExecuteList()
+ {
+ if (!_isGUIExecuteListDirty) return;
+ _isGUIExecuteListDirty = false;
+ _guiExecuteCount = 0;
+ foreach (var module in _guiUpdateModules)
+ {
+ if (_guiExecuteCount >= _guiUpdateExecuteArray.Length) break;
+ _guiUpdateExecuteArray[_guiExecuteCount++] = module;
+ }
+ }
+
+ #endregion
+
+ #region RunExecuteList
+
+ internal static void UpdateExecuteList(float elapseSeconds, float realElapseSeconds)
+ {
+ BuildExecuteList();
+ for (int i = 0; i < _updateExecuteCount; i++)
+ {
+ _updateExecuteArray[i].Update(elapseSeconds, realElapseSeconds);
+ }
+ }
+
+ internal static void UpdateLateExecuteList()
+ {
+ BuildLateExecuteList();
+ for (int i = 0; i < _lateUpdateExecuteCount; i++)
+ {
+ _lateUpdateExecuteArray[i].LateUpdate();
+ }
+ }
+
+ internal static void UpdateFixedExecuteList()
+ {
+ BuildFixedExecuteList();
+ for (int i = 0; i < _fixedUpdateExecuteCount; i++)
+ {
+ _fixedUpdateExecuteArray[i].FixedUpdate();
+ }
+ }
+
+ internal static void UpdateGizmosExecuteList()
+ {
+ BuildGizmosExecuteList();
+ for (int i = 0; i < _gizmosExecuteCount; i++)
+ {
+ _gizmosUpdateExecuteArray[i].DrawGizmos();
+ }
+ }
+
+ internal static void UpdateGUIExecuteList()
+ {
+ BuildGUIExecuteList();
+ for (int i = 0; i < _guiExecuteCount; i++)
+ {
+ _guiUpdateExecuteArray[i].OnGUI();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Runtime/ABase/Base/Module/ModuleSystem.cs.meta b/Runtime/ABase/Base/Module/ModuleSystem.cs.meta
new file mode 100644
index 0000000..35bbf77
--- /dev/null
+++ b/Runtime/ABase/Base/Module/ModuleSystem.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: b9fa1d3abb954b57989b94b4c77e45ce
+timeCreated: 1736928479
\ No newline at end of file
diff --git a/Runtime/ABase/Base/RootModule.cs b/Runtime/ABase/Base/RootModule.cs
new file mode 100644
index 0000000..6c24d3c
--- /dev/null
+++ b/Runtime/ABase/Base/RootModule.cs
@@ -0,0 +1,220 @@
+using System;
+using AlicizaX.ObjectPool;
+using Cysharp.Threading.Tasks;
+using UnityEngine;
+
+namespace AlicizaX
+{
+ ///
+ /// 基础组件。
+ ///
+ [DisallowMultipleComponent]
+ [UnityEngine.Scripting.Preserve]
+ public sealed class RootModule : MonoBehaviour
+ {
+ private static RootModule _instance = null;
+
+ public static RootModule Instance
+ {
+ get
+ {
+ if (_instance == null)
+ {
+ _instance = FindObjectOfType();
+ }
+
+ return _instance;
+ }
+ }
+
+ private const int DEFAULT_DPI = 96;
+
+ private float _gameSpeedBeforePause = 1f;
+
+ [SerializeField] private int frameRate = 120;
+
+ [SerializeField] private float gameSpeed = 1f;
+
+ [SerializeField] private bool runInBackground = true;
+
+ [SerializeField] private bool neverSleep = true;
+
+
+ ///
+ /// 获取或设置游戏帧率。
+ ///
+ public int FrameRate
+ {
+ get => frameRate;
+ set => Application.targetFrameRate = frameRate = value;
+ }
+
+ ///
+ /// 获取或设置游戏速度。
+ ///
+ public float GameSpeed
+ {
+ get => gameSpeed;
+ set => Time.timeScale = gameSpeed = value >= 0f ? value : 0f;
+ }
+
+ ///
+ /// 获取游戏是否暂停。
+ ///
+ public bool IsGamePaused => gameSpeed <= 0f;
+
+ ///
+ /// 获取是否正常游戏速度。
+ ///
+ public bool IsNormalGameSpeed => Math.Abs(gameSpeed - 1f) < 0.01f;
+
+ ///
+ /// 获取或设置是否允许后台运行。
+ ///
+ public bool RunInBackground
+ {
+ get => runInBackground;
+ set => Application.runInBackground = runInBackground = value;
+ }
+
+ ///
+ /// 获取或设置是否禁止休眠。
+ ///
+ public bool NeverSleep
+ {
+ get => neverSleep;
+ set
+ {
+ neverSleep = value;
+ Screen.sleepTimeout = value ? SleepTimeout.NeverSleep : SleepTimeout.SystemSetting;
+ }
+ }
+
+ ///
+ /// 暂停游戏。
+ ///
+ public void PauseGame()
+ {
+ if (IsGamePaused)
+ {
+ return;
+ }
+
+ _gameSpeedBeforePause = GameSpeed;
+ GameSpeed = 0f;
+ }
+
+ ///
+ /// 恢复游戏。
+ ///
+ public void ResumeGame()
+ {
+ if (!IsGamePaused)
+ {
+ return;
+ }
+
+ GameSpeed = _gameSpeedBeforePause;
+ }
+
+ ///
+ /// 重置为正常游戏速度。
+ ///
+ public void ResetNormalGameSpeed()
+ {
+ if (IsNormalGameSpeed)
+ {
+ return;
+ }
+
+ GameSpeed = 1f;
+ }
+
+
+ ///
+ /// 游戏框架组件初始化。
+ ///
+ private void Awake()
+ {
+ _instance = this;
+ DontDestroyOnLoad(this);
+ Utility.Unity.MakeEntity(transform);
+
+ Log.Init();
+
+ Log.Info("Game Version: {0}, Unity Version: {1}", AppVersion.GameVersion, Application.unityVersion);
+
+ Utility.Converter.ScreenDpi = Screen.dpi;
+ if (Utility.Converter.ScreenDpi <= 0)
+ {
+ Utility.Converter.ScreenDpi = DEFAULT_DPI;
+ }
+
+ Application.targetFrameRate = frameRate;
+ Time.timeScale = gameSpeed;
+ Application.runInBackground = runInBackground;
+ Screen.sleepTimeout = neverSleep ? SleepTimeout.NeverSleep : SleepTimeout.SystemSetting;
+
+ Application.lowMemory += OnLowMemory;
+ }
+
+ private void OnApplicationQuit()
+ {
+ Application.lowMemory -= OnLowMemory;
+ StopAllCoroutines();
+ Shutdown();
+ }
+
+
+ internal void Shutdown()
+ {
+ Destroy(gameObject);
+ Utility.Unity.Shutdown();
+ MemoryPool.ClearAll();
+ Utility.Marshal.FreeCachedHGlobal();
+ }
+
+ private void Update()
+ {
+ ModuleSystem.UpdateExecuteList(Time.deltaTime, Time.unscaledDeltaTime);
+ }
+
+ private void LateUpdate()
+ {
+ ModuleSystem.UpdateLateExecuteList();
+ }
+
+ private void FixedUpdate()
+ {
+ ModuleSystem.UpdateFixedExecuteList();
+ }
+
+ private void OnDrawGizmos()
+ {
+ ModuleSystem.UpdateGizmosExecuteList();
+ }
+
+ private void OnGUI()
+ {
+ ModuleSystem.UpdateGUIExecuteList();
+ }
+
+ private async void OnDestroy()
+ {
+ await UniTask.Yield();
+ ModuleSystem.Dispose();
+ }
+
+
+ private void OnLowMemory()
+ {
+ Log.Warning("Low memory reported...");
+
+ IObjectPoolModule objectPoolModule = ModuleSystem.GetModule();
+ if (objectPoolModule != null)
+ {
+ objectPoolModule.ReleaseAllUnused();
+ }
+ }
+ }
+}
diff --git a/Runtime/ABase/Base/RootModule.cs.meta b/Runtime/ABase/Base/RootModule.cs.meta
new file mode 100644
index 0000000..9b83cee
--- /dev/null
+++ b/Runtime/ABase/Base/RootModule.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 252fa1bb9e36411fb4582d0656b987bf
+timeCreated: 1737362886
\ No newline at end of file
diff --git a/Runtime/ABase/Base/Version.meta b/Runtime/ABase/Base/Version.meta
new file mode 100644
index 0000000..da700cd
--- /dev/null
+++ b/Runtime/ABase/Base/Version.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e3e833a043cfd47c1ba1b3fbff22109c
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/ABase/Base/Version/AppVersion.cs b/Runtime/ABase/Base/Version/AppVersion.cs
new file mode 100644
index 0000000..04ee093
--- /dev/null
+++ b/Runtime/ABase/Base/Version/AppVersion.cs
@@ -0,0 +1,28 @@
+using UnityEngine;
+
+namespace AlicizaX
+{
+ ///
+ /// 版本号类。
+ ///
+ public static partial class AppVersion
+ {
+ private const string GameFrameworkVersionString = "1.0.0";
+
+ ///
+ /// 获取游戏框架版本号。
+ ///
+ public static string GameFrameworkVersion
+ {
+ get { return GameFrameworkVersionString; }
+ }
+
+ ///
+ /// 获取游戏版本号。
+ ///
+ public static string GameVersion
+ {
+ get { return Application.version; }
+ }
+ }
+}
diff --git a/Runtime/ABase/Base/Version/AppVersion.cs.meta b/Runtime/ABase/Base/Version/AppVersion.cs.meta
new file mode 100644
index 0000000..0eac38f
--- /dev/null
+++ b/Runtime/ABase/Base/Version/AppVersion.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9d3aeaaff9b2447338611e06c6eca6f6
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/ABase/Event.meta b/Runtime/ABase/Event.meta
new file mode 100644
index 0000000..e74cce7
--- /dev/null
+++ b/Runtime/ABase/Event.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 421689177e8f4c58ae0ce91e5ea9f9c3
+timeCreated: 1736415505
\ No newline at end of file
diff --git a/Runtime/ABase/Event/EventRuntimeHandle.cs b/Runtime/ABase/Event/EventRuntimeHandle.cs
new file mode 100644
index 0000000..6438bfe
--- /dev/null
+++ b/Runtime/ABase/Event/EventRuntimeHandle.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Runtime.CompilerServices;
+using Unity.IL2CPP.CompilerServices;
+
+namespace AlicizaX
+{
+ public interface IEventArgs { }
+
+ [AttributeUsage(AttributeTargets.Struct)]
+ public sealed class PrewarmAttribute : Attribute
+ {
+ public int Capacity { get; }
+ public PrewarmAttribute(int capacity) => Capacity = capacity;
+ }
+
+ [Il2CppSetOption(Option.NullChecks, false)]
+ [Il2CppSetOption(Option.DivideByZeroChecks, false)]
+ [Il2CppSetOption(Option.ArrayBoundsChecks, false)]
+ public readonly struct EventRuntimeHandle
+ {
+ private readonly Action _unsubscribe;
+ private readonly int _index;
+ private readonly int _version;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public EventRuntimeHandle(Action unsubscribe, int index, int version)
+ {
+ _unsubscribe = unsubscribe;
+ _index = index;
+ _version = version;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Dispose() => _unsubscribe?.Invoke(_index, _version);
+ }
+}
diff --git a/Runtime/ABase/Event/EventRuntimeHandle.cs.meta b/Runtime/ABase/Event/EventRuntimeHandle.cs.meta
new file mode 100644
index 0000000..6539b7d
--- /dev/null
+++ b/Runtime/ABase/Event/EventRuntimeHandle.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: d85feee75c2d4b54bd6b593fe55145d7
+timeCreated: 1740488369
\ No newline at end of file
diff --git a/Runtime/ABase/Event/Il2CppSetOptionAttribute.cs b/Runtime/ABase/Event/Il2CppSetOptionAttribute.cs
new file mode 100644
index 0000000..e7453df
--- /dev/null
+++ b/Runtime/ABase/Event/Il2CppSetOptionAttribute.cs
@@ -0,0 +1,74 @@
+using System;
+
+namespace Unity.IL2CPP.CompilerServices
+{
+ ///
+ /// The code generation options available for IL to C++ conversion.
+ /// Enable or disabled these with caution.
+ ///
+ public enum Option
+ {
+ ///
+ /// Enable or disable code generation for null checks.
+ ///
+ /// Global null check support is enabled by default when il2cpp.exe
+ /// is launched from the Unity editor.
+ ///
+ /// Disabling this will prevent NullReferenceException exceptions from
+ /// being thrown in generated code. In *most* cases, code that dereferences
+ /// a null pointer will crash then. Sometimes the point where the crash
+ /// happens is later than the location where the null reference check would
+ /// have been emitted though.
+ ///
+ NullChecks = 1,
+ ///
+ /// Enable or disable code generation for array bounds checks.
+ ///
+ /// Global array bounds check support is enabled by default when il2cpp.exe
+ /// is launched from the Unity editor.
+ ///
+ /// Disabling this will prevent IndexOutOfRangeException exceptions from
+ /// being thrown in generated code. This will allow reading and writing to
+ /// memory outside of the bounds of an array without any runtime checks.
+ /// Disable this check with extreme caution.
+ ///
+ ArrayBoundsChecks = 2,
+ ///
+ /// Enable or disable code generation for divide by zero checks.
+ ///
+ /// Global divide by zero check support is disabled by default when il2cpp.exe
+ /// is launched from the Unity editor.
+ ///
+ /// Enabling this will cause DivideByZeroException exceptions to be
+ /// thrown in generated code. Most code doesn't need to handle this
+ /// exception, so it is probably safe to leave it disabled.
+ ///
+ DivideByZeroChecks = 3,
+ }
+
+ ///
+ /// Use this attribute on an assembly, struct, class, method, or property to inform the IL2CPP code conversion utility to override the
+ /// global setting for one of a few different runtime checks.
+ ///
+ /// Example:
+ ///
+ /// [Il2CppSetOption(Option.NullChecks, false)]
+ /// public static string MethodWithNullChecksDisabled()
+ /// {
+ /// var tmp = new Object();
+ /// return tmp.ToString();
+ /// }
+ ///
+ [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Struct | AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Delegate, Inherited = false, AllowMultiple = true)]
+ public class Il2CppSetOptionAttribute : Attribute
+ {
+ public Option Option { get; private set; }
+ public object Value { get; private set; }
+
+ public Il2CppSetOptionAttribute(Option option, object value)
+ {
+ Option = option;
+ Value = value;
+ }
+ }
+}
diff --git a/Runtime/ABase/Event/Il2CppSetOptionAttribute.cs.meta b/Runtime/ABase/Event/Il2CppSetOptionAttribute.cs.meta
new file mode 100644
index 0000000..691658c
--- /dev/null
+++ b/Runtime/ABase/Event/Il2CppSetOptionAttribute.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 997668ad76a5387428e679240d659155
\ No newline at end of file
diff --git a/Runtime/ABase/Extension.meta b/Runtime/ABase/Extension.meta
new file mode 100644
index 0000000..6af72b8
--- /dev/null
+++ b/Runtime/ABase/Extension.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: d1f7a826ba4e4916bf2e08b7019b78da
+timeCreated: 1736324891
\ No newline at end of file
diff --git a/Runtime/ABase/Extension/Extension.meta b/Runtime/ABase/Extension/Extension.meta
new file mode 100644
index 0000000..b3f2e6d
--- /dev/null
+++ b/Runtime/ABase/Extension/Extension.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: d9389ec4eb3a41858741b1b4564a53cc
+timeCreated: 1687699594
\ No newline at end of file
diff --git a/Runtime/ABase/Extension/Extension/BidirectionalDictionary.cs b/Runtime/ABase/Extension/Extension/BidirectionalDictionary.cs
new file mode 100644
index 0000000..7fe5967
--- /dev/null
+++ b/Runtime/ABase/Extension/Extension/BidirectionalDictionary.cs
@@ -0,0 +1,78 @@
+namespace System.Collections.Generic
+{
+ public class BidirectionalDictionary
+ {
+ private readonly Dictionary _forwardDictionary;
+ private readonly Dictionary _reverseDictionary;
+
+ private int _count;
+
+ public BidirectionalDictionary(int capacity = 8)
+ {
+ _count = 0;
+ _forwardDictionary = new Dictionary(capacity);
+ _reverseDictionary = new Dictionary(capacity);
+ }
+
+ public bool TryGetKey(TValue value, out TKey key)
+ {
+ return _reverseDictionary.TryGetValue(value, out key);
+ }
+
+ public bool TryGetValue(TKey key, out TValue value)
+ {
+ return _forwardDictionary.TryGetValue(key, out value);
+ }
+
+ public int Count
+ {
+ get { return _count; }
+ }
+
+ public void Clear()
+ {
+ _count = 0;
+ _forwardDictionary.Clear();
+ _reverseDictionary.Clear();
+ }
+
+ public bool TryAdd(TKey key, TValue value)
+ {
+ if (!_forwardDictionary.ContainsKey(key))
+ {
+ _forwardDictionary.Add(key, value);
+ _reverseDictionary.Add(value, key);
+ _count++;
+ return true;
+ }
+
+ return false;
+ }
+
+ public bool TryRemoveByKey(TKey key)
+ {
+ if (_forwardDictionary.TryGetValue(key, out var value))
+ {
+ _forwardDictionary.Remove(key);
+ _reverseDictionary.Remove(value);
+ _count--;
+ return true;
+ }
+
+ return false;
+ }
+
+ public bool TryRemoveByValue(TValue value)
+ {
+ if (_reverseDictionary.TryGetValue(value, out var key))
+ {
+ _reverseDictionary.Remove(value);
+ _forwardDictionary.Remove(key);
+ _count--;
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/ABase/Extension/Extension/BidirectionalDictionary.cs.meta b/Runtime/ABase/Extension/Extension/BidirectionalDictionary.cs.meta
new file mode 100644
index 0000000..d42e42b
--- /dev/null
+++ b/Runtime/ABase/Extension/Extension/BidirectionalDictionary.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 649c026872be44a5838e70a8a7eea4a6
+timeCreated: 1687699607
\ No newline at end of file
diff --git a/Runtime/ABase/Extension/Extension/BinaryExtension.cs b/Runtime/ABase/Extension/Extension/BinaryExtension.cs
new file mode 100644
index 0000000..71e28f9
--- /dev/null
+++ b/Runtime/ABase/Extension/Extension/BinaryExtension.cs
@@ -0,0 +1,190 @@
+using AlicizaX;
+using System;
+using System.IO;
+
+///
+/// 对 BinaryReader 和 BinaryWriter 的扩展方法。
+///
+public static class BinaryExtension
+{
+ private static readonly byte[] s_CachedBytes = new byte[byte.MaxValue + 1];
+
+ ///
+ /// 从二进制流读取编码后的 32 位有符号整数。
+ ///
+ /// 要读取的二进制流。
+ /// 读取的 32 位有符号整数。
+ public static int Read7BitEncodedInt32(this BinaryReader binaryReader)
+ {
+ int value = 0;
+ int shift = 0;
+ byte b;
+ do
+ {
+ if (shift >= 35)
+ {
+ throw new GameFrameworkException("7 bit encoded int is invalid.");
+ }
+
+ b = binaryReader.ReadByte();
+ value |= (b & 0x7f) << shift;
+ shift += 7;
+ } while ((b & 0x80) != 0);
+
+ return value;
+ }
+
+ ///
+ /// 向二进制流写入编码后的 32 位有符号整数。
+ ///
+ /// 要写入的二进制流。
+ /// 要写入的 32 位有符号整数。
+ public static void Write7BitEncodedInt32(this BinaryWriter binaryWriter, int value)
+ {
+ uint num = (uint)value;
+ while (num >= 0x80)
+ {
+ binaryWriter.Write((byte)(num | 0x80));
+ num >>= 7;
+ }
+
+ binaryWriter.Write((byte)num);
+ }
+
+ ///
+ /// 从二进制流读取编码后的 32 位无符号整数。
+ ///
+ /// 要读取的二进制流。
+ /// 读取的 32 位无符号整数。
+ public static uint Read7BitEncodedUInt32(this BinaryReader binaryReader)
+ {
+ return (uint)Read7BitEncodedInt32(binaryReader);
+ }
+
+ ///
+ /// 向二进制流写入编码后的 32 位无符号整数。
+ ///
+ /// 要写入的二进制流。
+ /// 要写入的 32 位无符号整数。
+ public static void Write7BitEncodedUInt32(this BinaryWriter binaryWriter, uint value)
+ {
+ Write7BitEncodedInt32(binaryWriter, (int)value);
+ }
+
+ ///
+ /// 从二进制流读取编码后的 64 位有符号整数。
+ ///
+ /// 要读取的二进制流。
+ /// 读取的 64 位有符号整数。
+ public static long Read7BitEncodedInt64(this BinaryReader binaryReader)
+ {
+ long value = 0L;
+ int shift = 0;
+ byte b;
+ do
+ {
+ if (shift >= 70)
+ {
+ throw new GameFrameworkException("7 bit encoded int is invalid.");
+ }
+
+ b = binaryReader.ReadByte();
+ value |= (b & 0x7fL) << shift;
+ shift += 7;
+ } while ((b & 0x80) != 0);
+
+ return value;
+ }
+
+ ///
+ /// 向二进制流写入编码后的 64 位有符号整数。
+ ///
+ /// 要写入的二进制流。
+ /// 要写入的 64 位有符号整数。
+ public static void Write7BitEncodedInt64(this BinaryWriter binaryWriter, long value)
+ {
+ ulong num = (ulong)value;
+ while (num >= 0x80)
+ {
+ binaryWriter.Write((byte)(num | 0x80));
+ num >>= 7;
+ }
+
+ binaryWriter.Write((byte)num);
+ }
+
+ ///
+ /// 从二进制流读取编码后的 64 位无符号整数。
+ ///
+ /// 要读取的二进制流。
+ /// 读取的 64 位无符号整数。
+ public static ulong Read7BitEncodedUInt64(this BinaryReader binaryReader)
+ {
+ return (ulong)Read7BitEncodedInt64(binaryReader);
+ }
+
+ ///
+ /// 向二进制流写入编码后的 64 位无符号整数。
+ ///
+ /// 要写入的二进制流。
+ /// 要写入的 64 位无符号整数。
+ public static void Write7BitEncodedUInt64(this BinaryWriter binaryWriter, ulong value)
+ {
+ Write7BitEncodedInt64(binaryWriter, (long)value);
+ }
+
+ ///
+ /// 从二进制流读取加密字符串。
+ ///
+ /// 要读取的二进制流。
+ /// 密钥数组。
+ /// 读取的字符串。
+ public static string ReadEncryptedString(this BinaryReader binaryReader, byte[] encryptBytes)
+ {
+ byte length = binaryReader.ReadByte();
+ if (length <= 0)
+ {
+ return null;
+ }
+
+ if (length > byte.MaxValue)
+ {
+ throw new GameFrameworkException("String is too long.");
+ }
+
+ for (byte i = 0; i < length; i++)
+ {
+ s_CachedBytes[i] = binaryReader.ReadByte();
+ }
+
+ Utility.Encryption.GetSelfXorBytes(s_CachedBytes, 0, length, encryptBytes);
+ string value = Utility.Converter.GetString(s_CachedBytes, 0, length);
+ Array.Clear(s_CachedBytes, 0, length);
+ return value;
+ }
+
+ ///
+ /// 向二进制流写入加密字符串。
+ ///
+ /// 要写入的二进制流。
+ /// 要写入的字符串。
+ /// 密钥数组。
+ public static void WriteEncryptedString(this BinaryWriter binaryWriter, string value, byte[] encryptBytes)
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ binaryWriter.Write((byte)0);
+ return;
+ }
+
+ int length = Utility.Converter.GetBytes(value, s_CachedBytes);
+ if (length > byte.MaxValue)
+ {
+ throw new GameFrameworkException(Utility.Text.Format("String '{0}' is too long.", value));
+ }
+
+ Utility.Encryption.GetSelfXorBytes(s_CachedBytes, encryptBytes);
+ binaryWriter.Write((byte)length);
+ binaryWriter.Write(s_CachedBytes, 0, length);
+ }
+}
diff --git a/Runtime/ABase/Extension/Extension/BinaryExtension.cs.meta b/Runtime/ABase/Extension/Extension/BinaryExtension.cs.meta
new file mode 100644
index 0000000..11cc1b2
--- /dev/null
+++ b/Runtime/ABase/Extension/Extension/BinaryExtension.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a9c8ae7c81e4c674a88100f89ad5812c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/ABase/Extension/Extension/BufferExtension.cs b/Runtime/ABase/Extension/Extension/BufferExtension.cs
new file mode 100644
index 0000000..a04e3b9
--- /dev/null
+++ b/Runtime/ABase/Extension/Extension/BufferExtension.cs
@@ -0,0 +1,779 @@
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+public static class BufferExtension
+{
+ ///
+ /// 整型的大小
+ ///
+ public const int IntSize = sizeof(int);
+
+ ///
+ /// 无符号整型的大小
+ ///
+ public const int UIntSize = sizeof(uint);
+
+ ///
+ /// 短整型的大小
+ ///
+ public const int ShortSize = sizeof(short);
+
+ ///
+ /// 无符号短整型的大小
+ ///
+ public const int UShortSize = sizeof(ushort);
+
+ ///
+ /// 长整型的大小
+ ///
+ public const int LongSize = sizeof(long);
+
+ ///
+ /// 单精度浮点数的大小
+ ///
+ public const int FloatSize = sizeof(float);
+
+ ///
+ /// 双精度浮点数的大小
+ ///
+ public const int DoubleSize = sizeof(double);
+
+ ///
+ /// 字节的大小
+ ///
+ public const int ByteSize = sizeof(byte);
+
+ ///
+ /// 有符号字节的大小
+ ///
+ public const int SbyteSize = sizeof(sbyte);
+
+ ///
+ /// 布尔值的大小
+ ///
+ public const int BoolSize = sizeof(bool);
+
+
+ #region Write
+
+ ///
+ /// 将整数写入字节数组中的指定偏移量处。
+ ///
+ /// 要写入的字节数组。
+ /// 要写入的整数值。
+ /// 写入操作的偏移量。
+ public static unsafe void WriteInt(this byte[] buffer, int value, ref int offset)
+ {
+ if (offset + IntSize > buffer.Length)
+ {
+ offset += IntSize;
+ return;
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ *(int*)(ptr + offset) = System.Net.IPAddress.HostToNetworkOrder(value);
+ offset += IntSize;
+ }
+ }
+
+ ///
+ /// 将无符号整数写入字节数组中的指定偏移量处。
+ ///
+ /// 要写入的字节数组。
+ /// 要写入的整数值。
+ /// 写入操作的偏移量。
+ public static unsafe void WriteUInt(this byte[] buffer, uint value, ref int offset)
+ {
+ if (offset + IntSize > buffer.Length)
+ {
+ offset += IntSize;
+ return;
+ }
+
+ var span = buffer.AsSpan();
+ ref var local = ref span;
+ int start = offset;
+ BinaryPrimitives.WriteUInt32BigEndian(local.Slice(start, local.Length - start), value);
+ offset += IntSize;
+ }
+
+ /// 将一个16位无符号整数写入指定的缓冲区,并更新偏移量。
+ /// 要写入的缓冲区。
+ /// 要写入的值。
+ /// 要写入值的缓冲区中的偏移量。
+ public static void WriteUShort(this byte[] buffer, ushort value, ref int offset)
+ {
+ if (offset + 2 > buffer.Length)
+ {
+ offset += 2;
+ }
+ else
+ {
+ Span span = buffer.AsSpan();
+ ref Span local = ref span;
+ int start = offset;
+ BinaryPrimitives.WriteUInt16BigEndian(local.Slice(start, local.Length - start), value);
+ offset += 2;
+ }
+ }
+
+ ///
+ /// 将短整数写入字节数组中的指定偏移量处。
+ ///
+ /// 要写入的字节数组。
+ /// 要写入的短整数值。
+ /// 写入操作的偏移量。
+ public static unsafe void WriteShort(this byte[] buffer, short value, ref int offset)
+ {
+ if (offset + ShortSize > buffer.Length)
+ {
+ offset += ShortSize;
+ return;
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ *(short*)(ptr + offset) = System.Net.IPAddress.HostToNetworkOrder(value);
+ offset += ShortSize;
+ }
+ }
+
+ ///
+ /// 将长整数写入字节数组中的指定偏移量处。
+ ///
+ /// 要写入的字节数组。
+ /// 要写入的长整数值。
+ /// 写入操作的偏移量。
+ public static unsafe void WriteLong(this byte[] buffer, long value, ref int offset)
+ {
+ if (offset + LongSize > buffer.Length)
+ {
+ offset += LongSize;
+ return;
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ *(long*)(ptr + offset) = System.Net.IPAddress.HostToNetworkOrder(value);
+ offset += LongSize;
+ }
+ }
+
+ ///
+ /// 将单精度浮点数写入字节数组中的指定偏移量处。
+ ///
+ /// 要写入的字节数组。
+ /// 要写入的单精度浮点数值。
+ /// 字节数组中的偏移量,传递引用以便更新偏移量。
+ public static unsafe void WriteFloat(this byte[] buffer, float value, ref int offset)
+ {
+ if (offset + FloatSize > buffer.Length)
+ {
+ offset += FloatSize;
+ return;
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ *(float*)(ptr + offset) = value;
+ *(int*)(ptr + offset) = System.Net.IPAddress.HostToNetworkOrder(*(int*)(ptr + offset));
+ offset += FloatSize;
+ }
+ }
+
+ ///
+ /// 将双精度浮点数写入字节数组中的指定偏移量处。
+ ///
+ /// 要写入的字节数组。
+ /// 要写入的双精度浮点数值。
+ /// 字节数组中的偏移量,传递引用以便更新偏移量。
+ public static unsafe void WriteDouble(this byte[] buffer, double value, ref int offset)
+ {
+ if (offset + DoubleSize > buffer.Length)
+ {
+ offset += DoubleSize;
+ return;
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ *(double*)(ptr + offset) = value;
+ *(long*)(ptr + offset) = System.Net.IPAddress.HostToNetworkOrder(*(long*)(ptr + offset));
+ offset += DoubleSize;
+ }
+ }
+
+ ///
+ /// 将字节写入字节数组中的指定偏移量处。
+ ///
+ /// 要写入的字节数组。
+ /// 要写入的字节值。
+ /// 字节数组中的偏移量,传递引用以便更新偏移量。
+ public static unsafe void WriteByte(this byte[] buffer, byte value, ref int offset)
+ {
+ if (offset + ByteSize > buffer.Length)
+ {
+ offset += ByteSize;
+ return;
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ *(ptr + offset) = value;
+ offset += ByteSize;
+ }
+ }
+
+ ///
+ /// 在给定的偏移量位置,向缓冲区中写入字节序列,不包含长度信息。
+ ///
+ /// 目标缓冲区。
+ /// 要写入的字节数组。
+ /// 偏移量。
+ public static unsafe void WriteBytesWithoutLength(this byte[] buffer, byte[] value, ref int offset)
+ {
+ if (value == null)
+ {
+ buffer.WriteInt(0, ref offset);
+ return;
+ }
+
+ if (offset + value.Length + IntSize > buffer.Length)
+ {
+ throw new ArgumentException($"buffer write out of index {offset + value.Length + IntSize}, {buffer.Length}");
+ }
+
+ fixed (byte* ptr = buffer, valPtr = value)
+ {
+ Buffer.MemoryCopy(valPtr, ptr + offset, value.Length, value.Length);
+ offset += value.Length;
+ }
+ }
+
+ ///
+ /// 将字节数组写入到缓冲区中,同时更新偏移量。
+ ///
+ /// 目标缓冲区。
+ /// 要写入的字节数组。
+ /// 偏移量。
+ public static unsafe void WriteBytes(this byte[] buffer, byte[] value, ref int offset)
+ {
+ if (value == null)
+ {
+ buffer.WriteInt(0, ref offset);
+ return;
+ }
+
+ if (offset + value.Length + IntSize > buffer.Length)
+ {
+ offset += value.Length + IntSize;
+ return;
+ }
+
+ buffer.WriteInt(value.Length, ref offset);
+ System.Array.Copy(value, 0, buffer, offset, value.Length);
+ offset += value.Length;
+ }
+
+ ///
+ /// 将有符号字节写入到缓冲区中,同时更新偏移量。
+ ///
+ /// 目标缓冲区。
+ /// 要写入的有符号字节。
+ /// 偏移量。
+ public static unsafe void WriteSByte(this byte[] buffer, sbyte value, ref int offset)
+ {
+ if (offset + SbyteSize > buffer.Length)
+ {
+ offset += SbyteSize;
+ return;
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ *(sbyte*)(ptr + offset) = value;
+ offset += SbyteSize;
+ }
+ }
+
+ ///
+ /// 将字符串写入到缓冲区中,同时更新偏移量。
+ ///
+ /// 目标缓冲区。
+ /// 要写入的字符串。
+ /// 偏移量。
+ public static unsafe void WriteString(this byte[] buffer, string value, ref int offset)
+ {
+ if (value == null)
+ {
+ value = string.Empty;
+ }
+
+ int len = System.Text.Encoding.UTF8.GetByteCount(value);
+
+ if (len > short.MaxValue)
+ {
+ throw new ArgumentException($"字符串长度超过了 short.MaxValue {len}, {short.MaxValue}");
+ }
+
+ // 预判已经超出长度了,直接计算长度就行了
+ if (offset + len + ShortSize > buffer.Length)
+ {
+ offset += len + ShortSize;
+ return;
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ System.Text.Encoding.UTF8.GetBytes(value, 0, value.Length, buffer, offset + ShortSize);
+ buffer.WriteShort((short)len, ref offset);
+ offset += len;
+ }
+ }
+
+ ///
+ /// 将布尔值写入到缓冲区中,同时更新偏移量。
+ ///
+ /// 目标缓冲区。
+ /// 要写入的布尔值。
+ /// 偏移量。
+ public static unsafe void WriteBool(this byte[] buffer, bool value, ref int offset)
+ {
+ if (offset + BoolSize > buffer.Length)
+ {
+ offset += BoolSize;
+ return;
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ *(bool*)(ptr + offset) = value;
+ offset += BoolSize;
+ }
+ }
+
+ #endregion
+
+ #region Read
+
+ ///
+ /// 从字节数组中读取一个整数值。
+ ///
+ /// 包含整数值的字节数组。
+ /// 从字节数组中读取整数值的偏移量。
+ /// 从字节数组中读取的整数值。
+ public static unsafe int ReadInt(this byte[] buffer, ref int offset)
+ {
+ if (offset > buffer.Length + IntSize)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), "buffer read out of index");
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ var value = *(int*)(ptr + offset);
+ offset += IntSize;
+ return System.Net.IPAddress.NetworkToHostOrder(value);
+ }
+ }
+
+ ///
+ /// 从字节数组中读取一个无符号整数值。
+ ///
+ /// 包含整数值的字节数组。
+ /// 从字节数组中读取整数值的偏移量。
+ /// 从字节数组中读取的无符号整数值。
+ ///
+ public static uint ReadUInt(this byte[] buffer, ref int offset)
+ {
+ if (offset > buffer.Length + UIntSize)
+ {
+ throw new Exception("buffer read out of index");
+ }
+
+ Span span = buffer.AsSpan();
+ ref Span local = ref span;
+ int start = offset;
+ int num = (int)BinaryPrimitives.ReadUInt32BigEndian((ReadOnlySpan)local.Slice(start, local.Length - start));
+ offset += UIntSize;
+ return (uint)num;
+ }
+
+ ///
+ /// 从字节数组中读取一个短整数值。
+ ///
+ /// 包含短整数值的字节数组。
+ /// 从字节数组中读取短整数值的偏移量。
+ /// 从字节数组中读取的短整数值。
+ public static unsafe short ReadShort(this byte[] buffer, ref int offset)
+ {
+ if (offset > buffer.Length + ShortSize)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), "buffer read out of index");
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ var value = *(short*)(ptr + offset);
+ offset += ShortSize;
+ return System.Net.IPAddress.NetworkToHostOrder(value);
+ }
+ }
+
+ /// 从字节数组中读取16位无符号整数,并将偏移量向前移动。
+ /// 要读取的字节数组。
+ /// 引用偏移量。
+ /// 返回读取的16位无符号整数。
+ public static ushort ReadUShort(this byte[] buffer, ref int offset)
+ {
+ if (offset > buffer.Length + UShortSize)
+ {
+ throw new Exception("buffer read out of index");
+ }
+
+ Span span = buffer.AsSpan();
+ ref Span local = ref span;
+ int start = offset;
+ int num = (int)BinaryPrimitives.ReadUInt16BigEndian((ReadOnlySpan)local.Slice(start, local.Length - start));
+ offset += UShortSize;
+ return (ushort)num;
+ }
+
+ ///
+ /// 从字节数组中读取一个长整型数值。
+ ///
+ /// 字节数组。
+ /// 偏移量。
+ /// 长整型数值。
+ public static unsafe long ReadLong(this byte[] buffer, ref int offset)
+ {
+ if (offset > buffer.Length + LongSize)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), "buffer read out of index");
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ var value = *(long*)(ptr + offset);
+ offset += LongSize;
+ return System.Net.IPAddress.NetworkToHostOrder(value);
+ }
+ }
+
+ ///
+ /// 从字节数组中读取一个单精度浮点数值。
+ ///
+ /// 字节数组。
+ /// 偏移量。
+ /// 单精度浮点数值。
+ public static unsafe float ReadFloat(this byte[] buffer, ref int offset)
+ {
+ if (offset > buffer.Length + FloatSize)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), "buffer read out of index");
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ *(int*)(ptr + offset) = System.Net.IPAddress.NetworkToHostOrder(*(int*)(ptr + offset));
+ var value = *(float*)(ptr + offset);
+ offset += FloatSize;
+ return value;
+ }
+ }
+
+ ///
+ /// 从字节数组中读取一个双精度浮点数值。
+ ///
+ /// 字节数组。
+ /// 偏移量。
+ /// 双精度浮点数值。
+ public static unsafe double ReadDouble(this byte[] buffer, ref int offset)
+ {
+ if (offset > buffer.Length + DoubleSize)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), "buffer read out of index");
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ *(long*)(ptr + offset) = System.Net.IPAddress.NetworkToHostOrder(*(long*)(ptr + offset));
+ var value = *(double*)(ptr + offset);
+ offset += DoubleSize;
+ return value;
+ }
+ }
+
+ ///
+ /// 从字节数组中读取一个字节。
+ ///
+ /// 字节数组。
+ /// 偏移量。
+ /// 字节值。
+ public static unsafe byte ReadByte(this byte[] buffer, ref int offset)
+ {
+ if (offset > buffer.Length + ByteSize)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), "buffer read out of index");
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ var value = *(ptr + offset);
+ offset += ByteSize;
+ return value;
+ }
+ }
+
+ ///
+ /// 从字节数组中读取一定长度的字节。
+ ///
+ /// 字节数组。
+ /// 偏移量。
+ /// 数据长度。
+ /// 读取的字节数组。
+ public static unsafe byte[] ReadBytes(this byte[] buffer, int offset, int len)
+ {
+ //数据不可信
+ if (len <= 0 || offset > buffer.Length + len * ByteSize)
+ {
+ return Array.Empty();
+ }
+
+ var data = new byte[len];
+ System.Array.Copy(buffer, offset, data, 0, len);
+ return data;
+ }
+
+ ///
+ /// 从字节数组中读取一定长度的字节。
+ ///
+ /// 字节数组。
+ /// 偏移量。
+ /// 数据长度。
+ /// 读取的字节数组。
+ public static unsafe byte[] ReadBytes(this byte[] buffer, ref int offset, int len)
+ {
+ //数据不可信
+ if (len <= 0 || offset > buffer.Length + len * ByteSize)
+ {
+ return Array.Empty();
+ }
+
+ var data = new byte[len];
+ System.Array.Copy(buffer, offset, data, 0, len);
+ offset += len;
+ return data;
+ }
+
+ ///
+ /// 从字节数组中读取一定长度的字节。
+ ///
+ /// 字节数组。
+ /// 偏移量。
+ /// 读取的字节数组。
+ public static unsafe byte[] ReadBytes(this byte[] buffer, ref int offset)
+ {
+ var len = ReadInt(buffer, ref offset);
+ //数据不可信
+ if (len <= 0 || offset > buffer.Length + len * ByteSize)
+ {
+ return Array.Empty();
+ }
+
+ var data = new byte[len];
+ System.Array.Copy(buffer, offset, data, 0, len);
+ offset += len;
+ return data;
+ }
+
+ ///
+ /// 从字节数组中读取有符号字节。
+ ///
+ /// 字节数组。
+ /// 偏移量。
+ /// 读取的有符号字节。
+ public static unsafe sbyte ReadSByte(this byte[] buffer, ref int offset)
+ {
+ if (offset > buffer.Length + ByteSize)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), "buffer read out of index");
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ var value = *(sbyte*)(ptr + offset);
+ offset += ByteSize;
+ return value;
+ }
+ }
+
+ ///
+ /// 从字节数组中读取字符串。
+ ///
+ /// 字节数组。
+ /// 偏移量。
+ /// 读取的字符串。
+ public static unsafe string ReadString(this byte[] buffer, ref int offset)
+ {
+ fixed (byte* ptr = buffer)
+ {
+ var len = ReadShort(buffer, ref offset);
+ //数据不可信
+ if (len <= 0 || offset > buffer.Length + len * ByteSize)
+ return "";
+
+ var value = System.Text.Encoding.UTF8.GetString(buffer, offset, len);
+ offset += len;
+ return value;
+ }
+ }
+
+ ///
+ /// 从字节数组中读取布尔值。
+ ///
+ /// 字节数组。
+ /// 偏移量。
+ /// 读取的布尔值。
+ public static unsafe bool ReadBool(this byte[] buffer, ref int offset)
+ {
+ if (offset > buffer.Length + BoolSize)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), "buffer read out of index");
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ var value = *(bool*)(ptr + offset);
+ offset += BoolSize;
+ return value;
+ }
+ }
+
+ #endregion
+
+ ///
+ /// 将字节数组转换为字符串
+ ///
+ ///
+ ///
+ public static string ToArrayString(this byte[] bytes)
+ {
+ StringBuilder.Clear();
+ foreach (byte b in bytes)
+ {
+ StringBuilder.Append(b + " ");
+ }
+
+ return StringBuilder.ToString();
+ }
+
+ private static readonly StringBuilder StringBuilder = new StringBuilder();
+
+ ///
+ /// 将字节转换为十六进制字符串。
+ ///
+ /// 要转换的字节。
+ /// 表示字节的十六进制字符串。
+ public static string ToHex(this byte b)
+ {
+ return b.ToString("X2");
+ }
+
+ ///
+ /// 将字节数组转换为十六进制字符串。
+ ///
+ /// 要转换的字节数组。
+ /// 表示字节数组的十六进制字符串。
+ public static string ToHex(this byte[] bytes)
+ {
+ StringBuilder.Clear();
+ foreach (byte b in bytes)
+ {
+ StringBuilder.Append(b.ToString("X2"));
+ }
+
+ return StringBuilder.ToString();
+ }
+
+ ///
+ /// 使用指定格式将字节数组转换为十六进制字符串。
+ ///
+ /// 要转换的字节数组。
+ /// 十六进制格式。
+ /// 表示字节数组的十六进制字符串。
+ public static string ToHex(this byte[] bytes, string format)
+ {
+ StringBuilder.Clear();
+ foreach (byte b in bytes)
+ {
+ StringBuilder.Append(b.ToString(format));
+ }
+
+ return StringBuilder.ToString();
+ }
+
+ ///
+ /// 将字节数组中指定范围的字节转换为十六进制字符串。
+ ///
+ /// 要转换的字节数组。
+ /// 起始偏移量。
+ /// 要转换的字节数。
+ /// 表示指定范围内字节的十六进制字符串。
+ public static string ToHex(this byte[] bytes, int offset, int count)
+ {
+ StringBuilder.Clear();
+ for (int i = offset; i < offset + count; ++i)
+ {
+ StringBuilder.Append(bytes[i].ToString("X2"));
+ }
+
+ return StringBuilder.ToString();
+ }
+
+ ///
+ /// 将字节数组转换为字符串,使用默认编码。
+ ///
+ /// 要转换的字节数组。
+ /// 转换后的字符串。
+ public static string ToDefaultString(this byte[] bytes)
+ {
+ return Encoding.Default.GetString(bytes);
+ }
+
+ ///
+ /// 将字节数组的一部分转换为字符串,使用默认编码。
+ ///
+ /// 要转换的字节数组。
+ /// 起始位置。
+ /// 要转换的字节数。
+ /// 转换后的字符串。
+ public static string ToDefaultString(this byte[] bytes, int index, int count)
+ {
+ return Encoding.Default.GetString(bytes, index, count);
+ }
+
+ ///
+ /// 将字节数组转换为字符串,使用UTF-8编码。
+ ///
+ /// 要转换的字节数组。
+ /// 转换后的字符串。
+ public static string ToUtf8String(this byte[] bytes)
+ {
+ return Encoding.UTF8.GetString(bytes);
+ }
+
+ ///
+ /// 将字节数组的一部分转换为字符串,使用UTF-8编码。
+ ///
+ /// 要转换的字节数组。
+ /// 起始位置。
+ /// 要转换的字节数。
+ /// 转换后的字符串。
+ public static string ToUtf8String(this byte[] bytes, int index, int count)
+ {
+ return Encoding.UTF8.GetString(bytes, index, count);
+ }
+}
\ No newline at end of file
diff --git a/Runtime/ABase/Extension/Extension/BufferExtension.cs.meta b/Runtime/ABase/Extension/Extension/BufferExtension.cs.meta
new file mode 100644
index 0000000..98eb360
--- /dev/null
+++ b/Runtime/ABase/Extension/Extension/BufferExtension.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: c988cd9ef9f54153b26dd521897b034f
+timeCreated: 1702464913
\ No newline at end of file
diff --git a/Runtime/ABase/Extension/Extension/CollectionExtensions.cs b/Runtime/ABase/Extension/Extension/CollectionExtensions.cs
new file mode 100644
index 0000000..1a48199
--- /dev/null
+++ b/Runtime/ABase/Extension/Extension/CollectionExtensions.cs
@@ -0,0 +1,148 @@
+using System.Text;
+
+namespace System.Collections.Generic
+{
+ [UnityEngine.Scripting.Preserve]
+ public static class CollectionExtensions
+ {
+ #region DictionaryExtensions
+
+ ///
+ /// 将k和v进行合并
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ [UnityEngine.Scripting.Preserve]
+ public static void Merge(this Dictionary self, TKey k, TValue v, Func func)
+ {
+ self[k] = self.TryGetValue(k, out var value) ? func(value, v) : v;
+ }
+
+ ///
+ /// 根据key获取value值,当不存在时通过valueGetter生成value,放入Dictionary并返回value
+ ///
+ [UnityEngine.Scripting.Preserve]
+ public static TValue GetOrAdd(this Dictionary self, TKey key, Func valueGetter)
+ {
+ if (!self.TryGetValue(key, out var value))
+ {
+ value = valueGetter(key);
+ self[key] = value;
+ }
+
+ return value;
+ }
+
+ ///
+ /// 根据key获取value值,当不存在时通过默认构造函数生成value,放入Dictionary并返回value
+ ///
+ [UnityEngine.Scripting.Preserve]
+ public static TValue GetOrAdd(this Dictionary self, TKey key) where TValue : new()
+ {
+ return GetOrAdd(self, key, k => new TValue());
+ }
+
+ ///
+ /// 根据条件移除
+ ///
+ [UnityEngine.Scripting.Preserve]
+ public static int RemoveIf(this Dictionary self, Func predict)
+ {
+ int count = 0;
+ var remove = new HashSet();
+ foreach (var kv in self)
+ {
+ if (predict(kv.Key, kv.Value))
+ {
+ remove.Add(kv.Key);
+ count++;
+ }
+ }
+
+ foreach (var key in remove)
+ {
+ self.Remove(key);
+ }
+
+ return count;
+ }
+
+ #endregion
+
+
+ #region ICollectionExtensions
+
+ [UnityEngine.Scripting.Preserve]
+ public static bool IsNullOrEmpty(this ICollection self)
+ {
+ return self == null || self.Count <= 0;
+ }
+
+ #endregion
+
+ #region List
+
+ ///
+ /// 打乱
+ ///
+ [UnityEngine.Scripting.Preserve]
+ public static void Shuffer(this List list)
+ {
+ int n = list.Count;
+ var r = ThreadLocalRandom.Current;
+ for (int i = 0; i < n; i++)
+ {
+ int rand = r.Next(i, n);
+ (list[i], list[rand]) = (list[rand], list[i]);
+ }
+ }
+
+ [UnityEngine.Scripting.Preserve]
+ public static void RemoveIf(this List list, Predicate condition)
+ {
+ var idx = list.FindIndex(condition);
+ while (idx >= 0)
+ {
+ list.RemoveAt(idx);
+ idx = list.FindIndex(condition);
+ }
+ }
+
+ private static readonly StringBuilder ListToStringBuilder = new StringBuilder();
+
+ ///
+ /// 将列表转换为以指定字符串分割的字符串
+ ///
+ ///
+ /// 默认为逗号
+ ///
+ ///
+ [UnityEngine.Scripting.Preserve]
+ public static string ListToString(this List list, string separator = ",")
+ {
+ ListToStringBuilder.Clear();
+ foreach (T t in list)
+ {
+ ListToStringBuilder.Append(t);
+ ListToStringBuilder.Append(separator);
+ }
+
+ return ListToStringBuilder.ToString();
+ }
+
+ #endregion
+
+ [UnityEngine.Scripting.Preserve]
+ public static void AddRange(this HashSet c, IEnumerable e)
+ {
+ foreach (var item in e)
+ {
+ c.Add(item);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/ABase/Extension/Extension/CollectionExtensions.cs.meta b/Runtime/ABase/Extension/Extension/CollectionExtensions.cs.meta
new file mode 100644
index 0000000..f6abe76
--- /dev/null
+++ b/Runtime/ABase/Extension/Extension/CollectionExtensions.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: a7c8de5927464927b11985da5d10b1f3
+timeCreated: 1687699607
\ No newline at end of file
diff --git a/Runtime/ABase/Extension/Extension/DateTimeExtensions.cs b/Runtime/ABase/Extension/Extension/DateTimeExtensions.cs
new file mode 100644
index 0000000..4dae047
--- /dev/null
+++ b/Runtime/ABase/Extension/Extension/DateTimeExtensions.cs
@@ -0,0 +1,17 @@
+namespace System
+{
+ public static class DateTimeExtensions
+ {
+ [UnityEngine.Scripting.Preserve]
+ public static int GetDaysFrom(this DateTime now, DateTime dt)
+ {
+ return (int)(now.Date - dt).TotalDays;
+ }
+
+ [UnityEngine.Scripting.Preserve]
+ public static int GetDaysFromDefault(this DateTime now)
+ {
+ return now.GetDaysFrom(new DateTime(1970, 1, 1).Date);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/ABase/Extension/Extension/DateTimeExtensions.cs.meta b/Runtime/ABase/Extension/Extension/DateTimeExtensions.cs.meta
new file mode 100644
index 0000000..6e4ead8
--- /dev/null
+++ b/Runtime/ABase/Extension/Extension/DateTimeExtensions.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: c801aa9d28754f9bb2006c02ba26de27
+timeCreated: 1687699607
\ No newline at end of file
diff --git a/Runtime/ABase/Extension/Extension/ObjectExtension.cs b/Runtime/ABase/Extension/Extension/ObjectExtension.cs
new file mode 100644
index 0000000..a967783
--- /dev/null
+++ b/Runtime/ABase/Extension/Extension/ObjectExtension.cs
@@ -0,0 +1,41 @@
+using System;
+
+///
+/// 对象扩展函数
+///
+public static class ObjectExtension
+{
+ ///
+ /// 检查对象是否为null
+ ///
+ ///
+ ///
+ public static bool IsNull(this object self)
+ {
+ return self == null;
+ }
+
+ ///
+ /// 检查对象是否不为null
+ ///
+ ///
+ ///
+ public static bool IsNotNull(this object self)
+ {
+ return !self.IsNull();
+ }
+
+ ///
+ /// 检查对象是否为null,当为null时抛出异常
+ ///
+ /// 对象值
+ /// 异常信息
+ /// 参数为空的异常
+ public static void CheckNull(this object self, string name)
+ {
+ if (self.IsNull())
+ {
+ throw new ArgumentNullException(name, " can not be null.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/ABase/Extension/Extension/ObjectExtension.cs.meta b/Runtime/ABase/Extension/Extension/ObjectExtension.cs.meta
new file mode 100644
index 0000000..a355252
--- /dev/null
+++ b/Runtime/ABase/Extension/Extension/ObjectExtension.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 0b772921c4b649928e403b664cf90f86
+timeCreated: 1730704001
\ No newline at end of file
diff --git a/Runtime/ABase/Extension/Extension/SpanExtension.cs b/Runtime/ABase/Extension/Extension/SpanExtension.cs
new file mode 100644
index 0000000..4702ec8
--- /dev/null
+++ b/Runtime/ABase/Extension/Extension/SpanExtension.cs
@@ -0,0 +1,538 @@
+using System;
+using System.Runtime.InteropServices;
+
+[UnityEngine.Scripting.Preserve]
+public static class SpanExtension
+{
+ ///
+ /// 整型的大小
+ ///
+ public const int IntSize = sizeof(int);
+
+ ///
+ /// 短整型的大小
+ ///
+ public const int ShortSize = sizeof(short);
+
+ ///
+ /// 长整型的大小
+ ///
+ public const int LongSize = sizeof(long);
+
+ ///
+ /// 单精度浮点数的大小
+ ///
+ public const int FloatSize = sizeof(float);
+
+ ///
+ /// 双精度浮点数的大小
+ ///
+ public const int DoubleSize = sizeof(double);
+
+ ///
+ /// 字节的大小
+ ///
+ public const int ByteSize = sizeof(byte);
+
+ ///
+ /// 有符号字节的大小
+ ///
+ public const int SbyteSize = sizeof(sbyte);
+
+ ///
+ /// 布尔值的大小
+ ///
+ public const int BoolSize = sizeof(bool);
+
+
+ #region WriteSpan
+
+ ///
+ /// 将一个整数写入到字节数组中。
+ ///
+ /// 要写入的字节数组。
+ /// 要写入的整数值。
+ /// 写入操作的偏移量。
+ public static unsafe void WriteInt(this Span buffer, int value, ref int offset)
+ {
+ if (offset + IntSize > buffer.Length)
+ {
+ throw new ArgumentException($"buffer write out of index {offset + IntSize}, {buffer.Length}");
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ *(int*)(ptr + offset) = System.Net.IPAddress.HostToNetworkOrder(value);
+ offset += IntSize;
+ }
+ }
+
+ ///
+ /// 将一个短整数写入到字节数组中。
+ ///
+ /// 要写入的字节数组。
+ /// 要写入的短整数值。
+ /// 写入操作的偏移量。
+ public static unsafe void WriteShort(this Span buffer, short value, ref int offset)
+ {
+ if (offset + ShortSize > buffer.Length)
+ {
+ throw new ArgumentException($"buffer write out of index {offset + ShortSize}, {buffer.Length}");
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ *(short*)(ptr + offset) = System.Net.IPAddress.HostToNetworkOrder(value);
+ offset += ShortSize;
+ }
+ }
+
+ ///
+ /// 将一个长整数写入到字节数组中。
+ ///
+ /// 要写入的字节数组。
+ /// 要写入的长整数值。
+ /// 写入操作的偏移量。
+ public static unsafe void WriteLong(this Span buffer, long value, ref int offset)
+ {
+ if (offset + LongSize > buffer.Length)
+ {
+ throw new ArgumentException($"buffer write out of index {offset + LongSize}, {buffer.Length}");
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ *(long*)(ptr + offset) = System.Net.IPAddress.HostToNetworkOrder(value);
+ offset += LongSize;
+ }
+ }
+
+ ///
+ /// 将一个浮点数写入到字节数组中。
+ ///
+ /// 要写入的字节数组。
+ /// 要写入的浮点数值。
+ /// 写入操作的偏移量。
+ public static unsafe void WriteFloat(this Span buffer, float value, ref int offset)
+ {
+ if (offset + FloatSize > buffer.Length)
+ {
+ throw new ArgumentException($"buffer write out of index {offset + FloatSize}, {buffer.Length}");
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ *(float*)(ptr + offset) = value;
+ *(int*)(ptr + offset) = System.Net.IPAddress.HostToNetworkOrder(*(int*)(ptr + offset));
+ offset += FloatSize;
+ }
+ }
+
+ ///
+ /// 将双精度浮点数写入缓冲区。
+ ///
+ /// 要写入的字节范围。
+ /// 要写入的双精度浮点数值。
+ /// 偏移量,指示从何处开始写入。
+ public static unsafe void WriteDouble(this Span buffer, double value, ref int offset)
+ {
+ if (offset + DoubleSize > buffer.Length)
+ {
+ throw new ArgumentException($"buffer write out of index {offset + DoubleSize}, {buffer.Length}");
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ *(double*)(ptr + offset) = value;
+ *(long*)(ptr + offset) = System.Net.IPAddress.HostToNetworkOrder(*(long*)(ptr + offset));
+ offset += DoubleSize;
+ }
+ }
+
+ ///
+ /// 将字节写入缓冲区。
+ ///
+ /// 要写入的字节范围。
+ /// 要写入的字节值。
+ /// 偏移量,指示从何处开始写入。
+ public static unsafe void WriteByte(this Span buffer, byte value, ref int offset)
+ {
+ if (offset + ByteSize > buffer.Length)
+ {
+ throw new ArgumentException($"buffer write out of index {offset + ByteSize}, {buffer.Length}");
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ *(ptr + offset) = value;
+ offset += ByteSize;
+ }
+ }
+
+ ///
+ /// 将字节数组写入缓冲区。
+ ///
+ /// 要写入的字节范围。
+ /// 要写入的字节数组。
+ /// 偏移量,指示从何处开始写入。
+ public static unsafe void WriteBytes(this Span buffer, byte[] value, ref int offset)
+ {
+ if (value == null)
+ {
+ buffer.WriteInt(0, ref offset);
+ return;
+ }
+
+ if (offset + value.Length + IntSize > buffer.Length)
+ {
+ throw new ArgumentException($"buffer write out of index {offset + value.Length + IntSize}, {buffer.Length}");
+ }
+
+ buffer.WriteInt(value.Length, ref offset);
+ //System.Array.Copy(value, 0, buffer, offset, value.Length);
+ //offset += value.Length;
+ fixed (byte* ptr = buffer, valPtr = value)
+ {
+ Buffer.MemoryCopy(valPtr, ptr + offset, value.Length, value.Length);
+ offset += value.Length;
+ }
+ }
+
+ ///
+ /// 将字节数组写入缓冲区,不包括长度信息。
+ ///
+ /// 要写入的字节范围。
+ /// 要写入的字节数组。
+ /// 偏移量,指示从缓冲区的哪个位置开始写入。
+ public static unsafe void WriteBytesWithoutLength(this Span buffer, byte[] value, ref int offset)
+ {
+ if (value == null)
+ {
+ buffer.WriteInt(0, ref offset);
+ return;
+ }
+
+ if (offset + value.Length + IntSize > buffer.Length)
+ {
+ throw new ArgumentException($"buffer write out of index {offset + value.Length + IntSize}, {buffer.Length}");
+ }
+
+ fixed (byte* ptr = buffer, valPtr = value)
+ {
+ Buffer.MemoryCopy(valPtr, ptr + offset, value.Length, value.Length);
+ offset += value.Length;
+ }
+ }
+
+ ///
+ /// 将有符号字节写入缓冲区。
+ ///
+ /// 要写入的字节范围。
+ /// 要写入的有符号字节值。
+ /// 偏移量,指示从缓冲区的哪个位置开始写入。
+ public static unsafe void WriteSByte(this Span buffer, sbyte value, ref int offset)
+ {
+ if (offset + SbyteSize > buffer.Length)
+ {
+ throw new ArgumentException($"buffer write out of index {offset + SbyteSize}, {buffer.Length}");
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ *(sbyte*)(ptr + offset) = value;
+ offset += SbyteSize;
+ }
+ }
+
+ ///
+ /// 将字符串写入缓冲区
+ ///
+ /// 要写入的缓冲区
+ /// 要写入的字符串
+ /// 偏移量
+ public static unsafe void WriteString(this Span buffer, string value, ref int offset)
+ {
+ if (value == null)
+ {
+ value = string.Empty;
+ }
+
+ int len = System.Text.Encoding.UTF8.GetByteCount(value);
+ if (len > short.MaxValue)
+ {
+ throw new ArgumentException($"string length exceed short.MaxValue {len}, {short.MaxValue}");
+ }
+
+ //预判已经超出长度了,直接计算长度就行了
+ if (offset + len + ShortSize > buffer.Length)
+ {
+ throw new ArgumentException($"buffer write out of index {offset + len + ShortSize}, {buffer.Length}");
+ }
+
+ buffer.WriteShort((short)len, ref offset);
+ var val = System.Text.Encoding.UTF8.GetBytes(value);
+ fixed (byte* ptr = buffer, valPtr = val)
+ {
+ //System.Text.Encoding.UTF8.GetBytes(value, 0, value.Length, buffer, offset + ShortSize);
+ //WriteShort((short)len, buffer, ref offset);
+ //offset += len;
+ Buffer.MemoryCopy(valPtr, ptr + offset, len, len);
+ offset += len;
+ }
+ }
+
+ ///
+ /// 将布尔值写入缓冲区
+ ///
+ /// 要写入的缓冲区
+ /// 要写入的布尔值
+ /// 偏移量
+ public static unsafe void WriteBool(this Span buffer, bool value, ref int offset)
+ {
+ if (offset + BoolSize > buffer.Length)
+ {
+ throw new ArgumentException($"buffer write out of index {offset + BoolSize}, {buffer.Length}");
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ *(bool*)(ptr + offset) = value;
+ offset += BoolSize;
+ }
+ }
+
+ #endregion
+
+
+ #region ReadSpan
+
+ ///
+ /// 从字节数组中读取一个整数。
+ ///
+ /// 包含整数的字节数组。
+ /// 偏移量,指示从何处开始读取整数。
+ /// 从字节数组中读取的整数。
+ public static unsafe int ReadInt(this Span buffer, ref int offset)
+ {
+ if (offset > buffer.Length + IntSize)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), "buffer read out of index");
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ var value = *(int*)(ptr + offset);
+ offset += IntSize;
+ return System.Net.IPAddress.NetworkToHostOrder(value);
+ }
+ }
+
+ ///
+ /// 从指定的字节数组中读取一个 16 位有符号整数,并将偏移量向前移动 2 个字节。
+ ///
+ /// 包含要读取的数据的字节数组。
+ /// 要开始读取的位置。读取完成后,此参数将包含新的偏移量值。
+ /// 一个 16 位有符号整数。
+ public static unsafe short ReadShort(this Span buffer, ref int offset)
+ {
+ if (offset > buffer.Length + ShortSize)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), "buffer read out of index");
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ var value = *(short*)(ptr + offset);
+ offset += ShortSize;
+ return System.Net.IPAddress.NetworkToHostOrder(value);
+ }
+ }
+
+ ///
+ /// 从字节数组中读取一个64位整数。
+ ///
+ /// 包含数据的字节数组。
+ /// 偏移量,指示从何处开始读取。
+ /// 读取的64位整数。
+ public static unsafe long ReadLong(this Span buffer, ref int offset)
+ {
+ if (offset > buffer.Length + LongSize)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), "buffer read out of index");
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ var value = *(long*)(ptr + offset);
+ offset += LongSize;
+ return System.Net.IPAddress.NetworkToHostOrder(value);
+ }
+ }
+
+ ///
+ /// 从字节数组中读取一个单精度浮点数。
+ ///
+ /// 包含数据的字节数组。
+ /// 偏移量,指示从何处开始读取。
+ /// 读取的单精度浮点数。
+ public static unsafe float ReadFloat(this Span buffer, ref int offset)
+ {
+ if (offset > buffer.Length + FloatSize)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), "buffer read out of index");
+ }
+
+ fixed (byte* ptr = buffer)
+ {
+ *(int*)(ptr + offset) = System.Net.IPAddress.NetworkToHostOrder(*(int*)(ptr + offset));
+ var value = *(float*)(ptr + offset);
+ offset += FloatSize;
+ return value;
+ }
+ }
+
+ ///