diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Editor.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor.meta
new file mode 100644
index 00000000..9ca6c08e
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 6deb0dcf8adbd4f8f92d5482999b05bf
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime.meta
new file mode 100644
index 00000000..de034322
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e8ba5b17cffbb4ffea892f674fc8629f
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/CheckUnityVersion.cs b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/CheckUnityVersion.cs
new file mode 100644
index 00000000..34f112cc
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/CheckUnityVersion.cs
@@ -0,0 +1,16 @@
+using System;
+using UnityEditor;
+
+namespace Fantasy
+{
+ internal static class CheckUnityVersion
+ {
+ [InitializeOnLoadMethod]
+ private static void OnInitializeOnLoad()
+ {
+#if !UNITY_2021_3_OR_NEWER
+ Debug.LogError("Fantasy支持的最低版本为Unity2021.3.14f1c1");
+#endif
+ }
+ }
+}
\ No newline at end of file
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/CheckUnityVersion.cs.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/CheckUnityVersion.cs.meta
new file mode 100644
index 00000000..1f13ace4
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/CheckUnityVersion.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 455f338921e74471841971fd6b79db01
+timeCreated: 1725943424
\ No newline at end of file
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Fantasy.Editor.asmdef b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Fantasy.Editor.asmdef
new file mode 100644
index 00000000..b6ddabbb
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Fantasy.Editor.asmdef
@@ -0,0 +1,18 @@
+{
+ "name": "Fantasy.Editor",
+ "rootNamespace": "",
+ "references": [
+ "GUID:0b7224b83ba514121aa026f3857f820a"
+ ],
+ "includePlatforms": [
+ "Editor"
+ ],
+ "excludePlatforms": [],
+ "allowUnsafeCode": false,
+ "overrideReferences": false,
+ "precompiledReferences": [],
+ "autoReferenced": true,
+ "defineConstraints": [],
+ "versionDefines": [],
+ "noEngineReferences": false
+}
\ No newline at end of file
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Fantasy.Editor.asmdef.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Fantasy.Editor.asmdef.meta
new file mode 100644
index 00000000..d5a4e8b1
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Fantasy.Editor.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 36410968656dd49358af485aad0b0c4c
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/FantasyStartup.cs b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/FantasyStartup.cs
new file mode 100644
index 00000000..c541657e
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/FantasyStartup.cs
@@ -0,0 +1,49 @@
+using System.IO;
+using UnityEditor;
+using UnityEngine;
+
+namespace Fantasy
+{
+ [InitializeOnLoad]
+ public static class FantasyStartup
+ {
+ private const string ScriptAssemblies = "Library/ScriptAssemblies/";
+
+ static FantasyStartup()
+ {
+ if (!FantasySettingsScriptableObject.Instance.autoCopyAssembly)
+ {
+ return;
+ }
+
+ var hotUpdatePath = FantasySettingsScriptableObject.Instance.hotUpdatePath;
+
+ if (string.IsNullOrEmpty(hotUpdatePath))
+ {
+ Debug.LogError("请先在菜单Fantasy-Fantasy Settings里设置HotUpdatePath目录位置");
+ return;
+ }
+
+ if (!Directory.Exists(hotUpdatePath))
+ {
+ Directory.CreateDirectory(hotUpdatePath);
+ }
+
+ // ReSharper disable once StringLastIndexOfIsCultureSpecific.1
+ if (hotUpdatePath.LastIndexOf("/") != hotUpdatePath.Length - 1)
+ {
+ FantasySettingsScriptableObject.Instance.hotUpdatePath += "/";
+ hotUpdatePath = FantasySettingsScriptableObject.Instance.hotUpdatePath;
+ }
+
+ foreach (var instanceHotUpdateAssemblyDefinition in FantasySettingsScriptableObject.Instance.hotUpdateAssemblyDefinitions)
+ {
+ var dll = instanceHotUpdateAssemblyDefinition.name;
+ File.Copy($"{ScriptAssemblies}{dll}.dll", $"{hotUpdatePath}/{dll}.dll.bytes", true);
+ File.Copy($"{ScriptAssemblies}{dll}.pdb", $"{hotUpdatePath}/{dll}.pdb.bytes", true);
+ }
+
+ AssetDatabase.Refresh();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/FantasyStartup.cs.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/FantasyStartup.cs.meta
new file mode 100644
index 00000000..abcd77d5
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/FantasyStartup.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 42156ba2865a4aa4a3e1e57b3ac9b984
+timeCreated: 1688276977
\ No newline at end of file
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/LinkXmlGenerator.cs b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/LinkXmlGenerator.cs
new file mode 100644
index 00000000..af8cf9bd
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/LinkXmlGenerator.cs
@@ -0,0 +1,46 @@
+using System.IO;
+using UnityEditor;
+using UnityEngine;
+
+namespace Fantasy
+{
+ public class LinkXmlGenerator
+ {
+ private const string LinkPath = "Assets/link.xml";
+ // 在Unity编辑器中运行该方法来生成link.xml文件
+ [UnityEditor.MenuItem("Fantasy/Generate link.xml")]
+ public static void GenerateLinkXml()
+ {
+ using (var writer = new StreamWriter("Assets/link.xml"))
+ {
+ writer.WriteLine("");
+ GenerateLinkXml(writer, "Assembly-CSharp", LinkPath);
+ Debug.Log("Assembly-CSharp Link generation completed");
+ GenerateLinkXml(writer, "Fantasy.Unity", LinkPath);
+ Debug.Log("Fantasy.Unity Link generation completed");
+ foreach (var linkAssembly in FantasySettingsScriptableObject.Instance.linkAssemblyDefinitions)
+ {
+ GenerateLinkXml(writer, linkAssembly.name, LinkPath);
+ Debug.Log($"{linkAssembly.name} Link generation completed");
+ }
+ writer.WriteLine("");
+ }
+
+ AssetDatabase.Refresh();
+ Debug.Log("link.xml generated successfully!");
+ }
+
+ private static void GenerateLinkXml(StreamWriter writer, string assemblyName, string outputPath)
+ {
+ var assembly = System.Reflection.Assembly.Load(assemblyName);
+ var types = assembly.GetTypes();
+ writer.WriteLine($" ");
+ foreach (var type in types)
+ {
+ var typeName = type.FullName.Replace('<', '+').Replace('>', '+');
+ writer.WriteLine($" ");
+ }
+ writer.WriteLine(" ");
+ }
+ }
+}
\ No newline at end of file
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/LinkXmlGenerator.cs.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/LinkXmlGenerator.cs.meta
new file mode 100644
index 00000000..dbab2202
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/LinkXmlGenerator.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: cda4c9403de946df9c31654416193a21
+timeCreated: 1722743236
\ No newline at end of file
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings.meta
new file mode 100644
index 00000000..6b097c8b
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 3a6997d946f3400e8c423fe1b9245f65
+timeCreated: 1688277110
\ No newline at end of file
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings/FantasySettings.cs b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings/FantasySettings.cs
new file mode 100644
index 00000000..4c85dc7e
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings/FantasySettings.cs
@@ -0,0 +1,13 @@
+using UnityEditor;
+
+namespace Fantasy
+{
+ public class FantasySettings
+ {
+ [MenuItem("Fantasy/Fantasy Settings")]
+ public static void OpenFantasySettings()
+ {
+ SettingsService.OpenProjectSettings("Project/Fantasy Settings");
+ }
+ }
+}
\ No newline at end of file
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings/FantasySettings.cs.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings/FantasySettings.cs.meta
new file mode 100644
index 00000000..852e825b
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings/FantasySettings.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 977a7c172c30403da60286ba39b7bc72
+timeCreated: 1686913667
\ No newline at end of file
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings/FantasySettingsProvider.cs b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings/FantasySettingsProvider.cs
new file mode 100644
index 00000000..d1511446
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings/FantasySettingsProvider.cs
@@ -0,0 +1,99 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using UnityEditor;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+namespace Fantasy
+{
+ public class FantasySettingsProvider : SettingsProvider
+ {
+ private SerializedObject _serializedObject;
+ private SerializedProperty _autoCopyAssembly;
+ private SerializedProperty _hotUpdatePath;
+ private SerializedProperty _hotUpdateAssemblyDefinitions;
+ private SerializedProperty _linkAssemblyDefinitions;
+ public FantasySettingsProvider() : base("Project/Fantasy Settings", SettingsScope.Project) { }
+
+ public override void OnActivate(string searchContext, VisualElement rootElement)
+ {
+ Init();
+ base.OnActivate(searchContext, rootElement);
+ }
+
+ public override void OnDeactivate()
+ {
+ base.OnDeactivate();
+ FantasySettingsScriptableObject.Save();
+ }
+
+ private void Init()
+ {
+ _serializedObject?.Dispose();
+ _serializedObject = new SerializedObject(FantasySettingsScriptableObject.Instance);
+ _autoCopyAssembly = _serializedObject.FindProperty("autoCopyAssembly");
+ _hotUpdatePath = _serializedObject.FindProperty("hotUpdatePath");
+ _hotUpdateAssemblyDefinitions = _serializedObject.FindProperty("hotUpdateAssemblyDefinitions");
+ _linkAssemblyDefinitions = _serializedObject.FindProperty("linkAssemblyDefinitions");
+ }
+
+ public override void OnGUI(string searchContext)
+ {
+ if (_serializedObject == null || !_serializedObject.targetObject)
+ {
+ Init();
+ }
+
+ using (CreateSettingsWindowGUIScope())
+ {
+ _serializedObject!.Update();
+
+ EditorGUI.BeginChangeCheck();
+ EditorGUILayout.PropertyField(_autoCopyAssembly);
+ EditorGUILayout.PropertyField(_hotUpdatePath);
+ EditorGUILayout.PropertyField(_hotUpdateAssemblyDefinitions);
+ EditorGUILayout.PropertyField(_linkAssemblyDefinitions);
+ EditorGUILayout.HelpBox("默认包括Assembly-CSharp和Fantasy.Unity,所以不需要再次指定。", MessageType.Info);
+
+ if (GUILayout.Button("GenerateLinkXml"))
+ {
+ LinkXmlGenerator.GenerateLinkXml();
+ }
+
+ if (EditorGUI.EndChangeCheck())
+ {
+ _serializedObject.ApplyModifiedProperties();
+ FantasySettingsScriptableObject.Save();
+ EditorApplication.RepaintHierarchyWindow();
+ }
+
+ base.OnGUI(searchContext);
+ }
+ }
+
+ private IDisposable CreateSettingsWindowGUIScope()
+ {
+ var unityEditorAssembly = System.Reflection.Assembly.GetAssembly(typeof(EditorWindow));
+ var type = unityEditorAssembly.GetType("UnityEditor.SettingsWindow+GUIScope");
+ return Activator.CreateInstance(type) as IDisposable;
+ }
+
+ static FantasySettingsProvider _provider;
+
+ [SettingsProvider]
+ public static SettingsProvider CreateMyCustomSettingsProvider()
+ {
+ if (FantasySettingsScriptableObject.Instance && _provider == null)
+ {
+ _provider = new FantasySettingsProvider();
+ using (var so = new SerializedObject(FantasySettingsScriptableObject.Instance))
+ {
+ _provider.keywords = GetSearchKeywordsFromSerializedObject(so);
+ }
+ }
+ return _provider;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings/FantasySettingsProvider.cs.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings/FantasySettingsProvider.cs.meta
new file mode 100644
index 00000000..72e475d5
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings/FantasySettingsProvider.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 047b2f13e73f413fa000bf7be979fb4a
+timeCreated: 1688380387
\ No newline at end of file
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings/FantasySettingsScriptableObject.cs b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings/FantasySettingsScriptableObject.cs
new file mode 100644
index 00000000..919285bb
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings/FantasySettingsScriptableObject.cs
@@ -0,0 +1,21 @@
+using UnityEditorInternal;
+using UnityEngine;
+using UnityEngine.Serialization;
+
+namespace Fantasy
+{
+ [ScriptableObjectPath("ProjectSettings/FantasySettings.asset")]
+ public class FantasySettingsScriptableObject : ScriptableObjectSingleton, ISerializationCallbackReceiver
+ {
+ [FormerlySerializedAs("AutoCopyAssembly")] [Header("自动拷贝程序集到HotUpdatePath目录中")]
+ public bool autoCopyAssembly = false;
+ [FormerlySerializedAs("HotUpdatePath")] [Header("HotUpdate目录(Unity编译后会把所有HotUpdate程序集Copy一份到这个目录下)")]
+ public string hotUpdatePath;
+ [FormerlySerializedAs("HotUpdateAssemblyDefinitions")] [Header("HotUpdate程序集")]
+ public AssemblyDefinitionAsset[] hotUpdateAssemblyDefinitions;
+ [FormerlySerializedAs("LinkAssemblyDefinitions")] [Header("生成Link.xml的程序集")]
+ public AssemblyDefinitionAsset[] linkAssemblyDefinitions;
+ public void OnBeforeSerialize() { }
+ public void OnAfterDeserialize() { }
+ }
+}
\ No newline at end of file
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings/FantasySettingsScriptableObject.cs.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings/FantasySettingsScriptableObject.cs.meta
new file mode 100644
index 00000000..b5bae5a8
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings/FantasySettingsScriptableObject.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 27a37e930ca3454fb57bc895f50d2106
+timeCreated: 1688277120
\ No newline at end of file
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings/ScriptableObjectSingleton.cs b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings/ScriptableObjectSingleton.cs
new file mode 100644
index 00000000..62066762
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings/ScriptableObjectSingleton.cs
@@ -0,0 +1,100 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using UnityEditorInternal;
+using UnityEngine;
+// ReSharper disable AssignNullToNotNullAttribute
+
+namespace Fantasy
+{
+ public class ScriptableObjectSingleton : ScriptableObject where T : ScriptableObject
+ {
+ private static T _instance;
+
+ public static T Instance
+ {
+ get
+ {
+ if (_instance == null)
+ {
+ _instance = Load();
+ }
+
+ return _instance;
+ }
+ }
+
+ private static T Load()
+ {
+ var scriptableObjectPath = GetScriptableObjectPath();
+
+ if (string.IsNullOrEmpty(scriptableObjectPath))
+ {
+ return null;
+ }
+
+ var loadSerializedFileAndForget = InternalEditorUtility.LoadSerializedFileAndForget(scriptableObjectPath);
+
+ if (loadSerializedFileAndForget.Length <= 0)
+ {
+ return CreateInstance();
+ }
+
+ return loadSerializedFileAndForget[0] as T;
+ }
+
+ public static void Save(bool saveAsText = true)
+ {
+ if (_instance == null)
+ {
+ Debug.LogError("Cannot save ScriptableObjectSingleton: no instance!");
+ return;
+ }
+
+ var scriptableObjectPath = GetScriptableObjectPath();
+
+ if (string.IsNullOrEmpty(scriptableObjectPath))
+ {
+ return;
+ }
+
+ var directoryName = Path.GetDirectoryName(scriptableObjectPath);
+
+ if (!Directory.Exists(directoryName))
+ {
+ Directory.CreateDirectory(directoryName);
+ }
+
+ UnityEngine.Object[] obj = { _instance };
+ InternalEditorUtility.SaveToSerializedFileAndForget(obj, scriptableObjectPath, saveAsText);
+ }
+
+ private static string GetScriptableObjectPath()
+ {
+ var scriptableObjectPathAttribute = typeof(T).GetCustomAttribute(typeof(ScriptableObjectPathAttribute)) as ScriptableObjectPathAttribute;
+ return scriptableObjectPathAttribute?.ScriptableObjectPath;
+ }
+ }
+
+ [AttributeUsage(AttributeTargets.Class, Inherited = false)]
+ public class ScriptableObjectPathAttribute : Attribute
+ {
+ internal readonly string ScriptableObjectPath;
+
+ public ScriptableObjectPathAttribute(string scriptableObjectPath)
+ {
+ if (string.IsNullOrEmpty(scriptableObjectPath))
+ {
+ throw new ArgumentException("Invalid relative path (it is empty)");
+ }
+
+ if (scriptableObjectPath[0] == '/')
+ {
+ scriptableObjectPath = scriptableObjectPath.Substring(1);
+ }
+
+ ScriptableObjectPath = scriptableObjectPath;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings/ScriptableObjectSingleton.cs.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings/ScriptableObjectSingleton.cs.meta
new file mode 100644
index 00000000..5105c6fc
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/Settings/ScriptableObjectSingleton.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 3c77f5208dc14542ae7497d59321ef76
+timeCreated: 1688278016
\ No newline at end of file
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/WSocket.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/WSocket.meta
new file mode 100644
index 00000000..21883b65
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/WSocket.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: b9e5c7d1436ec414fa3f69a23aaafc3b
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/WSocket/SettingsWindow.cs b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/WSocket/SettingsWindow.cs
new file mode 100644
index 00000000..0313b0e0
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/WSocket/SettingsWindow.cs
@@ -0,0 +1,229 @@
+using UnityEngine;
+using UnityEditor;
+using UnityEngine.Networking;
+using System.IO;
+using System;
+
+namespace UnityWebSocket.Editor
+{
+ internal class SettingsWindow : EditorWindow
+ {
+ static SettingsWindow window = null;
+ [MenuItem("Tools/UnityWebSocket", priority = 100)]
+ internal static void Open()
+ {
+ if (window != null)
+ {
+ window.Close();
+ }
+
+ window = GetWindow(true, "UnityWebSocket");
+ window.minSize = window.maxSize = new Vector2(600, 310);
+ window.Show();
+ window.BeginCheck();
+ }
+
+ private void OnGUI()
+ {
+ DrawLogo();
+ DrawVersion();
+ DrawSeparator(80);
+ DrawSeparator(186);
+ DrawHelper();
+ DrawFooter();
+ }
+
+ Texture2D logoTex = null;
+ private void DrawLogo()
+ {
+ if (logoTex == null)
+ {
+ logoTex = new Texture2D(66, 66);
+ logoTex.LoadImage(Convert.FromBase64String(LOGO_BASE64.VALUE));
+ for (int i = 0; i < 66; i++) for (int j = 0; j < 15; j++) logoTex.SetPixel(i, j, Color.clear);
+ logoTex.Apply();
+ }
+
+ var logoPos = new Rect(10, 10, 66, 66);
+ GUI.DrawTexture(logoPos, logoTex);
+ var title = "UnityWebSocket";
+ var titlePos = new Rect(80, 20, 500, 50);
+ GUI.Label(titlePos, title, TextStyle(24));
+ }
+
+ private void DrawSeparator(int y)
+ {
+ EditorGUI.DrawRect(new Rect(10, y, 580, 1), Color.white * 0.5f);
+ }
+
+ private GUIStyle TextStyle(int fontSize = 10, TextAnchor alignment = TextAnchor.UpperLeft, float alpha = 0.85f)
+ {
+ var style = new GUIStyle();
+ style.fontSize = fontSize;
+ style.normal.textColor = (EditorGUIUtility.isProSkin ? Color.white : Color.black) * alpha;
+ style.alignment = alignment;
+ style.richText = true;
+ return style;
+ }
+
+ private void DrawVersion()
+ {
+ GUI.Label(new Rect(440, 10, 150, 10), "Current Version: " + Settings.VERSION, TextStyle(alignment: TextAnchor.MiddleLeft));
+ if (string.IsNullOrEmpty(latestVersion))
+ {
+ GUI.Label(new Rect(440, 30, 150, 10), "Checking for Updates...", TextStyle(alignment: TextAnchor.MiddleLeft));
+ }
+ else if (latestVersion == "unknown")
+ {
+
+ }
+ else
+ {
+ GUI.Label(new Rect(440, 30, 150, 10), "Latest Version: " + latestVersion, TextStyle(alignment: TextAnchor.MiddleLeft));
+ if (Settings.VERSION == latestVersion)
+ {
+ if (GUI.Button(new Rect(440, 50, 150, 18), "Check Update"))
+ {
+ latestVersion = "";
+ changeLog = "";
+ BeginCheck();
+ }
+ }
+ else
+ {
+ if (GUI.Button(new Rect(440, 50, 150, 18), "Update to | " + latestVersion))
+ {
+ ShowUpdateDialog();
+ }
+ }
+ }
+ }
+
+ private void ShowUpdateDialog()
+ {
+ var isOK = EditorUtility.DisplayDialog("UnityWebSocket",
+ "Update UnityWebSocket now?\n" + changeLog,
+ "Update Now", "Cancel");
+
+ if (isOK)
+ {
+ UpdateVersion();
+ }
+ }
+
+ private void UpdateVersion()
+ {
+ Application.OpenURL(Settings.GITHUB + "/releases");
+ }
+
+ private void DrawHelper()
+ {
+ GUI.Label(new Rect(330, 200, 100, 18), "GitHub:", TextStyle(10, TextAnchor.MiddleRight));
+ if (GUI.Button(new Rect(440, 200, 150, 18), "UnityWebSocket"))
+ {
+ Application.OpenURL(Settings.GITHUB);
+ }
+
+ GUI.Label(new Rect(330, 225, 100, 18), "Report:", TextStyle(10, TextAnchor.MiddleRight));
+ if (GUI.Button(new Rect(440, 225, 150, 18), "Report an Issue"))
+ {
+ Application.OpenURL(Settings.GITHUB + "/issues/new");
+ }
+
+ GUI.Label(new Rect(330, 250, 100, 18), "Email:", TextStyle(10, TextAnchor.MiddleRight));
+ if (GUI.Button(new Rect(440, 250, 150, 18), Settings.EMAIL))
+ {
+ var uri = new Uri(string.Format("mailto:{0}?subject={1}", Settings.EMAIL, "UnityWebSocket Feedback"));
+ Application.OpenURL(uri.AbsoluteUri);
+ }
+
+ GUI.Label(new Rect(330, 275, 100, 18), "QQ群:", TextStyle(10, TextAnchor.MiddleRight));
+ if (GUI.Button(new Rect(440, 275, 150, 18), Settings.QQ_GROUP))
+ {
+ Application.OpenURL(Settings.QQ_GROUP_LINK);
+ }
+ }
+
+ private void DrawFooter()
+ {
+ EditorGUI.DropShadowLabel(new Rect(10, 230, 400, 20), "Developed by " + Settings.AUHTOR);
+ EditorGUI.DropShadowLabel(new Rect(10, 250, 400, 20), "All rights reserved");
+ }
+
+ UnityWebRequest req;
+ string changeLog = "";
+ string latestVersion = "";
+ void BeginCheck()
+ {
+ EditorApplication.update -= VersionCheckUpdate;
+ EditorApplication.update += VersionCheckUpdate;
+
+ req = UnityWebRequest.Get(Settings.GITHUB + "/releases/latest");
+ req.SendWebRequest();
+ }
+
+ private void VersionCheckUpdate()
+ {
+#if UNITY_2020_3_OR_NEWER
+ if (req == null
+ || req.result == UnityWebRequest.Result.ConnectionError
+ || req.result == UnityWebRequest.Result.DataProcessingError
+ || req.result == UnityWebRequest.Result.ProtocolError)
+#elif UNITY_2018_1_OR_NEWER
+ if (req == null || req.isNetworkError || req.isHttpError)
+#else
+ if (req == null || req.isError)
+#endif
+ {
+ EditorApplication.update -= VersionCheckUpdate;
+ latestVersion = "unknown";
+ return;
+ }
+
+ if (req.isDone)
+ {
+ EditorApplication.update -= VersionCheckUpdate;
+ latestVersion = req.url.Substring(req.url.LastIndexOf("/") + 1).TrimStart('v');
+
+ if (Settings.VERSION != latestVersion)
+ {
+ var text = req.downloadHandler.text;
+ var st = text.IndexOf("content=\"" + latestVersion);
+ st = st > 0 ? text.IndexOf("\n", st) : -1;
+ var end = st > 0 ? text.IndexOf("\" />", st) : -1;
+ if (st > 0 && end > st)
+ {
+ changeLog = text.Substring(st + 1, end - st - 1).Trim();
+ changeLog = changeLog.Replace("\r", "");
+ changeLog = changeLog.Replace("\n", "\n- ");
+ changeLog = "\nCHANGE LOG: \n- " + changeLog + "\n";
+ }
+ }
+
+ Repaint();
+ }
+ }
+ }
+
+ internal static class LOGO_BASE64
+ {
+ internal const string VALUE = "iVBORw0KGgoAAAANSUhEUgAAAEIAAABCCAMAAADUivDaAAAAq1BMVEUAAABKmtcvjtYzl" +
+ "9szmNszl9syl9k0mNs0mNwzmNs0mNszl9szl9s0mNs0mNwzmNw0mNwyltk0mNw0mNwzl9s0mNsymNs0mNszmNwzmNwzm" +
+ "NszmNs0mNwzl9w0mNwzmNw0mNs0mNs0mNwzl9wzmNs0mNwzmNs0mNwzl90zmNszmNszl9szmNsxmNszmNszmNw0mNwzm" +
+ "Nw0mNs2neM4pe41mt43ouo2oOY5qfM+UHlaAAAAMnRSTlMAAwXN3sgI+/069MSCK6M/MA74h9qfFHB8STWMJ9OSdmNcI" +
+ "8qya1IeF+/U0EIa57mqmFTYJe4AAAN3SURBVFjD7ZbpkppAFEa/bgVBREF2kEVGFNeZsM77P1kadURnJkr8k1Qlx1Khu" +
+ "/pw7+2lwH/+YcgfMBBLG7VocwDamzH+wJBB8Qhjve2f0TdrGwjei6o4Ub/nM/APw5Z7vvSB/qrCrqbD6fBEVtigeMxks" +
+ "fX9zWbj+z1jhqgSBplQ50eGo4614WXlRAzgrRhmtSfvxAn7pB0N5ObaKKZZuU5/d37IBcBgUQwqDuf7Z2gUmVAl4NGNr" +
+ "/UeHxV5n39ulbaKLI86h6HilmM5M1aN126lpNhtl59yeTsp8nUMvpNC1J3bh5FtfVRk+bJrJunn5d4U4piJ/Vw9eXgsj" +
+ "4ZpZaCjg9waZkIpnBWLJ44OwoNu60F2UnSaEkKv4XnAlCpm6B4F/aKMDiyGi2L8SEEAVdxNLuzmgV7nFwObEe2xQVuX+" +
+ "RV1lWetga3w+cN1sXgvm4cJH8OEgZC1DPKhfF/BIymmQrMjq/x65FUeEkDup8GxoexZmznHCvANtXU/CAq13yimhQGtm" +
+ "H4VCPnBBL1fTKo3CqEcvq7Lb/OwHxWTYlyw+JmjKoVvDLVOQB4pVsM8K8smgvLCxZDlIijwyOEc+nr/msMwK0+GQWGBd" +
+ "tmhjv8icTds1s2ammaFh04QLLe69NK7guP6mTDMaw3o6nAX/Z7EXUskPSvWEWg4srVlp5NTDXv9Lce9HGN5eeG4nj5Yz" +
+ "ACteU2wQLo4MBtJfd1nw5nG1/s9zwUQ6pykL1TQjqdeuvQW0naz2XKLYL4Cwzr4vj+OQdD96CSp7Lrynp4aeFF0xdm5q" +
+ "6OFtFfPv7URxpWJNjd/N+3+I9+1klMav12Qtgbt9R2JaIopjkzaPtOFq4KxUpqfUMSFnQrySWjLoQzRZS4HMH84ME1ej" +
+ "S1YJpQZ3B+sR1uCQJSBdGdCk1eAEgORR88KK05W8dh2MA+A/SKCYu3mCJ0Ek7HBx4HHeuwYy5G3x8hSMTJcOMFbinCsn" +
+ "hO1V1aszGULvA0g4UFsb4VA0hAFcyo6cgLsAoT7uUtGAH5wQKQle0wuLyxLTaNyJEYwxw4wSljLK1TP8CAaOyhBMMEsj" +
+ "OBoXgo7VGElFkSWL+vef1RF2YNXeRWYzQBTpkhC8KaZHhuIogArkQLKClBZjU26B2IZgGz+cpZkHl8g3fYUaW/YP2kb2" +
+ "M/V97JY/vZN859n+QmO7XtC9Bf2jAAAAABJRU5ErkJggg==";
+ }
+}
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/WSocket/SettingsWindow.cs.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/WSocket/SettingsWindow.cs.meta
new file mode 100644
index 00000000..74124688
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Editor/Runtime/WSocket/SettingsWindow.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 902614e06186a482f9e816e1d1984547
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Fantasy.Unity.asmdef b/Fantasy.Unity/Fantasy.Unity.UniTask/Fantasy.Unity.asmdef
new file mode 100644
index 00000000..ccfd42a4
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Fantasy.Unity.asmdef
@@ -0,0 +1,17 @@
+{
+ "name": "Fantasy.Unity",
+ "rootNamespace": "",
+ "references": [
+ "GUID:f51ebe6a0ceec4240a699833d6309b23",
+ "GUID:77d6c8c98758f884fbc6cb1c9bfb5924"
+ ],
+ "includePlatforms": [],
+ "excludePlatforms": [],
+ "allowUnsafeCode": true,
+ "overrideReferences": false,
+ "precompiledReferences": [],
+ "autoReferenced": true,
+ "defineConstraints": [],
+ "versionDefines": [],
+ "noEngineReferences": false
+}
\ No newline at end of file
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Fantasy.Unity.asmdef.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Fantasy.Unity.asmdef.meta
new file mode 100644
index 00000000..ac113c41
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Fantasy.Unity.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 0b7224b83ba514121aa026f3857f820a
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime.meta
new file mode 100644
index 00000000..b559b169
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d4180ba95bb674e6488cd44665e784d6
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core.meta
new file mode 100644
index 00000000..12f960bb
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 076d59bda84794582abf2d7b23d3cc01
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Assembly.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Assembly.meta
new file mode 100644
index 00000000..19f46f05
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Assembly.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: b5b24e6eec64b4702b871053139f8add
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Assembly/AssemblyInfo.cs b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Assembly/AssemblyInfo.cs
new file mode 100644
index 00000000..60db3fda
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Assembly/AssemblyInfo.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Fantasy.DataStructure.Collection;
+
+// ReSharper disable CollectionNeverQueried.Global
+#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+
+namespace Fantasy.Assembly
+{
+ ///
+ /// AssemblyInfo提供有关程序集和类型的信息
+ ///
+ public sealed class AssemblyInfo
+ {
+ ///
+ /// 唯一标识
+ ///
+ public readonly long AssemblyIdentity;
+ ///
+ /// 获取或设置与此程序集相关联的 实例。
+ ///
+ public System.Reflection.Assembly Assembly { get; private set; }
+ ///
+ /// 程序集类型集合,获取一个列表,包含从程序集加载的所有类型。
+ ///
+ public readonly List AssemblyTypeList = new List();
+ ///
+ /// 程序集类型分组集合,获取一个分组列表,将接口类型映射到实现这些接口的类型。
+ ///
+ public readonly OneToManyList AssemblyTypeGroupList = new OneToManyList();
+
+ ///
+ /// 初始化 类的新实例。
+ ///
+ ///
+ public AssemblyInfo(long assemblyIdentity)
+ {
+ AssemblyIdentity = assemblyIdentity;
+ }
+
+ ///
+ /// 从指定的程序集加载类型信息并进行分类。
+ ///
+ /// 要加载信息的程序集。
+ public void Load(System.Reflection.Assembly assembly)
+ {
+ Assembly = assembly;
+ var assemblyTypes = assembly.GetTypes().ToList();
+
+ foreach (var type in assemblyTypes)
+ {
+ if (type.IsAbstract || type.IsInterface)
+ {
+ continue;
+ }
+
+ var interfaces = type.GetInterfaces();
+
+ foreach (var interfaceType in interfaces)
+ {
+ AssemblyTypeGroupList.Add(interfaceType, type);
+ }
+ }
+
+ AssemblyTypeList.AddRange(assemblyTypes);
+ }
+
+ ///
+ /// 卸载程序集的类型信息。
+ ///
+ public void Unload()
+ {
+ AssemblyTypeList.Clear();
+ AssemblyTypeGroupList.Clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Assembly/AssemblyInfo.cs.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Assembly/AssemblyInfo.cs.meta
new file mode 100644
index 00000000..b5e3a284
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Assembly/AssemblyInfo.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6588e9470957646dfa79849126de341c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Assembly/AssemblySystem.cs b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Assembly/AssemblySystem.cs
new file mode 100644
index 00000000..c72b8675
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Assembly/AssemblySystem.cs
@@ -0,0 +1,262 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Security.Cryptography;
+using System.Text;
+using Cysharp.Threading.Tasks;
+using Fantasy.Async;
+using Fantasy.Helper;
+
+#pragma warning disable CS8604 // Possible null reference argument.
+#pragma warning disable CS8602 // Dereference of a possibly null reference.
+#pragma warning disable CS8603
+#pragma warning disable CS8618
+namespace Fantasy.Assembly
+{
+ ///
+ /// 管理程序集加载和卸载的帮助类。
+ ///
+ public static class AssemblySystem
+ {
+#if FANTASY_WEBGL
+ private static readonly List AssemblySystems = new List();
+ private static readonly Dictionary AssemblyList = new Dictionary();
+#else
+ private static readonly ConcurrentBag AssemblySystems = new ConcurrentBag();
+ private static readonly ConcurrentDictionary AssemblyList = new ConcurrentDictionary();
+#endif
+ ///
+ /// 初始化 AssemblySystem。
+ ///
+ public static void Initialize(params System.Reflection.Assembly[] assemblies)
+ {
+ LoadAssembly(typeof(AssemblySystem).Assembly);
+ foreach (var assembly in assemblies)
+ {
+ LoadAssembly(assembly);
+ }
+ }
+
+ ///
+ /// 加载指定的程序集,并触发相应的事件。
+ ///
+ /// 要加载的程序集。
+ public static void LoadAssembly(System.Reflection.Assembly assembly)
+ {
+ var assemblyIdentity = AssemblyIdentity(assembly);
+
+ if (AssemblyList.TryGetValue(assemblyIdentity, out var assemblyInfo))
+ {
+ assemblyInfo.Unload();
+ foreach (var assemblySystem in AssemblySystems)
+ {
+ assemblySystem.ReLoad(assemblyIdentity);
+ }
+ }
+ else
+ {
+ assemblyInfo = new AssemblyInfo(assemblyIdentity);
+ AssemblyList.TryAdd(assemblyIdentity, assemblyInfo);
+ foreach (var assemblySystem in AssemblySystems)
+ {
+ assemblySystem.Load(assemblyIdentity);
+ }
+ }
+
+ assemblyInfo.Load(assembly);
+ }
+
+ ///
+ /// 卸载程序集
+ ///
+ ///
+ public static void UnLoadAssembly(System.Reflection.Assembly assembly)
+ {
+ var assemblyIdentity = AssemblyIdentity(assembly);
+
+ if (!AssemblyList.Remove(assemblyIdentity, out var assemblyInfo))
+ {
+ return;
+ }
+
+ assemblyInfo.Unload();
+ foreach (var assemblySystem in AssemblySystems)
+ {
+ assemblySystem.OnUnLoad(assemblyIdentity);
+ }
+ }
+
+ ///
+ /// 将AssemblySystem接口的object注册到程序集管理中心
+ ///
+ ///
+ public static async UniTask Register(object obj)
+ {
+ if (obj is not IAssembly assemblySystem)
+ {
+ return;
+ }
+
+ AssemblySystems.Add(assemblySystem);
+
+ foreach (var (assemblyIdentity, _) in AssemblyList)
+ {
+ await assemblySystem.Load(assemblyIdentity);
+ }
+ }
+
+ ///
+ /// 程序集管理中心卸载注册的Load、ReLoad、UnLoad的接口
+ ///
+ ///
+ public static void UnRegister(object obj)
+ {
+ if (obj is not IAssembly assemblySystem)
+ {
+ return;
+ }
+#if FANTASY_WEBGL
+ AssemblySystems.Remove(assemblySystem);
+#else
+ while (AssemblySystems.TryTake(out var removeAssemblySystem))
+ {
+ if (removeAssemblySystem == assemblySystem)
+ {
+ continue;
+ }
+
+ AssemblySystems.Add(removeAssemblySystem);
+ }
+#endif
+ }
+
+ ///
+ /// 获取所有已加载程序集中的所有类型。
+ ///
+ /// 所有已加载程序集中的类型。
+ public static IEnumerable ForEach()
+ {
+ foreach (var (_, assemblyInfo) in AssemblyList)
+ {
+ foreach (var type in assemblyInfo.AssemblyTypeList)
+ {
+ yield return type;
+ }
+ }
+ }
+
+ ///
+ /// 获取指定程序集中的所有类型。
+ ///
+ /// 程序集唯一标识。
+ /// 指定程序集中的类型。
+ public static IEnumerable ForEach(long assemblyIdentity)
+ {
+ if (!AssemblyList.TryGetValue(assemblyIdentity, out var assemblyInfo))
+ {
+ yield break;
+ }
+
+ foreach (var type in assemblyInfo.AssemblyTypeList)
+ {
+ yield return type;
+ }
+ }
+
+ ///
+ /// 获取所有已加载程序集中实现指定类型的所有类型。
+ ///
+ /// 要查找的基类或接口类型。
+ /// 所有已加载程序集中实现指定类型的类型。
+ public static IEnumerable ForEach(Type findType)
+ {
+ foreach (var (_, assemblyInfo) in AssemblyList)
+ {
+ if (!assemblyInfo.AssemblyTypeGroupList.TryGetValue(findType, out var assemblyLoad))
+ {
+ continue;
+ }
+
+ foreach (var type in assemblyLoad)
+ {
+ yield return type;
+ }
+ }
+ }
+
+ ///
+ /// 获取指定程序集中实现指定类型的所有类型。
+ ///
+ /// 程序集唯一标识。
+ /// 要查找的基类或接口类型。
+ /// 指定程序集中实现指定类型的类型。
+ public static IEnumerable ForEach(long assemblyIdentity, Type findType)
+ {
+ if (!AssemblyList.TryGetValue(assemblyIdentity, out var assemblyInfo))
+ {
+ yield break;
+ }
+
+ if (!assemblyInfo.AssemblyTypeGroupList.TryGetValue(findType, out var assemblyLoad))
+ {
+ yield break;
+ }
+
+ foreach (var type in assemblyLoad)
+ {
+ yield return type;
+ }
+ }
+
+ ///
+ /// 获取指定程序集的实例。
+ ///
+ /// 程序集名称。
+ /// 指定程序集的实例,如果未加载则返回 null。
+ public static System.Reflection.Assembly GetAssembly(long assemblyIdentity)
+ {
+ return !AssemblyList.TryGetValue(assemblyIdentity, out var assemblyInfo) ? null : assemblyInfo.Assembly;
+ }
+
+ ///
+ /// 获取当前框架注册的Assembly
+ ///
+ ///
+ public static IEnumerable ForEachAssembly
+ {
+ get
+ {
+ foreach (var (_, assemblyInfo) in AssemblyList)
+ {
+ yield return assemblyInfo.Assembly;
+ }
+ }
+ }
+
+ ///
+ /// 根据Assembly的强命名计算唯一标识。
+ ///
+ ///
+ ///
+ private static long AssemblyIdentity(System.Reflection.Assembly assembly)
+ {
+ return HashCodeHelper.ComputeHash64(assembly.GetName().Name);
+ }
+
+ ///
+ /// 释放资源,卸载所有加载的程序集。
+ ///
+ public static void Dispose()
+ {
+ foreach (var (_, assemblyInfo) in AssemblyList.ToArray())
+ {
+ UnLoadAssembly(assemblyInfo.Assembly);
+ }
+
+ AssemblyList.Clear();
+ AssemblySystems.Clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Assembly/AssemblySystem.cs.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Assembly/AssemblySystem.cs.meta
new file mode 100644
index 00000000..6474bd99
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Assembly/AssemblySystem.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6f06892fa649d49c287bf07095569ae0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Assembly/IAssembly.cs b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Assembly/IAssembly.cs
new file mode 100644
index 00000000..487d4df5
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Assembly/IAssembly.cs
@@ -0,0 +1,28 @@
+using System;
+using Cysharp.Threading.Tasks;
+using Fantasy.Async;
+
+namespace Fantasy.Assembly
+{
+ ///
+ /// 实现这个接口、会再程序集首次加载、卸载、重载的时候调用
+ ///
+ public interface IAssembly : IDisposable
+ {
+ ///
+ /// 程序集加载时调用
+ ///
+ /// 程序集标识
+ public UniTask Load(long assemblyIdentity);
+ ///
+ /// 程序集重新加载的时候调用
+ ///
+ /// 程序集标识
+ public UniTask ReLoad(long assemblyIdentity);
+ ///
+ /// 卸载的时候调用
+ ///
+ /// 程序集标识
+ public UniTask OnUnLoad(long assemblyIdentity);
+ }
+}
\ No newline at end of file
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Assembly/IAssembly.cs.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Assembly/IAssembly.cs.meta
new file mode 100644
index 00000000..11db0dc6
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Assembly/IAssembly.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f14fcbf29decc4d47b6c548a3d5f2a3f
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Benchmark.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Benchmark.meta
new file mode 100644
index 00000000..cfe3201a
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Benchmark.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 160974fcbccd74d0eafedf8762807588
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Benchmark/Handler.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Benchmark/Handler.meta
new file mode 100644
index 00000000..cfda7e8e
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Benchmark/Handler.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 91eb4278fcdd44b6d8a782ca42107e4c
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Benchmark/Handler/BenchmarkRequestHandler.cs b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Benchmark/Handler/BenchmarkRequestHandler.cs
new file mode 100644
index 00000000..85325479
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Benchmark/Handler/BenchmarkRequestHandler.cs
@@ -0,0 +1,25 @@
+using Fantasy.Async;
+using Fantasy.InnerMessage;
+using Fantasy.Network.Interface;
+
+#if FANTASY_NET
+namespace Fantasy.Network.Benchmark.Handler;
+
+///
+/// BenchmarkRequestHandler
+///
+public sealed class BenchmarkRequestHandler : MessageRPC
+{
+ ///
+ /// Run方法
+ ///
+ ///
+ ///
+ ///
+ ///
+ protected override async FTask Run(Session session, BenchmarkRequest request, BenchmarkResponse response, Action reply)
+ {
+ await FTask.CompletedTask;
+ }
+}
+#endif
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Benchmark/Handler/BenchmarkRequestHandler.cs.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Benchmark/Handler/BenchmarkRequestHandler.cs.meta
new file mode 100644
index 00000000..e0451e32
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/Benchmark/Handler/BenchmarkRequestHandler.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0b5a936197cf64719b8d6b44158e0d91
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataBase.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataBase.meta
new file mode 100644
index 00000000..5a4bdb5b
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataBase.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 4961a2a852e804ad69bad45492f22555
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataBase/IDataBase.cs b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataBase/IDataBase.cs
new file mode 100644
index 00000000..9b08c8eb
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataBase/IDataBase.cs
@@ -0,0 +1,164 @@
+#if FANTASY_NET
+using System;
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using Fantasy.Async;
+using Fantasy.Entitas;
+
+#pragma warning disable CS8625
+
+namespace Fantasy.DataBase
+{
+ ///
+ /// 表示用于执行各种数据库操作的数据库接口。
+ ///
+ public interface IDataBase
+ {
+ ///
+ /// 初始化数据库连接。
+ ///
+ IDataBase Initialize(Scene scene, string connectionString, string dbName);
+ ///
+ /// 在指定的集合中检索类型 的实体数量。
+ ///
+ FTask Count(string collection = null) where T : Entity;
+ ///
+ /// 在指定的集合中检索满足给定筛选条件的类型 的实体数量。
+ ///
+ FTask Count(Expression> filter, string collection = null) where T : Entity;
+ ///
+ /// 检查指定集合中是否存在类型 的实体。
+ ///
+ FTask Exist(string collection = null) where T : Entity;
+ ///
+ /// 检查指定集合中是否存在满足给定筛选条件的类型 的实体。
+ ///
+ FTask Exist(Expression> filter, string collection = null) where T : Entity;
+ ///
+ /// 从指定集合中检索指定 ID 的类型 的实体,不锁定。
+ ///
+ FTask QueryNotLock(long id, string collection = null) where T : Entity;
+ ///
+ /// 从指定集合中检索指定 ID 的类型 的实体。
+ ///
+ FTask Query(long id, string collection = null) where T : Entity;
+ ///
+ /// 按页查询满足给定筛选条件的类型 的实体数量和日期。
+ ///
+ FTask<(int count, List dates)> QueryCountAndDatesByPage(Expression> filter, int pageIndex, int pageSize, string collection = null) where T : Entity;
+ ///
+ /// 按页查询满足给定筛选条件的类型 的实体数量和日期。
+ ///
+ FTask<(int count, List dates)> QueryCountAndDatesByPage(Expression> filter, int pageIndex, int pageSize, string[] cols, string collection = null) where T : Entity;
+ ///
+ /// 分页查询指定集合中满足给定筛选条件的类型 的实体列表。
+ ///
+ FTask> QueryByPage(Expression> filter, int pageIndex, int pageSize, string collection = null) where T : Entity;
+ ///
+ /// 分页查询指定集合中满足给定筛选条件的类型 的实体列表,仅返回指定列的数据。
+ ///
+ FTask> QueryByPage(Expression> filter, int pageIndex, int pageSize, string[] cols, string collection = null) where T : Entity;
+ ///
+ /// 从指定集合中按页查询满足给定筛选条件的类型 的实体列表,按指定字段排序。
+ ///
+ FTask> QueryByPageOrderBy(Expression> filter, int pageIndex, int pageSize, Expression> orderByExpression, bool isAsc = true, string collection = null) where T : Entity;
+ ///
+ /// 检索满足给定筛选条件的类型 的第一个实体,从指定集合中。
+ ///
+ FTask First(Expression> filter, string collection = null) where T : Entity;
+ ///
+ /// 查询指定集合中满足给定 JSON 查询字符串的类型 的第一个实体,仅返回指定列的数据。
+ ///
+ FTask First(string json, string[] cols, string collection = null) where T : Entity;
+ ///
+ /// 从指定集合中按页查询满足给定筛选条件的类型 的实体列表,按指定字段排序。
+ ///
+ FTask> QueryOrderBy(Expression> filter, Expression> orderByExpression, bool isAsc = true, string collection = null) where T : Entity;
+ ///
+ /// 从指定集合中按页查询满足给定筛选条件的类型 的实体列表。
+ ///
+ FTask> Query(Expression> filter, string collection = null) where T : Entity;
+ ///
+ /// 查询指定 ID 的多个集合,将结果存储在给定的实体列表中。
+ ///
+ FTask Query(long id, List collectionNames, List result);
+ ///
+ /// 根据给定的 JSON 查询字符串查询指定集合中的类型 实体列表。
+ ///
+ FTask> QueryJson(string json, string collection = null) where T : Entity;
+ ///
+ /// 根据给定的 JSON 查询字符串查询指定集合中的类型 实体列表,仅返回指定列的数据。
+ ///
+ FTask> QueryJson(string json, string[] cols, string collection = null) where T : Entity;
+ ///
+ /// 根据给定的 JSON 查询字符串查询指定集合中的类型 实体列表,通过指定的任务 ID 进行标识。
+ ///
+ FTask> QueryJson(long taskId, string json, string collection = null) where T : Entity;
+ ///
+ /// 查询指定集合中满足给定筛选条件的类型 实体列表,仅返回指定列的数据。
+ ///
+ FTask> Query(Expression> filter, string[] cols, string collection = null) where T : class;
+ ///
+ /// 保存类型 实体到指定集合中,如果集合不存在将自动创建。
+ ///
+ FTask Save(T entity, string collection = null) where T : Entity, new();
+ ///
+ /// 保存一组实体到数据库中,根据实体列表的 ID 进行区分和存储。
+ ///
+ FTask Save(long id, List entities);
+ ///
+ /// 通过事务会话将类型 实体保存到指定集合中,如果集合不存在将自动创建。
+ ///
+ FTask Save(object transactionSession, T entity, string collection = null) where T : Entity;
+ ///
+ /// 向指定集合中插入一个类型 实体,如果集合不存在将自动创建。
+ ///
+ FTask Insert(T entity, string collection = null) where T : Entity, new();
+ ///
+ /// 批量插入一组类型 实体到指定集合中,如果集合不存在将自动创建。
+ ///
+ FTask InsertBatch(IEnumerable list, string collection = null) where T : Entity, new();
+ ///
+ /// 通过事务会话,批量插入一组类型 实体到指定集合中,如果集合不存在将自动创建。
+ ///
+ FTask InsertBatch(object transactionSession, IEnumerable list, string collection = null) where T : Entity, new();
+ ///
+ /// 通过事务会话,根据指定的 ID 从数据库中删除指定类型 实体。
+ ///
+ FTask Remove(object transactionSession, long id, string collection = null) where T : Entity, new();
+ ///
+ /// 根据指定的 ID 从数据库中删除指定类型 实体。
+ ///
+ FTask Remove(long id, string collection = null) where T : Entity, new();
+ ///
+ /// 通过事务会话,根据给定的筛选条件从数据库中删除指定类型 实体。
+ ///
+ FTask Remove(long coroutineLockQueueKey, object transactionSession, Expression> filter, string collection = null) where T : Entity, new();
+ ///
+ /// 根据给定的筛选条件从数据库中删除指定类型 实体。
+ ///
+ FTask Remove(long coroutineLockQueueKey, Expression> filter, string collection = null) where T : Entity, new();
+ ///
+ /// 根据给定的筛选条件计算指定集合中类型 实体某个属性的总和。
+ ///
+ FTask Sum(Expression> filter, Expression> sumExpression, string collection = null) where T : Entity;
+ ///
+ /// 在指定的集合中创建索引,以提高类型 实体的查询性能。
+ ///
+ FTask CreateIndex(string collection, params object[] keys) where T : Entity;
+ ///
+ /// 在默认集合中创建索引,以提高类型 实体的查询性能。
+ ///
+ FTask CreateIndex(params object[] keys) where T : Entity;
+ ///
+ /// 创建指定类型 的数据库,用于存储实体。
+ ///
+ FTask CreateDB() where T : Entity;
+ ///
+ /// 根据指定类型创建数据库,用于存储实体。
+ ///
+ FTask CreateDB(Type type);
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataBase/IDataBase.cs.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataBase/IDataBase.cs.meta
new file mode 100644
index 00000000..9be13ba1
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataBase/IDataBase.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 83202aeeb0df040de8735030956ca861
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataBase/MongoDataBase.cs b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataBase/MongoDataBase.cs
new file mode 100644
index 00000000..47582732
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataBase/MongoDataBase.cs
@@ -0,0 +1,822 @@
+#if FANTASY_NET
+using System.Linq.Expressions;
+using Fantasy.Async;
+using Fantasy.DataStructure.Collection;
+using Fantasy.Entitas;
+using Fantasy.Helper;
+using Fantasy.Serialize;
+using MongoDB.Bson;
+using MongoDB.Driver;
+#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
+#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+
+namespace Fantasy.DataBase
+{
+ ///
+ /// 使用 MongoDB 数据库的实现。
+ ///
+ public sealed class MongoDataBase : IDataBase
+ {
+ private const int DefaultTaskSize = 1024;
+ private Scene _scene;
+ private string _dbName;
+ private string _connectionString;
+ private MongoClient _mongoClient;
+ private ISerialize _serializer;
+ private IMongoDatabase _mongoDatabase;
+ private CoroutineLock _dataBaseLock;
+ private readonly HashSet _collections = new HashSet();
+
+ ///
+ /// 初始化 MongoDB 数据库连接并记录所有集合名。
+ ///
+ /// 所在的Scene。
+ /// 数据库连接字符串。
+ /// 数据库名称。
+ /// 初始化后的数据库实例。
+ public IDataBase Initialize(Scene scene, string connectionString, string dbName)
+ {
+ _scene = scene;
+ _dbName = dbName;
+ _connectionString = connectionString;
+ _mongoClient = new MongoClient(connectionString);
+ _mongoDatabase = _mongoClient.GetDatabase(dbName);
+ _dataBaseLock = scene.CoroutineLockComponent.Create(GetType().TypeHandle.Value.ToInt64());
+ // 记录所有集合名
+ _collections.UnionWith(_mongoDatabase.ListCollectionNames().ToList());
+ _serializer = SerializerManager.GetSerializer(FantasySerializerType.Bson);
+ return this;
+ }
+
+ #region Other
+
+ ///
+ /// 对满足条件的文档中的某个数值字段进行求和操作。
+ ///
+ /// 实体类型。
+ /// 用于筛选文档的表达式。
+ /// 要对其进行求和的字段表达式。
+ /// 集合名称,可选。如果未指定,将使用实体类型的名称。
+ /// 满足条件的文档中指定字段的求和结果。
+ public async FTask Sum(Expression> filter, Expression> sumExpression, string collection = null) where T : Entity
+ {
+ var member = (MemberExpression)((UnaryExpression)sumExpression.Body).Operand;
+ var projection = new BsonDocument("_id", "null").Add("Result", new BsonDocument("$sum", $"${member.Member.Name}"));
+ var data = await GetCollection(collection).Aggregate().Match(filter).Group(projection).FirstOrDefaultAsync();
+ return data == null ? 0 : Convert.ToInt64(data["Result"]);
+ }
+
+ #endregion
+
+ #region GetCollection
+
+ ///
+ /// 获取指定集合中的 MongoDB 文档的 IMongoCollection 对象。
+ ///
+ /// 实体类型。
+ /// 集合名称,可选。如果未指定,将使用实体类型的名称。
+ /// IMongoCollection 对象。
+ private IMongoCollection GetCollection(string collection = null)
+ {
+ return _mongoDatabase.GetCollection(collection ?? typeof(T).Name);
+ }
+
+ ///
+ /// 获取指定集合中的 MongoDB 文档的 IMongoCollection 对象,其中实体类型为 Entity。
+ ///
+ /// 集合名称。
+ /// IMongoCollection 对象。
+ private IMongoCollection GetCollection(string name)
+ {
+ return _mongoDatabase.GetCollection(name);
+ }
+
+ #endregion
+
+ #region Count
+
+ ///
+ /// 统计指定集合中满足条件的文档数量。
+ ///
+ /// 实体类型。
+ /// 集合名称,可选。如果未指定,将使用实体类型的名称。
+ /// 满足条件的文档数量。
+ public async FTask Count(string collection = null) where T : Entity
+ {
+ return await GetCollection(collection).CountDocumentsAsync(d => true);
+ }
+
+ ///
+ /// 统计指定集合中满足条件的文档数量。
+ ///
+ /// 实体类型。
+ /// 用于筛选文档的表达式。
+ /// 集合名称,可选。如果未指定,将使用实体类型的名称。
+ /// 满足条件的文档数量。
+ public async FTask Count(Expression> filter, string collection = null) where T : Entity
+ {
+ return await GetCollection(collection).CountDocumentsAsync(filter);
+ }
+
+ #endregion
+
+ #region Exist
+
+ ///
+ /// 判断指定集合中是否存在文档。
+ ///
+ /// 实体类型。
+ /// 集合名称,可选。如果未指定,将使用实体类型的名称。
+ /// 如果存在文档则返回 true,否则返回 false。
+ public async FTask Exist(string collection = null) where T : Entity
+ {
+ return await Count(collection) > 0;
+ }
+
+ ///
+ /// 判断指定集合中是否存在满足条件的文档。
+ ///
+ /// 实体类型。
+ /// 用于筛选文档的表达式。
+ /// 集合名称,可选。如果未指定,将使用实体类型的名称。
+ /// 如果存在满足条件的文档则返回 true,否则返回 false。
+ public async FTask Exist(Expression> filter, string collection = null) where T : Entity
+ {
+ return await Count(filter, collection) > 0;
+ }
+
+ #endregion
+
+ #region Query
+
+ ///
+ /// 在不加数据库锁定的情况下,查询指定 ID 的文档。
+ ///
+ /// 文档实体类型。
+ /// 要查询的文档 ID。
+ /// 集合名称。
+ /// 查询到的文档。
+ public async FTask QueryNotLock(long id, string collection = null) where T : Entity
+ {
+ var cursor = await GetCollection(collection).FindAsync(d => d.Id == id);
+ var v = await cursor.FirstOrDefaultAsync();
+ return v;
+ }
+
+ ///
+ /// 查询指定 ID 的文档,并加数据库锁定以确保数据一致性。
+ ///
+ /// 文档实体类型。
+ /// 要查询的文档 ID。
+ /// 集合名称。
+ /// 查询到的文档。
+ public async FTask Query(long id, string collection = null) where T : Entity
+ {
+ using (await _dataBaseLock.Wait(id))
+ {
+ var cursor = await GetCollection(collection).FindAsync(d => d.Id == id);
+ var v = await cursor.FirstOrDefaultAsync();
+ return v;
+ }
+ }
+
+ ///
+ /// 通过分页查询并返回满足条件的文档数量和日期列表(不加锁)。
+ ///
+ /// 文档实体类型。
+ /// 查询过滤条件。
+ /// 页码。
+ /// 每页大小。
+ /// 集合名称。
+ /// 满足条件的文档数量和日期列表。
+ public async FTask<(int count, List dates)> QueryCountAndDatesByPage(Expression> filter, int pageIndex, int pageSize, string collection = null) where T : Entity
+ {
+ using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize))
+ {
+ var count = await Count(filter);
+ var dates = await QueryByPage(filter, pageIndex, pageSize, collection);
+ return ((int)count, dates);
+ }
+ }
+
+ ///
+ /// 通过分页查询并返回满足条件的文档数量和日期列表(加锁)。
+ ///
+ /// 文档实体类型。
+ /// 查询过滤条件。
+ /// 页码。
+ /// 每页大小。
+ /// 要查询的列名称数组。
+ /// 集合名称。
+ /// 满足条件的文档数量和日期列表。
+ public async FTask<(int count, List dates)> QueryCountAndDatesByPage(Expression> filter, int pageIndex, int pageSize, string[] cols, string collection = null) where T : Entity
+ {
+ using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize))
+ {
+ var count = await Count(filter);
+
+ var dates = await QueryByPage(filter, pageIndex, pageSize, cols, collection);
+
+ return ((int)count, dates);
+ }
+ }
+
+ ///
+ /// 通过分页查询并返回满足条件的文档列表(不加锁)。
+ ///
+ /// 文档实体类型。
+ /// 查询过滤条件。
+ /// 页码。
+ /// 每页大小。
+ /// 集合名称。
+ /// 满足条件的文档列表。
+ public async FTask> QueryByPage(Expression> filter, int pageIndex, int pageSize, string collection = null) where T : Entity
+ {
+ using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize))
+ {
+ return await GetCollection(collection).Find(filter).Skip((pageIndex - 1) * pageSize)
+ .Limit(pageSize)
+ .ToListAsync();
+ }
+ }
+
+ ///
+ /// 通过分页查询并返回满足条件的文档列表(加锁)。
+ ///
+ /// 文档实体类型。
+ /// 查询过滤条件。
+ /// 页码。
+ /// 每页大小。
+ /// 要查询的列名称数组。
+ /// 集合名称。
+ /// 满足条件的文档列表。
+ public async FTask> QueryByPage(Expression> filter, int pageIndex, int pageSize,
+ string[] cols, string collection = null) where T : Entity
+ {
+ using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize))
+ {
+ var projection = Builders.Projection.Include("");
+
+ foreach (var col in cols)
+ {
+ projection = projection.Include(col);
+ }
+
+ return await GetCollection(collection).Find(filter).Project(projection)
+ .Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync();
+ }
+ }
+
+ ///
+ /// 通过分页查询并返回满足条件的文档列表,并按指定表达式进行排序(加锁)。
+ ///
+ /// 文档实体类型。
+ /// 查询过滤条件。
+ /// 页码。
+ /// 每页大小。
+ /// 排序表达式。
+ /// 是否升序排序。
+ /// 集合名称。
+ /// 满足条件的文档列表。
+ public async FTask> QueryByPageOrderBy(Expression> filter, int pageIndex, int pageSize,
+ Expression> orderByExpression, bool isAsc = true, string collection = null) where T : Entity
+ {
+ using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize))
+ {
+ if (isAsc)
+ {
+ return await GetCollection(collection).Find(filter).SortBy(orderByExpression)
+ .Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync();
+ }
+
+ return await GetCollection(collection).Find(filter).SortByDescending(orderByExpression)
+ .Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync();
+ }
+ }
+
+ ///
+ /// 通过指定过滤条件查询并返回满足条件的第一个文档(加锁)。
+ ///
+ /// 文档实体类型。
+ /// 查询过滤条件。
+ /// 集合名称。
+ /// 满足条件的第一个文档,如果未找到则为 null。
+ public async FTask First(Expression> filter, string collection = null) where T : Entity
+ {
+ using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize))
+ {
+ var cursor = await GetCollection(collection).FindAsync(filter);
+
+ return await cursor.FirstOrDefaultAsync();
+ }
+ }
+
+ ///
+ /// 通过指定 JSON 格式查询并返回满足条件的第一个文档(加锁)。
+ ///
+ /// 文档实体类型。
+ /// JSON 查询条件。
+ /// 要查询的列名称数组。
+ /// 集合名称。
+ /// 满足条件的第一个文档。
+ public async FTask First(string json, string[] cols, string collection = null) where T : Entity
+ {
+ using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize))
+ {
+ var projection = Builders.Projection.Include("");
+
+ foreach (var col in cols)
+ {
+ projection = projection.Include(col);
+ }
+
+ var options = new FindOptions { Projection = projection };
+
+ FilterDefinition filterDefinition = new JsonFilterDefinition(json);
+
+ var cursor = await GetCollection(collection).FindAsync(filterDefinition, options);
+
+ return await cursor.FirstOrDefaultAsync();
+ }
+ }
+
+ ///
+ /// 通过指定过滤条件查询并返回满足条件的文档列表,并按指定表达式进行排序(加锁)。
+ ///
+ /// 文档实体类型。
+ /// 查询过滤条件。
+ /// 排序表达式。
+ /// 是否升序排序。
+ /// 集合名称。
+ /// 满足条件的文档列表。
+ public async FTask> QueryOrderBy(Expression> filter,
+ Expression> orderByExpression, bool isAsc = true, string collection = null) where T : Entity
+ {
+ using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize))
+ {
+ if (isAsc)
+ {
+ return await GetCollection(collection).Find(filter).SortBy(orderByExpression).ToListAsync();
+ }
+
+ return await GetCollection(collection).Find(filter).SortByDescending(orderByExpression)
+ .ToListAsync();
+ }
+ }
+
+ ///
+ /// 通过指定过滤条件查询并返回满足条件的文档列表(加锁)。
+ ///
+ /// 文档实体类型。
+ /// 查询过滤条件。
+ /// 集合名称。
+ /// 满足条件的文档列表。
+ public async FTask> Query(Expression> filter, string collection = null)
+ where T : Entity
+ {
+ using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize))
+ {
+ var cursor = await GetCollection(collection).FindAsync(filter);
+ var v = await cursor.ToListAsync();
+ return v;
+ }
+ }
+
+ ///
+ /// 根据指定 ID 加锁查询多个集合中的文档。
+ ///
+ /// 文档 ID。
+ /// 要查询的集合名称列表。
+ /// 查询结果存储列表。
+ public async FTask Query(long id, List? collectionNames, List result)
+ {
+ using (await _dataBaseLock.Wait(id))
+ {
+ if (collectionNames == null || collectionNames.Count == 0)
+ {
+ return;
+ }
+
+ foreach (var collectionName in collectionNames)
+ {
+ var cursor = await GetCollection(collectionName).FindAsync(d => d.Id == id);
+
+ var e = await cursor.FirstOrDefaultAsync();
+
+ if (e == null)
+ {
+ continue;
+ }
+
+ result.Add(e);
+ }
+ }
+ }
+
+ ///
+ /// 根据指定的 JSON 查询条件查询并返回满足条件的文档列表(加锁)。
+ ///
+ /// 文档实体类型。
+ /// JSON 查询条件。
+ /// 集合名称。
+ /// 满足条件的文档列表。
+ public async FTask> QueryJson(string json, string collection = null) where T : Entity
+ {
+ using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize))
+ {
+ FilterDefinition filterDefinition = new JsonFilterDefinition(json);
+ var cursor = await GetCollection(collection).FindAsync(filterDefinition);
+ var v = await cursor.ToListAsync();
+ return v;
+ }
+ }
+
+ ///
+ /// 根据指定的 JSON 查询条件查询并返回满足条件的文档列表,并选择指定的列(加锁)。
+ ///
+ /// 文档实体类型。
+ /// JSON 查询条件。
+ /// 要查询的列名称数组。
+ /// 集合名称。
+ /// 满足条件的文档列表。
+ public async FTask> QueryJson(string json, string[] cols, string collection = null) where T : Entity
+ {
+ using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize))
+ {
+ var projection = Builders.Projection.Include("");
+
+ foreach (var col in cols)
+ {
+ projection = projection.Include(col);
+ }
+
+ var options = new FindOptions { Projection = projection };
+
+ FilterDefinition filterDefinition = new JsonFilterDefinition(json);
+
+ var cursor = await GetCollection(collection).FindAsync(filterDefinition, options);
+ var v = await cursor.ToListAsync();
+ return v;
+ }
+ }
+
+ ///
+ /// 根据指定的 JSON 查询条件和任务 ID 查询并返回满足条件的文档列表(加锁)。
+ ///
+ /// 文档实体类型。
+ /// 任务 ID。
+ /// JSON 查询条件。
+ /// 集合名称。
+ /// 满足条件的文档列表。
+ public async FTask> QueryJson(long taskId, string json, string collection = null) where T : Entity
+ {
+ using (await _dataBaseLock.Wait(taskId))
+ {
+ FilterDefinition filterDefinition = new JsonFilterDefinition(json);
+ var cursor = await GetCollection(collection).FindAsync(filterDefinition);
+ var v = await cursor.ToListAsync();
+ return v;
+ }
+ }
+
+ ///
+ /// 根据指定过滤条件查询并返回满足条件的文档列表,选择指定的列(加锁)。
+ ///
+ /// 文档实体类型。
+ /// 查询过滤条件。
+ /// 要查询的列名称数组。
+ /// 集合名称。
+ /// 满足条件的文档列表。
+ public async FTask> Query(Expression> filter, string[] cols, string collection = null)
+ where T : class
+ {
+ using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize))
+ {
+ var projection = Builders.Projection.Include(cols[0]);
+
+ for (var i = 1; i < cols.Length; i++)
+ {
+ projection = projection.Include(cols[i]);
+ }
+
+ return await GetCollection(collection).Find(filter).Project(projection).ToListAsync();
+ }
+ }
+
+ #endregion
+
+ #region Save
+
+ ///
+ /// 保存实体对象到数据库(加锁)。
+ ///
+ /// 实体类型。
+ /// 事务会话对象。
+ /// 要保存的实体对象。
+ /// 集合名称。
+ public async FTask Save(object transactionSession, T? entity, string collection = null) where T : Entity
+ {
+ if (entity == null)
+ {
+ Log.Error($"save entity is null: {typeof(T).Name}");
+ return;
+ }
+
+ var clone = _serializer.Clone(entity);
+
+ using (await _dataBaseLock.Wait(clone.Id))
+ {
+ await GetCollection(collection ?? clone.GetType().Name).ReplaceOneAsync(
+ (IClientSessionHandle)transactionSession, d => d.Id == clone.Id, clone,
+ new ReplaceOptions { IsUpsert = true });
+ }
+ }
+
+ ///
+ /// 保存实体对象到数据库(加锁)。
+ ///
+ /// 实体类型。
+ /// 要保存的实体对象。
+ /// 集合名称。
+ public async FTask Save(T? entity, string collection = null) where T : Entity, new()
+ {
+ if (entity == null)
+ {
+ Log.Error($"save entity is null: {typeof(T).Name}");
+
+ return;
+ }
+
+ var clone = _serializer.Clone(entity);
+
+ using (await _dataBaseLock.Wait(clone.Id))
+ {
+ await GetCollection(collection ?? clone.GetType().Name).ReplaceOneAsync(d => d.Id == clone.Id, clone,
+ new ReplaceOptions { IsUpsert = true });
+ }
+ }
+
+ ///
+ /// 保存多个实体对象到数据库(加锁)。
+ ///
+ /// 文档 ID。
+ /// 要保存的实体对象列表。
+ public async FTask Save(long id, List? entities)
+ {
+ if (entities == null || entities.Count == 0)
+ {
+ Log.Error("save entity is null");
+ return;
+ }
+
+ using var listPool = ListPool.Create();
+
+ foreach (var entity in entities)
+ {
+ listPool.Add(_serializer.Clone(entity));
+ }
+
+ using (await _dataBaseLock.Wait(id))
+ {
+ foreach (var clone in listPool)
+ {
+ try
+ {
+ await GetCollection(clone.GetType().Name).ReplaceOneAsync(d => d.Id == clone.Id, clone,
+ new ReplaceOptions { IsUpsert = true });
+ }
+ catch (Exception e)
+ {
+ Log.Error($"Save List Entity Error: {clone.GetType().Name} {clone}\n{e}");
+ }
+ }
+ }
+ }
+
+ #endregion
+
+ #region Insert
+
+ ///
+ /// 插入单个实体对象到数据库(加锁)。
+ ///
+ /// 实体类型。
+ /// 要插入的实体对象。
+ /// 集合名称。
+ public FTask Insert(T entity, string collection = null) where T : Entity, new()
+ {
+ return Save(entity);
+ }
+
+ ///
+ /// 批量插入实体对象列表到数据库(加锁)。
+ ///
+ /// 实体类型。
+ /// 要插入的实体对象列表。
+ /// 集合名称。
+ public async FTask InsertBatch(IEnumerable list, string collection = null) where T : Entity, new()
+ {
+ using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize))
+ {
+ await GetCollection(collection ?? typeof(T).Name).InsertManyAsync(list);
+ }
+ }
+
+ ///
+ /// 批量插入实体对象列表到数据库(加锁)。
+ ///
+ /// 实体类型。
+ /// 事务会话对象。
+ /// 要插入的实体对象列表。
+ /// 集合名称。
+ public async FTask InsertBatch(object transactionSession, IEnumerable list, string collection = null)
+ where T : Entity, new()
+ {
+ using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize))
+ {
+ await GetCollection(collection ?? typeof(T).Name)
+ .InsertManyAsync((IClientSessionHandle)transactionSession, list);
+ }
+ }
+
+ #endregion
+
+ #region Remove
+
+ ///
+ /// 根据ID删除单个实体对象(加锁)。
+ ///
+ /// 实体类型。
+ /// 事务会话对象。
+ /// 要删除的实体的ID。
+ /// 集合名称。
+ /// 删除的实体数量。
+ public async FTask Remove(object transactionSession, long id, string collection = null)
+ where T : Entity, new()
+ {
+ using (await _dataBaseLock.Wait(id))
+ {
+ var result = await GetCollection(collection)
+ .DeleteOneAsync((IClientSessionHandle)transactionSession, d => d.Id == id);
+ return result.DeletedCount;
+ }
+ }
+
+ ///
+ /// 根据ID删除单个实体对象(加锁)。
+ ///
+ /// 实体类型。
+ /// 要删除的实体的ID。
+ /// 集合名称。
+ /// 删除的实体数量。
+ public async FTask Remove(long id, string collection = null) where T : Entity, new()
+ {
+ using (await _dataBaseLock.Wait(id))
+ {
+ var result = await GetCollection(collection).DeleteOneAsync(d => d.Id == id);
+ return result.DeletedCount;
+ }
+ }
+
+ ///
+ /// 根据ID和筛选条件删除多个实体对象(加锁)。
+ ///
+ /// 实体类型。
+ /// 异步锁Id。
+ /// 事务会话对象。
+ /// 筛选条件。
+ /// 集合名称。
+ /// 删除的实体数量。
+ public async FTask Remove(long coroutineLockQueueKey, object transactionSession,
+ Expression> filter, string collection = null) where T : Entity, new()
+ {
+ using (await _dataBaseLock.Wait(coroutineLockQueueKey))
+ {
+ var result = await GetCollection(collection)
+ .DeleteManyAsync((IClientSessionHandle)transactionSession, filter);
+ return result.DeletedCount;
+ }
+ }
+
+ ///
+ /// 根据ID和筛选条件删除多个实体对象(加锁)。
+ ///
+ /// 实体类型。
+ /// 异步锁Id。
+ /// 筛选条件。
+ /// 集合名称。
+ /// 删除的实体数量。
+ public async FTask Remove(long coroutineLockQueueKey, Expression> filter,
+ string collection = null) where T : Entity, new()
+ {
+ using (await _dataBaseLock.Wait(coroutineLockQueueKey))
+ {
+ var result = await GetCollection(collection).DeleteManyAsync(filter);
+ return result.DeletedCount;
+ }
+ }
+
+ #endregion
+
+ #region Index
+
+ ///
+ /// 创建数据库索引(加锁)。
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// 使用例子(可多个):
+ /// 1 : Builders.IndexKeys.Ascending(d=>d.Id)
+ /// 2 : Builders.IndexKeys.Descending(d=>d.Id).Ascending(d=>d.Name)
+ /// 3 : Builders.IndexKeys.Descending(d=>d.Id),Builders.IndexKeys.Descending(d=>d.Name)
+ ///
+ public async FTask CreateIndex(string collection, params object[]? keys) where T : Entity
+ {
+ if (keys == null || keys.Length <= 0)
+ {
+ return;
+ }
+
+ var indexModels = new List>();
+
+ foreach (object key in keys)
+ {
+ IndexKeysDefinition indexKeysDefinition = (IndexKeysDefinition)key;
+
+ indexModels.Add(new CreateIndexModel(indexKeysDefinition));
+ }
+
+ await GetCollection(collection).Indexes.CreateManyAsync(indexModels);
+ }
+
+ ///
+ /// 创建数据库的索引(加锁)。
+ ///
+ /// 实体类型。
+ /// 索引键定义。
+ public async FTask CreateIndex(params object[]? keys) where T : Entity
+ {
+ if (keys == null)
+ {
+ return;
+ }
+
+ List> indexModels = new List>();
+
+ foreach (object key in keys)
+ {
+ IndexKeysDefinition indexKeysDefinition = (IndexKeysDefinition)key;
+
+ indexModels.Add(new CreateIndexModel(indexKeysDefinition));
+ }
+
+ await GetCollection().Indexes.CreateManyAsync(indexModels);
+ }
+
+ #endregion
+
+ #region CreateDB
+
+ ///
+ /// 创建数据库集合(如果不存在)。
+ ///
+ /// 实体类型。
+ public async FTask CreateDB() where T : Entity
+ {
+ // 已经存在数据库表
+ string name = typeof(T).Name;
+
+ if (_collections.Contains(name))
+ {
+ return;
+ }
+
+ await _mongoDatabase.CreateCollectionAsync(name);
+
+ _collections.Add(name);
+ }
+
+ ///
+ /// 创建数据库集合(如果不存在)。
+ ///
+ /// 实体类型。
+ public async FTask CreateDB(Type type)
+ {
+ string name = type.Name;
+
+ if (_collections.Contains(name))
+ {
+ return;
+ }
+
+ await _mongoDatabase.CreateCollectionAsync(name);
+
+ _collections.Add(name);
+ }
+
+ #endregion
+ }
+}
+#endif
\ No newline at end of file
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataBase/MongoDataBase.cs.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataBase/MongoDataBase.cs.meta
new file mode 100644
index 00000000..b26748a9
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataBase/MongoDataBase.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 366dcc82090f0466da2c2b8d0e6fa2c2
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataBase/World.cs b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataBase/World.cs
new file mode 100644
index 00000000..d0a1b1a9
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataBase/World.cs
@@ -0,0 +1,83 @@
+#pragma warning disable CS8603 // Possible null reference return.
+#if FANTASY_NET
+using Fantasy.Platform.Net;
+
+namespace Fantasy.DataBase
+{
+ ///
+ /// 表示一个游戏世界。
+ ///
+ public sealed class World
+ {
+ ///
+ /// 获取游戏世界的唯一标识。
+ ///
+ public byte Id { get; private init; }
+ ///
+ /// 获取游戏世界的数据库接口。
+ ///
+ public IDataBase DataBase { get; private init; }
+ ///
+ /// 获取游戏世界的配置信息。
+ ///
+ public WorldConfig Config => WorldConfigData.Instance.Get(Id);
+ ///
+ /// 用于存储已创建的游戏世界实例
+ ///
+ private static readonly Dictionary Worlds = new();
+
+ ///
+ /// 使用指定的配置信息创建一个游戏世界实例。
+ ///
+ ///
+ ///
+ public World(Scene scene, byte worldConfigId)
+ {
+ Id = worldConfigId;
+ var worldConfig = Config;
+ var dbType = worldConfig.DbType.ToLower();
+
+ switch (dbType)
+ {
+ case "mongodb":
+ {
+ DataBase = new MongoDataBase();
+ DataBase.Initialize(scene, worldConfig.DbConnection, worldConfig.DbName);
+ break;
+ }
+ default:
+ throw new Exception("No supported database");
+ }
+ }
+
+ ///
+ /// 创建一个指定唯一标识的游戏世界实例。
+ ///
+ ///
+ /// 游戏世界的唯一标识。
+ /// 游戏世界实例。
+ public static World Create(Scene scene, byte id)
+ {
+ if (Worlds.TryGetValue(id, out var world))
+ {
+ return world;
+ }
+
+ if (!WorldConfigData.Instance.TryGet(id, out var worldConfigData))
+ {
+ return null;
+ }
+
+ if (string.IsNullOrEmpty(worldConfigData.DbConnection))
+ {
+ return null;
+ }
+
+ world = new World(scene, id);
+ Worlds.Add(id, world);
+ return world;
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataBase/World.cs.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataBase/World.cs.meta
new file mode 100644
index 00000000..00bf0522
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataBase/World.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6f02362ef34274e508386e3cdc247927
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataStructure.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataStructure.meta
new file mode 100644
index 00000000..9aa09d41
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataStructure.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: afe876696fb5f4779abe3289a3bf4f9f
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataStructure/Collection.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataStructure/Collection.meta
new file mode 100644
index 00000000..04e1280a
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataStructure/Collection.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 0b3d477f03f404310b5ad48f89d1f193
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataStructure/Collection/CircularBuffer.cs b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataStructure/Collection/CircularBuffer.cs
new file mode 100644
index 00000000..d80ec552
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataStructure/Collection/CircularBuffer.cs
@@ -0,0 +1,346 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
+#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+
+namespace Fantasy.DataStructure.Collection
+{
+ /// 环形缓存(自增式缓存,自动扩充、不会收缩缓存、所以不要用这个操作过大的IO流)
+ /// 1、环大小8192,溢出的会自动增加环的大小。
+ /// 2、每个块都是一个环形缓存,当溢出的时候会自动添加到下一个环中。
+ /// 3、当读取完成后用过的环会放在缓存中,不会销毁掉。
+ ///
+ /// 自增式缓存类,继承自 Stream 和 IDisposable 接口。
+ /// 环形缓存具有自动扩充的特性,但不会收缩,适用于操作不过大的 IO 流。
+ ///
+ public sealed class CircularBuffer : Stream, IDisposable
+ {
+ private byte[] _lastBuffer;
+ ///
+ /// 环形缓存块的默认大小
+ ///
+ public const int ChunkSize = 8192;
+ private readonly Queue _bufferCache = new Queue();
+ private readonly Queue _bufferQueue = new Queue();
+ ///
+ /// 获取或设置环形缓存的第一个索引位置
+ ///
+ public int FirstIndex { get; set; }
+ ///
+ /// 获取或设置环形缓存的最后一个索引位置
+ ///
+ public int LastIndex { get; set; }
+ ///
+ /// 获取环形缓存的总长度
+ ///
+ public override long Length
+ {
+ get
+ {
+ if (_bufferQueue.Count == 0)
+ {
+ return 0;
+ }
+
+ return (_bufferQueue.Count - 1) * ChunkSize + LastIndex - FirstIndex;
+ }
+ }
+
+ ///
+ /// 获取环形缓存的第一个块
+ ///
+ public byte[] First
+ {
+ get
+ {
+ if (_bufferQueue.Count == 0)
+ {
+ AddLast();
+ }
+
+ return _bufferQueue.Peek();
+ }
+ }
+
+ ///
+ /// 获取环形缓存的最后一个块
+ ///
+ public byte[] Last
+ {
+ get
+ {
+ if (_bufferQueue.Count == 0)
+ {
+ AddLast();
+ }
+
+ return _lastBuffer;
+ }
+ }
+ ///
+ /// 向环形缓存中添加一个新的块
+ ///
+ public void AddLast()
+ {
+ var buffer = _bufferCache.Count > 0 ? _bufferCache.Dequeue() : new byte[ChunkSize];
+ _bufferQueue.Enqueue(buffer);
+ _lastBuffer = buffer;
+ }
+ ///
+ /// 从环形缓存中移除第一个块
+ ///
+ public void RemoveFirst()
+ {
+ _bufferCache.Enqueue(_bufferQueue.Dequeue());
+ }
+
+ ///
+ /// 从流中读取指定数量的数据到缓存。
+ ///
+ /// 源数据流。
+ /// 要读取的字节数。
+ public void Read(Stream stream, int count)
+ {
+ if (count > Length)
+ {
+ throw new Exception($"bufferList length < count, {Length} {count}");
+ }
+
+ var copyCount = 0;
+ while (copyCount < count)
+ {
+ var n = count - copyCount;
+ if (ChunkSize - FirstIndex > n)
+ {
+ stream.Write(First, FirstIndex, n);
+ FirstIndex += n;
+ copyCount += n;
+ }
+ else
+ {
+ stream.Write(First, FirstIndex, ChunkSize - FirstIndex);
+ copyCount += ChunkSize - FirstIndex;
+ FirstIndex = 0;
+ RemoveFirst();
+ }
+ }
+ }
+
+ ///
+ /// 从缓存中读取指定数量的数据到内存。
+ ///
+ /// 目标内存。
+ /// 要读取的字节数。
+ public void Read(Memory memory, int count)
+ {
+ if (count > Length)
+ {
+ throw new Exception($"bufferList length < count, {Length} {count}");
+ }
+
+ var copyCount = 0;
+ while (copyCount < count)
+ {
+ var n = count - copyCount;
+ var asMemory = First.AsMemory();
+
+ if (ChunkSize - FirstIndex > n)
+ {
+ var slice = asMemory.Slice(FirstIndex, n);
+ slice.CopyTo(memory.Slice(copyCount, n));
+ FirstIndex += n;
+ copyCount += n;
+ }
+ else
+ {
+ var length = ChunkSize - FirstIndex;
+ var slice = asMemory.Slice(FirstIndex, length);
+ slice.CopyTo(memory.Slice(copyCount, length));
+ copyCount += ChunkSize - FirstIndex;
+ FirstIndex = 0;
+ RemoveFirst();
+ }
+ }
+ }
+
+ ///
+ /// 从自定义流中读取数据到指定的缓冲区。
+ ///
+ /// 目标缓冲区,用于存储读取的数据。
+ /// 目标缓冲区中的起始偏移量。
+ /// 要读取的字节数。
+ /// 实际读取的字节数。
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ if (buffer.Length < offset + count)
+ {
+ throw new Exception($"buffer length < count, buffer length: {buffer.Length} {offset} {count}");
+ }
+
+ var length = Length;
+ if (length < count)
+ {
+ count = (int) length;
+ }
+
+ var copyCount = 0;
+
+ // 循环直到成功读取所需的字节数
+ while (copyCount < count)
+ {
+ var copyLength = count - copyCount;
+
+ if (ChunkSize - FirstIndex > copyLength)
+ {
+ // 将数据从当前块的缓冲区复制到目标缓冲区
+ Array.Copy(First, FirstIndex, buffer, copyCount + offset, copyLength);
+
+ FirstIndex += copyLength;
+ copyCount += copyLength;
+ continue;
+ }
+
+ // 复制当前块中剩余的数据,并切换到下一个块
+ Array.Copy(First, FirstIndex, buffer, copyCount + offset, ChunkSize - FirstIndex);
+ copyCount += ChunkSize - FirstIndex;
+ FirstIndex = 0;
+
+ RemoveFirst();
+ }
+
+ return count;
+ }
+
+ ///
+ /// 将数据从给定的字节数组写入流中。
+ ///
+ /// 包含要写入的数据的字节数组。
+ public void Write(byte[] buffer)
+ {
+ Write(buffer, 0, buffer.Length);
+ }
+
+ ///
+ /// 将数据从给定的流写入流中。
+ ///
+ /// 包含要写入的数据的流。
+ public void Write(Stream stream)
+ {
+ var copyCount = 0;
+ var count = (int) (stream.Length - stream.Position);
+
+ while (copyCount < count)
+ {
+ if (LastIndex == ChunkSize)
+ {
+ AddLast();
+ LastIndex = 0;
+ }
+
+ var n = count - copyCount;
+
+ if (ChunkSize - LastIndex > n)
+ {
+ _ = stream.Read(Last, LastIndex, n);
+ LastIndex += count - copyCount;
+ copyCount += n;
+ }
+ else
+ {
+ _ = stream.Read(Last, LastIndex, ChunkSize - LastIndex);
+ copyCount += ChunkSize - LastIndex;
+ LastIndex = ChunkSize;
+ }
+ }
+ }
+
+ ///
+ /// 将数据从给定的字节数组写入流中。
+ ///
+ /// 包含要写入的数据的字节数组。
+ /// 开始写入的缓冲区中的索引。
+ /// 要写入的字节数。
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ var copyCount = 0;
+
+ while (copyCount < count)
+ {
+ if (ChunkSize == LastIndex)
+ {
+ AddLast();
+ LastIndex = 0;
+ }
+
+ var byteLength = count - copyCount;
+
+ if (ChunkSize - LastIndex > byteLength)
+ {
+ Array.Copy(buffer, copyCount + offset, Last, LastIndex, byteLength);
+ LastIndex += byteLength;
+ copyCount += byteLength;
+ }
+ else
+ {
+ Array.Copy(buffer, copyCount + offset, Last, LastIndex, ChunkSize - LastIndex);
+ copyCount += ChunkSize - LastIndex;
+ LastIndex = ChunkSize;
+ }
+ }
+ }
+
+ ///
+ /// 获取一个值,指示流是否支持读取操作。
+ ///
+ public override bool CanRead { get; } = true;
+ ///
+ /// 获取一个值,指示流是否支持寻找操作。
+ ///
+ public override bool CanSeek { get; } = false;
+ ///
+ /// 获取一个值,指示流是否支持写入操作。
+ ///
+ public override bool CanWrite { get; } = true;
+ ///
+ /// 获取或设置流中的位置。
+ ///
+ public override long Position { get; set; }
+
+ ///
+ /// 刷新流(在此实现中引发未实现异常)。
+ ///
+ public override void Flush()
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// 在流中寻找特定位置(在此实现中引发未实现异常)。
+ ///
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// 设置流的长度(在此实现中引发未实现异常)。
+ ///
+ public override void SetLength(long value)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// 释放 CustomStream 使用的所有资源。
+ ///
+ public new void Dispose()
+ {
+ _bufferQueue.Clear();
+ _lastBuffer = null;
+ FirstIndex = 0;
+ LastIndex = 0;
+ base.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataStructure/Collection/CircularBuffer.cs.meta b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataStructure/Collection/CircularBuffer.cs.meta
new file mode 100644
index 00000000..ee284a26
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataStructure/Collection/CircularBuffer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 38264758f23d1448a975efc4f4b7da63
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataStructure/Collection/ConcurrentOneToManyListPool.cs b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataStructure/Collection/ConcurrentOneToManyListPool.cs
new file mode 100644
index 00000000..e110a0b3
--- /dev/null
+++ b/Fantasy.Unity/Fantasy.Unity.UniTask/Runtime/Core/DataStructure/Collection/ConcurrentOneToManyListPool.cs
@@ -0,0 +1,197 @@
+#if !FANTASY_WEBGL
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using Fantasy.Pool;
+
+#pragma warning disable CS8603 // Possible null reference return.
+
+namespace Fantasy.DataStructure.Collection
+{
+ ///
+ /// 并发的一对多列表池,用于维护具有相同键的多个值的关联关系,实现了 接口。
+ ///
+ /// 关键字的类型,不能为空。
+ /// 值的类型。
+ public class ConcurrentOneToManyListPool : ConcurrentOneToManyList, IDisposable, IPool where TKey : notnull
+ {
+ private bool _isPool;
+ private bool _isDispose;
+
+ ///
+ /// 创建一个 的实例。
+ ///
+ /// 创建的实例。
+ public static ConcurrentOneToManyListPool Create()
+ {
+ var a = MultiThreadPool.Rent>();
+ a._isDispose = false;
+ a._isPool = true;
+ return a;
+ }
+
+ ///
+ /// 释放实例占用的资源。
+ ///
+ public void Dispose()
+ {
+ if (_isDispose)
+ {
+ return;
+ }
+
+ _isDispose = true;
+ // 清空实例的数据
+ Clear();
+ // 将实例返回到池中以便重用
+ MultiThreadPool.Return(this);
+ }
+
+ ///
+ /// 获取一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public bool IsPool()
+ {
+ return _isPool;
+ }
+
+ ///
+ /// 设置一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public void SetIsPool(bool isPool)
+ {
+ _isPool = isPool;
+ }
+ }
+
+ ///
+ /// 并发的一对多列表,用于维护具有相同键的多个值的关联关系。
+ ///
+ /// 关键字的类型,不能为空。
+ /// 值的类型。
+ public class ConcurrentOneToManyList : ConcurrentDictionary> where TKey : notnull
+ {
+ private readonly Queue> _queue = new Queue>();
+ private readonly int _recyclingLimit = 120;
+
+ ///
+ /// 初始化 类的新实例。
+ ///
+ public ConcurrentOneToManyList()
+ {
+ }
+
+ ///
+ /// 设置最大缓存数量
+ ///
+ ///
+ /// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
+ /// 2:设置成0不控制数量,全部缓存
+ ///
+ public ConcurrentOneToManyList(int recyclingLimit)
+ {
+ _recyclingLimit = recyclingLimit;
+ }
+
+ ///
+ /// 判断指定键的列表是否包含指定值。
+ ///
+ /// 要搜索的键。
+ /// 要搜索的值。
+ /// 如果列表包含值,则为 true;否则为 false。
+ public bool Contains(TKey key, TValue value)
+ {
+ TryGetValue(key, out var list);
+
+ return list != null && list.Contains(value);
+ }
+
+ ///
+ /// 向指定键的列表中添加一个值。
+ ///
+ /// 要添加值的键。
+ /// 要添加的值。
+ public void Add(TKey key, TValue value)
+ {
+ if (!TryGetValue(key, out var list))
+ {
+ list = Fetch();
+ list.Add(value);
+ base[key] = list;
+ return;
+ }
+
+ list.Add(value);
+ }
+
+ ///
+ /// 获取指定键的列表中的第一个值。
+ ///
+ /// 要获取第一个值的键。
+ /// 指定键的列表中的第一个值,如果不存在则为默认值。
+ public TValue First(TKey key)
+ {
+ return !TryGetValue(key, out var list) ? default : list.FirstOrDefault();
+ }
+
+ ///
+ /// 从指定键的列表中移除一个值。
+ ///
+ /// 要移除值的键。
+ /// 要移除的值。
+ public void RemoveValue(TKey key, TValue value)
+ {
+ if (!TryGetValue(key, out var list)) return;
+
+ list.Remove(value);
+
+ if (list.Count == 0) RemoveKey(key);
+ }
+
+ ///
+ /// 从字典中移除指定键以及其关联的列表。
+ ///
+ /// 要移除的键。
+ public void RemoveKey(TKey key)
+ {
+ if (!TryRemove(key, out var list)) return;
+
+ Recycle(list);
+ }
+
+ ///
+ /// 从队列中获取一个列表,如果队列为空则创建一个新的列表。
+ ///
+ /// 获取的列表。
+ private List Fetch()
+ {
+ return _queue.Count <= 0 ? new List() : _queue.Dequeue();
+ }
+
+ ///