commit 9085c3003aa48e399a6dc2d41aebad896865e61b
Author: BobSong <605277374@qq.com>
Date: Mon Aug 11 16:09:33 2025 +0800
fist commit
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..afc62bb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+obj/
+bin/
diff --git a/.idea/.idea.FantasyNetTest/.idea/.gitignore b/.idea/.idea.FantasyNetTest/.idea/.gitignore
new file mode 100644
index 0000000..e08cf47
--- /dev/null
+++ b/.idea/.idea.FantasyNetTest/.idea/.gitignore
@@ -0,0 +1,13 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# Rider 忽略的文件
+/contentModel.xml
+/modules.xml
+/projectSettingsUpdater.xml
+/.idea.FantasyNetTest.iml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/.idea.FantasyNetTest/.idea/encodings.xml b/.idea/.idea.FantasyNetTest/.idea/encodings.xml
new file mode 100644
index 0000000..df87cf9
--- /dev/null
+++ b/.idea/.idea.FantasyNetTest/.idea/encodings.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.FantasyNetTest/.idea/indexLayout.xml b/.idea/.idea.FantasyNetTest/.idea/indexLayout.xml
new file mode 100644
index 0000000..7b08163
--- /dev/null
+++ b/.idea/.idea.FantasyNetTest/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vs/FantasyNetTest/DesignTimeBuild/.dtbcache.v2 b/.vs/FantasyNetTest/DesignTimeBuild/.dtbcache.v2
new file mode 100644
index 0000000..bc2f8ef
Binary files /dev/null and b/.vs/FantasyNetTest/DesignTimeBuild/.dtbcache.v2 differ
diff --git a/.vs/FantasyNetTest/FileContentIndex/1b16a2e7-1bb2-4c3c-9d14-af55c9c33fc6.vsidx b/.vs/FantasyNetTest/FileContentIndex/1b16a2e7-1bb2-4c3c-9d14-af55c9c33fc6.vsidx
new file mode 100644
index 0000000..492b03b
Binary files /dev/null and b/.vs/FantasyNetTest/FileContentIndex/1b16a2e7-1bb2-4c3c-9d14-af55c9c33fc6.vsidx differ
diff --git a/.vs/FantasyNetTest/FileContentIndex/63aeb925-4ffa-4456-9651-416f182bf989.vsidx b/.vs/FantasyNetTest/FileContentIndex/63aeb925-4ffa-4456-9651-416f182bf989.vsidx
new file mode 100644
index 0000000..6bee42f
Binary files /dev/null and b/.vs/FantasyNetTest/FileContentIndex/63aeb925-4ffa-4456-9651-416f182bf989.vsidx differ
diff --git a/.vs/FantasyNetTest/FileContentIndex/64c8bdfc-9971-43cd-9e5b-02cd88a5d378.vsidx b/.vs/FantasyNetTest/FileContentIndex/64c8bdfc-9971-43cd-9e5b-02cd88a5d378.vsidx
new file mode 100644
index 0000000..88f97c8
Binary files /dev/null and b/.vs/FantasyNetTest/FileContentIndex/64c8bdfc-9971-43cd-9e5b-02cd88a5d378.vsidx differ
diff --git a/.vs/FantasyNetTest/FileContentIndex/803edca7-6048-4b38-896e-1c612aa4ddfb.vsidx b/.vs/FantasyNetTest/FileContentIndex/803edca7-6048-4b38-896e-1c612aa4ddfb.vsidx
new file mode 100644
index 0000000..f1339df
Binary files /dev/null and b/.vs/FantasyNetTest/FileContentIndex/803edca7-6048-4b38-896e-1c612aa4ddfb.vsidx differ
diff --git a/.vs/FantasyNetTest/FileContentIndex/a276d935-05f6-4ffd-9ee0-d04eb5db88f8.vsidx b/.vs/FantasyNetTest/FileContentIndex/a276d935-05f6-4ffd-9ee0-d04eb5db88f8.vsidx
new file mode 100644
index 0000000..cc0838b
Binary files /dev/null and b/.vs/FantasyNetTest/FileContentIndex/a276d935-05f6-4ffd-9ee0-d04eb5db88f8.vsidx differ
diff --git a/.vs/FantasyNetTest/v17/.futdcache.v2 b/.vs/FantasyNetTest/v17/.futdcache.v2
new file mode 100644
index 0000000..ad39b75
Binary files /dev/null and b/.vs/FantasyNetTest/v17/.futdcache.v2 differ
diff --git a/.vs/FantasyNetTest/v17/.suo b/.vs/FantasyNetTest/v17/.suo
new file mode 100644
index 0000000..a6cc766
Binary files /dev/null and b/.vs/FantasyNetTest/v17/.suo differ
diff --git a/.vs/FantasyNetTest/v17/DocumentLayout.json b/.vs/FantasyNetTest/v17/DocumentLayout.json
new file mode 100644
index 0000000..571a75a
--- /dev/null
+++ b/.vs/FantasyNetTest/v17/DocumentLayout.json
@@ -0,0 +1,101 @@
+{
+ "Version": 1,
+ "WorkspaceRootPath": "D:\\myself\\Games\\FantasyNetTest\\",
+ "Documents": [
+ {
+ "AbsoluteMoniker": "D:0:0:{812F24C9-1386-4A5F-AC1D-1840E28FA05D}|FantasyNetTest\\FantasyNetTest.csproj|d:\\myself\\games\\fantasynettest\\fantasynettest\\form1.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{812F24C9-1386-4A5F-AC1D-1840E28FA05D}|FantasyNetTest\\FantasyNetTest.csproj|solutionrelative:fantasynettest\\form1.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{812F24C9-1386-4A5F-AC1D-1840E28FA05D}|FantasyNetTest\\FantasyNetTest.csproj|D:\\myself\\Games\\FantasyNetTest\\fantasynettest\\form1.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}|Form",
+ "RelativeMoniker": "D:0:0:{812F24C9-1386-4A5F-AC1D-1840E28FA05D}|FantasyNetTest\\FantasyNetTest.csproj|solutionrelative:fantasynettest\\form1.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}|Form"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{812F24C9-1386-4A5F-AC1D-1840E28FA05D}|FantasyNetTest\\FantasyNetTest.csproj|d:\\myself\\games\\fantasynettest\\fantasynettest\\testprotocol.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{812F24C9-1386-4A5F-AC1D-1840E28FA05D}|FantasyNetTest\\FantasyNetTest.csproj|solutionrelative:fantasynettest\\testprotocol.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{812F24C9-1386-4A5F-AC1D-1840E28FA05D}|FantasyNetTest\\FantasyNetTest.csproj|d:\\myself\\games\\fantasynettest\\fantasynettest\\config.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}",
+ "RelativeMoniker": "D:0:0:{812F24C9-1386-4A5F-AC1D-1840E28FA05D}|FantasyNetTest\\FantasyNetTest.csproj|solutionrelative:fantasynettest\\config.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{812F24C9-1386-4A5F-AC1D-1840E28FA05D}|FantasyNetTest\\FantasyNetTest.csproj|d:\\myself\\games\\fantasynettest\\fantasynettest\\log.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{812F24C9-1386-4A5F-AC1D-1840E28FA05D}|FantasyNetTest\\FantasyNetTest.csproj|solutionrelative:fantasynettest\\log.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ }
+ ],
+ "DocumentGroupContainers": [
+ {
+ "Orientation": 0,
+ "VerticalTabListWidth": 256,
+ "DocumentGroups": [
+ {
+ "DockedWidth": 200,
+ "SelectedChildIndex": 0,
+ "Children": [
+ {
+ "$type": "Document",
+ "DocumentIndex": 0,
+ "Title": "Form1.cs",
+ "DocumentMoniker": "D:\\myself\\Games\\FantasyNetTest\\FantasyNetTest\\Form1.cs",
+ "RelativeDocumentMoniker": "FantasyNetTest\\Form1.cs",
+ "ToolTip": "D:\\myself\\Games\\FantasyNetTest\\FantasyNetTest\\Form1.cs",
+ "RelativeToolTip": "FantasyNetTest\\Form1.cs",
+ "ViewState": "AQIAAGcCAAAAAAAAAAAiwHUCAAAAAAAA",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+ "WhenOpened": "2025-08-10T14:06:30.336Z",
+ "EditorCaption": ""
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 1,
+ "Title": "Form1.cs [\u8BBE\u8BA1]",
+ "DocumentMoniker": "D:\\myself\\Games\\FantasyNetTest\\FantasyNetTest\\Form1.cs",
+ "RelativeDocumentMoniker": "FantasyNetTest\\Form1.cs",
+ "ToolTip": "D:\\myself\\Games\\FantasyNetTest\\FantasyNetTest\\Form1.cs [\u8BBE\u8BA1]",
+ "RelativeToolTip": "FantasyNetTest\\Form1.cs [\u8BBE\u8BA1]",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+ "WhenOpened": "2025-08-10T11:09:51.154Z",
+ "EditorCaption": " [\u8BBE\u8BA1]"
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 2,
+ "Title": "TestProtocol.cs",
+ "DocumentMoniker": "D:\\myself\\Games\\FantasyNetTest\\FantasyNetTest\\TestProtocol.cs",
+ "RelativeDocumentMoniker": "FantasyNetTest\\TestProtocol.cs",
+ "ToolTip": "D:\\myself\\Games\\FantasyNetTest\\FantasyNetTest\\TestProtocol.cs",
+ "RelativeToolTip": "FantasyNetTest\\TestProtocol.cs",
+ "ViewState": "AQIAAAYAAAAAAAAAAADwvxcAAAAlAAAA",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+ "WhenOpened": "2025-08-10T09:28:41.991Z"
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 3,
+ "Title": "Config.json",
+ "DocumentMoniker": "D:\\myself\\Games\\FantasyNetTest\\FantasyNetTest\\Config.json",
+ "RelativeDocumentMoniker": "FantasyNetTest\\Config.json",
+ "ToolTip": "D:\\myself\\Games\\FantasyNetTest\\FantasyNetTest\\Config.json",
+ "RelativeToolTip": "FantasyNetTest\\Config.json",
+ "ViewState": "AQIAAAAAAAAAAAAAAAAAAAMAAAABAAAA",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001642|",
+ "WhenOpened": "2025-08-10T07:28:50.344Z"
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 4,
+ "Title": "Log.cs",
+ "DocumentMoniker": "D:\\myself\\Games\\FantasyNetTest\\FantasyNetTest\\Log.cs",
+ "RelativeDocumentMoniker": "FantasyNetTest\\Log.cs",
+ "ToolTip": "D:\\myself\\Games\\FantasyNetTest\\FantasyNetTest\\Log.cs",
+ "RelativeToolTip": "FantasyNetTest\\Log.cs",
+ "ViewState": "AQIAAEsAAAAAAAAAAAAYwB8AAAApAAAA",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+ "WhenOpened": "2025-08-10T06:36:37.693Z"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.vs/ProjectEvaluation/fantasynettest.metadata.v7.bin b/.vs/ProjectEvaluation/fantasynettest.metadata.v7.bin
new file mode 100644
index 0000000..320df57
Binary files /dev/null and b/.vs/ProjectEvaluation/fantasynettest.metadata.v7.bin differ
diff --git a/.vs/ProjectEvaluation/fantasynettest.projects.v7.bin b/.vs/ProjectEvaluation/fantasynettest.projects.v7.bin
new file mode 100644
index 0000000..a5ded59
Binary files /dev/null and b/.vs/ProjectEvaluation/fantasynettest.projects.v7.bin differ
diff --git a/FantasyNetTest.sln b/FantasyNetTest.sln
new file mode 100644
index 0000000..e850cee
--- /dev/null
+++ b/FantasyNetTest.sln
@@ -0,0 +1,16 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FantasyNetTest", "FantasyNetTest\FantasyNetTest.csproj", "{812F24C9-1386-4A5F-AC1D-1840E28FA05D}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {812F24C9-1386-4A5F-AC1D-1840E28FA05D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {812F24C9-1386-4A5F-AC1D-1840E28FA05D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {812F24C9-1386-4A5F-AC1D-1840E28FA05D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {812F24C9-1386-4A5F-AC1D-1840E28FA05D}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/FantasyNetTest.sln.DotSettings.user b/FantasyNetTest.sln.DotSettings.user
new file mode 100644
index 0000000..a434953
--- /dev/null
+++ b/FantasyNetTest.sln.DotSettings.user
@@ -0,0 +1,9 @@
+
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ True
+ True
\ No newline at end of file
diff --git a/FantasyNetTest/Config.cs b/FantasyNetTest/Config.cs
new file mode 100644
index 0000000..ff025b3
--- /dev/null
+++ b/FantasyNetTest/Config.cs
@@ -0,0 +1,25 @@
+namespace FantasyNetTest;
+
+[Serializable]
+public class Config
+{
+ ///
+ /// 登录服地址
+ ///
+ public string Server { get; set; } = "127.0.0.1:20001";
+
+ ///
+ /// 心跳间隔
+ ///
+ public int Heartbeat { get; set; } = 5;
+
+ ///
+ /// 默认账号
+ ///
+ public string Account { get; set; } = "";
+
+ ///
+ /// 协议目录
+ ///
+ public string ProtocolScriptPath { get; set; } = "";
+}
\ No newline at end of file
diff --git a/FantasyNetTest/Config.json b/FantasyNetTest/Config.json
new file mode 100644
index 0000000..c47c2dd
--- /dev/null
+++ b/FantasyNetTest/Config.json
@@ -0,0 +1,6 @@
+{
+ "Server": "127.0.0.1:20001",
+ "Heartbeat": 5,
+ "ProtocolScriptPath": "D:\\myself\\Games\\Fishing2\\Assets\\Scripts\\Generate",
+ "Account": "test003"
+}
\ No newline at end of file
diff --git a/FantasyNetTest/FantasyNetTest.csproj b/FantasyNetTest/FantasyNetTest.csproj
new file mode 100644
index 0000000..341df3e
--- /dev/null
+++ b/FantasyNetTest/FantasyNetTest.csproj
@@ -0,0 +1,31 @@
+
+
+
+ WinExe
+ net8.0-windows
+ enable
+ true
+ enable
+ true
+
+
+
+
+ Never
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FantasyNetTest/FantasyNetTest.csproj.user b/FantasyNetTest/FantasyNetTest.csproj.user
new file mode 100644
index 0000000..3a34caa
--- /dev/null
+++ b/FantasyNetTest/FantasyNetTest.csproj.user
@@ -0,0 +1,8 @@
+
+
+
+
+ Form
+
+
+
\ No newline at end of file
diff --git a/FantasyNetTest/Form1.Designer.cs b/FantasyNetTest/Form1.Designer.cs
new file mode 100644
index 0000000..b99b35d
--- /dev/null
+++ b/FantasyNetTest/Form1.Designer.cs
@@ -0,0 +1,210 @@
+namespace FantasyNetTest;
+
+partial class Form1
+{
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ components = new System.ComponentModel.Container();
+ groupBox4 = new GroupBox();
+ linkLabel1 = new LinkLabel();
+ logTextBox = new RichTextBox();
+ groupBox1 = new GroupBox();
+ buttonConnect = new Button();
+ textBoxAccount = new TextBox();
+ groupBox2 = new GroupBox();
+ panel1 = new Panel();
+ buttonSend = new Button();
+ timer1 = new System.Windows.Forms.Timer(components);
+ groupBox3 = new GroupBox();
+ comboBoxProtocol = new ComboBox();
+ timer2 = new System.Windows.Forms.Timer(components);
+ groupBox4.SuspendLayout();
+ groupBox1.SuspendLayout();
+ groupBox2.SuspendLayout();
+ groupBox3.SuspendLayout();
+ SuspendLayout();
+ //
+ // groupBox4
+ //
+ groupBox4.Controls.Add(linkLabel1);
+ groupBox4.Controls.Add(logTextBox);
+ groupBox4.Location = new Point(329, 7);
+ groupBox4.Name = "groupBox4";
+ groupBox4.Size = new Size(375, 406);
+ groupBox4.TabIndex = 0;
+ groupBox4.TabStop = false;
+ groupBox4.Text = "输出";
+ //
+ // linkLabel1
+ //
+ linkLabel1.AutoSize = true;
+ linkLabel1.LinkBehavior = LinkBehavior.NeverUnderline;
+ linkLabel1.LinkColor = Color.Black;
+ linkLabel1.Location = new Point(354, 13);
+ linkLabel1.Name = "linkLabel1";
+ linkLabel1.Size = new Size(16, 17);
+ linkLabel1.TabIndex = 2;
+ linkLabel1.TabStop = true;
+ linkLabel1.Text = "X";
+ linkLabel1.LinkClicked += linkLabel1_LinkClicked;
+ //
+ // logTextBox
+ //
+ logTextBox.BackColor = SystemColors.Control;
+ logTextBox.BorderStyle = BorderStyle.None;
+ logTextBox.Location = new Point(6, 22);
+ logTextBox.Name = "logTextBox";
+ logTextBox.ReadOnly = true;
+ logTextBox.Size = new Size(363, 372);
+ logTextBox.TabIndex = 0;
+ logTextBox.Text = "";
+ //
+ // groupBox1
+ //
+ groupBox1.Controls.Add(buttonConnect);
+ groupBox1.Controls.Add(textBoxAccount);
+ groupBox1.Location = new Point(7, 7);
+ groupBox1.Name = "groupBox1";
+ groupBox1.Size = new Size(316, 62);
+ groupBox1.TabIndex = 1;
+ groupBox1.TabStop = false;
+ groupBox1.Text = "账号";
+ //
+ // buttonConnect
+ //
+ buttonConnect.Location = new Point(235, 23);
+ buttonConnect.Name = "buttonConnect";
+ buttonConnect.Size = new Size(75, 23);
+ buttonConnect.TabIndex = 2;
+ buttonConnect.Text = "登录";
+ buttonConnect.UseVisualStyleBackColor = true;
+ buttonConnect.Click += buttonConnect_Click;
+ //
+ // textBoxAccount
+ //
+ textBoxAccount.Location = new Point(10, 23);
+ textBoxAccount.Name = "textBoxAccount";
+ textBoxAccount.Size = new Size(219, 23);
+ textBoxAccount.TabIndex = 0;
+ //
+ // groupBox2
+ //
+ groupBox2.Controls.Add(panel1);
+ groupBox2.Location = new Point(5, 145);
+ groupBox2.Name = "groupBox2";
+ groupBox2.Size = new Size(318, 225);
+ groupBox2.TabIndex = 2;
+ groupBox2.TabStop = false;
+ groupBox2.Text = "协议内容";
+ //
+ // panel1
+ //
+ panel1.Location = new Point(4, 20);
+ panel1.Name = "panel1";
+ panel1.Size = new Size(308, 199);
+ panel1.TabIndex = 0;
+ //
+ // buttonSend
+ //
+ buttonSend.BackColor = SystemColors.ActiveCaption;
+ buttonSend.Font = new Font("Microsoft YaHei UI", 11F, FontStyle.Bold);
+ buttonSend.Location = new Point(7, 376);
+ buttonSend.Name = "buttonSend";
+ buttonSend.Size = new Size(318, 37);
+ buttonSend.TabIndex = 0;
+ buttonSend.Text = "发送";
+ buttonSend.UseVisualStyleBackColor = false;
+ buttonSend.Click += buttonSend_Click;
+ //
+ // timer1
+ //
+ timer1.Enabled = true;
+ timer1.Interval = 1;
+ timer1.Tick += timer1_Tick;
+ //
+ // groupBox3
+ //
+ groupBox3.Controls.Add(comboBoxProtocol);
+ groupBox3.Location = new Point(7, 75);
+ groupBox3.Name = "groupBox3";
+ groupBox3.Size = new Size(316, 64);
+ groupBox3.TabIndex = 3;
+ groupBox3.TabStop = false;
+ groupBox3.Text = "协议选择";
+ //
+ // comboBoxProtocol
+ //
+ comboBoxProtocol.FormattingEnabled = true;
+ comboBoxProtocol.Location = new Point(7, 28);
+ comboBoxProtocol.Name = "comboBoxProtocol";
+ comboBoxProtocol.Size = new Size(303, 25);
+ comboBoxProtocol.TabIndex = 0;
+ //
+ // timer2
+ //
+ timer2.Enabled = true;
+ timer2.Interval = 16;
+ timer2.Tick += timer2_Tick;
+ //
+ // Form1
+ //
+ AutoScaleDimensions = new SizeF(7F, 17F);
+ AutoScaleMode = AutoScaleMode.Font;
+ ClientSize = new Size(707, 418);
+ Controls.Add(buttonSend);
+ Controls.Add(groupBox3);
+ Controls.Add(groupBox2);
+ Controls.Add(groupBox1);
+ Controls.Add(groupBox4);
+ Name = "Form1";
+ Text = "协议调试器";
+ groupBox4.ResumeLayout(false);
+ groupBox4.PerformLayout();
+ groupBox1.ResumeLayout(false);
+ groupBox1.PerformLayout();
+ groupBox2.ResumeLayout(false);
+ groupBox3.ResumeLayout(false);
+ ResumeLayout(false);
+ }
+
+ #endregion
+
+ private GroupBox groupBox1;
+ private GroupBox groupBox2;
+ private GroupBox groupBox4;
+ private RichTextBox logTextBox;
+ private TextBox textBoxAccount;
+ private Button buttonConnect;
+ private Button buttonSend;
+ private System.Windows.Forms.Timer timer1;
+ private LinkLabel linkLabel1;
+ private GroupBox groupBox3;
+ private ComboBox comboBoxProtocol;
+ private Panel panel1;
+ private System.Windows.Forms.Timer timer2;
+}
\ No newline at end of file
diff --git a/FantasyNetTest/Form1.cs b/FantasyNetTest/Form1.cs
new file mode 100644
index 0000000..57d0849
--- /dev/null
+++ b/FantasyNetTest/Form1.cs
@@ -0,0 +1,757 @@
+using System.Globalization;
+using System.Reflection;
+using System.Text.Json;
+using NBC;
+using NBC.InnerMessage;
+using NBC.Network;
+using NBC.Network.Interface;
+using ProtoBuf;
+using EventArgs = System.EventArgs;
+
+#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑添加 'required' 修饰符或声明为可以为 null。
+
+namespace FantasyNetTest;
+
+public partial class Form1 : Form
+{
+ private Config _config;
+
+ private ScriptLoader _scriptLoader;
+
+ private List _allRequest = new List();
+
+ private object _selectProtocolObject;
+ private TableLayoutPanel _tableLayoutPanel;
+
+ private Scene _scene;
+
+ private string PathRoot;
+
+ public Form1()
+ {
+ InitializeComponent();
+ this.FormBorderStyle = FormBorderStyle.FixedSingle;
+ this.MaximizeBox = false;
+ this.MinimizeBox = false;
+
+ comboBoxProtocol.SelectedIndexChanged += ComboBox_SelectedIndexChanged;
+ comboBoxProtocol.DropDownStyle = ComboBoxStyle.DropDownList;
+ _config ??= new Config();
+ LoadConfig();
+ textBoxAccount.Text = _config.Account;
+
+ _scriptLoader = new ScriptLoader();
+
+
+ var time = UnityEngine.Time.time;
+ Log.Init(logTextBox);
+ LoadProtocol();
+ StartAsync().Coroutine();
+ }
+
+ private void SwitchLoginButtonState()
+ {
+ if (_session == null)
+ {
+ buttonConnect.Text = "登录";
+ }
+ else
+ {
+ buttonConnect.Text = "退出";
+ }
+ }
+
+ #region 框架
+
+ private NBC.Platform.Unity.Entry _entry;
+
+ private async FTask StartAsync()
+ {
+ Log.Info("开始初始化框架");
+ // 初始化框架
+ var assemblies = AppDomain.CurrentDomain.GetAssemblies();
+ List loadAssemblies = new List();
+ foreach (var assembly in assemblies)
+ {
+ // 跳过系统程序集以提高性能(可选)
+ if (IsSystemAssembly(assembly))
+ continue;
+ loadAssemblies.Add(assembly);
+ }
+
+ _entry = await NBC.Platform.Unity.Entry.Initialize(loadAssemblies.ToArray());
+ _scene = await Scene.Create(SceneRuntimeMode.MainThread);
+ _scene.AddComponent();
+
+ NetMessageDelegate.OnMessage += OnMessage;
+ NetMessageDelegate.OnRequest += OnRequest;
+ NetMessageDelegate.OnResponse += OnResponse;
+ Log.Succeed("初始化框架成功");
+ }
+
+ // 判断是否是系统程序集(可选优化)
+ private static bool IsSystemAssembly(System.Reflection.Assembly assembly)
+ {
+ string assemblyName = assembly.FullName;
+ return assemblyName.StartsWith("System") ||
+ assemblyName.StartsWith("Microsoft.") ||
+ assemblyName.StartsWith("UnityEngine") ||
+ assemblyName.StartsWith("UnityEditor") ||
+ assemblyName.StartsWith("mscorlib") ||
+ assemblyName.StartsWith("netstandard") ||
+ assemblyName.StartsWith("nunit.") ||
+ assemblyName.StartsWith("Unity.");
+ }
+
+ private void OnMessage(Session session, Type type, object message, uint rpcId, uint protocolCode)
+ {
+ if (session != _session) return;
+ var json = message.ToJson();
+ Log.Succeed($"收到消息, type={type.Name} data={json}");
+ }
+
+ private void OnRequest(IRequest request, long routeId = 0)
+ {
+ if (request == null) return;
+ if (request is PingRequest) return;
+ var json = request.ToJson();
+ Log.Info($"发送消息, op={request.OpCode()} data={json}");
+ }
+
+ private void OnResponse(IResponse request)
+ {
+ if (request == null) return;
+ if (request is PingResponse) return;
+ var json = request.ToJson();
+ Log.Succeed($"收到响应消息, op={request.OpCode()} data={json}");
+ }
+
+ #endregion
+
+
+ #region 登录
+
+ private Session? _session;
+
+ private async FTask OnLoginButtonClick(string account)
+ {
+ // 根据用户名来选择目标的鉴权服务器
+ // 根据鉴权服务器地址来创建一个新的网络会话
+ _session = SessionHelper.CreateSession(_scene, _config.Server, OnConnectComplete,
+ OnConnectFail,
+ OnConnectDisconnect);
+
+ var acc = account;
+
+
+ // 首先确保已加载包含该类的程序集
+ var type = CoderLoader.GetTypeByName("C2A_LoginRequest");
+
+
+ if (type == null)
+ {
+ Log.Error("NBC.C2A_LoginRequest 类未加载");
+ return;
+ }
+
+ var instance = Activator.CreateInstance(type);
+
+ if (instance == null)
+ {
+ Log.Error("NBC.C2A_LoginRequest 类实例化失败");
+ return;
+ }
+
+ instance.SetPropertyValue("Username", acc);
+ instance.SetPropertyValue("Password", acc);
+ instance.SetPropertyValue("LoginType", 1);
+
+ if (instance is not IRequest loginRequest)
+ {
+ Log.Error("instance is not IRequest loginRequest");
+ return;
+ }
+
+ var response = await _session.Call(loginRequest);
+ if (response.ErrorCode != 0)
+ {
+ Log.Error($"登录发生错误{response.ErrorCode}");
+ return;
+ }
+
+ string? token = response.GetPropertyValue("ToKen")?.ToString();
+
+ if (string.IsNullOrEmpty(token))
+ {
+ Log.Error($"登录 Token is null");
+ return;
+ }
+
+ if (!_scene.GetComponent().Parse(token, out var payload))
+ {
+ return;
+ }
+
+ // 根据ToKen返回的Address登录到Gate服务器
+ _session = SessionHelper.CreateSession(_scene, payload.Address, OnConnectComplete, OnConnectFail,
+ OnConnectDisconnect);
+
+
+ // 首先确保已加载包含该类的程序集
+ var typeGate = CoderLoader.GetTypeByName("C2G_LoginRequest");
+
+
+ if (typeGate == null)
+ {
+ Log.Error("NBC.C2G_LoginRequest 类未加载");
+ return;
+ }
+
+ var instanceGate = Activator.CreateInstance(typeGate);
+
+ if (instanceGate == null)
+ {
+ Log.Error("NBC.C2G_LoginRequest 类实例化失败");
+ return;
+ }
+
+ instanceGate.SetPropertyValue("ToKen", token);
+ if (instanceGate is not IRequest loginGateRequest)
+ {
+ Log.Error("instance is not IRequest loginRequest");
+ return;
+ }
+
+ var responseGate = await _session.Call(loginGateRequest);
+ if (responseGate.ErrorCode != 0)
+ {
+ Log.Error($"登录网关发生错误{responseGate.ErrorCode}");
+ return;
+ }
+
+ Log.Succeed($"登录到Gate服务器成功!ErrorCode:{responseGate.ErrorCode}");
+
+
+ // // 发送登录的请求给服务器
+ // var response = (A2C_LoginResponse)await _session.Call(new C2A_LoginRequest()
+ // {
+ // Username = acc,
+ // Password = acc,
+ // LoginType = 1
+ // });
+ //
+ // if (response.ErrorCode != 0)
+ // {
+ // Log.Error($"登录发生错误{response.ErrorCode}");
+ // return;
+ // }
+ //
+ // if (!_scene.GetComponent().Parse(response.ToKen, out var payload))
+ // {
+ // return;
+ // }
+ //
+ // // 根据ToKen返回的Address登录到Gate服务器
+ // _session = SessionHelper.CreateSession(_scene, payload.Address, OnConnectComplete, OnConnectFail,
+ // OnConnectDisconnect);
+ // // 发送登录请求到Gate服务器
+ // var loginResponse = (G2C_LoginResponse)await _session.Call(new C2G_LoginRequest()
+ // {
+ // ToKen = response.ToKen
+ // });
+ // if (loginResponse.ErrorCode != 0)
+ // {
+ // Log.Error($"登录发生错误{loginResponse.ErrorCode}");
+ // return;
+ // }
+ //
+ // Log.Succeed($"登录到Gate服务器成功!ErrorCode:{loginResponse.ErrorCode}");
+ SwitchLoginButtonState();
+ }
+
+ private void OnConnectComplete()
+ {
+ Log.Succeed("连接成功");
+ // 添加心跳组件给Session。
+ // Start(2000)就是2000毫秒。
+ _session?.AddComponent().Start(5000);
+ }
+
+ private void OnConnectFail()
+ {
+ Log.Error("连接失败");
+ _session = null;
+ SwitchLoginButtonState();
+ }
+
+ private void OnConnectDisconnect()
+ {
+ Log.Error("连接断开");
+ _session = null;
+ SwitchLoginButtonState();
+ }
+
+ #endregion
+
+
+ #region Config
+
+ private void LoadConfig()
+ {
+ var configPath = Path.Combine(Application.StartupPath, "Config.json");
+ if (File.Exists(configPath))
+ {
+ string jsonContent = File.ReadAllText(configPath);
+ var cfg = JsonSerializer.Deserialize(jsonContent);
+ if (cfg != null)
+ {
+ _config.Account = cfg.Account;
+ _config.Server = cfg.Server;
+ _config.Heartbeat = cfg.Heartbeat;
+ _config.ProtocolScriptPath = cfg.ProtocolScriptPath;
+ }
+ }
+ }
+
+ private void SaveConfig()
+ {
+ _config.Account = textBoxAccount.Text;
+ var configPath = Path.Combine(Application.StartupPath, "Config.json");
+ var json = JsonSerializer.Serialize(_config);
+ File.WriteAllText(configPath, json);
+ }
+
+ #endregion
+
+ #region 协议下拉框
+
+ private void LoadProtocol()
+ {
+ Log.Info("开始加载协议");
+ _scriptLoader.LoadAndExecuteScriptsAsync(_config.ProtocolScriptPath);
+ Log.Succeed("加载协议完成");
+ CoderLoader.Load();
+ SetProtocolComboBox();
+ }
+
+ private void SetProtocolComboBox()
+ {
+ comboBoxProtocol.Items.Clear();
+
+ var types = CoderLoader.RequestTypes;
+ _allRequest.Clear();
+ _allRequest.AddRange(types);
+ foreach (var type in _allRequest)
+ {
+ comboBoxProtocol.Items.Add(type.Name);
+ }
+ }
+
+ // 事件处理方法
+ private void ComboBox_SelectedIndexChanged(object sender, EventArgs e)
+ {
+ ComboBox comboBox = (ComboBox)sender;
+
+ var index = comboBox.SelectedIndex;
+ var proto = comboBox.SelectedItem?.ToString();
+ Log.Info($"选中协议:{proto} Index={index}");
+ SelectedProtocol(_allRequest[index]);
+ }
+
+ private void SelectedProtocol(Type type)
+ {
+ var obj = Activator.CreateInstance(type);
+ if (obj != null)
+ {
+ _selectProtocolObject = obj;
+ LoadCacheProtocol(obj);
+ // 生成UI
+ GenerateKeyValueInputs(panel1, _selectProtocolObject);
+ }
+ }
+
+ #endregion
+
+ #region 协议内容反射
+
+ private List _propertyInfos = new List();
+
+ public void GenerateKeyValueInputs(Panel containerPanel, object configObject)
+ {
+ // 清除现有控件
+ containerPanel.Controls.Clear();
+
+
+ // 创建TableLayoutPanel
+ var tablePanel = new TableLayoutPanel
+ {
+ ColumnCount = 2,
+ Dock = DockStyle.Top, // 重要:设置为Top而不是Fill
+ AutoSize = true,
+ AutoSizeMode = AutoSizeMode.GrowAndShrink,
+ GrowStyle = TableLayoutPanelGrowStyle.AddRows
+ };
+
+ _tableLayoutPanel = tablePanel;
+
+ // 设置列宽比例
+ tablePanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 30));
+ tablePanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 70));
+
+ // 获取对象的所有属性
+ var propertySources = configObject.GetType().GetProperties();
+
+ _propertyInfos.Clear();
+
+ foreach (var propertySource in propertySources)
+ {
+ if (propertySource.GetCustomAttribute() != null)
+ {
+ _propertyInfos.Add(propertySource);
+ }
+ }
+
+ PropertyInfo[] properties = _propertyInfos.ToArray();
+
+ //var properties
+ // 设置表格行数和列数
+ tablePanel.ColumnCount = 2;
+ tablePanel.RowCount = properties.Length;
+
+ // 设置固定的行高
+ int rowHeight = 30; // 每行固定高度
+
+
+ // 添加属性和值输入框
+ for (int i = 0; i < properties.Length; i++)
+ {
+ var property = properties[i];
+
+ // 设置行样式
+ tablePanel.RowStyles.Add(new RowStyle(SizeType.Absolute, rowHeight));
+
+ // 添加属性名标签 (Key)
+ var label = new Label
+ {
+ Text = $@"[{GetTypeShowName(property.PropertyType)}] {property.Name}",
+ Dock = DockStyle.Fill,
+ TextAlign = ContentAlignment.MiddleLeft,
+ Margin = new Padding(3),
+ Height = rowHeight,
+ };
+ if (label.Text.Length > 13)
+ {
+ label.Text = label.Text.Substring(0, 12);
+ }
+
+ tablePanel.Controls.Add(label, 0, i);
+
+ tablePanel.Controls.Add(CreateControl(property, configObject), 1, i);
+ }
+
+ // 将TableLayoutPanel添加到容器Panel中
+ containerPanel.Controls.Add(tablePanel);
+
+ // 启用自动滚动
+ containerPanel.AutoScroll = true;
+ containerPanel.VerticalScroll.Enabled = true;
+ containerPanel.VerticalScroll.Visible = true;
+ }
+
+ private Control CreateControl(PropertyInfo property, object configObject)
+ {
+ // 添加值输入控件 (Value)
+ Control inputControl;
+
+ // 根据属性类型创建不同的输入控件
+ if (property.PropertyType == typeof(bool))
+ {
+ var checkbox = new CheckBox
+ {
+ Checked = (bool)property.GetValue(configObject),
+ Dock = DockStyle.Fill,
+ TextAlign = ContentAlignment.MiddleLeft
+ };
+ inputControl = checkbox;
+ }
+ else if (property.PropertyType == typeof(int) || property.PropertyType == typeof(long) ||
+ property.PropertyType == typeof(double) ||
+ property.PropertyType == typeof(decimal) || property.PropertyType == typeof(float))
+ {
+ var numericBox = new NumericUpDown
+ {
+ Value = Convert.ToDecimal(property.GetValue(configObject)),
+ Dock = DockStyle.Fill,
+ Minimum = decimal.MinValue,
+ Maximum = decimal.MaxValue
+ };
+ inputControl = numericBox;
+ }
+ else if (property.PropertyType == typeof(uint))
+ {
+ var numericBox = new NumericUpDown
+ {
+ Value = Convert.ToDecimal(property.GetValue(configObject)),
+ Dock = DockStyle.Fill,
+ Minimum = uint.MinValue,
+ Maximum = uint.MaxValue
+ };
+ inputControl = numericBox;
+ }
+ else if (property.PropertyType == typeof(short))
+ {
+ var numericBox = new NumericUpDown
+ {
+ Value = Convert.ToDecimal(property.GetValue(configObject)),
+ Dock = DockStyle.Fill,
+ Minimum = short.MinValue,
+ Maximum = short.MaxValue
+ };
+ inputControl = numericBox;
+ }
+ else
+ {
+ var textBox = new TextBox
+ {
+ Text = property.GetValue(configObject)?.ToString() ?? "",
+ Dock = DockStyle.Fill,
+ Margin = new Padding(3)
+ };
+ inputControl = textBox;
+ }
+
+ return inputControl;
+ }
+
+ private string GetTypeShowName(Type type)
+ {
+ if (type == typeof(bool))
+ {
+ return "B";
+ }
+
+ if (type == typeof(byte))
+ {
+ return "B";
+ }
+
+ if (type == typeof(short))
+ {
+ return "S";
+ }
+
+ if (type == typeof(int))
+ {
+ return "I";
+ }
+
+ if (type == typeof(uint))
+ {
+ return "U";
+ }
+
+ if (type == typeof(long))
+ {
+ return "L";
+ }
+
+ if (type == typeof(double))
+ {
+ return "D";
+ }
+
+ if (type == typeof(float))
+ {
+ return "F";
+ }
+
+ if (type == typeof(decimal))
+ {
+ return "M";
+ }
+
+ if (type == typeof(string))
+ {
+ return "S";
+ }
+
+ return type.Name;
+ }
+
+ public void SaveDataFromUI(object configObject)
+ {
+ TableLayoutPanel tablePanel = _tableLayoutPanel;
+
+ // var properties = configObject.GetType().GetProperties();
+
+ for (int i = 0; i < _propertyInfos.Count; i++)
+ {
+ var property = _propertyInfos[i];
+ var inputControl = tablePanel.GetControlFromPosition(1, i);
+
+ try
+ {
+ if (inputControl is CheckBox checkbox)
+ {
+ property.SetValue(configObject, checkbox.Checked);
+ }
+ else if (inputControl is NumericUpDown numericBox)
+ {
+ if (property.PropertyType == typeof(int))
+ property.SetValue(configObject, (int)numericBox.Value);
+ else if (property.PropertyType == typeof(double))
+ property.SetValue(configObject, (double)numericBox.Value);
+ else if (property.PropertyType == typeof(decimal))
+ property.SetValue(configObject, numericBox.Value);
+ else if (property.PropertyType == typeof(uint))
+ property.SetValue(configObject, (uint)numericBox.Value);
+ else if (property.PropertyType == typeof(float))
+ property.SetValue(configObject, (float)numericBox.Value);
+ else if (property.PropertyType == typeof(short))
+ property.SetValue(configObject, (short)numericBox.Value);
+ }
+ else if (inputControl is TextBox textBox)
+ {
+ property.SetValue(configObject, Convert.ChangeType(textBox.Text, property.PropertyType));
+ }
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"设置属性 {property.Name} 时出错: {ex.Message}");
+ }
+ }
+ }
+
+ #endregion
+
+ #region 协议内存缓存和加载
+
+ private string ProtocolPathPath = Path.Combine(Application.StartupPath, "Protocol");
+
+ private void CacheProtocol(object obj)
+ {
+ if (obj == null) return;
+ if (!Directory.Exists(ProtocolPathPath))
+ {
+ Directory.CreateDirectory(ProtocolPathPath);
+ }
+
+ var json = JsonSerializer.Serialize(obj);
+ var type = obj.GetType();
+ File.WriteAllText(Path.Combine(ProtocolPathPath, type.Name), json);
+ }
+
+ private void LoadCacheProtocol(object obj)
+ {
+ if (obj == null) return;
+ if (!Directory.Exists(ProtocolPathPath))
+ {
+ return;
+ }
+
+ var path = Path.Combine(ProtocolPathPath, obj.GetType().Name);
+ if (File.Exists(path))
+ {
+ var json = File.ReadAllText(path);
+ var deserializeObj = JsonSerializer.Deserialize(json, obj.GetType());
+ if (deserializeObj != null)
+ {
+ // 获取所有可写属性
+ var properties = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
+ .Where(p => p.CanWrite);
+
+ foreach (var property in properties)
+ {
+ // 从反序列化对象中获取值
+ var value = property.GetValue(deserializeObj);
+ // 设置到目标对象中
+ property.SetValue(obj, value);
+ }
+
+ // 或者处理字段(如果需要)
+ var fields = obj.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance)
+ .Where(f => !f.IsInitOnly); // 排除只读字段
+
+ foreach (var field in fields)
+ {
+ var value = field.GetValue(deserializeObj);
+ field.SetValue(obj, value);
+ }
+ }
+ }
+ }
+
+ #endregion
+
+ #region 事件
+
+ private void buttonSend_Click(object sender, EventArgs e)
+ {
+ if (_session == null)
+ {
+ Log.Error("未连接服务器!");
+ return;
+ }
+
+ if (_selectProtocolObject == null)
+ {
+ Log.Error("未选择协议!");
+ return;
+ }
+
+ SaveDataFromUI(_selectProtocolObject);
+ CacheProtocol(_selectProtocolObject);
+
+ if (_selectProtocolObject is IRequest request)
+ {
+ _session.Call(request).Coroutine();
+ }
+ }
+
+ private void buttonConnect_Click(object sender, EventArgs e)
+ {
+ if (_session == null)
+ {
+ if (string.IsNullOrEmpty(textBoxAccount.Text))
+ {
+ Log.Error("未输入账号");
+ return;
+ }
+
+ //登录
+ OnLoginButtonClick(textBoxAccount.Text).Coroutine();
+ SwitchLoginButtonState();
+ }
+ else
+ {
+ //退出
+ _session.Dispose();
+ }
+
+ SaveConfig();
+ }
+
+
+ private void timer1_Tick(object sender, EventArgs e)
+ {
+ Log.Tick();
+ }
+
+ private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
+ {
+ Log.Clear();
+ }
+
+ private void timer2_Tick(object sender, EventArgs e)
+ {
+ _entry?.Update();
+ if (_session != null && _session.IsDisposed)
+ {
+ _session.Dispose();
+ _session = null;
+ return;
+ }
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/FantasyNetTest/Form1.resx b/FantasyNetTest/Form1.resx
new file mode 100644
index 0000000..ef1a3ec
--- /dev/null
+++ b/FantasyNetTest/Form1.resx
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ 17, 17
+
+
+ 109, 17
+
+
\ No newline at end of file
diff --git a/FantasyNetTest/Loader/ScriptLoader.cs b/FantasyNetTest/Loader/ScriptLoader.cs
new file mode 100644
index 0000000..9b77cc8
--- /dev/null
+++ b/FantasyNetTest/Loader/ScriptLoader.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.Loader;
+using System.Threading.Tasks;
+using FantasyNetTest;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Scripting;
+using Microsoft.CodeAnalysis.Scripting;
+using NBC;
+
+public class ScriptLoader
+{
+ public void LoadAndExecuteScriptsAsync(string folderPath)
+ {
+ // 1. 读取所有 .cs 文件
+ var csFiles = Directory.GetFiles(folderPath, "*.cs", SearchOption.AllDirectories);
+
+ if (!csFiles.Any())
+ {
+ Console.WriteLine("没有找到 .cs 文件");
+ return;
+ }
+
+ // 2. 创建语法树
+ var syntaxTrees = csFiles.Select(file =>
+ CSharpSyntaxTree.ParseText(File.ReadAllText(file))
+ );
+
+ // 3. 引用程序集
+ var references = AppDomain.CurrentDomain.GetAssemblies()
+ .Where(a => !a.IsDynamic && !string.IsNullOrWhiteSpace(a.Location))
+ .Select(a => MetadataReference.CreateFromFile(a.Location))
+ .Cast();
+
+ // 4. 编译成内存程序集
+ var compilation = CSharpCompilation.Create(
+ assemblyName: "DynamicScripts",
+ syntaxTrees: syntaxTrees,
+ references: references,
+ options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
+ );
+
+ using var ms = new MemoryStream();
+ var result = compilation.Emit(ms);
+
+ if (!result.Success)
+ {
+ foreach (var diag in result.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error))
+ Console.WriteLine(diag.ToString());
+ return;
+ }
+
+ // 5. 加载程序集
+ ms.Seek(0, SeekOrigin.Begin);
+
+ var assembly = AssemblyLoadContext.Default.LoadFromStream(ms);
+
+ // 6. 调用示例
+ var type = assembly.GetType("MyNamespace.MyClass"); // 你的类名
+ var method = type?.GetMethod("Run"); // 你的方法名
+ method?.Invoke(null, null); // 假设是静态方法无参数
+ }
+}
\ No newline at end of file
diff --git a/FantasyNetTest/Log.cs b/FantasyNetTest/Log.cs
new file mode 100644
index 0000000..21f1223
--- /dev/null
+++ b/FantasyNetTest/Log.cs
@@ -0,0 +1,157 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+
+namespace FantasyNetTest
+{
+ enum LogType
+ {
+ Info = 0,
+ Warn = 1,
+ Error = 2,
+ Success = 3,
+ }
+
+ struct LogData
+ {
+ public LogType type;
+ public string Content;
+
+ public LogData(LogType type, string content)
+ {
+ this.type = type;
+ this.Content = content;
+ }
+ }
+
+ internal static class Log
+ {
+ private static readonly Queue _logDatas = new Queue();
+
+ private static RichTextBox? _logTextBox;
+ private static bool _init;
+
+ public static void Init(RichTextBox? logTextBox)
+ {
+ _logTextBox = logTextBox;
+ _init = true;
+ }
+
+ #region Input
+
+ public static void Info(object obj, params object?[] args)
+ {
+ AddLog(LogType.Info, obj, args);
+ }
+
+ public static void Warn(object obj, params object?[] args)
+ {
+ AddLog(LogType.Warn, obj, args);
+ }
+
+ public static void Error(object obj, params object?[] args)
+ {
+ AddLog(LogType.Error, obj, args);
+ }
+
+ public static void Succeed(object obj, params object?[] args)
+ {
+ AddLog(LogType.Success, obj, args);
+ }
+
+ public static void AddLog(LogType type, object obj, params object?[] args)
+ {
+ if (!_init) return;
+ string content;
+ if (obj is string str)
+ {
+ content = str;
+ }
+ else
+ {
+ content = JsonSerializer.Serialize(obj);
+ }
+
+ if (!string.IsNullOrEmpty(content))
+ {
+ try
+ {
+ content = string.Format(content, args);
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+
+ _logDatas.Enqueue(new LogData { type = type, Content = content });
+ }
+
+ #endregion
+
+ #region 显示相关
+
+ public static void Tick()
+ {
+ if (_logDatas.Count < 1) return;
+
+ var count = _logDatas.Count;
+
+ for (int i = 0; i < count; i++)
+ {
+ AppendLog(_logDatas.Dequeue());
+ }
+ }
+
+ private static void AppendLog(LogData logData)
+ {
+ if (logData.type == LogType.Error)
+ {
+ _logTextBox.AppendTextWithColor(logData.Content, Color.Crimson);
+ }
+ else if (logData.type == LogType.Warn)
+ {
+ _logTextBox.AppendTextWithColor(logData.Content, Color.Coral);
+ }
+ else if (logData.type == LogType.Success)
+ {
+ _logTextBox.AppendTextWithColor(logData.Content, Color.ForestGreen);
+ }
+ else
+ {
+ _logTextBox.AppendTextWithColor(logData.Content, Color.Black);
+ }
+ }
+
+
+ public static void Clear()
+ {
+ if (!_init || _logTextBox == null) return;
+ _logTextBox.Clear();
+ _logTextBox.ScrollToCaret();
+ _logTextBox.ResumeLayout();
+ }
+
+ private static void AppendTextWithColor(this RichTextBox? rtb, string text, Color color, bool addNewLine = true)
+ {
+ if (rtb == null) return;
+
+ rtb.SuspendLayout();
+ rtb.SelectionStart = rtb.TextLength;
+ rtb.SelectionLength = 0;
+ rtb.SelectionColor = color;
+ rtb.AppendText(addNewLine ? $"{text}{Environment.NewLine}" : text);
+ rtb.SelectionColor = rtb.ForeColor;
+
+ rtb.ScrollToCaret();
+
+ rtb.ResumeLayout();
+ }
+
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/FantasyNetTest/LoginTask.cs b/FantasyNetTest/LoginTask.cs
new file mode 100644
index 0000000..cedeb08
--- /dev/null
+++ b/FantasyNetTest/LoginTask.cs
@@ -0,0 +1,45 @@
+using NBC;
+using NBC.Network;
+
+namespace FantasyNetTest;
+
+public class LoginTask
+{
+ public static async Task Login(Session session, string account, int type)
+ {
+ // // 发送登录的请求给服务器
+ // var response = (A2C_LoginResponse)await session.Call(new C2A_LoginRequest()
+ // {
+ // Username = account,
+ // Password = account,
+ // LoginType = type
+ // });
+ //
+ // if (response.ErrorCode != 0)
+ // {
+ // Log.Error($"登录发生错误{response.ErrorCode}");
+ // return;
+ // }
+ //
+ // if (!session.Scene.GetComponent().Parse(response.ToKen, out var payload))
+ // {
+ // return;
+ // }
+ //
+ // // 根据ToKen返回的Address登录到Gate服务器
+ // session = SessionHelper.CreateSession(session.Scene, payload.Address, OnConnectComplete, OnConnectFail,
+ // OnConnectDisconnect);
+ // // 发送登录请求到Gate服务器
+ // var loginResponse = (G2C_LoginResponse)await session.Call(new C2G_LoginRequest()
+ // {
+ // ToKen = response.ToKen
+ // });
+ // if (loginResponse.ErrorCode != 0)
+ // {
+ // Log.Error($"登录发生错误{loginResponse.ErrorCode}");
+ // return;
+ // }
+ //
+ // Log.Succeed($"登录到Gate服务器成功!ErrorCode:{loginResponse.ErrorCode}");
+ }
+}
\ No newline at end of file
diff --git a/FantasyNetTest/NBC.zip b/FantasyNetTest/NBC.zip
new file mode 100644
index 0000000..786802e
Binary files /dev/null and b/FantasyNetTest/NBC.zip differ
diff --git a/FantasyNetTest/NBC/Core/Assembly/AssemblyInfo.cs b/FantasyNetTest/NBC/Core/Assembly/AssemblyInfo.cs
new file mode 100644
index 0000000..7f6c1d0
--- /dev/null
+++ b/FantasyNetTest/NBC/Core/Assembly/AssemblyInfo.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using NBC.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 NBC.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/FantasyNetTest/NBC/Core/Assembly/AssemblySystem.cs b/FantasyNetTest/NBC/Core/Assembly/AssemblySystem.cs
new file mode 100644
index 0000000..25de737
--- /dev/null
+++ b/FantasyNetTest/NBC/Core/Assembly/AssemblySystem.cs
@@ -0,0 +1,286 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Security.Cryptography;
+using System.Text;
+using NBC.Async;
+using NBC.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 NBC.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/FantasyNetTest/NBC/Core/Assembly/IAssembly.cs b/FantasyNetTest/NBC/Core/Assembly/IAssembly.cs
new file mode 100644
index 0000000..f86c9f1
--- /dev/null
+++ b/FantasyNetTest/NBC/Core/Assembly/IAssembly.cs
@@ -0,0 +1,27 @@
+using System;
+using NBC.Async;
+
+namespace NBC.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/FantasyNetTest/NBC/Core/Attributes/Attributes.cs b/FantasyNetTest/NBC/Core/Attributes/Attributes.cs
new file mode 100644
index 0000000..1c95110
--- /dev/null
+++ b/FantasyNetTest/NBC/Core/Attributes/Attributes.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace NBC
+{
+ [AttributeUsage(AttributeTargets.Class)]
+ public class BaseAttribute : Attribute
+ {
+ }
+
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Method)]
+ public class SortAttribute : Attribute
+ {
+ public int Sort;
+
+ public SortAttribute(int sort)
+ {
+ Sort = sort;
+ }
+ }
+}
\ No newline at end of file
diff --git a/FantasyNetTest/NBC/Core/DataStructure/Collection/CircularBuffer.cs b/FantasyNetTest/NBC/Core/DataStructure/Collection/CircularBuffer.cs
new file mode 100644
index 0000000..d7a68c0
--- /dev/null
+++ b/FantasyNetTest/NBC/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 NBC.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/FantasyNetTest/NBC/Core/DataStructure/Collection/ConcurrentOneToManyListPool.cs b/FantasyNetTest/NBC/Core/DataStructure/Collection/ConcurrentOneToManyListPool.cs
new file mode 100644
index 0000000..d8a5e94
--- /dev/null
+++ b/FantasyNetTest/NBC/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 NBC.Pool;
+
+#pragma warning disable CS8603 // Possible null reference return.
+
+namespace NBC.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();
+ }
+
+ ///
+ /// 将一个列表回收到队列中。
+ ///
+ /// 要回收的列表。
+ private void Recycle(List list)
+ {
+ list.Clear();
+
+ if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return;
+
+ _queue.Enqueue(list);
+ }
+
+ ///
+ /// 清空当前类的数据,包括从基类继承的数据以及自定义的数据队列。
+ ///
+ protected new void Clear()
+ {
+ base.Clear();
+ _queue.Clear();
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/FantasyNetTest/NBC/Core/DataStructure/Collection/ConcurrentOneToManyQueuePool.cs b/FantasyNetTest/NBC/Core/DataStructure/Collection/ConcurrentOneToManyQueuePool.cs
new file mode 100644
index 0000000..e6ee79f
--- /dev/null
+++ b/FantasyNetTest/NBC/Core/DataStructure/Collection/ConcurrentOneToManyQueuePool.cs
@@ -0,0 +1,194 @@
+#if !FANTASY_WEBGL
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using NBC.Pool;
+
+#pragma warning disable CS8603
+
+namespace NBC.DataStructure.Collection
+{
+ ///
+ /// 表示一个并发的一对多队列池,用于维护具有相同键的多个值的关联关系,实现了 接口。
+ ///
+ /// 关键字的类型,不能为空。
+ /// 值的类型。
+ public class ConcurrentOneToManyQueuePool : ConcurrentOneToManyQueue, IDisposable, IPool where TKey : notnull
+ {
+ private bool _isPool;
+ private bool _isDispose;
+
+ ///
+ /// 创建并返回一个 的实例。
+ ///
+ /// 创建的实例。
+ public static ConcurrentOneToManyQueuePool 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 ConcurrentOneToManyQueue : ConcurrentDictionary> where TKey : notnull
+ {
+ private readonly Queue> _queue = new Queue>();
+ private readonly int _recyclingLimit;
+
+ ///
+ /// 设置最大缓存数量
+ ///
+ ///
+ /// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
+ /// 2:设置成0不控制数量,全部缓存
+ ///
+ public ConcurrentOneToManyQueue(int recyclingLimit = 0)
+ {
+ _recyclingLimit = recyclingLimit;
+ }
+
+ ///
+ /// 判断指定键的队列是否包含指定值。
+ ///
+ /// 要搜索的键。
+ /// 要搜索的值。
+ /// 如果队列包含值,则为 true;否则为 false。
+ public bool Contains(TKey key, TValue value)
+ {
+ TryGetValue(key, out var list);
+
+ return list != null && list.Contains(value);
+ }
+
+ ///
+ /// 向指定键的队列中添加一个值。
+ ///
+ /// 要添加值的键。
+ /// 要添加的值。
+ public void Enqueue(TKey key, TValue value)
+ {
+ if (!TryGetValue(key, out var list))
+ {
+ list = Fetch();
+ list.Enqueue(value);
+ TryAdd(key, list);
+ return;
+ }
+
+ list.Enqueue(value);
+ }
+
+ ///
+ /// 从指定键的队列中出队并返回一个值。
+ ///
+ /// 要出队的键。
+ /// 出队的值,如果队列为空则为默认值。
+ public TValue Dequeue(TKey key)
+ {
+ if (!TryGetValue(key, out var list) || list.Count == 0) return default;
+
+ var value = list.Dequeue();
+
+ if (list.Count == 0) RemoveKey(key);
+
+ return value;
+ }
+
+ ///
+ /// 尝试从指定键的队列中出队一个值。
+ ///
+ /// 要出队的键。
+ /// 出队的值,如果队列为空则为默认值。
+ /// 如果成功出队,则为 true;否则为 false。
+ public bool TryDequeue(TKey key, out TValue value)
+ {
+ value = Dequeue(key);
+
+ return value != null;
+ }
+
+ ///
+ /// 从字典中移除指定键以及其关联的队列。
+ ///
+ /// 要移除的键。
+ public void RemoveKey(TKey key)
+ {
+ if (!TryGetValue(key, out var list)) return;
+
+ TryRemove(key, out _);
+ Recycle(list);
+ }
+
+ ///
+ /// 从队列中获取一个新的队列,如果队列为空则创建一个新的队列。
+ ///
+ /// 获取的队列。
+ private Queue Fetch()
+ {
+ return _queue.Count <= 0 ? new Queue() : _queue.Dequeue();
+ }
+
+ ///
+ /// 将一个队列回收到队列池中。
+ ///
+ /// 要回收的队列。
+ private void Recycle(Queue list)
+ {
+ list.Clear();
+
+ if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return;
+
+ _queue.Enqueue(list);
+ }
+
+ ///
+ /// 清空当前类的数据,包括从基类继承的键值对字典中的数据以及自定义的队列池。
+ ///
+ protected new void Clear()
+ {
+ base.Clear();
+ _queue.Clear();
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/FantasyNetTest/NBC/Core/DataStructure/Collection/HashSetPool.cs b/FantasyNetTest/NBC/Core/DataStructure/Collection/HashSetPool.cs
new file mode 100644
index 0000000..5e46af0
--- /dev/null
+++ b/FantasyNetTest/NBC/Core/DataStructure/Collection/HashSetPool.cs
@@ -0,0 +1,134 @@
+using System;
+using System.Collections.Generic;
+using NBC.Pool;
+
+namespace NBC.DataStructure.Collection
+{
+ ///
+ /// 可释放的哈希集合对象池。
+ ///
+ /// 哈希集合中元素的类型。
+ public sealed class HashSetPool : HashSet, IDisposable, IPool
+ {
+ private bool _isPool;
+ private bool _isDispose;
+
+ ///
+ /// 释放实例所占用的资源,并将实例返回到对象池中,以便重用。
+ ///
+ public void Dispose()
+ {
+ if (_isDispose)
+ {
+ return;
+ }
+
+ _isDispose = true;
+ Clear();
+#if FANTASY_WEBGL
+ Pool>.Return(this);
+#else
+ MultiThreadPool.Return(this);
+#endif
+ }
+
+ ///
+ /// 创建一个 哈希集合池的实例。
+ ///
+ /// 创建的实例。
+ public static HashSetPool Create()
+ {
+#if FANTASY_WEBGL
+ var list = Pool>.Rent();
+ list._isDispose = false;
+ list._isPool = true;
+ return list;
+#else
+ var list = MultiThreadPool.Rent>();
+ list._isDispose = false;
+ list._isPool = true;
+ return list;
+#endif
+ }
+
+ ///
+ /// 获取一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public bool IsPool()
+ {
+ return _isPool;
+ }
+
+ ///
+ /// 设置一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public void SetIsPool(bool isPool)
+ {
+ _isPool = isPool;
+ }
+ }
+
+ ///
+ /// 基本哈希集合对象池,他自持有实际的哈希集合。
+ ///
+ /// 哈希集合中元素的类型。
+ public sealed class HashSetBasePool : IDisposable, IPool
+ {
+ private bool _isPool;
+
+ ///
+ /// 存储实际的哈希集合
+ ///
+ public HashSet Set = new HashSet();
+
+ ///
+ /// 创建一个 基本哈希集合对象池的实例。
+ ///
+ /// 创建的实例。
+ public static HashSetBasePool Create()
+ {
+#if FANTASY_WEBGL
+ var hashSetBasePool = Pool>.Rent();
+ hashSetBasePool._isPool = true;
+ return hashSetBasePool;
+#else
+ var hashSetBasePool = MultiThreadPool.Rent>();
+ hashSetBasePool._isPool = true;
+ return hashSetBasePool;
+#endif
+ }
+
+ ///
+ /// 释放实例所占用的资源,并将实例返回到对象池中,以便重用。
+ ///
+ public void Dispose()
+ {
+ Set.Clear();
+#if FANTASY_WEBGL
+ Pool>.Return(this);
+#else
+ MultiThreadPool.Return(this);
+#endif
+ }
+
+ ///
+ /// 获取一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public bool IsPool()
+ {
+ return _isPool;
+ }
+
+ ///
+ /// 设置一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public void SetIsPool(bool isPool)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/FantasyNetTest/NBC/Core/DataStructure/Collection/ListPool.cs b/FantasyNetTest/NBC/Core/DataStructure/Collection/ListPool.cs
new file mode 100644
index 0000000..13c272f
--- /dev/null
+++ b/FantasyNetTest/NBC/Core/DataStructure/Collection/ListPool.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Collections.Generic;
+using NBC.Pool;
+
+// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
+
+namespace NBC.DataStructure.Collection
+{
+ ///
+ /// 可释放的列表(List)对象池。
+ ///
+ /// 列表中元素的类型。
+ public sealed class ListPool : List, IDisposable, IPool
+ {
+ private bool _isPool;
+ private bool _isDispose;
+
+ ///
+ /// 释放实例所占用的资源,并将实例返回到对象池中,以便重用。
+ ///
+ public void Dispose()
+ {
+ if (_isDispose)
+ {
+ return;
+ }
+
+ _isDispose = true;
+ Clear();
+#if FANTASY_WEBGL
+ Pool>.Return(this);
+#else
+ MultiThreadPool.Return(this);
+#endif
+ }
+
+ ///
+ /// 使用指定的元素创建一个 列表(List)对象池的实例。
+ ///
+ /// 要添加到列表的元素。
+ /// 创建的实例。
+ public static ListPool Create(params T[] args)
+ {
+#if FANTASY_WEBGL
+ var list = Pool>.Rent();
+#else
+ var list = MultiThreadPool.Rent>();
+#endif
+ list._isDispose = false;
+ list._isPool = true;
+
+ if (args != null)
+ {
+ list.AddRange(args);
+ }
+
+ return list;
+ }
+
+ ///
+ /// 使用指定的列表创建一个 列表(List)对象池的实例。
+ ///
+ /// 要添加到列表的元素列表。
+ /// 创建的实例。
+ public static ListPool Create(List args)
+ {
+#if FANTASY_WEBGL
+ var list = Pool>.Rent();
+#else
+ var list = MultiThreadPool.Rent>();
+#endif
+ list._isDispose = false;
+ list._isPool = true;
+
+ if (args != null)
+ {
+ list.AddRange(args);
+ }
+
+ return list;
+ }
+
+ ///
+ /// 获取一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public bool IsPool()
+ {
+ return _isPool;
+ }
+
+ ///
+ /// 设置一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public void SetIsPool(bool isPool)
+ {
+ _isPool = isPool;
+ }
+ }
+}
\ No newline at end of file
diff --git a/FantasyNetTest/NBC/Core/DataStructure/Collection/OneToManyHashSetPool.cs b/FantasyNetTest/NBC/Core/DataStructure/Collection/OneToManyHashSetPool.cs
new file mode 100644
index 0000000..e0a223d
--- /dev/null
+++ b/FantasyNetTest/NBC/Core/DataStructure/Collection/OneToManyHashSetPool.cs
@@ -0,0 +1,208 @@
+using System;
+using System.Collections.Generic;
+using NBC.Pool;
+
+#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
+
+namespace NBC.DataStructure.Collection
+{
+ ///
+ /// 一对多哈希集合(OneToManyHashSet)对象池。
+ ///
+ /// 键的类型。
+ /// 值的类型。
+ public class OneToManyHashSetPool : OneToManyHashSet, IDisposable, IPool where TKey : notnull
+ {
+ private bool _isPool;
+ private bool _isDispose;
+
+ ///
+ /// 创建一个 一对多哈希集合(OneToManyHashSet)对象池的实例。
+ ///
+ /// 创建的实例。
+ public static OneToManyHashSetPool Create()
+ {
+#if FANTASY_WEBGL
+ var a = Pool>.Rent();
+#else
+ var a = MultiThreadPool.Rent>();
+#endif
+ a._isDispose = false;
+ a._isPool = true;
+ return a;
+ }
+
+ ///
+ /// 释放实例所占用的资源,并将实例返回到对象池中,以便重用。
+ ///
+ public void Dispose()
+ {
+ if (_isDispose)
+ {
+ return;
+ }
+
+ _isDispose = true;
+ Clear();
+#if FANTASY_WEBGL
+ Pool>.Return(this);
+#else
+ MultiThreadPool.Return(this);
+#endif
+ }
+
+ ///
+ /// 获取一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public bool IsPool()
+ {
+ return _isPool;
+ }
+
+ ///
+ /// 设置一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public void SetIsPool(bool isPool)
+ {
+ _isPool = isPool;
+ }
+ }
+
+ ///
+ /// 一对多哈希集合(OneToManyHashSet),用于创建和管理键对应多个值的集合。
+ ///
+ /// 键的类型。
+ /// 值的类型。
+ public class OneToManyHashSet : Dictionary> where TKey : notnull
+ {
+ /// 用于回收和重用的空闲值集合队列。
+ private readonly Queue> _queue = new Queue>();
+ /// 设置最大回收限制,用于控制值集合的最大数量。
+ private readonly int _recyclingLimit = 120;
+ /// 一个空的、不包含任何元素的哈希集合,用于在查找失败时返回。
+ private static HashSet _empty = new HashSet();
+
+ ///
+ /// 初始化 类的新实例。
+ ///
+ public OneToManyHashSet() { }
+
+ ///
+ /// 设置最大缓存数量
+ ///
+ ///
+ /// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
+ /// 2:设置成0不控制数量,全部缓存
+ ///
+ public OneToManyHashSet(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);
+ Add(key, list);
+
+ return;
+ }
+
+ list.Add(value);
+ }
+
+ ///
+ /// 从集合中移除指定键对应的值。
+ ///
+ /// 键。
+ /// 要移除的值。
+ 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 (!TryGetValue(key, out var list)) return;
+
+ Remove(key);
+ Recycle(list);
+ }
+
+ ///
+ /// 获取指定键对应的值集合,如果不存在则返回一个空的哈希集合。
+ ///
+ /// 键。
+ /// 对应的值集合或空的哈希集合。
+ public HashSet GetValue(TKey key)
+ {
+ if (TryGetValue(key, out HashSet value))
+ {
+ return value;
+ }
+
+ return _empty;
+ }
+
+ ///
+ /// 从队列中获取一个空闲的值集合,或者创建一个新的。
+ ///
+ /// 值集合。
+ private HashSet Fetch()
+ {
+ return _queue.Count <= 0 ? new HashSet() : _queue.Dequeue();
+ }
+
+ ///
+ /// 回收值集合到队列中,以便重复利用。
+ ///
+ /// 要回收的值集合。
+ private void Recycle(HashSet list)
+ {
+ list.Clear();
+
+ if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return;
+
+ _queue.Enqueue(list);
+ }
+
+ ///
+ /// 清空集合中的数据并和队列。
+ ///
+ protected new void Clear()
+ {
+ base.Clear();
+ _queue.Clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/FantasyNetTest/NBC/Core/DataStructure/Collection/OneToManyListPool.cs b/FantasyNetTest/NBC/Core/DataStructure/Collection/OneToManyListPool.cs
new file mode 100644
index 0000000..1f7d469
--- /dev/null
+++ b/FantasyNetTest/NBC/Core/DataStructure/Collection/OneToManyListPool.cs
@@ -0,0 +1,253 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using NBC.Pool;
+
+#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
+#pragma warning disable CS8603 // Possible null reference return.
+
+namespace NBC.DataStructure.Collection
+{
+ ///
+ /// 可回收的、一对多关系的列表池。
+ ///
+ /// 键的类型。
+ /// 值的类型。
+ public class OneToManyListPool : OneToManyList, IDisposable, IPool where TKey : notnull
+ {
+ private bool _isPool;
+ private bool _isDispose;
+
+ ///
+ /// 创建一个 一对多关系的列表池的实例。
+ ///
+ /// 创建的实例。
+ public static OneToManyListPool Create()
+ {
+#if FANTASY_WEBGL || FANTASY_EXPORTER
+ var list = Pool>.Rent();
+#else
+ var list = MultiThreadPool.Rent>();
+#endif
+ list._isDispose = false;
+ list._isPool = true;
+ return list;
+ }
+
+ ///
+ /// 释放当前对象所占用的资源,并将对象回收到对象池中。
+ ///
+ public void Dispose()
+ {
+ if (_isDispose)
+ {
+ return;
+ }
+
+ _isDispose = true;
+ Clear();
+#if FANTASY_WEBGL || FANTASY_EXPORTER
+ Pool>.Return(this);
+#else
+ MultiThreadPool.Return(this);
+#endif
+ }
+
+ ///
+ /// 获取一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public bool IsPool()
+ {
+ return _isPool;
+ }
+
+ ///
+ /// 设置一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public void SetIsPool(bool isPool)
+ {
+ _isPool = isPool;
+ }
+ }
+
+ ///
+ /// 一对多关系的列表字典。
+ ///
+ /// 键的类型。
+ /// 值的类型。
+ public class OneToManyList : Dictionary> where TKey : notnull
+ {
+ private readonly int _recyclingLimit = 120;
+ private static readonly List Empty = new List();
+ private readonly Queue> _queue = new Queue>();
+
+ ///
+ /// 初始化一个新的 实例。
+ ///
+ public OneToManyList() { }
+
+ ///
+ /// 对整个字典的所有 List 进行排序
+ ///
+ public void SortAll()
+ {
+ foreach (var key in Keys.ToList()) // 使用 ToList() 避免修改集合异常
+ {
+ this[key] = this[key].OrderByDescending(GetSortValue).ToList();
+ }
+ }
+
+ // 获取对象的排序值(SortAttribute 的值,默认 0)
+ private int GetSortValue(TValue obj)
+ {
+ if (obj == null) return 0;
+
+ var sortAttr = obj.GetType().GetCustomAttribute();
+ return sortAttr?.Sort ?? 0;
+ }
+
+ ///
+ /// 设置最大缓存数量
+ ///
+ ///
+ /// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
+ /// 2:设置成0不控制数量,全部缓存
+ ///
+ public OneToManyList(int recyclingLimit)
+ {
+ _recyclingLimit = recyclingLimit;
+ }
+
+ ///
+ /// 判断给定的键和值是否存在于列表中。
+ ///
+ /// 要搜索的键。
+ /// 要搜索的值。
+ /// 如果存在则为 ,否则为 。
+ 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);
+ Add(key, list);
+
+ return;
+ }
+
+ list.Add(value);
+ }
+
+ ///
+ /// 获取指定键对应的列表中的第一个值。
+ ///
+ /// 要获取值的键。
+ /// 键对应的列表中的第一个值。
+ public TValue First(TKey key)
+ {
+ return !TryGetValue(key, out var list) ? default : list.FirstOrDefault();
+ }
+
+ ///
+ /// 从列表中移除指定键和值。
+ ///
+ /// 要移除值的键。
+ /// 要移除的值。
+ /// 如果成功移除则为 ,否则为 。
+ public bool RemoveValue(TKey key, TValue value)
+ {
+ if (!TryGetValue(key, out var list))
+ {
+ return true;
+ }
+
+ var isRemove = list.Remove(value);
+
+ if (list.Count == 0)
+ {
+ isRemove = RemoveByKey(key);
+ }
+
+ return isRemove;
+ }
+
+ ///
+ /// 从列表中移除指定键及其关联的所有值。
+ ///
+ /// 要移除的键。
+ /// 如果成功移除则为 ,否则为 。
+ public bool RemoveByKey(TKey key)
+ {
+ if (!TryGetValue(key, out var list))
+ {
+ return false;
+ }
+
+ Remove(key);
+ Recycle(list);
+ return true;
+ }
+
+ ///
+ /// 获取指定键关联的所有值的列表。
+ ///
+ /// 要获取值的键。
+ /// 键关联的所有值的列表。
+ public List GetValues(TKey key)
+ {
+ if (TryGetValue(key, out List list))
+ {
+ return list;
+ }
+
+ return Empty;
+ }
+
+ ///
+ /// 清除字典中的所有键值对,并回收相关的值集合。
+ ///
+ public new void Clear()
+ {
+ foreach (var keyValuePair in this) Recycle(keyValuePair.Value);
+
+ base.Clear();
+ }
+
+ ///
+ /// 从空闲值集合队列中获取一个值集合,如果队列为空则创建一个新的值集合。
+ ///
+ /// 从队列中获取的值集合。
+ private List Fetch()
+ {
+ return _queue.Count <= 0 ? new List() : _queue.Dequeue();
+ }
+
+ ///
+ /// 回收一个不再使用的值集合到空闲值集合队列中。
+ ///
+ /// 要回收的值集合。
+ private void Recycle(List list)
+ {
+ list.Clear();
+
+ if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return;
+
+ _queue.Enqueue(list);
+ }
+ }
+}
\ No newline at end of file
diff --git a/FantasyNetTest/NBC/Core/DataStructure/Collection/OneToManyQueuePool.cs b/FantasyNetTest/NBC/Core/DataStructure/Collection/OneToManyQueuePool.cs
new file mode 100644
index 0000000..e787ba8
--- /dev/null
+++ b/FantasyNetTest/NBC/Core/DataStructure/Collection/OneToManyQueuePool.cs
@@ -0,0 +1,204 @@
+using System;
+using System.Collections.Generic;
+using NBC.Pool;
+
+#pragma warning disable CS8603
+
+namespace NBC.DataStructure.Collection
+{
+ ///
+ /// 支持一对多关系的队列池,用于存储具有相同键的值的队列集合。
+ ///
+ /// 键的类型。
+ /// 值的类型。
+ public class OneToManyQueuePool : OneToManyQueue, IDisposable, IPool where TKey : notnull
+ {
+ private bool _isPool;
+ private bool _isDispose;
+
+ ///
+ /// 创建一个 一对多关系的队列池的实例。
+ ///
+ /// 创建的实例。
+ public static OneToManyQueuePool Create()
+ {
+#if FANTASY_WEBGL
+ var a = Pool>.Rent();
+#else
+ var a = MultiThreadPool.Rent>();
+#endif
+ a._isDispose = false;
+ a._isPool = true;
+ return a;
+ }
+
+ ///
+ /// 释放当前实例所占用的资源,并将实例回收到对象池中。
+ ///
+ public void Dispose()
+ {
+ if (_isDispose)
+ {
+ return;
+ }
+
+ _isDispose = true;
+ Clear();
+#if FANTASY_WEBGL
+ Pool>.Return(this);
+#else
+ MultiThreadPool.Return(this);
+#endif
+ }
+
+ ///
+ /// 获取一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public bool IsPool()
+ {
+ return _isPool;
+ }
+
+ ///
+ /// 设置一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public void SetIsPool(bool isPool)
+ {
+ _isPool = isPool;
+ }
+ }
+
+ ///
+ /// 支持一对多关系的队列,用于存储具有相同键的值的队列集合。
+ ///
+ /// 键的类型。
+ /// 值的类型。
+ public class OneToManyQueue : Dictionary> where TKey : notnull
+ {
+ private readonly Queue> _queue = new Queue>();
+ private readonly int _recyclingLimit;
+
+ ///
+ /// 创建一个 一对多关系的队列的实例。设置最大缓存数量
+ ///
+ ///
+ /// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
+ /// 2:设置成0不控制数量,全部缓存
+ ///
+ public OneToManyQueue(int recyclingLimit = 0)
+ {
+ _recyclingLimit = recyclingLimit;
+ }
+
+ ///
+ /// 判断指定键的值队列是否包含指定的值。
+ ///
+ /// 要查找的键。
+ /// 要查找的值。
+ /// 如果存在,则为 true;否则为 false。
+ public bool Contains(TKey key, TValue value)
+ {
+ TryGetValue(key, out var list);
+
+ return list != null && list.Contains(value);
+ }
+
+ ///
+ /// 将指定的值添加到指定键的值队列中。
+ ///
+ /// 要添加值的键。
+ /// 要添加的值。
+ public void Enqueue(TKey key, TValue value)
+ {
+ if (!TryGetValue(key, out var list))
+ {
+ list = Fetch();
+ list.Enqueue(value);
+ Add(key, list);
+ return;
+ }
+
+ list.Enqueue(value);
+ }
+
+ ///
+ /// 从指定键的值队列中出队一个值。
+ ///
+ /// 要出队的键。
+ /// 出队的值。
+ public TValue Dequeue(TKey key)
+ {
+ if (!TryGetValue(key, out var list) || list.Count == 0)
+ {
+ return default;
+ }
+
+ var value = list.Dequeue();
+
+ if (list.Count == 0)
+ {
+ RemoveKey(key);
+ }
+
+ return value;
+ }
+
+ ///
+ /// 尝试从指定键的值队列中出队一个值。
+ ///
+ /// 要出队的键。
+ /// 出队的值。
+ /// 如果成功出队,则为 true;否则为 false。
+ public bool TryDequeue(TKey key, out TValue value)
+ {
+ value = Dequeue(key);
+
+ return value != null;
+ }
+
+ ///
+ /// 从字典中移除指定键及其对应的值队列。
+ ///
+ /// 要移除的键。
+ public void RemoveKey(TKey key)
+ {
+ if (!TryGetValue(key, out var list)) return;
+
+ Remove(key);
+ Recycle(list);
+ }
+
+ ///
+ /// 从队列池中获取一个值队列。如果队列池为空,则创建一个新的值队列。
+ ///
+ /// 获取的值队列。
+ private Queue Fetch()
+ {
+ return _queue.Count <= 0 ? new Queue() : _queue.Dequeue();
+ }
+
+ ///
+ /// 回收一个不再使用的值队列到队列池中,以便重用。
+ ///
+ /// 要回收的值队列。
+ private void Recycle(Queue list)
+ {
+ list.Clear();
+
+ if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return;
+
+ _queue.Enqueue(list);
+ }
+
+ ///
+ /// 清空当前实例的数据,同时回收所有值队列。
+ ///
+ protected new void Clear()
+ {
+ base.Clear();
+ _queue.Clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/FantasyNetTest/NBC/Core/DataStructure/Collection/ReuseList.cs b/FantasyNetTest/NBC/Core/DataStructure/Collection/ReuseList.cs
new file mode 100644
index 0000000..50be9bc
--- /dev/null
+++ b/FantasyNetTest/NBC/Core/DataStructure/Collection/ReuseList.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using NBC.Pool;
+
+namespace NBC.DataStructure.Collection
+{
+ ///
+ /// 可重用的列表,继承自 类。该类支持通过对象池重用列表实例,以减少对象分配和释放的开销。
+ ///
+ /// 列表中元素的类型。
+ public sealed class ReuseList : List, IDisposable, IPool
+ {
+ private bool _isPool;
+ private bool _isDispose;
+
+ ///
+ /// 创建一个 可重用的列表的实例。
+ ///
+ /// 创建的实例。
+ public static ReuseList Create()
+ {
+#if FANTASY_WEBGL
+ var list = Pool>.Rent();
+#else
+ var list = MultiThreadPool.Rent>();
+#endif
+ list._isDispose = false;
+ list._isPool = true;
+ return list;
+ }
+
+ ///
+ /// 释放该实例所占用的资源,并将实例返回到对象池中,以便重用。
+ ///
+ public void Dispose()
+ {
+ if (_isDispose)
+ {
+ return;
+ }
+
+ _isDispose = true;
+ Clear();
+#if FANTASY_WEBGL
+ Pool>.Return(this);
+#else
+ MultiThreadPool.Return(this);
+#endif
+ }
+
+ ///
+ /// 获取一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public bool IsPool()
+ {
+ return _isPool;
+ }
+
+ ///
+ /// 设置一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public void SetIsPool(bool isPool)
+ {
+ _isPool = isPool;
+ }
+ }
+}
\ No newline at end of file
diff --git a/FantasyNetTest/NBC/Core/DataStructure/Collection/SortedConcurrentOneToManyListPool.cs b/FantasyNetTest/NBC/Core/DataStructure/Collection/SortedConcurrentOneToManyListPool.cs
new file mode 100644
index 0000000..98198a9
--- /dev/null
+++ b/FantasyNetTest/NBC/Core/DataStructure/Collection/SortedConcurrentOneToManyListPool.cs
@@ -0,0 +1,226 @@
+#if !FANTASY_WEBGL
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NBC.Pool;
+
+#pragma warning disable CS8603
+
+namespace NBC.DataStructure.Collection
+{
+ ///
+ /// 基于排序字典和并发集合实现的一对多映射列表的对象池包装类,继承自 类,
+ /// 同时实现了 接口,以支持对象的重用和释放。
+ ///
+ /// 键的类型。
+ /// 值的类型。
+ public class SortedConcurrentOneToManyListPool : SortedConcurrentOneToManyList, IDisposable, IPool where TKey : notnull
+ {
+ private bool _isPool;
+ private bool _isDispose;
+
+ ///
+ /// 创建一个新的 实例,使用默认的参数设置。
+ ///
+ /// 新创建的 实例。
+ public static SortedConcurrentOneToManyListPool 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 SortedConcurrentOneToManyList : SortedDictionary> where TKey : notnull
+ {
+ /// 用于同步操作的锁对象,它确保在多线程环境下对数据的安全访问。
+ private readonly object _lockObject = new object();
+ /// 用于存储缓存的队列。
+ private readonly Queue> _queue = new Queue>();
+ /// 控制缓存回收的限制。当缓存的数量超过此限制时,旧的缓存将会被回收。
+ private readonly int _recyclingLimit;
+
+ ///
+ /// 初始化一个新的 类的实例,使用默认的参数设置。
+ ///
+ public SortedConcurrentOneToManyList()
+ {
+ }
+
+ ///
+ /// 初始化一个新的 类的实例,指定最大缓存数量。
+ ///
+ ///
+ /// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
+ /// 2:设置成0不控制数量,全部缓存
+ ///
+ public SortedConcurrentOneToManyList(int recyclingLimit = 0)
+ {
+ _recyclingLimit = recyclingLimit;
+ }
+
+ ///
+ /// 检查指定的键和值是否存在于映射列表中。
+ ///
+ /// 要检查的键。
+ /// 要检查的值。
+ /// 如果存在,则为 true;否则为 false。
+ public bool Contains(TKey key, TValue value)
+ {
+ lock (_lockObject)
+ {
+ TryGetValue(key, out var list);
+
+ return list != null && list.Contains(value);
+ }
+ }
+
+ ///
+ /// 将指定的值添加到与指定键关联的列表中。
+ ///
+ /// 要关联值的键。
+ /// 要添加到列表的值。
+ public void Add(TKey key, TValue value)
+ {
+ lock (_lockObject)
+ {
+ if (!TryGetValue(key, out var list))
+ {
+ list = Fetch();
+ list.Add(value);
+ base[key] = list;
+ return;
+ }
+
+ list.Add(value);
+ }
+ }
+
+ ///
+ /// 获取与指定键关联的列表中的第一个值。
+ /// 如果列表不存在或为空,则返回默认值。
+ ///
+ /// 要获取第一个值的键。
+ /// 第一个值,或默认值。
+ public TValue First(TKey key)
+ {
+ lock (_lockObject)
+ {
+ return !TryGetValue(key, out var list) ? default : list.FirstOrDefault();
+ }
+ }
+
+ ///
+ /// 从与指定键关联的列表中移除指定的值。
+ /// 如果列表不存在或值不存在于列表中,则不执行任何操作。
+ ///
+ /// 要移除值的键。
+ /// 要移除的值。
+ public void RemoveValue(TKey key, TValue value)
+ {
+ lock (_lockObject)
+ {
+ if (!TryGetValue(key, out var list)) return;
+
+ list.Remove(value);
+
+ if (list.Count == 0) RemoveKey(key);
+ }
+ }
+
+ ///
+ /// 从映射列表中移除指定的键及其关联的列表。
+ /// 如果键不存在于映射列表中,则不执行任何操作。
+ ///
+ /// 要移除的键。
+ public void RemoveKey(TKey key)
+ {
+ lock (_lockObject)
+ {
+ if (!TryGetValue(key, out var list)) return;
+
+ Remove(key);
+
+ Recycle(list);
+ }
+ }
+
+ ///
+ /// 从缓存中获取一个可重用的列表。如果缓存中不存在列表,则创建一个新的列表并返回。
+ ///
+ /// 可重用的列表。
+ private List Fetch()
+ {
+ lock (_lockObject)
+ {
+ return _queue.Count <= 0 ? new List() : _queue.Dequeue();
+ }
+ }
+
+ ///
+ /// 将不再使用的列表回收到缓存中,以便重复利用。如果缓存数量超过限制,则丢弃列表而不进行回收。
+ ///
+ /// 要回收的列表。
+ private void Recycle(List list)
+ {
+ lock (_lockObject)
+ {
+ list.Clear();
+
+ if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return;
+
+ _queue.Enqueue(list);
+ }
+ }
+
+ ///
+ /// 清空映射列表以及队列。
+ ///
+ protected new void Clear()
+ {
+ base.Clear();
+ _queue.Clear();
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/FantasyNetTest/NBC/Core/DataStructure/Collection/SortedOneToManyHashSetPool.cs b/FantasyNetTest/NBC/Core/DataStructure/Collection/SortedOneToManyHashSetPool.cs
new file mode 100644
index 0000000..0c49ca8
--- /dev/null
+++ b/FantasyNetTest/NBC/Core/DataStructure/Collection/SortedOneToManyHashSetPool.cs
@@ -0,0 +1,192 @@
+using System;
+using System.Collections.Generic;
+using NBC.Pool;
+
+namespace NBC.DataStructure.Collection
+{
+ ///
+ /// 基于排序字典实现的一对多关系的映射哈希集合的对象池包装类,将唯一键映射到多个值的哈希集合。
+ /// 同时实现了 接口,以支持对象的重用和释放。
+ ///
+ /// 字典中键的类型。
+ /// 哈希集合中值的类型。
+ public class SortedOneToManyHashSetPool : SortedOneToManyHashSet, IDisposable, IPool where TKey : notnull
+ {
+ private bool _isPool;
+ private bool _isDispose;
+
+ ///
+ /// 创建一个 实例。
+ ///
+ /// 新创建的实例。
+ public static SortedOneToManyHashSetPool Create()
+ {
+#if FANTASY_WEBGL
+ var a = Pool>.Rent();
+#else
+ var a = MultiThreadPool.Rent>();
+#endif
+ a._isDispose = false;
+ a._isPool = true;
+ return a;
+ }
+
+ ///
+ /// 释放当前对象池实例,将其返回到对象池以供重用。
+ ///
+ public void Dispose()
+ {
+ if (_isDispose)
+ {
+ return;
+ }
+
+ _isDispose = true;
+ Clear();
+#if FANTASY_WEBGL
+ Pool>.Return(this);
+#else
+ MultiThreadPool.Return(this);
+#endif
+ }
+
+ ///
+ /// 获取一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public bool IsPool()
+ {
+ return _isPool;
+ }
+
+ ///
+ /// 设置一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public void SetIsPool(bool isPool)
+ {
+ _isPool = isPool;
+ }
+ }
+
+ ///
+ /// 基于排序字典实现的一对多关系的映射哈希集合类,将唯一键映射到多个值的哈希集合。
+ /// 用于在多个值与一个键关联的情况下进行管理和存储。
+ ///
+ /// 字典中键的类型。
+ /// 集合中值的类型。
+ public class SortedOneToManyHashSet : SortedDictionary> where TKey : notnull
+ {
+ private readonly Queue> _queue = new Queue>();
+ private readonly int _recyclingLimit = 120;
+
+ ///
+ /// 创建一个新的 实例。
+ ///
+ public SortedOneToManyHashSet() { }
+
+ ///
+ /// 创建一个新的 实例,设置最大缓存数量
+ ///
+ ///
+ /// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
+ /// 2:设置成0不控制数量,全部缓存
+ ///
+ public SortedOneToManyHashSet(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);
+ Add(key, list);
+
+ return;
+ }
+
+ list.Add(value);
+ }
+
+ ///
+ /// 从指定键关联的哈希集合中移除特定值。
+ /// 如果哈希集合不存在或值不存在于集合中,则不执行任何操作。
+ ///
+ /// 要移除值的键。
+ /// 要移除的值。
+ 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 (!TryGetValue(key, out var list)) return;
+
+ Remove(key);
+
+ Recycle(list);
+ }
+
+ ///
+ /// 获取一个空的或回收的哈希集合。
+ ///
+ /// 获取的哈希集合实例。
+ private HashSet Fetch()
+ {
+ return _queue.Count <= 0 ? new HashSet() : _queue.Dequeue();
+ }
+
+ ///
+ /// 回收一个哈希集合,将其清空并放入回收队列中。
+ ///
+ /// 要回收的哈希集合。
+ private void Recycle(HashSet list)
+ {
+ list.Clear();
+
+ if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return;
+
+ _queue.Enqueue(list);
+ }
+
+ ///
+ /// 重写 Clear 方法,清空字典并清空回收队列。
+ ///
+ protected new void Clear()
+ {
+ base.Clear();
+ _queue.Clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/FantasyNetTest/NBC/Core/DataStructure/Collection/SortedOneToManyListPool.cs b/FantasyNetTest/NBC/Core/DataStructure/Collection/SortedOneToManyListPool.cs
new file mode 100644
index 0000000..dc349c9
--- /dev/null
+++ b/FantasyNetTest/NBC/Core/DataStructure/Collection/SortedOneToManyListPool.cs
@@ -0,0 +1,217 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NBC.Pool;
+
+#pragma warning disable CS8603
+
+namespace NBC.DataStructure.Collection
+{
+ ///
+ /// 基于排序字典实现的一对多映射列表的对象池包装类,继承自 类,
+ /// 同时实现了 接口,以支持对象的重用和释放。
+ ///
+ /// 字典中键的类型。
+ /// 列表中值的类型。
+ public class SortedOneToManyListPool : SortedOneToManyList, IDisposable, IPool where TKey : notnull
+ {
+ private bool _isPool;
+ private bool _isDispose;
+
+ ///
+ /// 创建一个 实例。
+ ///
+ /// 新创建的实例。
+ public static SortedOneToManyListPool Create()
+ {
+#if FANTASY_WEBGL
+ var a = Pool>.Rent();
+#else
+ var a = MultiThreadPool.Rent>();
+#endif
+ a._isDispose = false;
+ a._isPool = true;
+ return a;
+ }
+
+ ///
+ /// 释放当前对象池实例,将其返回到对象池以供重用。
+ ///
+ public void Dispose()
+ {
+ if (_isDispose)
+ {
+ return;
+ }
+
+ _isDispose = true;
+ Clear();
+#if FANTASY_WEBGL
+ Pool>.Return(this);
+#else
+ MultiThreadPool.Return(this);
+#endif
+ }
+
+ ///
+ /// 获取一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public bool IsPool()
+ {
+ return _isPool;
+ }
+
+ ///
+ /// 设置一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public void SetIsPool(bool isPool)
+ {
+ _isPool = isPool;
+ }
+ }
+
+ ///
+ /// 基于排序字典实现的一对多关系的映射列表类,将唯一键映射到包含多个值的列表。
+ /// 用于在多个值与一个键关联的情况下进行管理和存储。
+ ///
+ /// 字典中键的类型。
+ /// 列表中值的类型。
+ public class SortedOneToManyList : SortedDictionary> where TKey : notnull
+ {
+ private readonly Queue> _queue = new Queue>();
+ private readonly int _recyclingLimit;
+
+ ///
+ /// 创建一个新的 实例。
+ ///
+ public SortedOneToManyList()
+ {
+ }
+
+ ///
+ /// 创建一个新的 实例,设置最大缓存数量
+ ///
+ ///
+ /// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
+ /// 2:设置成0不控制数量,全部缓存
+ ///
+ public SortedOneToManyList(int recyclingLimit = 0)
+ {
+ _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 (!TryGetValue(key, out var list))
+ {
+ return;
+ }
+
+ Remove(key);
+ Recycle(list);
+ }
+
+ ///
+ /// 获取一个空的或回收的列表。
+ ///
+ /// 获取的列表实例。
+ private List Fetch()
+ {
+ return _queue.Count <= 0 ? new List() : _queue.Dequeue();
+ }
+
+ ///
+ /// 回收一个列表,将其清空并放入回收队列中。如果缓存数量超过限制,则丢弃列表而不进行回收
+ ///
+ /// 要回收的列表。
+ private void Recycle(List list)
+ {
+ list.Clear();
+
+ if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit)
+ {
+ return;
+ }
+
+ _queue.Enqueue(list);
+ }
+
+ ///
+ /// 重写 Clear 方法,清空字典并清空回收队列。
+ ///
+ protected new void Clear()
+ {
+ base.Clear();
+ _queue.Clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/FantasyNetTest/NBC/Core/DataStructure/Dictionary/DictionaryExtensions.cs b/FantasyNetTest/NBC/Core/DataStructure/Dictionary/DictionaryExtensions.cs
new file mode 100644
index 0000000..609c411
--- /dev/null
+++ b/FantasyNetTest/NBC/Core/DataStructure/Dictionary/DictionaryExtensions.cs
@@ -0,0 +1,31 @@
+using System.Collections.Generic;
+#pragma warning disable CS8601 // Possible null reference assignment.
+
+namespace NBC.DataStructure.Dictionary
+{
+ ///
+ /// 提供对字典的扩展方法。
+ ///
+ public static class DictionaryExtensions
+ {
+ ///
+ /// 尝试从字典中移除指定键,并返回相应的值。
+ ///
+ /// 字典中键的类型。
+ /// 字典中值的类型。
+ /// 要操作的字典实例。
+ /// 要移除的键。
+ /// 从字典中移除的值(如果成功移除)。
+ /// 如果成功移除键值对,则为 true;否则为 false。
+ public static bool TryRemove(this IDictionary self, T key, out TV value)
+ {
+ if (!self.TryGetValue(key, out value))
+ {
+ return false;
+ }
+
+ self.Remove(key);
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/FantasyNetTest/NBC/Core/DataStructure/Dictionary/DictionaryPool.cs b/FantasyNetTest/NBC/Core/DataStructure/Dictionary/DictionaryPool.cs
new file mode 100644
index 0000000..a65c8f1
--- /dev/null
+++ b/FantasyNetTest/NBC/Core/DataStructure/Dictionary/DictionaryPool.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using NBC.Pool;
+
+namespace NBC.DataStructure.Dictionary
+{
+ ///
+ /// 提供一个可以使用对象池管理的字典类。
+ ///
+ /// 字典中键的类型。
+ /// 字典中值的类型。
+ public sealed class DictionaryPool : Dictionary, IDisposable, IPool where TM : notnull
+ {
+ private bool _isPool;
+ private bool _isDispose;
+
+ ///
+ /// 释放实例占用的资源。
+ ///
+ public void Dispose()
+ {
+ if (_isDispose)
+ {
+ return;
+ }
+
+ _isDispose = true;
+ Clear();
+#if FANTASY_WEBGL
+ Pool>.Return(this);
+#else
+ MultiThreadPool.Return(this);
+#endif
+ }
+
+ ///
+ /// 创建一个新的 实例。
+ ///
+ /// 新创建的实例。
+ public static DictionaryPool Create()
+ {
+#if FANTASY_WEBGL
+ var dictionary = Pool>.Rent();
+#else
+ var dictionary = MultiThreadPool.Rent>();
+#endif
+ dictionary._isDispose = false;
+ dictionary._isPool = true;
+ return dictionary;
+ }
+
+ ///
+ /// 获取一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public bool IsPool()
+ {
+ return _isPool;
+ }
+
+ ///
+ /// 设置一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public void SetIsPool(bool isPool)
+ {
+ _isPool = isPool;
+ }
+ }
+}
\ No newline at end of file
diff --git a/FantasyNetTest/NBC/Core/DataStructure/Dictionary/DoubleMapDictionaryPool.cs b/FantasyNetTest/NBC/Core/DataStructure/Dictionary/DoubleMapDictionaryPool.cs
new file mode 100644
index 0000000..d786881
--- /dev/null
+++ b/FantasyNetTest/NBC/Core/DataStructure/Dictionary/DoubleMapDictionaryPool.cs
@@ -0,0 +1,289 @@
+using System;
+using System.Collections.Generic;
+using NBC.Pool;
+
+#pragma warning disable CS8601 // Possible null reference assignment.
+#pragma warning disable CS8604 // Possible null reference argument.
+#pragma warning disable CS8603 // Possible null reference return.
+
+namespace NBC.DataStructure.Dictionary
+{
+ ///
+ /// 提供一个双向映射字典对象池类,用于双向键值对映射。
+ ///
+ /// 字典中键的类型。
+ /// 字典中值的类型。
+ public class DoubleMapDictionaryPool : DoubleMapDictionary, IDisposable, IPool where TKey : notnull where TValue : notnull
+ {
+ private bool _isPool;
+ private bool _isDispose;
+
+ ///
+ /// 创建一个新的 实例。
+ ///
+ /// 新创建的实例。
+ public static DoubleMapDictionaryPool Create()
+ {
+#if FANTASY_WEBGL
+ var a = Pool>.Rent();
+#else
+ var a = MultiThreadPool.Rent>();
+#endif
+ a._isDispose = false;
+ a._isPool = true;
+ return a;
+ }
+
+ ///
+ /// 释放实例占用的资源。
+ ///
+ public void Dispose()
+ {
+ if (_isDispose)
+ {
+ return;
+ }
+
+ _isDispose = true;
+ Clear();
+#if FANTASY_WEBGL
+ Pool>.Return(this);
+#else
+ MultiThreadPool.Return(this);
+#endif
+ }
+
+ ///
+ /// 获取一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public bool IsPool()
+ {
+ return _isPool;
+ }
+
+ ///
+ /// 设置一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public void SetIsPool(bool isPool)
+ {
+ _isPool = isPool;
+ }
+ }
+
+ ///
+ /// 可以实现双向映射的字典类,用于将键和值进行双向映射。
+ ///
+ /// 键的类型,不能为 null。
+ /// 值的类型,不能为 null。
+ public class DoubleMapDictionary where TK : notnull where TV : notnull
+ {
+ private readonly Dictionary _kv = new Dictionary();
+ private readonly Dictionary _vk = new Dictionary();
+
+ ///
+ /// 创建一个新的空的 实例。
+ ///
+ public DoubleMapDictionary() { }
+
+ ///
+ /// 创建一个新的具有指定初始容量的 实例。
+ ///
+ /// 初始容量。
+ public DoubleMapDictionary(int capacity)
+ {
+ _kv = new Dictionary(capacity);
+ _vk = new Dictionary(capacity);
+ }
+
+ ///
+ /// 获取包含字典中所有键的列表。
+ ///
+ public List Keys => new List(_kv.Keys);
+
+ ///
+ /// 获取包含字典中所有值的列表。
+ ///
+ public List Values => new List(_vk.Keys);
+
+ ///
+ /// 对字典中的每个键值对执行指定的操作。
+ ///
+ /// 要执行的操作。
+ public void ForEach(Action action)
+ {
+ if (action == null)
+ {
+ return;
+ }
+
+ var keys = _kv.Keys;
+ foreach (var key in keys)
+ {
+ action(key, _kv[key]);
+ }
+ }
+
+ ///
+ /// 将指定的键值对添加到字典中。
+ ///
+ /// 要添加的键。
+ /// 要添加的值。
+ public void Add(TK key, TV value)
+ {
+ if (key == null || value == null || _kv.ContainsKey(key) || _vk.ContainsKey(value))
+ {
+ return;
+ }
+
+ _kv.Add(key, value);
+ _vk.Add(value, key);
+ }
+
+ ///
+ /// 根据指定的键获取相应的值。
+ ///
+ /// 要查找值的键。
+ /// 与指定键关联的值,如果找不到键,则返回默认值。
+ public TV GetValueByKey(TK key)
+ {
+ if (key != null && _kv.ContainsKey(key))
+ {
+ return _kv[key];
+ }
+
+ return default;
+ }
+
+ ///
+ /// 尝试根据指定的键获取相应的值。
+ ///
+ /// 要查找值的键。
+ /// 如果找到,则为与指定键关联的值;否则为值的默认值。
+ /// 如果找到键,则为 true;否则为 false。
+ public bool TryGetValueByKey(TK key, out TV value)
+ {
+ var result = key != null && _kv.ContainsKey(key);
+
+ value = result ? _kv[key] : default;
+
+ return result;
+ }
+
+ ///
+ /// 根据指定的值获取相应的键。
+ ///
+ /// 要查找键的值。
+ /// 与指定值关联的键,如果找不到值,则返回默认键。
+ public TK GetKeyByValue(TV value)
+ {
+ if (value != null && _vk.ContainsKey(value))
+ {
+ return _vk[value];
+ }
+
+ return default;
+ }
+
+ ///
+ /// 尝试根据指定的值获取相应的键。
+ ///
+ /// 要查找键的值。
+ /// 如果找到,则为与指定值关联的键;否则为键的默认值。
+ /// 如果找到值,则为 true;否则为 false。
+ public bool TryGetKeyByValue(TV value, out TK key)
+ {
+ var result = value != null && _vk.ContainsKey(value);
+
+ key = result ? _vk[value] : default;
+
+ return result;
+ }
+
+ ///
+ /// 根据指定的键移除键值对。
+ ///
+ /// 要移除的键。
+ public void RemoveByKey(TK key)
+ {
+ if (key == null)
+ {
+ return;
+ }
+
+ if (!_kv.TryGetValue(key, out var value))
+ {
+ return;
+ }
+
+ _kv.Remove(key);
+ _vk.Remove(value);
+ }
+
+ ///
+ /// 根据指定的值移除键值对。
+ ///
+ /// 要移除的值。
+ public void RemoveByValue(TV value)
+ {
+ if (value == null)
+ {
+ return;
+ }
+
+ if (!_vk.TryGetValue(value, out var key))
+ {
+ return;
+ }
+
+ _kv.Remove(key);
+ _vk.Remove(value);
+ }
+
+ ///
+ /// 清空字典中的所有键值对。
+ ///
+ public void Clear()
+ {
+ _kv.Clear();
+ _vk.Clear();
+ }
+
+ ///
+ /// 判断字典是否包含指定的键。
+ ///
+ /// 要检查的键。
+ /// 如果字典包含指定的键,则为 true;否则为 false。
+ public bool ContainsKey(TK key)
+ {
+ return key != null && _kv.ContainsKey(key);
+ }
+
+ ///
+ /// 判断字典是否包含指定的值。
+ ///
+ /// 要检查的值。
+ /// 如果字典包含指定的值,则为 true;否则为 false。
+ public bool ContainsValue(TV value)
+ {
+ return value != null && _vk.ContainsKey(value);
+ }
+
+ ///
+ /// 判断字典是否包含指定的键值对。
+ ///
+ /// 要检查的键。
+ /// 要检查的值。
+ /// 如果字典包含指定的键值对,则为 true;否则为 false。
+ public bool Contains(TK key, TV value)
+ {
+ if (key == null || value == null)
+ {
+ return false;
+ }
+
+ return _kv.ContainsKey(key) && _vk.ContainsKey(value);
+ }
+ }
+}
\ No newline at end of file
diff --git a/FantasyNetTest/NBC/Core/DataStructure/Dictionary/EntityDictionary.cs b/FantasyNetTest/NBC/Core/DataStructure/Dictionary/EntityDictionary.cs
new file mode 100644
index 0000000..4c4d25f
--- /dev/null
+++ b/FantasyNetTest/NBC/Core/DataStructure/Dictionary/EntityDictionary.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Collections.Generic;
+using NBC.Pool;
+
+namespace NBC.DataStructure.Dictionary
+{
+ ///
+ /// 提供一个带资源释放功能的实体字典类,支持使用对象池管理。
+ ///
+ /// 字典中键的类型。
+ /// 字典中值的类型,必须实现 IDisposable 接口。
+ public sealed class EntityDictionary : Dictionary, IDisposable, IPool where TN : IDisposable where TM : notnull
+ {
+ private bool _isPool;
+ private bool _isDispose;
+
+ ///
+ /// 创建一个新的 实例。
+ ///
+ /// 新创建的实例。
+ public static EntityDictionary Create()
+ {
+#if FANTASY_WEBGL
+ var entityDictionary = Pool>.Rent();
+#else
+ var entityDictionary = MultiThreadPool.Rent>();
+#endif
+ entityDictionary._isDispose = false;
+ entityDictionary._isPool = true;
+ return entityDictionary;
+ }
+
+ ///
+ /// 清空字典中的所有键值对,并释放值的资源。
+ ///
+ public new void Clear()
+ {
+ foreach (var keyValuePair in this)
+ {
+ keyValuePair.Value.Dispose();
+ }
+
+ base.Clear();
+ }
+
+ ///
+ /// 清空字典中的所有键值对,但不释放值的资源。
+ ///
+ public void ClearNotDispose()
+ {
+ base.Clear();
+ }
+
+ ///
+ /// 释放实例占用的资源。
+ ///
+ public void Dispose()
+ {
+ if (_isDispose)
+ {
+ return;
+ }
+
+ _isDispose = true;
+ Clear();
+#if FANTASY_WEBGL
+ Pool>.Return(this);
+#else
+ MultiThreadPool.Return(this);
+#endif
+ }
+
+ ///
+ /// 获取一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public bool IsPool()
+ {
+ return _isPool;
+ }
+
+ ///
+ /// 设置一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public void SetIsPool(bool isPool)
+ {
+ _isPool = isPool;
+ }
+ }
+}
\ No newline at end of file
diff --git a/FantasyNetTest/NBC/Core/DataStructure/Dictionary/OneToManyDictionaryPool.cs b/FantasyNetTest/NBC/Core/DataStructure/Dictionary/OneToManyDictionaryPool.cs
new file mode 100644
index 0000000..3d47561
--- /dev/null
+++ b/FantasyNetTest/NBC/Core/DataStructure/Dictionary/OneToManyDictionaryPool.cs
@@ -0,0 +1,247 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NBC.Pool;
+
+#pragma warning disable CS8603
+#pragma warning disable CS8601
+
+namespace NBC.DataStructure.Dictionary
+{
+ ///
+ /// 一对多映射关系的字典对象池。
+ ///
+ /// 外部字典中的键类型。
+ /// 内部字典中的键类型。
+ /// 内部字典中的值类型。
+ public class OneToManyDictionaryPool : OneToManyDictionary, IDisposable, IPool where TKey : notnull where TValueKey : notnull
+ {
+ private bool _isPool;
+ private bool _isDispose;
+
+ ///
+ /// 创建一个 的实例。
+ ///
+ /// 新创建的 OneToManyDictionaryPool 实例。
+ public static OneToManyDictionaryPool Create()
+ {
+#if FANTASY_WEBGL
+ var a = Pool>.Rent();
+#else
+ var a = MultiThreadPool.Rent>();
+#endif
+ a._isDispose = false;
+ a._isPool = true;
+ return a;
+ }
+
+ ///
+ /// 释放当前实例及其资源。
+ ///
+ public void Dispose()
+ {
+ if (_isDispose)
+ {
+ return;
+ }
+
+ _isDispose = true;
+ Clear();
+#if FANTASY_WEBGL
+ Pool>.Return(this);
+#else
+ MultiThreadPool.Return(this);
+#endif
+ }
+
+ ///
+ /// 获取一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public bool IsPool()
+ {
+ return _isPool;
+ }
+
+ ///
+ /// 设置一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public void SetIsPool(bool isPool)
+ {
+ _isPool = isPool;
+ }
+ }
+
+ ///
+ /// 一对多映射关系的字典。每个键都对应一个内部字典,该内部字典将键值映射到相应的值。
+ ///
+ /// 外部字典中的键类型。
+ /// 内部字典中的键类型。
+ /// 内部字典中的值类型。
+ public class OneToManyDictionary : Dictionary>
+ where TKey : notnull where TValueKey : notnull
+ {
+ private readonly Queue> _queue = new Queue>();
+ private readonly int _recyclingLimit = 120;
+
+ ///
+ /// 创建一个新的 实例。
+ ///
+ public OneToManyDictionary() { }
+
+ ///
+ /// 创建一个新的 实例,并指定最大缓存数量。
+ ///
+ ///
+ /// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
+ /// 2:设置成0不控制数量,全部缓存
+ ///
+ public OneToManyDictionary(int recyclingLimit = 0)
+ {
+ _recyclingLimit = recyclingLimit;
+ }
+
+ ///
+ /// 检查是否包含指定的键值对。
+ ///
+ /// 外部字典中的键。
+ /// 内部字典中的键。
+ /// 如果包含指定的键值对,则为 true;否则为 false。
+ public bool Contains(TKey key, TValueKey valueKey)
+ {
+ TryGetValue(key, out var dic);
+
+ return dic != null && dic.ContainsKey(valueKey);
+ }
+
+ ///
+ /// 尝试获取指定键值对的值。
+ ///
+ /// 外部字典中的键。
+ /// 内部字典中的键。
+ /// 获取的值,如果操作成功,则为值;否则为默认值。
+ /// 如果操作成功,则为 true;否则为 false。
+ public bool TryGetValue(TKey key, TValueKey valueKey, out TValue value)
+ {
+ value = default;
+ return TryGetValue(key, out var dic) && dic.TryGetValue(valueKey, out value);
+ }
+
+ ///
+ /// 获取指定键的第一个值。
+ ///
+ /// 要获取第一个值的键。
+ public TValue First(TKey key)
+ {
+ return !TryGetValue(key, out var dic) ? default : dic.First().Value;
+ }
+
+ ///
+ /// 向字典中添加指定的键值对。
+ ///
+ /// 要添加键值对的键。
+ /// 要添加键值对的内部字典键。
+ /// 要添加的值。
+ public void Add(TKey key, TValueKey valueKey, TValue value)
+ {
+ if (!TryGetValue(key, out var dic))
+ {
+ dic = Fetch();
+ dic[valueKey] = value;
+ // dic.Add(valueKey, value);
+ Add(key, dic);
+
+ return;
+ }
+
+ dic[valueKey] = value;
+ // dic.Add(valueKey, value);
+ }
+
+ ///
+ /// 从字典中移除指定的键值对。
+ ///
+ /// 要移除键值对的键。
+ /// 要移除键值对的内部字典键。
+ /// 如果成功移除键值对,则为 true;否则为 false。
+ public bool Remove(TKey key, TValueKey valueKey)
+ {
+ if (!TryGetValue(key, out var dic)) return false;
+
+ var result = dic.Remove(valueKey);
+
+ if (dic.Count == 0) RemoveKey(key);
+
+ return result;
+ }
+
+ ///
+ /// 从字典中移除指定的键值对。
+ ///
+ /// 要移除键值对的键。
+ /// 要移除键值对的内部字典键。
+ /// 如果成功移除键值对,则为移除的值;否则为默认值。
+ /// 如果成功移除键值对,则为 true;否则为 false。
+ public bool Remove(TKey key, TValueKey valueKey, out TValue value)
+ {
+ if (!TryGetValue(key, out var dic))
+ {
+ value = default;
+ return false;
+ }
+
+ var result = dic.TryGetValue(valueKey, out value);
+
+ if (result) dic.Remove(valueKey);
+
+ if (dic.Count == 0) RemoveKey(key);
+
+ return result;
+ }
+
+ ///
+ /// 移除字典中的指定键及其相关的所有键值对。
+ ///
+ /// 要移除的键。
+ public void RemoveKey(TKey key)
+ {
+ if (!TryGetValue(key, out var dic)) return;
+
+ Remove(key);
+ Recycle(dic);
+ }
+
+ ///
+ /// 从对象池中获取一个内部字典实例,如果池中没有,则创建一个新实例。
+ ///
+ /// 获取的内部字典实例。
+ private Dictionary Fetch()
+ {
+ return _queue.Count <= 0 ? new Dictionary() : _queue.Dequeue();
+ }
+
+ ///
+ /// 将不再使用的内部字典实例放回对象池中,以便后续重用。
+ ///
+ /// 要放回对象池的内部字典实例。
+ private void Recycle(Dictionary dic)
+ {
+ dic.Clear();
+
+ if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return;
+
+ _queue.Enqueue(dic);
+ }
+
+ ///
+ /// 清空字典中的所有键值对,并将不再使用的内部字典实例放回对象池中。
+ ///
+ public new void Clear()
+ {
+ foreach (var keyValuePair in this) Recycle(keyValuePair.Value);
+
+ base.Clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/FantasyNetTest/NBC/Core/DataStructure/Dictionary/OneToManySortedDictionaryPool.cs b/FantasyNetTest/NBC/Core/DataStructure/Dictionary/OneToManySortedDictionaryPool.cs
new file mode 100644
index 0000000..c6208a7
--- /dev/null
+++ b/FantasyNetTest/NBC/Core/DataStructure/Dictionary/OneToManySortedDictionaryPool.cs
@@ -0,0 +1,250 @@
+using System;
+using System.Collections.Generic;
+using NBC.Pool;
+
+#pragma warning disable CS8601
+
+namespace NBC.DataStructure.Dictionary
+{
+ ///
+ /// 一对多映射关系的排序字典对象池。
+ ///
+ /// 外部字典中的键类型。
+ /// 内部字典中的排序键类型。
+ /// 内部字典中的值类型。
+ public class OneToManySortedDictionaryPool : OneToManySortedDictionary, IDisposable, IPool where TKey : notnull where TSortedKey : notnull
+ {
+ private bool _isPool;
+ private bool _isDispose;
+
+ ///
+ /// 创建一个 的实例。
+ ///
+ /// 新创建的 OneToManySortedDictionaryPool 实例。
+ public static OneToManySortedDictionaryPool Create()
+ {
+#if FANTASY_WEBGL
+ var a = Pool>.Rent();
+#else
+ var a = MultiThreadPool.Rent>();
+#endif
+ a._isDispose = false;
+ a._isPool = true;
+ return a;
+ }
+
+ ///
+ /// 释放当前实例及其资源。
+ ///
+ public void Dispose()
+ {
+ if (_isDispose)
+ {
+ return;
+ }
+
+ _isDispose = true;
+ Clear();
+#if FANTASY_WEBGL
+ Pool>.Return(this);
+#else
+ MultiThreadPool.Return(this);
+#endif
+ }
+
+ ///
+ /// 获取一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public bool IsPool()
+ {
+ return _isPool;
+ }
+
+ ///
+ /// 设置一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public void SetIsPool(bool isPool)
+ {
+ _isPool = isPool;
+ }
+ }
+
+ ///
+ /// 一对多映射关系的排序字典。每个外部键映射到一个内部排序字典,该内部排序字典将排序键映射到相应的值。
+ ///
+ /// 外部字典中的键类型。
+ /// 内部字典中的排序键类型。
+ /// 内部字典中的值类型。
+ public class
+ OneToManySortedDictionary : Dictionary>
+ where TSortedKey : notnull where TKey : notnull
+ {
+ /// 缓存队列的回收限制
+ private readonly int _recyclingLimit = 120;
+ /// 缓存队列,用于存储已回收的内部排序字典
+ private readonly Queue> _queue = new Queue>();
+
+ ///
+ /// 创建一个新的 实例。
+ ///
+ protected OneToManySortedDictionary() { }
+
+ ///
+ /// 创建一个新的 实例。设置最大缓存数量
+ ///
+ ///
+ /// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
+ /// 2:设置成0不控制数量,全部缓存
+ ///
+ public OneToManySortedDictionary(int recyclingLimit)
+ {
+ _recyclingLimit = recyclingLimit;
+ }
+
+ ///
+ /// 检查字典是否包含指定的外部键。
+ ///
+ /// 要检查的外部键。
+ /// 如果字典包含指定的外部键,则为 true;否则为 false。
+ public bool Contains(TKey key)
+ {
+ return this.ContainsKey(key);
+ }
+
+ ///
+ /// 检查字典是否包含指定的外部键和排序键。
+ ///
+ /// 要检查的外部键。
+ /// 要检查的排序键。
+ /// 如果字典包含指定的外部键和排序键,则为 true;否则为 false。
+ public bool Contains(TKey key, TSortedKey sortedKey)
+ {
+ return TryGetValue(key, out var dic) && dic.ContainsKey(sortedKey);
+ }
+
+ ///
+ /// 尝试从字典中获取指定外部键对应的内部排序字典。
+ ///
+ /// 要获取内部排序字典的外部键。
+ /// 获取到的内部排序字典,如果找不到则为 null。
+ /// 如果找到内部排序字典,则为 true;否则为 false。
+ public new bool TryGetValue(TKey key, out SortedDictionary dic)
+ {
+ return base.TryGetValue(key, out dic);
+ }
+
+ ///
+ /// 尝试从字典中获取指定外部键和排序键对应的值。
+ ///
+ /// 要获取值的外部键。
+ /// 要获取值的排序键。
+ /// 获取到的值,如果找不到则为 default。
+ /// 如果找到值,则为 true;否则为 false。
+ public bool TryGetValueBySortedKey(TKey key, TSortedKey sortedKey, out TValue value)
+ {
+ if (base.TryGetValue(key, out var dic))
+ {
+ return dic.TryGetValue(sortedKey, out value);
+ }
+
+ value = default;
+ return false;
+ }
+
+ ///
+ /// 向字典中添加一个值,关联到指定的外部键和排序键。
+ ///
+ /// 要关联值的外部键。
+ /// 要关联值的排序键。
+ /// 要添加的值。
+ public void Add(TKey key, TSortedKey sortedKey, TValue value)
+ {
+ if (!TryGetValue(key, out var dic))
+ {
+ dic = Fetch();
+ dic.Add(sortedKey, value);
+ Add(key, dic);
+
+ return;
+ }
+
+ dic.Add(sortedKey, value);
+ }
+
+ ///
+ /// 从字典中移除指定外部键和排序键关联的值。
+ ///
+ /// 要移除值的外部键。
+ /// 要移除值的排序键。
+ /// 如果成功移除值,则为 true;否则为 false。
+ public bool RemoveSortedKey(TKey key, TSortedKey sortedKey)
+ {
+ if (!TryGetValue(key, out var dic))
+ {
+ return false;
+ }
+
+ var isRemove = dic.Remove(sortedKey);
+
+ if (dic.Count == 0)
+ {
+ isRemove = RemoveKey(key);
+ }
+
+ return isRemove;
+ }
+
+ ///
+ /// 从字典中移除指定外部键及其关联的所有值。
+ ///
+ /// 要移除的外部键。
+ /// 如果成功移除外部键及其关联的所有值,则为 true;否则为 false。
+ public bool RemoveKey(TKey key)
+ {
+ if (!TryGetValue(key, out var list))
+ {
+ return false;
+ }
+
+ Remove(key);
+ Recycle(list);
+ return true;
+ }
+
+ ///
+ /// 从缓存队列中获取一个内部排序字典。
+ ///
+ /// 一个内部排序字典。
+ private SortedDictionary Fetch()
+ {
+ return _queue.Count <= 0 ? new SortedDictionary() : _queue.Dequeue();
+ }
+
+ ///
+ /// 回收一个内部排序字典到缓存队列。
+ ///
+ /// 要回收的内部排序字典。
+ private void Recycle(SortedDictionary dic)
+ {
+ dic.Clear();
+
+ if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit)
+ {
+ return;
+ }
+
+ _queue.Enqueue(dic);
+ }
+
+ ///
+ /// 清空字典以及内部排序字典缓存队列,释放所有资源。
+ ///
+ protected new void Clear()
+ {
+ base.Clear();
+ _queue.Clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/FantasyNetTest/NBC/Core/DataStructure/Dictionary/ReuseDictionary.cs b/FantasyNetTest/NBC/Core/DataStructure/Dictionary/ReuseDictionary.cs
new file mode 100644
index 0000000..04fea5b
--- /dev/null
+++ b/FantasyNetTest/NBC/Core/DataStructure/Dictionary/ReuseDictionary.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using NBC.Pool;
+
+namespace NBC.DataStructure.Dictionary
+{
+ ///
+ /// 提供一个可以重用的字典类,支持使用对象池管理。
+ ///
+ /// 字典中键的类型。
+ /// 字典中值的类型。
+ public sealed class ReuseDictionary : Dictionary, IDisposable, IPool where TM : notnull
+ {
+ private bool _isPool;
+ private bool _isDispose;
+
+ ///
+ /// 创建一个新的 实例。
+ ///
+ /// 新创建的实例。
+ public static ReuseDictionary Create()
+ {
+#if FANTASY_WEBGL
+ var entityDictionary = Pool>.Rent();
+#else
+ var entityDictionary = MultiThreadPool.Rent>();
+#endif
+ entityDictionary._isDispose = false;
+ entityDictionary._isPool = true;
+ return entityDictionary;
+ }
+
+ ///
+ /// 释放实例占用的资源。
+ ///
+ public void Dispose()
+ {
+ if (_isDispose)
+ {
+ return;
+ }
+
+ _isDispose = true;
+ Clear();
+#if FANTASY_WEBGL
+ Pool>.Return(this);
+#else
+ MultiThreadPool.Return(this);
+#endif
+ }
+
+ ///
+ /// 获取一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public bool IsPool()
+ {
+ return _isPool;
+ }
+
+ ///
+ /// 设置一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public void SetIsPool(bool isPool)
+ {
+ _isPool = isPool;
+ }
+ }
+}
\ No newline at end of file
diff --git a/FantasyNetTest/NBC/Core/DataStructure/Dictionary/SortedDictionaryPool.cs b/FantasyNetTest/NBC/Core/DataStructure/Dictionary/SortedDictionaryPool.cs
new file mode 100644
index 0000000..b774175
--- /dev/null
+++ b/FantasyNetTest/NBC/Core/DataStructure/Dictionary/SortedDictionaryPool.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using NBC.Pool;
+
+namespace NBC.DataStructure.Dictionary
+{
+ ///
+ /// 提供一个可以使用对象池管理的排序字典类。
+ ///
+ ///
+ ///
+ public sealed class SortedDictionaryPool : SortedDictionary, IDisposable, IPool where TM : notnull
+ {
+ private bool _isPool;
+ private bool _isDispose;
+
+ ///
+ /// 释放实例占用的资源。
+ ///
+ public void Dispose()
+ {
+ if (_isDispose)
+ {
+ return;
+ }
+
+ _isDispose = true;
+ Clear();
+#if FANTASY_WEBGL
+ Pool>.Return(this);
+#else
+ MultiThreadPool.Return(this);
+#endif
+ }
+
+ ///
+ /// 创建一个新的 实例。
+ ///
+ /// 新创建的实例。
+ public static SortedDictionaryPool Create()
+ {
+#if FANTASY_WEBGL
+ var dictionary = Pool>.Rent();
+#else
+ var dictionary = MultiThreadPool.Rent>();
+#endif
+ dictionary._isDispose = false;
+ dictionary._isPool = true;
+ return dictionary;
+ }
+
+ ///
+ /// 获取一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public bool IsPool()
+ {
+ return _isPool;
+ }
+
+ ///
+ /// 设置一个值,该值指示当前实例是否为对象池中的实例。
+ ///
+ ///
+ public void SetIsPool(bool isPool)
+ {
+ _isPool = isPool;
+ }
+ }
+}
\ No newline at end of file
diff --git a/FantasyNetTest/NBC/Core/DataStructure/PriorityQueue/PriorityQueueGenerics.cs b/FantasyNetTest/NBC/Core/DataStructure/PriorityQueue/PriorityQueueGenerics.cs
new file mode 100644
index 0000000..4c1ae0e
--- /dev/null
+++ b/FantasyNetTest/NBC/Core/DataStructure/PriorityQueue/PriorityQueueGenerics.cs
@@ -0,0 +1,121 @@
+// ReSharper disable SwapViaDeconstruction
+// ReSharper disable UseIndexFromEndExpression
+// ReSharper disable ConvertToPrimaryConstructor
+using System;
+using System.Collections.Generic;
+#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
+
+#pragma warning disable CS8601 // Possible null reference assignment.
+namespace NBC.DataStructure.PriorityQueue
+{
+ ///
+ /// 优先队列
+ ///
+ /// 节点数据
+ /// 排序的类型、
+ public sealed class PriorityQueue where TPriority : IComparable
+ {
+ private readonly List> _heap;
+
+ public PriorityQueue(int initialCapacity = 16)
+ {
+ _heap = new List>(initialCapacity);
+ }
+
+ public int Count => _heap.Count;
+
+ public void Enqueue(TElement element, TPriority priority)
+ {
+ _heap.Add(new PriorityQueueItem(element, priority));
+ HeapifyUp(_heap.Count - 1);
+ }
+
+ public TElement Dequeue()
+ {
+ if (_heap.Count == 0)
+ {
+ throw new InvalidOperationException("The queue is empty.");
+ }
+
+ var item = _heap[0];
+ _heap[0] = _heap[_heap.Count - 1];
+ _heap.RemoveAt(_heap.Count - 1);
+ HeapifyDown(0);
+ return item.Element;
+ }
+
+ public bool TryDequeue(out TElement element)
+ {
+ if (_heap.Count == 0)
+ {
+ element = default(TElement);
+ return false;
+ }
+
+ element = Dequeue();
+ return true;
+ }
+
+ public TElement Peek()
+ {
+ if (_heap.Count == 0)
+ {
+ throw new InvalidOperationException("The queue is empty.");
+ }
+ return _heap[0].Element;
+ }
+
+ // ReSharper disable once IdentifierTypo
+ private void HeapifyUp(int index)
+ {
+ while (index > 0)
+ {
+ var parentIndex = (index - 1) / 2;
+ if (_heap[index].Priority.CompareTo(_heap[parentIndex].Priority) >= 0)
+ {
+ break;
+ }
+ Swap(index, parentIndex);
+ index = parentIndex;
+ }
+ }
+
+ // ReSharper disable once IdentifierTypo
+ private void HeapifyDown(int index)
+ {
+ var lastIndex = _heap.Count - 1;
+ while (true)
+ {
+ var smallestIndex = index;
+ var leftChildIndex = 2 * index + 1;
+ var rightChildIndex = 2 * index + 2;
+
+ if (leftChildIndex <= lastIndex && _heap[leftChildIndex].Priority.CompareTo(_heap[smallestIndex].Priority) < 0)
+ {
+ smallestIndex = leftChildIndex;
+ }
+
+ if (rightChildIndex <= lastIndex && _heap[rightChildIndex].Priority.CompareTo(_heap[smallestIndex].Priority) < 0)
+ {
+ smallestIndex = rightChildIndex;
+ }
+
+ if (smallestIndex == index)
+ {
+ break;
+ }
+
+ Swap(index, smallestIndex);
+ index = smallestIndex;
+ }
+ }
+
+ private void Swap(int index1, int index2)
+ {
+ var temp = _heap[index1];
+ _heap[index1] = _heap[index2];
+ _heap[index2] = temp;
+ }
+ }
+}
+
diff --git a/FantasyNetTest/NBC/Core/DataStructure/PriorityQueue/PriorityQueueItem.cs b/FantasyNetTest/NBC/Core/DataStructure/PriorityQueue/PriorityQueueItem.cs
new file mode 100644
index 0000000..754b313
--- /dev/null
+++ b/FantasyNetTest/NBC/Core/DataStructure/PriorityQueue/PriorityQueueItem.cs
@@ -0,0 +1,30 @@
+// ReSharper disable ConvertToPrimaryConstructor
+// ReSharper disable SwapViaDeconstruction
+// ReSharper disable InconsistentNaming
+#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
+namespace NBC.DataStructure.PriorityQueue
+{
+ public struct PriorityQueueItemUint
+ {
+ public T Element { get; set; }
+ public uint Priority { get; set; }
+
+ public PriorityQueueItemUint(T element, uint priority)
+ {
+ Element = element;
+ Priority = priority;
+ }
+ }
+
+ public struct PriorityQueueItem
+ {
+ public T Element { get; }
+ public T1 Priority { get; }
+
+ public PriorityQueueItem(T element, T1 priority)
+ {
+ Element = element;
+ Priority = priority;
+ }
+ }
+}
\ No newline at end of file
diff --git a/FantasyNetTest/NBC/Core/DataStructure/PriorityQueue/PriorityQueueSimple.cs b/FantasyNetTest/NBC/Core/DataStructure/PriorityQueue/PriorityQueueSimple.cs
new file mode 100644
index 0000000..9841c06
--- /dev/null
+++ b/FantasyNetTest/NBC/Core/DataStructure/PriorityQueue/PriorityQueueSimple.cs
@@ -0,0 +1,116 @@
+// ReSharper disable SwapViaDeconstruction
+// ReSharper disable UseIndexFromEndExpression
+// ReSharper disable ConvertToPrimaryConstructor
+using System;
+using System.Collections.Generic;
+#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
+#pragma warning disable CS8601 // Possible null reference assignment.
+namespace NBC.DataStructure.PriorityQueue
+{
+ public sealed class PriorityQueue where T : IComparable
+ {
+ private readonly List