diff --git a/Editor/CoreEditor.meta b/Editor/CoreEditor.meta
new file mode 100644
index 0000000..1956489
--- /dev/null
+++ b/Editor/CoreEditor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 77313bd6dcc91f645a147a10abedadc3
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/CoreEditor/Runtime.meta b/Editor/CoreEditor/Runtime.meta
new file mode 100644
index 0000000..de03432
--- /dev/null
+++ b/Editor/CoreEditor/Runtime.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e8ba5b17cffbb4ffea892f674fc8629f
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/CoreEditor/Runtime/CheckUnityVersion.cs b/Editor/CoreEditor/Runtime/CheckUnityVersion.cs
new file mode 100644
index 0000000..34f112c
--- /dev/null
+++ b/Editor/CoreEditor/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/Editor/CoreEditor/Runtime/CheckUnityVersion.cs.meta b/Editor/CoreEditor/Runtime/CheckUnityVersion.cs.meta
new file mode 100644
index 0000000..1f13ace
--- /dev/null
+++ b/Editor/CoreEditor/Runtime/CheckUnityVersion.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 455f338921e74471841971fd6b79db01
+timeCreated: 1725943424
\ No newline at end of file
diff --git a/Editor/CoreEditor/Runtime/FantasyStartup.cs b/Editor/CoreEditor/Runtime/FantasyStartup.cs
new file mode 100644
index 0000000..c541657
--- /dev/null
+++ b/Editor/CoreEditor/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/Editor/CoreEditor/Runtime/FantasyStartup.cs.meta b/Editor/CoreEditor/Runtime/FantasyStartup.cs.meta
new file mode 100644
index 0000000..abcd77d
--- /dev/null
+++ b/Editor/CoreEditor/Runtime/FantasyStartup.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 42156ba2865a4aa4a3e1e57b3ac9b984
+timeCreated: 1688276977
\ No newline at end of file
diff --git a/Editor/CoreEditor/Runtime/LinkXmlGenerator.cs b/Editor/CoreEditor/Runtime/LinkXmlGenerator.cs
new file mode 100644
index 0000000..155d0f8
--- /dev/null
+++ b/Editor/CoreEditor/Runtime/LinkXmlGenerator.cs
@@ -0,0 +1,52 @@
+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("");
+
+ foreach (var assembly in FantasySettingsScriptableObject.Instance.includeAssembly)
+ {
+ GenerateLinkXml(writer, assembly, LinkPath);
+ Debug.Log($"{assembly} Link generation completed");
+ }
+
+ if (FantasySettingsScriptableObject.Instance?.linkAssemblyDefinitions != null)
+ {
+ 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/Editor/CoreEditor/Runtime/LinkXmlGenerator.cs.meta b/Editor/CoreEditor/Runtime/LinkXmlGenerator.cs.meta
new file mode 100644
index 0000000..dbab220
--- /dev/null
+++ b/Editor/CoreEditor/Runtime/LinkXmlGenerator.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: cda4c9403de946df9c31654416193a21
+timeCreated: 1722743236
\ No newline at end of file
diff --git a/Editor/CoreEditor/Runtime/Settings.meta b/Editor/CoreEditor/Runtime/Settings.meta
new file mode 100644
index 0000000..6b097c8
--- /dev/null
+++ b/Editor/CoreEditor/Runtime/Settings.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 3a6997d946f3400e8c423fe1b9245f65
+timeCreated: 1688277110
\ No newline at end of file
diff --git a/Editor/CoreEditor/Runtime/Settings/FantasySettings.cs b/Editor/CoreEditor/Runtime/Settings/FantasySettings.cs
new file mode 100644
index 0000000..4c85dc7
--- /dev/null
+++ b/Editor/CoreEditor/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/Editor/CoreEditor/Runtime/Settings/FantasySettings.cs.meta b/Editor/CoreEditor/Runtime/Settings/FantasySettings.cs.meta
new file mode 100644
index 0000000..852e825
--- /dev/null
+++ b/Editor/CoreEditor/Runtime/Settings/FantasySettings.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 977a7c172c30403da60286ba39b7bc72
+timeCreated: 1686913667
\ No newline at end of file
diff --git a/Editor/CoreEditor/Runtime/Settings/FantasySettingsProvider.cs b/Editor/CoreEditor/Runtime/Settings/FantasySettingsProvider.cs
new file mode 100644
index 0000000..11ae01c
--- /dev/null
+++ b/Editor/CoreEditor/Runtime/Settings/FantasySettingsProvider.cs
@@ -0,0 +1,102 @@
+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;
+ private SerializedProperty _includeAssembly;
+ 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");
+ _includeAssembly = _serializedObject.FindProperty("includeAssembly");
+ }
+
+ 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(_includeAssembly);
+ EditorGUILayout.PropertyField(_linkAssemblyDefinitions);
+ // EditorGUILayout.HelpBox("默认包括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/Editor/CoreEditor/Runtime/Settings/FantasySettingsProvider.cs.meta b/Editor/CoreEditor/Runtime/Settings/FantasySettingsProvider.cs.meta
new file mode 100644
index 0000000..72e475d
--- /dev/null
+++ b/Editor/CoreEditor/Runtime/Settings/FantasySettingsProvider.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 047b2f13e73f413fa000bf7be979fb4a
+timeCreated: 1688380387
\ No newline at end of file
diff --git a/Editor/CoreEditor/Runtime/Settings/FantasySettingsScriptableObject.cs b/Editor/CoreEditor/Runtime/Settings/FantasySettingsScriptableObject.cs
new file mode 100644
index 0000000..5d03955
--- /dev/null
+++ b/Editor/CoreEditor/Runtime/Settings/FantasySettingsScriptableObject.cs
@@ -0,0 +1,25 @@
+using UnityEditor;
+using UnityEditor.Compilation;
+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;
+ [FormerlySerializedAs("IncludeAssembly")] [Header("生成Link.xml时候默认包含的程序集")]
+ public string[] includeAssembly = new[] { "Assembly-CSharp", "Fantasy.Unity" };
+ public void OnBeforeSerialize() { }
+ public void OnAfterDeserialize() { }
+ }
+}
\ No newline at end of file
diff --git a/Editor/CoreEditor/Runtime/Settings/FantasySettingsScriptableObject.cs.meta b/Editor/CoreEditor/Runtime/Settings/FantasySettingsScriptableObject.cs.meta
new file mode 100644
index 0000000..b5bae5a
--- /dev/null
+++ b/Editor/CoreEditor/Runtime/Settings/FantasySettingsScriptableObject.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 27a37e930ca3454fb57bc895f50d2106
+timeCreated: 1688277120
\ No newline at end of file
diff --git a/Editor/CoreEditor/Runtime/Settings/ScriptableObjectSingleton.cs b/Editor/CoreEditor/Runtime/Settings/ScriptableObjectSingleton.cs
new file mode 100644
index 0000000..6206676
--- /dev/null
+++ b/Editor/CoreEditor/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/Editor/CoreEditor/Runtime/Settings/ScriptableObjectSingleton.cs.meta b/Editor/CoreEditor/Runtime/Settings/ScriptableObjectSingleton.cs.meta
new file mode 100644
index 0000000..5105c6f
--- /dev/null
+++ b/Editor/CoreEditor/Runtime/Settings/ScriptableObjectSingleton.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 3c77f5208dc14542ae7497d59321ef76
+timeCreated: 1688278016
\ No newline at end of file
diff --git a/Editor/CoreEditor/Runtime/WSocket.meta b/Editor/CoreEditor/Runtime/WSocket.meta
new file mode 100644
index 0000000..21883b6
--- /dev/null
+++ b/Editor/CoreEditor/Runtime/WSocket.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: b9e5c7d1436ec414fa3f69a23aaafc3b
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/CoreEditor/Runtime/WSocket/SettingsWindow.cs b/Editor/CoreEditor/Runtime/WSocket/SettingsWindow.cs
new file mode 100644
index 0000000..0313b0e
--- /dev/null
+++ b/Editor/CoreEditor/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/Editor/CoreEditor/Runtime/WSocket/SettingsWindow.cs.meta b/Editor/CoreEditor/Runtime/WSocket/SettingsWindow.cs.meta
new file mode 100644
index 0000000..7412468
--- /dev/null
+++ b/Editor/CoreEditor/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/Runtime/CoreRuntime.meta b/Runtime/CoreRuntime.meta
new file mode 100644
index 0000000..ded1d9b
--- /dev/null
+++ b/Runtime/CoreRuntime.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: dcbd0e41955f82b45bab54ebc1183deb
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/CoreRuntime/Core.meta b/Runtime/CoreRuntime/Core.meta
new file mode 100644
index 0000000..112dbca
--- /dev/null
+++ b/Runtime/CoreRuntime/Core.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 4e4623e6f2b7ffd41b068edd3c9568d9
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/CoreRuntime/Core/Assembly.meta b/Runtime/CoreRuntime/Core/Assembly.meta
new file mode 100644
index 0000000..e0bc296
--- /dev/null
+++ b/Runtime/CoreRuntime/Core/Assembly.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 5006194ae42edaa4bb33cb6172c123ab
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/CoreRuntime/Core/Assembly/AssemblyInfo.cs b/Runtime/CoreRuntime/Core/Assembly/AssemblyInfo.cs
new file mode 100644
index 0000000..3957539
--- /dev/null
+++ b/Runtime/CoreRuntime/Core/Assembly/AssemblyInfo.cs
@@ -0,0 +1,89 @@
+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 ReLoad(System.Reflection.Assembly assembly)
+ {
+ Unload();
+ Load(assembly);
+ }
+
+ ///
+ /// 卸载程序集的类型信息。
+ ///
+ public void Unload()
+ {
+ AssemblyTypeList.Clear();
+ AssemblyTypeGroupList.Clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/CoreRuntime/Core/Assembly/AssemblyInfo.cs.meta b/Runtime/CoreRuntime/Core/Assembly/AssemblyInfo.cs.meta
new file mode 100644
index 0000000..f1bb2d8
--- /dev/null
+++ b/Runtime/CoreRuntime/Core/Assembly/AssemblyInfo.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6c962fd73de88d64fa9d1ab97ed270b6
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/CoreRuntime/Core/Assembly/AssemblySystem.cs b/Runtime/CoreRuntime/Core/Assembly/AssemblySystem.cs
new file mode 100644
index 0000000..deb9da6
--- /dev/null
+++ b/Runtime/CoreRuntime/Core/Assembly/AssemblySystem.cs
@@ -0,0 +1,285 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Security.Cryptography;
+using System.Text;
+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 ConcurrentQueue AssemblySystems = new ConcurrentQueue();
+ private static readonly ConcurrentDictionary AssemblyList = new ConcurrentDictionary();
+#endif
+ ///
+ /// 初始化 AssemblySystem。(仅限内部)
+ ///
+ ///
+ internal static async FTask InnerInitialize(params System.Reflection.Assembly[] assemblies)
+ {
+ await LoadAssembly(typeof(AssemblySystem).Assembly);
+ foreach (var assembly in assemblies)
+ {
+ await LoadAssembly(assembly);
+ }
+ }
+
+ ///
+ /// 加载指定的程序集,并触发相应的事件。
+ ///
+ /// 要加载的程序集。
+ /// 如果当前Domain中已经存在同名的Assembly,使用Domain中的程序集。
+ public static async FTask LoadAssembly(System.Reflection.Assembly assembly, bool isCurrentDomain = true)
+ {
+ if (isCurrentDomain)
+ {
+ var currentDomainAssemblies = System.AppDomain.CurrentDomain.GetAssemblies();
+ var currentAssembly = currentDomainAssemblies.FirstOrDefault(d => d.GetName().Name == assembly.GetName().Name);
+ if (currentAssembly != null)
+ {
+ assembly = currentAssembly;
+ }
+ }
+
+ var assemblyIdentity = AssemblyIdentity(assembly);
+
+ if (AssemblyList.TryGetValue(assemblyIdentity, out var assemblyInfo))
+ {
+ assemblyInfo.ReLoad(assembly);
+ foreach (var assemblySystem in AssemblySystems)
+ {
+ await assemblySystem.ReLoad(assemblyIdentity);
+ }
+ }
+ else
+ {
+ assemblyInfo = new AssemblyInfo(assemblyIdentity);
+ assemblyInfo.Load(assembly);
+ AssemblyList.TryAdd(assemblyIdentity, assemblyInfo);
+ foreach (var assemblySystem in AssemblySystems)
+ {
+ await assemblySystem.Load(assemblyIdentity);
+ }
+ }
+ }
+
+ ///
+ /// 卸载程序集
+ ///
+ ///
+ public static async FTask UnLoadAssembly(System.Reflection.Assembly assembly)
+ {
+ var assemblyIdentity = AssemblyIdentity(assembly);
+
+ if (!AssemblyList.Remove(assemblyIdentity, out var assemblyInfo))
+ {
+ return;
+ }
+
+ assemblyInfo.Unload();
+ foreach (var assemblySystem in AssemblySystems)
+ {
+ await assemblySystem.OnUnLoad(assemblyIdentity);
+ }
+ }
+
+ ///
+ /// 将AssemblySystem接口的object注册到程序集管理中心
+ ///
+ ///
+ public static async FTask Register(object obj)
+ {
+ if (obj is not IAssembly assemblySystem)
+ {
+ return;
+ }
+#if FANTASY_WEBGL
+ AssemblySystems.Add(assemblySystem);
+#else
+ AssemblySystems.Enqueue(assemblySystem);
+#endif
+ 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
+ var count = AssemblySystems.Count;
+
+ for (var i = 0; i < count; i++)
+ {
+ if (!AssemblySystems.TryDequeue(out var removeAssemblySystem))
+ {
+ continue;
+ }
+
+ if (removeAssemblySystem == assemblySystem)
+ {
+ break;
+ }
+
+ AssemblySystems.Enqueue(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()
+ {
+ DisposeAsync().Coroutine();
+ }
+
+ private static async FTask DisposeAsync()
+ {
+ foreach (var (_, assemblyInfo) in AssemblyList.ToArray())
+ {
+ await UnLoadAssembly(assemblyInfo.Assembly);
+ }
+
+ AssemblyList.Clear();
+ AssemblySystems.Clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/CoreRuntime/Core/Assembly/AssemblySystem.cs.meta b/Runtime/CoreRuntime/Core/Assembly/AssemblySystem.cs.meta
new file mode 100644
index 0000000..0252da6
--- /dev/null
+++ b/Runtime/CoreRuntime/Core/Assembly/AssemblySystem.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 533740363a43b98499d20ddab704ae7b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/CoreRuntime/Core/Assembly/IAssembly.cs b/Runtime/CoreRuntime/Core/Assembly/IAssembly.cs
new file mode 100644
index 0000000..185e037
--- /dev/null
+++ b/Runtime/CoreRuntime/Core/Assembly/IAssembly.cs
@@ -0,0 +1,27 @@
+using System;
+using Fantasy.Async;
+
+namespace Fantasy.Assembly
+{
+ ///
+ /// 实现这个接口、会再程序集首次加载、卸载、重载的时候调用
+ ///
+ public interface IAssembly : IDisposable
+ {
+ ///
+ /// 程序集加载时调用
+ ///
+ /// 程序集标识
+ public FTask Load(long assemblyIdentity);
+ ///
+ /// 程序集重新加载的时候调用
+ ///
+ /// 程序集标识
+ public FTask ReLoad(long assemblyIdentity);
+ ///
+ /// 卸载的时候调用
+ ///
+ /// 程序集标识
+ public FTask OnUnLoad(long assemblyIdentity);
+ }
+}
\ No newline at end of file
diff --git a/Runtime/CoreRuntime/Core/Assembly/IAssembly.cs.meta b/Runtime/CoreRuntime/Core/Assembly/IAssembly.cs.meta
new file mode 100644
index 0000000..16313d1
--- /dev/null
+++ b/Runtime/CoreRuntime/Core/Assembly/IAssembly.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2265dda8f756ced42b346d101b5c431c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/CoreRuntime/Core/Benchmark.meta b/Runtime/CoreRuntime/Core/Benchmark.meta
new file mode 100644
index 0000000..ca50a8f
--- /dev/null
+++ b/Runtime/CoreRuntime/Core/Benchmark.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: b9672ac65f515574a8e888c0baa4f5a2
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/CoreRuntime/Core/Benchmark/Handler.meta b/Runtime/CoreRuntime/Core/Benchmark/Handler.meta
new file mode 100644
index 0000000..92992e6
--- /dev/null
+++ b/Runtime/CoreRuntime/Core/Benchmark/Handler.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: dcf9f758978488e4d94d0aec6c151e3f
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/CoreRuntime/Core/Benchmark/Handler/BenchmarkRequestHandler.cs b/Runtime/CoreRuntime/Core/Benchmark/Handler/BenchmarkRequestHandler.cs
new file mode 100644
index 0000000..8532547
--- /dev/null
+++ b/Runtime/CoreRuntime/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/Runtime/CoreRuntime/Core/Benchmark/Handler/BenchmarkRequestHandler.cs.meta b/Runtime/CoreRuntime/Core/Benchmark/Handler/BenchmarkRequestHandler.cs.meta
new file mode 100644
index 0000000..8288de2
--- /dev/null
+++ b/Runtime/CoreRuntime/Core/Benchmark/Handler/BenchmarkRequestHandler.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0584f8f7232c13e4a9043b14fcb00ba6
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/CoreRuntime/Core/DataBase.meta b/Runtime/CoreRuntime/Core/DataBase.meta
new file mode 100644
index 0000000..ef8664a
--- /dev/null
+++ b/Runtime/CoreRuntime/Core/DataBase.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 158dbd1e6090fc6408b93c074e56a894
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/CoreRuntime/Core/DataBase/DataBaseType.cs b/Runtime/CoreRuntime/Core/DataBase/DataBaseType.cs
new file mode 100644
index 0000000..2fb03ec
--- /dev/null
+++ b/Runtime/CoreRuntime/Core/DataBase/DataBaseType.cs
@@ -0,0 +1,20 @@
+// ReSharper disable CheckNamespace
+// ReSharper disable InconsistentNaming
+#if FANTASY_NET
+namespace Fantasy.DataBase;
+
+///
+/// 数据库类型
+///
+public enum DataBaseType
+{
+ ///
+ /// 默认
+ ///
+ None = 0,
+ ///
+ /// MongoDB
+ ///
+ MongoDB = 1
+}
+#endif
\ No newline at end of file
diff --git a/Runtime/CoreRuntime/Core/DataBase/DataBaseType.cs.meta b/Runtime/CoreRuntime/Core/DataBase/DataBaseType.cs.meta
new file mode 100644
index 0000000..ad9a88d
--- /dev/null
+++ b/Runtime/CoreRuntime/Core/DataBase/DataBaseType.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a76811d05f24f5449bdc0fe27e6f7b8e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/CoreRuntime/Core/DataBase/IDataBase.cs b/Runtime/CoreRuntime/Core/DataBase/IDataBase.cs
new file mode 100644
index 0000000..6a2fe8b
--- /dev/null
+++ b/Runtime/CoreRuntime/Core/DataBase/IDataBase.cs
@@ -0,0 +1,210 @@
+#if FANTASY_NET
+using System;
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using Fantasy.Async;
+using Fantasy.Entitas;
+using MongoDB.Driver;
+// ReSharper disable InconsistentNaming
+#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
+
+#pragma warning disable CS8625
+
+namespace Fantasy.DataBase
+{
+ ///
+ /// 数据库设置助手
+ ///
+ public static class DataBaseSetting
+ {
+ ///
+ /// 初始化自定义委托,当设置了这个委托后,就不会自动创建MongoClient,需要自己在委托里创建MongoClient。
+ ///
+ public static Func? MongoDBCustomInitialize;
+ }
+
+ ///
+ /// MongoDB自定义连接参数
+ ///
+ public sealed class DataBaseCustomConfig
+ {
+ ///
+ /// 当前Scene
+ ///
+ public Scene Scene;
+ ///
+ /// 连接字符串
+ ///
+ public string ConnectionString;
+ ///
+ /// 数据库名字
+ ///
+ public string DBName;
+ }
+
+ ///
+ /// 表示用于执行各种数据库操作的数据库接口。
+ ///
+ public interface IDataBase : IDisposable
+ {
+ ///
+ /// 获得当前数据的类型
+ ///
+ public DataBaseType GetDataBaseType { get;}
+ ///
+ /// 获得对应数据的操作实例
+ ///
+ /// 如MongoDB就是IMongoDatabase
+ public object GetDataBaseInstance { get;}
+ ///
+ /// 初始化数据库连接。
+ ///
+ 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, bool isDeserialize = false, string collection = null) where T : Entity;
+ ///
+ /// 从指定集合中检索指定 ID 的类型 的实体。
+ ///
+ FTask Query(long id, bool isDeserialize = false, string collection = null) where T : Entity;
+ ///
+ /// 按页查询满足给定筛选条件的类型 的实体数量和日期。
+ ///
+ FTask<(int count, List dates)> QueryCountAndDatesByPage(Expression> filter, int pageIndex, int pageSize, bool isDeserialize = false, string collection = null) where T : Entity;
+ ///
+ /// 按页查询满足给定筛选条件的类型 的实体数量和日期。
+ ///
+ FTask<(int count, List dates)> QueryCountAndDatesByPage(Expression> filter, int pageIndex, int pageSize, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity;
+ ///
+ /// 分页查询指定集合中满足给定筛选条件的类型 的实体列表。
+ ///
+ FTask> QueryByPage(Expression> filter, int pageIndex, int pageSize, bool isDeserialize = false, string collection = null) where T : Entity;
+ ///
+ /// 分页查询指定集合中满足给定筛选条件的类型 的实体列表,仅返回指定列的数据。
+ ///
+ FTask> QueryByPage(Expression> filter, int pageIndex, int pageSize, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity;
+ ///
+ /// 从指定集合中按页查询满足给定筛选条件的类型 的实体列表,按指定字段排序。
+ ///
+ FTask> QueryByPageOrderBy(Expression> filter, int pageIndex, int pageSize, Expression> orderByExpression, bool isAsc = true, bool isDeserialize = false, string collection = null) where T : Entity;
+ ///
+ /// 检索满足给定筛选条件的类型 的第一个实体,从指定集合中。
+ ///
+ FTask First(Expression> filter, bool isDeserialize = false, string collection = null) where T : Entity;
+ ///
+ /// 查询指定集合中满足给定 JSON 查询字符串的类型 的第一个实体,仅返回指定列的数据。
+ ///
+ FTask First(string json, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity;
+ ///
+ /// 从指定集合中按页查询满足给定筛选条件的类型 的实体列表,按指定字段排序。
+ ///
+ FTask> QueryOrderBy(Expression> filter, Expression> orderByExpression, bool isAsc = true, bool isDeserialize = false, string collection = null) where T : Entity;
+ ///
+ /// 从指定集合中按页查询满足给定筛选条件的类型 的实体列表。
+ ///
+ FTask> Query(Expression> filter, bool isDeserialize = false, string collection = null) where T : Entity;
+ ///
+ /// 查询指定集合中满足给定筛选条件的类型 实体列表,仅返回指定字段的数据。
+ ///
+ FTask> Query(Expression> filter, Expression>[] cols, bool isDeserialize = false, string collection = null) where T : Entity;
+ ///
+ /// 查询指定 ID 的多个集合,将结果存储在给定的实体列表中。
+ ///
+ FTask Query(long id, List collectionNames, List result, bool isDeserialize = false);
+ ///
+ /// 根据给定的 JSON 查询字符串查询指定集合中的类型 实体列表。
+ ///
+ FTask> QueryJson(string json, bool isDeserialize = false, string collection = null) where T : Entity;
+ ///
+ /// 根据给定的 JSON 查询字符串查询指定集合中的类型 实体列表,仅返回指定列的数据。
+ ///
+ FTask> QueryJson(string json, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity;
+ ///
+ /// 根据给定的 JSON 查询字符串查询指定集合中的类型 实体列表,通过指定的任务 ID 进行标识。
+ ///
+ FTask> QueryJson(long taskId, string json, bool isDeserialize = false, string collection = null) where T : Entity;
+ ///
+ /// 查询指定集合中满足给定筛选条件的类型 实体列表,仅返回指定列的数据。
+ ///
+ FTask> Query(Expression> filter, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity;
+ ///
+ /// 保存类型 实体到指定集合中,如果集合不存在将自动创建。
+ ///
+ 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/Runtime/CoreRuntime/Core/DataBase/IDataBase.cs.meta b/Runtime/CoreRuntime/Core/DataBase/IDataBase.cs.meta
new file mode 100644
index 0000000..b01c0d5
--- /dev/null
+++ b/Runtime/CoreRuntime/Core/DataBase/IDataBase.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1630d7e4e5e733d439ab42b7e57fee87
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/CoreRuntime/Core/DataBase/MongoDataBase.cs b/Runtime/CoreRuntime/Core/DataBase/MongoDataBase.cs
new file mode 100644
index 0000000..49948f4
--- /dev/null
+++ b/Runtime/CoreRuntime/Core/DataBase/MongoDataBase.cs
@@ -0,0 +1,1081 @@
+#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 CS8602 // Dereference of a possibly null reference.
+#pragma warning disable CS8603 // Possible null reference return.
+#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 MongoClient _mongoClient;
+ private ISerialize _serializer;
+ private IMongoDatabase _mongoDatabase;
+ private CoroutineLock _dataBaseLock;
+ private readonly HashSet _collections = new HashSet();
+ ///
+ /// 获得当前数据的类型
+ ///
+ public DataBaseType GetDataBaseType { get; } = DataBaseType.MongoDB;
+ ///
+ /// 获得对应数据的操作实例
+ ///
+ public object GetDataBaseInstance => _mongoDatabase;
+ ///
+ /// 初始化 MongoDB 数据库连接并记录所有集合名。
+ ///
+ /// 场景对象。
+ /// 数据库连接字符串。
+ /// 数据库名称。
+ /// 初始化后的数据库实例。
+ public IDataBase Initialize(Scene scene, string connectionString, string dbName)
+ {
+ _scene = scene;
+ _mongoClient = DataBaseSetting.MongoDBCustomInitialize != null
+ ? DataBaseSetting.MongoDBCustomInitialize(new DataBaseCustomConfig()
+ {
+ Scene = scene, ConnectionString = connectionString, DBName = dbName
+ })
+ : 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;
+ }
+
+ ///
+ /// 销毁释放资源。
+ ///
+ public void Dispose()
+ {
+ // 优先释放协程锁。
+ _dataBaseLock.Dispose();
+ // 清理资源。
+ _scene = null;
+ _serializer = null;
+ _mongoDatabase = null;
+ _dataBaseLock = null;
+ _collections.Clear();
+ _mongoClient.Dispose();
+ }
+
+ #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, bool isDeserialize = false, string collection = null) where T : Entity
+ {
+ var cursor = await GetCollection(collection).FindAsync(d => d.Id == id);
+ var v = await cursor.FirstOrDefaultAsync();
+
+ if (isDeserialize && v != null)
+ {
+ v.Deserialize(_scene);
+ }
+
+ return v;
+ }
+
+ ///
+ /// 查询指定 ID 的文档,并加数据库锁定以确保数据一致性。
+ ///
+ /// 文档实体类型。
+ /// 要查询的文档 ID。
+ /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。
+ /// 集合名称。
+ /// 查询到的文档。
+ public async FTask Query(long id, bool isDeserialize = false, 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();
+
+ if (isDeserialize && v != null)
+ {
+ v.Deserialize(_scene);
+ }
+
+ return v;
+ }
+ }
+
+ ///
+ /// 通过分页查询并返回满足条件的文档数量和日期列表(不加锁)。
+ ///
+ /// 文档实体类型。
+ /// 查询过滤条件。
+ /// 页码。
+ /// 每页大小。
+ /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。
+ /// 集合名称。
+ /// 满足条件的文档数量和日期列表。
+ public async FTask<(int count, List dates)> QueryCountAndDatesByPage(Expression> filter, int pageIndex, int pageSize, bool isDeserialize = false, 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, isDeserialize, collection);
+ return ((int)count, dates);
+ }
+ }
+
+ ///
+ /// 通过分页查询并返回满足条件的文档数量和日期列表(加锁)。
+ ///
+ /// 文档实体类型。
+ /// 查询过滤条件。
+ /// 页码。
+ /// 每页大小。
+ /// 要查询的列名称数组。
+ /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。
+ /// 集合名称。
+ /// 满足条件的文档数量和日期列表。
+ public async FTask<(int count, List dates)> QueryCountAndDatesByPage(Expression> filter, int pageIndex, int pageSize, string[] cols, bool isDeserialize = false, 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, isDeserialize, collection);
+ return ((int)count, dates);
+ }
+ }
+
+ ///
+ /// 通过分页查询并返回满足条件的文档列表(不加锁)。
+ ///
+ /// 文档实体类型。
+ /// 查询过滤条件。
+ /// 页码。
+ /// 每页大小。
+ /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。
+ /// 集合名称。
+ /// 满足条件的文档列表。
+ public async FTask> QueryByPage(Expression> filter, int pageIndex, int pageSize, bool isDeserialize = false, string collection = null) where T : Entity
+ {
+ using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize))
+ {
+ var list = await GetCollection(collection).Find(filter).Skip((pageIndex - 1) * pageSize)
+ .Limit(pageSize)
+ .ToListAsync();
+
+ if (!isDeserialize || list is not { Count: > 0 })
+ {
+ return list;
+ }
+
+ foreach (var entity in list)
+ {
+ entity.Deserialize(_scene);
+ }
+
+ return list;
+ }
+ }
+
+ ///
+ /// 通过分页查询并返回满足条件的文档列表(加锁)。
+ ///
+ /// 文档实体类型。
+ /// 查询过滤条件。
+ /// 页码。
+ /// 每页大小。
+ /// 要查询的列名称数组。
+ /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。
+ /// 集合名称。
+ /// 满足条件的文档列表。
+ public async FTask> QueryByPage(Expression> filter, int pageIndex, int pageSize, string[] cols, bool isDeserialize = false, 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 list = await GetCollection(collection).Find(filter).Project(projection)
+ .Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync();
+
+ if (!isDeserialize || list is not { Count: > 0 })
+ {
+ return list;
+ }
+
+ foreach (var entity in list)
+ {
+ entity.Deserialize(_scene);
+ }
+
+ return list;
+ }
+ }
+
+ ///
+ /// 通过分页查询并返回满足条件的文档列表,并按指定表达式进行排序(加锁)。
+ ///
+ /// 文档实体类型。
+ /// 查询过滤条件。
+ /// 页码。
+ /// 每页大小。
+ /// 排序表达式。
+ /// 是否升序排序。
+ /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。
+ /// 集合名称。
+ /// 满足条件的文档列表。
+ public async FTask> QueryByPageOrderBy(Expression> filter, int pageIndex, int pageSize, Expression> orderByExpression, bool isAsc = true, bool isDeserialize = false, string collection = null) where T : Entity
+ {
+ using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize))
+ {
+ List list;
+
+ if (isAsc)
+ {
+ list = await GetCollection(collection).Find(filter).SortBy(orderByExpression).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync();
+ }
+ else
+ {
+ list = await GetCollection(collection).Find(filter).SortByDescending(orderByExpression).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync();
+ }
+
+ if (!isDeserialize || list is not { Count: > 0 })
+ {
+ return list;
+ }
+
+ foreach (var entity in list)
+ {
+ entity.Deserialize(_scene);
+ }
+
+ return list;
+ }
+ }
+
+ ///
+ /// 通过指定过滤条件查询并返回满足条件的第一个文档(加锁)。
+ ///
+ /// 文档实体类型。
+ /// 查询过滤条件。
+ /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。
+ /// 集合名称。
+ /// 满足条件的第一个文档,如果未找到则为 null。
+ public async FTask First(Expression> filter, bool isDeserialize = false, string collection = null) where T : Entity
+ {
+ using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize))
+ {
+ var cursor = await GetCollection(collection).FindAsync(filter);
+ var t = await cursor.FirstOrDefaultAsync();
+
+ if (isDeserialize && t != null)
+ {
+ t.Deserialize(_scene);
+ }
+
+ return t;
+ }
+ }
+
+ ///
+ /// 通过指定 JSON 格式查询并返回满足条件的第一个文档(加锁)。
+ ///
+ /// 文档实体类型。
+ /// JSON 查询条件。
+ /// 要查询的列名称数组。
+ /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。
+ /// 集合名称。
+ /// 满足条件的第一个文档。
+ public async FTask First(string json, string[] cols, bool isDeserialize = false, 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 t = await cursor.FirstOrDefaultAsync();
+
+ if (isDeserialize && t != null)
+ {
+ t.Deserialize(_scene);
+ }
+
+ return t;
+ }
+ }
+
+ ///
+ /// 通过指定过滤条件查询并返回满足条件的文档列表,并按指定表达式进行排序(加锁)。
+ ///
+ /// 文档实体类型。
+ /// 查询过滤条件。
+ /// 排序表达式。
+ /// 是否升序排序。
+ /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。
+ /// 集合名称。
+ /// 满足条件的文档列表。
+ public async FTask> QueryOrderBy(Expression> filter, Expression> orderByExpression, bool isAsc = true, bool isDeserialize = false, string collection = null) where T : Entity
+ {
+ using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize))
+ {
+ List list;
+
+ if (isAsc)
+ {
+ list = await GetCollection(collection).Find(filter).SortBy(orderByExpression).ToListAsync();
+ }
+ else
+ {
+ list = await GetCollection(collection).Find(filter).SortByDescending(orderByExpression).ToListAsync();
+ }
+
+ if (!isDeserialize || list is not { Count: > 0 })
+ {
+ return list;
+ }
+
+ foreach (var entity in list)
+ {
+ entity.Deserialize(_scene);
+ }
+
+ return list;
+ }
+ }
+
+ ///
+ /// 通过指定过滤条件查询并返回满足条件的文档列表(加锁)。
+ ///
+ /// 文档实体类型。
+ /// 查询过滤条件。
+ /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。
+ /// 集合名称。
+ /// 满足条件的文档列表。
+ public async FTask> Query(Expression> filter, bool isDeserialize = false, string collection = null) where T : Entity
+ {
+ using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize))
+ {
+ var cursor = await GetCollection(collection).FindAsync(filter);
+ var list = await cursor.ToListAsync();
+
+ if (!isDeserialize || list is not { Count: > 0 })
+ {
+ return list;
+ }
+
+ foreach (var entity in list)
+ {
+ entity.Deserialize(_scene);
+ }
+
+ return list;
+ }
+ }
+
+ ///
+ /// 根据指定 ID 加锁查询多个集合中的文档。
+ ///
+ /// 文档 ID。
+ /// 要查询的集合名称列表。
+ /// 查询结果存储列表。
+ /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。
+ public async FTask Query(long id, List? collectionNames, List result, bool isDeserialize = false)
+ {
+ 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;
+ }
+
+ if (isDeserialize)
+ {
+ e.Deserialize(_scene);
+ }
+
+ result.Add(e);
+ }
+ }
+ }
+
+ ///
+ /// 根据指定的 JSON 查询条件查询并返回满足条件的文档列表(加锁)。
+ ///
+ /// 文档实体类型。
+ /// JSON 查询条件。
+ /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。
+ /// 集合名称。
+ /// 满足条件的文档列表。
+ public async FTask> QueryJson(string json, bool isDeserialize = false, 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 list = await cursor.ToListAsync();
+
+ if (!isDeserialize || list is not { Count: > 0 })
+ {
+ return list;
+ }
+
+ foreach (var entity in list)
+ {
+ entity.Deserialize(_scene);
+ }
+
+ return list;
+ }
+ }
+
+ ///
+ /// 根据指定的 JSON 查询条件查询并返回满足条件的文档列表,并选择指定的列(加锁)。
+ ///
+ /// 文档实体类型。
+ /// JSON 查询条件。
+ /// 要查询的列名称数组。
+ /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。
+ /// 集合名称。
+ /// 满足条件的文档列表。
+ public async FTask> QueryJson(string json, string[] cols, bool isDeserialize = false, 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 list = await cursor.ToListAsync();
+
+ if (!isDeserialize || list is not { Count: > 0 })
+ {
+ return list;
+ }
+
+ foreach (var entity in list)
+ {
+ entity.Deserialize(_scene);
+ }
+
+ return list;
+ }
+ }
+
+ ///
+ /// 根据指定的 JSON 查询条件和任务 ID 查询并返回满足条件的文档列表(加锁)。
+ ///
+ /// 文档实体类型。
+ /// 任务 ID。
+ /// JSON 查询条件。
+ /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。
+ /// 集合名称。
+ /// 满足条件的文档列表。
+ public async FTask> QueryJson(long taskId, string json, bool isDeserialize = false, string collection = null) where T : Entity
+ {
+ using (await _dataBaseLock.Wait(taskId))
+ {
+ FilterDefinition filterDefinition = new JsonFilterDefinition(json);
+ var cursor = await GetCollection(collection).FindAsync(filterDefinition);
+ var list = await cursor.ToListAsync();
+
+ if (!isDeserialize || list is not { Count: > 0 })
+ {
+ return list;
+ }
+
+ foreach (var entity in list)
+ {
+ entity.Deserialize(_scene);
+ }
+
+ return list;
+ }
+ }
+
+ ///
+ /// 根据指定过滤条件查询并返回满足条件的文档列表,选择指定的列(加锁)。
+ ///
+ /// 文档实体类型。
+ /// 查询过滤条件。
+ /// 要查询的列名称数组。
+ /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。
+ /// 集合名称。
+ /// 满足条件的文档列表。
+ public async FTask> Query(Expression> filter, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity
+ {
+ using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize))
+ {
+ var projection = Builders.Projection.Include("_id");
+
+ foreach (var t in cols)
+ {
+ projection = projection.Include(t);
+ }
+
+ var list = await GetCollection(collection).Find(filter).Project(projection).ToListAsync();
+
+ if (!isDeserialize || list is not { Count: > 0 })
+ {
+ return list;
+ }
+
+ foreach (var entity in list)
+ {
+ entity.Deserialize(_scene);
+ }
+
+ return list;
+ }
+ }
+
+ ///
+ /// 根据指定过滤条件查询并返回满足条件的文档列表,选择指定的列(加锁)。
+ ///
+ /// 文档实体类型。
+ /// 查询过滤条件。
+ /// 要查询的列名称数组。
+ /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。
+ /// 集合名称。
+ ///
+ public async FTask> Query(Expression> filter, Expression>[] cols, bool isDeserialize = false, string collection = null) where T : Entity
+ {
+ using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize))
+ {
+ var projection = Builders.Projection.Include("_id");
+
+ foreach (var col in cols)
+ {
+ if (col.Body is not MemberExpression memberExpression)
+ {
+ throw new ArgumentException("Lambda expression must be a member access expression.");
+ }
+
+ projection = projection.Include(memberExpression.Member.Name);
+ }
+
+ var list = await GetCollection(collection).Find(filter).Project(projection).ToListAsync();
+
+ if (!isDeserialize || list is not { Count: > 0 })
+ {
+ return list;
+ }
+
+ foreach (var entity in list)
+ {
+ entity.Deserialize(_scene);
+ }
+
+ return list;
+ }
+ }
+
+ #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).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).ReplaceOneAsync(d => d.Id == clone.Id, clone, new ReplaceOptions { IsUpsert = true });
+ }
+ }
+
+ ///
+ /// 保存实体对象到数据库(加锁)。
+ ///
+ /// 保存的条件表达式。
+ /// 实体类型。
+ /// 集合名称。
+ ///
+ public async FTask Save(Expression> filter, T? entity, string collection = null) where T : Entity, new()
+ {
+ if (entity == null)
+ {
+ Log.Error($"save entity is null: {typeof(T).Name}");
+ return;
+ }
+
+ T clone = _serializer.Clone(entity);
+
+ using (await _dataBaseLock.Wait(clone.Id))
+ {
+ await GetCollection(collection).ReplaceOneAsync(filter, 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 async FTask Insert(T? entity, string collection = null) where T : Entity, new()
+ {
+ if (entity == null)
+ {
+ Log.Error($"insert entity is null: {typeof(T).Name}");
+ return;
+ }
+
+ var clone = _serializer.Clone(entity);
+
+ using (await _dataBaseLock.Wait(entity.Id))
+ {
+ await GetCollection(collection).InsertOneAsync(clone);
+ }
+ }
+
+ ///
+ /// 批量插入实体对象列表到数据库(加锁)。
+ ///
+ /// 实体类型。
+ /// 要插入的实体对象列表。
+ /// 集合名称。
+ public async FTask InsertBatch(IEnumerable list, string collection = null) where T : Entity, new()
+ {
+ using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize))
+ {
+ await GetCollection(collection).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).InsertManyAsync((IClientSessionHandle)transactionSession, list);
+ }
+ }
+
+ ///
+ /// 插入BsonDocument到数据库(加锁)。
+ ///
+ ///
+ ///
+ ///
+ public async Task Insert(BsonDocument bsonDocument, long taskId) where T : Entity
+ {
+ using (await _dataBaseLock.Wait(taskId))
+ {
+ await GetCollection(typeof(T).Name).InsertOneAsync(bsonDocument);
+ }
+ }
+
+ #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