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; + } + } + + /// + /// 从字节数组中读取一个双精度浮点数。 + /// + /// 包含数据的 Span 对象。 + /// 从字节数组开始读取的偏移量。 + /// 从字节数组中读取的双精度浮点数。 + public static unsafe double ReadDouble(this Span 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; + } + } + + /// + /// 从字节数组中读取一个字节。 + /// + /// 包含数据的 Span 对象。 + /// 从字节数组开始读取的偏移量。 + /// 从字节数组中读取的字节。 + public static unsafe byte ReadByte(this Span 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; + } + } + + /// + /// 从给定的缓冲区中读取字节数组。 + /// + /// 包含数据的 Span。 + /// 从缓冲区中读取数据的偏移量。 + /// 从缓冲区中读取的字节数组。 + public static unsafe byte[] ReadBytes(this Span 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); + var data = buffer.Slice(offset, len).ToArray(); + offset += len; + return data; + } + + /// + /// 从给定的缓冲区中读取有符号字节。 + /// + /// 包含数据的 Span。 + /// 从缓冲区中读取数据的偏移量。 + /// 从缓冲区中读取的有符号字节。 + public static unsafe sbyte ReadSByte(this Span 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 Span buffer, ref int offset) + { + var len = ReadShort(buffer, ref offset); + //数据不可信 + if (len <= 0 || offset > buffer.Length + len * ByteSize) + { + return string.Empty; + } + + fixed (byte* ptr = buffer) + { + var value = System.Text.Encoding.UTF8.GetString(ptr + offset, len); + offset += len; + return value; + } + } + + /// + /// 从字节范围中读取布尔值。 + /// + /// 包含布尔值的字节范围。 + /// 偏移量。 + /// 从字节范围中读取的布尔值。 + public static unsafe bool ReadBool(this Span 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 ArraySegment GetArray(this ReadOnlyMemory memory) + { + if (!MemoryMarshal.TryGetArray(memory, out var result)) + { + throw new InvalidOperationException("Buffer backed by array was expected"); + } + + return result; + } +} \ No newline at end of file diff --git a/Runtime/ABase/Extension/Extension/SpanExtension.cs.meta b/Runtime/ABase/Extension/Extension/SpanExtension.cs.meta new file mode 100644 index 0000000..6bc9c8f --- /dev/null +++ b/Runtime/ABase/Extension/Extension/SpanExtension.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 399e387d625e4a74b3b305a2a5391716 +timeCreated: 1681717779 \ No newline at end of file diff --git a/Runtime/ABase/Extension/Extension/StringExtensions.cs b/Runtime/ABase/Extension/Extension/StringExtensions.cs new file mode 100644 index 0000000..3aefd4d --- /dev/null +++ b/Runtime/ABase/Extension/Extension/StringExtensions.cs @@ -0,0 +1,331 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; + +[UnityEngine.Scripting.Preserve] +public static class StringExtension +{ + /// + /// 快速比较两个字符串内容是否一致 + /// + /// 当前字符串 + /// 对比的目标字符串 + /// + /// 当前对象为空 + public static bool EqualsFast(this string self, string target) + { + if (self == null) + { + return target == null; + } + + if (target == null) + { + return false; + } + + if (self.Length != target.Length) + { + return false; + } + + int ap = self.Length - 1; + int bp = target.Length - 1; + + while (ap >= 0 && bp >= 0 && self[ap] == target[bp]) + { + ap--; + bp--; + } + + return (bp < 0); + } + + /// + /// 判断字符串是否以目标字符串结尾 + /// + /// + /// 目标字符串 + /// + public static bool EndsWithFast(this string self, string target) + { + int ap = self.Length - 1; + int bp = target.Length - 1; + + while (ap >= 0 && bp >= 0 && self[ap] == target[bp]) + { + ap--; + bp--; + } + + return (bp < 0); + } + + /// + /// 判断字符串是否以目标字符串开始 + /// + /// + /// 目标字符串 + /// + public static bool StartsWithFast(this string self, string target) + { + int aLen = self.Length; + int bLen = target.Length; + + int ap = 0; + int bp = 0; + + while (ap < aLen && bp < bLen && self[ap] == target[bp]) + { + ap++; + bp++; + } + + return (bp == bLen); + } + + /// + /// 字符串转字符数组 + /// + /// + /// + public static IEnumerable ToBytes(this string self) + { + byte[] byteArray = Encoding.Default.GetBytes(self); + return byteArray; + } + + /// + /// 字符串转字符数组 + /// + /// + /// + public static byte[] ToByteArray(this string self) + { + byte[] byteArray = Encoding.Default.GetBytes(self); + return byteArray; + } + + /// + /// 字符串转UTF8字符数组 + /// + /// + /// + public static byte[] ToUtf8(this string self) + { + byte[] byteArray = Encoding.UTF8.GetBytes(self); + return byteArray; + } + + /// + /// 16进制字符串转字节数组 + /// + /// 字符串 + /// + /// 字符串字符数不是偶数引发异常 + public static byte[] HexToBytes(this string hexString) + { + if (hexString.Length % 2 != 0) + { + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "The binary key cannot have an odd number of digits: {0}", + hexString)); + } + + var hexAsBytes = new byte[hexString.Length / 2]; + for (int index = 0; index < hexAsBytes.Length; index++) + { + string byteValue = ""; + byteValue += hexString[index * 2]; + byteValue += hexString[index * 2 + 1]; + hexAsBytes[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture); + } + + return hexAsBytes; + } + + /// + /// 指定的字符串是 null、空还是仅由空白字符组成。 + /// + /// + /// + public static bool IsNullOrWhiteSpace(this string self) + { + const string nullString = "null"; + return self.EqualsFast(nullString) || string.IsNullOrWhiteSpace(self); + } + + /// + /// 指定的字符串是 null 还是 Empty 字符串。 + /// + /// + /// + public static bool IsNullOrEmpty(this string self) + { + return string.IsNullOrEmpty(self); + } + + /// + /// 指定的字符串[不]是 null、空还是仅由空白字符组成。 + /// + /// + /// + public static bool IsNotNullOrWhiteSpace(this string self) + { + return !self.IsNullOrWhiteSpace(); + } + + /// + /// 指定的字符串[不]是 null 还是 Empty 字符串。 + /// + /// + /// + public static bool IsNotNullOrEmpty(this string self) + { + return !self.IsNullOrEmpty(); + } + + /// + /// 格式化 + /// + /// + /// + /// + public static string Format(this string text, params object[] args) + { + return string.Format(text, args); + } + + /// + /// 将[\n、\t、\r、空格]替换为空,并返回 + /// + /// 原始字符串 + /// + public static string TrimEmpty(this string self) + { + self = self.Replace("\n", string.Empty).Replace(" ", string.Empty).Replace("\t", string.Empty).Replace("\r", string.Empty); + return self; + } + + + /// + /// 匹配中文正则表达式 + /// + private static readonly Regex CnReg = new Regex(@"[\u4e00-\u9fa5]"); + + /// + /// 替换中文为空字符串 + /// + /// 原始字符串 + /// + public static string TrimZhCn(this string self) + { + self = CnReg.Replace(self, string.Empty); + return self; + } + + public static int[] SplitToIntArray(this string str, char sep = '+') + { + if (string.IsNullOrEmpty(str)) + return Array.Empty(); + + var arr = str.Split(sep); + int[] ret = new int[arr.Length]; + for (int i = 0; i < arr.Length; ++i) + { + if (int.TryParse(arr[i], out var t)) + ret[i] = t; + } + + return ret; + } + + public static int[][] SplitTo2IntArray(this string str, char sep1 = ';', char sep2 = '+') + { + if (string.IsNullOrEmpty(str)) + return Array.Empty(); + + var arr = str.Split(sep1); + if (arr.Length <= 0) + return Array.Empty(); + + int[][] ret = new int[arr.Length][]; + + for (int i = 0; i < arr.Length; ++i) + ret[i] = arr[i].SplitToIntArray(sep2); + return ret; + } + + /// + /// 根据字符串创建目录,递归 + /// + public static void CreateAsDirectory(this string path, bool isFile = false) + { + if (isFile) + { + path = Path.GetDirectoryName(path); + } + + if (!Directory.Exists(path)) + { + CreateAsDirectory(path, true); + Directory.CreateDirectory(path); + } + } + + /// + /// 从指定字符串中的指定位置处开始读取一行。 + /// + /// 指定的字符串。 + /// 从指定位置处开始读取一行,读取后将返回下一行开始的位置。 + /// 读取的一行字符串。 + public static string ReadLine(this string rawString, ref int position) + { + if (position < 0) + { + return null; + } + + int length = rawString.Length; + int offset = position; + while (offset < length) + { + char ch = rawString[offset]; + switch (ch) + { + case '\r': + case '\n': + if (offset > position) + { + string line = rawString.Substring(position, offset - position); + position = offset + 1; + if ((ch == '\r') && (position < length) && (rawString[position] == '\n')) + { + position++; + } + + return line; + } + + offset++; + position++; + break; + + default: + offset++; + break; + } + } + + if (offset > position) + { + string line = rawString.Substring(position, offset - position); + position = offset; + return line; + } + + return null; + } +} \ No newline at end of file diff --git a/Runtime/ABase/Extension/Extension/StringExtensions.cs.meta b/Runtime/ABase/Extension/Extension/StringExtensions.cs.meta new file mode 100644 index 0000000..c33b78a --- /dev/null +++ b/Runtime/ABase/Extension/Extension/StringExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ab144055b8e545599f0d393eea23ea7d +timeCreated: 1687699607 \ No newline at end of file diff --git a/Runtime/ABase/Extension/Extension/ThreadLocalRandom.cs b/Runtime/ABase/Extension/Extension/ThreadLocalRandom.cs new file mode 100644 index 0000000..f37778a --- /dev/null +++ b/Runtime/ABase/Extension/Extension/ThreadLocalRandom.cs @@ -0,0 +1,23 @@ +using System.Threading; + +namespace System +{ + /// + /// 线程私有random对象 + /// + [UnityEngine.Scripting.Preserve] + public static class ThreadLocalRandom + { + private static int _seed = Environment.TickCount; + + private static readonly ThreadLocal _rng = new ThreadLocal(() => new Random(Interlocked.Increment(ref _seed))); + + /// + /// The current random number seed available to this thread + /// + public static Random Current + { + get { return _rng.Value; } + } + } +} \ No newline at end of file diff --git a/Runtime/ABase/Extension/Extension/ThreadLocalRandom.cs.meta b/Runtime/ABase/Extension/Extension/ThreadLocalRandom.cs.meta new file mode 100644 index 0000000..f3f22a4 --- /dev/null +++ b/Runtime/ABase/Extension/Extension/ThreadLocalRandom.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7b653a5332094db9a1380e60d874e038 +timeCreated: 1687699607 \ No newline at end of file diff --git a/Runtime/ABase/Extension/Extension/ToStringNonAllocExtensions.cs b/Runtime/ABase/Extension/Extension/ToStringNonAllocExtensions.cs new file mode 100644 index 0000000..ebb0d65 --- /dev/null +++ b/Runtime/ABase/Extension/Extension/ToStringNonAllocExtensions.cs @@ -0,0 +1,69 @@ +using System; +using System.Threading; +using Cysharp.Text; + +public static class ToStringNonAllocExtensions +{ + // 使用 ThreadLocal 确保线程安全 + private static readonly ThreadLocal ThreadLocalStringBuilder = + new ThreadLocal(() => ZString.CreateStringBuilder(), true); + + public static string ToStringNonAlloc(this int value) + { + var sb = ThreadLocalStringBuilder.Value; + sb.Clear(); + sb.Append(value); + return sb.ToString(); + } + + public static string ToStringNonAlloc(this float value) + { + var sb = ThreadLocalStringBuilder.Value; + sb.Clear(); + sb.Append(value); + return sb.ToString(); + } + + public static string ToStringNonAlloc(this double value) + { + var sb = ThreadLocalStringBuilder.Value; + sb.Clear(); + sb.Append(value); + return sb.ToString(); + } + + public static string ToStringNonAlloc(this bool value) + { + var sb = ThreadLocalStringBuilder.Value; + sb.Clear(); + sb.Append(value); + return sb.ToString(); + } + + public static string ToStringNonAlloc(this string value) + { + var sb = ThreadLocalStringBuilder.Value; + sb.Clear(); + sb.Append(value); + return sb.ToString(); + } + + public static string ToStringNonAlloc(this T value) where T : IFormattable + { + var sb = ThreadLocalStringBuilder.Value; + sb.Clear(); + sb.AppendFormat(null, "{0}", value); + return sb.ToString(); + } + + // 释放所有 ThreadLocal 的实例 + public static void Dispose() + { + if (ThreadLocalStringBuilder.IsValueCreated) + { + ThreadLocalStringBuilder.Value.Dispose(); + } + + ThreadLocalStringBuilder.Dispose(); + } +} \ No newline at end of file diff --git a/Runtime/ABase/Extension/Extension/ToStringNonAllocExtensions.cs.meta b/Runtime/ABase/Extension/Extension/ToStringNonAllocExtensions.cs.meta new file mode 100644 index 0000000..31c7122 --- /dev/null +++ b/Runtime/ABase/Extension/Extension/ToStringNonAllocExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 04aea3f8cc1a4e68be2e2a476c765a28 +timeCreated: 1737449203 \ No newline at end of file diff --git a/Runtime/ABase/Extension/Extension/TypeExtensions.cs b/Runtime/ABase/Extension/Extension/TypeExtensions.cs new file mode 100644 index 0000000..8f2a012 --- /dev/null +++ b/Runtime/ABase/Extension/Extension/TypeExtensions.cs @@ -0,0 +1,12 @@ +namespace System +{ + [UnityEngine.Scripting.Preserve] + public static class TypeExtensions + { + [UnityEngine.Scripting.Preserve] + public static bool IsImplWithInterface(this Type self, Type target) + { + return self.GetInterface(target.FullName) != null && !self.IsInterface && !self.IsAbstract; + } + } +} \ No newline at end of file diff --git a/Runtime/ABase/Extension/Extension/TypeExtensions.cs.meta b/Runtime/ABase/Extension/Extension/TypeExtensions.cs.meta new file mode 100644 index 0000000..a05faaa --- /dev/null +++ b/Runtime/ABase/Extension/Extension/TypeExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5c2d1df80f7943858cc52f5ce7e0c4d6 +timeCreated: 1687699607 \ No newline at end of file diff --git a/Runtime/ABase/Extension/SequenceReader.meta b/Runtime/ABase/Extension/SequenceReader.meta new file mode 100644 index 0000000..6259c49 --- /dev/null +++ b/Runtime/ABase/Extension/SequenceReader.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f74afe8474234514a52ccfc40a8c069c +timeCreated: 1708529284 \ No newline at end of file diff --git a/Runtime/ABase/Extension/SequenceReader/SequenceReader.cs b/Runtime/ABase/Extension/SequenceReader/SequenceReader.cs new file mode 100644 index 0000000..76ff757 --- /dev/null +++ b/Runtime/ABase/Extension/SequenceReader/SequenceReader.cs @@ -0,0 +1,496 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +/* Licensed to the .NET Foundation under one or more agreements. + * The .NET Foundation licenses this file to you under the MIT license. + * See the LICENSE file in the project root for more information. */ + +using System; +using System.Buffers; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace AlicizaX +{ + public ref partial struct SequenceReader where T : unmanaged, IEquatable + { + /// + /// A value indicating whether we're using (as opposed to . + /// + private bool usingSequence; + + /// + /// Backing for the entire sequence when we're not using . + /// + private ReadOnlySequence sequence; + + /// + /// The position at the start of the . + /// + private SequencePosition currentPosition; + + /// + /// The position at the end of the . + /// + private SequencePosition nextPosition; + + /// + /// Backing for the entire sequence when we're not using . + /// + private ReadOnlyMemory memory; + + /// + /// A value indicating whether there is unread data remaining. + /// + private bool moreData; + + /// + /// The total number of elements in the sequence. + /// + private long length; + + /// + /// Initializes a new instance of the struct + /// over the given . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public SequenceReader(in ReadOnlySequence sequence) + { + this.usingSequence = true; + this.CurrentSpanIndex = 0; + this.Consumed = 0; + this.sequence = sequence; + this.memory = default; + this.currentPosition = sequence.Start; + this.length = -1; + + ReadOnlySpan first = sequence.First.Span; + this.nextPosition = sequence.GetPosition(first.Length); + this.CurrentSpan = first; + this.moreData = first.Length > 0; + + if (!this.moreData && !sequence.IsSingleSegment) + { + this.moreData = true; + this.GetNextSpan(); + } + } + + /// + /// Initializes a new instance of the struct + /// over the given . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public SequenceReader(ReadOnlyMemory memory) + { + this.usingSequence = false; + this.CurrentSpanIndex = 0; + this.Consumed = 0; + this.memory = memory; + this.CurrentSpan = memory.Span; + this.length = memory.Length; + this.moreData = memory.Length > 0; + + this.currentPosition = default; + this.nextPosition = default; + this.sequence = default; + } + + /// + /// Gets a value indicating whether there is no more data in the . + /// + public bool End => !this.moreData; + + /// + /// Gets the underlying for the reader. + /// + public ReadOnlySequence Sequence + { + get + { + if (this.sequence.IsEmpty && !this.memory.IsEmpty) + { + // We're in memory mode (instead of sequence mode). + // Lazily fill in the sequence data. + this.sequence = new ReadOnlySequence(this.memory); + this.currentPosition = this.sequence.Start; + this.nextPosition = this.sequence.End; + } + + return this.sequence; + } + } + + /// + /// Gets the current position in the . + /// + public SequencePosition Position + => this.Sequence.GetPosition(this.CurrentSpanIndex, this.currentPosition); + + /// + /// Gets the current segment in the as a span. + /// + public ReadOnlySpan CurrentSpan { get; private set; } + + /// + /// Gets the index in the . + /// + public int CurrentSpanIndex { get; private set; } + + /// + /// Gets the unread portion of the . + /// + public ReadOnlySpan UnreadSpan + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.CurrentSpan.Slice(this.CurrentSpanIndex); + } + + /// + /// Gets the total number of 's processed by the reader. + /// + public long Consumed { get; private set; } + + /// + /// Gets remaining 's in the reader's . + /// + public long Remaining => this.Length - this.Consumed; + + /// + /// Gets count of in the reader's . + /// + public long Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if (this.length < 0) + { + // Cache the length + this.length = this.Sequence.Length; + } + + return this.length; + } + } + + /// + /// Peeks at the next value without advancing the reader. + /// + /// The next value or default if at the end. + /// False if at the end of the reader. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryPeek(out T value) + { + if (this.moreData) + { + value = this.CurrentSpan[this.CurrentSpanIndex]; + return true; + } + else + { + value = default; + return false; + } + } + + /// + /// Read the next value and advance the reader. + /// + /// The next value or default if at the end. + /// False if at the end of the reader. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryRead(out T value) + { + if (this.End) + { + value = default; + return false; + } + + value = this.CurrentSpan[this.CurrentSpanIndex]; + this.CurrentSpanIndex++; + this.Consumed++; + + if (this.CurrentSpanIndex >= this.CurrentSpan.Length) + { + if (this.usingSequence) + { + this.GetNextSpan(); + } + else + { + this.moreData = false; + } + } + + return true; + } + + /// + /// Move the reader back the specified number of items. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Rewind(long count) + { + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + this.Consumed -= count; + + if (this.CurrentSpanIndex >= count) + { + this.CurrentSpanIndex -= (int)count; + this.moreData = true; + } + else if (this.usingSequence) + { + // Current segment doesn't have enough data, scan backward through segments + this.RetreatToPreviousSpan(this.Consumed); + } + else + { + throw new ArgumentOutOfRangeException("Rewind went past the start of the memory."); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void RetreatToPreviousSpan(long consumed) + { + Debug.Assert(this.usingSequence, "usingSequence"); + this.ResetReader(); + this.Advance(consumed); + } + + private void ResetReader() + { + Debug.Assert(this.usingSequence, "usingSequence"); + this.CurrentSpanIndex = 0; + this.Consumed = 0; + this.currentPosition = this.Sequence.Start; + this.nextPosition = this.currentPosition; + + if (this.Sequence.TryGet(ref this.nextPosition, out ReadOnlyMemory memory, advance: true)) + { + this.moreData = true; + + if (memory.Length == 0) + { + this.CurrentSpan = default; + + // No data in the first span, move to one with data + this.GetNextSpan(); + } + else + { + this.CurrentSpan = memory.Span; + } + } + else + { + // No data in any spans and at end of sequence + this.moreData = false; + this.CurrentSpan = default; + } + } + + /// + /// Get the next segment with available data, if any. + /// + private void GetNextSpan() + { + Debug.Assert(this.usingSequence, "usingSequence"); + if (!this.Sequence.IsSingleSegment) + { + SequencePosition previousNextPosition = this.nextPosition; + while (this.Sequence.TryGet(ref this.nextPosition, out ReadOnlyMemory memory, advance: true)) + { + this.currentPosition = previousNextPosition; + if (memory.Length > 0) + { + this.CurrentSpan = memory.Span; + this.CurrentSpanIndex = 0; + return; + } + else + { + this.CurrentSpan = default; + this.CurrentSpanIndex = 0; + previousNextPosition = this.nextPosition; + } + } + } + + this.moreData = false; + } + + /// + /// Move the reader ahead the specified number of items. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Advance(long count) + { + const long TooBigOrNegative = unchecked((long)0xFFFFFFFF80000000); + if ((count & TooBigOrNegative) == 0 && this.CurrentSpan.Length - this.CurrentSpanIndex > (int)count) + { + this.CurrentSpanIndex += (int)count; + this.Consumed += count; + } + else if (this.usingSequence) + { + // Can't satisfy from the current span + this.AdvanceToNextSpan(count); + } + else if (this.CurrentSpan.Length - this.CurrentSpanIndex == (int)count) + { + this.CurrentSpanIndex += (int)count; + this.Consumed += count; + this.moreData = false; + } + else + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + } + + /// + /// Unchecked helper to avoid unnecessary checks where you know count is valid. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void AdvanceCurrentSpan(long count) + { + Debug.Assert(count >= 0, "count >= 0"); + + this.Consumed += count; + this.CurrentSpanIndex += (int)count; + if (this.usingSequence && this.CurrentSpanIndex >= this.CurrentSpan.Length) + { + this.GetNextSpan(); + } + } + + /// + /// Only call this helper if you know that you are advancing in the current span + /// with valid count and there is no need to fetch the next one. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void AdvanceWithinSpan(long count) + { + Debug.Assert(count >= 0, "count >= 0"); + + this.Consumed += count; + this.CurrentSpanIndex += (int)count; + + Debug.Assert(this.CurrentSpanIndex < this.CurrentSpan.Length, "this.CurrentSpanIndex < this.CurrentSpan.Length"); + } + + /// + /// Move the reader ahead the specified number of items + /// if there are enough elements remaining in the sequence. + /// + /// if there were enough elements to advance; otherwise . + internal bool TryAdvance(long count) + { + if (this.Remaining < count) + { + return false; + } + + this.Advance(count); + return true; + } + + private void AdvanceToNextSpan(long count) + { + Debug.Assert(this.usingSequence, "usingSequence"); + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + this.Consumed += count; + while (this.moreData) + { + int remaining = this.CurrentSpan.Length - this.CurrentSpanIndex; + + if (remaining > count) + { + this.CurrentSpanIndex += (int)count; + count = 0; + break; + } + + // As there may not be any further segments we need to + // push the current index to the end of the span. + this.CurrentSpanIndex += remaining; + count -= remaining; + Debug.Assert(count >= 0, "count >= 0"); + + this.GetNextSpan(); + + if (count == 0) + { + break; + } + } + + if (count != 0) + { + // Not enough data left- adjust for where we actually ended and throw + this.Consumed -= count; + throw new ArgumentOutOfRangeException(nameof(count)); + } + } + + /// + /// Copies data from the current to the given span. + /// + /// Destination to copy to. + /// True if there is enough data to copy to the . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryCopyTo(Span destination) + { + ReadOnlySpan firstSpan = this.UnreadSpan; + if (firstSpan.Length >= destination.Length) + { + firstSpan.Slice(0, destination.Length).CopyTo(destination); + return true; + } + + return this.TryCopyMultisegment(destination); + } + + internal bool TryCopyMultisegment(Span destination) + { + if (this.Remaining < destination.Length) + { + return false; + } + + ReadOnlySpan firstSpan = this.UnreadSpan; + Debug.Assert(firstSpan.Length < destination.Length, "firstSpan.Length < destination.Length"); + firstSpan.CopyTo(destination); + int copied = firstSpan.Length; + + SequencePosition next = this.nextPosition; + while (this.Sequence.TryGet(ref next, out ReadOnlyMemory nextSegment, true)) + { + if (nextSegment.Length > 0) + { + ReadOnlySpan nextSpan = nextSegment.Span; + int toCopy = Math.Min(nextSpan.Length, destination.Length - copied); + nextSpan.Slice(0, toCopy).CopyTo(destination.Slice(copied)); + copied += toCopy; + if (copied >= destination.Length) + { + break; + } + } + } + + return true; + } + } +} diff --git a/Runtime/ABase/Extension/SequenceReader/SequenceReader.cs.meta b/Runtime/ABase/Extension/SequenceReader/SequenceReader.cs.meta new file mode 100644 index 0000000..e9460b3 --- /dev/null +++ b/Runtime/ABase/Extension/SequenceReader/SequenceReader.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 25906ef608f044e4948c7171c50a8e50 +timeCreated: 1708529317 \ No newline at end of file diff --git a/Runtime/ABase/Extension/SequenceReader/SequenceReaderExtensions.cs b/Runtime/ABase/Extension/SequenceReader/SequenceReaderExtensions.cs new file mode 100644 index 0000000..c3a11a3 --- /dev/null +++ b/Runtime/ABase/Extension/SequenceReader/SequenceReaderExtensions.cs @@ -0,0 +1,238 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +/* Licensed to the .NET Foundation under one or more agreements. + * The .NET Foundation licenses this file to you under the MIT license. + * See the LICENSE file in the project root for more information. */ + +using System; +using System.Buffers.Binary; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace AlicizaX +{ + public static partial class SequenceReaderExtensions + { + /// + /// Try to read the given type out of the buffer if possible. Warning: this is dangerous to use with arbitrary + /// structs- see remarks for full details. + /// + /// + /// IMPORTANT: The read is a straight copy of bits. If a struct depends on specific state of its members to + /// behave correctly this can lead to exceptions, etc. If reading endian specific integers, use the explicit + /// overloads such as . + /// + /// + /// True if successful. will be default if failed (due to lack of space). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe bool TryRead(ref this SequenceReader reader, out T value) + where T : unmanaged + { + ReadOnlySpan span = reader.UnreadSpan; + if (span.Length < sizeof(T)) + { + return TryReadMultisegment(ref reader, out value); + } + + value = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(span)); + reader.Advance(sizeof(T)); + return true; + } + + private static unsafe bool TryReadMultisegment(ref SequenceReader reader, out T value) + where T : unmanaged + { + Debug.Assert(reader.UnreadSpan.Length < sizeof(T), "reader.UnreadSpan.Length < sizeof(T)"); + + // Not enough data in the current segment, try to peek for the data we need. + T buffer = default; + Span tempSpan = new Span(&buffer, sizeof(T)); + + if (!reader.TryCopyTo(tempSpan)) + { + value = default; + return false; + } + + value = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(tempSpan)); + reader.Advance(sizeof(T)); + return true; + } + + /// + /// Reads an from the next position in the sequence. + /// + /// The reader to read from. + /// Receives the value read. + /// if there was another byte in the sequence; otherwise. + public static bool TryRead(ref this SequenceReader reader, out sbyte value) + { + if (TryRead(ref reader, out byte byteValue)) + { + value = unchecked((sbyte)byteValue); + return true; + } + + value = default; + return false; + } + + /// + /// Reads an as big endian. + /// + /// False if there wasn't enough data for an . + public static bool TryReadBigEndian(ref this SequenceReader reader, out short value) + { + if (!BitConverter.IsLittleEndian) + { + return reader.TryRead(out value); + } + + return TryReadReverseEndianness(ref reader, out value); + } + + /// + /// Reads an as big endian. + /// + /// False if there wasn't enough data for an . + public static bool TryReadBigEndian(ref this SequenceReader reader, out ushort value) + { + if (TryReadBigEndian(ref reader, out short shortValue)) + { + value = unchecked((ushort)shortValue); + return true; + } + + value = default; + return false; + } + + private static bool TryReadReverseEndianness(ref SequenceReader reader, out short value) + { + if (reader.TryRead(out value)) + { + value = BinaryPrimitives.ReverseEndianness(value); + return true; + } + + return false; + } + + /// + /// Reads an as big endian. + /// + /// False if there wasn't enough data for an . + public static bool TryReadBigEndian(ref this SequenceReader reader, out int value) + { + if (!BitConverter.IsLittleEndian) + { + return reader.TryRead(out value); + } + + return TryReadReverseEndianness(ref reader, out value); + } + + /// + /// Reads an as big endian. + /// + /// False if there wasn't enough data for an . + public static bool TryReadBigEndian(ref this SequenceReader reader, out uint value) + { + if (TryReadBigEndian(ref reader, out int intValue)) + { + value = unchecked((uint)intValue); + return true; + } + + value = default; + return false; + } + + private static bool TryReadReverseEndianness(ref SequenceReader reader, out int value) + { + if (reader.TryRead(out value)) + { + value = BinaryPrimitives.ReverseEndianness(value); + return true; + } + + return false; + } + + /// + /// Reads an as big endian. + /// + /// False if there wasn't enough data for an . + public static bool TryReadBigEndian(ref this SequenceReader reader, out long value) + { + if (!BitConverter.IsLittleEndian) + { + return reader.TryRead(out value); + } + + return TryReadReverseEndianness(ref reader, out value); + } + + /// + /// Reads an as big endian. + /// + /// False if there wasn't enough data for an . + public static bool TryReadBigEndian(ref this SequenceReader reader, out ulong value) + { + if (TryReadBigEndian(ref reader, out long longValue)) + { + value = unchecked((ulong)longValue); + return true; + } + + value = default; + return false; + } + + private static bool TryReadReverseEndianness(ref SequenceReader reader, out long value) + { + if (reader.TryRead(out value)) + { + value = BinaryPrimitives.ReverseEndianness(value); + return true; + } + + return false; + } + + /// + /// Reads a as big endian. + /// + /// False if there wasn't enough data for a . + public static unsafe bool TryReadBigEndian(ref this SequenceReader reader, out float value) + { + if (TryReadBigEndian(ref reader, out int intValue)) + { + value = *(float*)&intValue; + return true; + } + + value = default; + return false; + } + + /// + /// Reads a as big endian. + /// + /// False if there wasn't enough data for a . + public static unsafe bool TryReadBigEndian(ref this SequenceReader reader, out double value) + { + if (TryReadBigEndian(ref reader, out long longValue)) + { + value = *(double*)&longValue; + return true; + } + + value = default; + return false; + } + } +} diff --git a/Runtime/ABase/Extension/SequenceReader/SequenceReaderExtensions.cs.meta b/Runtime/ABase/Extension/SequenceReader/SequenceReaderExtensions.cs.meta new file mode 100644 index 0000000..0f0fea4 --- /dev/null +++ b/Runtime/ABase/Extension/SequenceReader/SequenceReaderExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 70685780749541938090d6e7433fd406 +timeCreated: 1708529295 \ No newline at end of file diff --git a/Runtime/ABase/Extension/UnityEngage.GameObject.meta b/Runtime/ABase/Extension/UnityEngage.GameObject.meta new file mode 100644 index 0000000..38c998d --- /dev/null +++ b/Runtime/ABase/Extension/UnityEngage.GameObject.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: dbc2edb7919142ca9461e0335753a7df +timeCreated: 1694526362 \ No newline at end of file diff --git a/Runtime/ABase/Extension/UnityEngage.GameObject/ComponentExtensions.cs b/Runtime/ABase/Extension/UnityEngage.GameObject/ComponentExtensions.cs new file mode 100644 index 0000000..4e1abef --- /dev/null +++ b/Runtime/ABase/Extension/UnityEngage.GameObject/ComponentExtensions.cs @@ -0,0 +1,97 @@ +using UnityEngine.Assertions; + +namespace UnityEngine +{ + /// + /// . + /// + public static class ComponentExtensions + { + /// + /// 从目标组件中获取一个组件,如果是组件类型不存在,则添加 + /// + public static Component GetOrAddComponent( + this Component obj, System.Type type) + { + var component = obj.GetComponent(type); + if (component == null) + { + component = obj.gameObject.AddComponent(type); + } + + return component; + } + + /// + /// 从目标组件中获取一个组件,如果是组件类型不存在,则添加 + /// + public static T GetOrAddComponent( + this Component obj) where T : Component + { + var component = obj.GetComponent(); + if (component == null) + { + component = obj.gameObject.AddComponent(); + } + + return component; + } + + /// + /// 从目标组件中获取一个组件,如果是组件类型不存在,则添加 + /// 标记不保存 + /// + public static T GetOrAddComponentDontSave( + this Component obj) where T : Component + { + var component = obj.GetComponent(); + if (component == null) + { + component = obj.gameObject.AddComponent(); + component.hideFlags = HideFlags.DontSave; + } + + return component; + } + + /// + /// 检查目标组件的GameObject上是否有一个或多个指定类型的组件 + /// + public static bool HasComponent( + this Component obj, System.Type type) + { + return obj.GetComponent(type) != null; + } + + /// + /// 检查目标组件的GameObject上是否有一个或多个指定类型的组件 + /// + public static bool HasComponent( + this Component obj) where T : Component + { + return obj.GetComponent() != null; + } + + /// + /// 在parent中查找组件,即使该组件处于非活动状态或已禁用 都能查询 + /// + public static T GetComponentInParentHard( + this Component obj) where T : Component + { + Assert.IsNotNull(obj); + var transform = obj.transform; + while (transform != null) + { + var component = transform.GetComponent(); + if (component != null) + { + return component; + } + + transform = transform.parent; + } + + return null; + } + } +} diff --git a/Runtime/ABase/Extension/UnityEngage.GameObject/ComponentExtensions.cs.meta b/Runtime/ABase/Extension/UnityEngage.GameObject/ComponentExtensions.cs.meta new file mode 100644 index 0000000..e292248 --- /dev/null +++ b/Runtime/ABase/Extension/UnityEngage.GameObject/ComponentExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 15e221e13b3e456aa5e3473d1e28f921 +timeCreated: 1744372997 \ No newline at end of file diff --git a/Runtime/ABase/Extension/UnityEngage.GameObject/UnityEngage.GameObjectExtension.cs b/Runtime/ABase/Extension/UnityEngage.GameObject/UnityEngage.GameObjectExtension.cs new file mode 100644 index 0000000..495f9db --- /dev/null +++ b/Runtime/ABase/Extension/UnityEngage.GameObject/UnityEngage.GameObjectExtension.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; + +namespace UnityEngine +{ + public static class UnityEngageGameObjectExtension + { + public static void SafeDestroySelf( + this Object obj) + { + if (obj == null) return; + +#if UNITY_EDITOR + if (!Application.isPlaying) + Object.DestroyImmediate(obj); + else + Object.Destroy(obj); +#else + Object.Destroy(obj); +#endif + } + private static readonly List s_CachedTransforms = new List(); + + /// + /// 获取或增加组件。 + /// + /// 要获取或增加的组件。 + /// 目标对象。 + /// 获取或增加的组件。 + public static void DestroyComponent(this GameObject gameObject) where T : Component + { + T component = gameObject.GetComponent(); + if (component != null) + { + Object.Destroy(component); + } + + // return component; + } + + /// + /// 获取或增加组件。 + /// + /// 要获取或增加的组件。 + /// 目标对象。 + /// 获取或增加的组件。 + public static T GetOrAddComponent(this GameObject gameObject) where T : Component + { + T component = gameObject.GetComponent(); + if (component == null) + { + component = gameObject.AddComponent(); + } + + return component; + } + + /// + /// 获取或增加组件。 + /// + /// 目标对象。 + /// 要获取或增加的组件类型。 + /// 获取或增加的组件。 + public static Component GetOrAddComponent(this GameObject gameObject, Type type) + { + Component component = gameObject.GetComponent(type); + if (component == null) + { + component = gameObject.AddComponent(type); + } + + return component; + } + + /// + /// 获取 GameObject 是否在场景中。 + /// + /// 目标对象。 + /// GameObject 是否在场景中。 + /// 若返回 true,表明此 GameObject 是一个场景中的实例对象;若返回 false,表明此 GameObject 是一个 Prefab。 + public static bool InScene(this GameObject gameObject) + { + return gameObject.scene.name != null; + } + + /// + /// 递归设置游戏对象的层次。 + /// + /// 对象。 + /// 目标层次的编号。 + public static void SetLayerRecursively(this GameObject gameObject, int layer) + { + gameObject.GetComponentsInChildren(true, s_CachedTransforms); + foreach (var tf in s_CachedTransforms) + { + tf.gameObject.layer = layer; + } + + s_CachedTransforms.Clear(); + } + } +} diff --git a/Runtime/ABase/Extension/UnityEngage.GameObject/UnityEngage.GameObjectExtension.cs.meta b/Runtime/ABase/Extension/UnityEngage.GameObject/UnityEngage.GameObjectExtension.cs.meta new file mode 100644 index 0000000..ab1bee8 --- /dev/null +++ b/Runtime/ABase/Extension/UnityEngage.GameObject/UnityEngage.GameObjectExtension.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0593cc2c12f442e394bc9bba24e15d80 +timeCreated: 1694526284 \ No newline at end of file diff --git a/Runtime/ABase/Extension/UnityEngine.Transform.meta b/Runtime/ABase/Extension/UnityEngine.Transform.meta new file mode 100644 index 0000000..35a1827 --- /dev/null +++ b/Runtime/ABase/Extension/UnityEngine.Transform.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 62437af40e3e46868cd21e6e32601d44 +timeCreated: 1694526450 \ No newline at end of file diff --git a/Runtime/ABase/Extension/UnityEngine.Transform/UnityEngine.TransformExtension.cs b/Runtime/ABase/Extension/UnityEngine.Transform/UnityEngine.TransformExtension.cs new file mode 100644 index 0000000..34aea17 --- /dev/null +++ b/Runtime/ABase/Extension/UnityEngine.Transform/UnityEngine.TransformExtension.cs @@ -0,0 +1,274 @@ +using UnityEngine; + +namespace UnityEngine +{ + [UnityEngine.Scripting.Preserve] + public static class UnityEngineTransformExtension + { + /// + /// 查找子节点的名称符合的 。 + /// + /// 对象。 + /// 子节点的名称 + public static Transform FindChildName(this Transform transform, string name) + { + var child = transform.Find(name); + if (child.IsNotNull()) + { + return child; + } + + var childCount = transform.childCount; + for (int i = 0; i < childCount; i++) + { + var t = transform.GetChild(i); + if (t.name.EqualsFast(name)) + { + return t; + } + + t = t.FindChildName(name); + + if (t.IsNotNull()) + { + return t; + } + } + + return null; + } + + /// + /// 设置绝对位置的 x 坐标。 + /// + /// 对象。 + /// x 坐标值。 + public static void SetPositionX(this Transform transform, float newValue) + { + Vector3 v = transform.position; + v.x = newValue; + transform.position = v; + } + + /// + /// 设置绝对位置的 y 坐标。 + /// + /// 对象。 + /// y 坐标值。 + public static void SetPositionY(this Transform transform, float newValue) + { + Vector3 v = transform.position; + v.y = newValue; + transform.position = v; + } + + /// + /// 设置绝对位置的 z 坐标。 + /// + /// 对象。 + /// z 坐标值。 + public static void SetPositionZ(this Transform transform, float newValue) + { + Vector3 v = transform.position; + v.z = newValue; + transform.position = v; + } + + /// + /// 增加绝对位置的 x 坐标。 + /// + /// 对象。 + /// x 坐标值增量。 + public static void AddPositionX(this Transform transform, float deltaValue) + { + Vector3 v = transform.position; + v.x += deltaValue; + transform.position = v; + } + + /// + /// 增加绝对位置的 y 坐标。 + /// + /// 对象。 + /// y 坐标值增量。 + public static void AddPositionY(this Transform transform, float deltaValue) + { + Vector3 v = transform.position; + v.y += deltaValue; + transform.position = v; + } + + /// + /// 增加绝对位置的 z 坐标。 + /// + /// 对象。 + /// z 坐标值增量。 + public static void AddPositionZ(this Transform transform, float deltaValue) + { + Vector3 v = transform.position; + v.z += deltaValue; + transform.position = v; + } + + /// + /// 设置相对位置的 x 坐标。 + /// + /// 对象。 + /// x 坐标值。 + public static void SetLocalPositionX(this Transform transform, float newValue) + { + Vector3 v = transform.localPosition; + v.x = newValue; + transform.localPosition = v; + } + + /// + /// 设置相对位置的 y 坐标。 + /// + /// 对象。 + /// y 坐标值。 + public static void SetLocalPositionY(this Transform transform, float newValue) + { + Vector3 v = transform.localPosition; + v.y = newValue; + transform.localPosition = v; + } + + /// + /// 设置相对位置的 z 坐标。 + /// + /// 对象。 + /// z 坐标值。 + public static void SetLocalPositionZ(this Transform transform, float newValue) + { + Vector3 v = transform.localPosition; + v.z = newValue; + transform.localPosition = v; + } + + /// + /// 增加相对位置的 x 坐标。 + /// + /// 对象。 + /// x 坐标值。 + public static void AddLocalPositionX(this Transform transform, float deltaValue) + { + Vector3 v = transform.localPosition; + v.x += deltaValue; + transform.localPosition = v; + } + + /// + /// 增加相对位置的 y 坐标。 + /// + /// 对象。 + /// y 坐标值。 + public static void AddLocalPositionY(this Transform transform, float deltaValue) + { + Vector3 v = transform.localPosition; + v.y += deltaValue; + transform.localPosition = v; + } + + /// + /// 增加相对位置的 z 坐标。 + /// + /// 对象。 + /// z 坐标值。 + public static void AddLocalPositionZ(this Transform transform, float deltaValue) + { + Vector3 v = transform.localPosition; + v.z += deltaValue; + transform.localPosition = v; + } + + /// + /// 设置相对尺寸的 x 分量。 + /// + /// 对象。 + /// x 分量值。 + public static void SetLocalScaleX(this Transform transform, float newValue) + { + Vector3 v = transform.localScale; + v.x = newValue; + transform.localScale = v; + } + + /// + /// 设置相对尺寸的 y 分量。 + /// + /// 对象。 + /// y 分量值。 + public static void SetLocalScaleY(this Transform transform, float newValue) + { + Vector3 v = transform.localScale; + v.y = newValue; + transform.localScale = v; + } + + /// + /// 设置相对尺寸的 z 分量。 + /// + /// 对象。 + /// z 分量值。 + public static void SetLocalScaleZ(this Transform transform, float newValue) + { + Vector3 v = transform.localScale; + v.z = newValue; + transform.localScale = v; + } + + /// + /// 增加相对尺寸的 x 分量。 + /// + /// 对象。 + /// x 分量增量。 + public static void AddLocalScaleX(this Transform transform, float deltaValue) + { + Vector3 v = transform.localScale; + v.x += deltaValue; + transform.localScale = v; + } + + /// + /// 增加相对尺寸的 y 分量。 + /// + /// 对象。 + /// y 分量增量。 + public static void AddLocalScaleY(this Transform transform, float deltaValue) + { + Vector3 v = transform.localScale; + v.y += deltaValue; + transform.localScale = v; + } + + /// + /// 增加相对尺寸的 z 分量。 + /// + /// 对象。 + /// z 分量增量。 + public static void AddLocalScaleZ(this Transform transform, float deltaValue) + { + Vector3 v = transform.localScale; + v.z += deltaValue; + transform.localScale = v; + } + + /// + /// 二维空间下使 指向指向目标点的算法,使用世界坐标。 + /// + /// 对象。 + /// 要朝向的二维坐标点。 + /// 假定其 forward 向量为 + public static void LookAt2D(this Transform transform, Vector2 lookAtPoint2D) + { + Vector3 vector = lookAtPoint2D.ToVector3() - transform.position; + vector.y = 0f; + + if (vector.magnitude > 0f) + { + transform.rotation = Quaternion.LookRotation(vector.normalized, Vector3.up); + } + } + } +} \ No newline at end of file diff --git a/Runtime/ABase/Extension/UnityEngine.Transform/UnityEngine.TransformExtension.cs.meta b/Runtime/ABase/Extension/UnityEngine.Transform/UnityEngine.TransformExtension.cs.meta new file mode 100644 index 0000000..c9b2282 --- /dev/null +++ b/Runtime/ABase/Extension/UnityEngine.Transform/UnityEngine.TransformExtension.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f560f74f02a64ee9a483c606fac1b725 +timeCreated: 1694526427 \ No newline at end of file diff --git a/Runtime/ABase/Extension/UnityEngine.Transform/UnityEngine.UIExtension.cs b/Runtime/ABase/Extension/UnityEngine.Transform/UnityEngine.UIExtension.cs new file mode 100644 index 0000000..19b12e1 --- /dev/null +++ b/Runtime/ABase/Extension/UnityEngine.Transform/UnityEngine.UIExtension.cs @@ -0,0 +1,27 @@ +namespace UnityEngine +{ + [UnityEngine.Scripting.Preserve] + public static class UnityEngine_UIExtension + { + //重置为全屏自适应UI + public static void ResetToFullScreen(this RectTransform self) + { + self.anchorMin = Vector2.zero; + self.anchorMax = Vector2.one; + self.anchoredPosition3D = Vector3.zero; + self.pivot = new Vector2(0.5f, 0.5f); + self.offsetMax = Vector2.zero; + self.offsetMin = Vector2.zero; + self.sizeDelta = Vector2.zero; + self.localEulerAngles = Vector3.zero; + self.localScale = Vector3.one; + } + + //重置位置与旋转 + public static void ResetLocalPosAndRot(this RectTransform self) + { + self.localPosition = Vector3.zero; + self.localRotation = Quaternion.identity; + } + } +} diff --git a/Runtime/ABase/Extension/UnityEngine.Transform/UnityEngine.UIExtension.cs.meta b/Runtime/ABase/Extension/UnityEngine.Transform/UnityEngine.UIExtension.cs.meta new file mode 100644 index 0000000..6c44dae --- /dev/null +++ b/Runtime/ABase/Extension/UnityEngine.Transform/UnityEngine.UIExtension.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 56352ccd99c4451e8356690eff9b1e90 +timeCreated: 1739263302 \ No newline at end of file diff --git a/Runtime/ABase/Extension/UnityEngine.Vector2.meta b/Runtime/ABase/Extension/UnityEngine.Vector2.meta new file mode 100644 index 0000000..6d3a260 --- /dev/null +++ b/Runtime/ABase/Extension/UnityEngine.Vector2.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4b84b3e6d60942c39f9fbc6e5b967d15 +timeCreated: 1694526550 \ No newline at end of file diff --git a/Runtime/ABase/Extension/UnityEngine.Vector2/UnityEngine.Vector2Extension.cs b/Runtime/ABase/Extension/UnityEngine.Vector2/UnityEngine.Vector2Extension.cs new file mode 100644 index 0000000..4d7548a --- /dev/null +++ b/Runtime/ABase/Extension/UnityEngine.Vector2/UnityEngine.Vector2Extension.cs @@ -0,0 +1,26 @@ +namespace UnityEngine +{ + public static class UnityEngineVector2Extension + { + /// + /// 取 的 (x, y) 转换为 的 (x, 0, y)。 + /// + /// 要转换的 Vector2。 + /// 转换后的 Vector3。 + public static Vector3 ToVector3(this Vector2 vector2) + { + return new Vector3(vector2.x, 0f, vector2.y); + } + + /// + /// 取 的 (x, y) 和给定参数 y 转换为 的 (x, 参数 y, y)。 + /// + /// 要转换的 Vector2。 + /// Vector3 的 y 值。 + /// 转换后的 Vector3。 + public static Vector3 ToVector3(this Vector2 vector2, float y) + { + return new Vector3(vector2.x, y, vector2.y); + } + } +} \ No newline at end of file diff --git a/Runtime/ABase/Extension/UnityEngine.Vector2/UnityEngine.Vector2Extension.cs.meta b/Runtime/ABase/Extension/UnityEngine.Vector2/UnityEngine.Vector2Extension.cs.meta new file mode 100644 index 0000000..893106c --- /dev/null +++ b/Runtime/ABase/Extension/UnityEngine.Vector2/UnityEngine.Vector2Extension.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 425f418e0f0041e69195848947330483 +timeCreated: 1694526635 \ No newline at end of file diff --git a/Runtime/ABase/Extension/UnityEngine.Vector3.meta b/Runtime/ABase/Extension/UnityEngine.Vector3.meta new file mode 100644 index 0000000..3a16576 --- /dev/null +++ b/Runtime/ABase/Extension/UnityEngine.Vector3.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7e89a99f77d34c2ea459fd54fc6f202d +timeCreated: 1694526562 \ No newline at end of file diff --git a/Runtime/ABase/Extension/UnityEngine.Vector3/UnityEngine.Vector3Extension.cs b/Runtime/ABase/Extension/UnityEngine.Vector3/UnityEngine.Vector3Extension.cs new file mode 100644 index 0000000..d58d65f --- /dev/null +++ b/Runtime/ABase/Extension/UnityEngine.Vector3/UnityEngine.Vector3Extension.cs @@ -0,0 +1,29 @@ +namespace UnityEngine +{ + /// + /// 对 Unity 的扩展方法。 + /// + public static class UnityEngineVector3Extension + { + /// + /// 取 的 (x, y, z) 转换为 的 (x, z)。 + /// + /// 要转换的 Vector3。 + /// 转换后的 Vector2。 + public static Vector2 ToVector2(this Vector3 vector3) + { + return new Vector2(vector3.x, vector3.z); + } + + + /// + /// 取 的 (x, y) 转换为 的 (x, 0, y)。 + /// + /// 要转换的 Vector3。 + /// 转换后的 Vector3。 + public static Vector3 ToVector3(this Vector3Int vector3) + { + return new Vector3(vector3.x, vector3.y, vector3.z); + } + } +} \ No newline at end of file diff --git a/Runtime/ABase/Extension/UnityEngine.Vector3/UnityEngine.Vector3Extension.cs.meta b/Runtime/ABase/Extension/UnityEngine.Vector3/UnityEngine.Vector3Extension.cs.meta new file mode 100644 index 0000000..7738c7d --- /dev/null +++ b/Runtime/ABase/Extension/UnityEngine.Vector3/UnityEngine.Vector3Extension.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 24f918603ece9974690d60aa69d97be2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Helper.meta b/Runtime/ABase/Helper.meta new file mode 100644 index 0000000..e9e0687 --- /dev/null +++ b/Runtime/ABase/Helper.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 634e669ad63f4302acca846ec1a0240a +timeCreated: 1736324891 \ No newline at end of file diff --git a/Runtime/ABase/Helper/ApplicationHelper.cs b/Runtime/ABase/Helper/ApplicationHelper.cs new file mode 100644 index 0000000..76612fc --- /dev/null +++ b/Runtime/ABase/Helper/ApplicationHelper.cs @@ -0,0 +1,128 @@ +using UnityEngine; + +namespace AlicizaX +{ + /// + /// 应用帮助类 + /// + [UnityEngine.Scripting.Preserve] + public static class ApplicationHelper + { + /// + /// 是否是编辑器 + /// + [UnityEngine.Scripting.Preserve] + public static bool IsEditor + { + get + { +#if UNITY_EDITOR + return true; +#else + return false; +#endif + } + } + + /// + /// 是否是安卓 + /// + [UnityEngine.Scripting.Preserve] + public static bool IsAndroid + { + get + { +#if UNITY_ANDROID + return true; +#else + return false; +#endif + } + } + + /// + /// 是否是WebGL平台 + /// + [UnityEngine.Scripting.Preserve] + public static bool IsWebGL + { + get { return Application.platform == RuntimePlatform.WebGLPlayer; } + } + + /// + /// 是否是Windows平台 + /// + [UnityEngine.Scripting.Preserve] + public static bool IsWindows + { + get { return Application.platform == RuntimePlatform.WindowsPlayer; } + } + + /// + /// 是否是Linux平台 + /// + [UnityEngine.Scripting.Preserve] + public static bool IsLinux + { + get { return Application.platform == RuntimePlatform.LinuxPlayer; } + } + + + /// + /// 是否是Mac平台 + /// + [UnityEngine.Scripting.Preserve] + public static bool IsMacOsx + { + get { return Application.platform == RuntimePlatform.OSXPlayer; } + } + + /// + /// 是否是iOS 移动平台 + /// + [UnityEngine.Scripting.Preserve] + public static bool IsIOS + { + get + { +#if UNITY_IOS + return true; +#else + return false; +#endif + } + } + + /// + /// 退出 + /// + public static void Quit() + { +#if UNITY_EDITOR + UnityEditor.EditorApplication.isPlaying = false; + return; +#endif + Application.Quit(); + } +#if UNITY_IOS + [System.Runtime.InteropServices.DllImport("__Internal")] + private static extern void open_url(string url); +#endif + /// + /// 打开URL + /// + /// url地址 + public static void OpenURL(string url) + { +#if UNITY_EDITOR + Application.OpenURL(url); + return; +#endif +#if UNITY_IOS + open_url(url); +#else + Application.OpenURL(url); +#endif + } + } +} diff --git a/Runtime/ABase/Helper/ApplicationHelper.cs.meta b/Runtime/ABase/Helper/ApplicationHelper.cs.meta new file mode 100644 index 0000000..be5aafc --- /dev/null +++ b/Runtime/ABase/Helper/ApplicationHelper.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4bb21c08b2dd4b15af73858c5daf4a99 +timeCreated: 1676885563 \ No newline at end of file diff --git a/Runtime/ABase/Helper/CameraHelper.cs b/Runtime/ABase/Helper/CameraHelper.cs new file mode 100644 index 0000000..a015e33 --- /dev/null +++ b/Runtime/ABase/Helper/CameraHelper.cs @@ -0,0 +1,40 @@ +using System; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace AlicizaX +{ + /// + /// 相机帮助类 + /// + [UnityEngine.Scripting.Preserve] + public static class CameraHelper + { + /// + /// 获取相机快照 + /// + /// 相机 + /// 缩放比 + public static Texture2D GetCaptureScreenshot(Camera main, float scale = 0.5f) + { + Rect rect = new Rect(0, 0, Screen.width * scale, Screen.height * scale); + string name = DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss"); + RenderTexture renderTexture = RenderTexture.GetTemporary((int)rect.width, (int)rect.height, 0); + renderTexture.name = SceneManager.GetActiveScene().name + "_" + renderTexture.width + "_" + renderTexture.height + "_" + name; + main.targetTexture = renderTexture; + main.Render(); + + RenderTexture.active = renderTexture; + Texture2D screenShot = new Texture2D((int)rect.width, (int)rect.height, TextureFormat.RGB24, false) + { + name = renderTexture.name + }; + screenShot.ReadPixels(rect, 0, 0); + screenShot.Apply(); + main.targetTexture = null; + RenderTexture.active = null; + RenderTexture.ReleaseTemporary(renderTexture); + return screenShot; + } + } +} diff --git a/Runtime/ABase/Helper/CameraHelper.cs.meta b/Runtime/ABase/Helper/CameraHelper.cs.meta new file mode 100644 index 0000000..47ce360 --- /dev/null +++ b/Runtime/ABase/Helper/CameraHelper.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d6f0c95931944448a8c8f1790d20059a +timeCreated: 1663323744 \ No newline at end of file diff --git a/Runtime/ABase/Helper/CompressionUtility.cs b/Runtime/ABase/Helper/CompressionUtility.cs new file mode 100644 index 0000000..3547fee --- /dev/null +++ b/Runtime/ABase/Helper/CompressionUtility.cs @@ -0,0 +1,206 @@ +using AlicizaX; +using ICSharpCode.SharpZipLib.GZip; +using System; +using System.IO; + +namespace AlicizaX +{ + /// + /// 默认压缩解压缩辅助器。 + /// + [UnityEngine.Scripting.Preserve] + public class CompressionUtility + { + private const int CachedBytesLength = 0x1000; + private static readonly byte[] m_CachedBytes = new byte[CachedBytesLength]; + + /// + /// 压缩数据。 + /// + /// 要压缩的数据的二进制流。 + /// 要压缩的数据的二进制流的偏移。 + /// 要压缩的数据的二进制流的长度。 + /// 压缩后的数据的二进制流。 + /// 是否压缩数据成功。 + public static bool Compress(byte[] bytes, int offset, int length, Stream compressedStream) + { + if (bytes == null) + { + return false; + } + + if (offset < 0 || length < 0 || offset + length > bytes.Length) + { + return false; + } + + if (compressedStream == null) + { + return false; + } + + try + { + GZipOutputStream gZipOutputStream = new GZipOutputStream(compressedStream); + gZipOutputStream.Write(bytes, offset, length); + gZipOutputStream.Finish(); + ProcessHeader(compressedStream); + return true; + } + catch + { + return false; + } + } + + /// + /// 压缩数据。 + /// + /// 要压缩的数据的二进制流。 + /// 压缩后的数据的二进制流。 + /// 是否压缩数据成功。 + public static bool Compress(Stream stream, Stream compressedStream) + { + if (stream == null) + { + return false; + } + + if (compressedStream == null) + { + return false; + } + + try + { + GZipOutputStream gZipOutputStream = new GZipOutputStream(compressedStream); + int bytesRead = 0; + while ((bytesRead = stream.Read(m_CachedBytes, 0, CachedBytesLength)) > 0) + { + gZipOutputStream.Write(m_CachedBytes, 0, bytesRead); + } + + gZipOutputStream.Finish(); + ProcessHeader(compressedStream); + return true; + } + catch + { + return false; + } + finally + { + Array.Clear(m_CachedBytes, 0, CachedBytesLength); + } + } + + /// + /// 解压缩数据。 + /// + /// 要解压缩的数据的二进制流。 + /// 要解压缩的数据的二进制流的偏移。 + /// 要解压缩的数据的二进制流的长度。 + /// 解压缩后的数据的二进制流。 + /// 是否解压缩数据成功。 + public static bool Decompress(byte[] bytes, int offset, int length, Stream decompressedStream) + { + if (bytes == null) + { + return false; + } + + if (offset < 0 || length < 0 || offset + length > bytes.Length) + { + return false; + } + + if (decompressedStream == null) + { + return false; + } + + MemoryStream memoryStream = null; + try + { + memoryStream = new MemoryStream(bytes, offset, length, false); + using (GZipInputStream gZipInputStream = new GZipInputStream(memoryStream)) + { + int bytesRead = 0; + while ((bytesRead = gZipInputStream.Read(m_CachedBytes, 0, CachedBytesLength)) > 0) + { + decompressedStream.Write(m_CachedBytes, 0, bytesRead); + } + } + + return true; + } + catch + { + return false; + } + finally + { + if (memoryStream != null) + { + memoryStream.Dispose(); + memoryStream = null; + } + + Array.Clear(m_CachedBytes, 0, CachedBytesLength); + } + } + + /// + /// 解压缩数据。 + /// + /// 要解压缩的数据的二进制流。 + /// 解压缩后的数据的二进制流。 + /// 是否解压缩数据成功。 + public static bool Decompress(Stream stream, Stream decompressedStream) + { + if (stream == null) + { + return false; + } + + if (decompressedStream == null) + { + return false; + } + + try + { + GZipInputStream gZipInputStream = new GZipInputStream(stream); + int bytesRead = 0; + while ((bytesRead = gZipInputStream.Read(m_CachedBytes, 0, CachedBytesLength)) > 0) + { + decompressedStream.Write(m_CachedBytes, 0, bytesRead); + } + + return true; + } + catch + { + return false; + } + finally + { + Array.Clear(m_CachedBytes, 0, CachedBytesLength); + } + } + + private static void ProcessHeader(Stream compressedStream) + { + if (compressedStream.Length >= 8L) + { + long current = compressedStream.Position; + compressedStream.Position = 4L; + compressedStream.WriteByte(25); + compressedStream.WriteByte(134); + compressedStream.WriteByte(2); + compressedStream.WriteByte(32); + compressedStream.Position = current; + } + } + } +} diff --git a/Runtime/ABase/Helper/CompressionUtility.cs.meta b/Runtime/ABase/Helper/CompressionUtility.cs.meta new file mode 100644 index 0000000..193ed8a --- /dev/null +++ b/Runtime/ABase/Helper/CompressionUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1d572c5f6d619fd4cab0970b865300f7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Helper/DistinctHelper.cs b/Runtime/ABase/Helper/DistinctHelper.cs new file mode 100644 index 0000000..741c831 --- /dev/null +++ b/Runtime/ABase/Helper/DistinctHelper.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; + +namespace AlicizaX +{ + /// + /// 去重。帮助类 + /// + [UnityEngine.Scripting.Preserve] + public static class DistinctHelper + { + /// + /// 根据条件去重 + /// + /// + /// + /// + /// + /// + [UnityEngine.Scripting.Preserve] + public static IEnumerable DistinctBy(this IEnumerable source, Func keySelector) + { + var identifiedKeys = new HashSet(); + + foreach (var item in source) + { + if (identifiedKeys.Add(keySelector(item))) + { + yield return item; + } + } + } + } +} diff --git a/Runtime/ABase/Helper/DistinctHelper.cs.meta b/Runtime/ABase/Helper/DistinctHelper.cs.meta new file mode 100644 index 0000000..120b38d --- /dev/null +++ b/Runtime/ABase/Helper/DistinctHelper.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 777c1056a4804d90960109d21275c56e +timeCreated: 1670814641 \ No newline at end of file diff --git a/Runtime/ABase/Helper/FileHelper.cs b/Runtime/ABase/Helper/FileHelper.cs new file mode 100644 index 0000000..016e544 --- /dev/null +++ b/Runtime/ABase/Helper/FileHelper.cs @@ -0,0 +1,321 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using UnityEngine; + +namespace AlicizaX +{ + /// + /// 文件帮助类 + /// + [UnityEngine.Scripting.Preserve] + public static class FileHelper + { + /// + /// 获取目录下的所有文件 + /// + /// 文件存放路径列表对象 + /// 目标目录 + [UnityEngine.Scripting.Preserve] + public static void GetAllFiles(List files, string dir) + { + if (!Directory.Exists(dir)) + { + return; + } + + string[] strings = Directory.GetFiles(dir); + foreach (string item in strings) + { + files.Add(item); + } + + string[] subDirs = Directory.GetDirectories(dir); + foreach (string subDir in subDirs) + { + GetAllFiles(files, subDir); + } + } + + /// + /// 清理目录 + /// + /// 目标路径 + [UnityEngine.Scripting.Preserve] + public static void CleanDirectory(string dir) + { + if (!Directory.Exists(dir)) + { + return; + } + + foreach (string subDir in Directory.GetDirectories(dir)) + { + Directory.Delete(subDir, true); + } + + foreach (string subFile in Directory.GetFiles(dir)) + { + File.Delete(subFile); + } + } + + /// + /// 目录复制 + /// + /// 源路径 + /// 目标路径 + /// + [UnityEngine.Scripting.Preserve] + public static void CopyDirectory(string srcDir, string targetDir) + { + DirectoryInfo source = new DirectoryInfo(srcDir); + DirectoryInfo target = new DirectoryInfo(targetDir); + + if (target.FullName.StartsWith(source.FullName, StringComparison.CurrentCultureIgnoreCase)) + { + throw new Exception("父目录不能拷贝到子目录!"); + } + + if (!source.Exists) + { + return; + } + + if (!target.Exists) + { + target.Create(); + } + + FileInfo[] files = source.GetFiles(); + + for (int i = 0; i < files.Length; i++) + { + File.Copy(files[i].FullName, Path.Combine(target.FullName, files[i].Name), true); + } + + DirectoryInfo[] dirs = source.GetDirectories(); + + for (int j = 0; j < dirs.Length; j++) + { + CopyDirectory(dirs[j].FullName, Path.Combine(target.FullName, dirs[j].Name)); + } + } + + /// + /// 复制文件到目标目录 + /// + /// 源路径 + /// 目标路径 + /// 是否覆盖 + [UnityEngine.Scripting.Preserve] + public static void Copy(string sourceFileName, string destFileName, bool overwrite = false) + { + if (!File.Exists(sourceFileName)) + { + return; + } + + File.Copy(sourceFileName, destFileName, overwrite); + } + + /// + /// 删除文件 + /// + /// 文件路径 + [UnityEngine.Scripting.Preserve] + public static void Delete(string path) + { + File.Delete(path); + } + + /// + /// 判断文件是否存在 + /// + /// 文件路径 + /// + [UnityEngine.Scripting.Preserve] + public static bool IsExists(string path) + { +#if ENABLE_GAME_FRAME_X_READ_ASSETS + if (IsAndroidReadOnlyPath(path, out var readPath)) + { + return BlankReadAssets.BlankReadAssets.IsFileExists(readPath); + } +#endif + return File.Exists(path); + } + + [UnityEngine.Scripting.Preserve] + private static bool IsAndroidReadOnlyPath(string path, out string readPath) + { + if (Application.platform == RuntimePlatform.Android) + { + if (PathHelper.NormalizePath(path).Contains(PathHelper.AppResPath)) + { + readPath = path.Substring(PathHelper.AppResPath.Length); + return true; + } + } + + readPath = null; + return false; + } + + /// + /// 移动文件到目标目录 + /// + /// 文件源路径 + /// 目标路径 + [UnityEngine.Scripting.Preserve] + public static void Move(string sourceFileName, string destFileName) + { + if (!File.Exists(sourceFileName)) + { + return; + } + + Copy(sourceFileName, destFileName, true); + Delete(sourceFileName); + } + + /// + /// 读取指定路径的文件内容 + /// + /// 文件路径 + /// + [UnityEngine.Scripting.Preserve] + public static byte[] ReadAllBytes(string path) + { +#if ENABLE_GAME_FRAME_X_READ_ASSETS + if (IsAndroidReadOnlyPath((path), out var readPath)) + { + return BlankReadAssets.BlankReadAssets.Read(readPath); + } +#endif + + return File.ReadAllBytes(path); + } + + /// + /// 读取指定路径的文件内容 + /// + /// 文件路径 + /// 编码 + /// + [UnityEngine.Scripting.Preserve] + public static string ReadAllText(string path, Encoding encoding) + { + return File.ReadAllText(path, encoding); + } + + /// + /// 读取指定路径的文件内容 + /// + /// 文件路径 + /// + [UnityEngine.Scripting.Preserve] + public static string ReadAllText(string path) + { + return File.ReadAllText(path, Encoding.UTF8); + } + + /// + /// 读取指定路径的文件内容 + /// + /// 文件路径 + /// 编码 + /// + [UnityEngine.Scripting.Preserve] + public static string[] ReadAllLines(string path, Encoding encoding) + { + return File.ReadAllLines(path, encoding); + } + + /// + /// 读取指定路径的文件内容 + /// + /// 文件路径 + /// + [UnityEngine.Scripting.Preserve] + public static string[] ReadAllLines(string path) + { + return File.ReadAllLines(path, Encoding.UTF8); + } + + /// + /// 写入指定路径的文件内容 + /// + /// 文件路径 + /// 写入内容 + /// + [UnityEngine.Scripting.Preserve] + public static void ReadAllLines(string path, byte[] buffer) + { + File.WriteAllBytes(path, buffer); + } + + /// + /// 写入指定路径的文件内容 + /// + /// 文件路径 + /// 写入的内容 + /// 编码 + /// + [UnityEngine.Scripting.Preserve] + public static void WriteAllLines(string path, string[] lines, Encoding encoding) + { + File.WriteAllLines(path, lines, encoding); + } + + /// + /// 写入指定路径的文件内容 + /// + /// 文件路径 + /// 写入的内容 + /// + [UnityEngine.Scripting.Preserve] + public static void WriteAllLines(string path, string[] lines) + { + File.WriteAllLines(path, lines, Encoding.UTF8); + } + + /// + /// 写入指定路径的文件内容 + /// + /// 文件路径 + /// 写入的内容 + /// 编码 + /// + [UnityEngine.Scripting.Preserve] + public static void WriteAllText(string path, string content, Encoding encoding) + { + File.WriteAllText(path, content, encoding); + } + + /// + /// 写入指定路径的文件内容,UTF-8 + /// + /// 文件路径 + /// 写入的内容 + /// + [UnityEngine.Scripting.Preserve] + public static void WriteAllText(string path, string content) + { + File.WriteAllText(path, content, Encoding.UTF8); + } + + /// + /// 写入指定路径的文件内容 + /// + /// 文件路径 + /// 写入的内容 + /// + [UnityEngine.Scripting.Preserve] + public static void WriteAllBytes(string path, byte[] buffer) + { + File.WriteAllBytes(path, buffer); + } + } +} diff --git a/Runtime/ABase/Helper/FileHelper.cs.meta b/Runtime/ABase/Helper/FileHelper.cs.meta new file mode 100644 index 0000000..06807d4 --- /dev/null +++ b/Runtime/ABase/Helper/FileHelper.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 74cc512af7309484fa066b16175c6331 +timeCreated: 1474943113 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Helper/GameHelper.cs b/Runtime/ABase/Helper/GameHelper.cs new file mode 100644 index 0000000..2b2d22a --- /dev/null +++ b/Runtime/ABase/Helper/GameHelper.cs @@ -0,0 +1,1021 @@ +using System.Linq; +using System.Globalization; +using System.Collections.Generic; + +using System.Text.RegularExpressions; +using UnityEngine; +using UnityEngine.UI; + +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace AlicizaX +{ + public static class GameHelper + { + + + /// + /// Change color alpha (0 is transparent, 1 is opaque). + /// + public static Color Alpha(this Color col, float alpha) + { + col.a = alpha; + return col; + } + + /// + /// Change lightness of the color. + /// + public static Color Lightness(this Color col, float lightness) + { + Color.RGBToHSV(col, out var hue, out var saturation, out var _); + return Color.HSVToRGB(hue, saturation, lightness); + } + + /// + /// Change image color alpha (0 is transparent, 1 is opaque). + /// + public static void Alpha(this Image image, float alpha) + { + Color color = image.color; + color.a = alpha; + image.color = color; + } + + /// + /// Set Sound Clip to the Audio Source. + /// + public static void SetSoundClip(this AudioSource audioSource, SoundClip soundClip, float volumeMul = 1f, bool play = false) + { + if (soundClip == null || soundClip.audioClip == null || audioSource == null) return; + + if (audioSource.clip != soundClip.audioClip) + audioSource.clip = soundClip.audioClip; + + audioSource.volume = soundClip.volume * volumeMul; + + if(play && !audioSource.isPlaying) + audioSource.Play(); + } + + /// + /// Play One Shot Sudio Clip in the audio source. + /// + public static void PlayOneShotSoundClip(this AudioSource audioSource, SoundClip soundClip, float volumeMul = 1f) + { + if (soundClip == null || soundClip.audioClip == null || audioSource == null) + return; + + audioSource.PlayOneShot(soundClip.audioClip, soundClip.volume * volumeMul); + } + + /// + /// Create new unique Guid. + /// + public static string GetGuid() => System.Guid.NewGuid().ToString("N"); + + /// + /// Change Cursor States. + /// + public static void ShowCursor(bool locked, bool visible) + { + Cursor.lockState = locked ? CursorLockMode.Locked : CursorLockMode.None; + Cursor.visible = visible; + } + + /// + /// Change the GameObject layer including all children. + /// + public static void SetLayerRecursively(this GameObject obj, int layer) + { + if (layer < 0 || layer > 31) + { + Debug.LogError("Invalid layer value. Must be between 0 and 31."); + return; + } + + obj.layer = layer; + + foreach (Transform child in obj.transform) + { + child.gameObject.SetLayerRecursively(layer); + } + } + + /// + /// Set the rendering layer of the MeshRenderer in the GameObject. + /// + public static void SetRenderingLayer(this GameObject obj, uint layer, bool set = true) + { + if (layer < 0 || layer > 31) + { + Debug.LogError("Invalid layer value. Must be between 0 and 31."); + return; + } + + uint layerMask = 1u << (int)layer; + + foreach (MeshRenderer renderer in obj.GetComponentsInChildren()) + { + if (set) renderer.renderingLayerMask |= layerMask; + else renderer.renderingLayerMask &= ~layerMask; + } + } + + /// + /// Basic raycast with interact layer checking. + /// + /// Status whether the object hit by the raycast has an interact layer. + public static bool Raycast(Ray ray, out RaycastHit hitInfo, float maxDistance, int cullLayers, Layer interactLayer) + { + if (Physics.Raycast(ray, out RaycastHit hit, maxDistance, cullLayers)) + { + if (interactLayer.CompareLayer(hit.collider.gameObject)) + { + hitInfo = hit; + return true; + } + } + + hitInfo = default; + return false; + } + + /// + /// Check if the value-A and value-B are close to the tolerance. + /// + public static bool IsApproximate(float valueA, float valueB, float tollerance) + { + return Mathf.Abs(valueA - valueB) < tollerance; + } + + /// + /// Correct the Angle. + /// + public static float FixAngle(this float angle, float min, float max) + { + if (angle < min) + angle += 360F; + if (angle > max) + angle -= 360F; + + return angle; + } + + /// + /// Correct the Angle (-180, 180). + /// + public static float FixAngle180(this float angle) + { + if (angle < -180F) + angle += 360F; + if (angle > 180F) + angle -= 360F; + + return angle; + } + + /// + /// Correct the Angle (-360, 360). + /// + public static float FixAngle(this float angle) + { + if (angle < -360F) + angle += 360F; + if (angle > 360F) + angle -= 360F; + + return angle; + } + + /// + /// Correct the Angle (0 - 360). + /// + public static float FixAngle360(this float angle) + { + if (angle < 0) + angle += 360F; + if (angle > 360F) + angle -= 360F; + + return angle; + } + + /// + /// Check if value is in vector range. + /// + public static bool InRange(this Vector2 vector, float value, bool equal = false) + { + return equal ? value >= vector.x && value <= vector.y : + value > vector.x && value < vector.y; + } + + /// + /// Check if value is in vector degrees. + /// + public static bool InDegrees(this Vector2 vector, float value, bool equal = false) + { + if(vector.x > vector.y) + { + return equal ? value >= (vector.x - 360) && value <= vector.y : + value > (vector.x - 360) && value < vector.y; + } + else + { + return equal ? value >= vector.x && value <= vector.y : + value > vector.x && value < vector.y; + } + } + + /// + /// Convert string to title case. + /// + public static string ToTitleCase(this string str) + { + return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(str.ToLower()); + } + + /// + /// Check if string is empty. + /// + public static bool IsEmpty(this string str) + { + return string.IsNullOrEmpty(str); + } + + /// + /// Returns a string or otherwise if the string is empty. + /// + public static string Or(this string str, string otherwise) + { + return !string.IsNullOrEmpty(str) ? str : otherwise; + } + + /// + /// Compare Layer with LayerMask. + /// + public static bool CompareLayer(this LayerMask layermask, int layer) + { + return layermask == (layermask | (1 << layer)); + } + + /// + /// Function to generate a random integer number (No Duplicates). + /// + public static int RandomUnique(int min, int max, int last) + { + System.Random rnd = new System.Random(); + + if (min + 1 < max) + { + return Enumerable.Range(min, max).OrderBy(x => rnd.Next()).Where(x => x != last).Take(1).Single(); + } + else + { + return min; + } + } + + /// + /// Function to generate a random integer without excluded numbers. + /// + public static int RandomExclude(int min, int max, int[] ex, int maxIterations = 1000) + { + int result; + int iterations = 0; + + do + { + if (iterations > maxIterations) + { + result = -1; + break; + } + + result = UnityEngine.Random.Range(min, max); + iterations++; + } + while (ex.Contains(result)); + + return result; + } + + /// + /// Function to generate a random unique integer without excluded numbers. + /// + public static int RandomExcludeUnique(int min, int max, int[] ex, int[] current, int maxIterations = 1000) + { + return RandomExclude(min, max, ex.Concat(current).ToArray(), maxIterations); + } + + /// + /// Pick a random element from item array. + /// + public static T Random(this T[] items) + { + System.Random rnd = new System.Random(); + if(items.Length > 0) return items[rnd.Next(0, items.Length)]; + return default; + } + + /// + /// Get Random Value from Min/Max vector. + /// + public static float Random(this Vector2 vector) + { + return UnityEngine.Random.Range(vector.x, vector.y); + } + + /// + /// Get Random Value from Min/Max structure. + /// + public static float Random(this MinMax minMax) + { + return UnityEngine.Random.Range(minMax.RealMin, minMax.RealMax); + } + + /// + /// Get Random Value from Min/Max structure. + /// + public static int Random(this MinMaxInt minMax) + { + return UnityEngine.Random.Range(minMax.RealMin, minMax.RealMax); + } + + /// + /// Oscillate between the two values at speed. + /// + public static float PingPong(float min, float max, float speed = 1f) + { + return Mathf.PingPong(Time.time * speed, max - min) + min; + } + + /// + /// Wrap value between min and max values. + /// + public static int Wrap(int value, int min, int max) + { + int newValue = value % max; + if (newValue < min) newValue = max - 1; + return newValue; + } + + /// + /// Determines where a value lies between three points. + /// + public static float InverseLerp3(float min, float mid, float max, float t) + { + if (t <= min) return 0f; + if (t >= max) return 0f; + + if (t <= mid) return Mathf.InverseLerp(min, mid, t); + else return 1f - Mathf.InverseLerp(mid, max, t); + } + + /// + /// Get closest index from an integer array using a value. + /// + public static int ClosestIndex(this int[] array, int value) + { + int closestIndex = 0; + int minDifference = Mathf.Abs(array[0] - value); + + for (int i = 1; i < array.Length; i++) + { + int difference = Mathf.Abs(array[i] - value); + if (difference < minDifference) + { + minDifference = difference; + closestIndex = i; + } + } + + return closestIndex; + } + + /// + /// Play OneShot Audio Clip 2D. + /// + public static AudioSource PlayOneShot2D(Vector3 position, AudioClip clip, float volume = 1f, string name = "OneShotAudio") + { + if(clip == null) + return null; + + GameObject go = new GameObject(name); + go.transform.position = position; + AudioSource source = go.AddComponent(); + source.spatialBlend = 0f; + source.clip = clip; + source.volume = volume; + source.Play(); + Object.Destroy(go, clip.length * ((Time.timeScale < 0.01f) ? 0.01f : Time.timeScale)); + return source; + } + + /// + /// Play OneShot Sound Clip 3D. + /// + public static AudioSource PlayOneShot2D(Vector3 position, SoundClip clip, string name = "OneShotAudio") + { + if (clip == null || clip.audioClip == null) + return null; + + AudioClip audioClip = clip.audioClip; + float volume = clip.volume; + return PlayOneShot2D(position, audioClip, volume, name); + } + + /// + /// Play OneShot Audio Clip 3D. + /// + public static AudioSource PlayOneShot3D(Vector3 position, AudioClip clip, float volume = 1f, string name = "OneShotAudio") + { + if (clip == null) + return null; + + GameObject go = new GameObject(name); + go.transform.position = position; + AudioSource source = go.AddComponent(); + source.spatialBlend = 1f; + source.clip = clip; + source.volume = volume; + source.Play(); + Object.Destroy(go, clip.length * ((Time.timeScale < 0.01f) ? 0.01f : Time.timeScale)); + return source; + } + + /// + /// Play OneShot Audio Clip 3D. + /// + public static AudioSource PlayOneShot3D(Vector3 position, AudioClip clip, float maxDistance, float volume = 1f, string name = "OneShotAudio") + { + if (clip == null) + return null; + + GameObject go = new GameObject(name); + go.transform.position = position; + AudioSource source = go.AddComponent(); + source.spatialBlend = 1f; + source.clip = clip; + source.volume = volume; + source.maxDistance = maxDistance; + source.Play(); + Object.Destroy(go, clip.length * ((Time.timeScale < 0.01f) ? 0.01f : Time.timeScale)); + return source; + } + + /// + /// Play OneShot Sound Clip 3D. + /// + public static AudioSource PlayOneShot3D(Vector3 position, SoundClip clip, string name = "OneShotAudio") + { + if (clip == null || clip.audioClip == null) + return null; + + AudioClip audioClip = clip.audioClip; + float volume = clip.volume; + return PlayOneShot3D(position, audioClip, volume, name); + } + + /// + /// Remap range A to range B. + /// + public static float Remap(float minA, float maxA, float minB, float maxB, float t) + { + return minB + (t - minA) * (maxB - minB) / (maxA - minA); + } + + /// + /// Replace string part inside two chars + /// + public static string ReplacePart(this string str, char start, char end, string replace) + { + int chStart = str.IndexOf(start); + int chEnd = str.IndexOf(end); + string old = str.Substring(chStart, chEnd - chStart + 1); + return str.Replace(old, replace); + } + + /// + /// Replace tag inside two chars (Regex). + /// + public static string RegexReplaceTag(this string str, char start, char end, string tag, string replace) + { + Regex regex = new Regex($@"\{start}({tag})\{end}"); + if(regex.Match(str).Success) + return regex.Replace(str, replace); + + return str; + } + + /// + /// Get a value between two chars (Regex). + /// + public static bool RegexGet(this string str, char start, char end, out string result) + { + // escaping special characters if necessary + string escapedStart = Regex.Escape(start.ToString()); + string escapedEnd = Regex.Escape(end.ToString()); + + // regex pattern to match text between 'start' and 'end' characters + string pattern = $"{escapedStart}(.*?){escapedEnd}"; + + Match match = Regex.Match(str, pattern); + if (match.Success) + { + result = match.Groups[1].Value; + return true; + } + + result = string.Empty; + return false; + } + + /// + /// Get all values between two chars (Regex). + /// + public static bool RegexGetMany(this string str, char start, char end, out string[] results) + { + // escaping special characters if necessary + string escapedStart = Regex.Escape(start.ToString()); + string escapedEnd = Regex.Escape(end.ToString()); + + // regex pattern to match text between 'start' and 'end' characters + string pattern = $"{escapedStart}(.*?){escapedEnd}"; + + // using Regex.Matches to find all matches + MatchCollection matches = Regex.Matches(str, pattern); + + // check if there are any matches + if (matches.Count > 0) + { + // initialize a list to hold the results + List matchList = new List(); + + foreach (Match match in matches) + { + // add each found match to the list, excluding the 'start' and 'end' characters + matchList.Add(match.Groups[1].Value); + } + + // convert the list to an array and assign to 'results' + results = matchList.ToArray(); + return true; + } + + results = new string[0]; + return false; + } + + /// + /// Replace a word in string (Regex). + /// + public static string RegexReplace(this string str, string word, string replace) + { + string escapedWord = Regex.Escape(word); + string pattern = $@"\b{escapedWord}\b"; + + // replace the word with the provided replacement text + return Regex.Replace(str, pattern, replace); + } + + /// + /// Check if any animator state is being played. + /// + public static bool IsAnyPlaying(this Animator animator) + { + AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0); + return (stateInfo.length + 0.1f > stateInfo.normalizedTime || animator.IsInTransition(0)) && !stateInfo.IsName("Default"); + } + + /// + /// Applies an ease-out easing function, which starts the interpolation quickly and then slows down as it approaches the end point. + /// + public static float EaseOut(float start, float end, float t) + { + t = Mathf.Clamp01(t); + t = Mathf.Sin(t * Mathf.PI * 0.5f); + return Mathf.Lerp(start, end, t); + } + + /// + /// Applies an ease-in easing function, which starts the interpolation slowly and then accelerates as it approaches the end point. + /// + public static float EaseIn(float start, float end, float t) + { + t = Mathf.Clamp01(t); + t = 1f - Mathf.Cos(t * Mathf.PI * 0.5f); + return Mathf.Lerp(start, end, t); + } + + /// + /// Implements a smooth step interpolation, offering a more gradual and smoother transition. + /// + /// + /// Use this when you want an even gentler and smoother transition, especially for animations. + /// + public static float SmootherStep(float start, float end, float t) + { + t = Mathf.Clamp01(t); + t = t * t * t * (t * (6f * t - 15f) + 10f); + return Mathf.Lerp(start, end, t); + } + } + + public static class GizmosE + { + /// + /// Draw arrow using gizmos. + /// + public static void DrawGizmosArrow(Vector3 pos, Vector3 direction, float arrowHeadLength = 0.25f, float arrowHeadAngle = 20.0f) + { + Gizmos.DrawRay(pos, direction); + + Vector3 right = Quaternion.LookRotation(direction) * Quaternion.Euler(0, 180 + arrowHeadAngle, 0) * new Vector3(0, 0, 1); + Vector3 left = Quaternion.LookRotation(direction) * Quaternion.Euler(0, 180 - arrowHeadAngle, 0) * new Vector3(0, 0, 1); + Gizmos.DrawRay(pos + direction, right * arrowHeadLength); + Gizmos.DrawRay(pos + direction, left * arrowHeadLength); + } + + /// + /// Draw wire capsule. + /// + public static void DrawWireCapsule(Vector3 p1, Vector3 p2, float radius) + { +#if UNITY_EDITOR + // Special case when both points are in the same position + if (p1 == p2) + { + // DrawWireSphere works only in gizmo methods + Gizmos.DrawWireSphere(p1, radius); + return; + } + using (new Handles.DrawingScope(Gizmos.color, Gizmos.matrix)) + { + Quaternion p1Rotation = Quaternion.LookRotation(p1 - p2); + Quaternion p2Rotation = Quaternion.LookRotation(p2 - p1); + + // Check if capsule direction is collinear to Vector.up + float c = Vector3.Dot((p1 - p2).normalized, Vector3.up); + if (c == 1f || c == -1f) + { + // Fix rotation + p2Rotation = Quaternion.Euler(p2Rotation.eulerAngles.x, p2Rotation.eulerAngles.y + 180f, p2Rotation.eulerAngles.z); + } + + // First side + Handles.DrawWireArc(p1, p1Rotation * Vector3.left, p1Rotation * Vector3.down, 180f, radius); + Handles.DrawWireArc(p1, p1Rotation * Vector3.up, p1Rotation * Vector3.left, 180f, radius); + Handles.DrawWireDisc(p1, (p2 - p1).normalized, radius); + // Second side + Handles.DrawWireArc(p2, p2Rotation * Vector3.left, p2Rotation * Vector3.down, 180f, radius); + Handles.DrawWireArc(p2, p2Rotation * Vector3.up, p2Rotation * Vector3.left, 180f, radius); + Handles.DrawWireDisc(p2, (p1 - p2).normalized, radius); + // Lines + Handles.DrawLine(p1 + p1Rotation * Vector3.down * radius, p2 + p2Rotation * Vector3.down * radius); + Handles.DrawLine(p1 + p1Rotation * Vector3.left * radius, p2 + p2Rotation * Vector3.right * radius); + Handles.DrawLine(p1 + p1Rotation * Vector3.up * radius, p2 + p2Rotation * Vector3.up * radius); + Handles.DrawLine(p1 + p1Rotation * Vector3.right * radius, p2 + p2Rotation * Vector3.left * radius); + } +#endif + } + + /// + /// Draw wire capsule. + /// + public static void DrawWireCapsule(Vector3 position, Quaternion rotation, float radius, float height) + { +#if UNITY_EDITOR + Matrix4x4 angleMatrix = Matrix4x4.TRS(position, rotation, Handles.matrix.lossyScale); + using (new Handles.DrawingScope(angleMatrix)) + { + var pointOffset = (height - (radius * 2)) / 2; + + //draw sideways + Handles.DrawWireArc(Vector3.up * pointOffset, Vector3.left, Vector3.back, -180, radius); + Handles.DrawLine(new Vector3(0, pointOffset, -radius), new Vector3(0, -pointOffset, -radius)); + Handles.DrawLine(new Vector3(0, pointOffset, radius), new Vector3(0, -pointOffset, radius)); + Handles.DrawWireArc(Vector3.down * pointOffset, Vector3.left, Vector3.back, 180, radius); + //draw frontways + Handles.DrawWireArc(Vector3.up * pointOffset, Vector3.back, Vector3.left, 180, radius); + Handles.DrawLine(new Vector3(-radius, pointOffset, 0), new Vector3(-radius, -pointOffset, 0)); + Handles.DrawLine(new Vector3(radius, pointOffset, 0), new Vector3(radius, -pointOffset, 0)); + Handles.DrawWireArc(Vector3.down * pointOffset, Vector3.back, Vector3.left, -180, radius); + //draw center + Handles.DrawWireDisc(Vector3.up * pointOffset, Vector3.up, radius); + Handles.DrawWireDisc(Vector3.down * pointOffset, Vector3.up, radius); + + } +#endif + } + + /// + /// Draw the label aligned to the center of the position. + /// + public static void DrawCenteredLabel(Vector3 position, string labelText, GUIStyle style = null) + { +#if UNITY_EDITOR + if (style == null) style = new GUIStyle(GUI.skin.label); + + GUIContent content = new GUIContent(labelText); + Vector2 labelSize = style.CalcSize(content); + + // Calculate the offset to center the label + Vector3 screenPosition = HandleUtility.WorldToGUIPoint(position); + screenPosition.x -= labelSize.x / 2; + screenPosition.y -= labelSize.y / 2; + Vector3 worldPosition = HandleUtility.GUIPointToWorldRay(screenPosition).origin; + + Handles.Label(worldPosition, labelText, style); +#endif + } + + /// + /// Draw disc at the position. + /// + public static void DrawDisc(Vector3 position, float radius, Color outerColor, Color innerColor) + { +#if UNITY_EDITOR + Handles.color = innerColor; + Handles.DrawSolidDisc(position, Vector3.up, radius); + Handles.color = outerColor; + Handles.DrawWireDisc(position, Vector3.up, radius); +#endif + } + } + + public static class VectorE + { + /// + /// Determines where a value lies between two vectors. + /// + public static float InverseLerp(Vector3 a, Vector3 b, Vector3 value) + { + if (a != b) + { + Vector3 AB = b - a; + Vector3 AV = value - a; + float t = Vector3.Dot(AV, AB) / Vector3.Dot(AB, AB); + return Mathf.Clamp01(t); + } + + return 0f; + } + + /// + /// Get position in Quadratic Bezier Curve. + /// + /// Starting point + /// Ending point + /// Control point + public static Vector3 QuadraticBezier(Vector3 p1, Vector3 p2, Vector3 cp, float t) + { + t = Mathf.Clamp01(t); + Vector3 m1 = Vector3.LerpUnclamped(p1, cp, t); + Vector3 m2 = Vector3.LerpUnclamped(cp, p2, t); + return Vector3.LerpUnclamped(m1, m2, t); + } + + /// + /// Bezier Curve between multiple points. + /// + public static Vector3 BezierCurve(float t, params Vector3[] points) + { + if (points.Length < 1) return Vector3.zero; + else if (points.Length == 1) return points[0]; + + t = Mathf.Clamp01(t); + Vector3[] cp = points; + int n = points.Length - 1; + + while (n > 1) + { + Vector3[] rp = new Vector3[n]; + for (int i = 0; i < rp.Length; i++) + { + rp[i] = Vector3.LerpUnclamped(cp[i], cp[i + 1], t); + } + + cp = rp; + n--; + } + + return Vector3.LerpUnclamped(cp[0], cp[1], t); + } + + /// + /// Linearly interpolates between three points. + /// + public static Vector3 Lerp3(Vector3 a, Vector3 b, Vector3 c, float t) + { + t = Mathf.Clamp01(t); + if (t <= 0.5f) return Vector3.LerpUnclamped(a, b, t * 2f); + return Vector3.LerpUnclamped(b, c, (t * 2f) - 1f); + } + + /// + /// Linearly interpolates between multiple points. + /// + public static Vector3 RangeLerp(float t, params Vector3[] points) + { + if (points.Length < 1) return Vector3.zero; + else if (points.Length == 1) return points[0]; + + t = Mathf.Clamp01(t); + int pointsCount = points.Length - 1; + float scale = 1f / pointsCount; + float remap = GameHelper.Remap(0, 1, 0, pointsCount, t); + int index = Mathf.Clamp(Mathf.FloorToInt(remap), 0, pointsCount - 1); + float indexT = Mathf.InverseLerp(index * scale, (index + 1) * scale, t); + return Vector3.LerpUnclamped(points[index], points[index + 1], indexT); + } + + /// + /// Linearly interpolates between two, three or multiple points. + ///
The function selects the best method for linear interpolation.
+ ///
+ public static Vector3 Lerp(float t, Vector3[] points) + { + if (points.Length > 3) + { + return RangeLerp(t, points); + } + else if (points.Length == 3) + { + return Lerp3(points[0], points[1], points[2], t); + } + else if (points.Length == 2) + { + return Vector3.Lerp(points[0], points[1], t); + } + else if (points.Length == 1) + { + return points[0]; + } + + return Vector3.zero; + } + + /// + /// Scale a vector by another vector and return the scaled vector. + /// + public static Vector3 Multiply(this Vector3 lhs, Vector3 rhs) + { + lhs.Scale(rhs); + return lhs; + } + + /// + /// Checks if a collection contains all values from another collection. + /// + public static bool ContainsAll(this IEnumerable source, IEnumerable values) + { + return !source.Except(values).Any(); + } + } + + public static class AxisE + { + /// + /// Convert Axis to Vector3 Direction. + /// + public static Vector3 Convert(this Axis axis) => axis switch + { + Axis.X => Vector3.right, + Axis.X_Negative => Vector3.left, + Axis.Y => Vector3.up, + Axis.Y_Negative => Vector3.down, + Axis.Z => Vector3.forward, + Axis.Z_Negative => Vector3.back, + _ => Vector3.up, + }; + + /// + /// Convert Axis to Transform Direction. + /// + public static Vector3 Direction(this Transform transform, Axis axis) + { + return axis switch + { + Axis.X => transform.right, + Axis.X_Negative => -transform.right, + Axis.Y => transform.up, + Axis.Y_Negative => -transform.up, + Axis.Z => transform.forward, + Axis.Z_Negative => -transform.forward, + _ => transform.up, + }; + } + + /// + /// Get Vector Axis Component. + /// + public static float Component(this Vector3 vector, Axis axis) + { + return axis switch + { + Axis.X or Axis.X_Negative => vector.x, + Axis.Y or Axis.Y_Negative => vector.y, + Axis.Z or Axis.Z_Negative => vector.z, + _ => vector.y, + }; + } + + /// + /// Set Vector Axis Component Value. + /// + public static Vector3 SetComponent(this Vector3 vector, Axis axis, float value) + { + switch (axis) + { + case Axis.X: + case Axis.X_Negative: + vector.x = value; break; + case Axis.Y: + case Axis.Y_Negative: + vector.y = value; break; + case Axis.Z: + case Axis.Z_Negative: + vector.z = value; break; + } + return vector; + } + + /// + /// Clamp Vector Axis to Range. + /// + public static Vector3 Clamp(this Vector3 vector, Axis axis, MinMax limits) + { + switch (axis) + { + case Axis.X: + case Axis.X_Negative: + vector.x = Mathf.Clamp(vector.x, limits.RealMin, limits.RealMax); break; + case Axis.Y: + case Axis.Y_Negative: + vector.y = Mathf.Clamp(vector.y, limits.RealMin, limits.RealMax); break; + case Axis.Z: + case Axis.Z_Negative: + vector.z = Mathf.Clamp(vector.z, limits.RealMin, limits.RealMax); break; + } + return vector; + } + } + + public static class RectTransformE + { + public static void SetWidth(this RectTransform rectTransform, float width) + { + Vector2 size = rectTransform.sizeDelta; + size.x = width; + rectTransform.sizeDelta = size; + } + + public static void SetHeight(this RectTransform rectTransform, float height) + { + Vector2 size = rectTransform.sizeDelta; + size.y = height; + rectTransform.sizeDelta = size; + } + + public static void SetAnchoredX(this RectTransform rectTransform, float x) + { + Vector2 position = rectTransform.anchoredPosition; + position.x = x; + rectTransform.anchoredPosition = position; + } + + public static void SetAnchoredY(this RectTransform rectTransform, float y) + { + Vector2 position = rectTransform.anchoredPosition; + position.y = y; + rectTransform.anchoredPosition = position; + } + + public static IEnumerable GetChildTransforms(this RectTransform rectTransform) + { + foreach (var item in rectTransform) + { + yield return item as RectTransform; + } + } + } + + public struct Pair + { + public T1 Key { get; set; } + public T2 Value { get; set; } + public bool IsAssigned + { + get => Key != null && Value != null; + } + + public Pair(T1 key, T2 value) + { + Key = key; + Value = value; + } + } +} diff --git a/Runtime/ABase/Helper/GameHelper.cs.meta b/Runtime/ABase/Helper/GameHelper.cs.meta new file mode 100644 index 0000000..6bea2c8 --- /dev/null +++ b/Runtime/ABase/Helper/GameHelper.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ef59646f457548b0962c49a3434cf7e3 +timeCreated: 1758265544 \ No newline at end of file diff --git a/Runtime/ABase/Helper/GameObjectHelper.cs b/Runtime/ABase/Helper/GameObjectHelper.cs new file mode 100644 index 0000000..89a4286 --- /dev/null +++ b/Runtime/ABase/Helper/GameObjectHelper.cs @@ -0,0 +1,205 @@ +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.SceneManagement; + +namespace AlicizaX +{ + /// + /// 游戏对象帮助类 + /// + public static class GameObjectHelper + { + /// + /// 销毁子物体 + /// + /// + [UnityEngine.Scripting.Preserve] + public static void RemoveChildren(GameObject go) + { + for (var i = go.transform.childCount - 1; i >= 0; i--) + { + Destroy(go.transform.GetChild(i).gameObject); + } + } + + /// + /// 销毁游戏物体 + /// + /// + [UnityEngine.Scripting.Preserve] + public static void DestroyObject(this GameObject gameObject) + { + if (!ReferenceEquals(gameObject, null)) + { + if (Application.isEditor && !Application.isPlaying) + { + Object.DestroyImmediate(gameObject); + return; + } + + Object.Destroy(gameObject); + } + } + + /// + /// 销毁游戏物体 + /// + /// + public static void Destroy(GameObject gameObject) + { + gameObject.DestroyObject(); + } + + /// + /// 销毁游戏组件 + /// + /// + [UnityEngine.Scripting.Preserve] + public static void DestroyComponent(UnityEngine.Component component) + { + if (!ReferenceEquals(component, null)) + { + if (Application.isEditor && !Application.isPlaying) + { + Object.DestroyImmediate(component); + return; + } + + Object.Destroy(component); + } + } + + /// + /// 在指定场景中查找特定名称的节点。 + /// + /// 场景名称。 + /// 节点名称。 + /// 找到的节点的GameObject实例,如果没有找到返回null。 + public static GameObject FindChildGamObjectByName(string nodeName, string sceneName = null) + { + UnityEngine.SceneManagement.Scene scene; + if (sceneName.IsNullOrWhiteSpace()) + { + scene = SceneManager.GetActiveScene(); + } + else + { + scene = SceneManager.GetSceneByName(sceneName); + if (!scene.isLoaded) + { + return null; + } + } + + var rootObjects = scene.GetRootGameObjects(); + foreach (var rootObject in rootObjects) + { + var result = FindChildGamObjectByName(rootObject, nodeName); + if (result.IsNotNull()) + { + return result; + } + } + + return null; + } + + /// + /// 根据游戏对象名称查询子对象 + /// + /// + /// + /// + [UnityEngine.Scripting.Preserve] + public static GameObject FindChildGamObjectByName(GameObject gameObject, string name) + { + var transform = gameObject.transform.FindChildName(name); + if (transform.IsNotNull()) + { + return transform.gameObject; + } + + return null; + } + + /// + /// 创建游戏对象 + /// + /// + /// + /// + [UnityEngine.Scripting.Preserve] + public static GameObject Create(Transform parent, string name) + { + Debug.Assert(!ReferenceEquals(parent, null), nameof(parent) + " == null"); + var gameObject = new GameObject(name); + gameObject.transform.SetParent(parent); + return gameObject; + } + + /// + /// 创建游戏对象 + /// + /// + /// + /// + [UnityEngine.Scripting.Preserve] + public static GameObject Create(GameObject parent, string name) + { + Debug.Assert(!ReferenceEquals(parent, null), nameof(parent) + " == null"); + return Create(parent.transform, name); + } + + /// + /// 重置游戏对象的变换数据 + /// + /// + /// + [UnityEngine.Scripting.Preserve] + public static void ResetTransform(GameObject gameObject) + { + gameObject.transform.localScale = Vector3.one; + gameObject.transform.localPosition = Vector3.zero; + gameObject.transform.localRotation = Quaternion.identity; + } + + /// + /// 设置对象的显示排序层 + /// + /// 游戏对象 + /// 显示层 + [UnityEngine.Scripting.Preserve] + public static void SetSortingGroupLayer(GameObject gameObject, string sortingLayer) + { + SortingGroup[] sortingGroups = gameObject.GetComponentsInChildren(); + foreach (SortingGroup sg in sortingGroups) + { + sg.sortingLayerName = sortingLayer; + } + } + + /// + /// 设置对象的层 + /// + /// 游戏对象 + /// 层 + /// 是否设置子物体 + [UnityEngine.Scripting.Preserve] + public static void SetLayer(GameObject gameObject, int layer, bool children = true) + { + if (gameObject.layer != layer) + { + gameObject.layer = layer; + } + + if (children) + { + Transform[] transforms = gameObject.GetComponentsInChildren(); + foreach (var sg in transforms) + { + sg.gameObject.layer = layer; + } + } + } + } +} diff --git a/Runtime/ABase/Helper/GameObjectHelper.cs.meta b/Runtime/ABase/Helper/GameObjectHelper.cs.meta new file mode 100644 index 0000000..df7572b --- /dev/null +++ b/Runtime/ABase/Helper/GameObjectHelper.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 998123e39155448eb8a773436d34eac8 +timeCreated: 1666446475 \ No newline at end of file diff --git a/Runtime/ABase/Helper/MathHelper.cs b/Runtime/ABase/Helper/MathHelper.cs new file mode 100644 index 0000000..7b0dd64 --- /dev/null +++ b/Runtime/ABase/Helper/MathHelper.cs @@ -0,0 +1,130 @@ +using System; +using UnityEngine; + +namespace AlicizaX +{ + /// + /// 数学帮助类 + /// + public static class MathHelper + { + /// + /// 检查两个矩形是否相交 + /// + /// + /// + /// + public static bool CheckIntersect(RectInt src, RectInt target) + { + int minX = Math.Max(src.x, target.x); + int minY = Math.Max(src.y, target.y); + int maxX = Math.Min(src.x + src.width, target.x + target.width); + int maxY = Math.Min(src.y + src.height, target.y + target.height); + if (minX >= maxX || minY >= maxY) + { + return false; + } + + return true; + } + + /// + /// 检查两个矩形是否相交 + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static bool CheckIntersect(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2) + { + int minX = Math.Max(x1, x2); + int minY = Math.Max(y1, y2); + int maxX = Math.Min(x1 + w1, x2 + w2); + int maxY = Math.Min(y1 + h1, y2 + h2); + if (minX >= maxX || minY >= maxY) + { + return false; + } + + return true; + } + + /// + /// 检查两个矩形是否相交,并返回相交的区域 + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + private static bool CheckIntersect(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2, out RectInt rect) + { + rect = default; + int minX = Math.Max(x1, x2); + int minY = Math.Max(y1, y2); + int maxX = Math.Min(x1 + w1, x2 + w2); + int maxY = Math.Min(y1 + h1, y2 + h2); + if (minX >= maxX || minY >= maxY) + { + return false; + } + + rect.x = minX; + rect.y = minY; + rect.width = Math.Abs(maxX - minX); + rect.height = Math.Abs(maxY - minY); + return true; + } + + /// + /// 检查两个矩形相交的点 + /// + /// A 坐标X + /// A 坐标Y + /// A 宽度 + /// A 高度 + /// B 坐标X + /// B 坐标Y + /// B 宽度 + /// B 高度 + /// 交叉点列表 + /// 返回是否相交 + public static bool CheckIntersectPoints(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2, int[] intersectPoints) + { + Vector2Int dPt = new Vector2Int(); + + if (false == CheckIntersect(x1, y1, w1, h1, x2, y2, w2, h2, out var rectInt)) + { + return false; + } + + for (var i = 0; i < w1; i++) + { + for (var n = 0; n < h1; n++) + { + if (intersectPoints[i * h1 + n] == 1) + { + dPt.x = x1 + i; + dPt.y = y1 + n; + if (rectInt.Contains(dPt)) + { + intersectPoints[i * h1 + n] = 0; + } + } + } + } + + return true; + } + } +} diff --git a/Runtime/ABase/Helper/MathHelper.cs.meta b/Runtime/ABase/Helper/MathHelper.cs.meta new file mode 100644 index 0000000..1b416c4 --- /dev/null +++ b/Runtime/ABase/Helper/MathHelper.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c10ee3753d16449f96d506192b4652c6 +timeCreated: 1670988225 \ No newline at end of file diff --git a/Runtime/ABase/Helper/NetworkHelper.cs b/Runtime/ABase/Helper/NetworkHelper.cs new file mode 100644 index 0000000..999ff0b --- /dev/null +++ b/Runtime/ABase/Helper/NetworkHelper.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.Net; +using UnityEngine; + +namespace AlicizaX +{ + /// + /// 网络帮助类 + /// + public static class NetworkHelper + { + /// + /// 获取本地的IP列表 + /// + /// + public static string[] GetAddressIPs() + { + //获取本地的IP地址 + var list = Dns.GetHostEntry(Dns.GetHostName()).AddressList; + string[] addressIPs = new string[list.Length]; + for (var index = 0; index < list.Length; index++) + { + IPAddress address = list[index]; + addressIPs[index] = address.ToString(); + } + + return addressIPs; + } + + /// + /// 是否有网络 + /// + /// + public static bool IsReachable() + { + return Application.internetReachability != NetworkReachability.NotReachable; + } + + /// + /// 是否是WIFI + /// + /// + public static bool IsWifi() + { + return Application.internetReachability == NetworkReachability.ReachableViaLocalAreaNetwork; + } + + /// + /// 是否是移动网络 + /// + /// + public static bool IsViaCarrierData() + { + //当用户使用移动网络时 + return Application.internetReachability == NetworkReachability.ReachableViaCarrierDataNetwork; + } + } +} diff --git a/Runtime/ABase/Helper/NetworkHelper.cs.meta b/Runtime/ABase/Helper/NetworkHelper.cs.meta new file mode 100644 index 0000000..83204e2 --- /dev/null +++ b/Runtime/ABase/Helper/NetworkHelper.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: debb4ec407ad464e9aa0cbee452683e1 +timeCreated: 1666448562 \ No newline at end of file diff --git a/Runtime/ABase/Helper/ObjectHelper.cs b/Runtime/ABase/Helper/ObjectHelper.cs new file mode 100644 index 0000000..610fe39 --- /dev/null +++ b/Runtime/ABase/Helper/ObjectHelper.cs @@ -0,0 +1,12 @@ +namespace AlicizaX +{ + [UnityEngine.Scripting.Preserve] + public static class ObjectHelper + { + [UnityEngine.Scripting.Preserve] + public static void Swap(ref T t1, ref T t2) + { + (t1, t2) = (t2, t1); + } + } +} diff --git a/Runtime/ABase/Helper/ObjectHelper.cs.meta b/Runtime/ABase/Helper/ObjectHelper.cs.meta new file mode 100644 index 0000000..d826c1f --- /dev/null +++ b/Runtime/ABase/Helper/ObjectHelper.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 41b9276a283a4b6a9afcdde1b884ff70 +timeCreated: 1667393013 \ No newline at end of file diff --git a/Runtime/ABase/Helper/PathHelper.cs b/Runtime/ABase/Helper/PathHelper.cs new file mode 100644 index 0000000..07b6fd1 --- /dev/null +++ b/Runtime/ABase/Helper/PathHelper.cs @@ -0,0 +1,112 @@ +using System.Text; +using UnityEngine; + +namespace AlicizaX +{ + public static class PathHelper + { + /// + ///应用程序外部资源路径存放路径(热更新资源路径) + /// + [UnityEngine.Scripting.Preserve] + public static string AppHotfixResPath + { + get + { + string game = Application.productName; + string path = $"{Application.persistentDataPath}/{game}/"; + return path; + } + } + + /// + /// 应用程序内部资源路径存放路径 + /// + public static string AppResPath + { + get { return NormalizePath(Application.streamingAssetsPath); } + } + + /// + /// 应用程序内部资源路径存放路径(www/webrequest专用) + /// + [UnityEngine.Scripting.Preserve] + public static string AppResPath4Web + { + get + { +#if UNITY_IOS || UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN || UNITY_EDITOR + return $"file://{Application.streamingAssetsPath}"; +#else + return NormalizePath(Application.streamingAssetsPath); +#endif + } + } + + /// + /// 获取平台名称 + /// + public static string GetPlatformName + { + get + { +#if UNITY_ANDROID + return $"Android"; +#elif UNITY_STANDALONE_OSX + return $"MacOs"; +#elif UNITY_IOS || UNITY_IPHONE + return $"iOS"; +#elif UNITY_WEBGL + return $"WebGL"; +#elif UNITY_STANDALONE_WIN + return $"Windows"; +#else + return string.Empty; +#endif + } + } + + /// + /// 规范化路径 + /// + /// + /// + public static string NormalizePath(string path) + { + return path.Replace('\\', '/').Replace("\\", "/"); + } + + static readonly StringBuilder CombineStringBuilder = new StringBuilder(); + + /// + /// 拼接路径 + /// + /// + /// + public static string Combine(params string[] paths) + { + CombineStringBuilder.Clear(); + const string separatorA = "/"; + const string separatorB = "\\"; + for (var index = 0; index < paths.Length - 1; index++) + { + var path = paths[index]; + CombineStringBuilder.Append(path); + if (path.EndsWithFast(separatorA) || path.EndsWithFast(separatorB)) + { + continue; + } + + if (path.StartsWithFast(separatorA) || path.StartsWithFast(separatorB)) + { + continue; + } + + CombineStringBuilder.Append(separatorA); + } + + CombineStringBuilder.Append(paths[paths.Length - 1]); + return CombineStringBuilder.ToString(); + } + } +} diff --git a/Runtime/ABase/Helper/PathHelper.cs.meta b/Runtime/ABase/Helper/PathHelper.cs.meta new file mode 100644 index 0000000..22bd71d --- /dev/null +++ b/Runtime/ABase/Helper/PathHelper.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: abf5957e17cb46339da8f67cd4074ffd +timeCreated: 1671505311 \ No newline at end of file diff --git a/Runtime/ABase/Helper/PositionHelper.cs b/Runtime/ABase/Helper/PositionHelper.cs new file mode 100644 index 0000000..7153975 --- /dev/null +++ b/Runtime/ABase/Helper/PositionHelper.cs @@ -0,0 +1,150 @@ +using UnityEngine; + +namespace AlicizaX +{ + /// + /// 坐标帮助类 + /// + public static class PositionHelper + { + public static Vector3 RayCastV2ToV3(Vector2 pos) + { + return new Vector3(pos.x, 0, pos.y); + } + + public static Vector3 RayCastXYToV3(float x, float y) + { + return new Vector3(x, 0, y); + } + + public static Vector3 RayCastV3ToV3(Vector3 pos) + { + return new Vector3(pos.x, 0, pos.z); + } + + public static Quaternion AngleToQuaternion(int angle) + { + return Quaternion.AngleAxis(-angle, Vector3.up) * Quaternion.AngleAxis(90, Vector3.up); + } + + public static Quaternion GetVector3ToQuaternion(Vector3 source, Vector3 dire) + { + Vector3 nowPos = source; + if (nowPos == dire) + { + return new Quaternion(); + } + + Vector3 direction = (dire - nowPos).normalized; + return Quaternion.LookRotation(direction, Vector3.up); + } + + + public static float Distance2D(Vector3 v1, Vector3 v2) + { + Vector2 d1 = new Vector2(v1.x, v1.z); + Vector2 d2 = new Vector2(v2.x, v2.z); + return Vector2.Distance(d1, d2); + } + + public static Quaternion GetAngleToQuaternion(float angle) + { + return Quaternion.AngleAxis(-angle, Vector3.up) * Quaternion.AngleAxis(90, Vector3.up); + } + + public static float Vector3ToAngle360(Vector3 from, Vector3 to) + { + float angle = Vector3.Angle(from, to); + Vector3 cross = Vector3.Cross(from, to); + return cross.y > 0 ? angle : 360 - angle; + } + + /// + /// 求点到直线的距离,采用数学公式Ax+By+C = 0; d = A*p.x + B * p.y + C / sqrt(A^2 + B ^ 2) + /// + /// 线的起点 + /// 线的终点 + /// 点 + /// + public static float DistanceOfPointToVector(Vector3 startPoint, Vector3 endPoint, Vector3 point) + { + Vector2 startVe2 = startPoint.IgnoreYAxis(); + Vector2 endVe2 = endPoint.IgnoreYAxis(); + float A = endVe2.y - startVe2.y; + float B = startVe2.x - endVe2.x; + float C = endVe2.x * startVe2.y - startVe2.x * endVe2.y; + float denominator = Mathf.Sqrt(A * A + B * B); + Vector2 pointVe2 = point.IgnoreYAxis(); + return Mathf.Abs((A * pointVe2.x + B * pointVe2.y + C) / denominator); + ; + } + + /// + /// 判断射线是否碰撞到球体,如果碰撞到,返回射线起点到碰撞点之间的距离 + /// + /// 射线 + /// 中心点 + /// 半径 + /// 距离 + /// + public static bool RayCastSphere(Ray ray, Vector3 center, float redis, out float dist) + { + dist = 0; + Vector3 ma = center - ray.origin; + float distance = Vector3.Cross(ma, ray.direction).magnitude / ray.direction.magnitude; + if (distance < redis) + { + float op = PythagoreanTheorem(Vector3.Distance(center, ray.origin), distance); + float rp = PythagoreanTheorem(redis, distance); + dist = op - rp; + return true; + } + + return false; + } + + /// + /// 勾股定理 + /// + /// + /// + /// + public static float PythagoreanTheorem(float x, float y) + { + return Mathf.Sqrt(x * x + y * y); + } + + /// + /// 去掉三维向量的Y轴,把向量投射到xz平面。 + /// + /// + /// + public static Vector2 IgnoreYAxis(this Vector3 vector3) + { + return new Vector2(vector3.x, vector3.z); + } + + /// + /// 判断目标点是否位于向量的左边 + /// + /// True is on left, false is on right + public static bool PointOnLeftSideOfVector(this Vector3 vector3, Vector3 originPoint, Vector3 point) + { + Vector2 originVec2 = originPoint.IgnoreYAxis(); + + Vector2 pointVec2 = (point.IgnoreYAxis() - originVec2).normalized; + + Vector2 vector2 = vector3.IgnoreYAxis(); + + float verticalX = originVec2.x; + + float verticalY = (-verticalX * vector2.x) / vector2.y; + + Vector2 norVertical = (new Vector2(verticalX, verticalY)).normalized; + + float dotValue = Vector2.Dot(norVertical, pointVec2); + + return dotValue < 0f; + } + } +} diff --git a/Runtime/ABase/Helper/PositionHelper.cs.meta b/Runtime/ABase/Helper/PositionHelper.cs.meta new file mode 100644 index 0000000..a0eb9de --- /dev/null +++ b/Runtime/ABase/Helper/PositionHelper.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: fb356e4ecfd547b280b88ca74a847918 +timeCreated: 1666447289 \ No newline at end of file diff --git a/Runtime/ABase/Helper/RandomHelper.cs b/Runtime/ABase/Helper/RandomHelper.cs new file mode 100644 index 0000000..c471b06 --- /dev/null +++ b/Runtime/ABase/Helper/RandomHelper.cs @@ -0,0 +1,63 @@ +using System; + +namespace AlicizaX +{ + /// + /// 随机数帮助类 + /// + public static class RandomHelper + { + private static Random _random = new Random((int) DateTime.UtcNow.Ticks); + + /// + /// 设置随机种子 + /// + /// + public static void SetSeed(int seed) + { + _random = new Random(seed); + } + + /// + /// 获取UInt64范围内的随机数 + /// + /// + public static ulong NextUInt64() + { + var bytes = new byte[8]; + _random.NextBytes(bytes); + return BitConverter.ToUInt64(bytes, 0); + } + + /// + /// 获取Int64范围内的随机数 + /// + /// + public static long NextInt64() + { + var bytes = new byte[8]; + _random.NextBytes(bytes); + return BitConverter.ToInt64(bytes, 0); + } + + /// + /// 获取lower与Upper之间的随机数 + /// + /// + /// + /// + public static int Next(int lower, int upper) + { + return _random.Next(lower, upper); + } + + /// + /// 获取0与1之间的随机数 + /// + /// + public static float Next() + { + return _random.Next(0, 100_000) / 100_000f; + } + } +} diff --git a/Runtime/ABase/Helper/RandomHelper.cs.meta b/Runtime/ABase/Helper/RandomHelper.cs.meta new file mode 100644 index 0000000..488ca5d --- /dev/null +++ b/Runtime/ABase/Helper/RandomHelper.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: c7bd0b921d0715a4caa7a59d261e8803 +timeCreated: 1474942922 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Helper/TimerHelper.cs b/Runtime/ABase/Helper/TimerHelper.cs new file mode 100644 index 0000000..42b4a98 --- /dev/null +++ b/Runtime/ABase/Helper/TimerHelper.cs @@ -0,0 +1,266 @@ +using System; + +namespace AlicizaX +{ + /// + /// 游戏时间帮助类 + /// + public static class TimerHelper + { + private static readonly DateTime EpochTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private static readonly long Epoch = EpochTime.Ticks; + + /// + /// 时间差 + /// + private static long _differenceTime; + + private static bool _isSecLevel = true; + + /// + /// 微秒 + /// + public const long TicksPerMicrosecond = 1; //100微秒 + + /// + /// 10微秒 + /// + public const long TicksPer = 10 * TicksPerMicrosecond; + + /// + /// 1毫秒 + /// + public const long TicksMillisecondUnit = TicksPer * 1000; //毫秒 + + /// + /// 1秒 + /// + public const long TicksSecondUnit = TicksMillisecondUnit * 1000; // 秒 //10000000 + + /// + /// 设置时间差 + /// + /// + [UnityEngine.Scripting.Preserve] + public static void SetDifferenceTime(long timeSpan) + { + if (timeSpan > 1000000000000) + { + _isSecLevel = false; + } + else + { + _isSecLevel = true; + } + + if (_isSecLevel) + { + _differenceTime = timeSpan - ClientNow(); + } + else + { + _differenceTime = timeSpan - ClientNowMillisecond(); + } + } + + /// + /// 毫秒级 + /// + /// + [UnityEngine.Scripting.Preserve] + public static long ClientNowMillisecond() + { + return (DateTime.UtcNow.Ticks - Epoch) / TicksMillisecondUnit; + } + + [UnityEngine.Scripting.Preserve] + public static long ServerToday() + { + if (_isSecLevel) + { + return _differenceTime + ClientToday(); + } + + return (_differenceTime + ClientTodayMillisecond()) / 1000; + } + + [UnityEngine.Scripting.Preserve] + public static long ClientTodayMillisecond() + { + return (DateTime.Now.Date.ToUniversalTime().Ticks - Epoch) / 10000; + } + + /// + /// 服务器当前时间 + /// + /// + [UnityEngine.Scripting.Preserve] + public static long ServerNow() //秒级 + { + if (_isSecLevel) + { + return _differenceTime + ClientNow(); + } + + return (_differenceTime + ClientNowMillisecond()) / 1000; + } + + /// + /// 将秒数转换成TimeSpan + /// + /// 秒 + /// + [UnityEngine.Scripting.Preserve] + public static TimeSpan FromSeconds(int seconds) + { + return TimeSpan.FromSeconds(seconds); + } + + /// + /// 今天的客户端时间 + /// + /// + [UnityEngine.Scripting.Preserve] + public static long ClientToday() + { + return (DateTime.Now.Date.ToUniversalTime().Ticks - Epoch) / TicksSecondUnit; + } + + /// + /// 客户端时间,毫秒 + /// + /// + [UnityEngine.Scripting.Preserve] + public static long ClientNow() + { + return (DateTime.UtcNow.Ticks - Epoch) / 10000; + } + + /// + /// 客户端时间。秒 + /// + /// + [UnityEngine.Scripting.Preserve] + public static long ClientNowSeconds() + { + return (DateTime.UtcNow.Ticks - Epoch) / 10000000; + } + + /// + /// 客户端时间 + /// + /// + [UnityEngine.Scripting.Preserve] + public static long Now() + { + return ClientNow(); + } + + /// + /// UTC时间 秒 + /// + /// + [UnityEngine.Scripting.Preserve] + public static long UnixTimeSeconds() + { + return new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds(); + } + + /// + /// UTC时间 毫秒 + /// + /// + [UnityEngine.Scripting.Preserve] + public static long UnixTimeMilliseconds() + { + return new DateTimeOffset(DateTime.UtcNow).ToUnixTimeMilliseconds(); + } + + + /// + /// 按照UTC时间判断两个时间戳是否是同一天 + /// + /// 时间戳1 + /// 时间戳2 + /// 是否是同一天 + [UnityEngine.Scripting.Preserve] + public static bool IsUnixSameDay(long timestamp1, long timestamp2) + { + var time1 = UtcToUtcDateTime(timestamp1); + var time2 = UtcToUtcDateTime(timestamp2); + return time1.Date.Year == time2.Date.Year && time1.Date.Month == time2.Date.Month && time1.Date.Day == time2.Date.Day; + } + + /// + /// 按照本地时间判断两个时间戳是否是同一天 + /// + /// 时间戳1 + /// 时间戳2 + /// 是否是同一天 + [UnityEngine.Scripting.Preserve] + public static bool IsLocalSameDay(long timestamp1, long timestamp2) + { + var time1 = UtcToLocalDateTime(timestamp1); + var time2 = UtcToLocalDateTime(timestamp2); + return time1.Date.Year == time2.Date.Year && time1.Date.Month == time2.Date.Month && time1.Date.Day == time2.Date.Day; + } + + /// + /// UTC 时间戳 转换成UTC时间 + /// + /// UTC时间戳,单位秒 + /// + [UnityEngine.Scripting.Preserve] + public static DateTime UtcToUtcDateTime(long utcTimestamp) + { + return DateTimeOffset.FromUnixTimeSeconds(utcTimestamp).UtcDateTime; + } + + /// + /// UTC 时间戳 转换成本地时间 + /// + /// UTC时间戳,单位秒 + /// + [UnityEngine.Scripting.Preserve] + public static DateTime UtcToLocalDateTime(long utcTimestamp) + { + return DateTimeOffset.FromUnixTimeSeconds(utcTimestamp).LocalDateTime; + } + + /// + /// 判断两个时间是否是同一天 + /// + /// 时间1 + /// 时间2 + /// 是否是同一天 + [UnityEngine.Scripting.Preserve] + public static bool IsSameDay(DateTime time1, DateTime time2) + { + return time1.Date.Year == time2.Date.Year && time1.Date.Month == time2.Date.Month && time1.Date.Day == time2.Date.Day; + } + + /// + /// 指定时间转换成Unix时间戳的时间差,单位秒 + /// + /// 指定时间 + /// + [UnityEngine.Scripting.Preserve] + public static long LocalTimeToUnixTimeSeconds(DateTime time) + { + var utcDateTime = time.ToUniversalTime(); + return (long)(utcDateTime - EpochTime).TotalSeconds; + } + + /// + /// 指定时间转换成Unix时间戳的时间差,单位毫秒 + /// + /// 指定时间 + /// + [UnityEngine.Scripting.Preserve] + public static long LocalTimeToUnixTimeMilliseconds(DateTime time) + { + var utcDateTime = time.ToUniversalTime(); + return (long)(utcDateTime - EpochTime).TotalMilliseconds; + } + } +} diff --git a/Runtime/ABase/Helper/TimerHelper.cs.meta b/Runtime/ABase/Helper/TimerHelper.cs.meta new file mode 100644 index 0000000..4ccf0fb --- /dev/null +++ b/Runtime/ABase/Helper/TimerHelper.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0d61562907c14a5295de01e78a41cd22 +timeCreated: 1666538474 \ No newline at end of file diff --git a/Runtime/ABase/Helper/UnityRendererHelper.cs b/Runtime/ABase/Helper/UnityRendererHelper.cs new file mode 100644 index 0000000..55b8c95 --- /dev/null +++ b/Runtime/ABase/Helper/UnityRendererHelper.cs @@ -0,0 +1,34 @@ +using UnityEngine; + +namespace AlicizaX +{ + /// + /// Unity 渲染帮助类 + /// + public static class UnityRendererHelper + { + /// + /// 判断渲染组件是否在相机范围内 + /// + /// 渲染组件 + /// 相机对象 + /// + public static bool IsVisibleFrom(Renderer renderer, Camera camera) + { + Plane[] planes = GeometryUtility.CalculateFrustumPlanes(camera); + return GeometryUtility.TestPlanesAABB(planes, renderer.bounds); + } + + /// + /// 判断渲染组件是否在相机范围内 + /// + /// 渲染对象 + /// 相机对象 + /// + public static bool IsVisibleFrom(MeshRenderer renderer, Camera camera) + { + Plane[] planes = GeometryUtility.CalculateFrustumPlanes(camera); + return GeometryUtility.TestPlanesAABB(planes, renderer.bounds); + } + } +} diff --git a/Runtime/ABase/Helper/UnityRendererHelper.cs.meta b/Runtime/ABase/Helper/UnityRendererHelper.cs.meta new file mode 100644 index 0000000..6343692 --- /dev/null +++ b/Runtime/ABase/Helper/UnityRendererHelper.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f8cc8709b4b6495eab2f7dcba3529dcf +timeCreated: 1654011360 \ No newline at end of file diff --git a/Runtime/ABase/Helper/ZipHelper.cs b/Runtime/ABase/Helper/ZipHelper.cs new file mode 100644 index 0000000..339acc2 --- /dev/null +++ b/Runtime/ABase/Helper/ZipHelper.cs @@ -0,0 +1,327 @@ +using System; +using System.IO; +using ICSharpCode.SharpZipLib.Checksums; +using ICSharpCode.SharpZipLib.Zip; +using ICSharpCode.SharpZipLib.Zip.Compression; +using UnityEngine; + +namespace AlicizaX +{ + /// + /// 压缩帮助类 + /// + [UnityEngine.Scripting.Preserve] + public static class ZipHelper + { + private static readonly Crc32 CRC = new Crc32(); + + /// + /// 压缩文件夹 + /// + /// 要压缩的文件夹路径 + /// 压缩前的Stream,方法执行后变为压缩完成后的文件 + /// 密码 + /// 是否成功 + [UnityEngine.Scripting.Preserve] + public static bool CompressDirectoryToStream(string folderToZip, Stream stream, string password = null) + { + return CompressDirectoryToZipStream(folderToZip, stream, password) != null; + } + + + /// + /// 压缩文件夹 + /// + /// 要压缩的文件夹路径 + /// 压缩前的Stream,方法执行后变为压缩完成后的文件 + /// 密码 + /// 是否压缩成功返回ZipOutputStream,否则返回null + [UnityEngine.Scripting.Preserve] + public static ZipOutputStream CompressDirectoryToZipStream(string folderToZip, Stream stream, string password = null) + { + if (!Directory.Exists(folderToZip)) + { + return null; + } + + ZipOutputStream zipStream = new ZipOutputStream(stream); + zipStream.SetLevel(6); + if (!string.IsNullOrEmpty(password)) + { + zipStream.Password = password; + } + + if (CompressDirectory(folderToZip, zipStream, "")) + { + zipStream.Finish(); + return zipStream; + } + + GC.Collect(1); + return null; + } + + /// + /// 递归压缩文件夹的内部方法 + /// + /// 要压缩的文件夹路径 + /// 压缩输出流 + /// 此文件夹的上级文件夹 + /// 是否成功 + [UnityEngine.Scripting.Preserve] + private static bool CompressDirectory(string folderToZip, ZipOutputStream zipStream, string parentFolderName) + { + //这段是创建空文件夹,注释掉可以去掉空文件夹(因为在写入文件的时候也会创建文件夹) + if (parentFolderName.IsNotNullOrWhiteSpace()) + { + var ent = new ZipEntry(parentFolderName + "/"); + zipStream.PutNextEntry(ent); + zipStream.Flush(); + } + + var files = Directory.GetFiles(folderToZip); + foreach (string file in files) + { + byte[] buffer = File.ReadAllBytes(file); + var path = Path.GetFileName(file); + if (parentFolderName.IsNotNullOrWhiteSpace()) + { + path = parentFolderName + Path.DirectorySeparatorChar + Path.GetFileName(file); + } + + var ent = new ZipEntry(path) + { + //ent.DateTime = File.GetLastWriteTime(file);//设置文件最后修改时间 + DateTime = DateTime.Now, + Size = buffer.Length, + }; + + CRC.Reset(); + CRC.Update(buffer); + + ent.Crc = CRC.Value; + zipStream.PutNextEntry(ent); + zipStream.Write(buffer, 0, buffer.Length); + } + + var folders = Directory.GetDirectories(folderToZip); + foreach (var folder in folders) + { + var folderName = folder.Substring(folder.LastIndexOf('\\') + 1); + + if (parentFolderName.IsNotNullOrWhiteSpace()) + { + folderName = parentFolderName + "\\" + folder.Substring(folder.LastIndexOf('\\') + 1); + } + + if (!CompressDirectory(folder, zipStream, folderName)) + { + return false; + } + } + + return true; + } + + /// + /// 压缩文件夹 + /// + /// 要压缩的文件夹路径 + /// 压缩文件完整路径 + /// 密码 + /// 是否成功 + [UnityEngine.Scripting.Preserve] + public static bool CompressDirectory(string folderToZip, string zipFile, string password = null) + { + if (folderToZip.EndsWithFast(Path.DirectorySeparatorChar.ToString()) || folderToZip.EndsWithFast("/")) + { + folderToZip = folderToZip.Substring(0, folderToZip.Length - 1); + } + + var zipStream = CompressDirectoryToZipStream(folderToZip, new FileStream(zipFile, FileMode.Create, FileAccess.Write, FileShare.Write), password); + if (zipStream == null) + { + return false; + } + + zipStream.Close(); + return true; + } + + /// + /// 压缩文件 + /// + /// 要压缩的文件全名 + /// 压缩后的文件名 + /// 密码 + /// 是否成功 + [UnityEngine.Scripting.Preserve] + public static bool CompressFile(string fileToZip, string zipFile, string password = null) + { + if (!File.Exists(fileToZip)) + { + return false; + } + + using (var readStream = File.OpenRead(fileToZip)) + { + byte[] buffer = new byte[readStream.Length]; + var read = readStream.Read(buffer, 0, buffer.Length); + using (var writeStream = File.Create(zipFile)) + { + var entry = new ZipEntry(Path.GetFileName(fileToZip)) + { + DateTime = DateTime.Now, + Size = readStream.Length + }; + CRC.Reset(); + CRC.Update(buffer); + entry.Crc = CRC.Value; + using (var zipStream = new ZipOutputStream(writeStream)) + { + if (!string.IsNullOrEmpty(password)) + { + zipStream.Password = password; + } + + zipStream.PutNextEntry(entry); + zipStream.SetLevel(Deflater.BEST_COMPRESSION); + zipStream.Write(buffer, 0, buffer.Length); + } + } + } + + GC.Collect(1); + + return true; + } + + /// + /// 解压功能(解压压缩文件到指定目录) + /// + /// 待解压的文件 + /// 指定解压目标目录 + /// 密码 + /// 是否成功 + [UnityEngine.Scripting.Preserve] + public static bool DecompressFile(string fileToUnZip, string zipFolder, string password = null) + { + if (!System.IO.File.Exists(fileToUnZip)) + { + return false; + } + + if (!Directory.Exists(zipFolder)) + { + Directory.CreateDirectory(zipFolder); + } + + if (!zipFolder.EndsWith("\\")) + { + zipFolder += "\\"; + } + + using (var zipStream = new ZipInputStream(System.IO.File.OpenRead(fileToUnZip))) + { + if (!string.IsNullOrEmpty(password)) + { + zipStream.Password = password; + } + + ZipEntry zipEntry = null; + while ((zipEntry = zipStream.GetNextEntry()) != null) + { + if (zipEntry.IsDirectory) + { + continue; + } + + if (string.IsNullOrEmpty(zipEntry.Name)) + { + continue; + } + + string fileName = zipFolder + zipEntry.Name.Replace('/', '\\'); + var index = zipEntry.Name.LastIndexOf('/'); + if (index != -1) + { + string path = zipFolder + zipEntry.Name.Substring(0, index).Replace('/', '\\'); + System.IO.Directory.CreateDirectory(path); + } + + var bytes = new byte[zipEntry.Size]; + var read = zipStream.Read(bytes, 0, bytes.Length); + System.IO.File.WriteAllBytes(fileName, bytes); + } + } + + GC.Collect(1); + return true; + } + + /// + /// 压缩 + /// + /// + /// + [UnityEngine.Scripting.Preserve] + public static byte[] Compress(byte[] content) + { + //return content; + Deflater compressor = new Deflater(); + compressor.SetLevel(Deflater.BEST_COMPRESSION); + + compressor.SetInput(content); + compressor.Finish(); + + using (MemoryStream bos = new MemoryStream(content.Length)) + { + var buf = new byte[4096]; + while (!compressor.IsFinished) + { + int n = compressor.Deflate(buf); + bos.Write(buf, 0, n); + } + + return bos.ToArray(); + } + } + + /// + /// 解压缩 + /// + /// + /// + [UnityEngine.Scripting.Preserve] + public static byte[] Decompress(byte[] content) + { + return Decompress(content, 0, content.Length); + } + + /// + /// 解压缩 + /// + /// + /// + /// + /// + [UnityEngine.Scripting.Preserve] + public static byte[] Decompress(byte[] content, int offset, int count) + { + //return content; + Inflater decompressor = new Inflater(); + decompressor.SetInput(content, offset, count); + using (MemoryStream bos = new MemoryStream(content.Length)) + { + var buf = new byte[4096]; + while (!decompressor.IsFinished) + { + int n = decompressor.Inflate(buf); + bos.Write(buf, 0, n); + } + + return bos.ToArray(); + } + } + } +} diff --git a/Runtime/ABase/Helper/ZipHelper.cs.meta b/Runtime/ABase/Helper/ZipHelper.cs.meta new file mode 100644 index 0000000..f1761c8 --- /dev/null +++ b/Runtime/ABase/Helper/ZipHelper.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 75d07d90984a5dc4698c98d2fe3809bf +timeCreated: 1474948216 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/ModuleDynamicBind.cs b/Runtime/ABase/ModuleDynamicBind.cs new file mode 100644 index 0000000..b5a2305 --- /dev/null +++ b/Runtime/ABase/ModuleDynamicBind.cs @@ -0,0 +1,52 @@ +using System; +using AlicizaX.Debugger.Runtime; +using AlicizaX.Localization.Runtime; +using AlicizaX.Resource.Runtime; +using UnityEngine; + +namespace AlicizaX.Framework.Runtime.ABase +{ + public class ModuleDynamicBind : MonoBehaviour + { + [SerializeField] private ResourceComponent _resourceComponent; + [SerializeField] private DebuggerComponent _debuggerComponent; + [SerializeField] private LocalizationComponent _localizationComponent; + private ModuleDynamicBindInfo _dynamicBindInfo; + + private void OnValidate() + { + _resourceComponent = GetComponentInChildren(); + _debuggerComponent = GetComponentInChildren(); + _localizationComponent = GetComponentInChildren(); + } + + private void Awake() + { + if (Application.isEditor) return; + TextAsset text = Resources.Load("ModuleDynamicBindInfo"); + _dynamicBindInfo = Utility.Json.ToObject(text.text); + + if (_resourceComponent != null) + { + _resourceComponent.SetPlayMode(_dynamicBindInfo.ResMode); + } + + if (_debuggerComponent != null) + { + _debuggerComponent.SetActiveMode(_dynamicBindInfo.DebuggerActiveWindowType); + } + + if (_localizationComponent != null) + { + _localizationComponent.SetLanguage(_dynamicBindInfo.Language); + } + } + } + + public struct ModuleDynamicBindInfo + { + public DebuggerActiveWindowType DebuggerActiveWindowType; + public int ResMode; + public string Language; + } +} diff --git a/Runtime/ABase/ModuleDynamicBind.cs.meta b/Runtime/ABase/ModuleDynamicBind.cs.meta new file mode 100644 index 0000000..b463d58 --- /dev/null +++ b/Runtime/ABase/ModuleDynamicBind.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d544088dbfb44263bddb36004521fee5 +timeCreated: 1760163632 \ No newline at end of file diff --git a/Runtime/ABase/ObjectPool.meta b/Runtime/ABase/ObjectPool.meta new file mode 100644 index 0000000..7c186ae --- /dev/null +++ b/Runtime/ABase/ObjectPool.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6ee4cbbbf5b9431a8be20a230c92f667 +timeCreated: 1737097589 \ No newline at end of file diff --git a/Runtime/ABase/ObjectPool/IObjectPool.cs b/Runtime/ABase/ObjectPool/IObjectPool.cs new file mode 100644 index 0000000..e44fbdc --- /dev/null +++ b/Runtime/ABase/ObjectPool/IObjectPool.cs @@ -0,0 +1,181 @@ +using System; + +namespace AlicizaX.ObjectPool +{ + public interface IObjectPool + { + } + + /// + /// 对象池接口。 + /// + /// 对象类型。 + public interface IObjectPool : IObjectPool where T : ObjectBase + { + /// + /// 获取对象池名称。 + /// + string Name { get; } + + /// + /// 获取对象池完整名称。 + /// + string FullName { get; } + + /// + /// 获取对象池对象类型。 + /// + Type ObjectType { get; } + + /// + /// 获取对象池中对象的数量。 + /// + int Count { get; } + + /// + /// 获取对象池中能被释放的对象的数量。 + /// + int CanReleaseCount { get; } + + /// + /// 获取是否允许对象被多次获取。 + /// + bool AllowMultiSpawn { get; } + + /// + /// 获取或设置对象池自动释放可释放对象的间隔秒数。 + /// + float AutoReleaseInterval { get; set; } + + /// + /// 获取或设置对象池的容量。 + /// + int Capacity { get; set; } + + /// + /// 获取或设置对象池对象过期秒数。 + /// + float ExpireTime { get; set; } + + /// + /// 获取或设置对象池的优先级。 + /// + int Priority { get; set; } + + /// + /// 创建对象。 + /// + /// 对象。 + /// 对象是否已被获取。 + void Register(T obj, bool spawned); + + /// + /// 检查对象。 + /// + /// 要检查的对象是否存在。 + bool CanSpawn(); + + /// + /// 检查对象。 + /// + /// 对象名称。 + /// 要检查的对象是否存在。 + bool CanSpawn(string name); + + /// + /// 获取对象。 + /// + /// 要获取的对象。 + T Spawn(); + + /// + /// 获取对象。 + /// + /// 对象名称。 + /// 要获取的对象。 + T Spawn(string name); + + /// + /// 回收对象。 + /// + /// 要回收的对象。 + void Unspawn(T obj); + + /// + /// 回收对象。 + /// + /// 要回收的对象。 + void Unspawn(object target); + + /// + /// 设置对象是否被加锁。 + /// + /// 要设置被加锁的对象。 + /// 是否被加锁。 + void SetLocked(T obj, bool locked); + + /// + /// 设置对象是否被加锁。 + /// + /// 要设置被加锁的对象。 + /// 是否被加锁。 + void SetLocked(object target, bool locked); + + /// + /// 设置对象的优先级。 + /// + /// 要设置优先级的对象。 + /// 优先级。 + void SetPriority(T obj, int priority); + + /// + /// 设置对象的优先级。 + /// + /// 要设置优先级的对象。 + /// 优先级。 + void SetPriority(object target, int priority); + + /// + /// 释放对象。 + /// + /// 要释放的对象。 + /// 释放对象是否成功。 + bool ReleaseObject(T obj); + + /// + /// 释放对象。 + /// + /// 要释放的对象。 + /// 释放对象是否成功。 + bool ReleaseObject(object target); + + /// + /// 释放对象池中的可释放对象。 + /// + void Release(); + + /// + /// 释放对象池中的可释放对象。 + /// + /// 尝试释放对象数量。 + void Release(int toReleaseCount); + + /// + /// 释放对象池中的可释放对象。 + /// + /// 释放对象筛选函数。 + void Release(ReleaseObjectFilterCallback releaseObjectFilterCallback); + + /// + /// 释放对象池中的可释放对象。 + /// + /// 尝试释放对象数量。 + /// 释放对象筛选函数。 + void Release(int toReleaseCount, ReleaseObjectFilterCallback releaseObjectFilterCallback); + + /// + /// 释放对象池中的所有未使用对象。 + /// + void ReleaseAllUnused(); + } +} diff --git a/Runtime/ABase/ObjectPool/IObjectPool.cs.meta b/Runtime/ABase/ObjectPool/IObjectPool.cs.meta new file mode 100644 index 0000000..85a5d9a --- /dev/null +++ b/Runtime/ABase/ObjectPool/IObjectPool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4db90b1dfd1634dbd8762aeae4f46b60 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/ObjectPool/IObjectPoolModule.cs b/Runtime/ABase/ObjectPool/IObjectPoolModule.cs new file mode 100644 index 0000000..56c0432 --- /dev/null +++ b/Runtime/ABase/ObjectPool/IObjectPoolModule.cs @@ -0,0 +1,745 @@ +using System; +using System.Collections.Generic; +using AlicizaX; + +namespace AlicizaX.ObjectPool +{ + /// + /// 对象池管理器。 + /// + public interface IObjectPoolModule: IModule, IModuleUpdate + { + /// + /// 获取对象池数量。 + /// + int Count + { + get; + } + + /// + /// 检查是否存在对象池。 + /// + /// 对象类型。 + /// 是否存在对象池。 + bool HasObjectPool() where T : ObjectBase; + + /// + /// 检查是否存在对象池。 + /// + /// 对象类型。 + /// 是否存在对象池。 + bool HasObjectPool(Type objectType); + + /// + /// 检查是否存在对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 是否存在对象池。 + bool HasObjectPool(string name) where T : ObjectBase; + + /// + /// 检查是否存在对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 是否存在对象池。 + bool HasObjectPool(Type objectType, string name); + + /// + /// 检查是否存在对象池。 + /// + /// 要检查的条件。 + /// 是否存在对象池。 + bool HasObjectPool(Predicate condition); + + /// + /// 获取对象池。 + /// + /// 对象类型。 + /// 要获取的对象池。 + IObjectPool GetObjectPool() where T : ObjectBase; + + /// + /// 获取对象池。 + /// + /// 对象类型。 + /// 要获取的对象池。 + ObjectPoolBase GetObjectPool(Type objectType); + + /// + /// 获取对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 要获取的对象池。 + IObjectPool GetObjectPool(string name) where T : ObjectBase; + + /// + /// 获取对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 要获取的对象池。 + ObjectPoolBase GetObjectPool(Type objectType, string name); + + /// + /// 获取对象池。 + /// + /// 要检查的条件。 + /// 要获取的对象池。 + ObjectPoolBase GetObjectPool(Predicate condition); + + /// + /// 获取对象池。 + /// + /// 要检查的条件。 + /// 要获取的对象池。 + ObjectPoolBase[] GetObjectPools(Predicate condition); + + /// + /// 获取对象池。 + /// + /// 要检查的条件。 + /// 要获取的对象池。 + void GetObjectPools(Predicate condition, List results); + + /// + /// 获取所有对象池。 + /// + /// 所有对象池。 + ObjectPoolBase[] GetAllObjectPools(); + + /// + /// 获取所有对象池。 + /// + /// 所有对象池。 + void GetAllObjectPools(List results); + + /// + /// 获取所有对象池。 + /// + /// 是否根据对象池的优先级排序。 + /// 所有对象池。 + ObjectPoolBase[] GetAllObjectPools(bool sort); + + /// + /// 获取所有对象池。 + /// + /// 是否根据对象池的优先级排序。 + /// 所有对象池。 + void GetAllObjectPools(bool sort, List results); + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 要创建的允许单次获取的对象池。 + IObjectPool CreateSingleSpawnObjectPool() where T : ObjectBase; + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 要创建的允许单次获取的对象池。 + ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType); + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 要创建的允许单次获取的对象池。 + IObjectPool CreateSingleSpawnObjectPool(string name) where T : ObjectBase; + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 要创建的允许单次获取的对象池。 + ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, string name); + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 要创建的允许单次获取的对象池。 + IObjectPool CreateSingleSpawnObjectPool(int capacity) where T : ObjectBase; + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 要创建的允许单次获取的对象池。 + ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, int capacity); + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池对象过期秒数。 + /// 要创建的允许单次获取的对象池。 + IObjectPool CreateSingleSpawnObjectPool(float expireTime) where T : ObjectBase; + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池对象过期秒数。 + /// 要创建的允许单次获取的对象池。 + ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, float expireTime); + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 要创建的允许单次获取的对象池。 + IObjectPool CreateSingleSpawnObjectPool(string name, int capacity) where T : ObjectBase; + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 要创建的允许单次获取的对象池。 + ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, string name, int capacity); + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池对象过期秒数。 + /// 要创建的允许单次获取的对象池。 + IObjectPool CreateSingleSpawnObjectPool(string name, float expireTime) where T : ObjectBase; + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池对象过期秒数。 + /// 要创建的允许单次获取的对象池。 + ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, string name, float expireTime); + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 要创建的允许单次获取的对象池。 + IObjectPool CreateSingleSpawnObjectPool(int capacity, float expireTime) where T : ObjectBase; + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 要创建的允许单次获取的对象池。 + ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, int capacity, float expireTime); + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + IObjectPool CreateSingleSpawnObjectPool(int capacity, int priority) where T : ObjectBase; + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, int capacity, int priority); + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + IObjectPool CreateSingleSpawnObjectPool(float expireTime, int priority) where T : ObjectBase; + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, float expireTime, int priority); + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 要创建的允许单次获取的对象池。 + IObjectPool CreateSingleSpawnObjectPool(string name, int capacity, float expireTime) where T : ObjectBase; + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 要创建的允许单次获取的对象池。 + ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, string name, int capacity, float expireTime); + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + IObjectPool CreateSingleSpawnObjectPool(string name, int capacity, int priority) where T : ObjectBase; + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, string name, int capacity, int priority); + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + IObjectPool CreateSingleSpawnObjectPool(string name, float expireTime, int priority) where T : ObjectBase; + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, string name, float expireTime, int priority); + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + IObjectPool CreateSingleSpawnObjectPool(int capacity, float expireTime, int priority) where T : ObjectBase; + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, int capacity, float expireTime, int priority); + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + IObjectPool CreateSingleSpawnObjectPool(string name, int capacity, float expireTime, int priority) where T : ObjectBase; + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, string name, int capacity, float expireTime, int priority); + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池自动释放可释放对象的间隔秒数。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + IObjectPool CreateSingleSpawnObjectPool(string name, float autoReleaseInterval, int capacity, float expireTime, int priority) where T : ObjectBase; + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池自动释放可释放对象的间隔秒数。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, string name, float autoReleaseInterval, int capacity, float expireTime, int priority); + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 要创建的允许多次获取的对象池。 + IObjectPool CreateMultiSpawnObjectPool() where T : ObjectBase; + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 要创建的允许多次获取的对象池。 + ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType); + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 要创建的允许多次获取的对象池。 + IObjectPool CreateMultiSpawnObjectPool(string name) where T : ObjectBase; + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 要创建的允许多次获取的对象池。 + ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, string name); + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 要创建的允许多次获取的对象池。 + IObjectPool CreateMultiSpawnObjectPool(int capacity) where T : ObjectBase; + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 要创建的允许多次获取的对象池。 + ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, int capacity); + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池对象过期秒数。 + /// 要创建的允许多次获取的对象池。 + IObjectPool CreateMultiSpawnObjectPool(float expireTime) where T : ObjectBase; + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池对象过期秒数。 + /// 要创建的允许多次获取的对象池。 + ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, float expireTime); + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 要创建的允许多次获取的对象池。 + IObjectPool CreateMultiSpawnObjectPool(string name, int capacity) where T : ObjectBase; + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 要创建的允许多次获取的对象池。 + ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, string name, int capacity); + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池对象过期秒数。 + /// 要创建的允许多次获取的对象池。 + IObjectPool CreateMultiSpawnObjectPool(string name, float expireTime) where T : ObjectBase; + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池对象过期秒数。 + /// 要创建的允许多次获取的对象池。 + ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, string name, float expireTime); + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 要创建的允许多次获取的对象池。 + IObjectPool CreateMultiSpawnObjectPool(int capacity, float expireTime) where T : ObjectBase; + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 要创建的允许多次获取的对象池。 + ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, int capacity, float expireTime); + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + IObjectPool CreateMultiSpawnObjectPool(int capacity, int priority) where T : ObjectBase; + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, int capacity, int priority); + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + IObjectPool CreateMultiSpawnObjectPool(float expireTime, int priority) where T : ObjectBase; + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, float expireTime, int priority); + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 要创建的允许多次获取的对象池。 + IObjectPool CreateMultiSpawnObjectPool(string name, int capacity, float expireTime) where T : ObjectBase; + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 要创建的允许多次获取的对象池。 + ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, string name, int capacity, float expireTime); + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + IObjectPool CreateMultiSpawnObjectPool(string name, int capacity, int priority) where T : ObjectBase; + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, string name, int capacity, int priority); + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + IObjectPool CreateMultiSpawnObjectPool(string name, float expireTime, int priority) where T : ObjectBase; + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, string name, float expireTime, int priority); + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + IObjectPool CreateMultiSpawnObjectPool(int capacity, float expireTime, int priority) where T : ObjectBase; + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, int capacity, float expireTime, int priority); + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + IObjectPool CreateMultiSpawnObjectPool(string name, int capacity, float expireTime, int priority) where T : ObjectBase; + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, string name, int capacity, float expireTime, int priority); + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池自动释放可释放对象的间隔秒数。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + IObjectPool CreateMultiSpawnObjectPool(string name, float autoReleaseInterval, int capacity, float expireTime, int priority) where T : ObjectBase; + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池自动释放可释放对象的间隔秒数。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, string name, float autoReleaseInterval, int capacity, float expireTime, int priority); + + /// + /// 销毁对象池。 + /// + /// 对象类型。 + /// 是否销毁对象池成功。 + bool DestroyObjectPool() where T : ObjectBase; + + /// + /// 销毁对象池。 + /// + /// 对象类型。 + /// 是否销毁对象池成功。 + bool DestroyObjectPool(Type objectType); + + /// + /// 销毁对象池。 + /// + /// 对象类型。 + /// 要销毁的对象池名称。 + /// 是否销毁对象池成功。 + bool DestroyObjectPool(string name) where T : ObjectBase; + + /// + /// 销毁对象池。 + /// + /// 对象类型。 + /// 要销毁的对象池名称。 + /// 是否销毁对象池成功。 + bool DestroyObjectPool(Type objectType, string name); + + /// + /// 销毁对象池。 + /// + /// 对象类型。 + /// 要销毁的对象池。 + /// 是否销毁对象池成功。 + bool DestroyObjectPool(IObjectPool objectPool) where T : ObjectBase; + + /// + /// 销毁对象池。 + /// + /// 要销毁的对象池。 + /// 是否销毁对象池成功。 + bool DestroyObjectPool(ObjectPoolBase objectPool); + + /// + /// 释放对象池中的可释放对象。 + /// + void Release(); + + /// + /// 释放对象池中的所有未使用对象。 + /// + void ReleaseAllUnused(); + } +} diff --git a/Runtime/ABase/ObjectPool/IObjectPoolModule.cs.meta b/Runtime/ABase/ObjectPool/IObjectPoolModule.cs.meta new file mode 100644 index 0000000..59eaa10 --- /dev/null +++ b/Runtime/ABase/ObjectPool/IObjectPoolModule.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 49048c1357e5d4f68abd00f66bad5979 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/ObjectPool/ObjectBase.cs b/Runtime/ABase/ObjectPool/ObjectBase.cs new file mode 100644 index 0000000..75322e0 --- /dev/null +++ b/Runtime/ABase/ObjectPool/ObjectBase.cs @@ -0,0 +1,175 @@ +using System; +using AlicizaX; + +namespace AlicizaX.ObjectPool +{ + /// + /// 对象基类。 + /// + public abstract class ObjectBase : IMemory + { + private string m_Name; + private object m_Target; + private bool m_Locked; + private int m_Priority; + private DateTime m_LastUseTime; + + /// + /// 初始化对象基类的新实例。 + /// + public ObjectBase() + { + m_Name = null; + m_Target = null; + m_Locked = false; + m_Priority = 0; + m_LastUseTime = default(DateTime); + } + + /// + /// 获取对象名称。 + /// + public virtual string Name + { + get { return m_Name; } + protected set { m_Name = value; } + } + + /// + /// 获取对象。 + /// + public object Target + { + get { return m_Target; } + } + + /// + /// 获取或设置对象是否被加锁。 + /// + public bool Locked + { + get { return m_Locked; } + set { m_Locked = value; } + } + + /// + /// 获取或设置对象的优先级。 + /// + public int Priority + { + get { return m_Priority; } + set { m_Priority = value; } + } + + /// + /// 获取自定义释放检查标记。 + /// + public virtual bool CustomCanReleaseFlag + { + get { return true; } + } + + /// + /// 获取对象上次使用时间。 + /// + public DateTime LastUseTime + { + get { return m_LastUseTime; } + internal set { m_LastUseTime = value; } + } + + /// + /// 初始化对象基类。 + /// + /// 对象。 + protected void Initialize(object target) + { + Initialize(null, target, false, 0); + } + + /// + /// 初始化对象基类。 + /// + /// 对象名称。 + /// 对象。 + protected void Initialize(string name, object target) + { + Initialize(name, target, false, 0); + } + + /// + /// 初始化对象基类。 + /// + /// 对象名称。 + /// 对象。 + /// 对象是否被加锁。 + protected void Initialize(string name, object target, bool locked) + { + Initialize(name, target, locked, 0); + } + + /// + /// 初始化对象基类。 + /// + /// 对象名称。 + /// 对象。 + /// 对象的优先级。 + protected void Initialize(string name, object target, int priority) + { + Initialize(name, target, false, priority); + } + + /// + /// 初始化对象基类。 + /// + /// 对象名称。 + /// 对象。 + /// 对象是否被加锁。 + /// 对象的优先级。 + protected void Initialize(string name, object target, bool locked, int priority) + { + if (target == null) + { + throw new GameFrameworkException(Utility.Text.Format("Target '{0}' is invalid.", name)); + } + + m_Name = name ?? string.Empty; + m_Target = target; + m_Locked = locked; + m_Priority = priority; + m_LastUseTime = DateTime.UtcNow; + } + + /// + /// 清理对象基类。 + /// + public virtual void Clear() + { + m_Name = null; + m_Target = null; + m_Locked = false; + m_Priority = 0; + m_LastUseTime = default(DateTime); + } + + /// + /// 获取对象时的事件。 + /// + protected internal virtual void OnSpawn() + { + } + + /// + /// 回收对象时的事件。 + /// + protected internal virtual void OnUnspawn() + { + } + + /// + /// 释放对象。 + /// + /// 是否是关闭对象池时触发。 + protected internal abstract void Release(bool isShutdown); + } +} diff --git a/Runtime/ABase/ObjectPool/ObjectBase.cs.meta b/Runtime/ABase/ObjectPool/ObjectBase.cs.meta new file mode 100644 index 0000000..4c52863 --- /dev/null +++ b/Runtime/ABase/ObjectPool/ObjectBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: be7043381438c434d849c33f39b8169b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/ObjectPool/ObjectInfo.cs b/Runtime/ABase/ObjectPool/ObjectInfo.cs new file mode 100644 index 0000000..6d08d64 --- /dev/null +++ b/Runtime/ABase/ObjectPool/ObjectInfo.cs @@ -0,0 +1,115 @@ +using System; +using System.Runtime.InteropServices; + +namespace AlicizaX.ObjectPool +{ + /// + /// 对象信息。 + /// + [StructLayout(LayoutKind.Auto)] + public struct ObjectInfo + { + private readonly string m_Name; + private readonly bool m_Locked; + private readonly bool m_CustomCanReleaseFlag; + private readonly int m_Priority; + private readonly DateTime m_LastUseTime; + private readonly int m_SpawnCount; + + /// + /// 初始化对象信息的新实例。 + /// + /// 对象名称。 + /// 对象是否被加锁。 + /// 对象自定义释放检查标记。 + /// 对象的优先级。 + /// 对象上次使用时间。 + /// 对象的获取计数。 + public ObjectInfo(string name, bool locked, bool customCanReleaseFlag, int priority, DateTime lastUseTime, int spawnCount) + { + m_Name = name; + m_Locked = locked; + m_CustomCanReleaseFlag = customCanReleaseFlag; + m_Priority = priority; + m_LastUseTime = lastUseTime; + m_SpawnCount = spawnCount; + } + + /// + /// 获取对象名称。 + /// + public string Name + { + get + { + return m_Name; + } + } + + /// + /// 获取对象是否被加锁。 + /// + public bool Locked + { + get + { + return m_Locked; + } + } + + /// + /// 获取对象自定义释放检查标记。 + /// + public bool CustomCanReleaseFlag + { + get + { + return m_CustomCanReleaseFlag; + } + } + + /// + /// 获取对象的优先级。 + /// + public int Priority + { + get + { + return m_Priority; + } + } + + /// + /// 获取对象上次使用时间。 + /// + public DateTime LastUseTime + { + get + { + return m_LastUseTime; + } + } + + /// + /// 获取对象是否正在使用。 + /// + public bool IsInUse + { + get + { + return m_SpawnCount > 0; + } + } + + /// + /// 获取对象的获取计数。 + /// + public int SpawnCount + { + get + { + return m_SpawnCount; + } + } + } +} diff --git a/Runtime/ABase/ObjectPool/ObjectInfo.cs.meta b/Runtime/ABase/ObjectPool/ObjectInfo.cs.meta new file mode 100644 index 0000000..866763e --- /dev/null +++ b/Runtime/ABase/ObjectPool/ObjectInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b4086dd9ad70440bc8d3e6fc260dc338 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/ObjectPool/ObjectPoolBase.cs b/Runtime/ABase/ObjectPool/ObjectPoolBase.cs new file mode 100644 index 0000000..00df568 --- /dev/null +++ b/Runtime/ABase/ObjectPool/ObjectPoolBase.cs @@ -0,0 +1,146 @@ +using System; +using AlicizaX; + +namespace AlicizaX.ObjectPool +{ + /// + /// 对象池基类。 + /// + public abstract class ObjectPoolBase + { + private readonly string m_Name; + + /// + /// 初始化对象池基类的新实例。 + /// + public ObjectPoolBase() + : this(null) + { + } + + /// + /// 初始化对象池基类的新实例。 + /// + /// 对象池名称。 + public ObjectPoolBase(string name) + { + m_Name = name ?? string.Empty; + } + + /// + /// 获取对象池名称。 + /// + public string Name + { + get + { + return m_Name; + } + } + + /// + /// 获取对象池完整名称。 + /// + public string FullName + { + get + { + return new TypeNamePair(ObjectType, m_Name).ToString(); + } + } + + /// + /// 获取对象池对象类型。 + /// + public abstract Type ObjectType + { + get; + } + + /// + /// 获取对象池中对象的数量。 + /// + public abstract int Count + { + get; + } + + /// + /// 获取对象池中能被释放的对象的数量。 + /// + public abstract int CanReleaseCount + { + get; + } + + /// + /// 获取是否允许对象被多次获取。 + /// + public abstract bool AllowMultiSpawn + { + get; + } + + /// + /// 获取或设置对象池自动释放可释放对象的间隔秒数。 + /// + public abstract float AutoReleaseInterval + { + get; + set; + } + + /// + /// 获取或设置对象池的容量。 + /// + public abstract int Capacity + { + get; + set; + } + + /// + /// 获取或设置对象池对象过期秒数。 + /// + public abstract float ExpireTime + { + get; + set; + } + + /// + /// 获取或设置对象池的优先级。 + /// + public abstract int Priority + { + get; + set; + } + + /// + /// 释放对象池中的可释放对象。 + /// + public abstract void Release(); + + /// + /// 释放对象池中的可释放对象。 + /// + /// 尝试释放对象数量。 + public abstract void Release(int toReleaseCount); + + /// + /// 释放对象池中的所有未使用对象。 + /// + public abstract void ReleaseAllUnused(); + + /// + /// 获取所有对象信息。 + /// + /// 所有对象信息。 + public abstract ObjectInfo[] GetAllObjectInfos(); + + internal abstract void Update(float elapseSeconds, float realElapseSeconds); + + internal abstract void Shutdown(); + } +} diff --git a/Runtime/ABase/ObjectPool/ObjectPoolBase.cs.meta b/Runtime/ABase/ObjectPool/ObjectPoolBase.cs.meta new file mode 100644 index 0000000..cc9d1c8 --- /dev/null +++ b/Runtime/ABase/ObjectPool/ObjectPoolBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 55354203a152747e9a796e696909fb91 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/ObjectPool/ObjectPoolComponent.cs b/Runtime/ABase/ObjectPool/ObjectPoolComponent.cs new file mode 100644 index 0000000..42b0ab3 --- /dev/null +++ b/Runtime/ABase/ObjectPool/ObjectPoolComponent.cs @@ -0,0 +1,46 @@ +using AlicizaX.ObjectPool; +using UnityEngine; + +namespace AlicizaX +{ + /// + /// 对象池组件。 + /// + public sealed class ObjectPoolComponent : MonoBehaviour + { + private IObjectPoolModule _mObjectPoolModule = null; + + /// + /// 获取对象池数量。 + /// + public int Count + { + get { return _mObjectPoolModule.Count; } + } + + private void Awake() + { + _mObjectPoolModule = ModuleSystem.RegisterModule(); + if (_mObjectPoolModule == null) + { + Log.Error("Object pool manager is invalid."); + return; + } + } + + private void OnDestroy() + { + _mObjectPoolModule = null; + } + + /// + /// 获取所有对象池。 + /// + /// 是否根据对象池的优先级排序。 + /// 所有对象池。 + public ObjectPoolBase[] GetAllObjectPools(bool sort) + { + return _mObjectPoolModule.GetAllObjectPools(sort); + } + } +} diff --git a/Runtime/ABase/ObjectPool/ObjectPoolComponent.cs.meta b/Runtime/ABase/ObjectPool/ObjectPoolComponent.cs.meta new file mode 100644 index 0000000..432ece2 --- /dev/null +++ b/Runtime/ABase/ObjectPool/ObjectPoolComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1e28a727443c86c40aeb42ff20e0a343 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/ObjectPool/ObjectPoolManager.Object.cs b/Runtime/ABase/ObjectPool/ObjectPoolManager.Object.cs new file mode 100644 index 0000000..c534df1 --- /dev/null +++ b/Runtime/ABase/ObjectPool/ObjectPoolManager.Object.cs @@ -0,0 +1,163 @@ +using System; +using AlicizaX; + +namespace AlicizaX.ObjectPool +{ + internal sealed partial class ObjectPoolModule : IObjectPoolModule + { + /// + /// 内部对象。 + /// + /// 对象类型。 + private sealed class Object : IMemory where T : ObjectBase + { + private T m_Object; + private int m_SpawnCount; + + /// + /// 初始化内部对象的新实例。 + /// + public Object() + { + m_Object = null; + m_SpawnCount = 0; + } + + /// + /// 获取对象名称。 + /// + public string Name + { + get { return m_Object.Name; } + } + + /// + /// 获取对象是否被加锁。 + /// + public bool Locked + { + get { return m_Object.Locked; } + internal set { m_Object.Locked = value; } + } + + /// + /// 获取对象的优先级。 + /// + public int Priority + { + get { return m_Object.Priority; } + internal set { m_Object.Priority = value; } + } + + /// + /// 获取自定义释放检查标记。 + /// + public bool CustomCanReleaseFlag + { + get { return m_Object.CustomCanReleaseFlag; } + } + + /// + /// 获取对象上次使用时间。 + /// + public DateTime LastUseTime + { + get { return m_Object.LastUseTime; } + } + + /// + /// 获取对象是否正在使用。 + /// + public bool IsInUse + { + get { return m_SpawnCount > 0; } + } + + /// + /// 获取对象的获取计数。 + /// + public int SpawnCount + { + get { return m_SpawnCount; } + } + + /// + /// 创建内部对象。 + /// + /// 对象。 + /// 对象是否已被获取。 + /// 创建的内部对象。 + public static Object Create(T obj, bool spawned) + { + if (obj == null) + { + throw new GameFrameworkException("Object is invalid."); + } + + Object internalObject = MemoryPool.Acquire>(); + internalObject.m_Object = obj; + internalObject.m_SpawnCount = spawned ? 1 : 0; + if (spawned) + { + obj.OnSpawn(); + } + + return internalObject; + } + + /// + /// 清理内部对象。 + /// + public void Clear() + { + m_Object = null; + m_SpawnCount = 0; + } + + /// + /// 查看对象。 + /// + /// 对象。 + public T Peek() + { + return m_Object; + } + + /// + /// 获取对象。 + /// + /// 对象。 + public T Spawn() + { + m_SpawnCount++; + m_Object.LastUseTime = DateTime.UtcNow; + m_Object.OnSpawn(); + return m_Object; + } + + /// + /// 回收对象。 + /// + public void Unspawn() + { + m_Object.OnUnspawn(); + m_Object.LastUseTime = DateTime.UtcNow; + m_SpawnCount--; + if (m_SpawnCount < 0) + { + throw new GameFrameworkException(Utility.Text.Format("Object '{0}' spawn count is less than 0.", Name)); + } + } + + /// + /// 释放对象。 + /// + /// 是否是关闭对象池时触发。 + public void Release(bool isShutdown) + { + m_Object.Release(isShutdown); + MemoryPool.Release(m_Object); + } + } + } +} diff --git a/Runtime/ABase/ObjectPool/ObjectPoolManager.Object.cs.meta b/Runtime/ABase/ObjectPool/ObjectPoolManager.Object.cs.meta new file mode 100644 index 0000000..6f0fcb9 --- /dev/null +++ b/Runtime/ABase/ObjectPool/ObjectPoolManager.Object.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 35ccf480f8b7a42a4ba523c51d58e544 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/ObjectPool/ObjectPoolManager.ObjectPool.cs b/Runtime/ABase/ObjectPool/ObjectPoolManager.ObjectPool.cs new file mode 100644 index 0000000..f3afb9f --- /dev/null +++ b/Runtime/ABase/ObjectPool/ObjectPoolManager.ObjectPool.cs @@ -0,0 +1,604 @@ +using System; +using System.Collections.Generic; +using AlicizaX; + +namespace AlicizaX.ObjectPool +{ + internal sealed partial class ObjectPoolModule : IObjectPoolModule + { + /// + /// 对象池。 + /// + /// 对象类型。 + private sealed class ObjectPool : ObjectPoolBase, IObjectPool where T : ObjectBase + { + private readonly GameFrameworkMultiDictionary> m_Objects; + private readonly Dictionary> m_ObjectMap; + private readonly ReleaseObjectFilterCallback m_DefaultReleaseObjectFilterCallback; + private readonly List m_CachedCanReleaseObjects; + private readonly List m_CachedToReleaseObjects; + private readonly bool m_AllowMultiSpawn; + private float m_AutoReleaseInterval; + private int m_Capacity; + private float m_ExpireTime; + private int m_Priority; + private float m_AutoReleaseTime; + + /// + /// 初始化对象池的新实例。 + /// + /// 对象池名称。 + /// 是否允许对象被多次获取。 + /// 对象池自动释放可释放对象的间隔秒数。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + public ObjectPool(string name, bool allowMultiSpawn, float autoReleaseInterval, int capacity, float expireTime, int priority) + : base(name) + { + m_Objects = new GameFrameworkMultiDictionary>(); + m_ObjectMap = new Dictionary>(); + m_DefaultReleaseObjectFilterCallback = DefaultReleaseObjectFilterCallback; + m_CachedCanReleaseObjects = new List(); + m_CachedToReleaseObjects = new List(); + m_AllowMultiSpawn = allowMultiSpawn; + m_AutoReleaseInterval = autoReleaseInterval; + Capacity = capacity; + ExpireTime = expireTime; + m_Priority = priority; + m_AutoReleaseTime = 0f; + } + + /// + /// 获取对象池对象类型。 + /// + public override Type ObjectType + { + get { return typeof(T); } + } + + /// + /// 获取对象池中对象的数量。 + /// + public override int Count + { + get { return m_ObjectMap.Count; } + } + + /// + /// 获取对象池中能被释放的对象的数量。 + /// + public override int CanReleaseCount + { + get + { + GetCanReleaseObjects(m_CachedCanReleaseObjects); + return m_CachedCanReleaseObjects.Count; + } + } + + /// + /// 获取是否允许对象被多次获取。 + /// + public override bool AllowMultiSpawn + { + get { return m_AllowMultiSpawn; } + } + + /// + /// 获取或设置对象池自动释放可释放对象的间隔秒数。 + /// + public override float AutoReleaseInterval + { + get { return m_AutoReleaseInterval; } + set { m_AutoReleaseInterval = value; } + } + + /// + /// 获取或设置对象池的容量。 + /// + public override int Capacity + { + get { return m_Capacity; } + set + { + if (value < 0) + { + throw new GameFrameworkException("Capacity is invalid."); + } + + if (m_Capacity == value) + { + return; + } + + m_Capacity = value; + Release(); + } + } + + /// + /// 获取或设置对象池对象过期秒数。 + /// + public override float ExpireTime + { + get { return m_ExpireTime; } + + set + { + if (value < 0f) + { + throw new GameFrameworkException("ExpireTime is invalid."); + } + + if (ExpireTime == value) + { + return; + } + + m_ExpireTime = value; + Release(); + } + } + + /// + /// 获取或设置对象池的优先级。 + /// + public override int Priority + { + get { return m_Priority; } + set { m_Priority = value; } + } + + /// + /// 创建对象。 + /// + /// 对象。 + /// 对象是否已被获取。 + public void Register(T obj, bool spawned) + { + if (obj == null) + { + throw new GameFrameworkException("Object is invalid."); + } + + Object internalObject = Object.Create(obj, spawned); + m_Objects.Add(obj.Name, internalObject); + m_ObjectMap.Add(obj.Target, internalObject); + + if (Count > m_Capacity) + { + Release(); + } + } + + /// + /// 检查对象。 + /// + /// 要检查的对象是否存在。 + public bool CanSpawn() + { + return CanSpawn(string.Empty); + } + + /// + /// 检查对象。 + /// + /// 对象名称。 + /// 要检查的对象是否存在。 + public bool CanSpawn(string name) + { + if (name == null) + { + throw new GameFrameworkException("Name is invalid."); + } + + GameFrameworkLinkedListRange> objectRange = default(GameFrameworkLinkedListRange>); + if (m_Objects.TryGetValue(name, out objectRange)) + { + foreach (Object internalObject in objectRange) + { + if (m_AllowMultiSpawn || !internalObject.IsInUse) + { + return true; + } + } + } + + return false; + } + + /// + /// 获取对象。 + /// + /// 要获取的对象。 + public T Spawn() + { + return Spawn(string.Empty); + } + + /// + /// 获取对象。 + /// + /// 对象名称。 + /// 要获取的对象。 + public T Spawn(string name) + { + if (name == null) + { + throw new GameFrameworkException("Name is invalid."); + } + + GameFrameworkLinkedListRange> objectRange = default(GameFrameworkLinkedListRange>); + if (m_Objects.TryGetValue(name, out objectRange)) + { + foreach (Object internalObject in objectRange) + { + if (m_AllowMultiSpawn || !internalObject.IsInUse) + { + return internalObject.Spawn(); + } + } + } + + return null; + } + + /// + /// 回收对象。 + /// + /// 要回收的对象。 + public void Unspawn(T obj) + { + if (obj == null) + { + throw new GameFrameworkException("Object is invalid."); + } + + Unspawn(obj.Target); + } + + /// + /// 回收对象。 + /// + /// 要回收的对象。 + public void Unspawn(object target) + { + if (target == null) + { + throw new GameFrameworkException("Target is invalid."); + } + + Object internalObject = GetObject(target); + if (internalObject != null) + { + internalObject.Unspawn(); + if (Count > m_Capacity && internalObject.SpawnCount <= 0) + { + Release(); + } + } + else + { + throw new GameFrameworkException(Utility.Text.Format("Can not find target in object pool '{0}', target type is '{1}', target value is '{2}'.", new TypeNamePair(typeof(T), Name), target.GetType().FullName, target)); + } + } + + /// + /// 设置对象是否被加锁。 + /// + /// 要设置被加锁的对象。 + /// 是否被加锁。 + public void SetLocked(T obj, bool locked) + { + if (obj == null) + { + throw new GameFrameworkException("Object is invalid."); + } + + SetLocked(obj.Target, locked); + } + + /// + /// 设置对象是否被加锁。 + /// + /// 要设置被加锁的对象。 + /// 是否被加锁。 + public void SetLocked(object target, bool locked) + { + if (target == null) + { + throw new GameFrameworkException("Target is invalid."); + } + + Object internalObject = GetObject(target); + if (internalObject != null) + { + internalObject.Locked = locked; + } + else + { + throw new GameFrameworkException(Utility.Text.Format("Can not find target in object pool '{0}', target type is '{1}', target value is '{2}'.", new TypeNamePair(typeof(T), Name), target.GetType().FullName, target)); + } + } + + /// + /// 设置对象的优先级。 + /// + /// 要设置优先级的对象。 + /// 优先级。 + public void SetPriority(T obj, int priority) + { + if (obj == null) + { + throw new GameFrameworkException("Object is invalid."); + } + + SetPriority(obj.Target, priority); + } + + /// + /// 设置对象的优先级。 + /// + /// 要设置优先级的对象。 + /// 优先级。 + public void SetPriority(object target, int priority) + { + if (target == null) + { + throw new GameFrameworkException("Target is invalid."); + } + + Object internalObject = GetObject(target); + if (internalObject != null) + { + internalObject.Priority = priority; + } + else + { + throw new GameFrameworkException(Utility.Text.Format("Can not find target in object pool '{0}', target type is '{1}', target value is '{2}'.", new TypeNamePair(typeof(T), Name), target.GetType().FullName, target)); + } + } + + /// + /// 释放对象。 + /// + /// 要释放的对象。 + /// 释放对象是否成功。 + public bool ReleaseObject(T obj) + { + if (obj == null) + { + throw new GameFrameworkException("Object is invalid."); + } + + return ReleaseObject(obj.Target); + } + + /// + /// 释放对象。 + /// + /// 要释放的对象。 + /// 释放对象是否成功。 + public bool ReleaseObject(object target) + { + if (target == null) + { + throw new GameFrameworkException("Target is invalid."); + } + + Object internalObject = GetObject(target); + if (internalObject == null) + { + return false; + } + + if (internalObject.IsInUse || internalObject.Locked || !internalObject.CustomCanReleaseFlag) + { + return false; + } + + m_Objects.Remove(internalObject.Name, internalObject); + m_ObjectMap.Remove(internalObject.Peek().Target); + + internalObject.Release(false); + MemoryPool.Release(internalObject); + return true; + } + + /// + /// 释放对象池中的可释放对象。 + /// + public override void Release() + { + Release(Count - m_Capacity, m_DefaultReleaseObjectFilterCallback); + } + + /// + /// 释放对象池中的可释放对象。 + /// + /// 尝试释放对象数量。 + public override void Release(int toReleaseCount) + { + Release(toReleaseCount, m_DefaultReleaseObjectFilterCallback); + } + + /// + /// 释放对象池中的可释放对象。 + /// + /// 释放对象筛选函数。 + public void Release(ReleaseObjectFilterCallback releaseObjectFilterCallback) + { + Release(Count - m_Capacity, releaseObjectFilterCallback); + } + + /// + /// 释放对象池中的可释放对象。 + /// + /// 尝试释放对象数量。 + /// 释放对象筛选函数。 + public void Release(int toReleaseCount, ReleaseObjectFilterCallback releaseObjectFilterCallback) + { + if (releaseObjectFilterCallback == null) + { + throw new GameFrameworkException("Release object filter callback is invalid."); + } + + if (toReleaseCount < 0) + { + toReleaseCount = 0; + } + + DateTime expireTime = DateTime.MinValue; + if (m_ExpireTime < float.MaxValue) + { + expireTime = DateTime.UtcNow.AddSeconds(-m_ExpireTime); + } + + m_AutoReleaseTime = 0f; + GetCanReleaseObjects(m_CachedCanReleaseObjects); + List toReleaseObjects = releaseObjectFilterCallback(m_CachedCanReleaseObjects, toReleaseCount, expireTime); + if (toReleaseObjects == null || toReleaseObjects.Count <= 0) + { + return; + } + + foreach (T toReleaseObject in toReleaseObjects) + { + ReleaseObject(toReleaseObject); + } + } + + /// + /// 释放对象池中的所有未使用对象。 + /// + public override void ReleaseAllUnused() + { + m_AutoReleaseTime = 0f; + GetCanReleaseObjects(m_CachedCanReleaseObjects); + foreach (T toReleaseObject in m_CachedCanReleaseObjects) + { + ReleaseObject(toReleaseObject); + } + } + + /// + /// 获取所有对象信息。 + /// + /// 所有对象信息。 + public override ObjectInfo[] GetAllObjectInfos() + { + List results = new List(); + foreach (KeyValuePair>> objectRanges in m_Objects) + { + foreach (Object internalObject in objectRanges.Value) + { + results.Add(new ObjectInfo(internalObject.Name, internalObject.Locked, internalObject.CustomCanReleaseFlag, internalObject.Priority, internalObject.LastUseTime, internalObject.SpawnCount)); + } + } + + return results.ToArray(); + } + + internal override void Update(float elapseSeconds, float realElapseSeconds) + { + m_AutoReleaseTime += realElapseSeconds; + if (m_AutoReleaseTime < m_AutoReleaseInterval) + { + return; + } + + Release(); + } + + internal override void Shutdown() + { + foreach (KeyValuePair> objectInMap in m_ObjectMap) + { + objectInMap.Value.Release(true); + MemoryPool.Release(objectInMap.Value); + } + + m_Objects.Clear(); + m_ObjectMap.Clear(); + m_CachedCanReleaseObjects.Clear(); + m_CachedToReleaseObjects.Clear(); + } + + private Object GetObject(object target) + { + if (target == null) + { + throw new GameFrameworkException("Target is invalid."); + } + + Object internalObject = null; + if (m_ObjectMap.TryGetValue(target, out internalObject)) + { + return internalObject; + } + + return null; + } + + private void GetCanReleaseObjects(List results) + { + if (results == null) + { + throw new GameFrameworkException("Results is invalid."); + } + + results.Clear(); + foreach (KeyValuePair> objectInMap in m_ObjectMap) + { + Object internalObject = objectInMap.Value; + if (internalObject.IsInUse || internalObject.Locked || !internalObject.CustomCanReleaseFlag) + { + continue; + } + + results.Add(internalObject.Peek()); + } + } + + private List DefaultReleaseObjectFilterCallback(List candidateObjects, int toReleaseCount, DateTime expireTime) + { + m_CachedToReleaseObjects.Clear(); + + if (expireTime > DateTime.MinValue) + { + for (int i = candidateObjects.Count - 1; i >= 0; i--) + { + if (candidateObjects[i].LastUseTime <= expireTime) + { + m_CachedToReleaseObjects.Add(candidateObjects[i]); + candidateObjects.RemoveAt(i); + continue; + } + } + + toReleaseCount -= m_CachedToReleaseObjects.Count; + } + + for (int i = 0; toReleaseCount > 0 && i < candidateObjects.Count; i++) + { + for (int j = i + 1; j < candidateObjects.Count; j++) + { + if (candidateObjects[i].Priority > candidateObjects[j].Priority + || candidateObjects[i].Priority == candidateObjects[j].Priority && candidateObjects[i].LastUseTime > candidateObjects[j].LastUseTime) + { + T temp = candidateObjects[i]; + candidateObjects[i] = candidateObjects[j]; + candidateObjects[j] = temp; + } + } + + m_CachedToReleaseObjects.Add(candidateObjects[i]); + toReleaseCount--; + } + + return m_CachedToReleaseObjects; + } + } + } +} diff --git a/Runtime/ABase/ObjectPool/ObjectPoolManager.ObjectPool.cs.meta b/Runtime/ABase/ObjectPool/ObjectPoolManager.ObjectPool.cs.meta new file mode 100644 index 0000000..d070a88 --- /dev/null +++ b/Runtime/ABase/ObjectPool/ObjectPoolManager.ObjectPool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5389e9b49cd564ff88b88f78f7fc7663 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/ObjectPool/ObjectPoolModule.cs b/Runtime/ABase/ObjectPool/ObjectPoolModule.cs new file mode 100644 index 0000000..399e6bf --- /dev/null +++ b/Runtime/ABase/ObjectPool/ObjectPoolModule.cs @@ -0,0 +1,1289 @@ +using System; +using System.Collections.Generic; +using AlicizaX; + +namespace AlicizaX.ObjectPool +{ + /// + /// 对象池管理器。 + /// + [UnityEngine.Scripting.Preserve] + internal sealed partial class ObjectPoolModule : IObjectPoolModule + { + private const int DefaultCapacity = int.MaxValue; + private const float DefaultExpireTime = float.MaxValue; + private const int DefaultPriority = 0; + + private readonly Dictionary m_ObjectPools; + private readonly List m_CachedAllObjectPools; + private readonly Comparison m_ObjectPoolComparer; + + /// + /// 初始化对象池管理器的新实例。 + /// + public ObjectPoolModule() + { + m_ObjectPools = new Dictionary(); + m_CachedAllObjectPools = new List(); + m_ObjectPoolComparer = ObjectPoolComparer; + } + + /// + /// 获取游戏框架模块优先级。 + /// + /// 优先级较高的模块会优先轮询,并且关闭操作会后进行。 + public int Priority => 1; + + /// + /// 获取对象池数量。 + /// + public int Count + { + get { return m_ObjectPools.Count; } + } + + /// + /// 对象池管理器轮询。 + /// + /// 逻辑流逝时间,以秒为单位。 + /// 真实流逝时间,以秒为单位。 + void IModuleUpdate.Update(float elapseSeconds, float realElapseSeconds) + { + foreach (KeyValuePair objectPool in m_ObjectPools) + { + objectPool.Value.Update(elapseSeconds, realElapseSeconds); + } + } + + /// + /// 关闭并清理对象池管理器。 + /// + void IModule.Dispose() + { + foreach (KeyValuePair objectPool in m_ObjectPools) + { + objectPool.Value.Shutdown(); + } + + m_ObjectPools.Clear(); + m_CachedAllObjectPools.Clear(); + } + + /// + /// 检查是否存在对象池。 + /// + /// 对象类型。 + /// 是否存在对象池。 + public bool HasObjectPool() where T : ObjectBase + { + return InternalHasObjectPool(new TypeNamePair(typeof(T))); + } + + /// + /// 检查是否存在对象池。 + /// + /// 对象类型。 + /// 是否存在对象池。 + public bool HasObjectPool(Type objectType) + { + if (objectType == null) + { + throw new GameFrameworkException("Object type is invalid."); + } + + if (!typeof(ObjectBase).IsAssignableFrom(objectType)) + { + throw new GameFrameworkException(Utility.Text.Format("Object type '{0}' is invalid.", objectType.FullName)); + } + + return InternalHasObjectPool(new TypeNamePair(objectType)); + } + + /// + /// 检查是否存在对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 是否存在对象池。 + public bool HasObjectPool(string name) where T : ObjectBase + { + return InternalHasObjectPool(new TypeNamePair(typeof(T), name)); + } + + /// + /// 检查是否存在对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 是否存在对象池。 + public bool HasObjectPool(Type objectType, string name) + { + if (objectType == null) + { + throw new GameFrameworkException("Object type is invalid."); + } + + if (!typeof(ObjectBase).IsAssignableFrom(objectType)) + { + throw new GameFrameworkException(Utility.Text.Format("Object type '{0}' is invalid.", objectType.FullName)); + } + + return InternalHasObjectPool(new TypeNamePair(objectType, name)); + } + + /// + /// 检查是否存在对象池。 + /// + /// 要检查的条件。 + /// 是否存在对象池。 + public bool HasObjectPool(Predicate condition) + { + if (condition == null) + { + throw new GameFrameworkException("Condition is invalid."); + } + + foreach (KeyValuePair objectPool in m_ObjectPools) + { + if (condition(objectPool.Value)) + { + return true; + } + } + + return false; + } + + /// + /// 获取对象池。 + /// + /// 对象类型。 + /// 要获取的对象池。 + public IObjectPool GetObjectPool() where T : ObjectBase + { + return (IObjectPool)InternalGetObjectPool(new TypeNamePair(typeof(T))); + } + + /// + /// 获取对象池。 + /// + /// 对象类型。 + /// 要获取的对象池。 + public ObjectPoolBase GetObjectPool(Type objectType) + { + if (objectType == null) + { + throw new GameFrameworkException("Object type is invalid."); + } + + if (!typeof(ObjectBase).IsAssignableFrom(objectType)) + { + throw new GameFrameworkException(Utility.Text.Format("Object type '{0}' is invalid.", objectType.FullName)); + } + + return InternalGetObjectPool(new TypeNamePair(objectType)); + } + + /// + /// 获取对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 要获取的对象池。 + public IObjectPool GetObjectPool(string name) where T : ObjectBase + { + return (IObjectPool)InternalGetObjectPool(new TypeNamePair(typeof(T), name)); + } + + /// + /// 获取对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 要获取的对象池。 + public ObjectPoolBase GetObjectPool(Type objectType, string name) + { + if (objectType == null) + { + throw new GameFrameworkException("Object type is invalid."); + } + + if (!typeof(ObjectBase).IsAssignableFrom(objectType)) + { + throw new GameFrameworkException(Utility.Text.Format("Object type '{0}' is invalid.", objectType.FullName)); + } + + return InternalGetObjectPool(new TypeNamePair(objectType, name)); + } + + /// + /// 获取对象池。 + /// + /// 要检查的条件。 + /// 要获取的对象池。 + public ObjectPoolBase GetObjectPool(Predicate condition) + { + if (condition == null) + { + throw new GameFrameworkException("Condition is invalid."); + } + + foreach (KeyValuePair objectPool in m_ObjectPools) + { + if (condition(objectPool.Value)) + { + return objectPool.Value; + } + } + + return null; + } + + /// + /// 获取对象池。 + /// + /// 要检查的条件。 + /// 要获取的对象池。 + public ObjectPoolBase[] GetObjectPools(Predicate condition) + { + if (condition == null) + { + throw new GameFrameworkException("Condition is invalid."); + } + + List results = new List(); + foreach (KeyValuePair objectPool in m_ObjectPools) + { + if (condition(objectPool.Value)) + { + results.Add(objectPool.Value); + } + } + + return results.ToArray(); + } + + /// + /// 获取对象池。 + /// + /// 要检查的条件。 + /// 要获取的对象池。 + public void GetObjectPools(Predicate condition, List results) + { + if (condition == null) + { + throw new GameFrameworkException("Condition is invalid."); + } + + if (results == null) + { + throw new GameFrameworkException("Results is invalid."); + } + + results.Clear(); + foreach (KeyValuePair objectPool in m_ObjectPools) + { + if (condition(objectPool.Value)) + { + results.Add(objectPool.Value); + } + } + } + + /// + /// 获取所有对象池。 + /// + /// 所有对象池。 + public ObjectPoolBase[] GetAllObjectPools() + { + return GetAllObjectPools(false); + } + + /// + /// 获取所有对象池。 + /// + /// 所有对象池。 + public void GetAllObjectPools(List results) + { + GetAllObjectPools(false, results); + } + + /// + /// 获取所有对象池。 + /// + /// 是否根据对象池的优先级排序。 + /// 所有对象池。 + public ObjectPoolBase[] GetAllObjectPools(bool sort) + { + if (sort) + { + List results = new List(); + foreach (KeyValuePair objectPool in m_ObjectPools) + { + results.Add(objectPool.Value); + } + + results.Sort(m_ObjectPoolComparer); + return results.ToArray(); + } + else + { + int index = 0; + ObjectPoolBase[] results = new ObjectPoolBase[m_ObjectPools.Count]; + foreach (KeyValuePair objectPool in m_ObjectPools) + { + results[index++] = objectPool.Value; + } + + return results; + } + } + + /// + /// 获取所有对象池。 + /// + /// 是否根据对象池的优先级排序。 + /// 所有对象池。 + public void GetAllObjectPools(bool sort, List results) + { + if (results == null) + { + throw new GameFrameworkException("Results is invalid."); + } + + results.Clear(); + foreach (KeyValuePair objectPool in m_ObjectPools) + { + results.Add(objectPool.Value); + } + + if (sort) + { + results.Sort(m_ObjectPoolComparer); + } + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 要创建的允许单次获取的对象池。 + public IObjectPool CreateSingleSpawnObjectPool() where T : ObjectBase + { + return InternalCreateObjectPool(string.Empty, false, DefaultExpireTime, DefaultCapacity, DefaultExpireTime, DefaultPriority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 要创建的允许单次获取的对象池。 + public ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType) + { + return InternalCreateObjectPool(objectType, string.Empty, false, DefaultExpireTime, DefaultCapacity, DefaultExpireTime, DefaultPriority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 要创建的允许单次获取的对象池。 + public IObjectPool CreateSingleSpawnObjectPool(string name) where T : ObjectBase + { + return InternalCreateObjectPool(name, false, DefaultExpireTime, DefaultCapacity, DefaultExpireTime, DefaultPriority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 要创建的允许单次获取的对象池。 + public ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, string name) + { + return InternalCreateObjectPool(objectType, name, false, DefaultExpireTime, DefaultCapacity, DefaultExpireTime, DefaultPriority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 要创建的允许单次获取的对象池。 + public IObjectPool CreateSingleSpawnObjectPool(int capacity) where T : ObjectBase + { + return InternalCreateObjectPool(string.Empty, false, DefaultExpireTime, capacity, DefaultExpireTime, DefaultPriority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 要创建的允许单次获取的对象池。 + public ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, int capacity) + { + return InternalCreateObjectPool(objectType, string.Empty, false, DefaultExpireTime, capacity, DefaultExpireTime, DefaultPriority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池对象过期秒数。 + /// 要创建的允许单次获取的对象池。 + public IObjectPool CreateSingleSpawnObjectPool(float expireTime) where T : ObjectBase + { + return InternalCreateObjectPool(string.Empty, false, expireTime, DefaultCapacity, expireTime, DefaultPriority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池对象过期秒数。 + /// 要创建的允许单次获取的对象池。 + public ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, float expireTime) + { + return InternalCreateObjectPool(objectType, string.Empty, false, expireTime, DefaultCapacity, expireTime, DefaultPriority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 要创建的允许单次获取的对象池。 + public IObjectPool CreateSingleSpawnObjectPool(string name, int capacity) where T : ObjectBase + { + return InternalCreateObjectPool(name, false, DefaultExpireTime, capacity, DefaultExpireTime, DefaultPriority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 要创建的允许单次获取的对象池。 + public ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, string name, int capacity) + { + return InternalCreateObjectPool(objectType, name, false, DefaultExpireTime, capacity, DefaultExpireTime, DefaultPriority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池对象过期秒数。 + /// 要创建的允许单次获取的对象池。 + public IObjectPool CreateSingleSpawnObjectPool(string name, float expireTime) where T : ObjectBase + { + return InternalCreateObjectPool(name, false, expireTime, DefaultCapacity, expireTime, DefaultPriority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池对象过期秒数。 + /// 要创建的允许单次获取的对象池。 + public ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, string name, float expireTime) + { + return InternalCreateObjectPool(objectType, name, false, expireTime, DefaultCapacity, expireTime, DefaultPriority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 要创建的允许单次获取的对象池。 + public IObjectPool CreateSingleSpawnObjectPool(int capacity, float expireTime) where T : ObjectBase + { + return InternalCreateObjectPool(string.Empty, false, expireTime, capacity, expireTime, DefaultPriority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 要创建的允许单次获取的对象池。 + public ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, int capacity, float expireTime) + { + return InternalCreateObjectPool(objectType, string.Empty, false, expireTime, capacity, expireTime, DefaultPriority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + public IObjectPool CreateSingleSpawnObjectPool(int capacity, int priority) where T : ObjectBase + { + return InternalCreateObjectPool(string.Empty, false, DefaultExpireTime, capacity, DefaultExpireTime, priority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + public ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, int capacity, int priority) + { + return InternalCreateObjectPool(objectType, string.Empty, false, DefaultExpireTime, capacity, DefaultExpireTime, priority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + public IObjectPool CreateSingleSpawnObjectPool(float expireTime, int priority) where T : ObjectBase + { + return InternalCreateObjectPool(string.Empty, false, expireTime, DefaultCapacity, expireTime, priority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + public ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, float expireTime, int priority) + { + return InternalCreateObjectPool(objectType, string.Empty, false, expireTime, DefaultCapacity, expireTime, priority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 要创建的允许单次获取的对象池。 + public IObjectPool CreateSingleSpawnObjectPool(string name, int capacity, float expireTime) where T : ObjectBase + { + return InternalCreateObjectPool(name, false, expireTime, capacity, expireTime, DefaultPriority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 要创建的允许单次获取的对象池。 + public ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, string name, int capacity, float expireTime) + { + return InternalCreateObjectPool(objectType, name, false, expireTime, capacity, expireTime, DefaultPriority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + public IObjectPool CreateSingleSpawnObjectPool(string name, int capacity, int priority) where T : ObjectBase + { + return InternalCreateObjectPool(name, false, DefaultExpireTime, capacity, DefaultExpireTime, priority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + public ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, string name, int capacity, int priority) + { + return InternalCreateObjectPool(objectType, name, false, DefaultExpireTime, capacity, DefaultExpireTime, priority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + public IObjectPool CreateSingleSpawnObjectPool(string name, float expireTime, int priority) where T : ObjectBase + { + return InternalCreateObjectPool(name, false, expireTime, DefaultCapacity, expireTime, priority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + public ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, string name, float expireTime, int priority) + { + return InternalCreateObjectPool(objectType, name, false, expireTime, DefaultCapacity, expireTime, priority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + public IObjectPool CreateSingleSpawnObjectPool(int capacity, float expireTime, int priority) where T : ObjectBase + { + return InternalCreateObjectPool(string.Empty, false, expireTime, capacity, expireTime, priority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + public ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, int capacity, float expireTime, int priority) + { + return InternalCreateObjectPool(objectType, string.Empty, false, expireTime, capacity, expireTime, priority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + public IObjectPool CreateSingleSpawnObjectPool(string name, int capacity, float expireTime, int priority) where T : ObjectBase + { + return InternalCreateObjectPool(name, false, expireTime, capacity, expireTime, priority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + public ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, string name, int capacity, float expireTime, int priority) + { + return InternalCreateObjectPool(objectType, name, false, expireTime, capacity, expireTime, priority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池自动释放可释放对象的间隔秒数。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + public IObjectPool CreateSingleSpawnObjectPool(string name, float autoReleaseInterval, int capacity, float expireTime, int priority) where T : ObjectBase + { + return InternalCreateObjectPool(name, false, autoReleaseInterval, capacity, expireTime, priority); + } + + /// + /// 创建允许单次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池自动释放可释放对象的间隔秒数。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许单次获取的对象池。 + public ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, string name, float autoReleaseInterval, int capacity, float expireTime, int priority) + { + return InternalCreateObjectPool(objectType, name, false, autoReleaseInterval, capacity, expireTime, priority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 要创建的允许多次获取的对象池。 + public IObjectPool CreateMultiSpawnObjectPool() where T : ObjectBase + { + return InternalCreateObjectPool(string.Empty, true, DefaultExpireTime, DefaultCapacity, DefaultExpireTime, DefaultPriority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 要创建的允许多次获取的对象池。 + public ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType) + { + return InternalCreateObjectPool(objectType, string.Empty, true, DefaultExpireTime, DefaultCapacity, DefaultExpireTime, DefaultPriority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 要创建的允许多次获取的对象池。 + public IObjectPool CreateMultiSpawnObjectPool(string name) where T : ObjectBase + { + return InternalCreateObjectPool(name, true, DefaultExpireTime, DefaultCapacity, DefaultExpireTime, DefaultPriority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 要创建的允许多次获取的对象池。 + public ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, string name) + { + return InternalCreateObjectPool(objectType, name, true, DefaultExpireTime, DefaultCapacity, DefaultExpireTime, DefaultPriority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 要创建的允许多次获取的对象池。 + public IObjectPool CreateMultiSpawnObjectPool(int capacity) where T : ObjectBase + { + return InternalCreateObjectPool(string.Empty, true, DefaultExpireTime, capacity, DefaultExpireTime, DefaultPriority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 要创建的允许多次获取的对象池。 + public ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, int capacity) + { + return InternalCreateObjectPool(objectType, string.Empty, true, DefaultExpireTime, capacity, DefaultExpireTime, DefaultPriority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池对象过期秒数。 + /// 要创建的允许多次获取的对象池。 + public IObjectPool CreateMultiSpawnObjectPool(float expireTime) where T : ObjectBase + { + return InternalCreateObjectPool(string.Empty, true, expireTime, DefaultCapacity, expireTime, DefaultPriority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池对象过期秒数。 + /// 要创建的允许多次获取的对象池。 + public ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, float expireTime) + { + return InternalCreateObjectPool(objectType, string.Empty, true, expireTime, DefaultCapacity, expireTime, DefaultPriority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 要创建的允许多次获取的对象池。 + public IObjectPool CreateMultiSpawnObjectPool(string name, int capacity) where T : ObjectBase + { + return InternalCreateObjectPool(name, true, DefaultExpireTime, capacity, DefaultExpireTime, DefaultPriority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 要创建的允许多次获取的对象池。 + public ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, string name, int capacity) + { + return InternalCreateObjectPool(objectType, name, true, DefaultExpireTime, capacity, DefaultExpireTime, DefaultPriority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池对象过期秒数。 + /// 要创建的允许多次获取的对象池。 + public IObjectPool CreateMultiSpawnObjectPool(string name, float expireTime) where T : ObjectBase + { + return InternalCreateObjectPool(name, true, expireTime, DefaultCapacity, expireTime, DefaultPriority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池对象过期秒数。 + /// 要创建的允许多次获取的对象池。 + public ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, string name, float expireTime) + { + return InternalCreateObjectPool(objectType, name, true, expireTime, DefaultCapacity, expireTime, DefaultPriority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 要创建的允许多次获取的对象池。 + public IObjectPool CreateMultiSpawnObjectPool(int capacity, float expireTime) where T : ObjectBase + { + return InternalCreateObjectPool(string.Empty, true, expireTime, capacity, expireTime, DefaultPriority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 要创建的允许多次获取的对象池。 + public ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, int capacity, float expireTime) + { + return InternalCreateObjectPool(objectType, string.Empty, true, expireTime, capacity, expireTime, DefaultPriority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + public IObjectPool CreateMultiSpawnObjectPool(int capacity, int priority) where T : ObjectBase + { + return InternalCreateObjectPool(string.Empty, true, DefaultExpireTime, capacity, DefaultExpireTime, priority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + public ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, int capacity, int priority) + { + return InternalCreateObjectPool(objectType, string.Empty, true, DefaultExpireTime, capacity, DefaultExpireTime, priority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + public IObjectPool CreateMultiSpawnObjectPool(float expireTime, int priority) where T : ObjectBase + { + return InternalCreateObjectPool(string.Empty, true, expireTime, DefaultCapacity, expireTime, priority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + public ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, float expireTime, int priority) + { + return InternalCreateObjectPool(objectType, string.Empty, true, expireTime, DefaultCapacity, expireTime, priority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 要创建的允许多次获取的对象池。 + public IObjectPool CreateMultiSpawnObjectPool(string name, int capacity, float expireTime) where T : ObjectBase + { + return InternalCreateObjectPool(name, true, expireTime, capacity, expireTime, DefaultPriority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 要创建的允许多次获取的对象池。 + public ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, string name, int capacity, float expireTime) + { + return InternalCreateObjectPool(objectType, name, true, expireTime, capacity, expireTime, DefaultPriority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + public IObjectPool CreateMultiSpawnObjectPool(string name, int capacity, int priority) where T : ObjectBase + { + return InternalCreateObjectPool(name, true, DefaultExpireTime, capacity, DefaultExpireTime, priority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + public ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, string name, int capacity, int priority) + { + return InternalCreateObjectPool(objectType, name, true, DefaultExpireTime, capacity, DefaultExpireTime, priority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + public IObjectPool CreateMultiSpawnObjectPool(string name, float expireTime, int priority) where T : ObjectBase + { + return InternalCreateObjectPool(name, true, expireTime, DefaultCapacity, expireTime, priority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + public ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, string name, float expireTime, int priority) + { + return InternalCreateObjectPool(objectType, name, true, expireTime, DefaultCapacity, expireTime, priority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + public IObjectPool CreateMultiSpawnObjectPool(int capacity, float expireTime, int priority) where T : ObjectBase + { + return InternalCreateObjectPool(string.Empty, true, expireTime, capacity, expireTime, priority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + public ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, int capacity, float expireTime, int priority) + { + return InternalCreateObjectPool(objectType, string.Empty, true, expireTime, capacity, expireTime, priority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + public IObjectPool CreateMultiSpawnObjectPool(string name, int capacity, float expireTime, int priority) where T : ObjectBase + { + return InternalCreateObjectPool(name, true, expireTime, capacity, expireTime, priority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + public ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, string name, int capacity, float expireTime, int priority) + { + return InternalCreateObjectPool(objectType, name, true, expireTime, capacity, expireTime, priority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池自动释放可释放对象的间隔秒数。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + public IObjectPool CreateMultiSpawnObjectPool(string name, float autoReleaseInterval, int capacity, float expireTime, int priority) where T : ObjectBase + { + return InternalCreateObjectPool(name, true, autoReleaseInterval, capacity, expireTime, priority); + } + + /// + /// 创建允许多次获取的对象池。 + /// + /// 对象类型。 + /// 对象池名称。 + /// 对象池自动释放可释放对象的间隔秒数。 + /// 对象池的容量。 + /// 对象池对象过期秒数。 + /// 对象池的优先级。 + /// 要创建的允许多次获取的对象池。 + public ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, string name, float autoReleaseInterval, int capacity, float expireTime, int priority) + { + return InternalCreateObjectPool(objectType, name, true, autoReleaseInterval, capacity, expireTime, priority); + } + + /// + /// 销毁对象池。 + /// + /// 对象类型。 + /// 是否销毁对象池成功。 + public bool DestroyObjectPool() where T : ObjectBase + { + return InternalDestroyObjectPool(new TypeNamePair(typeof(T))); + } + + /// + /// 销毁对象池。 + /// + /// 对象类型。 + /// 是否销毁对象池成功。 + public bool DestroyObjectPool(Type objectType) + { + if (objectType == null) + { + throw new GameFrameworkException("Object type is invalid."); + } + + if (!typeof(ObjectBase).IsAssignableFrom(objectType)) + { + throw new GameFrameworkException(Utility.Text.Format("Object type '{0}' is invalid.", objectType.FullName)); + } + + return InternalDestroyObjectPool(new TypeNamePair(objectType)); + } + + /// + /// 销毁对象池。 + /// + /// 对象类型。 + /// 要销毁的对象池名称。 + /// 是否销毁对象池成功。 + public bool DestroyObjectPool(string name) where T : ObjectBase + { + return InternalDestroyObjectPool(new TypeNamePair(typeof(T), name)); + } + + /// + /// 销毁对象池。 + /// + /// 对象类型。 + /// 要销毁的对象池名称。 + /// 是否销毁对象池成功。 + public bool DestroyObjectPool(Type objectType, string name) + { + if (objectType == null) + { + throw new GameFrameworkException("Object type is invalid."); + } + + if (!typeof(ObjectBase).IsAssignableFrom(objectType)) + { + throw new GameFrameworkException(Utility.Text.Format("Object type '{0}' is invalid.", objectType.FullName)); + } + + return InternalDestroyObjectPool(new TypeNamePair(objectType, name)); + } + + /// + /// 销毁对象池。 + /// + /// 对象类型。 + /// 要销毁的对象池。 + /// 是否销毁对象池成功。 + public bool DestroyObjectPool(IObjectPool objectPool) where T : ObjectBase + { + if (objectPool == null) + { + throw new GameFrameworkException("Object pool is invalid."); + } + + return InternalDestroyObjectPool(new TypeNamePair(typeof(T), objectPool.Name)); + } + + /// + /// 销毁对象池。 + /// + /// 要销毁的对象池。 + /// 是否销毁对象池成功。 + public bool DestroyObjectPool(ObjectPoolBase objectPool) + { + if (objectPool == null) + { + throw new GameFrameworkException("Object pool is invalid."); + } + + return InternalDestroyObjectPool(new TypeNamePair(objectPool.ObjectType, objectPool.Name)); + } + + /// + /// 释放对象池中的可释放对象。 + /// + public void Release() + { + GetAllObjectPools(true, m_CachedAllObjectPools); + foreach (ObjectPoolBase objectPool in m_CachedAllObjectPools) + { + objectPool.Release(); + } + } + + /// + /// 释放对象池中的所有未使用对象。 + /// + public void ReleaseAllUnused() + { + GetAllObjectPools(true, m_CachedAllObjectPools); + foreach (ObjectPoolBase objectPool in m_CachedAllObjectPools) + { + objectPool.ReleaseAllUnused(); + } + } + + private bool InternalHasObjectPool(TypeNamePair typeNamePair) + { + return m_ObjectPools.ContainsKey(typeNamePair); + } + + private ObjectPoolBase InternalGetObjectPool(TypeNamePair typeNamePair) + { + ObjectPoolBase objectPool = null; + if (m_ObjectPools.TryGetValue(typeNamePair, out objectPool)) + { + return objectPool; + } + + return null; + } + + private IObjectPool InternalCreateObjectPool(string name, bool allowMultiSpawn, float autoReleaseInterval, int capacity, float expireTime, int priority) where T : ObjectBase + { + TypeNamePair typeNamePair = new TypeNamePair(typeof(T), name); + if (HasObjectPool(name)) + { + throw new GameFrameworkException(Utility.Text.Format("Already exist object pool '{0}'.", typeNamePair)); + } + + ObjectPool objectPool = new ObjectPool(name, allowMultiSpawn, autoReleaseInterval, capacity, expireTime, priority); + m_ObjectPools.Add(typeNamePair, objectPool); + return objectPool; + } + + private ObjectPoolBase InternalCreateObjectPool(Type objectType, string name, bool allowMultiSpawn, float autoReleaseInterval, int capacity, float expireTime, int priority) + { + if (objectType == null) + { + throw new GameFrameworkException("Object type is invalid."); + } + + if (!typeof(ObjectBase).IsAssignableFrom(objectType)) + { + throw new GameFrameworkException(Utility.Text.Format("Object type '{0}' is invalid.", objectType.FullName)); + } + + TypeNamePair typeNamePair = new TypeNamePair(objectType, name); + if (HasObjectPool(objectType, name)) + { + throw new GameFrameworkException(Utility.Text.Format("Already exist object pool '{0}'.", typeNamePair)); + } + + Type objectPoolType = typeof(ObjectPool<>).MakeGenericType(objectType); + ObjectPoolBase objectPool = (ObjectPoolBase)Activator.CreateInstance(objectPoolType, name, allowMultiSpawn, autoReleaseInterval, capacity, expireTime, priority); + m_ObjectPools.Add(typeNamePair, objectPool); + return objectPool; + } + + private bool InternalDestroyObjectPool(TypeNamePair typeNamePair) + { + ObjectPoolBase objectPool = null; + if (m_ObjectPools.TryGetValue(typeNamePair, out objectPool)) + { + objectPool.Shutdown(); + return m_ObjectPools.Remove(typeNamePair); + } + + return false; + } + + private static int ObjectPoolComparer(ObjectPoolBase a, ObjectPoolBase b) + { + return a.Priority.CompareTo(b.Priority); + } + } +} diff --git a/Runtime/ABase/ObjectPool/ObjectPoolModule.cs.meta b/Runtime/ABase/ObjectPool/ObjectPoolModule.cs.meta new file mode 100644 index 0000000..39ebe6c --- /dev/null +++ b/Runtime/ABase/ObjectPool/ObjectPoolModule.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e9c7dea0fcf0d4d2fba4dbac749c19d3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/ObjectPool/ReleaseObjectFilterCallback.cs b/Runtime/ABase/ObjectPool/ReleaseObjectFilterCallback.cs new file mode 100644 index 0000000..8a7f7ff --- /dev/null +++ b/Runtime/ABase/ObjectPool/ReleaseObjectFilterCallback.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace AlicizaX.ObjectPool +{ + /// + /// 释放对象筛选函数。 + /// + /// 对象类型。 + /// 要筛选的对象集合。 + /// 需要释放的对象数量。 + /// 对象过期参考时间。 + /// 经筛选需要释放的对象集合。 + public delegate List ReleaseObjectFilterCallback(List candidateObjects, int toReleaseCount, DateTime expireTime) where T : ObjectBase; +} diff --git a/Runtime/ABase/ObjectPool/ReleaseObjectFilterCallback.cs.meta b/Runtime/ABase/ObjectPool/ReleaseObjectFilterCallback.cs.meta new file mode 100644 index 0000000..06edf9e --- /dev/null +++ b/Runtime/ABase/ObjectPool/ReleaseObjectFilterCallback.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7109de176e3874b1d863ca0d2cdba894 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Property.meta b/Runtime/ABase/Property.meta new file mode 100644 index 0000000..6f0bca6 --- /dev/null +++ b/Runtime/ABase/Property.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d73cc40a000b40e4949fa532373115b5 +timeCreated: 1736324891 \ No newline at end of file diff --git a/Runtime/ABase/Property/BindableProperty.cs b/Runtime/ABase/Property/BindableProperty.cs new file mode 100644 index 0000000..1dcb127 --- /dev/null +++ b/Runtime/ABase/Property/BindableProperty.cs @@ -0,0 +1,83 @@ +using System; + +namespace AlicizaX +{ + public sealed class BindableProperty + { + private T _value; + private Action _onValueChanged; + + /// + /// 值 + /// + public T Value + { + get { return _value; } + set + { + if (!Equals(_value, value)) + { + _value = value; + _onValueChanged?.Invoke(_value); + } + } + } + + private BindableProperty() + { + _onValueChanged = null; + } + + /// + /// 默认构造函数 + /// + /// 默认值 + public BindableProperty(T defaultValue = default) : this() + { + _value = defaultValue; + } + + + /// + /// 注册值变化事件 + /// + /// + /// + public BindableProperty Add(Action callback) + { + GameFrameworkGuard.NotNull(callback, nameof(callback)); + _onValueChanged += callback; + return this; + } + + /// + /// 注册事件 + /// + /// + /// + public BindableProperty RegisterWithInitValue(Action callback) + { + GameFrameworkGuard.NotNull(callback, nameof(callback)); + callback?.Invoke(_value); + return Add(callback); + } + + /// + /// 移除事件 + /// + /// 事件 + public void Remove(Action callback) + { + GameFrameworkGuard.NotNull(callback, nameof(callback)); + _onValueChanged -= callback; + } + + /// + /// 清除事件 + /// + public void Clear() + { + _onValueChanged = null; + } + } +} diff --git a/Runtime/ABase/Property/BindableProperty.cs.meta b/Runtime/ABase/Property/BindableProperty.cs.meta new file mode 100644 index 0000000..bca08d6 --- /dev/null +++ b/Runtime/ABase/Property/BindableProperty.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 89edab77b55f4b29bce7d110705a586a +timeCreated: 1730705832 \ No newline at end of file diff --git a/Runtime/ABase/Structs.meta b/Runtime/ABase/Structs.meta new file mode 100644 index 0000000..27b03a3 --- /dev/null +++ b/Runtime/ABase/Structs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9f7a385073b440a187f621e02a05054e +timeCreated: 1758265822 \ No newline at end of file diff --git a/Runtime/ABase/Structs/Axis.cs b/Runtime/ABase/Structs/Axis.cs new file mode 100644 index 0000000..b426e60 --- /dev/null +++ b/Runtime/ABase/Structs/Axis.cs @@ -0,0 +1,18 @@ +namespace AlicizaX +{ + public enum Axis + { + /// Positive X Axis (1, 0, 0) + X, + /// Negative X Axis (-1, 0, 0) + X_Negative, + /// Positive Y Axis (0, 1, 0) + Y, + /// Negative Y Axis (0, -1, 0) + Y_Negative, + /// Positive Z Axis (0, 0, 1) + Z, + /// Negative Z Axis (0, 0, -1) + Z_Negative + } +} diff --git a/Runtime/ABase/Structs/Axis.cs.meta b/Runtime/ABase/Structs/Axis.cs.meta new file mode 100644 index 0000000..c79cece --- /dev/null +++ b/Runtime/ABase/Structs/Axis.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0ce7f24ac6bf9eb47852ad00497b036e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Structs/GenericReflectionField.cs b/Runtime/ABase/Structs/GenericReflectionField.cs new file mode 100644 index 0000000..2a3f4cd --- /dev/null +++ b/Runtime/ABase/Structs/GenericReflectionField.cs @@ -0,0 +1,92 @@ +using System; +using System.Reflection; +using UnityEngine; + +namespace AlicizaX +{ + /// + /// A generic reflection field allows you to get or set a generic (object) value for a property, field, or method. + /// + [Serializable] + public sealed class GenericReflectionField + { + public enum ReflectionType { Field, Property, Method }; + + public ReflectionType ReflectType; + public MonoBehaviour Instance; + public string ReflectName; + public bool ReflectDerived; + + public bool IsSet => Instance != null; + + private FieldInfo fieldInfo = null; + private FieldInfo FieldInfo + { + get + { + if (fieldInfo == null) + fieldInfo = Instance.GetType().GetField(ReflectName, BindingFlags.Public | BindingFlags.Instance); + + return fieldInfo; + } + } + + private PropertyInfo propertyInfo = null; + private PropertyInfo PropertyInfo + { + get + { + if (propertyInfo == null) + propertyInfo = Instance.GetType().GetProperty(ReflectName, BindingFlags.Public | BindingFlags.Instance); + + return propertyInfo; + } + } + + private MethodInfo methodInfo = null; + private MethodInfo MethodInfo + { + get + { + if (methodInfo == null) + methodInfo = Instance.GetType().GetMethod(ReflectName, BindingFlags.Public | BindingFlags.Instance); + + return methodInfo; + } + } + + public object Value + { + get => ReflectType switch + { + ReflectionType.Field => FieldInfo.GetValue(Instance), + ReflectionType.Property => PropertyInfo.GetValue(Instance), + ReflectionType.Method => MethodInfo.Invoke(Instance, new object[0]), + _ => throw new NullReferenceException() + }; + + set + { + try + { + if (ReflectType == ReflectionType.Field) + { + FieldInfo.SetValue(Instance, value); + } + else if (ReflectType == ReflectionType.Property) + { + PropertyInfo.SetValue(Instance, value); + } + else + { + MethodInfo.Invoke(Instance, new object[] { value }); + } + } + catch (Exception exception) + { + throw exception; + } + } + } + } +} diff --git a/Runtime/ABase/Structs/GenericReflectionField.cs.meta b/Runtime/ABase/Structs/GenericReflectionField.cs.meta new file mode 100644 index 0000000..5f28d68 --- /dev/null +++ b/Runtime/ABase/Structs/GenericReflectionField.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1f04b88956af3864487d98e1d262f914 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Structs/InputReference.cs b/Runtime/ABase/Structs/InputReference.cs new file mode 100644 index 0000000..92f649e --- /dev/null +++ b/Runtime/ABase/Structs/InputReference.cs @@ -0,0 +1,11 @@ +using System; + +namespace AlicizaX +{ + [Serializable] + public struct InputReference + { + public string ActionName; + public int BindingIndex; + } +} diff --git a/Runtime/ABase/Structs/InputReference.cs.meta b/Runtime/ABase/Structs/InputReference.cs.meta new file mode 100644 index 0000000..5ba4be8 --- /dev/null +++ b/Runtime/ABase/Structs/InputReference.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 44c9bb4f4146283479e938779f3ae878 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Structs/Layer.cs b/Runtime/ABase/Structs/Layer.cs new file mode 100644 index 0000000..f6ed31f --- /dev/null +++ b/Runtime/ABase/Structs/Layer.cs @@ -0,0 +1,28 @@ +using System; +using UnityEngine; + +namespace AlicizaX +{ + [Serializable] + public struct Layer + { + public int index; + + public static implicit operator int(Layer layer) + { + return layer.index; + } + + public static implicit operator Layer(int intVal) + { + Layer result = default; + result.index = intVal; + return result; + } + + public bool CompareLayer(GameObject obj) + { + return obj.layer == this; + } + } +} diff --git a/Runtime/ABase/Structs/Layer.cs.meta b/Runtime/ABase/Structs/Layer.cs.meta new file mode 100644 index 0000000..320234c --- /dev/null +++ b/Runtime/ABase/Structs/Layer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eab4448cb56de1345b19f4aebde2d2b5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Structs/MinMax.cs b/Runtime/ABase/Structs/MinMax.cs new file mode 100644 index 0000000..3bd75d3 --- /dev/null +++ b/Runtime/ABase/Structs/MinMax.cs @@ -0,0 +1,44 @@ +using System; +using UnityEngine; + +namespace AlicizaX +{ + [Serializable] + public struct MinMax + { + public float min; + public float max; + + public bool Flipped => max < min; + + public bool HasValue => min != 0 || max != 0; + + public float RealMin => Mathf.Min(min, max); + public float RealMax => Mathf.Max(max, min); + public Vector2 RealVector => this; + public Vector2 Vector => new(min, max); + + public MinMax(float min, float max) + { + this.min = min; + this.max = max; + } + + public static implicit operator Vector2(MinMax minMax) + { + return new Vector2(minMax.RealMin, minMax.RealMax); + } + + public static implicit operator MinMax(Vector2 vector) + { + MinMax result = default; + result.min = vector.x; + result.max = vector.y; + return result; + } + + public MinMax Flip() => new MinMax(max, min); + + public override string ToString() => $"({RealMin}, {RealMax})"; + } +} diff --git a/Runtime/ABase/Structs/MinMax.cs.meta b/Runtime/ABase/Structs/MinMax.cs.meta new file mode 100644 index 0000000..4fd1176 --- /dev/null +++ b/Runtime/ABase/Structs/MinMax.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3f6a5c9983635b043a5ebe83dc4d4042 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Structs/MinMaxInt.cs b/Runtime/ABase/Structs/MinMaxInt.cs new file mode 100644 index 0000000..cf6d4ef --- /dev/null +++ b/Runtime/ABase/Structs/MinMaxInt.cs @@ -0,0 +1,40 @@ +using System; +using UnityEngine; + +namespace AlicizaX +{ + [Serializable] + public struct MinMaxInt + { + public int min; + public int max; + + public bool Flipped => max < min; + + public int RealMin => Flipped ? max : min; + public int RealMax => Flipped ? min : max; + public Vector2Int RealVector => this; + public Vector2Int Vector => new Vector2Int(min, max); + + public MinMaxInt(int min, int max) + { + this.min = min; + this.max = max; + } + + public static implicit operator Vector2Int(MinMaxInt minMax) + { + return new Vector2Int(minMax.RealMin, minMax.RealMax); + } + + public static implicit operator MinMaxInt(Vector2Int vector) + { + MinMaxInt result = default; + result.min = vector.x; + result.max = vector.y; + return result; + } + + public MinMaxInt Flip() => new MinMaxInt(max, min); + } +} diff --git a/Runtime/ABase/Structs/MinMaxInt.cs.meta b/Runtime/ABase/Structs/MinMaxInt.cs.meta new file mode 100644 index 0000000..20e9eeb --- /dev/null +++ b/Runtime/ABase/Structs/MinMaxInt.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2cc5aae3ef78a5b4597b1222f09ca777 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Structs/ObjectReference.cs b/Runtime/ABase/Structs/ObjectReference.cs new file mode 100644 index 0000000..082f924 --- /dev/null +++ b/Runtime/ABase/Structs/ObjectReference.cs @@ -0,0 +1,12 @@ +using System; +using UnityEngine; + +namespace AlicizaX +{ + [Serializable] + public sealed class ObjectReference + { + public string GUID; + public GameObject Object; + } +} diff --git a/Runtime/ABase/Structs/ObjectReference.cs.meta b/Runtime/ABase/Structs/ObjectReference.cs.meta new file mode 100644 index 0000000..a6bc447 --- /dev/null +++ b/Runtime/ABase/Structs/ObjectReference.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 65d272994796f0a46bdff22287a8ac4e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Structs/ObservableValue.cs b/Runtime/ABase/Structs/ObservableValue.cs new file mode 100644 index 0000000..0e67d8f --- /dev/null +++ b/Runtime/ABase/Structs/ObservableValue.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; + +namespace AlicizaX +{ + public class ObservableValue + { + private T _value; + private bool _isChanged; + + public T Value + { + get => _value; + set + { + if (!EqualityComparer.Default.Equals(_value, value)) + { + _value = value; + _isChanged = true; + } + } + } + + public T SilentValue + { + get => _value; + set => _value = value; + } + + public bool IsChanged => _isChanged; + + public ObservableValue(T initialValue) + { + _value = initialValue; + _isChanged = false; + } + + public ObservableValue() + { + _value = default; + _isChanged = false; + } + + public void ResetFlag() + { + _isChanged = false; + } + + public override string ToString() => $"[{_isChanged}] {_value}"; + } +} diff --git a/Runtime/ABase/Structs/ObservableValue.cs.meta b/Runtime/ABase/Structs/ObservableValue.cs.meta new file mode 100644 index 0000000..4d6e5ec --- /dev/null +++ b/Runtime/ABase/Structs/ObservableValue.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 33812ab22fc0ec24c8b1a820d5d295b3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Structs/Percentage.cs b/Runtime/ABase/Structs/Percentage.cs new file mode 100644 index 0000000..b15c2fa --- /dev/null +++ b/Runtime/ABase/Structs/Percentage.cs @@ -0,0 +1,27 @@ +namespace AlicizaX +{ + [System.Serializable] + public struct Percentage + { + public ushort Value; + + public Percentage(ushort value) + { + Value = value; + } + + public float Ratio() => (float)Value / 100; + + public float From(float value) => Ratio() * value; + + public static implicit operator ushort(Percentage percentage) + { + return percentage.Value; + } + + public static implicit operator Percentage(ushort value) + { + return new Percentage(value); + } + } +} diff --git a/Runtime/ABase/Structs/Percentage.cs.meta b/Runtime/ABase/Structs/Percentage.cs.meta new file mode 100644 index 0000000..36089fb --- /dev/null +++ b/Runtime/ABase/Structs/Percentage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7e55417c4d6d6ae49bcc07e45c45874b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Structs/ReflectionField.cs b/Runtime/ABase/Structs/ReflectionField.cs new file mode 100644 index 0000000..7ead72e --- /dev/null +++ b/Runtime/ABase/Structs/ReflectionField.cs @@ -0,0 +1,101 @@ +using System; +using System.Reflection; +using UnityEngine; + +namespace AlicizaX +{ + /// + /// Reflection field allows you to get or set a bool value for a property, field, or method. + /// + [Serializable] + public sealed class ReflectionField + { + public enum ReflectionType { Field, Property, Method }; + + public ReflectionType ReflectType; + public MonoBehaviour Instance; + public string ReflectName; + public bool ReflectDerived; + + public bool IsSet => Instance != null; + + private FieldInfo fieldInfo = null; + private FieldInfo FieldInfo + { + get + { + if (fieldInfo == null) + fieldInfo = Instance.GetType().GetField(ReflectName, BindingFlags.Public | BindingFlags.Instance); + + return fieldInfo; + } + } + + private PropertyInfo propertyInfo = null; + private PropertyInfo PropertyInfo + { + get + { + if (propertyInfo == null) + propertyInfo = Instance.GetType().GetProperty(ReflectName, BindingFlags.Public | BindingFlags.Instance); + + return propertyInfo; + } + } + + private MethodInfo methodInfo = null; + private MethodInfo MethodInfo + { + get + { + if (methodInfo == null) + methodInfo = Instance.GetType().GetMethod(ReflectName, BindingFlags.Public | BindingFlags.Instance); + + return methodInfo; + } + } + + public bool Value + { + get + { + object value = ReflectType switch + { + ReflectionType.Field => FieldInfo.GetValue(Instance), + ReflectionType.Property => PropertyInfo.GetValue(Instance), + ReflectionType.Method => MethodInfo.Invoke(Instance, new object[0]), + _ => throw new NullReferenceException() + }; + + if (value is bool _value) + return _value; + + Debug.LogError($"Reflection Error: The specified reflection type '{ReflectName}' is not a bool type!"); + return false; + } + + set + { + try + { + if (ReflectType == ReflectionType.Field) + { + FieldInfo.SetValue(Instance, value); + } + else if (ReflectType == ReflectionType.Property) + { + PropertyInfo.SetValue(Instance, value); + } + else + { + MethodInfo.Invoke(Instance, new object[] { value }); + } + } + catch (Exception exception) + { + throw exception; + } + } + } + } +} diff --git a/Runtime/ABase/Structs/ReflectionField.cs.meta b/Runtime/ABase/Structs/ReflectionField.cs.meta new file mode 100644 index 0000000..24e7169 --- /dev/null +++ b/Runtime/ABase/Structs/ReflectionField.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 72b415c497128f644aa56e7b22522445 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Structs/RendererMaterial.cs b/Runtime/ABase/Structs/RendererMaterial.cs new file mode 100644 index 0000000..b583b5c --- /dev/null +++ b/Runtime/ABase/Structs/RendererMaterial.cs @@ -0,0 +1,26 @@ +using System; +using UnityEngine; + +namespace AlicizaX +{ + [Serializable] + public struct RendererMaterial + { + public Renderer meshRenderer; + public Material material; + public int materialIndex; + + public bool IsAssigned => meshRenderer != null && material != null; + + public Material ClonedMaterial + { + get => material = meshRenderer.materials[materialIndex]; + set + { + Material[] materials = meshRenderer.materials; + materials[materialIndex] = value; + meshRenderer.materials = materials; + } + } + } +} diff --git a/Runtime/ABase/Structs/RendererMaterial.cs.meta b/Runtime/ABase/Structs/RendererMaterial.cs.meta new file mode 100644 index 0000000..d430d48 --- /dev/null +++ b/Runtime/ABase/Structs/RendererMaterial.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1cc638090ef59504789bec943d76040f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Structs/SoundClip.cs b/Runtime/ABase/Structs/SoundClip.cs new file mode 100644 index 0000000..545bb94 --- /dev/null +++ b/Runtime/ABase/Structs/SoundClip.cs @@ -0,0 +1,18 @@ +using System; +using UnityEngine; + +namespace AlicizaX +{ + [Serializable] + public sealed class SoundClip + { + public AudioClip audioClip; + public float volume = 1f; + + public SoundClip(AudioClip audioClip, float volume = 1f) + { + this.audioClip = audioClip; + this.volume = volume; + } + } +} diff --git a/Runtime/ABase/Structs/SoundClip.cs.meta b/Runtime/ABase/Structs/SoundClip.cs.meta new file mode 100644 index 0000000..eadf839 --- /dev/null +++ b/Runtime/ABase/Structs/SoundClip.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1294437a3f121f74bb56ca39c90d2819 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Structs/StorableCollection.cs b/Runtime/ABase/Structs/StorableCollection.cs new file mode 100644 index 0000000..0841999 --- /dev/null +++ b/Runtime/ABase/Structs/StorableCollection.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; + +namespace AlicizaX +{ + /// + /// A buffer that stores data that can be used for serializing data or do other operations. + ///
Implements where the key is and the value is .
+ ///
+ public class StorableCollection : Dictionary + { + public T GetT(string key) + { + if (TryGetValue(key, out var value)) + if (value is T valueT) + return valueT; + + throw new System.NullReferenceException($"Could not find item with key '{key}' or could not convert to type '{typeof(T).Name}'."); + } + + public bool TryGetValue(string key, out T value) + { + if (TryGetValue(key, out var valueO)) + { + if (valueO is T valueT) + { + value = valueT; + return true; + } + } + + value = default; + return false; + } + } +} diff --git a/Runtime/ABase/Structs/StorableCollection.cs.meta b/Runtime/ABase/Structs/StorableCollection.cs.meta new file mode 100644 index 0000000..f0f32b6 --- /dev/null +++ b/Runtime/ABase/Structs/StorableCollection.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6f8037a466030e74aa944b0b439d5f07 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Structs/Tag.cs b/Runtime/ABase/Structs/Tag.cs new file mode 100644 index 0000000..db5c7d5 --- /dev/null +++ b/Runtime/ABase/Structs/Tag.cs @@ -0,0 +1,28 @@ +using System; +using UnityEngine; + +namespace AlicizaX +{ + [Serializable] + public struct Tag + { + public string tag; + + public static implicit operator string(Tag tag) + { + return tag.tag; + } + + public static implicit operator Tag(string tag) + { + Tag result = default; + result.tag = tag; + return result; + } + + public bool CompareTag(GameObject obj) + { + return obj.CompareTag(this); + } + } +} diff --git a/Runtime/ABase/Structs/Tag.cs.meta b/Runtime/ABase/Structs/Tag.cs.meta new file mode 100644 index 0000000..a8ee851 --- /dev/null +++ b/Runtime/ABase/Structs/Tag.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 431d94b8a8d5f5f4fa780e79c149f880 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Structs/UniqueID.cs b/Runtime/ABase/Structs/UniqueID.cs new file mode 100644 index 0000000..80c1369 --- /dev/null +++ b/Runtime/ABase/Structs/UniqueID.cs @@ -0,0 +1,44 @@ +using System; + +namespace AlicizaX +{ + /// + /// Generates a Unique ID that can be used to identify scripts when saving/loading script state. + /// + [Serializable] + public sealed class UniqueID + { + public string Id; + + public UniqueID() + { + GenerateIfEmpty(); + } + + /// + /// Generate an ID only if it's missing. + /// + public void GenerateIfEmpty() + { + if (!string.IsNullOrEmpty(Id)) + return; + + Generate(); + } + + /// + /// Assign a new random ID and overwrite the previous. + /// + public void Generate() + { + Id = GameHelper.GetGuid(); + } + + public static implicit operator string(UniqueID uniqueID) + { + return uniqueID.Id; + } + + public override string ToString() => Id; + } +} diff --git a/Runtime/ABase/Structs/UniqueID.cs.meta b/Runtime/ABase/Structs/UniqueID.cs.meta new file mode 100644 index 0000000..5a6258b --- /dev/null +++ b/Runtime/ABase/Structs/UniqueID.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dab79b064f432d34a9382f36d581c1ae +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Utility.meta b/Runtime/ABase/Utility.meta new file mode 100644 index 0000000..dfb35b9 --- /dev/null +++ b/Runtime/ABase/Utility.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: eeca40a965b84a87967d8e31da6153d0 +timeCreated: 1736324891 \ No newline at end of file diff --git a/Runtime/ABase/Utility/Utility.Assembly.cs b/Runtime/ABase/Utility/Utility.Assembly.cs new file mode 100644 index 0000000..3a748a3 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Assembly.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; + +namespace AlicizaX +{ + public static partial class Utility + { + /// + /// 程序集相关的实用函数。 + /// + public static class Assembly + { + private static readonly System.Reflection.Assembly[] s_Assemblies = null; + private static readonly Dictionary s_CachedTypes = new Dictionary(StringComparer.Ordinal); + + static Assembly() + { + s_Assemblies = AppDomain.CurrentDomain.GetAssemblies(); + } + + /// + /// 获取已加载的程序集。 + /// + /// 已加载的程序集。 + public static System.Reflection.Assembly[] GetAssemblies() + { + return s_Assemblies; + } + + /// + /// 获取已加载的程序集中的所有类型。 + /// + /// 已加载的程序集中的所有类型。 + public static Type[] GetTypes() + { + List results = new List(); + foreach (System.Reflection.Assembly assembly in s_Assemblies) + { + results.AddRange(assembly.GetTypes()); + } + + return results.ToArray(); + } + + /// + /// 获取已加载的程序集中的所有类型。 + /// + /// 已加载的程序集中的所有类型。 + public static void GetTypes(List results) + { + if (results == null) + { + throw new GameFrameworkException("Results is invalid."); + } + + results.Clear(); + foreach (System.Reflection.Assembly assembly in s_Assemblies) + { + results.AddRange(assembly.GetTypes()); + } + } + + /// + /// 获取已加载的程序集中的指定类型。 + /// + /// 要获取的类型名。 + /// 已加载的程序集中的指定类型。 + public static Type GetType(string typeName) + { + if (string.IsNullOrEmpty(typeName)) + { + throw new GameFrameworkException("Type name is invalid."); + } + + Type type = null; + if (s_CachedTypes.TryGetValue(typeName, out type)) + { + return type; + } + + type = Type.GetType(typeName); + if (type != null) + { + s_CachedTypes.Add(typeName, type); + return type; + } + + foreach (System.Reflection.Assembly assembly in s_Assemblies) + { + type = Type.GetType(Text.Format("{0}, {1}", typeName, assembly.FullName)); + if (type != null) + { + s_CachedTypes.Add(typeName, type); + return type; + } + } + + return null; + } + + /// + /// 获取已加载的程序集中的指定类型的子类列表。 + /// + /// 指定类型 + /// + public static List GetRuntimeTypeNames(Type type) + { + var types = GetTypes(); + List results = new List(); + foreach (var t in types) + { + if (t.IsAbstract || !t.IsClass) + { + continue; + } + + if (t.IsSubclassOf(type) || t.IsImplWithInterface(type)) + { + results.Add(t.FullName); + } + } + + return results; + } + + public static List GetRuntimeTypes(Type typeBase) + { + var types = GetTypes(); + List results = new List(); + foreach (var t in types) + { + if (t.IsAbstract || !t.IsClass) + { + continue; + } + + if (t.IsSubclassOf(typeBase) || t.IsImplWithInterface(typeBase)) + { + results.Add(t); + } + } + + return results; + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Assembly.cs.meta b/Runtime/ABase/Utility/Utility.Assembly.cs.meta new file mode 100644 index 0000000..1b27bc9 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Assembly.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e2418995de0194c41b04c3c414f5d8cd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Utility/Utility.Asset.Path.cs b/Runtime/ABase/Utility/Utility.Asset.Path.cs new file mode 100644 index 0000000..cf6212c --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Asset.Path.cs @@ -0,0 +1,249 @@ +namespace AlicizaX +{ + public static partial class Utility + { + /// + /// AB实用函数集,主要是路径拼接 + /// + public static class Asset + { + /// + /// 路径 + /// + public static class Path + { + /// + /// 打包资源根路径 + /// + public const string BundlesPath = "Assets/Bundles"; + + /// + /// 打包资源文件夹名称 + /// + public const string BundlesDirectoryName = "Bundles"; + + /// + /// 打包资源文件夹Scene名称 + /// + public const string BundlesDirectorySceneName = "Scene"; + + /// + /// 打包资源文件夹Localization名称 + /// + public const string BundlesDirectoryLocalizationName = "Localization"; + + /// + /// 打包资源文件夹Config名称 + /// + public const string BundlesDirectoryConfigName = "Config"; + + /// + /// 打包资源文件夹AOTCode名称 + /// + public const string BundlesDirectoryAOTCodeName = "AOTCode"; + + /// + /// 打包资源文件夹Code名称 + /// + public const string BundlesDirectoryCodeName = "Code"; + + /// + /// 打包资源文件夹Sound名称 + /// + public const string BundlesDirectorySoundName = "Sound"; + + /// + /// 打包资源文件夹Prefab名称 + /// + public const string BundlesDirectoryPrefabName = "Prefabs"; + + /// + /// 打包资源文件夹Video名称 + /// + public const string BundlesDirectoryVideoName = "Video"; + + /// + /// 打包资源文件夹Image名称 + /// + public const string BundlesDirectoryImageName = "Image"; + + /// + /// 打包资源文件夹UI名称 + /// + public const string BundlesDirectoryUIName = "UI"; + + /// + /// 打包资源文件夹Sprite名称 + /// + public const string BundlesDirectorySpriteName = "Sprite"; + + /// + /// 打包资源文件夹Shader名称 + /// + public const string BundlesDirectoryShaderName = "Shader"; + + /// + /// 获取文件路径 + /// + /// 相对于Bundles的路径,不要以/开头 + /// 返回拼接好的路径 + public static string GetFilePath(string filePath) + { + return $"{BundlesPath}/{filePath}"; + } + + /// + /// 获取图片文件路径 + /// + /// 相对于Bundles/Image的路径,不要以/开头,需要携带扩展名 + /// 返回拼接好的路径 + public static string GetImagePath(string filePath) + { + return GetCategoryFilePath(BundlesDirectoryImageName, filePath); + } + + /// + /// 获取视频文件路径 + /// + /// 相对于Bundles/Video的路径,不要以/开头,需要携带扩展名 + /// 返回拼接好的路径 + public static string GetVideoPath(string filePath) + { + return GetCategoryFilePath(BundlesDirectoryVideoName, filePath); + } + + /// + /// 获取Sprite文件路径 + /// + /// 相对于Bundles/Sprite的路径,不要以/开头,需要携带扩展名 + /// 返回拼接好的路径 + public static string GetSpritePath(string filePath) + { + return GetCategoryFilePath(BundlesDirectorySpriteName, filePath); + } + + /// + /// 获取Sprite文件路径 + /// + /// 相对于Bundles/Prefabs的路径,不要以/开头,需要携带扩展名 + /// 返回拼接好的路径 + public static string GetPrefabPath(string filePath) + { + return GetCategoryFilePath(BundlesDirectoryPrefabName, filePath); + } + + /// + /// 获取根据类别文件夹名称和文件路径获得完整文件路径 + /// + /// 相对于Bundles的类别名称 + /// 相对于Bundles的路径,不要以/开头 + /// 返回拼接好的路径 + public static string GetCategoryFilePath(string category, string filePath) + { + return $"{BundlesPath}/{category}/{filePath}"; + } + + /// + /// 获取配置文件路径 + /// + /// 相对于Bundles/Config的路径,不要以/开头,需要携带扩展名 + /// 文件扩展名称 + /// 返回拼接好的路径 + public static string GetConfigPath(string fileName, string extension = ".bytes") + { + return GetCategoryFilePath(BundlesDirectoryConfigName, $"{fileName}{extension}"); + } + + /// + /// 获取AOT元数据代码文件路径 + /// + /// 相对于Bundles/AOTCode的路径,不要以/开头,需要携带扩展名 + /// 文件扩展名称 + /// 返回拼接好的路径 + public static string GetAOTCodePath(string fileName, string extension = ".bytes") + { + return GetCategoryFilePath(BundlesDirectoryAOTCodeName, $"{fileName}{extension}"); + } + + /// + /// 获取代码文件路径 + /// + /// 相对于Bundles/Code的路径,不要以/开头,需要携带扩展名 + /// 文件扩展名称 + /// 返回拼接好的路径 + public static string GetCodePath(string fileName, string extension = ".bytes") + { + return GetCategoryFilePath(BundlesDirectoryCodeName, $"{fileName}{extension}"); + } + + /// + /// 获取UI文件路径 + /// + /// UI包名 + /// 返回拼接好的路径 + public static string GetUIPackagePath(string uiPackageName) + { + return GetCategoryFilePath(BundlesDirectoryUIName, $"{uiPackageName}/{uiPackageName}"); + } + + /// + /// 获取UI文件路径 + /// + /// UI路径 + /// 返回拼接好的路径 + public static string GetUIPath(string uiPath) + { + return GetCategoryFilePath(BundlesDirectoryUIName, uiPath); + } + + /// + /// 获取声音文件路径 + /// + /// 路径包含名称 + /// 扩展名称,默认为.mp3 + /// 返回拼接好的路径 + public static string GetSoundPath(string pathName, string extension = ".mp3") + { + if (pathName.IndexOf('.') >= 0) + { + return GetCategoryFilePath(BundlesDirectorySoundName, pathName); + } + + return GetCategoryFilePath(BundlesDirectorySoundName, $"{pathName}{extension}"); + } + + /// + /// 获取场景文件路径 + /// + /// 路径包含名称 + /// 扩展名,默认为.unity + /// 返回拼接好的路径 + public static string GetScenePath(string pathName, string extension = ".unity") + { + if (pathName.IndexOf('.') >= 0) + { + return GetCategoryFilePath(BundlesDirectorySceneName, pathName); + } + + return GetCategoryFilePath(BundlesDirectorySceneName, $"{pathName}{extension}"); + } + + /// + /// 获取本地化文件路径 + /// + /// 路径包含名称 + /// 文件扩展名 + /// 返回拼接好的路径 + public static string GetLocalizationPath(string pathName, string extension = ".xml") + { + if (pathName.IndexOf('.') >= 0) + { + return GetCategoryFilePath(BundlesDirectoryLocalizationName, pathName); + } + + return GetCategoryFilePath(BundlesDirectoryLocalizationName, $"{pathName}{extension}"); + } + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Asset.Path.cs.meta b/Runtime/ABase/Utility/Utility.Asset.Path.cs.meta new file mode 100644 index 0000000..3a22be6 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Asset.Path.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 58ab045aa01445339811d8bee78b82cd +timeCreated: 1697686753 \ No newline at end of file diff --git a/Runtime/ABase/Utility/Utility.Compression.cs b/Runtime/ABase/Utility/Utility.Compression.cs new file mode 100644 index 0000000..50b3019 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Compression.cs @@ -0,0 +1,306 @@ +using System; +using System.IO; + +namespace AlicizaX +{ + public static partial class Utility + { + /// + /// 压缩解压缩相关的实用函数。 + /// + public static partial class Compression + { + /// + /// 压缩数据。 + /// + /// 要压缩的数据的二进制流。 + /// 压缩后的数据的二进制流。 + public static byte[] Compress(byte[] bytes) + { + if (bytes == null) + { + throw new GameFrameworkException("Bytes is invalid."); + } + + return Compress(bytes, 0, bytes.Length); + } + + /// + /// 压缩数据。 + /// + /// 要压缩的数据的二进制流。 + /// 压缩后的数据的二进制流。 + /// 是否压缩数据成功。 + public static bool Compress(byte[] bytes, Stream compressedStream) + { + if (bytes == null) + { + throw new GameFrameworkException("Bytes is invalid."); + } + + return Compress(bytes, 0, bytes.Length, compressedStream); + } + + /// + /// 压缩数据。 + /// + /// 要压缩的数据的二进制流。 + /// 要压缩的数据的二进制流的偏移。 + /// 要压缩的数据的二进制流的长度。 + /// 压缩后的数据的二进制流。 + public static byte[] Compress(byte[] bytes, int offset, int length) + { + using (MemoryStream compressedStream = new MemoryStream()) + { + if (Compress(bytes, offset, length, compressedStream)) + { + return compressedStream.ToArray(); + } + else + { + return null; + } + } + } + + /// + /// 压缩数据。 + /// + /// 要压缩的数据的二进制流。 + /// 要压缩的数据的二进制流的偏移。 + /// 要压缩的数据的二进制流的长度。 + /// 压缩后的数据的二进制流。 + /// 是否压缩数据成功。 + public static bool Compress(byte[] bytes, int offset, int length, Stream compressedStream) + { + if (bytes == null) + { + throw new GameFrameworkException("Bytes is invalid."); + } + + if (offset < 0 || length < 0 || offset + length > bytes.Length) + { + throw new GameFrameworkException("Offset or length is invalid."); + } + + if (compressedStream == null) + { + throw new GameFrameworkException("Compressed stream is invalid."); + } + + try + { + return CompressionUtility.Compress(bytes, offset, length, compressedStream); + } + catch (Exception exception) + { + if (exception is GameFrameworkException) + { + throw; + } + + throw new GameFrameworkException(Text.Format("Can not compress with exception '{0}'.", exception), exception); + } + } + + /// + /// 压缩数据。 + /// + /// 要压缩的数据的二进制流。 + /// 压缩后的数据的二进制流。 + public static byte[] Compress(Stream stream) + { + using (MemoryStream compressedStream = new MemoryStream()) + { + if (Compress(stream, compressedStream)) + { + return compressedStream.ToArray(); + } + else + { + return null; + } + } + } + + /// + /// 压缩数据。 + /// + /// 要压缩的数据的二进制流。 + /// 压缩后的数据的二进制流。 + /// 是否压缩数据成功。 + public static bool Compress(Stream stream, Stream compressedStream) + { + if (stream == null) + { + throw new GameFrameworkException("Stream is invalid."); + } + + if (compressedStream == null) + { + throw new GameFrameworkException("Compressed stream is invalid."); + } + + try + { + return CompressionUtility.Compress(stream, compressedStream); + } + catch (Exception exception) + { + if (exception is GameFrameworkException) + { + throw; + } + + throw new GameFrameworkException(Text.Format("Can not compress with exception '{0}'.", exception), exception); + } + } + + /// + /// 解压缩数据。 + /// + /// 要解压缩的数据的二进制流。 + /// 解压缩后的数据的二进制流。 + public static byte[] Decompress(byte[] bytes) + { + if (bytes == null) + { + throw new GameFrameworkException("Bytes is invalid."); + } + + return Decompress(bytes, 0, bytes.Length); + } + + /// + /// 解压缩数据。 + /// + /// 要解压缩的数据的二进制流。 + /// 解压缩后的数据的二进制流。 + /// 是否解压缩数据成功。 + public static bool Decompress(byte[] bytes, Stream decompressedStream) + { + if (bytes == null) + { + throw new GameFrameworkException("Bytes is invalid."); + } + + return Decompress(bytes, 0, bytes.Length, decompressedStream); + } + + /// + /// 解压缩数据。 + /// + /// 要解压缩的数据的二进制流。 + /// 要解压缩的数据的二进制流的偏移。 + /// 要解压缩的数据的二进制流的长度。 + /// 解压缩后的数据的二进制流。 + public static byte[] Decompress(byte[] bytes, int offset, int length) + { + using (MemoryStream decompressedStream = new MemoryStream()) + { + if (Decompress(bytes, offset, length, decompressedStream)) + { + return decompressedStream.ToArray(); + } + else + { + return null; + } + } + } + + /// + /// 解压缩数据。 + /// + /// 要解压缩的数据的二进制流。 + /// 要解压缩的数据的二进制流的偏移。 + /// 要解压缩的数据的二进制流的长度。 + /// 解压缩后的数据的二进制流。 + /// 是否解压缩数据成功。 + public static bool Decompress(byte[] bytes, int offset, int length, Stream decompressedStream) + { + if (bytes == null) + { + throw new GameFrameworkException("Bytes is invalid."); + } + + if (offset < 0 || length < 0 || offset + length > bytes.Length) + { + throw new GameFrameworkException("Offset or length is invalid."); + } + + if (decompressedStream == null) + { + throw new GameFrameworkException("Decompressed stream is invalid."); + } + + try + { + return CompressionUtility.Decompress(bytes, offset, length, decompressedStream); + } + catch (Exception exception) + { + if (exception is GameFrameworkException) + { + throw; + } + + throw new GameFrameworkException(Text.Format("Can not decompress with exception '{0}'.", exception), exception); + } + } + + /// + /// 解压缩数据。 + /// + /// 要解压缩的数据的二进制流。 + /// 是否解压缩数据成功。 + public static byte[] Decompress(Stream stream) + { + using (MemoryStream decompressedStream = new MemoryStream()) + { + if (Decompress(stream, decompressedStream)) + { + return decompressedStream.ToArray(); + } + else + { + return null; + } + } + } + + /// + /// 解压缩数据。 + /// + /// 要解压缩的数据的二进制流。 + /// 解压缩后的数据的二进制流。 + /// 是否解压缩数据成功。 + public static bool Decompress(Stream stream, Stream decompressedStream) + { + if (stream == null) + { + throw new GameFrameworkException("Stream is invalid."); + } + + if (decompressedStream == null) + { + throw new GameFrameworkException("Decompressed stream is invalid."); + } + + try + { + return CompressionUtility.Decompress(stream, decompressedStream); + } + catch (Exception exception) + { + if (exception is GameFrameworkException) + { + throw; + } + + throw new GameFrameworkException(Text.Format("Can not decompress with exception '{0}'.", exception), exception); + } + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Compression.cs.meta b/Runtime/ABase/Utility/Utility.Compression.cs.meta new file mode 100644 index 0000000..912c60e --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Compression.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3fb8acaf643af46aa98e9fbc1d2840ed +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Utility/Utility.Const.FileNameSuffix.cs b/Runtime/ABase/Utility/Utility.Const.FileNameSuffix.cs new file mode 100644 index 0000000..7bb2499 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Const.FileNameSuffix.cs @@ -0,0 +1,92 @@ +namespace AlicizaX +{ + public static partial class Utility + { + /// + /// 常量相关的实用函数。 + /// + public static partial class Const + { + /// + /// 文件名后缀 + /// + public static partial class FileNameSuffix + { + /// + /// Json 文件 + /// + public const string Json = ".json"; + + /// + /// Wav 文件 + /// + public const string Wav = ".wav"; + + /// + /// Mp3 文件 + /// + public const string Mp3 = ".mp3"; + + /// + /// Xml 文件 + /// + public const string Xml = ".xml"; + + /// + /// 文本文件 + /// + public const string Txt = ".txt"; + + /// + /// 日志文件 + /// + public const string Log = ".log"; + + /// + /// CSharp 文件 + /// + public const string CSharp = ".cs"; + + /// + /// Zip 压缩文件 + /// + public const string Zip = ".zip"; + + /// + /// 图片PNG + /// + public const string PNG = ".png"; + + /// + /// 图片JPG + /// + public const string JPG = ".jpg"; + + /// + /// 二进制文件 + /// + public const string Binary = ".bytes"; + + /// + /// 配置文件 + /// + public const string Config = ".config"; + + /// + /// DLL 文件 + /// + public const string DLL = ".dll"; + + /// + /// PDB 调试文件 + /// + public const string PDB = ".pdb"; + + /// + /// Asset 文件 + /// + public const string Asset = ".asset"; + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Const.FileNameSuffix.cs.meta b/Runtime/ABase/Utility/Utility.Const.FileNameSuffix.cs.meta new file mode 100644 index 0000000..1557ad5 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Const.FileNameSuffix.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c9817341914b4e6c850cbb2f68f0e76b +timeCreated: 1694616828 \ No newline at end of file diff --git a/Runtime/ABase/Utility/Utility.Converter.cs b/Runtime/ABase/Utility/Utility.Converter.cs new file mode 100644 index 0000000..0e67db2 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Converter.cs @@ -0,0 +1,841 @@ +using System; +using System.Text; + +namespace AlicizaX +{ + public static partial class Utility + { + /// + /// 类型转换相关的实用函数。 + /// + public static class Converter + { + private const float InchesToCentimeters = 2.54f; // 1 inch = 2.54 cm + private const float CentimetersToInches = 1f / InchesToCentimeters; // 1 cm = 0.3937 inches + + /// + /// 获取数据在此计算机结构中存储时的字节顺序。 + /// + public static bool IsLittleEndian + { + get + { + return BitConverter.IsLittleEndian; + } + } + + /// + /// 获取或设置屏幕每英寸点数。 + /// + public static float ScreenDpi + { + get; + set; + } + + /// + /// 将像素转换为厘米。 + /// + /// 像素。 + /// 厘米。 + public static float GetCentimetersFromPixels(float pixels) + { + if (ScreenDpi <= 0) + { + throw new GameFrameworkException("You must set screen DPI first."); + } + + return InchesToCentimeters * pixels / ScreenDpi; + } + + /// + /// 将厘米转换为像素。 + /// + /// 厘米。 + /// 像素。 + public static float GetPixelsFromCentimeters(float centimeters) + { + if (ScreenDpi <= 0) + { + throw new GameFrameworkException("You must set screen DPI first."); + } + + return CentimetersToInches * centimeters * ScreenDpi; + } + + /// + /// 将像素转换为英寸。 + /// + /// 像素。 + /// 英寸。 + public static float GetInchesFromPixels(float pixels) + { + if (ScreenDpi <= 0) + { + throw new GameFrameworkException("You must set screen DPI first."); + } + + return pixels / ScreenDpi; + } + + /// + /// 将英寸转换为像素。 + /// + /// 英寸。 + /// 像素。 + public static float GetPixelsFromInches(float inches) + { + if (ScreenDpi <= 0) + { + throw new GameFrameworkException("You must set screen DPI first."); + } + + return inches * ScreenDpi; + } + + /// + /// 以字节数组的形式获取指定的布尔值。 + /// + /// 要转换的布尔值。 + /// 用于存放结果的字节数组。 + public static byte[] GetBytes(bool value) + { + byte[] buffer = new byte[1]; + GetBytes(value, buffer, 0); + return buffer; + } + + /// + /// 以字节数组的形式获取指定的布尔值。 + /// + /// 要转换的布尔值。 + /// 用于存放结果的字节数组。 + public static void GetBytes(bool value, byte[] buffer) + { + GetBytes(value, buffer, 0); + } + + /// + /// 以字节数组的形式获取指定的布尔值。 + /// + /// 要转换的布尔值。 + /// 用于存放结果的字节数组。 + /// buffer 内的起始位置。 + public static void GetBytes(bool value, byte[] buffer, int startIndex) + { + if (buffer == null) + { + throw new GameFrameworkException("Buffer is invalid."); + } + + if (startIndex < 0 || startIndex + 1 > buffer.Length) + { + throw new GameFrameworkException("Start index is invalid."); + } + + buffer[startIndex] = value ? (byte)1 : (byte)0; + } + + /// + /// 返回由字节数组中首字节转换来的布尔值。 + /// + /// 字节数组。 + /// 如果 value 中的首字节非零,则为 true,否则为 false。 + public static bool GetBoolean(byte[] value) + { + return BitConverter.ToBoolean(value, 0); + } + + /// + /// 返回由字节数组中指定位置的一个字节转换来的布尔值。 + /// + /// 字节数组。 + /// value 内的起始位置。 + /// 如果 value 中指定位置的字节非零,则为 true,否则为 false。 + public static bool GetBoolean(byte[] value, int startIndex) + { + return BitConverter.ToBoolean(value, startIndex); + } + + /// + /// 以字节数组的形式获取指定的 Unicode 字符值。 + /// + /// 要转换的字符。 + /// 用于存放结果的字节数组。 + public static byte[] GetBytes(char value) + { + byte[] buffer = new byte[2]; + GetBytes((short)value, buffer, 0); + return buffer; + } + + /// + /// 以字节数组的形式获取指定的 Unicode 字符值。 + /// + /// 要转换的字符。 + /// 用于存放结果的字节数组。 + public static void GetBytes(char value, byte[] buffer) + { + GetBytes((short)value, buffer, 0); + } + + /// + /// 以字节数组的形式获取指定的 Unicode 字符值。 + /// + /// 要转换的字符。 + /// 用于存放结果的字节数组。 + /// buffer 内的起始位置。 + public static void GetBytes(char value, byte[] buffer, int startIndex) + { + GetBytes((short)value, buffer, startIndex); + } + + /// + /// 返回由字节数组中前两个字节转换来的 Unicode 字符。 + /// + /// 字节数组。 + /// 由两个字节构成的字符。 + public static char GetChar(byte[] value) + { + return BitConverter.ToChar(value, 0); + } + + /// + /// 返回由字节数组中指定位置的两个字节转换来的 Unicode 字符。 + /// + /// 字节数组。 + /// value 内的起始位置。 + /// 由两个字节构成的字符。 + public static char GetChar(byte[] value, int startIndex) + { + return BitConverter.ToChar(value, startIndex); + } + + /// + /// 以字节数组的形式获取指定的 16 位有符号整数值。 + /// + /// 要转换的数字。 + /// 用于存放结果的字节数组。 + public static byte[] GetBytes(short value) + { + byte[] buffer = new byte[2]; + GetBytes(value, buffer, 0); + return buffer; + } + + /// + /// 以字节数组的形式获取指定的 16 位有符号整数值。 + /// + /// 要转换的数字。 + /// 用于存放结果的字节数组。 + public static void GetBytes(short value, byte[] buffer) + { + GetBytes(value, buffer, 0); + } + + /// + /// 以字节数组的形式获取指定的 16 位有符号整数值。 + /// + /// 要转换的数字。 + /// 用于存放结果的字节数组。 + /// buffer 内的起始位置。 + public static unsafe void GetBytes(short value, byte[] buffer, int startIndex) + { + if (buffer == null) + { + throw new GameFrameworkException("Buffer is invalid."); + } + + if (startIndex < 0 || startIndex + 2 > buffer.Length) + { + throw new GameFrameworkException("Start index is invalid."); + } + + fixed (byte* valueRef = buffer) + { + *(short*)(valueRef + startIndex) = value; + } + } + + /// + /// 返回由字节数组中前两个字节转换来的 16 位有符号整数。 + /// + /// 字节数组。 + /// 由两个字节构成的 16 位有符号整数。 + public static short GetInt16(byte[] value) + { + return BitConverter.ToInt16(value, 0); + } + + /// + /// 返回由字节数组中指定位置的两个字节转换来的 16 位有符号整数。 + /// + /// 字节数组。 + /// value 内的起始位置。 + /// 由两个字节构成的 16 位有符号整数。 + public static short GetInt16(byte[] value, int startIndex) + { + return BitConverter.ToInt16(value, startIndex); + } + + /// + /// 以字节数组的形式获取指定的 16 位无符号整数值。 + /// + /// 要转换的数字。 + /// 用于存放结果的字节数组。 + public static byte[] GetBytes(ushort value) + { + byte[] buffer = new byte[2]; + GetBytes((short)value, buffer, 0); + return buffer; + } + + /// + /// 以字节数组的形式获取指定的 16 位无符号整数值。 + /// + /// 要转换的数字。 + /// 用于存放结果的字节数组。 + public static void GetBytes(ushort value, byte[] buffer) + { + GetBytes((short)value, buffer, 0); + } + + /// + /// 以字节数组的形式获取指定的 16 位无符号整数值。 + /// + /// 要转换的数字。 + /// 用于存放结果的字节数组。 + /// buffer 内的起始位置。 + public static void GetBytes(ushort value, byte[] buffer, int startIndex) + { + GetBytes((short)value, buffer, startIndex); + } + + /// + /// 返回由字节数组中前两个字节转换来的 16 位无符号整数。 + /// + /// 字节数组。 + /// 由两个字节构成的 16 位无符号整数。 + public static ushort GetUInt16(byte[] value) + { + return BitConverter.ToUInt16(value, 0); + } + + /// + /// 返回由字节数组中指定位置的两个字节转换来的 16 位无符号整数。 + /// + /// 字节数组。 + /// value 内的起始位置。 + /// 由两个字节构成的 16 位无符号整数。 + public static ushort GetUInt16(byte[] value, int startIndex) + { + return BitConverter.ToUInt16(value, startIndex); + } + + /// + /// 以字节数组的形式获取指定的 32 位有符号整数值。 + /// + /// 要转换的数字。 + /// 用于存放结果的字节数组。 + public static byte[] GetBytes(int value) + { + byte[] buffer = new byte[4]; + GetBytes(value, buffer, 0); + return buffer; + } + + /// + /// 以字节数组的形式获取指定的 32 位有符号整数值。 + /// + /// 要转换的数字。 + /// 用于存放结果的字节数组。 + public static void GetBytes(int value, byte[] buffer) + { + GetBytes(value, buffer, 0); + } + + /// + /// 以字节数组的形式获取指定的 32 位有符号整数值。 + /// + /// 要转换的数字。 + /// 用于存放结果的字节数组。 + /// buffer 内的起始位置。 + public static unsafe void GetBytes(int value, byte[] buffer, int startIndex) + { + if (buffer == null) + { + throw new GameFrameworkException("Buffer is invalid."); + } + + if (startIndex < 0 || startIndex + 4 > buffer.Length) + { + throw new GameFrameworkException("Start index is invalid."); + } + + fixed (byte* valueRef = buffer) + { + *(int*)(valueRef + startIndex) = value; + } + } + + /// + /// 返回由字节数组中前四个字节转换来的 32 位有符号整数。 + /// + /// 字节数组。 + /// 由四个字节构成的 32 位有符号整数。 + public static int GetInt32(byte[] value) + { + return BitConverter.ToInt32(value, 0); + } + + /// + /// 返回由字节数组中指定位置的四个字节转换来的 32 位有符号整数。 + /// + /// 字节数组。 + /// value 内的起始位置。 + /// 由四个字节构成的 32 位有符号整数。 + public static int GetInt32(byte[] value, int startIndex) + { + return BitConverter.ToInt32(value, startIndex); + } + + /// + /// 以字节数组的形式获取指定的 32 位无符号整数值。 + /// + /// 要转换的数字。 + /// 用于存放结果的字节数组。 + public static byte[] GetBytes(uint value) + { + byte[] buffer = new byte[4]; + GetBytes((int)value, buffer, 0); + return buffer; + } + + /// + /// 以字节数组的形式获取指定的 32 位无符号整数值。 + /// + /// 要转换的数字。 + /// 用于存放结果的字节数组。 + public static void GetBytes(uint value, byte[] buffer) + { + GetBytes((int)value, buffer, 0); + } + + /// + /// 以字节数组的形式获取指定的 32 位无符号整数值。 + /// + /// 要转换的数字。 + /// 用于存放结果的字节数组。 + /// buffer 内的起始位置。 + public static void GetBytes(uint value, byte[] buffer, int startIndex) + { + GetBytes((int)value, buffer, startIndex); + } + + /// + /// 返回由字节数组中前四个字节转换来的 32 位无符号整数。 + /// + /// 字节数组。 + /// 由四个字节构成的 32 位无符号整数。 + public static uint GetUInt32(byte[] value) + { + return BitConverter.ToUInt32(value, 0); + } + + /// + /// 返回由字节数组中指定位置的四个字节转换来的 32 位无符号整数。 + /// + /// 字节数组。 + /// value 内的起始位置。 + /// 由四个字节构成的 32 位无符号整数。 + public static uint GetUInt32(byte[] value, int startIndex) + { + return BitConverter.ToUInt32(value, startIndex); + } + + /// + /// 以字节数组的形式获取指定的 64 位有符号整数值。 + /// + /// 要转换的数字。 + /// 用于存放结果的字节数组。 + public static byte[] GetBytes(long value) + { + byte[] buffer = new byte[8]; + GetBytes(value, buffer, 0); + return buffer; + } + + /// + /// 以字节数组的形式获取指定的 64 位有符号整数值。 + /// + /// 要转换的数字。 + /// 用于存放结果的字节数组。 + public static void GetBytes(long value, byte[] buffer) + { + GetBytes(value, buffer, 0); + } + + /// + /// 以字节数组的形式获取指定的 64 位有符号整数值。 + /// + /// 要转换的数字。 + /// 用于存放结果的字节数组。 + /// buffer 内的起始位置。 + public static unsafe void GetBytes(long value, byte[] buffer, int startIndex) + { + if (buffer == null) + { + throw new GameFrameworkException("Buffer is invalid."); + } + + if (startIndex < 0 || startIndex + 8 > buffer.Length) + { + throw new GameFrameworkException("Start index is invalid."); + } + + fixed (byte* valueRef = buffer) + { + *(long*)(valueRef + startIndex) = value; + } + } + + /// + /// 返回由字节数组中前八个字节转换来的 64 位有符号整数。 + /// + /// 字节数组。 + /// 由八个字节构成的 64 位有符号整数。 + public static long GetInt64(byte[] value) + { + return BitConverter.ToInt64(value, 0); + } + + /// + /// 返回由字节数组中指定位置的八个字节转换来的 64 位有符号整数。 + /// + /// 字节数组。 + /// value 内的起始位置。 + /// 由八个字节构成的 64 位有符号整数。 + public static long GetInt64(byte[] value, int startIndex) + { + return BitConverter.ToInt64(value, startIndex); + } + + /// + /// 以字节数组的形式获取指定的 64 位无符号整数值。 + /// + /// 要转换的数字。 + /// 用于存放结果的字节数组。 + public static byte[] GetBytes(ulong value) + { + byte[] buffer = new byte[8]; + GetBytes((long)value, buffer, 0); + return buffer; + } + + /// + /// 以字节数组的形式获取指定的 64 位无符号整数值。 + /// + /// 要转换的数字。 + /// 用于存放结果的字节数组。 + public static void GetBytes(ulong value, byte[] buffer) + { + GetBytes((long)value, buffer, 0); + } + + /// + /// 以字节数组的形式获取指定的 64 位无符号整数值。 + /// + /// 要转换的数字。 + /// 用于存放结果的字节数组。 + /// buffer 内的起始位置。 + public static void GetBytes(ulong value, byte[] buffer, int startIndex) + { + GetBytes((long)value, buffer, startIndex); + } + + /// + /// 返回由字节数组中前八个字节转换来的 64 位无符号整数。 + /// + /// 字节数组。 + /// 由八个字节构成的 64 位无符号整数。 + public static ulong GetUInt64(byte[] value) + { + return BitConverter.ToUInt64(value, 0); + } + + /// + /// 返回由字节数组中指定位置的八个字节转换来的 64 位无符号整数。 + /// + /// 字节数组。 + /// value 内的起始位置。 + /// 由八个字节构成的 64 位无符号整数。 + public static ulong GetUInt64(byte[] value, int startIndex) + { + return BitConverter.ToUInt64(value, startIndex); + } + + /// + /// 以字节数组的形式获取指定的单精度浮点值。 + /// + /// 要转换的数字。 + /// 用于存放结果的字节数组。 + public static unsafe byte[] GetBytes(float value) + { + byte[] buffer = new byte[4]; + GetBytes(*(int*)&value, buffer, 0); + return buffer; + } + + /// + /// 以字节数组的形式获取指定的单精度浮点值。 + /// + /// 要转换的数字。 + /// 用于存放结果的字节数组。 + public static unsafe void GetBytes(float value, byte[] buffer) + { + GetBytes(*(int*)&value, buffer, 0); + } + + /// + /// 以字节数组的形式获取指定的单精度浮点值。 + /// + /// 要转换的数字。 + /// 用于存放结果的字节数组。 + /// buffer 内的起始位置。 + public static unsafe void GetBytes(float value, byte[] buffer, int startIndex) + { + GetBytes(*(int*)&value, buffer, startIndex); + } + + /// + /// 返回由字节数组中前四个字节转换来的单精度浮点数。 + /// + /// 字节数组。 + /// 由四个字节构成的单精度浮点数。 + public static float GetSingle(byte[] value) + { + return BitConverter.ToSingle(value, 0); + } + + /// + /// 返回由字节数组中指定位置的四个字节转换来的单精度浮点数。 + /// + /// 字节数组。 + /// value 内的起始位置。 + /// 由四个字节构成的单精度浮点数。 + public static float GetSingle(byte[] value, int startIndex) + { + return BitConverter.ToSingle(value, startIndex); + } + + /// + /// 以字节数组的形式获取指定的双精度浮点值。 + /// + /// 要转换的数字。 + /// 用于存放结果的字节数组。 + public static unsafe byte[] GetBytes(double value) + { + byte[] buffer = new byte[8]; + GetBytes(*(long*)&value, buffer, 0); + return buffer; + } + + /// + /// 以字节数组的形式获取指定的双精度浮点值。 + /// + /// 要转换的数字。 + /// 用于存放结果的字节数组。 + public static unsafe void GetBytes(double value, byte[] buffer) + { + GetBytes(*(long*)&value, buffer, 0); + } + + /// + /// 以字节数组的形式获取指定的双精度浮点值。 + /// + /// 要转换的数字。 + /// 用于存放结果的字节数组。 + /// buffer 内的起始位置。 + public static unsafe void GetBytes(double value, byte[] buffer, int startIndex) + { + GetBytes(*(long*)&value, buffer, startIndex); + } + + /// + /// 返回由字节数组中前八个字节转换来的双精度浮点数。 + /// + /// 字节数组。 + /// 由八个字节构成的双精度浮点数。 + public static double GetDouble(byte[] value) + { + return BitConverter.ToDouble(value, 0); + } + + /// + /// 返回由字节数组中指定位置的八个字节转换来的双精度浮点数。 + /// + /// 字节数组。 + /// value 内的起始位置。 + /// 由八个字节构成的双精度浮点数。 + public static double GetDouble(byte[] value, int startIndex) + { + return BitConverter.ToDouble(value, startIndex); + } + + /// + /// 以字节数组的形式获取 UTF-8 编码的字符串。 + /// + /// 要转换的字符串。 + /// 用于存放结果的字节数组。 + public static byte[] GetBytes(string value) + { + return GetBytes(value, Encoding.UTF8); + } + + /// + /// 以字节数组的形式获取 UTF-8 编码的字符串。 + /// + /// 要转换的字符串。 + /// 用于存放结果的字节数组。 + /// buffer 内实际填充了多少字节。 + public static int GetBytes(string value, byte[] buffer) + { + return GetBytes(value, Encoding.UTF8, buffer, 0); + } + + /// + /// 以字节数组的形式获取 UTF-8 编码的字符串。 + /// + /// 要转换的字符串。 + /// 用于存放结果的字节数组。 + /// buffer 内的起始位置。 + /// buffer 内实际填充了多少字节。 + public static int GetBytes(string value, byte[] buffer, int startIndex) + { + return GetBytes(value, Encoding.UTF8, buffer, startIndex); + } + + /// + /// 以字节数组的形式获取指定编码的字符串。 + /// + /// 要转换的字符串。 + /// 要使用的编码。 + /// 用于存放结果的字节数组。 + public static byte[] GetBytes(string value, Encoding encoding) + { + if (value == null) + { + throw new GameFrameworkException("Value is invalid."); + } + + if (encoding == null) + { + throw new GameFrameworkException("Encoding is invalid."); + } + + return encoding.GetBytes(value); + } + + /// + /// 以字节数组的形式获取指定编码的字符串。 + /// + /// 要转换的字符串。 + /// 要使用的编码。 + /// 用于存放结果的字节数组。 + /// buffer 内实际填充了多少字节。 + public static int GetBytes(string value, Encoding encoding, byte[] buffer) + { + return GetBytes(value, encoding, buffer, 0); + } + + /// + /// 以字节数组的形式获取指定编码的字符串。 + /// + /// 要转换的字符串。 + /// 要使用的编码。 + /// 用于存放结果的字节数组。 + /// buffer 内的起始位置。 + /// buffer 内实际填充了多少字节。 + public static int GetBytes(string value, Encoding encoding, byte[] buffer, int startIndex) + { + if (value == null) + { + throw new GameFrameworkException("Value is invalid."); + } + + if (encoding == null) + { + throw new GameFrameworkException("Encoding is invalid."); + } + + return encoding.GetBytes(value, 0, value.Length, buffer, startIndex); + } + + /// + /// 返回由字节数组使用 UTF-8 编码转换成的字符串。 + /// + /// 字节数组。 + /// 转换后的字符串。 + public static string GetString(byte[] value) + { + return GetString(value, Encoding.UTF8); + } + + /// + /// 返回由字节数组使用指定编码转换成的字符串。 + /// + /// 字节数组。 + /// 要使用的编码。 + /// 转换后的字符串。 + public static string GetString(byte[] value, Encoding encoding) + { + if (value == null) + { + throw new GameFrameworkException("Value is invalid."); + } + + if (encoding == null) + { + throw new GameFrameworkException("Encoding is invalid."); + } + + return encoding.GetString(value); + } + + /// + /// 返回由字节数组使用 UTF-8 编码转换成的字符串。 + /// + /// 字节数组。 + /// value 内的起始位置。 + /// 长度。 + /// 转换后的字符串。 + public static string GetString(byte[] value, int startIndex, int length) + { + return GetString(value, startIndex, length, Encoding.UTF8); + } + + /// + /// 返回由字节数组使用指定编码转换成的字符串。 + /// + /// 字节数组。 + /// value 内的起始位置。 + /// 长度。 + /// 要使用的编码。 + /// 转换后的字符串。 + public static string GetString(byte[] value, int startIndex, int length, Encoding encoding) + { + if (value == null) + { + throw new GameFrameworkException("Value is invalid."); + } + + if (encoding == null) + { + throw new GameFrameworkException("Encoding is invalid."); + } + + return encoding.GetString(value, startIndex, length); + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Converter.cs.meta b/Runtime/ABase/Utility/Utility.Converter.cs.meta new file mode 100644 index 0000000..8281083 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Converter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 07b596ddbbc734d68aa8f25528ba9dfe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Utility/Utility.Encryption.Aes.cs b/Runtime/ABase/Utility/Utility.Encryption.Aes.cs new file mode 100644 index 0000000..95bf268 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Encryption.Aes.cs @@ -0,0 +1,181 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace AlicizaX +{ + public static partial class Utility + { + /// + /// 加密解密相关的实用函数。 + /// + public static partial class Encryption + { + public static class Aes + { + #region 加密 + + #region 加密字符串 + + /// + /// AES 加密(高级加密标准,是下一代的加密算法标准,速度快,安全级别高,目前 AES 标准的一个实现是 Rijndael 算法) + /// + /// 待加密密文 + /// 加密密钥 + public static string AESEncrypt(string EncryptString, string EncryptKey) + { + return Convert.ToBase64String(AESEncrypt(Encoding.UTF8.GetBytes(EncryptString), EncryptKey)); + } + + #endregion + + #region 加密字节数组 + + /// + /// AES 加密(高级加密标准,是下一代的加密算法标准,速度快,安全级别高,目前 AES 标准的一个实现是 Rijndael 算法) + /// + /// 待加密密文 + /// 加密密钥 + public static byte[] AESEncrypt(byte[] EncryptByte, string EncryptKey) + { + if (EncryptByte.Length == 0) + { + throw (new Exception("明文不得为空")); + } + + if (string.IsNullOrEmpty(EncryptKey)) + { + throw (new Exception("密钥不得为空")); + } + + byte[] m_strEncrypt; + byte[] m_btIV = new byte[16] {224, 131, 122, 101, 37, 254, 33, 17, 19, 28, 212, 130, 45, 65, 43, 32}; + byte[] m_salt = new byte[16] {234, 231, 123, 100, 87, 254, 123, 17, 89, 18, 230, 13, 45, 65, 43, 32}; + Rijndael m_AESProvider = Rijndael.Create(); + try + { + MemoryStream m_stream = new MemoryStream(); + PasswordDeriveBytes pdb = new PasswordDeriveBytes(EncryptKey, m_salt); + ICryptoTransform transform = m_AESProvider.CreateEncryptor(pdb.GetBytes(32), m_btIV); + CryptoStream m_csstream = new CryptoStream(m_stream, transform, CryptoStreamMode.Write); + m_csstream.Write(EncryptByte, 0, EncryptByte.Length); + m_csstream.FlushFinalBlock(); + m_strEncrypt = m_stream.ToArray(); + m_stream.Close(); + m_stream.Dispose(); + m_csstream.Close(); + m_csstream.Dispose(); + } + catch (IOException ex) + { + throw ex; + } + catch (CryptographicException ex) + { + throw ex; + } + catch (ArgumentException ex) + { + throw ex; + } + catch (Exception ex) + { + throw ex; + } + finally + { + m_AESProvider.Clear(); + } + + return m_strEncrypt; + } + + #endregion + + #endregion + + #region 解密 + + #region 解密字符串 + + /// + /// AES 解密(高级加密标准,是下一代的加密算法标准,速度快,安全级别高,目前 AES 标准的一个实现是 Rijndael 算法) + /// + /// 待解密密文 + /// 解密密钥 + public static string AESDecrypt(string DecryptString, string DecryptKey) + { + return Encoding.UTF8.GetString((AESDecrypt(Convert.FromBase64String(DecryptString), DecryptKey))); + } + + #endregion + + #region 解密字节数组 + + /// + /// AES 解密(高级加密标准,是下一代的加密算法标准,速度快,安全级别高,目前 AES 标准的一个实现是 Rijndael 算法) + /// + /// 待解密密文 + /// 解密密钥 + public static byte[] AESDecrypt(byte[] DecryptByte, string DecryptKey) + { + if (DecryptByte.Length == 0) + { + throw (new Exception("密文不得为空")); + } + + if (string.IsNullOrEmpty(DecryptKey)) + { + throw (new Exception("密钥不得为空")); + } + + byte[] m_strDecrypt; + byte[] m_btIV = new byte[16] {224, 131, 122, 101, 37, 254, 33, 17, 19, 28, 212, 130, 45, 65, 43, 32}; + byte[] m_salt = new byte[16] {234, 231, 123, 100, 87, 254, 123, 17, 89, 18, 230, 13, 45, 65, 43, 32}; + Rijndael m_AESProvider = Rijndael.Create(); + try + { + MemoryStream m_stream = new MemoryStream(); + PasswordDeriveBytes pdb = new PasswordDeriveBytes(DecryptKey, m_salt); + ICryptoTransform transform = m_AESProvider.CreateDecryptor(pdb.GetBytes(32), m_btIV); + CryptoStream m_csstream = new CryptoStream(m_stream, transform, CryptoStreamMode.Write); + m_csstream.Write(DecryptByte, 0, DecryptByte.Length); + m_csstream.FlushFinalBlock(); + m_strDecrypt = m_stream.ToArray(); + m_stream.Close(); + m_stream.Dispose(); + m_csstream.Close(); + m_csstream.Dispose(); + } + catch (IOException ex) + { + throw ex; + } + catch (CryptographicException ex) + { + throw ex; + } + catch (ArgumentException ex) + { + throw ex; + } + catch (Exception ex) + { + throw ex; + } + finally + { + m_AESProvider.Clear(); + } + + return m_strDecrypt; + } + + #endregion + + #endregion + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Encryption.Aes.cs.meta b/Runtime/ABase/Utility/Utility.Encryption.Aes.cs.meta new file mode 100644 index 0000000..115a45b --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Encryption.Aes.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0ff4529b751f4e0db576324e945f35d4 +timeCreated: 1665542201 \ No newline at end of file diff --git a/Runtime/ABase/Utility/Utility.Encryption.Dsa.cs b/Runtime/ABase/Utility/Utility.Encryption.Dsa.cs new file mode 100644 index 0000000..ea75220 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Encryption.Dsa.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; + +namespace AlicizaX +{ + public static partial class Utility + { + /// + /// 加密解密相关的实用函数。 + /// + public static partial class Encryption + { + public class Dsa + { + private readonly DSACryptoServiceProvider _dsa = null; + + public Dsa(DSACryptoServiceProvider dsa) + { + _dsa = dsa; + } + + public Dsa(string key) + { + DSACryptoServiceProvider rsa = new DSACryptoServiceProvider(); + rsa.FromXmlString(key); + this._dsa = rsa; + } + + public static Dictionary Make() + { + Dictionary dic = new Dictionary(); + DSACryptoServiceProvider dsa = new DSACryptoServiceProvider(); + dic["privatekey"] = dsa.ToXmlString(true); + dic["publickey"] = dsa.ToXmlString(false); + return dic; + } + + /// + /// 签名 + /// + /// + /// + /// + public static byte[] DSASignData(byte[] dataToSign, string privateKey) + { + try + { + DSACryptoServiceProvider dsa = new DSACryptoServiceProvider(); + dsa.FromXmlString(privateKey); + return dsa.SignData(dataToSign); + } + catch + { + return null; + } + } + + public static string DSASignData(string dataToSign, string privateKey) + { + byte[] res = DSASignData(Encoding.UTF8.GetBytes(dataToSign), privateKey); + return Convert.ToBase64String(res); + } + + + /// + /// 签名 + /// + /// + /// + public byte[] SignData(byte[] dataToSign) + { + try + { + return _dsa.SignData(dataToSign); + } + catch + { + return null; + } + } + + public string SignData(string dataToSign) + { + byte[] res = SignData(Encoding.UTF8.GetBytes(dataToSign)); + return Convert.ToBase64String(res); + } + + + /// + /// 验证签名 + /// + /// + /// + /// + /// + public static bool DSAVerifyData(byte[] dataToVerify, byte[] signedData, string privateKey) + { + try + { + DSACryptoServiceProvider dsa = new DSACryptoServiceProvider(); + dsa.FromXmlString(privateKey); + return dsa.VerifyData(dataToVerify, signedData); + } + catch + { + return false; + } + } + + public static bool DSAVerifyData(string dataToVerify, string signedData, string privateKey) + { + return DSAVerifyData(Encoding.UTF8.GetBytes(dataToVerify), Convert.FromBase64String(signedData), + privateKey); + } + + public bool VerifyData(byte[] dataToVerify, byte[] signedData) + { + try + { + return _dsa.VerifyData(dataToVerify, signedData); + } + catch + { + return false; + } + } + + public bool VerifyData(string dataToVerify, string signedData) + { + try + { + return VerifyData(Encoding.UTF8.GetBytes(dataToVerify), Convert.FromBase64String(signedData)); + } + catch + { + return false; + } + } + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Encryption.Dsa.cs.meta b/Runtime/ABase/Utility/Utility.Encryption.Dsa.cs.meta new file mode 100644 index 0000000..434d3cb --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Encryption.Dsa.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8e62286aead54bf88a9cb27279f96ef7 +timeCreated: 1665542268 \ No newline at end of file diff --git a/Runtime/ABase/Utility/Utility.Encryption.Rsa.cs b/Runtime/ABase/Utility/Utility.Encryption.Rsa.cs new file mode 100644 index 0000000..294ef5a --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Encryption.Rsa.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; + +namespace AlicizaX +{ + public static partial class Utility + { + /// + /// 加密解密相关的实用函数。 + /// + public static partial class Encryption + { + public class Rsa + { + private readonly RSACryptoServiceProvider _rsa = null; + + public Rsa(RSACryptoServiceProvider rsa) + { + _rsa = rsa; + } + + public Rsa(string key) + { + RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); + rsa.FromXmlString(key); + this._rsa = rsa; + } + + public static Dictionary Make() + { + Dictionary dic = new Dictionary(); + RSACryptoServiceProvider dsa = new RSACryptoServiceProvider(); + dic["privateKey"] = dsa.ToXmlString(true); + dic["publicKey"] = dsa.ToXmlString(false); + return dic; + } + + /// + /// 加密 + /// + /// 公钥 + /// 所加密的内容 + /// 加密后的内容 + public static string RSAEncrypt(string publicKey, string content) + { + byte[] res = RSAEncrypt(publicKey, Encoding.UTF8.GetBytes(content)); + return Convert.ToBase64String(res); + } + + public string Encrypt(string content) + { + byte[] res = Encrypt(Encoding.UTF8.GetBytes(content)); + return Convert.ToBase64String(res); + } + + public static byte[] RSAEncrypt(string publicKey, byte[] content) + { + RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); + rsa.FromXmlString(publicKey); + byte[] cipherBytes = rsa.Encrypt(content, false); + return cipherBytes; + } + + public byte[] Encrypt(byte[] content) + { + byte[] cipherBytes = _rsa.Encrypt(content, false); + return cipherBytes; + } + + /// + /// 解密 + /// + /// 私钥 + /// 加密后的内容 + /// 解密后的内容 + public static string RSADecrypt(string privateKey, string content) + { + byte[] res = RSADecrypt(privateKey, Convert.FromBase64String(content)); + return Encoding.UTF8.GetString(res); + } + + + public static byte[] RSADecrypt(string privateKey, byte[] content) + { + RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); + rsa.FromXmlString(privateKey); + byte[] cipherBytes = rsa.Decrypt(content, false); + return cipherBytes; + } + + public string Decrypt(string content) + { + byte[] res = Decrypt(Convert.FromBase64String(content)); + return Encoding.UTF8.GetString(res); + } + + public byte[] Decrypt(byte[] content) + { + byte[] bytes = _rsa.Decrypt(content, false); + return bytes; + } + + /// + /// 签名 + /// + public static byte[] RSASignData(byte[] dataToSign, string privateKey) + { + try + { + RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); + rsa.FromXmlString(privateKey); + return rsa.SignData(dataToSign, new SHA1CryptoServiceProvider()); + } + catch + { + return null; + } + } + + public static string RSASignData(string dataToSign, string privateKey) + { + byte[] res = RSASignData(Encoding.UTF8.GetBytes(dataToSign), privateKey); + return Convert.ToBase64String(res); + } + + public byte[] SignData(byte[] dataToSign) + { + try + { + return _rsa.SignData(dataToSign, new SHA1CryptoServiceProvider()); + } + catch + { + return null; + } + } + + public string SignData(string dataToSign) + { + byte[] res = SignData(Encoding.UTF8.GetBytes(dataToSign)); + return Convert.ToBase64String(res); + } + + /// + /// 验证签名 + /// + public static bool RSAVerifyData(byte[] dataToVerify, byte[] signedData, string publicKey) + { + try + { + RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); + rsa.FromXmlString(publicKey); + return rsa.VerifyData(dataToVerify, new SHA1CryptoServiceProvider(), signedData); + } + catch + { + return false; + } + } + + public static bool RSAVerifyData(string dataToVerify, string signedData, string publicKey) + { + return RSAVerifyData(Encoding.UTF8.GetBytes(dataToVerify), Convert.FromBase64String(signedData), + publicKey); + } + + public bool VerifyData(byte[] dataToVerify, byte[] signedData) + { + try + { + return _rsa.VerifyData(dataToVerify, new SHA1CryptoServiceProvider(), signedData); + } + catch + { + return false; + } + } + + public bool VerifyData(string dataToVerify, string signedData) + { + try + { + return VerifyData(Encoding.UTF8.GetBytes(dataToVerify), Convert.FromBase64String(signedData)); + } + catch + { + return false; + } + } + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Encryption.Rsa.cs.meta b/Runtime/ABase/Utility/Utility.Encryption.Rsa.cs.meta new file mode 100644 index 0000000..17015e2 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Encryption.Rsa.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 39dd8745138c41bba054d3c6d6c31839 +timeCreated: 1665542480 \ No newline at end of file diff --git a/Runtime/ABase/Utility/Utility.Encryption.cs b/Runtime/ABase/Utility/Utility.Encryption.cs new file mode 100644 index 0000000..4ebe336 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Encryption.cs @@ -0,0 +1,130 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace AlicizaX +{ + public static partial class Utility + { + /// + /// 加密解密相关的实用函数。 + /// + public static partial class Encryption + { + internal const int QuickEncryptLength = 220; + + /// + /// 将 bytes 使用 code 做异或运算的快速版本。 + /// + /// 原始二进制流。 + /// 异或二进制流。 + /// 异或后的二进制流。 + public static byte[] GetQuickXorBytes(byte[] bytes, byte[] code) + { + return GetXorBytes(bytes, 0, QuickEncryptLength, code); + } + + /// + /// 将 bytes 使用 code 做异或运算的快速版本。此方法将复用并改写传入的 bytes 作为返回值,而不额外分配内存空间。 + /// + /// 原始及异或后的二进制流。 + /// 异或二进制流。 + public static void GetQuickSelfXorBytes(byte[] bytes, byte[] code) + { + GetSelfXorBytes(bytes, 0, QuickEncryptLength, code); + } + + /// + /// 将 bytes 使用 code 做异或运算。 + /// + /// 原始二进制流。 + /// 异或二进制流。 + /// 异或后的二进制流。 + public static byte[] GetXorBytes(byte[] bytes, byte[] code) + { + if (bytes == null) + { + return null; + } + + return GetXorBytes(bytes, 0, bytes.Length, code); + } + + /// + /// 将 bytes 使用 code 做异或运算。此方法将复用并改写传入的 bytes 作为返回值,而不额外分配内存空间。 + /// + /// 原始及异或后的二进制流。 + /// 异或二进制流。 + public static void GetSelfXorBytes(byte[] bytes, byte[] code) + { + if (bytes == null) + { + return; + } + + GetSelfXorBytes(bytes, 0, bytes.Length, code); + } + + /// + /// 将 bytes 使用 code 做异或运算。 + /// + /// 原始二进制流。 + /// 异或计算的开始位置。 + /// 异或计算长度,若小于 0,则计算整个二进制流。 + /// 异或二进制流。 + /// 异或后的二进制流。 + public static byte[] GetXorBytes(byte[] bytes, int startIndex, int length, byte[] code) + { + if (bytes == null) + { + return null; + } + + int bytesLength = bytes.Length; + byte[] results = new byte[bytesLength]; + Array.Copy(bytes, 0, results, 0, bytesLength); + GetSelfXorBytes(results, startIndex, length, code); + return results; + } + + /// + /// 将 bytes 使用 code 做异或运算。此方法将复用并改写传入的 bytes 作为返回值,而不额外分配内存空间。 + /// + /// 原始及异或后的二进制流。 + /// 异或计算的开始位置。 + /// 异或计算长度。 + /// 异或二进制流。 + public static void GetSelfXorBytes(byte[] bytes, int startIndex, int length, byte[] code) + { + if (bytes == null) + { + return; + } + + if (code == null) + { + throw new GameFrameworkException("Code is invalid."); + } + + int codeLength = code.Length; + if (codeLength <= 0) + { + throw new GameFrameworkException("Code length is invalid."); + } + + if (startIndex < 0 || length < 0 || startIndex + length > bytes.Length) + { + throw new GameFrameworkException("Start index or length is invalid."); + } + + int codeIndex = startIndex % codeLength; + for (int i = startIndex; i < length; i++) + { + bytes[i] ^= code[codeIndex++]; + codeIndex %= codeLength; + } + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Encryption.cs.meta b/Runtime/ABase/Utility/Utility.Encryption.cs.meta new file mode 100644 index 0000000..0c1cec4 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Encryption.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b0ce2191dca84408cbd7da3d9946d008 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Utility/Utility.File.cs b/Runtime/ABase/Utility/Utility.File.cs new file mode 100644 index 0000000..beb8ef6 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.File.cs @@ -0,0 +1,47 @@ +namespace AlicizaX +{ + public static partial class Utility + { + /// + /// 文件相关的实用函数 + /// + public static class File + { + private static readonly string[] UnitList = new[] {"B", "KB", "MB", "GB", "TB", "PB"}; + + /// + /// 获取字节大小 + /// + public static string GetBytesSize(long size) + { + foreach (var unit in UnitList) + { + if (size <= 1024) + { + return size + unit; + } + + size /= 1024; + } + + return size + UnitList[0]; + } + + public static string GetLengthString(long length) + { + if (length < 1024) + { + return $"{length.ToString()} Bytes"; + } + + if (length < 1024 * 1024) + { + return $"{(length / 1024f):F2} KB"; + } + + return length < 1024 * 1024 * 1024 ? $"{(length / 1024f / 1024f):F2} MB" : $"{(length / 1024f / 1024f / 1024f):F2} GB"; + } + + } + } +} diff --git a/Runtime/ABase/Utility/Utility.File.cs.meta b/Runtime/ABase/Utility/Utility.File.cs.meta new file mode 100644 index 0000000..131b754 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.File.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 872e0e5b6e4041ce9c04c0b8da7ba9ed +timeCreated: 1692236575 \ No newline at end of file diff --git a/Runtime/ABase/Utility/Utility.Hash.HMACSha256.cs b/Runtime/ABase/Utility/Utility.Hash.HMACSha256.cs new file mode 100644 index 0000000..2b07bff --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Hash.HMACSha256.cs @@ -0,0 +1,40 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace AlicizaX +{ + public static partial class Utility + { + /// + /// 哈希计算相关的实用函数。 + /// + public static partial class Hash + { + /// + /// HMACSha256 + /// + public static class HMACSha256 + { + /// + /// 使用提供的密钥对指定消息进行HMACSHA256哈希计算。 + /// + /// 要进行哈希计算的消息。 + /// 用于哈希计算的密钥。 + /// Base64编码的哈希值。 + public static string Hash(string message, string key) + { + byte[] keyBytes = Encoding.UTF8.GetBytes(key); + byte[] messageBytes = Encoding.UTF8.GetBytes(message); + + using (var hmac = new HMACSHA256(keyBytes)) + { + byte[] hashBytes = hmac.ComputeHash(messageBytes); + return Convert.ToBase64String(hashBytes); + } + } + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Hash.HMACSha256.cs.meta b/Runtime/ABase/Utility/Utility.Hash.HMACSha256.cs.meta new file mode 100644 index 0000000..3f292c9 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Hash.HMACSha256.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3ca6e0d2ef7f4019997c82de1069ef83 +timeCreated: 1699699593 \ No newline at end of file diff --git a/Runtime/ABase/Utility/Utility.Hash.Md5.cs b/Runtime/ABase/Utility/Utility.Hash.Md5.cs new file mode 100644 index 0000000..fa8271b --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Hash.Md5.cs @@ -0,0 +1,81 @@ +using System; +using System.IO; +using System.Text; + +namespace AlicizaX +{ + public static partial class Utility + { + /// + /// 哈希计算相关的实用函数。 + /// + public static partial class Hash + { + /// + /// Md5 + /// + public static class MD5 + { + private static readonly System.Security.Cryptography.MD5 MD5Cryptography = System.Security.Cryptography.MD5.Create(); + + /// + /// 获取字符串的Md5值 + /// + /// + /// + public static string Hash(string input) + { + var data = MD5Cryptography.ComputeHash(Encoding.UTF8.GetBytes(input)); + return ToHash(data); + } + + /// + /// 获取流的Md5值 + /// + /// + /// + public static string Hash(Stream input) + { + var data = MD5Cryptography.ComputeHash(input); + return ToHash(data); + } + + /// + /// 验证Md5值是否一致 + /// + /// + /// + /// + public static bool IsVerify(string input, string hash) + { + var comparer = StringComparer.OrdinalIgnoreCase; + return 0 == comparer.Compare(input, hash); + } + + static string ToHash(byte[] data) + { + var sb = new StringBuilder(); + foreach (var t in data) + { + sb.Append(t.ToString("x2")); + } + + return sb.ToString(); + } + + /// + /// 获取指定文件路径的Md5值 + /// + /// + /// + public static string FileHash(string filePath) + { + using (FileStream file = new FileStream(filePath, FileMode.Open)) + { + return Hash(file); + } + } + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Hash.Md5.cs.meta b/Runtime/ABase/Utility/Utility.Hash.Md5.cs.meta new file mode 100644 index 0000000..dd5d68d --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Hash.Md5.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9c37f13dde60869459e624143ae45a3c +timeCreated: 1474942922 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Utility/Utility.Hash.MurmurHash3.cs b/Runtime/ABase/Utility/Utility.Hash.MurmurHash3.cs new file mode 100644 index 0000000..a088529 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Hash.MurmurHash3.cs @@ -0,0 +1,102 @@ +using System; + +namespace AlicizaX +{ + public static partial class Utility + { + /// + /// 哈希计算相关的实用函数。 + /// + public static partial class Hash + { + /// + /// MurmurHash3 + /// + public static class MurmurHash3 + { + public static uint Hash(string str, uint seed = 27) + { + var data = System.Text.Encoding.UTF8.GetBytes(str); + return Hash(data, (uint) data.Length, seed); + } + + private static uint Hash(byte[] data, uint length, uint seed) + { + uint nBlocks = length >> 2; + + uint h1 = seed; + + const uint c1 = 0xcc9e2d51; + const uint c2 = 0x1b873593; + + //---------- + // body + + int i = 0; + + for (uint j = nBlocks; j > 0; --j) + { + uint k1L = BitConverter.ToUInt32(data, i); + + k1L *= c1; + k1L = Rotl32(k1L, 15); + k1L *= c2; + + h1 ^= k1L; + h1 = Rotl32(h1, 13); + h1 = h1 * 5 + 0xe6546b64; + + i += 4; + } + + //---------- + // tail + + nBlocks <<= 2; + + uint k1 = 0; + + uint tailLength = length & 3; + + if (tailLength == 3) + k1 ^= (uint) data[2 + nBlocks] << 16; + if (tailLength >= 2) + k1 ^= (uint) data[1 + nBlocks] << 8; + if (tailLength >= 1) + { + k1 ^= data[nBlocks]; + k1 *= c1; + k1 = Rotl32(k1, 15); + k1 *= c2; + h1 ^= k1; + } + + //---------- + // finalization + + h1 ^= length; + + h1 = Fmix32(h1); + + return h1; + } + + static uint Fmix32(uint h) + { + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + + return h; + } + + static uint Rotl32(uint x, byte r) + { + return x << r | x >> 32 - r; + } + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Hash.MurmurHash3.cs.meta b/Runtime/ABase/Utility/Utility.Hash.MurmurHash3.cs.meta new file mode 100644 index 0000000..323cb4b --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Hash.MurmurHash3.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c6b0c93f47274edd8d796731a0d8a367 +timeCreated: 1681728618 \ No newline at end of file diff --git a/Runtime/ABase/Utility/Utility.Hash.Sha1.cs b/Runtime/ABase/Utility/Utility.Hash.Sha1.cs new file mode 100644 index 0000000..6d0f39c --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Hash.Sha1.cs @@ -0,0 +1,51 @@ +using System; +using System.IO; +using System.Text; + +namespace AlicizaX +{ + public static partial class Utility + { + /// + /// 哈希计算相关的实用函数。 + /// + public static partial class Hash + { + /// + /// Sha1 + /// + public static class Sha1 + { + /// + /// 使用UTF-8 编码计算Sha1 + /// + /// + /// + public static string Hash(string content) + { + return Hash(content, Encoding.UTF8); + } + + /// + /// 使用指定编码 计算Sha1 + /// + /// + /// + /// + public static string Hash(string content, Encoding encode) + { + //创建SHA1对象 + using (var sha1 = new System.Security.Cryptography.SHA1CryptoServiceProvider()) + { + //将待加密字符串转为byte类型 + var bytesIn = encode.GetBytes(content); + var bytesOut = sha1.ComputeHash(bytesIn); + var result = BitConverter.ToString(bytesOut); //将运算结果转为string类型 + result = result.Replace("-", "").ToLower(); + return result; + } + } + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Hash.Sha1.cs.meta b/Runtime/ABase/Utility/Utility.Hash.Sha1.cs.meta new file mode 100644 index 0000000..e8ddd18 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Hash.Sha1.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 756e2050a90e416a9cdd9550f0bcd938 +timeCreated: 1681727448 \ No newline at end of file diff --git a/Runtime/ABase/Utility/Utility.Hash.XxHash.cs b/Runtime/ABase/Utility/Utility.Hash.XxHash.cs new file mode 100644 index 0000000..5ea2d09 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Hash.XxHash.cs @@ -0,0 +1,309 @@ +using System; +using System.Runtime.CompilerServices; +using System.Text; + +namespace AlicizaX +{ + public static partial class Utility + { + /// + /// 哈希计算相关的实用函数。 + /// + public static partial class Hash + { + /// + /// xxHash + /// + public static class XXHash + { + public static uint Hash32(byte[] buffer) + { + return XXHashHelper.Hash32(buffer); + } + + public static uint Hash32(string text) + { + return XXHashHelper.Hash32(text); + } + + public static uint Hash32(Type type) + { + return XXHashHelper.Hash32(type); + } + + public static uint Hash32() + { + return XXHashHelper.Hash32(); + } + + public static ulong Hash64(byte[] buffer) + { + return XXHashHelper.Hash64(buffer); + } + + public static ulong Hash64(string text) + { + return XXHashHelper.Hash64(text); + } + + public static ulong Hash64(Type type) + { + return XXHashHelper.Hash64(type); + } + + public static ulong Hash64() + { + return XXHashHelper.Hash64(); + } + } + + /// + /// XX Hash 计算帮助类 + /// + static class XXHashHelper + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static unsafe uint Hash32(byte* input, int length, uint seed = 0) + { + unchecked + { + const uint prime1 = 2654435761u; + const uint prime2 = 2246822519u; + const uint prime3 = 3266489917u; + const uint prime4 = 0668265263u; + const uint prime5 = 0374761393u; + + uint hash = seed + prime5; + + if (length >= 16) + { + uint val0 = seed + prime1 + prime2; + uint val1 = seed + prime2; + uint val2 = seed + 0; + uint val3 = seed - prime1; + + int count = length >> 4; + for (int i = 0; i < count; i++) + { + var pos0 = *(uint*) (input + 0); + var pos1 = *(uint*) (input + 4); + var pos2 = *(uint*) (input + 8); + var pos3 = *(uint*) (input + 12); + + val0 += pos0 * prime2; + val0 = (val0 << 13) | (val0 >> (32 - 13)); + val0 *= prime1; + + val1 += pos1 * prime2; + val1 = (val1 << 13) | (val1 >> (32 - 13)); + val1 *= prime1; + + val2 += pos2 * prime2; + val2 = (val2 << 13) | (val2 >> (32 - 13)); + val2 *= prime1; + + val3 += pos3 * prime2; + val3 = (val3 << 13) | (val3 >> (32 - 13)); + val3 *= prime1; + + input += 16; + } + + hash = ((val0 << 01) | (val0 >> (32 - 01))) + + ((val1 << 07) | (val1 >> (32 - 07))) + + ((val2 << 12) | (val2 >> (32 - 12))) + + ((val3 << 18) | (val3 >> (32 - 18))); + } + + hash += (uint) length; + + length &= 15; + while (length >= 4) + { + hash += *(uint*) input * prime3; + hash = ((hash << 17) | (hash >> (32 - 17))) * prime4; + input += 4; + length -= 4; + } + + while (length > 0) + { + hash += *input * prime5; + hash = ((hash << 11) | (hash >> (32 - 11))) * prime1; + ++input; + --length; + } + + hash ^= hash >> 15; + hash *= prime2; + hash ^= hash >> 13; + hash *= prime3; + hash ^= hash >> 16; + + return hash; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static unsafe ulong Hash64(byte* input, int length, uint seed = 0) + { + unchecked + { + const ulong prime1 = 11400714785074694791ul; + const ulong prime2 = 14029467366897019727ul; + const ulong prime3 = 01609587929392839161ul; + const ulong prime4 = 09650029242287828579ul; + const ulong prime5 = 02870177450012600261ul; + + ulong hash = seed + prime5; + + if (length >= 32) + { + ulong val0 = seed + prime1 + prime2; + ulong val1 = seed + prime2; + ulong val2 = seed + 0; + ulong val3 = seed - prime1; + + int count = length >> 5; + for (int i = 0; i < count; i++) + { + var pos0 = *(ulong*) (input + 0); + var pos1 = *(ulong*) (input + 8); + var pos2 = *(ulong*) (input + 16); + var pos3 = *(ulong*) (input + 24); + + val0 += pos0 * prime2; + val0 = (val0 << 31) | (val0 >> (64 - 31)); + val0 *= prime1; + + val1 += pos1 * prime2; + val1 = (val1 << 31) | (val1 >> (64 - 31)); + val1 *= prime1; + + val2 += pos2 * prime2; + val2 = (val2 << 31) | (val2 >> (64 - 31)); + val2 *= prime1; + + val3 += pos3 * prime2; + val3 = (val3 << 31) | (val3 >> (64 - 31)); + val3 *= prime1; + + input += 32; + } + + hash = ((val0 << 01) | (val0 >> (64 - 01))) + + ((val1 << 07) | (val1 >> (64 - 07))) + + ((val2 << 12) | (val2 >> (64 - 12))) + + ((val3 << 18) | (val3 >> (64 - 18))); + + val0 *= prime2; + val0 = (val0 << 31) | (val0 >> (64 - 31)); + val0 *= prime1; + hash ^= val0; + hash = hash * prime1 + prime4; + + val1 *= prime2; + val1 = (val1 << 31) | (val1 >> (64 - 31)); + val1 *= prime1; + hash ^= val1; + hash = hash * prime1 + prime4; + + val2 *= prime2; + val2 = (val2 << 31) | (val2 >> (64 - 31)); + val2 *= prime1; + hash ^= val2; + hash = hash * prime1 + prime4; + + val3 *= prime2; + val3 = (val3 << 31) | (val3 >> (64 - 31)); + val3 *= prime1; + hash ^= val3; + hash = hash * prime1 + prime4; + } + + hash += (ulong) length; + + length &= 31; + while (length >= 8) + { + ulong lane = *(ulong*) input * prime2; + lane = ((lane << 31) | (lane >> (64 - 31))) * prime1; + hash ^= lane; + hash = ((hash << 27) | (hash >> (64 - 27))) * prime1 + prime4; + input += 8; + length -= 8; + } + + if (length >= 4) + { + hash ^= *(uint*) input * prime1; + hash = ((hash << 23) | (hash >> (64 - 23))) * prime2 + prime3; + input += 4; + length -= 4; + } + + while (length > 0) + { + hash ^= *input * prime5; + hash = ((hash << 11) | (hash >> (64 - 11))) * prime1; + ++input; + --length; + } + + hash ^= hash >> 33; + hash *= prime2; + hash ^= hash >> 29; + hash *= prime3; + hash ^= hash >> 32; + + return hash; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Hash32(byte[] buffer) + { + int length = buffer.Length; + unsafe + { + fixed (byte* pointer = buffer) + { + return Hash32(pointer, length); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Hash32(string text) => Hash32(Encoding.UTF8.GetBytes(text)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Hash32(Type type) => Hash32(type.FullName); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Hash32() => Hash32(typeof(T)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Hash64(byte[] buffer) + { + int length = buffer.Length; + unsafe + { + fixed (byte* pointer = buffer) + { + return Hash64(pointer, length); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Hash64(string text) => Hash64(Encoding.UTF8.GetBytes(text)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Hash64(Type type) => Hash64(type.FullName); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Hash64() => Hash64(typeof(T)); + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Hash.XxHash.cs.meta b/Runtime/ABase/Utility/Utility.Hash.XxHash.cs.meta new file mode 100644 index 0000000..684bfdd --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Hash.XxHash.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ed97638733d9428ca5eee5e72c677280 +timeCreated: 1671505737 \ No newline at end of file diff --git a/Runtime/ABase/Utility/Utility.Http.cs b/Runtime/ABase/Utility/Utility.Http.cs new file mode 100644 index 0000000..40ec2b7 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Http.cs @@ -0,0 +1,105 @@ +using System.Collections.Generic; +using System.Text; +using Cysharp.Threading.Tasks; +using UnityEngine; +using UnityEngine.Networking; + +namespace AlicizaX +{ + public static partial class Utility + { + public static partial class Http + { + /// + /// 发送 GET 请求 + /// + public static async UniTask Get(string url, float timeout = 5f) + { + using var request = UnityWebRequest.Get(url); + return await SendRequest(request); + } + + /// + /// 发送 JSON 格式的 POST 请求(服务端需用 [FromBody] 接收) + /// + public static async UniTask PostJson(string url, object jsonData, float timeout = 5f) + { + var json = Json.ToJson(jsonData); + using var request = CreateJsonPostRequest(url, json); + return await SendRequest(request); + } + + /// + /// 发送表单格式的 POST 请求(x-www-form-urlencoded) + /// + public static async UniTask PostForm(string url, Dictionary formData, float timeout = 5f) + { + using var request = UnityWebRequest.Post(url, formData); + return await SendRequest(request); + } + + /// + /// 发送多部分表单的 POST 请求(multipart/form-data,支持文件上传) + /// + public static async UniTask PostMultipart(string url, WWWForm formData, float timeout = 5f) + { + using var request = UnityWebRequest.Post(url, formData); + return await SendRequest(request); + } + + + private static UnityWebRequest CreateJsonPostRequest(string url, string json) + { + var request = new UnityWebRequest(url, "POST"); + byte[] jsonBytes = Encoding.UTF8.GetBytes(json); + request.uploadHandler = new UploadHandlerRaw(jsonBytes); + request.downloadHandler = new DownloadHandlerBuffer(); + request.SetRequestHeader("Content-Type", "application/json"); + return request; + } + + private static async UniTask SendRequest(UnityWebRequest request) + { + string url = request.url; // 提前获取请求地址 + + try + { + await request.SendWebRequest(); + + if (request.result != UnityWebRequest.Result.Success) + { + HandleRequestError(request, url); + return string.Empty; + } + + return request.downloadHandler.text; + } + catch (UnityWebRequestException e) + { + HandleRequestError(request, url); + return string.Empty; + } + } + + private static void HandleRequestError(UnityWebRequest request, string url) + { + ; + switch (request.result) + { + case UnityWebRequest.Result.ConnectionError: + Log.Error($"无法访问地址:{url}"); + break; + case UnityWebRequest.Result.ProtocolError: + Log.Error($"HTTP协议错误 ({request.responseCode}):{url}\n{request.error}"); + break; + case UnityWebRequest.Result.DataProcessingError: + Log.Error($"数据处理错误:{url}\n{request.error}"); + break; + default: + Log.Error($"未知网络错误:{url}\n{request.error}"); + break; + } + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Http.cs.meta b/Runtime/ABase/Utility/Utility.Http.cs.meta new file mode 100644 index 0000000..d78c86b --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Http.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3ee53f5e088c48fcbe63d030b2e55998 +timeCreated: 1736410066 \ No newline at end of file diff --git a/Runtime/ABase/Utility/Utility.IdGenerator.cs b/Runtime/ABase/Utility/Utility.IdGenerator.cs new file mode 100644 index 0000000..292af51 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.IdGenerator.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading; + +namespace AlicizaX +{ + public static partial class Utility + { + public static class IdGenerator + { + private static readonly DateTime s_UtcTimeStart = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + // 共享计数器 + private static long _counter = (long)(DateTime.UtcNow - s_UtcTimeStart).TotalSeconds; + private static int _intCounter = (int)(DateTime.UtcNow - s_UtcTimeStart).TotalSeconds; + + /// + /// 使用Interlocked.Increment生成唯一ID的方法 + /// + /// + public static long GetNextUniqueId() + { + // 原子性地递增值 + return Interlocked.Increment(ref _counter); + } + + /// + /// 使用Interlocked.Increment生成唯一ID的方法 + /// + /// + public static int GetNextUniqueIntId() + { + return Interlocked.Increment(ref _intCounter); + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.IdGenerator.cs.meta b/Runtime/ABase/Utility/Utility.IdGenerator.cs.meta new file mode 100644 index 0000000..299731f --- /dev/null +++ b/Runtime/ABase/Utility/Utility.IdGenerator.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 092e0d51f3da49a384dd0db27b6fa881 +timeCreated: 1715400168 \ No newline at end of file diff --git a/Runtime/ABase/Utility/Utility.Json.cs b/Runtime/ABase/Utility/Utility.Json.cs new file mode 100644 index 0000000..f0b1152 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Json.cs @@ -0,0 +1,87 @@ +using System; +using Newtonsoft.Json; + +namespace AlicizaX +{ + public static partial class Utility + { + /// + /// JSON 相关的实用函数。 + /// + public static partial class Json + { + /// + /// 将对象序列化为 JSON 字符串。 + /// + /// 要序列化的对象。 + /// 序列化后的 JSON 字符串。 + public static string ToJson(object obj) + { + try + { + return JsonConvert.SerializeObject(obj); + } + catch (Exception exception) + { + if (exception is GameFrameworkException) + { + throw; + } + + throw new GameFrameworkException(Text.Format("Can not convert to JSON with exception '{0}'.", exception), exception); + } + } + + /// + /// 将 JSON 字符串反序列化为对象。 + /// + /// 对象类型。 + /// 要反序列化的 JSON 字符串。 + /// 反序列化后的对象。 + public static T ToObject(string json) + { + try + { + return JsonConvert.DeserializeObject(json); + } + catch (Exception exception) + { + if (exception is GameFrameworkException) + { + throw; + } + + throw new GameFrameworkException(Text.Format("Can not convert to object with exception '{0}'.", exception), exception); + } + } + + /// + /// 将 JSON 字符串反序列化为对象。 + /// + /// 对象类型。 + /// 要反序列化的 JSON 字符串。 + /// 反序列化后的对象。 + public static object ToObject(Type objectType, string json) + { + if (objectType == null) + { + throw new GameFrameworkException("Object type is invalid."); + } + + try + { + return JsonConvert.DeserializeObject(json, objectType); + } + catch (Exception exception) + { + if (exception is GameFrameworkException) + { + throw; + } + + throw new GameFrameworkException(Text.Format("Can not convert to object with exception '{0}'.", exception), exception); + } + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Json.cs.meta b/Runtime/ABase/Utility/Utility.Json.cs.meta new file mode 100644 index 0000000..89fa539 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Json.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cf9b8799ff4a44353a2756a9b527d818 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Utility/Utility.Marshal.cs b/Runtime/ABase/Utility/Utility.Marshal.cs new file mode 100644 index 0000000..167ea10 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Marshal.cs @@ -0,0 +1,233 @@ +using System; + +namespace AlicizaX +{ + public static partial class Utility + { + /// + /// Marshal 相关的实用函数。 + /// + public static class Marshal + { + private const int BlockSize = 1024 * 4; + private static IntPtr s_CachedHGlobalPtr = IntPtr.Zero; + private static int s_CachedHGlobalSize = 0; + + /// + /// 获取缓存的从进程的非托管内存中分配的内存的大小。 + /// + public static int CachedHGlobalSize + { + get + { + return s_CachedHGlobalSize; + } + } + + /// + /// 确保从进程的非托管内存中分配足够大小的内存并缓存。 + /// + /// 要确保从进程的非托管内存中分配内存的大小。 + public static void EnsureCachedHGlobalSize(int ensureSize) + { + if (ensureSize < 0) + { + throw new GameFrameworkException("Ensure size is invalid."); + } + + if (s_CachedHGlobalPtr == IntPtr.Zero || s_CachedHGlobalSize < ensureSize) + { + FreeCachedHGlobal(); + int size = (ensureSize - 1 + BlockSize) / BlockSize * BlockSize; + s_CachedHGlobalPtr = System.Runtime.InteropServices.Marshal.AllocHGlobal(size); + s_CachedHGlobalSize = size; + } + } + + /// + /// 释放缓存的从进程的非托管内存中分配的内存。 + /// + public static void FreeCachedHGlobal() + { + if (s_CachedHGlobalPtr != IntPtr.Zero) + { + System.Runtime.InteropServices.Marshal.FreeHGlobal(s_CachedHGlobalPtr); + s_CachedHGlobalPtr = IntPtr.Zero; + s_CachedHGlobalSize = 0; + } + } + + /// + /// 将数据从对象转换为二进制流。 + /// + /// 要转换的对象的类型。 + /// 要转换的对象。 + /// 存储转换结果的二进制流。 + public static byte[] StructureToBytes(T structure) + { + return StructureToBytes(structure, System.Runtime.InteropServices.Marshal.SizeOf(typeof(T))); + } + + /// + /// 将数据从对象转换为二进制流。 + /// + /// 要转换的对象的类型。 + /// 要转换的对象。 + /// 要转换的对象的大小。 + /// 存储转换结果的二进制流。 + internal static byte[] StructureToBytes(T structure, int structureSize) + { + if (structureSize < 0) + { + throw new GameFrameworkException("Structure size is invalid."); + } + + EnsureCachedHGlobalSize(structureSize); + System.Runtime.InteropServices.Marshal.StructureToPtr(structure, s_CachedHGlobalPtr, true); + byte[] result = new byte[structureSize]; + System.Runtime.InteropServices.Marshal.Copy(s_CachedHGlobalPtr, result, 0, structureSize); + return result; + } + + /// + /// 将数据从对象转换为二进制流。 + /// + /// 要转换的对象的类型。 + /// 要转换的对象。 + /// 存储转换结果的二进制流。 + public static void StructureToBytes(T structure, byte[] result) + { + StructureToBytes(structure, System.Runtime.InteropServices.Marshal.SizeOf(typeof(T)), result, 0); + } + + /// + /// 将数据从对象转换为二进制流。 + /// + /// 要转换的对象的类型。 + /// 要转换的对象。 + /// 要转换的对象的大小。 + /// 存储转换结果的二进制流。 + internal static void StructureToBytes(T structure, int structureSize, byte[] result) + { + StructureToBytes(structure, structureSize, result, 0); + } + + /// + /// 将数据从对象转换为二进制流。 + /// + /// 要转换的对象的类型。 + /// 要转换的对象。 + /// 存储转换结果的二进制流。 + /// 写入存储转换结果的二进制流的起始位置。 + public static void StructureToBytes(T structure, byte[] result, int startIndex) + { + StructureToBytes(structure, System.Runtime.InteropServices.Marshal.SizeOf(typeof(T)), result, startIndex); + } + + /// + /// 将数据从对象转换为二进制流。 + /// + /// 要转换的对象的类型。 + /// 要转换的对象。 + /// 要转换的对象的大小。 + /// 存储转换结果的二进制流。 + /// 写入存储转换结果的二进制流的起始位置。 + internal static void StructureToBytes(T structure, int structureSize, byte[] result, int startIndex) + { + if (structureSize < 0) + { + throw new GameFrameworkException("Structure size is invalid."); + } + + if (result == null) + { + throw new GameFrameworkException("Result is invalid."); + } + + if (startIndex < 0) + { + throw new GameFrameworkException("Start index is invalid."); + } + + if (startIndex + structureSize > result.Length) + { + throw new GameFrameworkException("Result length is not enough."); + } + + EnsureCachedHGlobalSize(structureSize); + System.Runtime.InteropServices.Marshal.StructureToPtr(structure, s_CachedHGlobalPtr, true); + System.Runtime.InteropServices.Marshal.Copy(s_CachedHGlobalPtr, result, startIndex, structureSize); + } + + /// + /// 将数据从二进制流转换为对象。 + /// + /// 要转换的对象的类型。 + /// 要转换的二进制流。 + /// 存储转换结果的对象。 + public static T BytesToStructure(byte[] buffer) + { + return BytesToStructure(System.Runtime.InteropServices.Marshal.SizeOf(typeof(T)), buffer, 0); + } + + /// + /// 将数据从二进制流转换为对象。 + /// + /// 要转换的对象的类型。 + /// 要转换的二进制流。 + /// 读取要转换的二进制流的起始位置。 + /// 存储转换结果的对象。 + public static T BytesToStructure(byte[] buffer, int startIndex) + { + return BytesToStructure(System.Runtime.InteropServices.Marshal.SizeOf(typeof(T)), buffer, startIndex); + } + + /// + /// 将数据从二进制流转换为对象。 + /// + /// 要转换的对象的类型。 + /// 要转换的对象的大小。 + /// 要转换的二进制流。 + /// 存储转换结果的对象。 + internal static T BytesToStructure(int structureSize, byte[] buffer) + { + return BytesToStructure(structureSize, buffer, 0); + } + + /// + /// 将数据从二进制流转换为对象。 + /// + /// 要转换的对象的类型。 + /// 要转换的对象的大小。 + /// 要转换的二进制流。 + /// 读取要转换的二进制流的起始位置。 + /// 存储转换结果的对象。 + internal static T BytesToStructure(int structureSize, byte[] buffer, int startIndex) + { + if (structureSize < 0) + { + throw new GameFrameworkException("Structure size is invalid."); + } + + if (buffer == null) + { + throw new GameFrameworkException("Buffer is invalid."); + } + + if (startIndex < 0) + { + throw new GameFrameworkException("Start index is invalid."); + } + + if (startIndex + structureSize > buffer.Length) + { + throw new GameFrameworkException("Buffer length is not enough."); + } + + EnsureCachedHGlobalSize(structureSize); + System.Runtime.InteropServices.Marshal.Copy(buffer, startIndex, s_CachedHGlobalPtr, structureSize); + return (T)System.Runtime.InteropServices.Marshal.PtrToStructure(s_CachedHGlobalPtr, typeof(T)); + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Marshal.cs.meta b/Runtime/ABase/Utility/Utility.Marshal.cs.meta new file mode 100644 index 0000000..f1b9778 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Marshal.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 06313a0939ff740f8bdc2cdc1006991c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Utility/Utility.Net.cs b/Runtime/ABase/Utility/Utility.Net.cs new file mode 100644 index 0000000..4ef9926 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Net.cs @@ -0,0 +1,135 @@ +using System.Collections; +using System.Collections.Generic; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; + +namespace AlicizaX +{ + public static partial class Utility + { + /// + /// 网络相关的对象工具类 + /// + public static class Net + { + /// + /// 获取第一个可用的端口号 + /// + /// 起始端口号 + /// 结束端口号 + /// + public static int GetFirstAvailablePort(int startPort = 667, int maxPort = 65535) + { + for (int i = startPort; i < maxPort; i++) + { + if (PortIsAvailable(i)) return i; + } + + return -1; + } + + /// + /// 获取操作系统已用的端口号 + /// + /// + public static List PortIsUsed() + { + //获取本地计算机的网络连接和通信统计数据的信息 + var ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties(); + + //返回本地计算机上的所有Tcp监听程序 + var ipsTcp = ipGlobalProperties.GetActiveTcpListeners(); + + //返回本地计算机上的所有UDP监听程序 + var ipsUDP = ipGlobalProperties.GetActiveUdpListeners(); + + //返回本地计算机上的Internet协议版本4(IPV4 传输控制协议(TCP)连接的信息。 + var tcpConnInfoArray = ipGlobalProperties.GetActiveTcpConnections(); + + var allPorts = new List(); + foreach (var ep in ipsTcp) + { + allPorts.Add(ep.Port); + } + + foreach (var ep in ipsUDP) + { + allPorts.Add(ep.Port); + } + + foreach (var conn in tcpConnInfoArray) + { + allPorts.Add(conn.LocalEndPoint.Port); + } + + return allPorts; + } + + /// + /// 检查指定端口是否已用 + /// + /// + /// + public static bool PortIsAvailable(int port) + { + var isAvailable = true; + + var portUsed = PortIsUsed(); + + foreach (int p in portUsed) + { + if (p == port) + { + isAvailable = false; + break; + } + } + + return isAvailable; + } + + /// + /// 获取本机ip地址 + /// + /// + public static string GetIP() + { + var hostName = Dns.GetHostName(); + var iPHostEntry = Dns.GetHostEntry(hostName); + foreach (var address in iPHostEntry.AddressList) + { + if (address.AddressFamily == AddressFamily.InterNetwork) + { + return address.ToString(); + } + } + + return string.Empty; + } + + public static (AddressFamily, string) GetIPv6Address(string host) + { + var addresses = Dns.GetHostAddresses(host); + + foreach (var ipAddress in addresses) + { + if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6) + { + return (AddressFamily.InterNetworkV6, ipAddress.ToString()); + } + } + + foreach (var ipAddress in addresses) + { + if (ipAddress.AddressFamily == AddressFamily.InterNetwork) + { + return (AddressFamily.InterNetwork, ipAddress.ToString()); + } + } + + return (AddressFamily.InterNetwork, host); + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Net.cs.meta b/Runtime/ABase/Utility/Utility.Net.cs.meta new file mode 100644 index 0000000..ad77dcd --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Net.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e4cce3f967084c2e97b8152a67e5fa33 +timeCreated: 1683295297 \ No newline at end of file diff --git a/Runtime/ABase/Utility/Utility.Object.cs b/Runtime/ABase/Utility/Utility.Object.cs new file mode 100644 index 0000000..5f2d17c --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Object.cs @@ -0,0 +1,22 @@ +namespace AlicizaX +{ + public static partial class Utility + { + /// + /// Object 对象工具类 + /// + public static class Object + { + /// + /// 交换对象 + /// + /// + /// + /// + public static void Swap(ref T t1, ref T t2) + { + (t1, t2) = (t2, t1); + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Object.cs.meta b/Runtime/ABase/Utility/Utility.Object.cs.meta new file mode 100644 index 0000000..9249cd1 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Object.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0264830b4fe54557b5b0f6a6123ffbb1 +timeCreated: 1666234520 \ No newline at end of file diff --git a/Runtime/ABase/Utility/Utility.Path.cs b/Runtime/ABase/Utility/Utility.Path.cs new file mode 100644 index 0000000..c1056a8 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Path.cs @@ -0,0 +1,93 @@ +using System.IO; + +namespace AlicizaX +{ + public static partial class Utility + { + /// + /// 路径相关的实用函数。 + /// + public static class Path + { + /// + /// 获取规范的路径。 + /// + /// 要规范的路径。 + /// 规范的路径。 + public static string GetRegularPath(string path) + { + if (path == null) + { + return null; + } + + return path.Replace('\\', '/'); + } + + /// + /// 获取远程格式的路径(带有file:// 或 http:// 前缀)。 + /// + /// 原始路径。 + /// 远程格式路径。 + public static string GetRemotePath(string path) + { + string regularPath = GetRegularPath(path); + if (regularPath == null) + { + return null; + } + + return regularPath.Contains("://") ? regularPath : ("file:///" + regularPath).Replace("file:////", "file:///"); + } + + /// + /// 移除空文件夹。 + /// + /// 要处理的文件夹名称。 + /// 是否移除空文件夹成功。 + public static bool RemoveEmptyDirectory(string directoryName) + { + if (string.IsNullOrEmpty(directoryName)) + { + throw new GameFrameworkException("Directory name is invalid."); + } + + try + { + if (!Directory.Exists(directoryName)) + { + return false; + } + + // 不使用 SearchOption.AllDirectories,以便于在可能产生异常的环境下删除尽可能多的目录 + string[] subDirectoryNames = Directory.GetDirectories(directoryName, "*"); + int subDirectoryCount = subDirectoryNames.Length; + foreach (string subDirectoryName in subDirectoryNames) + { + if (RemoveEmptyDirectory(subDirectoryName)) + { + subDirectoryCount--; + } + } + + if (subDirectoryCount > 0) + { + return false; + } + + if (Directory.GetFiles(directoryName, "*").Length > 0) + { + return false; + } + + Directory.Delete(directoryName); + return true; + } + catch + { + return false; + } + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Path.cs.meta b/Runtime/ABase/Utility/Utility.Path.cs.meta new file mode 100644 index 0000000..7778381 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Path.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bb122d6a3966a41ffa8619c345e8abee +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Utility/Utility.PlayerPrefs.cs b/Runtime/ABase/Utility/Utility.PlayerPrefs.cs new file mode 100644 index 0000000..56afa85 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.PlayerPrefs.cs @@ -0,0 +1,137 @@ +using System; +using UnityEngine; + +namespace AlicizaX +{ + public static partial class Utility + { + public static class PlayerPrefsX + { + private static string _prefix = string.Empty; + + /// + /// 当前用户前缀,用于隔离不同用户的配置项。 + /// + public static string CurrentUserPrefix + { + get + { + if (string.IsNullOrEmpty(_prefix)) + { + _prefix = Application.productName.ToString(); + } + + return _prefix; + } + set => _prefix = value; + } + + /// + /// 组合完整键名,格式为"[前缀].[原始键名]" + /// + private static string CombineKey(string settingName) + { + return string.IsNullOrEmpty(CurrentUserPrefix) + ? settingName + : $"{CurrentUserPrefix}.{settingName}"; + } + + /// + /// 保存游戏配置。 + /// + public static bool Save() + { + PlayerPrefs.Save(); + return true; + } + + /// + /// 检查是否存在指定配置项。 + /// + public static bool HasSetting(string settingName) + => PlayerPrefs.HasKey(CombineKey(settingName)); + + /// + /// 移除指定配置项。 + /// + public static bool RemoveSetting(string settingName) + { + var fullKey = CombineKey(settingName); + if (!PlayerPrefs.HasKey(fullKey)) return false; + + PlayerPrefs.DeleteKey(fullKey); + return true; + } + + /// + /// 清空所有配置项(包括所有用户)。 + /// + public static void RemoveAllSettings() => PlayerPrefs.DeleteAll(); + + // 布尔值处理 + public static bool GetBool(string settingName) + => PlayerPrefs.GetInt(CombineKey(settingName)) != 0; + + public static bool GetBool(string settingName, bool defaultValue) + => PlayerPrefs.GetInt(CombineKey(settingName), defaultValue ? 1 : 0) != 0; + + public static void SetBool(string settingName, bool value) + => PlayerPrefs.SetInt(CombineKey(settingName), value ? 1 : 0); + + // 整型处理 + public static int GetInt(string settingName) + => PlayerPrefs.GetInt(CombineKey(settingName)); + + public static int GetInt(string settingName, int defaultValue) + => PlayerPrefs.GetInt(CombineKey(settingName), defaultValue); + + public static void SetInt(string settingName, int value) + => PlayerPrefs.SetInt(CombineKey(settingName), value); + + // 浮点数处理 + public static float GetFloat(string settingName) + => PlayerPrefs.GetFloat(CombineKey(settingName)); + + public static float GetFloat(string settingName, float defaultValue) + => PlayerPrefs.GetFloat(CombineKey(settingName), defaultValue); + + public static void SetFloat(string settingName, float value) + => PlayerPrefs.SetFloat(CombineKey(settingName), value); + + // 字符串处理 + public static string GetString(string settingName) + => PlayerPrefs.GetString(CombineKey(settingName)); + + public static string GetString(string settingName, string defaultValue) + => PlayerPrefs.GetString(CombineKey(settingName), defaultValue); + + public static void SetString(string settingName, string value) + => PlayerPrefs.SetString(CombineKey(settingName), value); + + // 对象序列化处理 + public static T GetObject(string settingName) + => Utility.Json.ToObject(GetString(settingName)); + + public static object GetObject(Type objectType, string settingName) + => Utility.Json.ToObject(objectType, GetString(settingName)); + + public static T GetObject(string settingName, T defaultObj) + { + var json = GetString(settingName, null); + return string.IsNullOrWhiteSpace(json) ? defaultObj : Utility.Json.ToObject(json); + } + + public static object GetObject(Type objectType, string settingName, object defaultObj) + { + var json = GetString(settingName, null); + return string.IsNullOrWhiteSpace(json) ? defaultObj : Utility.Json.ToObject(objectType, json); + } + + public static void SetObject(string settingName, T obj) + => SetString(settingName, Utility.Json.ToJson(obj)); + + public static void SetObject(string settingName, object obj) + => SetString(settingName, Utility.Json.ToJson(obj)); + } + } +} diff --git a/Runtime/ABase/Utility/Utility.PlayerPrefs.cs.meta b/Runtime/ABase/Utility/Utility.PlayerPrefs.cs.meta new file mode 100644 index 0000000..aca8237 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.PlayerPrefs.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8481b9e709a045ffb3b89dfd5c00b198 +timeCreated: 1742453838 \ No newline at end of file diff --git a/Runtime/ABase/Utility/Utility.Random.cs b/Runtime/ABase/Utility/Utility.Random.cs new file mode 100644 index 0000000..52cb02e --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Random.cs @@ -0,0 +1,72 @@ +using System; + +namespace AlicizaX +{ + public static partial class Utility + { + /// + /// 随机相关的实用函数。 + /// + public static class Random + { + private static System.Random s_Random = new System.Random((int)DateTime.UtcNow.Ticks); + + /// + /// 设置随机数种子。 + /// + /// 随机数种子。 + public static void SetSeed(int seed) + { + s_Random = new System.Random(seed); + } + + /// + /// 返回非负随机数。 + /// + /// 大于等于零且小于 System.Int32.MaxValue 的 32 位带符号整数。 + public static int GetRandom() + { + return s_Random.Next(); + } + + /// + /// 返回一个小于所指定最大值的非负随机数。 + /// + /// 要生成的随机数的上界(随机数不能取该上界值)。maxValue 必须大于等于零。 + /// 大于等于零且小于 maxValue 的 32 位带符号整数,即:返回值的范围通常包括零但不包括 maxValue。不过,如果 maxValue 等于零,则返回 maxValue。 + public static int GetRandom(int maxValue) + { + return s_Random.Next(maxValue); + } + + /// + /// 返回一个指定范围内的随机数。 + /// + /// 返回的随机数的下界(随机数可取该下界值)。 + /// 返回的随机数的上界(随机数不能取该上界值)。maxValue 必须大于等于 minValue。 + /// 一个大于等于 minValue 且小于 maxValue 的 32 位带符号整数,即:返回的值范围包括 minValue 但不包括 maxValue。如果 minValue 等于 maxValue,则返回 minValue。 + public static int GetRandom(int minValue, int maxValue) + { + return s_Random.Next(minValue, maxValue); + } + + /// + /// 返回一个介于 0.0 和 1.0 之间的随机数。 + /// + /// 大于等于 0.0 并且小于 1.0 的双精度浮点数。 + public static double GetRandomDouble() + { + return s_Random.NextDouble(); + } + + /// + /// 用随机数填充指定字节数组的元素。 + /// + /// 包含随机数的字节数组。 + public static void GetRandomBytes(byte[] buffer) + { + s_Random.NextBytes(buffer); + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Random.cs.meta b/Runtime/ABase/Utility/Utility.Random.cs.meta new file mode 100644 index 0000000..3a417db --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Random.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 763bd9317868044859e585d1e4fa8fdc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Utility/Utility.Reflection.cs b/Runtime/ABase/Utility/Utility.Reflection.cs new file mode 100644 index 0000000..b6e249f --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Reflection.cs @@ -0,0 +1,358 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace AlicizaX +{ + public static partial class Utility + { + /// + /// 反射相关的实用函数。 + /// + public static class Reflection + { + /// + /// 执行方法。 + /// + /// 目标对象。 + /// 方法名。 + /// 方法参数。 + /// 返回值。 + public static object InvokeMethod(object obj, string methodName, object[] parameters = null) + { + if (obj == null) + throw new NullReferenceException($"Obj is invalid !"); + if (string.IsNullOrEmpty(methodName)) + { + throw new ArgumentNullException($"MethodName is invalid !"); + } + + Type type = obj.GetType(); + if (type == null) + { + throw new ArgumentNullException($"Type is invalid !"); + } + + if (type.BaseType == null) + { + throw new ArgumentNullException($"BaseType is invalid !"); + } + + var method = type.BaseType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic); + if (method == null) + { + throw new NullReferenceException($"Type : {type} can not find method : {methodName} !"); + } + + return method.Invoke(obj, parameters); + } + + /// + /// 执行方法. + /// + /// 方法所在的type。 + /// 目标对象。 + /// 方法名。 + /// 方法参数。 + /// 返回值。 + public static object InvokeMethod(Type type, object obj, string methodName, object[] parameters = null) + { + if (type == null) + { + throw new ArgumentNullException($"Type is invalid !"); + } + + if (obj == null) + { + throw new NullReferenceException($"Obj is invalid !"); + } + + if (string.IsNullOrEmpty(methodName)) + { + throw new ArgumentNullException($"MethodName is invalid !"); + } + + var method = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic); + if (method == null) + { + throw new NullReferenceException($"Type : {type} can not find method : {methodName} !"); + } + + return method.Invoke(obj, parameters); + } + + /// + /// 设置对象属性值。 + /// + /// 目标对象。 + /// 属性名。 + /// 属性值。 + public static void SetPropertyValue(object obj, string propertyName, object newValue) + { + if (obj == null) + { + throw new NullReferenceException($"Obj is invalid !"); + } + + if (string.IsNullOrEmpty(propertyName)) + { + throw new ArgumentNullException($"PropertyName is invalid !"); + } + + Type type = obj.GetType(); + PropertyInfo prop = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic); + if (prop == null) + { + throw new NullReferenceException($"Type : {type} can not find prop: {propertyName} !"); + } + + prop.SetValue(obj, newValue, null); + } + + /// + /// 设置对象属性值。 + /// 使用此方法为对象属性进行赋值时,若Type类型赋予正确,则可赋值非public类型的属性值. + /// + /// 属性可写的类Type。 + /// 目标对象。 + /// 属性名。 + /// 属性值。 + public static void SetPropertyValue(Type type, object obj, string propertyName, object value) + { + if (type == null) + { + throw new ArgumentNullException($"Type is invalid !"); + } + + if (obj == null) + { + throw new NullReferenceException($"Obj is invalid !"); + } + + if (string.IsNullOrEmpty(propertyName)) + { + throw new ArgumentNullException($"PropertyName is invalid !"); + } + + PropertyInfo prop = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic); + if (prop == null) + { + throw new NullReferenceException($"Type : {type} can not find prop: {propertyName} !"); + } + + prop.SetValue(obj, value, null); + } + + /// + /// 设置对象字段值。 + /// + /// 字段可赋值所在类Type。 + /// 目标对象。 + /// 字段名。 + /// 字段值。 + public static void SetFieldValue(Type type, object obj, string fieldName, object value) + { + if (type == null) + { + throw new ArgumentNullException($"Type is invalid !"); + } + + if (string.IsNullOrEmpty(fieldName)) + { + throw new ArgumentNullException($"FieldName is invalid !"); + } + + var field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic); + if (field == null) + { + throw new NullReferenceException($"Type : {type} can not find field: {fieldName} !"); + } + + field.SetValue(obj, value); + } + + /// + /// 设置对象字段值。 + /// + /// 目标对象。 + /// 字段名。 + /// 字段值。 + public static void SetFieldValue(object obj, string fieldName, object value) + { + if (obj == null) + { + throw new NullReferenceException("Obj is invalid !"); + } + + if (string.IsNullOrEmpty(fieldName)) + { + throw new ArgumentNullException($"FieldName is invalid !"); + } + + Type type = obj.GetType(); + var field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic); + if (field == null) + { + throw new NullReferenceException($"Type : {type} can not find field: {fieldName} !"); + } + + field.SetValue(obj, value); + } + + /// + /// 获取对象的属性值。 + /// + /// 目标对象。 + /// 属性名。 + /// 属性值。 + public static object GetPropertyValue(object obj, string propertyName) + { + if (obj == null) + { + throw new NullReferenceException("Obj is invalid !"); + } + + if (string.IsNullOrEmpty(propertyName)) + { + throw new ArgumentNullException($"PropertyName is invalid !"); + } + + Type type = obj.GetType(); + var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic); + if (property == null) + { + throw new NullReferenceException($"Type : {type} can not find property: {propertyName} !"); + } + + return property.GetValue(obj); + } + + /// + /// 获取非实例对象属性。 + /// + /// 类型。 + /// 属性名。 + /// 属性值。 + public static object GetNonInstancePropertyValue(Type type, string propertyName) + { + var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic); + if (property == null) + { + throw new NullReferenceException($"Type : {type} can not find property: {propertyName} !"); + } + + return property.GetValue(propertyName); + } + + /// + /// 获取属性。 + /// + /// 类型。 + /// 属性名。 + /// 属性信息 + public static PropertyInfo GetProperty(Type type, string propertyName) + { + var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic); + if (property == null) + { + throw new NullReferenceException($"Type : {type} can not find property: {propertyName} !"); + } + + return property; + } + + /// + /// 获取对象字段值。 + /// + /// 目标对象。 + /// 字段名。 + /// 字段值 + public static object GetFieldValue(object obj, string fieldName) + { + if (obj == null) + { + throw new NullReferenceException("Obj is invalid !"); + } + + if (string.IsNullOrEmpty(fieldName)) + { + throw new ArgumentNullException($"FieldName is invalid !"); + } + + Type type = obj.GetType(); + var field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic); + if (field == null) + { + throw new NullReferenceException($"Type : {type} can not find field: {fieldName} !"); + } + + return field.GetValue(obj); + } + + /// + /// 获取类Type类型中的所有字段名. + /// + /// type类型。 + /// 名称数组。 + public static string[] GetTypeAllFields() + { + return GetTypeAllFields(typeof(T)); + } + + /// + /// 获取类Type类型中的所有字段名. + /// + /// type类型。 + /// 名称数组。 + public static string[] GetTypeAllFields(Type type) + { + var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static); + return fields.Select(f => f.Name).ToArray(); + } + + /// + /// 获取Type类型中所有属性字段名。 + /// + /// type类型。 + /// 名称数组。 + public static string[] GetTypeAllProperties() + { + return GetTypeAllProperties(typeof(T)); + } + + /// + /// 获取Type类型中所有属性字段名. + /// + /// type类型。 + /// 名称数组。 + public static string[] GetTypeAllProperties(Type type) + { + var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static); + return properties.Select(f => f.Name).ToArray(); + } + + /// + /// 获取Type类型中所有字段名称与字段类型的映射。 + /// + /// type类型。 + /// 名称与类型的映射。 + public static IDictionary GetTypeFieldsNameAndTypeMapping(Type type) + { + var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static); + return fields.ToDictionary(f => f.Name, t => t.FieldType); + } + + /// + /// 获取Type类型中所有属性名称与字段类型的映射。 + /// + /// type类型。 + /// 名称与类型的映射。 + public static IDictionary GetTypePropertyNameAndTypeMapping(Type type) + { + var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static); + return properties.ToDictionary(f => f.Name, t => t.PropertyType); + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Reflection.cs.meta b/Runtime/ABase/Utility/Utility.Reflection.cs.meta new file mode 100644 index 0000000..104f2ce --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Reflection.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ec7e712406294c6b85dc53b15cee821c +timeCreated: 1736410066 \ No newline at end of file diff --git a/Runtime/ABase/Utility/Utility.Text.cs b/Runtime/ABase/Utility/Utility.Text.cs new file mode 100644 index 0000000..3a1bf0d --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Text.cs @@ -0,0 +1,572 @@ +#if ZSTRING_SUPPORT +using Cysharp.Text; +#endif + +namespace AlicizaX +{ + public static partial class Utility + { + public static class Text + { + /// + /// 获取格式化字符串。 + /// + /// 字符串参数的类型。 + /// 字符串格式。 + /// 字符串参数。 + /// 格式化后的字符串。 + public static string Format(string format, T arg) + { + if (format == null) + { + throw new GameFrameworkException("Format is invalid."); + } +#if ZSTRING_SUPPORT + return ZString.Format(format, arg); +#else + return string.Format(format, arg); +#endif + } + + /// + /// 获取格式化字符串。 + /// + /// 字符串参数 1 的类型。 + /// 字符串参数 2 的类型。 + /// 字符串格式。 + /// 字符串参数 1。 + /// 字符串参数 2。 + /// 格式化后的字符串。 + public static string Format(string format, T1 arg1, T2 arg2) + { + if (format == null) + { + throw new GameFrameworkException("Format is invalid."); + } +#if ZSTRING_SUPPORT + return ZString.Format(format, arg1, arg2); +#else + return string.Format(format, arg1, arg2); +#endif + } + + /// + /// 获取格式化字符串。 + /// + /// 字符串参数 1 的类型。 + /// 字符串参数 2 的类型。 + /// 字符串参数 3 的类型。 + /// 字符串格式。 + /// 字符串参数 1。 + /// 字符串参数 2。 + /// 字符串参数 3。 + /// 格式化后的字符串。 + public static string Format(string format, T1 arg1, T2 arg2, T3 arg3) + { + if (format == null) + { + throw new GameFrameworkException("Format is invalid."); + } +#if ZSTRING_SUPPORT + return ZString.Format(format, arg1, arg2, arg3); +#else + return string.Format(format, arg1, arg2, arg3); +#endif + } + + /// + /// 获取格式化字符串。 + /// + /// 字符串参数 1 的类型。 + /// 字符串参数 2 的类型。 + /// 字符串参数 3 的类型。 + /// 字符串参数 4 的类型。 + /// 字符串格式。 + /// 字符串参数 1。 + /// 字符串参数 2。 + /// 字符串参数 3。 + /// 字符串参数 4。 + /// 格式化后的字符串。 + public static string Format(string format, T1 arg1, T2 arg2, T3 arg3, T4 arg4) + { + if (format == null) + { + throw new GameFrameworkException("Format is invalid."); + } +#if ZSTRING_SUPPORT + return ZString.Format(format, arg1, arg2, arg3, arg4); +#else + return string.Format(format, arg1, arg2, arg3, arg4); +#endif + } + + /// + /// 获取格式化字符串。 + /// + /// 字符串参数 1 的类型。 + /// 字符串参数 2 的类型。 + /// 字符串参数 3 的类型。 + /// 字符串参数 4 的类型。 + /// 字符串参数 5 的类型。 + /// 字符串格式。 + /// 字符串参数 1。 + /// 字符串参数 2。 + /// 字符串参数 3。 + /// 字符串参数 4。 + /// 字符串参数 5。 + /// 格式化后的字符串。 + public static string Format(string format, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) + { + if (format == null) + { + throw new GameFrameworkException("Format is invalid."); + } +#if ZSTRING_SUPPORT + return ZString.Format(format, arg1, arg2, arg3, arg4, arg5); +#else + return string.Format(format, arg1, arg2, arg3, arg4, arg5); +#endif + } + + /// + /// 获取格式化字符串。 + /// + /// 字符串参数 1 的类型。 + /// 字符串参数 2 的类型。 + /// 字符串参数 3 的类型。 + /// 字符串参数 4 的类型。 + /// 字符串参数 5 的类型。 + /// 字符串参数 6 的类型。 + /// 字符串格式。 + /// 字符串参数 1。 + /// 字符串参数 2。 + /// 字符串参数 3。 + /// 字符串参数 4。 + /// 字符串参数 5。 + /// 字符串参数 6。 + /// 格式化后的字符串。 + public static string Format(string format, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) + { + if (format == null) + { + throw new GameFrameworkException("Format is invalid."); + } +#if ZSTRING_SUPPORT + return ZString.Format(format, arg1, arg2, arg3, arg4, arg5, arg6); +#else + return string.Format(format, arg1, arg2, arg3, arg4, arg5, arg6); +#endif + } + + /// + /// 获取格式化字符串。 + /// + /// 字符串参数 1 的类型。 + /// 字符串参数 2 的类型。 + /// 字符串参数 3 的类型。 + /// 字符串参数 4 的类型。 + /// 字符串参数 5 的类型。 + /// 字符串参数 6 的类型。 + /// 字符串参数 7 的类型。 + /// 字符串格式。 + /// 字符串参数 1。 + /// 字符串参数 2。 + /// 字符串参数 3。 + /// 字符串参数 4。 + /// 字符串参数 5。 + /// 字符串参数 6。 + /// 字符串参数 7。 + /// 格式化后的字符串。 + public static string Format(string format, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) + { + if (format == null) + { + throw new GameFrameworkException("Format is invalid."); + } +#if ZSTRING_SUPPORT + return ZString.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7); +#else + return string.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7); +#endif + } + + /// + /// 获取格式化字符串。 + /// + /// 字符串参数 1 的类型。 + /// 字符串参数 2 的类型。 + /// 字符串参数 3 的类型。 + /// 字符串参数 4 的类型。 + /// 字符串参数 5 的类型。 + /// 字符串参数 6 的类型。 + /// 字符串参数 7 的类型。 + /// 字符串参数 8 的类型。 + /// 字符串格式。 + /// 字符串参数 1。 + /// 字符串参数 2。 + /// 字符串参数 3。 + /// 字符串参数 4。 + /// 字符串参数 5。 + /// 字符串参数 6。 + /// 字符串参数 7。 + /// 字符串参数 8。 + /// 格式化后的字符串。 + public static string Format(string format, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) + { + if (format == null) + { + throw new GameFrameworkException("Format is invalid."); + } +#if ZSTRING_SUPPORT + return ZString.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); +#else + return string.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); +#endif + } + + /// + /// 获取格式化字符串。 + /// + /// 字符串参数 1 的类型。 + /// 字符串参数 2 的类型。 + /// 字符串参数 3 的类型。 + /// 字符串参数 4 的类型。 + /// 字符串参数 5 的类型。 + /// 字符串参数 6 的类型。 + /// 字符串参数 7 的类型。 + /// 字符串参数 8 的类型。 + /// 字符串参数 9 的类型。 + /// 字符串格式。 + /// 字符串参数 1。 + /// 字符串参数 2。 + /// 字符串参数 3。 + /// 字符串参数 4。 + /// 字符串参数 5。 + /// 字符串参数 6。 + /// 字符串参数 7。 + /// 字符串参数 8。 + /// 字符串参数 9。 + /// 格式化后的字符串。 + public static string Format(string format, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9) + { + if (format == null) + { + throw new GameFrameworkException("Format is invalid."); + } +#if ZSTRING_SUPPORT + return ZString.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); +#else + return string.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); +#endif + } + + /// + /// 获取格式化字符串。 + /// + /// 字符串参数 1 的类型。 + /// 字符串参数 2 的类型。 + /// 字符串参数 3 的类型。 + /// 字符串参数 4 的类型。 + /// 字符串参数 5 的类型。 + /// 字符串参数 6 的类型。 + /// 字符串参数 7 的类型。 + /// 字符串参数 8 的类型。 + /// 字符串参数 9 的类型。 + /// 字符串参数 10 的类型。 + /// 字符串格式。 + /// 字符串参数 1。 + /// 字符串参数 2。 + /// 字符串参数 3。 + /// 字符串参数 4。 + /// 字符串参数 5。 + /// 字符串参数 6。 + /// 字符串参数 7。 + /// 字符串参数 8。 + /// 字符串参数 9。 + /// 字符串参数 10。 + /// 格式化后的字符串。 + public static string Format(string format, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10) + { + if (format == null) + { + throw new GameFrameworkException("Format is invalid."); + } +#if ZSTRING_SUPPORT + return ZString.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); +#else + return string.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); +#endif + } + + /// + /// 获取格式化字符串。 + /// + /// 字符串参数 1 的类型。 + /// 字符串参数 2 的类型。 + /// 字符串参数 3 的类型。 + /// 字符串参数 4 的类型。 + /// 字符串参数 5 的类型。 + /// 字符串参数 6 的类型。 + /// 字符串参数 7 的类型。 + /// 字符串参数 8 的类型。 + /// 字符串参数 9 的类型。 + /// 字符串参数 10 的类型。 + /// 字符串参数 11 的类型。 + /// 字符串格式。 + /// 字符串参数 1。 + /// 字符串参数 2。 + /// 字符串参数 3。 + /// 字符串参数 4。 + /// 字符串参数 5。 + /// 字符串参数 6。 + /// 字符串参数 7。 + /// 字符串参数 8。 + /// 字符串参数 9。 + /// 字符串参数 10。 + /// 字符串参数 11。 + /// 格式化后的字符串。 + public static string Format(string format, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11) + { + if (format == null) + { + throw new GameFrameworkException("Format is invalid."); + } +#if ZSTRING_SUPPORT + return ZString.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11); +#else + return string.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11); +#endif + } + + /// + /// 获取格式化字符串。 + /// + /// 字符串参数 1 的类型。 + /// 字符串参数 2 的类型。 + /// 字符串参数 3 的类型。 + /// 字符串参数 4 的类型。 + /// 字符串参数 5 的类型。 + /// 字符串参数 6 的类型。 + /// 字符串参数 7 的类型。 + /// 字符串参数 8 的类型。 + /// 字符串参数 9 的类型。 + /// 字符串参数 10 的类型。 + /// 字符串参数 11 的类型。 + /// 字符串参数 12 的类型。 + /// 字符串格式。 + /// 字符串参数 1。 + /// 字符串参数 2。 + /// 字符串参数 3。 + /// 字符串参数 4。 + /// 字符串参数 5。 + /// 字符串参数 6。 + /// 字符串参数 7。 + /// 字符串参数 8。 + /// 字符串参数 9。 + /// 字符串参数 10。 + /// 字符串参数 11。 + /// 字符串参数 12。 + /// 格式化后的字符串。 + public static string Format(string format, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12) + { + if (format == null) + { + throw new GameFrameworkException("Format is invalid."); + } +#if ZSTRING_SUPPORT + return ZString.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12); +#else + return string.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12); +#endif + } + + /// + /// 获取格式化字符串。 + /// + /// 字符串参数 1 的类型。 + /// 字符串参数 2 的类型。 + /// 字符串参数 3 的类型。 + /// 字符串参数 4 的类型。 + /// 字符串参数 5 的类型。 + /// 字符串参数 6 的类型。 + /// 字符串参数 7 的类型。 + /// 字符串参数 8 的类型。 + /// 字符串参数 9 的类型。 + /// 字符串参数 10 的类型。 + /// 字符串参数 11 的类型。 + /// 字符串参数 12 的类型。 + /// 字符串参数 13 的类型。 + /// 字符串格式。 + /// 字符串参数 1。 + /// 字符串参数 2。 + /// 字符串参数 3。 + /// 字符串参数 4。 + /// 字符串参数 5。 + /// 字符串参数 6。 + /// 字符串参数 7。 + /// 字符串参数 8。 + /// 字符串参数 9。 + /// 字符串参数 10。 + /// 字符串参数 11。 + /// 字符串参数 12。 + /// 字符串参数 13。 + /// 格式化后的字符串。 + public static string Format(string format, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13) + { + if (format == null) + { + throw new GameFrameworkException("Format is invalid."); + } +#if ZSTRING_SUPPORT + return ZString.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13); +#else + return string.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13); +#endif + } + + /// + /// 获取格式化字符串。 + /// + /// 字符串参数 1 的类型。 + /// 字符串参数 2 的类型。 + /// 字符串参数 3 的类型。 + /// 字符串参数 4 的类型。 + /// 字符串参数 5 的类型。 + /// 字符串参数 6 的类型。 + /// 字符串参数 7 的类型。 + /// 字符串参数 8 的类型。 + /// 字符串参数 9 的类型。 + /// 字符串参数 10 的类型。 + /// 字符串参数 11 的类型。 + /// 字符串参数 12 的类型。 + /// 字符串参数 13 的类型。 + /// 字符串参数 14 的类型。 + /// 字符串格式。 + /// 字符串参数 1。 + /// 字符串参数 2。 + /// 字符串参数 3。 + /// 字符串参数 4。 + /// 字符串参数 5。 + /// 字符串参数 6。 + /// 字符串参数 7。 + /// 字符串参数 8。 + /// 字符串参数 9。 + /// 字符串参数 10。 + /// 字符串参数 11。 + /// 字符串参数 12。 + /// 字符串参数 13。 + /// 字符串参数 14。 + /// 格式化后的字符串。 + public static string Format(string format, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14) + { + if (format == null) + { + throw new GameFrameworkException("Format is invalid."); + } +#if ZSTRING_SUPPORT + return ZString.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); +#else + return string.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); +#endif + } + + /// + /// 获取格式化字符串。 + /// + /// 字符串参数 1 的类型。 + /// 字符串参数 2 的类型。 + /// 字符串参数 3 的类型。 + /// 字符串参数 4 的类型。 + /// 字符串参数 5 的类型。 + /// 字符串参数 6 的类型。 + /// 字符串参数 7 的类型。 + /// 字符串参数 8 的类型。 + /// 字符串参数 9 的类型。 + /// 字符串参数 10 的类型。 + /// 字符串参数 11 的类型。 + /// 字符串参数 12 的类型。 + /// 字符串参数 13 的类型。 + /// 字符串参数 14 的类型。 + /// 字符串参数 15 的类型。 + /// 字符串格式。 + /// 字符串参数 1。 + /// 字符串参数 2。 + /// 字符串参数 3。 + /// 字符串参数 4。 + /// 字符串参数 5。 + /// 字符串参数 6。 + /// 字符串参数 7。 + /// 字符串参数 8。 + /// 字符串参数 9。 + /// 字符串参数 10。 + /// 字符串参数 11。 + /// 字符串参数 12。 + /// 字符串参数 13。 + /// 字符串参数 14。 + /// 字符串参数 15。 + /// 格式化后的字符串。 + public static string Format(string format, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15) + { + if (format == null) + { + throw new GameFrameworkException("Format is invalid."); + } +#if ZSTRING_SUPPORT + return ZString.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15); +#else + return string.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15); +#endif + } + + /// + /// 获取格式化字符串。 + /// + /// 字符串参数 1 的类型。 + /// 字符串参数 2 的类型。 + /// 字符串参数 3 的类型。 + /// 字符串参数 4 的类型。 + /// 字符串参数 5 的类型。 + /// 字符串参数 6 的类型。 + /// 字符串参数 7 的类型。 + /// 字符串参数 8 的类型。 + /// 字符串参数 9 的类型。 + /// 字符串参数 10 的类型。 + /// 字符串参数 11 的类型。 + /// 字符串参数 12 的类型。 + /// 字符串参数 13 的类型。 + /// 字符串参数 14 的类型。 + /// 字符串参数 15 的类型。 + /// 字符串参数 16 的类型。 + /// 字符串格式。 + /// 字符串参数 1。 + /// 字符串参数 2。 + /// 字符串参数 3。 + /// 字符串参数 4。 + /// 字符串参数 5。 + /// 字符串参数 6。 + /// 字符串参数 7。 + /// 字符串参数 8。 + /// 字符串参数 9。 + /// 字符串参数 10。 + /// 字符串参数 11。 + /// 字符串参数 12。 + /// 字符串参数 13。 + /// 字符串参数 14。 + /// 字符串参数 15。 + /// 字符串参数 16。 + /// 格式化后的字符串。 + public static string Format(string format, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16) + { + if (format == null) + { + throw new GameFrameworkException("Format is invalid."); + } +#if ZSTRING_SUPPORT + return ZString.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16); +#else + return string.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16); +#endif + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Text.cs.meta b/Runtime/ABase/Utility/Utility.Text.cs.meta new file mode 100644 index 0000000..1d78ec5 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Text.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c8725acd4aac493787749762fe7247ee +timeCreated: 1736410066 \ No newline at end of file diff --git a/Runtime/ABase/Utility/Utility.Unity.cs b/Runtime/ABase/Utility/Utility.Unity.cs new file mode 100644 index 0000000..5de3720 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Unity.cs @@ -0,0 +1,444 @@ +using System; +using System.Collections; +using Cysharp.Threading.Tasks; +using UnityEngine; +using UnityEngine.Events; +using UnityEngine.Internal; +using Object = UnityEngine.Object; + +namespace AlicizaX +{ + public static partial class Utility + { + /// + /// Unity相关的实用函数。 + /// + public static partial class Unity + { + private static GameObject _entity; + private static MainBehaviour _behaviour; + + #region 控制协程Coroutine + + public static Coroutine StartCoroutine(string methodName) + { + if (string.IsNullOrEmpty(methodName)) + { + return null; + } + return _behaviour.StartCoroutine(methodName); + } + + public static Coroutine StartCoroutine(IEnumerator routine) + { + if (routine == null) + { + return null; + } + return _behaviour.StartCoroutine(routine); + } + + public static Coroutine StartCoroutine(string methodName, [DefaultValue("null")] object value) + { + if (string.IsNullOrEmpty(methodName)) + { + return null; + } + return _behaviour.StartCoroutine(methodName, value); + } + + public static void StopCoroutine(string methodName) + { + if (string.IsNullOrEmpty(methodName)) + { + return; + } + + if (_entity != null) + { + _behaviour.StopCoroutine(methodName); + } + } + + public static void StopCoroutine(IEnumerator routine) + { + if (routine == null) + { + return; + } + + if (_entity != null) + { + _behaviour.StopCoroutine(routine); + } + } + + public static void StopCoroutine(Coroutine routine) + { + if (routine == null) + return; + + if (_entity != null) + { + _behaviour.StopCoroutine(routine); + routine = null; + } + } + + public static void StopAllCoroutines() + { + if (_entity != null) + { + _behaviour.StopAllCoroutines(); + } + } + + #endregion + + #region 注入UnityUpdate/FixedUpdate/LateUpdate + + /// + /// 为给外部提供的 添加帧更新事件。 + /// + /// + public static void AddUpdateListener(UnityAction fun) + { + AddUpdateListenerImp(fun).Forget(); + } + + private static async UniTaskVoid AddUpdateListenerImp(UnityAction fun) + { + await UniTask.Yield(PlayerLoopTiming.LastPreUpdate); + _behaviour.AddUpdateListener(fun); + } + + /// + /// 为给外部提供的 添加物理帧更新事件。 + /// + /// + public static void AddFixedUpdateListener(UnityAction fun) + { + AddFixedUpdateListenerImp(fun).Forget(); + } + + private static async UniTaskVoid AddFixedUpdateListenerImp(UnityAction fun) + { + await UniTask.Yield(PlayerLoopTiming.LastEarlyUpdate); + _behaviour.AddFixedUpdateListener(fun); + } + + /// + /// 为给外部提供的 添加Late帧更新事件。 + /// + /// + public static void AddLateUpdateListener(UnityAction fun) + { + AddLateUpdateListenerImp(fun).Forget(); + } + + private static async UniTaskVoid AddLateUpdateListenerImp(UnityAction fun) + { + await UniTask.Yield(PlayerLoopTiming.LastPreLateUpdate); + _behaviour.AddLateUpdateListener(fun); + } + + /// + /// 移除帧更新事件。 + /// + /// + public static void RemoveUpdateListener(UnityAction fun) + { + _behaviour.RemoveUpdateListener(fun); + } + + /// + /// 移除物理帧更新事件。 + /// + /// + public static void RemoveFixedUpdateListener(UnityAction fun) + { + _behaviour.RemoveFixedUpdateListener(fun); + } + + /// + /// 移除Late帧更新事件。 + /// + /// + public static void RemoveLateUpdateListener(UnityAction fun) + { + _behaviour.RemoveLateUpdateListener(fun); + } + + #endregion + + #region Unity Events 注入 + /// + /// 为给外部提供的Destroy注册事件。 + /// + /// + public static void AddDestroyListener(UnityAction fun) + { + _behaviour.AddDestroyListener(fun); + } + + /// + /// 为给外部提供的Destroy反注册事件。 + /// + /// + public static void RemoveDestroyListener(UnityAction fun) + { + _behaviour.RemoveDestroyListener(fun); + } + + /// + /// 为给外部提供的OnDrawGizmos注册事件。 + /// + /// + public static void AddOnDrawGizmosListener(UnityAction fun) + { + _behaviour.AddOnDrawGizmosListener(fun); + } + + /// + /// 为给外部提供的OnDrawGizmos反注册事件。 + /// + /// + public static void RemoveOnDrawGizmosListener(UnityAction fun) + { + _behaviour.RemoveOnDrawGizmosListener(fun); + } + + /// + /// 为给外部提供的OnApplicationPause注册事件。 + /// + /// + public static void AddOnApplicationPauseListener(UnityAction fun) + { + _behaviour.AddOnApplicationPauseListener(fun); + } + + /// + /// 为给外部提供的OnApplicationPause反注册事件。 + /// + /// + public static void RemoveOnApplicationPauseListener(UnityAction fun) + { + _behaviour.RemoveOnApplicationPauseListener(fun); + } + + /// + /// 为给外部提供的OnApplicationQuit注册事件。 + /// + /// + public static void AddOnApplicationQuitListener(UnityAction fun) + { + _behaviour.AddOnApplicationQuitListener(fun); + } + + /// + /// 为给外部提供的OnApplicationQuit反注册事件。 + /// + /// + public static void RemoveOnApplicationQuitListener(UnityAction fun) + { + _behaviour.RemoveOnApplicationQuitListener(fun); + } + #endregion + + public static void MakeEntity(Transform parent) + { + if (_entity != null) + { + return; + } + + _entity = new GameObject("[Unity.Utility]"); + _entity.SetActive(true); + _entity.transform.SetParent(parent); + _behaviour = _entity.AddComponent(); + } + + /// + /// 释放Behaviour生命周期。 + /// + public static void Shutdown() + { + + if (_behaviour != null) + { + _behaviour.Dispose(); + _behaviour.Shutdown(); + } + + if (_entity != null) + { + UnityEngine.Object.Destroy(_entity); + } + + + _entity = null; + } + + private class MainBehaviour : MonoBehaviour + { + private event UnityAction UpdateEvent; + private event UnityAction FixedUpdateEvent; + private event UnityAction LateUpdateEvent; + private event UnityAction DestroyEvent; + private event UnityAction OnDrawGizmosEvent; + private event UnityAction OnApplicationPauseEvent; + private event UnityAction OnApplicationQuitEvent; + + void Update() + { + if (UpdateEvent != null) + { + UpdateEvent(); + } + } + + void FixedUpdate() + { + if (FixedUpdateEvent != null) + { + FixedUpdateEvent(); + } + } + + void LateUpdate() + { + if (LateUpdateEvent != null) + { + LateUpdateEvent(); + } + } + + private void OnDestroy() + { + if (DestroyEvent != null) + { + DestroyEvent(); + } + } + + private void OnDrawGizmos() + { + if (OnDrawGizmosEvent != null) + { + OnDrawGizmosEvent(); + } + } + + private void OnApplicationPause(bool pauseStatus) + { + if (OnApplicationPauseEvent != null) + { + OnApplicationPauseEvent(pauseStatus); + } + } + + private void OnApplicationQuit() + { + if (OnApplicationQuitEvent != null) + { + OnApplicationQuitEvent(); + } + } + + public void AddLateUpdateListener(UnityAction fun) + { + LateUpdateEvent += fun; + } + + public void RemoveLateUpdateListener(UnityAction fun) + { + LateUpdateEvent -= fun; + } + + public void AddFixedUpdateListener(UnityAction fun) + { + FixedUpdateEvent += fun; + } + + public void RemoveFixedUpdateListener(UnityAction fun) + { + FixedUpdateEvent -= fun; + } + + public void AddUpdateListener(UnityAction fun) + { + UpdateEvent += fun; + } + + public void RemoveUpdateListener(UnityAction fun) + { + UpdateEvent -= fun; + } + + public void AddDestroyListener(UnityAction fun) + { + DestroyEvent += fun; + } + + public void RemoveDestroyListener(UnityAction fun) + { + DestroyEvent -= fun; + } + + public void AddOnDrawGizmosListener(UnityAction fun) + { + OnDrawGizmosEvent += fun; + } + + public void RemoveOnDrawGizmosListener(UnityAction fun) + { + OnDrawGizmosEvent -= fun; + } + + public void AddOnApplicationPauseListener(UnityAction fun) + { + OnApplicationPauseEvent += fun; + } + + public void RemoveOnApplicationPauseListener(UnityAction fun) + { + OnApplicationPauseEvent -= fun; + } + + public void AddOnApplicationQuitListener(UnityAction fun) + { + OnApplicationQuitEvent += fun; + } + + public void RemoveOnApplicationQuitListener(UnityAction fun) + { + OnApplicationQuitEvent -= fun; + } + + + public void Shutdown() + { + UpdateEvent = null; + FixedUpdateEvent = null; + LateUpdateEvent = null; + OnDrawGizmosEvent = null; + DestroyEvent = null; + OnApplicationPauseEvent = null; + OnApplicationQuitEvent = null; + } + + public void Dispose() + { + if (OnApplicationQuitEvent != null) + { + OnApplicationQuitEvent(); + } + if (DestroyEvent != null) + { + DestroyEvent(); + } + } + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Unity.cs.meta b/Runtime/ABase/Utility/Utility.Unity.cs.meta new file mode 100644 index 0000000..6d0b57b --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Unity.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4c43e783b23c42a186ee4de2f8d107c9 +timeCreated: 1736410066 \ No newline at end of file diff --git a/Runtime/ABase/Utility/Utility.Verifier.Crc32.cs b/Runtime/ABase/Utility/Utility.Verifier.Crc32.cs new file mode 100644 index 0000000..f527d5c --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Verifier.Crc32.cs @@ -0,0 +1,87 @@ +namespace AlicizaX +{ + public static partial class Utility + { + public static partial class Verifier + { + /// + /// CRC32 算法。 + /// + private sealed class Crc32 + { + private const int TableLength = 256; + private const uint DefaultPolynomial = 0xedb88320; + private const uint DefaultSeed = 0xffffffff; + + private readonly uint m_Seed; + private readonly uint[] m_Table; + private uint m_Hash; + + public Crc32() + : this(DefaultPolynomial, DefaultSeed) + { + } + + public Crc32(uint polynomial, uint seed) + { + m_Seed = seed; + m_Table = InitializeTable(polynomial); + m_Hash = seed; + } + + public void Initialize() + { + m_Hash = m_Seed; + } + + public void HashCore(byte[] bytes, int offset, int length) + { + m_Hash = CalculateHash(m_Table, m_Hash, bytes, offset, length); + } + + public uint HashFinal() + { + return ~m_Hash; + } + + private static uint CalculateHash(uint[] table, uint value, byte[] bytes, int offset, int length) + { + int last = offset + length; + for (int i = offset; i < last; i++) + { + unchecked + { + value = (value >> 8) ^ table[bytes[i] ^ value & 0xff]; + } + } + + return value; + } + + private static uint[] InitializeTable(uint polynomial) + { + uint[] table = new uint[TableLength]; + for (int i = 0; i < TableLength; i++) + { + uint entry = (uint)i; + for (int j = 0; j < 8; j++) + { + if ((entry & 1) == 1) + { + entry = (entry >> 1) ^ polynomial; + } + else + { + entry >>= 1; + } + } + + table[i] = entry; + } + + return table; + } + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Verifier.Crc32.cs.meta b/Runtime/ABase/Utility/Utility.Verifier.Crc32.cs.meta new file mode 100644 index 0000000..48843b8 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Verifier.Crc32.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 515ae20dacbff49af9cb524fb062718a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Utility/Utility.Verifier.Crc64.cs b/Runtime/ABase/Utility/Utility.Verifier.Crc64.cs new file mode 100644 index 0000000..58a3901 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Verifier.Crc64.cs @@ -0,0 +1,589 @@ +using System; +using System.Buffers; +using System.Buffers.Binary; +using System.ComponentModel; +using System.IO; +using System.Threading; +using UnityEngine; + +namespace AlicizaX +{ + public static partial class Utility + { + public static partial class Verifier + { + /// + /// Provides an implementation of the CRC-64 algorithm as described in ECMA-182, Annex B. + /// + /// + /// + /// For methods that return byte arrays or that write into spans of bytes, + /// this implementation emits the answer in the Big Endian byte order so that + /// the CRC residue relationship (CRC(message concat CRC(message))) is a fixed value) holds. + /// For CRC-64 this stable output is the byte sequence + /// { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }. + /// + /// + /// There are multiple, incompatible, definitions of a 64-bit cyclic redundancy + /// check (CRC) algorithm. When interoperating with another system, ensure that you + /// are using the same definition. The definition used by this implementation is not + /// compatible with the cyclic redundancy check described in ISO 3309. + /// + /// + public sealed partial class Crc64 : NonCryptographicHashAlgorithm + { + private const ulong InitialState = 0UL; + private const int Size = sizeof(ulong); + + private ulong _crc = InitialState; + + /// + /// Initializes a new instance of the class. + /// + public Crc64() + : base(Size) + { + } + + /// + /// Appends the contents of to the data already + /// processed for the current hash computation. + /// + /// The data to process. + public override void Append(ReadOnlySpan source) + { + _crc = Update(_crc, source); + } + + /// + /// Resets the hash computation to the initial state. + /// + public override void Reset() + { + _crc = InitialState; + } + + /// + /// Writes the computed hash value to + /// without modifying accumulated state. + /// + /// The buffer that receives the computed hash value. + protected override void GetCurrentHashCore(Span destination) + { + BinaryPrimitives.WriteUInt64BigEndian(destination, _crc); + } + + /// + /// Writes the computed hash value to + /// then clears the accumulated state. + /// + protected override void GetHashAndResetCore(Span destination) + { + BinaryPrimitives.WriteUInt64BigEndian(destination, _crc); + _crc = InitialState; + } + + /// + /// Gets the current computed hash value without modifying accumulated state. + /// + /// The hash value for the data already provided. + public ulong GetCurrentHashAsUInt64() => _crc; + + /// + /// Computes the CRC-64 hash of the provided data. + /// + /// The data to hash. + /// The CRC-64 hash of the provided data. + /// + /// is . + /// + public static byte[] Hash(byte[] source) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + return Hash(new ReadOnlySpan(source)); + } + + /// + /// Computes the CRC-64 hash of the provided data. + /// + /// The data to hash. + /// The CRC-64 hash of the provided data. + public static byte[] Hash(ReadOnlySpan source) + { + byte[] ret = new byte[Size]; + ulong hash = HashToUInt64(source); + BinaryPrimitives.WriteUInt64BigEndian(ret, hash); + return ret; + } + + /// + /// Attempts to compute the CRC-64 hash of the provided data into the provided destination. + /// + /// The data to hash. + /// The buffer that receives the computed hash value. + /// + /// On success, receives the number of bytes written to . + /// + /// + /// if is long enough to receive + /// the computed hash value (8 bytes); otherwise, . + /// + public static bool TryHash(ReadOnlySpan source, Span destination, out int bytesWritten) + { + if (destination.Length < Size) + { + bytesWritten = 0; + return false; + } + + ulong hash = HashToUInt64(source); + BinaryPrimitives.WriteUInt64BigEndian(destination, hash); + bytesWritten = Size; + return true; + } + + /// + /// Computes the CRC-64 hash of the provided data into the provided destination. + /// + /// The data to hash. + /// The buffer that receives the computed hash value. + /// + /// The number of bytes written to . + /// + public static int Hash(ReadOnlySpan source, Span destination) + { + if (destination.Length < Size) + { + ThrowDestinationTooShort(); + } + + ulong hash = HashToUInt64(source); + BinaryPrimitives.WriteUInt64BigEndian(destination, hash); + return Size; + } + + /// Computes the CRC-64 hash of the provided data. + /// The data to hash. + /// The computed CRC-64 hash. + public static ulong HashToUInt64(ReadOnlySpan source) => + Update(InitialState, source); + + private static ulong Update(ulong crc, ReadOnlySpan source) + { + ReadOnlySpan crcLookup = CrcLookup; + for (int i = 0; i < source.Length; i++) + { + ulong idx = (crc >> 56); + idx ^= source[i]; + crc = crcLookup[(int) idx] ^ (crc << 8); + } + + return crc; + } + + /// CRC-64 transition table. + private static ReadOnlySpan CrcLookup => new ulong[256] + { + // Generated by GenerateTable(0x42F0E1EBA9EA3693): + // + // static ulong[] GenerateTable(ulong polynomial) + // { + // var table = new ulong[256]; + // for (int i = 0; i < table.Length; i++) + // { + // ulong val = (ulong)i << 56; + // for (int j = 0; j < 8; j++) + // { + // if ((val & 0x8000_0000_0000_0000) == 0) + // { + // val <<= 1; + // } + // else + // { + // val = (val << 1) ^ polynomial; + // } + // } + // table[i] = val; + // } + // return table; + // } + + 0x0, 0x42F0E1EBA9EA3693, 0x85E1C3D753D46D26, 0xC711223CFA3E5BB5, 0x493366450E42ECDF, 0xBC387AEA7A8DA4C, 0xCCD2A5925D9681F9, 0x8E224479F47CB76A, + 0x9266CC8A1C85D9BE, 0xD0962D61B56FEF2D, 0x17870F5D4F51B498, 0x5577EEB6E6BB820B, 0xDB55AACF12C73561, 0x99A54B24BB2D03F2, 0x5EB4691841135847, 0x1C4488F3E8F96ED4, + 0x663D78FF90E185EF, 0x24CD9914390BB37C, 0xE3DCBB28C335E8C9, 0xA12C5AC36ADFDE5A, 0x2F0E1EBA9EA36930, 0x6DFEFF5137495FA3, 0xAAEFDD6DCD770416, 0xE81F3C86649D3285, + 0xF45BB4758C645C51, 0xB6AB559E258E6AC2, 0x71BA77A2DFB03177, 0x334A9649765A07E4, 0xBD68D2308226B08E, 0xFF9833DB2BCC861D, 0x388911E7D1F2DDA8, 0x7A79F00C7818EB3B, + 0xCC7AF1FF21C30BDE, 0x8E8A101488293D4D, 0x499B3228721766F8, 0xB6BD3C3DBFD506B, 0x854997BA2F81E701, 0xC7B97651866BD192, 0xA8546D7C558A27, 0x4258B586D5BFBCB4, + 0x5E1C3D753D46D260, 0x1CECDC9E94ACE4F3, 0xDBFDFEA26E92BF46, 0x990D1F49C77889D5, 0x172F5B3033043EBF, 0x55DFBADB9AEE082C, 0x92CE98E760D05399, 0xD03E790CC93A650A, + 0xAA478900B1228E31, 0xE8B768EB18C8B8A2, 0x2FA64AD7E2F6E317, 0x6D56AB3C4B1CD584, 0xE374EF45BF6062EE, 0xA1840EAE168A547D, 0x66952C92ECB40FC8, 0x2465CD79455E395B, + 0x3821458AADA7578F, 0x7AD1A461044D611C, 0xBDC0865DFE733AA9, 0xFF3067B657990C3A, 0x711223CFA3E5BB50, 0x33E2C2240A0F8DC3, 0xF4F3E018F031D676, 0xB60301F359DBE0E5, + 0xDA050215EA6C212F, 0x98F5E3FE438617BC, 0x5FE4C1C2B9B84C09, 0x1D14202910527A9A, 0x93366450E42ECDF0, 0xD1C685BB4DC4FB63, 0x16D7A787B7FAA0D6, 0x5427466C1E109645, + 0x4863CE9FF6E9F891, 0xA932F745F03CE02, 0xCD820D48A53D95B7, 0x8F72ECA30CD7A324, 0x150A8DAF8AB144E, 0x43A04931514122DD, 0x84B16B0DAB7F7968, 0xC6418AE602954FFB, + 0xBC387AEA7A8DA4C0, 0xFEC89B01D3679253, 0x39D9B93D2959C9E6, 0x7B2958D680B3FF75, 0xF50B1CAF74CF481F, 0xB7FBFD44DD257E8C, 0x70EADF78271B2539, 0x321A3E938EF113AA, + 0x2E5EB66066087D7E, 0x6CAE578BCFE24BED, 0xABBF75B735DC1058, 0xE94F945C9C3626CB, 0x676DD025684A91A1, 0x259D31CEC1A0A732, 0xE28C13F23B9EFC87, 0xA07CF2199274CA14, + 0x167FF3EACBAF2AF1, 0x548F120162451C62, 0x939E303D987B47D7, 0xD16ED1D631917144, 0x5F4C95AFC5EDC62E, 0x1DBC74446C07F0BD, 0xDAAD56789639AB08, 0x985DB7933FD39D9B, + 0x84193F60D72AF34F, 0xC6E9DE8B7EC0C5DC, 0x1F8FCB784FE9E69, 0x43081D5C2D14A8FA, 0xCD2A5925D9681F90, 0x8FDAB8CE70822903, 0x48CB9AF28ABC72B6, 0xA3B7B1923564425, + 0x70428B155B4EAF1E, 0x32B26AFEF2A4998D, 0xF5A348C2089AC238, 0xB753A929A170F4AB, 0x3971ED50550C43C1, 0x7B810CBBFCE67552, 0xBC902E8706D82EE7, 0xFE60CF6CAF321874, + 0xE224479F47CB76A0, 0xA0D4A674EE214033, 0x67C58448141F1B86, 0x253565A3BDF52D15, 0xAB1721DA49899A7F, 0xE9E7C031E063ACEC, 0x2EF6E20D1A5DF759, 0x6C0603E6B3B7C1CA, + 0xF6FAE5C07D3274CD, 0xB40A042BD4D8425E, 0x731B26172EE619EB, 0x31EBC7FC870C2F78, 0xBFC9838573709812, 0xFD39626EDA9AAE81, 0x3A28405220A4F534, 0x78D8A1B9894EC3A7, + 0x649C294A61B7AD73, 0x266CC8A1C85D9BE0, 0xE17DEA9D3263C055, 0xA38D0B769B89F6C6, 0x2DAF4F0F6FF541AC, 0x6F5FAEE4C61F773F, 0xA84E8CD83C212C8A, 0xEABE6D3395CB1A19, + 0x90C79D3FEDD3F122, 0xD2377CD44439C7B1, 0x15265EE8BE079C04, 0x57D6BF0317EDAA97, 0xD9F4FB7AE3911DFD, 0x9B041A914A7B2B6E, 0x5C1538ADB04570DB, 0x1EE5D94619AF4648, + 0x2A151B5F156289C, 0x4051B05E58BC1E0F, 0x87409262A28245BA, 0xC5B073890B687329, 0x4B9237F0FF14C443, 0x962D61B56FEF2D0, 0xCE73F427ACC0A965, 0x8C8315CC052A9FF6, + 0x3A80143F5CF17F13, 0x7870F5D4F51B4980, 0xBF61D7E80F251235, 0xFD913603A6CF24A6, 0x73B3727A52B393CC, 0x31439391FB59A55F, 0xF652B1AD0167FEEA, 0xB4A25046A88DC879, + 0xA8E6D8B54074A6AD, 0xEA16395EE99E903E, 0x2D071B6213A0CB8B, 0x6FF7FA89BA4AFD18, 0xE1D5BEF04E364A72, 0xA3255F1BE7DC7CE1, 0x64347D271DE22754, 0x26C49CCCB40811C7, + 0x5CBD6CC0CC10FAFC, 0x1E4D8D2B65FACC6F, 0xD95CAF179FC497DA, 0x9BAC4EFC362EA149, 0x158E0A85C2521623, 0x577EEB6E6BB820B0, 0x906FC95291867B05, 0xD29F28B9386C4D96, + 0xCEDBA04AD0952342, 0x8C2B41A1797F15D1, 0x4B3A639D83414E64, 0x9CA82762AAB78F7, 0x87E8C60FDED7CF9D, 0xC51827E4773DF90E, 0x20905D88D03A2BB, 0x40F9E43324E99428, + 0x2CFFE7D5975E55E2, 0x6E0F063E3EB46371, 0xA91E2402C48A38C4, 0xEBEEC5E96D600E57, 0x65CC8190991CB93D, 0x273C607B30F68FAE, 0xE02D4247CAC8D41B, 0xA2DDA3AC6322E288, + 0xBE992B5F8BDB8C5C, 0xFC69CAB42231BACF, 0x3B78E888D80FE17A, 0x7988096371E5D7E9, 0xF7AA4D1A85996083, 0xB55AACF12C735610, 0x724B8ECDD64D0DA5, 0x30BB6F267FA73B36, + 0x4AC29F2A07BFD00D, 0x8327EC1AE55E69E, 0xCF235CFD546BBD2B, 0x8DD3BD16FD818BB8, 0x3F1F96F09FD3CD2, 0x41011884A0170A41, 0x86103AB85A2951F4, 0xC4E0DB53F3C36767, + 0xD8A453A01B3A09B3, 0x9A54B24BB2D03F20, 0x5D45907748EE6495, 0x1FB5719CE1045206, 0x919735E51578E56C, 0xD367D40EBC92D3FF, 0x1476F63246AC884A, 0x568617D9EF46BED9, + 0xE085162AB69D5E3C, 0xA275F7C11F7768AF, 0x6564D5FDE549331A, 0x279434164CA30589, 0xA9B6706FB8DFB2E3, 0xEB46918411358470, 0x2C57B3B8EB0BDFC5, 0x6EA7525342E1E956, + 0x72E3DAA0AA188782, 0x30133B4B03F2B111, 0xF7021977F9CCEAA4, 0xB5F2F89C5026DC37, 0x3BD0BCE5A45A6B5D, 0x79205D0E0DB05DCE, 0xBE317F32F78E067B, 0xFCC19ED95E6430E8, + 0x86B86ED5267CDBD3, 0xC4488F3E8F96ED40, 0x359AD0275A8B6F5, 0x41A94CE9DC428066, 0xCF8B0890283E370C, 0x8D7BE97B81D4019F, 0x4A6ACB477BEA5A2A, 0x89A2AACD2006CB9, + 0x14DEA25F3AF9026D, 0x562E43B4931334FE, 0x913F6188692D6F4B, 0xD3CF8063C0C759D8, 0x5DEDC41A34BBEEB2, 0x1F1D25F19D51D821, 0xD80C07CD676F8394, 0x9AFCE626CE85B507, + }; + } + + /// + /// Represents a non-cryptographic hash algorithm. + /// + public abstract class NonCryptographicHashAlgorithm + { + /// + /// Gets the number of bytes produced from this hash algorithm. + /// + /// The number of bytes produced from this hash algorithm. + public int HashLengthInBytes { get; } + + /// + /// Called from constructors in derived classes to initialize the + /// class. + /// + /// + /// The number of bytes produced from this hash algorithm. + /// + /// + /// is less than 1. + /// + protected NonCryptographicHashAlgorithm(int hashLengthInBytes) + { + if (hashLengthInBytes < 1) + throw new ArgumentOutOfRangeException(nameof(hashLengthInBytes)); + + HashLengthInBytes = hashLengthInBytes; + } + + /// + /// When overridden in a derived class, + /// appends the contents of to the data already + /// processed for the current hash computation. + /// + /// The data to process. + public abstract void Append(ReadOnlySpan source); + + /// + /// When overridden in a derived class, + /// resets the hash computation to the initial state. + /// + public abstract void Reset(); + + /// + /// When overridden in a derived class, + /// writes the computed hash value to + /// without modifying accumulated state. + /// + /// The buffer that receives the computed hash value. + /// + /// + /// Implementations of this method must write exactly + /// bytes to . + /// Do not assume that the buffer was zero-initialized. + /// + /// + /// The class validates the + /// size of the buffer before calling this method, and slices the span + /// down to be exactly in length. + /// + /// + protected abstract void GetCurrentHashCore(Span destination); + + /// + /// Appends the contents of to the data already + /// processed for the current hash computation. + /// + /// The data to process. + /// + /// is . + /// + public void Append(byte[] source) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + Append(new ReadOnlySpan(source)); + } + + /// + /// Appends the contents of to the data already + /// processed for the current hash computation. + /// + /// The data to process. + /// + /// is . + /// + /// + public void Append(Stream stream) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + byte[] buffer = ArrayPool.Shared.Rent(4096); + + while (true) + { + int read = stream.Read(buffer, 0, buffer.Length); + + if (read == 0) + { + break; + } + + Append(new ReadOnlySpan(buffer, 0, read)); + } + + ArrayPool.Shared.Return(buffer); + } + + /*/// + /// Asychronously reads the contents of + /// and appends them to the data already + /// processed for the current hash computation. + /// + /// The data to process. + /// + /// The token to monitor for cancellation requests. + /// The default value is . + /// + /// + /// A task that represents the asynchronous append operation. + /// + /// + /// is . + /// + public Task AppendAsync(Stream stream, CancellationToken cancellationToken = default) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + return AppendAsyncCore(stream, cancellationToken); + } + + private async Task AppendAsyncCore(Stream stream, CancellationToken cancellationToken) + { + byte[] buffer = ArrayPool.Shared.Rent(4096); + + while (true) + { +#if NETCOREAPP + int read = await stream.ReadAsync(buffer.AsMemory(), cancellationToken).ConfigureAwait(false); +#else + int read = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); +#endif + + if (read == 0) + { + break; + } + + Append(new ReadOnlySpan(buffer, 0, read)); + } + + ArrayPool.Shared.Return(buffer); + }*/ + + /// + /// Gets the current computed hash value without modifying accumulated state. + /// + /// + /// The hash value for the data already provided. + /// + public byte[] GetCurrentHash() + { + byte[] ret = new byte[HashLengthInBytes]; + GetCurrentHashCore(ret); + return ret; + } + + /// + /// Attempts to write the computed hash value to + /// without modifying accumulated state. + /// + /// The buffer that receives the computed hash value. + /// + /// On success, receives the number of bytes written to . + /// + /// + /// if is long enough to receive + /// the computed hash value; otherwise, . + /// + public bool TryGetCurrentHash(Span destination, out int bytesWritten) + { + if (destination.Length < HashLengthInBytes) + { + bytesWritten = 0; + return false; + } + + GetCurrentHashCore(destination.Slice(0, HashLengthInBytes)); + bytesWritten = HashLengthInBytes; + return true; + } + + /// + /// Writes the computed hash value to + /// without modifying accumulated state. + /// + /// The buffer that receives the computed hash value. + /// + /// The number of bytes written to , + /// which is always . + /// + /// + /// is shorter than . + /// + public int GetCurrentHash(Span destination) + { + if (destination.Length < HashLengthInBytes) + { + ThrowDestinationTooShort(); + } + + GetCurrentHashCore(destination.Slice(0, HashLengthInBytes)); + return HashLengthInBytes; + } + + /// + /// Gets the current computed hash value and clears the accumulated state. + /// + /// + /// The hash value for the data already provided. + /// + public byte[] GetHashAndReset() + { + byte[] ret = new byte[HashLengthInBytes]; + GetHashAndResetCore(ret); + return ret; + } + + /// + /// Attempts to write the computed hash value to . + /// If successful, clears the accumulated state. + /// + /// The buffer that receives the computed hash value. + /// + /// On success, receives the number of bytes written to . + /// + /// + /// and clears the accumulated state + /// if is long enough to receive + /// the computed hash value; otherwise, . + /// + public bool TryGetHashAndReset(Span destination, out int bytesWritten) + { + if (destination.Length < HashLengthInBytes) + { + bytesWritten = 0; + return false; + } + + GetHashAndResetCore(destination.Slice(0, HashLengthInBytes)); + bytesWritten = HashLengthInBytes; + return true; + } + + /// + /// Writes the computed hash value to + /// then clears the accumulated state. + /// + /// The buffer that receives the computed hash value. + /// + /// The number of bytes written to , + /// which is always . + /// + /// + /// is shorter than . + /// + public int GetHashAndReset(Span destination) + { + if (destination.Length < HashLengthInBytes) + { + ThrowDestinationTooShort(); + } + + GetHashAndResetCore(destination.Slice(0, HashLengthInBytes)); + return HashLengthInBytes; + } + + /// + /// Writes the computed hash value to + /// then clears the accumulated state. + /// + /// The buffer that receives the computed hash value. + /// + /// + /// Implementations of this method must write exactly + /// bytes to . + /// Do not assume that the buffer was zero-initialized. + /// + /// + /// The class validates the + /// size of the buffer before calling this method, and slices the span + /// down to be exactly in length. + /// + /// + /// The default implementation of this method calls + /// followed by . + /// Overrides of this method do not need to call either of those methods, + /// but must ensure that the caller cannot observe a difference in behavior. + /// + /// + protected virtual void GetHashAndResetCore(Span destination) + { + Debug.Assert(destination.Length == HashLengthInBytes); + + GetCurrentHashCore(destination); + Reset(); + } + + /// + /// This method is not supported and should not be called. + /// Call or + /// instead. + /// + /// This method will always throw a . + /// In all cases. + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Use GetCurrentHash() to retrieve the computed hash code.", true)] +#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member + public override int GetHashCode() +#pragma warning restore CS0809 // Obsolete member overrides non-obsolete member + { + throw new NotSupportedException(); + } + + + private protected static void ThrowDestinationTooShort() => + throw new ArgumentException("destination"); + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Verifier.Crc64.cs.meta b/Runtime/ABase/Utility/Utility.Verifier.Crc64.cs.meta new file mode 100644 index 0000000..02e88ca --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Verifier.Crc64.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 61e3efb334af41a7ae8ba8bd10f047e5 +timeCreated: 1681727672 \ No newline at end of file diff --git a/Runtime/ABase/Utility/Utility.Verifier.cs b/Runtime/ABase/Utility/Utility.Verifier.cs new file mode 100644 index 0000000..27d11cf --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Verifier.cs @@ -0,0 +1,213 @@ +using System; +using System.IO; + +namespace AlicizaX +{ + public static partial class Utility + { + /// + /// 校验相关的实用函数。 + /// + public static partial class Verifier + { + private const int CachedBytesLength = 0x1000; + private static readonly byte[] SCachedBytes = new byte[CachedBytesLength]; + private static readonly Crc32 SAlgorithm = new Crc32(); + private static readonly Crc64 SAlgorithm64 = new Crc64(); + + /// + /// 计算二进制流的CRC64 + /// + /// + /// + public static ulong GetCrc64(byte[] bytes) + { + SAlgorithm64.Reset(); + SAlgorithm64.Append(bytes); + return SAlgorithm64.GetCurrentHashAsUInt64(); + } + + /// + /// 计算流的CRC64 + /// + /// + /// + public static ulong GetCrc64(Stream stream) + { + SAlgorithm64.Reset(); + SAlgorithm64.Append(stream); + return SAlgorithm64.GetCurrentHashAsUInt64(); + } + + /// + /// 计算二进制流的 CRC32。 + /// + /// 指定的二进制流。 + /// 计算后的 CRC32。 + public static int GetCrc32(byte[] bytes) + { + if (bytes == null) + { + throw new GameFrameworkException("Bytes is invalid."); + } + + return GetCrc32(bytes, 0, bytes.Length); + } + + /// + /// 计算二进制流的 CRC32。 + /// + /// 指定的二进制流。 + /// 二进制流的偏移。 + /// 二进制流的长度。 + /// 计算后的 CRC32。 + public static int GetCrc32(byte[] bytes, int offset, int length) + { + if (bytes == null) + { + throw new GameFrameworkException("Bytes is invalid."); + } + + if (offset < 0 || length < 0 || offset + length > bytes.Length) + { + throw new GameFrameworkException("Offset or length is invalid."); + } + + SAlgorithm.HashCore(bytes, offset, length); + int result = (int) SAlgorithm.HashFinal(); + SAlgorithm.Initialize(); + return result; + } + + /// + /// 计算二进制流的 CRC32。 + /// + /// 指定的二进制流。 + /// 计算后的 CRC32。 + public static int GetCrc32(Stream stream) + { + if (stream == null) + { + throw new GameFrameworkException("Stream is invalid."); + } + + while (true) + { + int bytesRead = stream.Read(SCachedBytes, 0, CachedBytesLength); + if (bytesRead > 0) + { + SAlgorithm.HashCore(SCachedBytes, 0, bytesRead); + } + else + { + break; + } + } + + int result = (int) SAlgorithm.HashFinal(); + SAlgorithm.Initialize(); + Array.Clear(SCachedBytes, 0, CachedBytesLength); + return result; + } + + /// + /// 获取 CRC32 数值的二进制数组。 + /// + /// CRC32 数值。 + /// CRC32 数值的二进制数组。 + public static byte[] GetCrc32Bytes(int crc32) + { + return new byte[] {(byte) ((crc32 >> 24) & 0xff), (byte) ((crc32 >> 16) & 0xff), (byte) ((crc32 >> 8) & 0xff), (byte) (crc32 & 0xff)}; + } + + /// + /// 获取 CRC32 数值的二进制数组。 + /// + /// CRC32 数值。 + /// 要存放结果的数组。 + public static void GetCrc32Bytes(int crc32, byte[] bytes) + { + GetCrc32Bytes(crc32, bytes, 0); + } + + /// + /// 获取 CRC32 数值的二进制数组。 + /// + /// CRC32 数值。 + /// 要存放结果的数组。 + /// CRC32 数值的二进制数组在结果数组内的起始位置。 + public static void GetCrc32Bytes(int crc32, byte[] bytes, int offset) + { + if (bytes == null) + { + throw new GameFrameworkException("Result is invalid."); + } + + if (offset < 0 || offset + 4 > bytes.Length) + { + throw new GameFrameworkException("Offset or length is invalid."); + } + + bytes[offset] = (byte) ((crc32 >> 24) & 0xff); + bytes[offset + 1] = (byte) ((crc32 >> 16) & 0xff); + bytes[offset + 2] = (byte) ((crc32 >> 8) & 0xff); + bytes[offset + 3] = (byte) (crc32 & 0xff); + } + + internal static int GetCrc32(Stream stream, byte[] code, int length) + { + if (stream == null) + { + throw new GameFrameworkException("Stream is invalid."); + } + + if (code == null) + { + throw new GameFrameworkException("Code is invalid."); + } + + int codeLength = code.Length; + if (codeLength <= 0) + { + throw new GameFrameworkException("Code length is invalid."); + } + + int bytesLength = (int) stream.Length; + if (length < 0 || length > bytesLength) + { + length = bytesLength; + } + + int codeIndex = 0; + while (true) + { + int bytesRead = stream.Read(SCachedBytes, 0, CachedBytesLength); + if (bytesRead > 0) + { + if (length > 0) + { + for (int i = 0; i < bytesRead && i < length; i++) + { + SCachedBytes[i] ^= code[codeIndex++]; + codeIndex %= codeLength; + } + + length -= bytesRead; + } + + SAlgorithm.HashCore(SCachedBytes, 0, bytesRead); + } + else + { + break; + } + } + + int result = (int) SAlgorithm.HashFinal(); + SAlgorithm.Initialize(); + Array.Clear(SCachedBytes, 0, CachedBytesLength); + return result; + } + } + } +} diff --git a/Runtime/ABase/Utility/Utility.Verifier.cs.meta b/Runtime/ABase/Utility/Utility.Verifier.cs.meta new file mode 100644 index 0000000..073ca6e --- /dev/null +++ b/Runtime/ABase/Utility/Utility.Verifier.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f54adff1b0e414d9ca9cad7a69d131de +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Utility/Utility.cs b/Runtime/ABase/Utility/Utility.cs new file mode 100644 index 0000000..32703d1 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.cs @@ -0,0 +1,9 @@ +namespace AlicizaX +{ + /// + /// 实用函数集。 + /// + public static partial class Utility + { + } +} diff --git a/Runtime/ABase/Utility/Utility.cs.meta b/Runtime/ABase/Utility/Utility.cs.meta new file mode 100644 index 0000000..8d7bef4 --- /dev/null +++ b/Runtime/ABase/Utility/Utility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9c0886780a70e4939903937939d018cd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/AlicizaX.Framework.Runtime.asmdef b/Runtime/AlicizaX.Framework.Runtime.asmdef index 8669906..2def7ed 100644 --- a/Runtime/AlicizaX.Framework.Runtime.asmdef +++ b/Runtime/AlicizaX.Framework.Runtime.asmdef @@ -2,11 +2,11 @@ "name": "AlicizaX.Framework.Runtime", "rootNamespace": "AlicizaX.Framework.Runtime", "references": [ - "GUID:75b6f2078d190f14dbda4a5b747d709c", "GUID:b4c00b967a932af48b2e067403eecbe2", "GUID:f51ebe6a0ceec4240a699833d6309b23", "GUID:e34a5702dd353724aa315fb8011f08c3", - "GUID:189d55e03d78888459720d730f4d2424" + "GUID:189d55e03d78888459720d730f4d2424", + "GUID:33661e06c33d31b4c9223810bf503247" ], "includePlatforms": [], "excludePlatforms": [], @@ -20,6 +20,11 @@ "name": "com.alicizax.unity.animationflow", "expression": "", "define": "ALICIZAX_UI_ANIMATION_SUPPORT" + }, + { + "name": "com.alicizax.unity.cysharp.zstring", + "expression": "2.3.0", + "define": "ZSTRING_SUPPORT" } ], "noEngineReferences": false diff --git a/Runtime/Debugger/DebuggerComponent.cs b/Runtime/Debugger/DebuggerComponent.cs index b9ba013..3e424f5 100644 --- a/Runtime/Debugger/DebuggerComponent.cs +++ b/Runtime/Debugger/DebuggerComponent.cs @@ -149,7 +149,7 @@ namespace AlicizaX.Debugger.Runtime private void Awake() { _instance = this; - _mDebuggerModule = ModuleSystem.RegisterModule(); + _mDebuggerModule = ModuleSystem.RegisterModule(); if (_mDebuggerModule == null) { Log.Error("Debugger manager is invalid."); @@ -159,6 +159,11 @@ namespace AlicizaX.Debugger.Runtime m_FpsCounter = new FpsCounter(0.5f); } + internal void SetActiveMode(DebuggerActiveWindowType activeWindowType) + { + m_ActiveWindow = activeWindowType; + } + private void Start() { if (m_Skin == null) @@ -213,14 +218,10 @@ namespace AlicizaX.Debugger.Runtime case DebuggerActiveWindowType.OnlyOpenInEditor: ActiveWindow = Application.isEditor; break; - default: ActiveWindow = false; break; } -#if !UNITY_EDITOR - ActiveWindow = AppBuilderSetting.Instance.DebugMode; -#endif } private void Update() diff --git a/Runtime/Localization/LocalizationComponent.cs b/Runtime/Localization/LocalizationComponent.cs index 655210e..6a0113e 100644 --- a/Runtime/Localization/LocalizationComponent.cs +++ b/Runtime/Localization/LocalizationComponent.cs @@ -13,13 +13,24 @@ namespace AlicizaX.Localization.Runtime public static string PrefsKey = Application.dataPath.GetHashCode() + "Language"; - private void Awake() + [SerializeField] private string _language; + + public void SetLanguage(string language) { - _mLocalizationModule = ModuleSystem.RegisterModule(); + _language = language; + } + + private void Start() + { + _mLocalizationModule = ModuleSystem.RegisterModule(); if (_mLocalizationModule == null) { Log.Info("Localization manager is invalid."); } +#if UNITY_EDITOR + _language = UnityEditor.EditorPrefs.GetString(LocalizationComponent.PrefsKey, "None"); +#endif + _mLocalizationModule.Initialize(_language); } } } diff --git a/Runtime/Localization/Manager/ILocalizationModule.cs b/Runtime/Localization/Manager/ILocalizationModule.cs index 6e54369..38ced9d 100644 --- a/Runtime/Localization/Manager/ILocalizationModule.cs +++ b/Runtime/Localization/Manager/ILocalizationModule.cs @@ -6,10 +6,11 @@ namespace AlicizaX.Localization.Runtime /// /// 本地化管理器接口。 /// - public interface ILocalizationModule : IModule, IModuleAwake + public interface ILocalizationModule : IModule { public string Language { get; } + void Initialize(string language); /// /// 根据字典主键获取字典内容字符串。 /// diff --git a/Runtime/Localization/Manager/LocalizationModule.cs b/Runtime/Localization/Manager/LocalizationModule.cs index fe1c355..c307caf 100644 --- a/Runtime/Localization/Manager/LocalizationModule.cs +++ b/Runtime/Localization/Manager/LocalizationModule.cs @@ -25,6 +25,12 @@ namespace AlicizaX.Localization.Runtime LocalizationChangeEvent.Publisher(_language); } + public void Initialize(string language) + { + _language = language; + Log.Info($"Initializing LocalizationModule :{language}"); + } + /// /// 根据字典主键获取字典内容字符串。 /// @@ -747,14 +753,5 @@ namespace AlicizaX.Localization.Runtime { Dic.Clear(); } - - void IModuleAwake.Awake() - { -#if UNITY_EDITOR - _language = UnityEditor.EditorPrefs.GetString(LocalizationComponent.PrefsKey, "None"); -#else - _language = AppBuilderSetting.Instance.Language; -#endif - } } } diff --git a/Runtime/Resource/Resource/Extension/AssetItemObject.cs b/Runtime/Resource/Resource/Extension/AssetItemObject.cs index 8cb2104..2b3bd16 100644 --- a/Runtime/Resource/Resource/Extension/AssetItemObject.cs +++ b/Runtime/Resource/Resource/Extension/AssetItemObject.cs @@ -13,7 +13,7 @@ namespace AlicizaX.Resource.Runtime return item; } - protected override void Release(bool isShutdown) + protected internal override void Release(bool isShutdown) { if (Target == null) { diff --git a/Runtime/Resource/Resource/ResourceComponent.cs b/Runtime/Resource/Resource/ResourceComponent.cs index 532e2d3..815922a 100644 --- a/Runtime/Resource/Resource/ResourceComponent.cs +++ b/Runtime/Resource/Resource/ResourceComponent.cs @@ -35,6 +35,7 @@ namespace AlicizaX.Resource.Runtime [SerializeField] private bool useSystemUnloadUnusedAssets = true; [SerializeField] private string _decryptionServices = ""; + [SerializeField] private EPlayMode _playMode = EPlayMode.EditorSimulateMode; /// /// 当前最新的包裹版本。 @@ -168,9 +169,14 @@ namespace AlicizaX.Resource.Runtime #endregion + public void SetPlayMode(int playMode) + { + _playMode = (EPlayMode)playMode; + } + private void Awake() { - _resourceModule = ModuleSystem.RegisterModule(); + _resourceModule = ModuleSystem.RegisterModule(); if (_resourceModule == null) { Log.Error("Resource module is invalid."); @@ -184,15 +190,12 @@ namespace AlicizaX.Resource.Runtime private void Start() { - EPlayMode playMode = EPlayMode.EditorSimulateMode; #if UNITY_EDITOR - playMode = (EPlayMode)UnityEditor.EditorPrefs.GetInt(PrefsKey, 0); -#else - playMode = (EPlayMode)AppBuilderSetting.Instance.ResMode; + _playMode = (EPlayMode)UnityEditor.EditorPrefs.GetInt(ResourceComponent.PrefsKey, 0); #endif _resourceModule.DefaultPackageName = PackageName; _resourceModule.DecryptionServices = _decryptionServices; - _resourceModule.PlayMode = playMode; + _resourceModule.PlayMode = _playMode; _resourceModule.Milliseconds = milliseconds; _resourceModule.DownloadingMaxNum = DownloadingMaxNum; _resourceModule.FailedTryAgain = FailedTryAgain; @@ -202,7 +205,7 @@ namespace AlicizaX.Resource.Runtime _resourceModule.AssetExpireTime = assetExpireTime; _resourceModule.AssetPriority = assetPriority; _resourceModule.SetForceUnloadUnusedAssetsAction(ForceUnloadUnusedAssets); - Log.Info($"ResourceModule Run Mode:{playMode}"); + Log.Info($"ResourceModule Run Mode:{_playMode}"); } private void OnApplicationQuit() diff --git a/Runtime/Resource/Resource/ResourceModule.AssetObject.cs b/Runtime/Resource/Resource/ResourceModule.AssetObject.cs index 243454a..9618cca 100644 --- a/Runtime/Resource/Resource/ResourceModule.AssetObject.cs +++ b/Runtime/Resource/Resource/ResourceModule.AssetObject.cs @@ -47,12 +47,12 @@ namespace AlicizaX.Resource.Runtime m_AssetHandle = null; } - protected override void OnUnspawn() + protected internal override void OnUnspawn() { base.OnUnspawn(); } - protected override void Release(bool isShutdown) + protected internal override void Release(bool isShutdown) { if (!isShutdown) {