fist commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
obj/
|
||||
bin/
|
||||
13
.idea/.idea.FantasyNetTest/.idea/.gitignore
generated
vendored
Normal file
13
.idea/.idea.FantasyNetTest/.idea/.gitignore
generated
vendored
Normal file
@@ -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
|
||||
4
.idea/.idea.FantasyNetTest/.idea/encodings.xml
generated
Normal file
4
.idea/.idea.FantasyNetTest/.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||
</project>
|
||||
8
.idea/.idea.FantasyNetTest/.idea/indexLayout.xml
generated
Normal file
8
.idea/.idea.FantasyNetTest/.idea/indexLayout.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
||||
BIN
.vs/FantasyNetTest/DesignTimeBuild/.dtbcache.v2
Normal file
BIN
.vs/FantasyNetTest/DesignTimeBuild/.dtbcache.v2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.vs/FantasyNetTest/v17/.futdcache.v2
Normal file
BIN
.vs/FantasyNetTest/v17/.futdcache.v2
Normal file
Binary file not shown.
BIN
.vs/FantasyNetTest/v17/.suo
Normal file
BIN
.vs/FantasyNetTest/v17/.suo
Normal file
Binary file not shown.
101
.vs/FantasyNetTest/v17/DocumentLayout.json
Normal file
101
.vs/FantasyNetTest/v17/DocumentLayout.json
Normal file
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
.vs/ProjectEvaluation/fantasynettest.metadata.v7.bin
Normal file
BIN
.vs/ProjectEvaluation/fantasynettest.metadata.v7.bin
Normal file
Binary file not shown.
BIN
.vs/ProjectEvaluation/fantasynettest.projects.v7.bin
Normal file
BIN
.vs/ProjectEvaluation/fantasynettest.projects.v7.bin
Normal file
Binary file not shown.
16
FantasyNetTest.sln
Normal file
16
FantasyNetTest.sln
Normal file
@@ -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
|
||||
9
FantasyNetTest.sln.DotSettings.user
Normal file
9
FantasyNetTest.sln.DotSettings.user
Normal file
@@ -0,0 +1,9 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AComboBox_002Ecs_002Fl_003AC_0021_003FUsers_003F60527_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fce02df019cd24b8b9cbae16d58cdd1e7cee8a0_003F41_003Faa378fa3_003FComboBox_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADockStyle_002Ecs_002Fl_003AC_0021_003FUsers_003F60527_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fce02df019cd24b8b9cbae16d58cdd1e7cee8a0_003F1e_003F1b6a177c_003FDockStyle_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003AC_0021_003FUsers_003F60527_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fde3edec1170e48a0a2478d8743508635c8e910_003F1b_003F30d746c0_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANativeWindow_002Ecs_002Fl_003AC_0021_003FUsers_003F60527_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fce02df019cd24b8b9cbae16d58cdd1e7cee8a0_003F63_003F48c3a174_003FNativeWindow_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APropertyInfo_002Ecs_002Fl_003AC_0021_003FUsers_003F60527_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fde3edec1170e48a0a2478d8743508635c8e910_003F07_003F70ad38ae_003FPropertyInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATextBoxBase_002Ecs_002Fl_003AC_0021_003FUsers_003F60527_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fce02df019cd24b8b9cbae16d58cdd1e7cee8a0_003F73_003F00565224_003FTextBoxBase_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=FantasyNetTest_002FForm1/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/ResxEditorPersonal/Initialized/@EntryValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
25
FantasyNetTest/Config.cs
Normal file
25
FantasyNetTest/Config.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace FantasyNetTest;
|
||||
|
||||
[Serializable]
|
||||
public class Config
|
||||
{
|
||||
/// <summary>
|
||||
/// 登录服地址
|
||||
/// </summary>
|
||||
public string Server { get; set; } = "127.0.0.1:20001";
|
||||
|
||||
/// <summary>
|
||||
/// 心跳间隔
|
||||
/// </summary>
|
||||
public int Heartbeat { get; set; } = 5;
|
||||
|
||||
/// <summary>
|
||||
/// 默认账号
|
||||
/// </summary>
|
||||
public string Account { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 协议目录
|
||||
/// </summary>
|
||||
public string ProtocolScriptPath { get; set; } = "";
|
||||
}
|
||||
6
FantasyNetTest/Config.json
Normal file
6
FantasyNetTest/Config.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"Server": "127.0.0.1:20001",
|
||||
"Heartbeat": 5,
|
||||
"ProtocolScriptPath": "D:\\myself\\Games\\Fishing2\\Assets\\Scripts\\Generate",
|
||||
"Account": "test003"
|
||||
}
|
||||
31
FantasyNetTest/FantasyNetTest.csproj
Normal file
31
FantasyNetTest/FantasyNetTest.csproj
Normal file
@@ -0,0 +1,31 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Config.json">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="NBC\Core\Platform\Test\" />
|
||||
<Folder Include="NBC\Plugins\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Scripting" Version="4.14.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="System.Buffers" Version="4.6.1" />
|
||||
<PackageReference Include="System.IO.Pipelines" Version="9.0.8" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
8
FantasyNetTest/FantasyNetTest.csproj.user
Normal file
8
FantasyNetTest/FantasyNetTest.csproj.user
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Compile Update="Form1.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
210
FantasyNetTest/Form1.Designer.cs
generated
Normal file
210
FantasyNetTest/Form1.Designer.cs
generated
Normal file
@@ -0,0 +1,210 @@
|
||||
namespace FantasyNetTest;
|
||||
|
||||
partial class Form1
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
757
FantasyNetTest/Form1.cs
Normal file
757
FantasyNetTest/Form1.cs
Normal file
@@ -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<Type> _allRequest = new List<Type>();
|
||||
|
||||
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<Assembly> loadAssemblies = new List<Assembly>();
|
||||
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<JWTParseComponent>();
|
||||
|
||||
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<JWTParseComponent>().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<JWTParseComponent>().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<SessionHeartbeatComponent>().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<Config>(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<PropertyInfo> _propertyInfos = new List<PropertyInfo>();
|
||||
|
||||
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<ProtoMemberAttribute>() != 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
|
||||
}
|
||||
126
FantasyNetTest/Form1.resx
Normal file
126
FantasyNetTest/Form1.resx
Normal file
@@ -0,0 +1,126 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="timer1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
<metadata name="timer2.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>109, 17</value>
|
||||
</metadata>
|
||||
</root>
|
||||
67
FantasyNetTest/Loader/ScriptLoader.cs
Normal file
67
FantasyNetTest/Loader/ScriptLoader.cs
Normal file
@@ -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<MetadataReference>();
|
||||
|
||||
// 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); // 假设是静态方法无参数
|
||||
}
|
||||
}
|
||||
157
FantasyNetTest/Log.cs
Normal file
157
FantasyNetTest/Log.cs
Normal file
@@ -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<LogData> _logDatas = new Queue<LogData>();
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
45
FantasyNetTest/LoginTask.cs
Normal file
45
FantasyNetTest/LoginTask.cs
Normal file
@@ -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<JWTParseComponent>().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}");
|
||||
}
|
||||
}
|
||||
BIN
FantasyNetTest/NBC.zip
Normal file
BIN
FantasyNetTest/NBC.zip
Normal file
Binary file not shown.
89
FantasyNetTest/NBC/Core/Assembly/AssemblyInfo.cs
Normal file
89
FantasyNetTest/NBC/Core/Assembly/AssemblyInfo.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// AssemblyInfo提供有关程序集和类型的信息
|
||||
/// </summary>
|
||||
public sealed class AssemblyInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 唯一标识
|
||||
/// </summary>
|
||||
public readonly long AssemblyIdentity;
|
||||
/// <summary>
|
||||
/// 获取或设置与此程序集相关联的 <see cref="Assembly"/> 实例。
|
||||
/// </summary>
|
||||
public System.Reflection.Assembly Assembly { get; private set; }
|
||||
/// <summary>
|
||||
/// 程序集类型集合,获取一个列表,包含从程序集加载的所有类型。
|
||||
/// </summary>
|
||||
public readonly List<Type> AssemblyTypeList = new List<Type>();
|
||||
/// <summary>
|
||||
/// 程序集类型分组集合,获取一个分组列表,将接口类型映射到实现这些接口的类型。
|
||||
/// </summary>
|
||||
public readonly OneToManyList<Type, Type> AssemblyTypeGroupList = new OneToManyList<Type, Type>();
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 <see cref="AssemblyInfo"/> 类的新实例。
|
||||
/// </summary>
|
||||
/// <param name="assemblyIdentity"></param>
|
||||
public AssemblyInfo(long assemblyIdentity)
|
||||
{
|
||||
AssemblyIdentity = assemblyIdentity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从指定的程序集加载类型信息并进行分类。
|
||||
/// </summary>
|
||||
/// <param name="assembly">要加载信息的程序集。</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重新加载程序集的类型信息。
|
||||
/// </summary>
|
||||
/// <param name="assembly"></param>
|
||||
public void ReLoad(System.Reflection.Assembly assembly)
|
||||
{
|
||||
Unload();
|
||||
Load(assembly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 卸载程序集的类型信息。
|
||||
/// </summary>
|
||||
public void Unload()
|
||||
{
|
||||
AssemblyTypeList.Clear();
|
||||
AssemblyTypeGroupList.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
286
FantasyNetTest/NBC/Core/Assembly/AssemblySystem.cs
Normal file
286
FantasyNetTest/NBC/Core/Assembly/AssemblySystem.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 管理程序集加载和卸载的帮助类。
|
||||
/// </summary>
|
||||
public static class AssemblySystem
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
private static readonly List<IAssembly> AssemblySystems = new List<IAssembly>();
|
||||
private static readonly Dictionary<long, AssemblyInfo> AssemblyList = new Dictionary<long, AssemblyInfo>();
|
||||
#else
|
||||
private static readonly ConcurrentQueue<IAssembly> AssemblySystems = new ConcurrentQueue<IAssembly>();
|
||||
private static readonly ConcurrentDictionary<long, AssemblyInfo> AssemblyList = new ConcurrentDictionary<long, AssemblyInfo>();
|
||||
#endif
|
||||
/// <summary>
|
||||
/// 初始化 AssemblySystem。(仅限内部)
|
||||
/// </summary>
|
||||
/// <param name="assemblies"></param>
|
||||
internal static async FTask InnerInitialize(params System.Reflection.Assembly[] assemblies)
|
||||
{
|
||||
await LoadAssembly(typeof(AssemblySystem).Assembly);
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
await LoadAssembly(assembly);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载指定的程序集,并触发相应的事件。
|
||||
/// </summary>
|
||||
/// <param name="assembly">要加载的程序集。</param>
|
||||
/// <param name="isCurrentDomain">如果当前Domain中已经存在同名的Assembly,使用Domain中的程序集。</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 卸载程序集
|
||||
/// </summary>
|
||||
/// <param name="assembly"></param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将AssemblySystem接口的object注册到程序集管理中心
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 程序集管理中心卸载注册的Load、ReLoad、UnLoad的接口
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有已加载程序集中的所有类型。
|
||||
/// </summary>
|
||||
/// <returns>所有已加载程序集中的类型。</returns>
|
||||
public static IEnumerable<Type> ForEach()
|
||||
{
|
||||
foreach (var (_, assemblyInfo) in AssemblyList)
|
||||
{
|
||||
foreach (var type in assemblyInfo.AssemblyTypeList)
|
||||
{
|
||||
yield return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定程序集中的所有类型。
|
||||
/// </summary>
|
||||
/// <param name="assemblyIdentity">程序集唯一标识。</param>
|
||||
/// <returns>指定程序集中的类型。</returns>
|
||||
public static IEnumerable<Type> ForEach(long assemblyIdentity)
|
||||
{
|
||||
if (!AssemblyList.TryGetValue(assemblyIdentity, out var assemblyInfo))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var type in assemblyInfo.AssemblyTypeList)
|
||||
{
|
||||
yield return type;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有已加载程序集中实现指定类型的所有类型。
|
||||
/// </summary>
|
||||
/// <param name="findType">要查找的基类或接口类型。</param>
|
||||
/// <returns>所有已加载程序集中实现指定类型的类型。</returns>
|
||||
public static IEnumerable<Type> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定程序集中实现指定类型的所有类型。
|
||||
/// </summary>
|
||||
/// <param name="assemblyIdentity">程序集唯一标识。</param>
|
||||
/// <param name="findType">要查找的基类或接口类型。</param>
|
||||
/// <returns>指定程序集中实现指定类型的类型。</returns>
|
||||
public static IEnumerable<Type> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定程序集的实例。
|
||||
/// </summary>
|
||||
/// <param name="assemblyIdentity">程序集名称。</param>
|
||||
/// <returns>指定程序集的实例,如果未加载则返回 null。</returns>
|
||||
public static System.Reflection.Assembly GetAssembly(long assemblyIdentity)
|
||||
{
|
||||
return !AssemblyList.TryGetValue(assemblyIdentity, out var assemblyInfo) ? null : assemblyInfo.Assembly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前框架注册的Assembly
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<System.Reflection.Assembly> ForEachAssembly
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var (_, assemblyInfo) in AssemblyList)
|
||||
{
|
||||
yield return assemblyInfo.Assembly;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据Assembly的强命名计算唯一标识。
|
||||
/// </summary>
|
||||
/// <param name="assembly"></param>
|
||||
/// <returns></returns>
|
||||
private static long AssemblyIdentity(System.Reflection.Assembly assembly)
|
||||
{
|
||||
return HashCodeHelper.ComputeHash64(assembly.GetName().Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源,卸载所有加载的程序集。
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
27
FantasyNetTest/NBC/Core/Assembly/IAssembly.cs
Normal file
27
FantasyNetTest/NBC/Core/Assembly/IAssembly.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using NBC.Async;
|
||||
|
||||
namespace NBC.Assembly
|
||||
{
|
||||
/// <summary>
|
||||
/// 实现这个接口、会再程序集首次加载、卸载、重载的时候调用
|
||||
/// </summary>
|
||||
public interface IAssembly : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 程序集加载时调用
|
||||
/// </summary>
|
||||
/// <param name="assemblyIdentity">程序集标识</param>
|
||||
public FTask Load(long assemblyIdentity);
|
||||
/// <summary>
|
||||
/// 程序集重新加载的时候调用
|
||||
/// </summary>
|
||||
/// <param name="assemblyIdentity">程序集标识</param>
|
||||
public FTask ReLoad(long assemblyIdentity);
|
||||
/// <summary>
|
||||
/// 卸载的时候调用
|
||||
/// </summary>
|
||||
/// <param name="assemblyIdentity">程序集标识</param>
|
||||
public FTask OnUnLoad(long assemblyIdentity);
|
||||
}
|
||||
}
|
||||
20
FantasyNetTest/NBC/Core/Attributes/Attributes.cs
Normal file
20
FantasyNetTest/NBC/Core/Attributes/Attributes.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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、当读取完成后用过的环会放在缓存中,不会销毁掉。
|
||||
/// <summary>
|
||||
/// 自增式缓存类,继承自 Stream 和 IDisposable 接口。
|
||||
/// 环形缓存具有自动扩充的特性,但不会收缩,适用于操作不过大的 IO 流。
|
||||
/// </summary>
|
||||
public sealed class CircularBuffer : Stream, IDisposable
|
||||
{
|
||||
private byte[] _lastBuffer;
|
||||
/// <summary>
|
||||
/// 环形缓存块的默认大小
|
||||
/// </summary>
|
||||
public const int ChunkSize = 8192;
|
||||
private readonly Queue<byte[]> _bufferCache = new Queue<byte[]>();
|
||||
private readonly Queue<byte[]> _bufferQueue = new Queue<byte[]>();
|
||||
/// <summary>
|
||||
/// 获取或设置环形缓存的第一个索引位置
|
||||
/// </summary>
|
||||
public int FirstIndex { get; set; }
|
||||
/// <summary>
|
||||
/// 获取或设置环形缓存的最后一个索引位置
|
||||
/// </summary>
|
||||
public int LastIndex { get; set; }
|
||||
/// <summary>
|
||||
/// 获取环形缓存的总长度
|
||||
/// </summary>
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_bufferQueue.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (_bufferQueue.Count - 1) * ChunkSize + LastIndex - FirstIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取环形缓存的第一个块
|
||||
/// </summary>
|
||||
public byte[] First
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_bufferQueue.Count == 0)
|
||||
{
|
||||
AddLast();
|
||||
}
|
||||
|
||||
return _bufferQueue.Peek();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取环形缓存的最后一个块
|
||||
/// </summary>
|
||||
public byte[] Last
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_bufferQueue.Count == 0)
|
||||
{
|
||||
AddLast();
|
||||
}
|
||||
|
||||
return _lastBuffer;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 向环形缓存中添加一个新的块
|
||||
/// </summary>
|
||||
public void AddLast()
|
||||
{
|
||||
var buffer = _bufferCache.Count > 0 ? _bufferCache.Dequeue() : new byte[ChunkSize];
|
||||
_bufferQueue.Enqueue(buffer);
|
||||
_lastBuffer = buffer;
|
||||
}
|
||||
/// <summary>
|
||||
/// 从环形缓存中移除第一个块
|
||||
/// </summary>
|
||||
public void RemoveFirst()
|
||||
{
|
||||
_bufferCache.Enqueue(_bufferQueue.Dequeue());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从流中读取指定数量的数据到缓存。
|
||||
/// </summary>
|
||||
/// <param name="stream">源数据流。</param>
|
||||
/// <param name="count">要读取的字节数。</param>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从缓存中读取指定数量的数据到内存。
|
||||
/// </summary>
|
||||
/// <param name="memory">目标内存。</param>
|
||||
/// <param name="count">要读取的字节数。</param>
|
||||
public void Read(Memory<byte> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从自定义流中读取数据到指定的缓冲区。
|
||||
/// </summary>
|
||||
/// <param name="buffer">目标缓冲区,用于存储读取的数据。</param>
|
||||
/// <param name="offset">目标缓冲区中的起始偏移量。</param>
|
||||
/// <param name="count">要读取的字节数。</param>
|
||||
/// <returns>实际读取的字节数。</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将数据从给定的字节数组写入流中。
|
||||
/// </summary>
|
||||
/// <param name="buffer">包含要写入的数据的字节数组。</param>
|
||||
public void Write(byte[] buffer)
|
||||
{
|
||||
Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将数据从给定的流写入流中。
|
||||
/// </summary>
|
||||
/// <param name="stream">包含要写入的数据的流。</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将数据从给定的字节数组写入流中。
|
||||
/// </summary>
|
||||
/// <param name="buffer">包含要写入的数据的字节数组。</param>
|
||||
/// <param name="offset">开始写入的缓冲区中的索引。</param>
|
||||
/// <param name="count">要写入的字节数。</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,指示流是否支持读取操作。
|
||||
/// </summary>
|
||||
public override bool CanRead { get; } = true;
|
||||
/// <summary>
|
||||
/// 获取一个值,指示流是否支持寻找操作。
|
||||
/// </summary>
|
||||
public override bool CanSeek { get; } = false;
|
||||
/// <summary>
|
||||
/// 获取一个值,指示流是否支持写入操作。
|
||||
/// </summary>
|
||||
public override bool CanWrite { get; } = true;
|
||||
/// <summary>
|
||||
/// 获取或设置流中的位置。
|
||||
/// </summary>
|
||||
public override long Position { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 刷新流(在此实现中引发未实现异常)。
|
||||
/// </summary>
|
||||
public override void Flush()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在流中寻找特定位置(在此实现中引发未实现异常)。
|
||||
/// </summary>
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置流的长度(在此实现中引发未实现异常)。
|
||||
/// </summary>
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放 CustomStream 使用的所有资源。
|
||||
/// </summary>
|
||||
public new void Dispose()
|
||||
{
|
||||
_bufferQueue.Clear();
|
||||
_lastBuffer = null;
|
||||
FirstIndex = 0;
|
||||
LastIndex = 0;
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 并发的一对多列表池,用于维护具有相同键的多个值的关联关系,实现了 <see cref="IDisposable"/> 接口。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">关键字的类型,不能为空。</typeparam>
|
||||
/// <typeparam name="TValue">值的类型。</typeparam>
|
||||
public class ConcurrentOneToManyListPool<TKey, TValue> : ConcurrentOneToManyList<TKey, TValue>, IDisposable, IPool where TKey : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="ConcurrentOneToManyListPool{TKey, TValue}"/> 的实例。
|
||||
/// </summary>
|
||||
/// <returns>创建的实例。</returns>
|
||||
public static ConcurrentOneToManyListPool<TKey, TValue> Create()
|
||||
{
|
||||
var a = MultiThreadPool.Rent<ConcurrentOneToManyListPool<TKey, TValue>>();
|
||||
a._isDispose = false;
|
||||
a._isPool = true;
|
||||
return a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放实例占用的资源。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
// 清空实例的数据
|
||||
Clear();
|
||||
// 将实例返回到池中以便重用
|
||||
MultiThreadPool.Return(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 并发的一对多列表,用于维护具有相同键的多个值的关联关系。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">关键字的类型,不能为空。</typeparam>
|
||||
/// <typeparam name="TValue">值的类型。</typeparam>
|
||||
public class ConcurrentOneToManyList<TKey, TValue> : ConcurrentDictionary<TKey, List<TValue>> where TKey : notnull
|
||||
{
|
||||
private readonly Queue<List<TValue>> _queue = new Queue<List<TValue>>();
|
||||
private readonly int _recyclingLimit = 120;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 <see cref="ConcurrentOneToManyList{TKey, TValue}"/> 类的新实例。
|
||||
/// </summary>
|
||||
public ConcurrentOneToManyList()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置最大缓存数量
|
||||
/// </summary>
|
||||
/// <param name="recyclingLimit">
|
||||
/// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
|
||||
/// 2:设置成0不控制数量,全部缓存
|
||||
/// </param>
|
||||
public ConcurrentOneToManyList(int recyclingLimit)
|
||||
{
|
||||
_recyclingLimit = recyclingLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断指定键的列表是否包含指定值。
|
||||
/// </summary>
|
||||
/// <param name="key">要搜索的键。</param>
|
||||
/// <param name="value">要搜索的值。</param>
|
||||
/// <returns>如果列表包含值,则为 true;否则为 false。</returns>
|
||||
public bool Contains(TKey key, TValue value)
|
||||
{
|
||||
TryGetValue(key, out var list);
|
||||
|
||||
return list != null && list.Contains(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向指定键的列表中添加一个值。
|
||||
/// </summary>
|
||||
/// <param name="key">要添加值的键。</param>
|
||||
/// <param name="value">要添加的值。</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定键的列表中的第一个值。
|
||||
/// </summary>
|
||||
/// <param name="key">要获取第一个值的键。</param>
|
||||
/// <returns>指定键的列表中的第一个值,如果不存在则为默认值。</returns>
|
||||
public TValue First(TKey key)
|
||||
{
|
||||
return !TryGetValue(key, out var list) ? default : list.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从指定键的列表中移除一个值。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除值的键。</param>
|
||||
/// <param name="value">要移除的值。</param>
|
||||
public void RemoveValue(TKey key, TValue value)
|
||||
{
|
||||
if (!TryGetValue(key, out var list)) return;
|
||||
|
||||
list.Remove(value);
|
||||
|
||||
if (list.Count == 0) RemoveKey(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字典中移除指定键以及其关联的列表。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除的键。</param>
|
||||
public void RemoveKey(TKey key)
|
||||
{
|
||||
if (!TryRemove(key, out var list)) return;
|
||||
|
||||
Recycle(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从队列中获取一个列表,如果队列为空则创建一个新的列表。
|
||||
/// </summary>
|
||||
/// <returns>获取的列表。</returns>
|
||||
private List<TValue> Fetch()
|
||||
{
|
||||
return _queue.Count <= 0 ? new List<TValue>() : _queue.Dequeue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一个列表回收到队列中。
|
||||
/// </summary>
|
||||
/// <param name="list">要回收的列表。</param>
|
||||
private void Recycle(List<TValue> list)
|
||||
{
|
||||
list.Clear();
|
||||
|
||||
if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return;
|
||||
|
||||
_queue.Enqueue(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空当前类的数据,包括从基类继承的数据以及自定义的数据队列。
|
||||
/// </summary>
|
||||
protected new void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
_queue.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示一个并发的一对多队列池,用于维护具有相同键的多个值的关联关系,实现了 <see cref="IDisposable"/> 接口。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">关键字的类型,不能为空。</typeparam>
|
||||
/// <typeparam name="TValue">值的类型。</typeparam>
|
||||
public class ConcurrentOneToManyQueuePool<TKey, TValue> : ConcurrentOneToManyQueue<TKey, TValue>, IDisposable, IPool where TKey : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建并返回一个 <see cref="ConcurrentOneToManyQueuePool{TKey, TValue}"/> 的实例。
|
||||
/// </summary>
|
||||
/// <returns>创建的实例。</returns>
|
||||
public static ConcurrentOneToManyQueuePool<TKey, TValue> Create()
|
||||
{
|
||||
var a = MultiThreadPool.Rent<ConcurrentOneToManyQueuePool<TKey, TValue>>();
|
||||
a._isDispose = false;
|
||||
a._isPool = true;
|
||||
return a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放当前实例所占用的资源,并将实例返回到对象池中,以便重用。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
// 将实例返回到对象池中,以便重用
|
||||
MultiThreadPool.Return(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个并发的一对多队列,用于维护具有相同键的多个值的关联关系。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">关键字的类型,不能为空。</typeparam>
|
||||
/// <typeparam name="TValue">值的类型。</typeparam>
|
||||
public class ConcurrentOneToManyQueue<TKey, TValue> : ConcurrentDictionary<TKey, Queue<TValue>> where TKey : notnull
|
||||
{
|
||||
private readonly Queue<Queue<TValue>> _queue = new Queue<Queue<TValue>>();
|
||||
private readonly int _recyclingLimit;
|
||||
|
||||
/// <summary>
|
||||
/// 设置最大缓存数量
|
||||
/// </summary>
|
||||
/// <param name="recyclingLimit">
|
||||
/// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
|
||||
/// 2:设置成0不控制数量,全部缓存
|
||||
/// </param>
|
||||
public ConcurrentOneToManyQueue(int recyclingLimit = 0)
|
||||
{
|
||||
_recyclingLimit = recyclingLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断指定键的队列是否包含指定值。
|
||||
/// </summary>
|
||||
/// <param name="key">要搜索的键。</param>
|
||||
/// <param name="value">要搜索的值。</param>
|
||||
/// <returns>如果队列包含值,则为 true;否则为 false。</returns>
|
||||
public bool Contains(TKey key, TValue value)
|
||||
{
|
||||
TryGetValue(key, out var list);
|
||||
|
||||
return list != null && list.Contains(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向指定键的队列中添加一个值。
|
||||
/// </summary>
|
||||
/// <param name="key">要添加值的键。</param>
|
||||
/// <param name="value">要添加的值。</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从指定键的队列中出队并返回一个值。
|
||||
/// </summary>
|
||||
/// <param name="key">要出队的键。</param>
|
||||
/// <returns>出队的值,如果队列为空则为默认值。</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试从指定键的队列中出队一个值。
|
||||
/// </summary>
|
||||
/// <param name="key">要出队的键。</param>
|
||||
/// <param name="value">出队的值,如果队列为空则为默认值。</param>
|
||||
/// <returns>如果成功出队,则为 true;否则为 false。</returns>
|
||||
public bool TryDequeue(TKey key, out TValue value)
|
||||
{
|
||||
value = Dequeue(key);
|
||||
|
||||
return value != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字典中移除指定键以及其关联的队列。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除的键。</param>
|
||||
public void RemoveKey(TKey key)
|
||||
{
|
||||
if (!TryGetValue(key, out var list)) return;
|
||||
|
||||
TryRemove(key, out _);
|
||||
Recycle(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从队列中获取一个新的队列,如果队列为空则创建一个新的队列。
|
||||
/// </summary>
|
||||
/// <returns>获取的队列。</returns>
|
||||
private Queue<TValue> Fetch()
|
||||
{
|
||||
return _queue.Count <= 0 ? new Queue<TValue>() : _queue.Dequeue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一个队列回收到队列池中。
|
||||
/// </summary>
|
||||
/// <param name="list">要回收的队列。</param>
|
||||
private void Recycle(Queue<TValue> list)
|
||||
{
|
||||
list.Clear();
|
||||
|
||||
if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return;
|
||||
|
||||
_queue.Enqueue(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空当前类的数据,包括从基类继承的键值对字典中的数据以及自定义的队列池。
|
||||
/// </summary>
|
||||
protected new void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
_queue.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
134
FantasyNetTest/NBC/Core/DataStructure/Collection/HashSetPool.cs
Normal file
134
FantasyNetTest/NBC/Core/DataStructure/Collection/HashSetPool.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NBC.Pool;
|
||||
|
||||
namespace NBC.DataStructure.Collection
|
||||
{
|
||||
/// <summary>
|
||||
/// 可释放的哈希集合对象池。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">哈希集合中元素的类型。</typeparam>
|
||||
public sealed class HashSetPool<T> : HashSet<T>, IDisposable, IPool
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 释放实例所占用的资源,并将实例返回到对象池中,以便重用。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<HashSetPool<T>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="HashSetPool{T}"/> 哈希集合池的实例。
|
||||
/// </summary>
|
||||
/// <returns>创建的实例。</returns>
|
||||
public static HashSetPool<T> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var list = Pool<HashSetPool<T>>.Rent();
|
||||
list._isDispose = false;
|
||||
list._isPool = true;
|
||||
return list;
|
||||
#else
|
||||
var list = MultiThreadPool.Rent<HashSetPool<T>>();
|
||||
list._isDispose = false;
|
||||
list._isPool = true;
|
||||
return list;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基本哈希集合对象池,他自持有实际的哈希集合。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">哈希集合中元素的类型。</typeparam>
|
||||
public sealed class HashSetBasePool<T> : IDisposable, IPool
|
||||
{
|
||||
private bool _isPool;
|
||||
|
||||
/// <summary>
|
||||
/// 存储实际的哈希集合
|
||||
/// </summary>
|
||||
public HashSet<T> Set = new HashSet<T>();
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="HashSetBasePool{T}"/> 基本哈希集合对象池的实例。
|
||||
/// </summary>
|
||||
/// <returns>创建的实例。</returns>
|
||||
public static HashSetBasePool<T> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var hashSetBasePool = Pool<HashSetBasePool<T>>.Rent();
|
||||
hashSetBasePool._isPool = true;
|
||||
return hashSetBasePool;
|
||||
#else
|
||||
var hashSetBasePool = MultiThreadPool.Rent<HashSetBasePool<T>>();
|
||||
hashSetBasePool._isPool = true;
|
||||
return hashSetBasePool;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放实例所占用的资源,并将实例返回到对象池中,以便重用。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Set.Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<HashSetBasePool<T>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
101
FantasyNetTest/NBC/Core/DataStructure/Collection/ListPool.cs
Normal file
101
FantasyNetTest/NBC/Core/DataStructure/Collection/ListPool.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NBC.Pool;
|
||||
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
|
||||
namespace NBC.DataStructure.Collection
|
||||
{
|
||||
/// <summary>
|
||||
/// 可释放的列表(List)对象池。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">列表中元素的类型。</typeparam>
|
||||
public sealed class ListPool<T> : List<T>, IDisposable, IPool
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 释放实例所占用的资源,并将实例返回到对象池中,以便重用。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<ListPool<T>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定的元素创建一个 <see cref="ListPool{T}"/> 列表(List)对象池的实例。
|
||||
/// </summary>
|
||||
/// <param name="args">要添加到列表的元素。</param>
|
||||
/// <returns>创建的实例。</returns>
|
||||
public static ListPool<T> Create(params T[] args)
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var list = Pool<ListPool<T>>.Rent();
|
||||
#else
|
||||
var list = MultiThreadPool.Rent<ListPool<T>>();
|
||||
#endif
|
||||
list._isDispose = false;
|
||||
list._isPool = true;
|
||||
|
||||
if (args != null)
|
||||
{
|
||||
list.AddRange(args);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定的列表创建一个 <see cref="ListPool{T}"/> 列表(List)对象池的实例。
|
||||
/// </summary>
|
||||
/// <param name="args">要添加到列表的元素列表。</param>
|
||||
/// <returns>创建的实例。</returns>
|
||||
public static ListPool<T> Create(List<T> args)
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var list = Pool<ListPool<T>>.Rent();
|
||||
#else
|
||||
var list = MultiThreadPool.Rent<ListPool<T>>();
|
||||
#endif
|
||||
list._isDispose = false;
|
||||
list._isPool = true;
|
||||
|
||||
if (args != null)
|
||||
{
|
||||
list.AddRange(args);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 一对多哈希集合(OneToManyHashSet)对象池。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">值的类型。</typeparam>
|
||||
public class OneToManyHashSetPool<TKey, TValue> : OneToManyHashSet<TKey, TValue>, IDisposable, IPool where TKey : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="OneToManyHashSetPool{TKey, TValue}"/> 一对多哈希集合(OneToManyHashSet)对象池的实例。
|
||||
/// </summary>
|
||||
/// <returns>创建的实例。</returns>
|
||||
public static OneToManyHashSetPool<TKey, TValue> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var a = Pool<OneToManyHashSetPool<TKey, TValue>>.Rent();
|
||||
#else
|
||||
var a = MultiThreadPool.Rent<OneToManyHashSetPool<TKey, TValue>>();
|
||||
#endif
|
||||
a._isDispose = false;
|
||||
a._isPool = true;
|
||||
return a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放实例所占用的资源,并将实例返回到对象池中,以便重用。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<OneToManyHashSetPool<TKey, TValue>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一对多哈希集合(OneToManyHashSet),用于创建和管理键对应多个值的集合。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">值的类型。</typeparam>
|
||||
public class OneToManyHashSet<TKey, TValue> : Dictionary<TKey, HashSet<TValue>> where TKey : notnull
|
||||
{
|
||||
/// 用于回收和重用的空闲值集合队列。
|
||||
private readonly Queue<HashSet<TValue>> _queue = new Queue<HashSet<TValue>>();
|
||||
/// 设置最大回收限制,用于控制值集合的最大数量。
|
||||
private readonly int _recyclingLimit = 120;
|
||||
/// 一个空的、不包含任何元素的哈希集合,用于在查找失败时返回。
|
||||
private static HashSet<TValue> _empty = new HashSet<TValue>();
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 <see cref="OneToManyHashSet{TKey, TValue}"/> 类的新实例。
|
||||
/// </summary>
|
||||
public OneToManyHashSet() { }
|
||||
|
||||
/// <summary>
|
||||
/// 设置最大缓存数量
|
||||
/// </summary>
|
||||
/// <param name="recyclingLimit">
|
||||
/// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
|
||||
/// 2:设置成0不控制数量,全部缓存
|
||||
/// </param>
|
||||
public OneToManyHashSet(int recyclingLimit)
|
||||
{
|
||||
_recyclingLimit = recyclingLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断指定的键值对是否存在于集合中。
|
||||
/// </summary>
|
||||
/// <param name="key">键。</param>
|
||||
/// <param name="value">值。</param>
|
||||
/// <returns>如果存在则为 true,否则为 false。</returns>
|
||||
public bool Contains(TKey key, TValue value)
|
||||
{
|
||||
TryGetValue(key, out var list);
|
||||
|
||||
return list != null && list.Contains(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加指定的键值对到集合中。
|
||||
/// </summary>
|
||||
/// <param name="key">键。</param>
|
||||
/// <param name="value">值。</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从集合中移除指定键对应的值。
|
||||
/// </summary>
|
||||
/// <param name="key">键。</param>
|
||||
/// <param name="value">要移除的值。</param>
|
||||
public void RemoveValue(TKey key, TValue value)
|
||||
{
|
||||
if (!TryGetValue(key, out var list)) return;
|
||||
|
||||
list.Remove(value);
|
||||
|
||||
if (list.Count == 0) RemoveKey(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从集合中移除指定键及其对应的值集合。
|
||||
/// </summary>
|
||||
/// <param name="key">键。</param>
|
||||
public void RemoveKey(TKey key)
|
||||
{
|
||||
if (!TryGetValue(key, out var list)) return;
|
||||
|
||||
Remove(key);
|
||||
Recycle(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定键对应的值集合,如果不存在则返回一个空的哈希集合。
|
||||
/// </summary>
|
||||
/// <param name="key">键。</param>
|
||||
/// <returns>对应的值集合或空的哈希集合。</returns>
|
||||
public HashSet<TValue> GetValue(TKey key)
|
||||
{
|
||||
if (TryGetValue(key, out HashSet<TValue> value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return _empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从队列中获取一个空闲的值集合,或者创建一个新的。
|
||||
/// </summary>
|
||||
/// <returns>值集合。</returns>
|
||||
private HashSet<TValue> Fetch()
|
||||
{
|
||||
return _queue.Count <= 0 ? new HashSet<TValue>() : _queue.Dequeue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回收值集合到队列中,以便重复利用。
|
||||
/// </summary>
|
||||
/// <param name="list">要回收的值集合。</param>
|
||||
private void Recycle(HashSet<TValue> list)
|
||||
{
|
||||
list.Clear();
|
||||
|
||||
if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return;
|
||||
|
||||
_queue.Enqueue(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空集合中的数据并和队列。
|
||||
/// </summary>
|
||||
protected new void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
_queue.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 可回收的、一对多关系的列表池。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">值的类型。</typeparam>
|
||||
public class OneToManyListPool<TKey, TValue> : OneToManyList<TKey, TValue>, IDisposable, IPool where TKey : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="OneToManyListPool{TKey, TValue}"/> 一对多关系的列表池的实例。
|
||||
/// </summary>
|
||||
/// <returns>创建的实例。</returns>
|
||||
public static OneToManyListPool<TKey, TValue> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL || FANTASY_EXPORTER
|
||||
var list = Pool<OneToManyListPool<TKey, TValue>>.Rent();
|
||||
#else
|
||||
var list = MultiThreadPool.Rent<OneToManyListPool<TKey, TValue>>();
|
||||
#endif
|
||||
list._isDispose = false;
|
||||
list._isPool = true;
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放当前对象所占用的资源,并将对象回收到对象池中。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL || FANTASY_EXPORTER
|
||||
Pool<OneToManyListPool<TKey, TValue>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一对多关系的列表字典。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">值的类型。</typeparam>
|
||||
public class OneToManyList<TKey, TValue> : Dictionary<TKey, List<TValue>> where TKey : notnull
|
||||
{
|
||||
private readonly int _recyclingLimit = 120;
|
||||
private static readonly List<TValue> Empty = new List<TValue>();
|
||||
private readonly Queue<List<TValue>> _queue = new Queue<List<TValue>>();
|
||||
|
||||
/// <summary>
|
||||
/// 初始化一个新的 <see cref="OneToManyList{TKey, TValue}"/> 实例。
|
||||
/// </summary>
|
||||
public OneToManyList() { }
|
||||
|
||||
/// <summary>
|
||||
/// 对整个字典的所有 List 进行排序
|
||||
/// </summary>
|
||||
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<SortAttribute>();
|
||||
return sortAttr?.Sort ?? 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置最大缓存数量
|
||||
/// </summary>
|
||||
/// <param name="recyclingLimit">
|
||||
/// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
|
||||
/// 2:设置成0不控制数量,全部缓存
|
||||
/// </param>
|
||||
public OneToManyList(int recyclingLimit)
|
||||
{
|
||||
_recyclingLimit = recyclingLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断给定的键和值是否存在于列表中。
|
||||
/// </summary>
|
||||
/// <param name="key">要搜索的键。</param>
|
||||
/// <param name="value">要搜索的值。</param>
|
||||
/// <returns>如果存在则为 <see langword="true"/>,否则为 <see langword="false"/>。</returns>
|
||||
public bool Contains(TKey key, TValue value)
|
||||
{
|
||||
TryGetValue(key, out var list);
|
||||
|
||||
return list != null && list.Contains(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向列表中添加指定键和值。
|
||||
/// </summary>
|
||||
/// <param name="key">要添加值的键。</param>
|
||||
/// <param name="value">要添加的值。</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定键对应的列表中的第一个值。
|
||||
/// </summary>
|
||||
/// <param name="key">要获取值的键。</param>
|
||||
/// <returns>键对应的列表中的第一个值。</returns>
|
||||
public TValue First(TKey key)
|
||||
{
|
||||
return !TryGetValue(key, out var list) ? default : list.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从列表中移除指定键和值。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除值的键。</param>
|
||||
/// <param name="value">要移除的值。</param>
|
||||
/// <returns>如果成功移除则为 <see langword="true"/>,否则为 <see langword="false"/>。</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从列表中移除指定键及其关联的所有值。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除的键。</param>
|
||||
/// <returns>如果成功移除则为 <see langword="true"/>,否则为 <see langword="false"/>。</returns>
|
||||
public bool RemoveByKey(TKey key)
|
||||
{
|
||||
if (!TryGetValue(key, out var list))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Remove(key);
|
||||
Recycle(list);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定键关联的所有值的列表。
|
||||
/// </summary>
|
||||
/// <param name="key">要获取值的键。</param>
|
||||
/// <returns>键关联的所有值的列表。</returns>
|
||||
public List<TValue> GetValues(TKey key)
|
||||
{
|
||||
if (TryGetValue(key, out List<TValue> list))
|
||||
{
|
||||
return list;
|
||||
}
|
||||
|
||||
return Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除字典中的所有键值对,并回收相关的值集合。
|
||||
/// </summary>
|
||||
public new void Clear()
|
||||
{
|
||||
foreach (var keyValuePair in this) Recycle(keyValuePair.Value);
|
||||
|
||||
base.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从空闲值集合队列中获取一个值集合,如果队列为空则创建一个新的值集合。
|
||||
/// </summary>
|
||||
/// <returns>从队列中获取的值集合。</returns>
|
||||
private List<TValue> Fetch()
|
||||
{
|
||||
return _queue.Count <= 0 ? new List<TValue>() : _queue.Dequeue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回收一个不再使用的值集合到空闲值集合队列中。
|
||||
/// </summary>
|
||||
/// <param name="list">要回收的值集合。</param>
|
||||
private void Recycle(List<TValue> list)
|
||||
{
|
||||
list.Clear();
|
||||
|
||||
if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return;
|
||||
|
||||
_queue.Enqueue(list);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NBC.Pool;
|
||||
|
||||
#pragma warning disable CS8603
|
||||
|
||||
namespace NBC.DataStructure.Collection
|
||||
{
|
||||
/// <summary>
|
||||
/// 支持一对多关系的队列池,用于存储具有相同键的值的队列集合。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">值的类型。</typeparam>
|
||||
public class OneToManyQueuePool<TKey, TValue> : OneToManyQueue<TKey, TValue>, IDisposable, IPool where TKey : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="OneToManyQueuePool{TKey, TValue}"/> 一对多关系的队列池的实例。
|
||||
/// </summary>
|
||||
/// <returns>创建的实例。</returns>
|
||||
public static OneToManyQueuePool<TKey, TValue> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var a = Pool<OneToManyQueuePool<TKey, TValue>>.Rent();
|
||||
#else
|
||||
var a = MultiThreadPool.Rent<OneToManyQueuePool<TKey, TValue>>();
|
||||
#endif
|
||||
a._isDispose = false;
|
||||
a._isPool = true;
|
||||
return a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放当前实例所占用的资源,并将实例回收到对象池中。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<OneToManyQueuePool<TKey, TValue>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 支持一对多关系的队列,用于存储具有相同键的值的队列集合。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">值的类型。</typeparam>
|
||||
public class OneToManyQueue<TKey, TValue> : Dictionary<TKey, Queue<TValue>> where TKey : notnull
|
||||
{
|
||||
private readonly Queue<Queue<TValue>> _queue = new Queue<Queue<TValue>>();
|
||||
private readonly int _recyclingLimit;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="OneToManyQueue{TKey, TValue}"/> 一对多关系的队列的实例。设置最大缓存数量
|
||||
/// </summary>
|
||||
/// <param name="recyclingLimit">
|
||||
/// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
|
||||
/// 2:设置成0不控制数量,全部缓存
|
||||
/// </param>
|
||||
public OneToManyQueue(int recyclingLimit = 0)
|
||||
{
|
||||
_recyclingLimit = recyclingLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断指定键的值队列是否包含指定的值。
|
||||
/// </summary>
|
||||
/// <param name="key">要查找的键。</param>
|
||||
/// <param name="value">要查找的值。</param>
|
||||
/// <returns>如果存在,则为 <c>true</c>;否则为 <c>false</c>。</returns>
|
||||
public bool Contains(TKey key, TValue value)
|
||||
{
|
||||
TryGetValue(key, out var list);
|
||||
|
||||
return list != null && list.Contains(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定的值添加到指定键的值队列中。
|
||||
/// </summary>
|
||||
/// <param name="key">要添加值的键。</param>
|
||||
/// <param name="value">要添加的值。</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从指定键的值队列中出队一个值。
|
||||
/// </summary>
|
||||
/// <param name="key">要出队的键。</param>
|
||||
/// <returns>出队的值。</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试从指定键的值队列中出队一个值。
|
||||
/// </summary>
|
||||
/// <param name="key">要出队的键。</param>
|
||||
/// <param name="value">出队的值。</param>
|
||||
/// <returns>如果成功出队,则为 <c>true</c>;否则为 <c>false</c>。</returns>
|
||||
public bool TryDequeue(TKey key, out TValue value)
|
||||
{
|
||||
value = Dequeue(key);
|
||||
|
||||
return value != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字典中移除指定键及其对应的值队列。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除的键。</param>
|
||||
public void RemoveKey(TKey key)
|
||||
{
|
||||
if (!TryGetValue(key, out var list)) return;
|
||||
|
||||
Remove(key);
|
||||
Recycle(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从队列池中获取一个值队列。如果队列池为空,则创建一个新的值队列。
|
||||
/// </summary>
|
||||
/// <returns>获取的值队列。</returns>
|
||||
private Queue<TValue> Fetch()
|
||||
{
|
||||
return _queue.Count <= 0 ? new Queue<TValue>() : _queue.Dequeue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回收一个不再使用的值队列到队列池中,以便重用。
|
||||
/// </summary>
|
||||
/// <param name="list">要回收的值队列。</param>
|
||||
private void Recycle(Queue<TValue> list)
|
||||
{
|
||||
list.Clear();
|
||||
|
||||
if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return;
|
||||
|
||||
_queue.Enqueue(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空当前实例的数据,同时回收所有值队列。
|
||||
/// </summary>
|
||||
protected new void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
_queue.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NBC.Pool;
|
||||
|
||||
namespace NBC.DataStructure.Collection
|
||||
{
|
||||
/// <summary>
|
||||
/// 可重用的列表,继承自 <see cref="List{T}"/> 类。该类支持通过对象池重用列表实例,以减少对象分配和释放的开销。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">列表中元素的类型。</typeparam>
|
||||
public sealed class ReuseList<T> : List<T>, IDisposable, IPool
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="ReuseList{T}"/> 可重用的列表的实例。
|
||||
/// </summary>
|
||||
/// <returns>创建的实例。</returns>
|
||||
public static ReuseList<T> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var list = Pool<ReuseList<T>>.Rent();
|
||||
#else
|
||||
var list = MultiThreadPool.Rent<ReuseList<T>>();
|
||||
#endif
|
||||
list._isDispose = false;
|
||||
list._isPool = true;
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放该实例所占用的资源,并将实例返回到对象池中,以便重用。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<ReuseList<T>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 基于排序字典和并发集合实现的一对多映射列表的对象池包装类,继承自 <see cref="SortedConcurrentOneToManyList{TKey, TValue}"/> 类,
|
||||
/// 同时实现了 <see cref="IDisposable"/> 接口,以支持对象的重用和释放。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">值的类型。</typeparam>
|
||||
public class SortedConcurrentOneToManyListPool<TKey, TValue> : SortedConcurrentOneToManyList<TKey, TValue>, IDisposable, IPool where TKey : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="SortedConcurrentOneToManyListPool{TKey, TValue}"/> 实例,使用默认的参数设置。
|
||||
/// </summary>
|
||||
/// <returns>新创建的 <see cref="SortedConcurrentOneToManyListPool{TKey, TValue}"/> 实例。</returns>
|
||||
public static SortedConcurrentOneToManyListPool<TKey, TValue> Create()
|
||||
{
|
||||
var a = MultiThreadPool.Rent<SortedConcurrentOneToManyListPool<TKey, TValue>>();
|
||||
a._isDispose = false;
|
||||
a._isPool = true;
|
||||
return a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放当前对象池实例,将其返回到对象池以供重用。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
MultiThreadPool.Return(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于排序字典和并发集合实现的一多对映射列表类,继承自 <see cref="SortedDictionary{TKey, TValue}"/> 类,
|
||||
/// 用于在多个值与一个键关联的情况下进行管理和存储。该类支持并发操作,适用于多线程环境。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">值的类型。</typeparam>
|
||||
public class SortedConcurrentOneToManyList<TKey, TValue> : SortedDictionary<TKey, List<TValue>> where TKey : notnull
|
||||
{
|
||||
/// 用于同步操作的锁对象,它确保在多线程环境下对数据的安全访问。
|
||||
private readonly object _lockObject = new object();
|
||||
/// 用于存储缓存的队列。
|
||||
private readonly Queue<List<TValue>> _queue = new Queue<List<TValue>>();
|
||||
/// 控制缓存回收的限制。当缓存的数量超过此限制时,旧的缓存将会被回收。
|
||||
private readonly int _recyclingLimit;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化一个新的 <see cref="SortedConcurrentOneToManyList{TKey, TValue}"/> 类的实例,使用默认的参数设置。
|
||||
/// </summary>
|
||||
public SortedConcurrentOneToManyList()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化一个新的 <see cref="SortedConcurrentOneToManyList{TKey, TValue}"/> 类的实例,指定最大缓存数量。
|
||||
/// </summary>
|
||||
/// <param name="recyclingLimit">
|
||||
/// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
|
||||
/// 2:设置成0不控制数量,全部缓存
|
||||
/// </param>
|
||||
public SortedConcurrentOneToManyList(int recyclingLimit = 0)
|
||||
{
|
||||
_recyclingLimit = recyclingLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查指定的键和值是否存在于映射列表中。
|
||||
/// </summary>
|
||||
/// <param name="key">要检查的键。</param>
|
||||
/// <param name="value">要检查的值。</param>
|
||||
/// <returns>如果存在,则为 true;否则为 false。</returns>
|
||||
public bool Contains(TKey key, TValue value)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
TryGetValue(key, out var list);
|
||||
|
||||
return list != null && list.Contains(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定的值添加到与指定键关联的列表中。
|
||||
/// </summary>
|
||||
/// <param name="key">要关联值的键。</param>
|
||||
/// <param name="value">要添加到列表的值。</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取与指定键关联的列表中的第一个值。
|
||||
/// 如果列表不存在或为空,则返回默认值。
|
||||
/// </summary>
|
||||
/// <param name="key">要获取第一个值的键。</param>
|
||||
/// <returns>第一个值,或默认值。</returns>
|
||||
public TValue First(TKey key)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
return !TryGetValue(key, out var list) ? default : list.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从与指定键关联的列表中移除指定的值。
|
||||
/// 如果列表不存在或值不存在于列表中,则不执行任何操作。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除值的键。</param>
|
||||
/// <param name="value">要移除的值。</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从映射列表中移除指定的键及其关联的列表。
|
||||
/// 如果键不存在于映射列表中,则不执行任何操作。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除的键。</param>
|
||||
public void RemoveKey(TKey key)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (!TryGetValue(key, out var list)) return;
|
||||
|
||||
Remove(key);
|
||||
|
||||
Recycle(list);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从缓存中获取一个可重用的列表。如果缓存中不存在列表,则创建一个新的列表并返回。
|
||||
/// </summary>
|
||||
/// <returns>可重用的列表。</returns>
|
||||
private List<TValue> Fetch()
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
return _queue.Count <= 0 ? new List<TValue>() : _queue.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将不再使用的列表回收到缓存中,以便重复利用。如果缓存数量超过限制,则丢弃列表而不进行回收。
|
||||
/// </summary>
|
||||
/// <param name="list">要回收的列表。</param>
|
||||
private void Recycle(List<TValue> list)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
list.Clear();
|
||||
|
||||
if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return;
|
||||
|
||||
_queue.Enqueue(list);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空映射列表以及队列。
|
||||
/// </summary>
|
||||
protected new void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
_queue.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,192 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NBC.Pool;
|
||||
|
||||
namespace NBC.DataStructure.Collection
|
||||
{
|
||||
/// <summary>
|
||||
/// 基于排序字典实现的一对多关系的映射哈希集合的对象池包装类,将唯一键映射到多个值的哈希集合。
|
||||
/// 同时实现了 <see cref="IDisposable"/> 接口,以支持对象的重用和释放。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">字典中键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">哈希集合中值的类型。</typeparam>
|
||||
public class SortedOneToManyHashSetPool<TKey, TValue> : SortedOneToManyHashSet<TKey, TValue>, IDisposable, IPool where TKey : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="SortedOneToManyHashSetPool{TKey, TValue}"/> 实例。
|
||||
/// </summary>
|
||||
/// <returns>新创建的实例。</returns>
|
||||
public static SortedOneToManyHashSetPool<TKey, TValue> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var a = Pool<SortedOneToManyHashSetPool<TKey, TValue>>.Rent();
|
||||
#else
|
||||
var a = MultiThreadPool.Rent<SortedOneToManyHashSetPool<TKey, TValue>>();
|
||||
#endif
|
||||
a._isDispose = false;
|
||||
a._isPool = true;
|
||||
return a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放当前对象池实例,将其返回到对象池以供重用。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<SortedOneToManyHashSetPool<TKey, TValue>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于排序字典实现的一对多关系的映射哈希集合类,将唯一键映射到多个值的哈希集合。
|
||||
/// 用于在多个值与一个键关联的情况下进行管理和存储。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">字典中键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">集合中值的类型。</typeparam>
|
||||
public class SortedOneToManyHashSet<TKey, TValue> : SortedDictionary<TKey, HashSet<TValue>> where TKey : notnull
|
||||
{
|
||||
private readonly Queue<HashSet<TValue>> _queue = new Queue<HashSet<TValue>>();
|
||||
private readonly int _recyclingLimit = 120;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="SortedOneToManyHashSet{TKey, TValue}"/> 实例。
|
||||
/// </summary>
|
||||
public SortedOneToManyHashSet() { }
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="SortedOneToManyHashSet{TKey, TValue}"/> 实例,设置最大缓存数量
|
||||
/// </summary>
|
||||
/// <param name="recyclingLimit">
|
||||
/// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
|
||||
/// 2:设置成0不控制数量,全部缓存
|
||||
/// </param>
|
||||
public SortedOneToManyHashSet(int recyclingLimit)
|
||||
{
|
||||
_recyclingLimit = recyclingLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断哈希集合中是否包含指定的键值对。
|
||||
/// </summary>
|
||||
/// <param name="key">要查找的键。</param>
|
||||
/// <param name="value">要查找的值。</param>
|
||||
/// <returns>如果键值对存在,则为 true;否则为 false。</returns>
|
||||
public bool Contains(TKey key, TValue value)
|
||||
{
|
||||
TryGetValue(key, out var list);
|
||||
|
||||
return list != null && list.Contains(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定值添加到给定键关联的哈希集合中。
|
||||
/// </summary>
|
||||
/// <param name="key">要添加值的键。</param>
|
||||
/// <param name="value">要添加的值。</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从指定键关联的哈希集合中移除特定值。
|
||||
/// 如果哈希集合不存在或值不存在于集合中,则不执行任何操作。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除值的键。</param>
|
||||
/// <param name="value">要移除的值。</param>
|
||||
public void RemoveValue(TKey key, TValue value)
|
||||
{
|
||||
if (!TryGetValue(key, out var list)) return;
|
||||
|
||||
list.Remove(value);
|
||||
|
||||
if (list.Count == 0) RemoveKey(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字典中移除指定键以及关联的哈希集合,并将集合进行回收。
|
||||
/// 如果键不存在于映射列表中,则不执行任何操作。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除的键。</param>
|
||||
public void RemoveKey(TKey key)
|
||||
{
|
||||
if (!TryGetValue(key, out var list)) return;
|
||||
|
||||
Remove(key);
|
||||
|
||||
Recycle(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个空的或回收的哈希集合。
|
||||
/// </summary>
|
||||
/// <returns>获取的哈希集合实例。</returns>
|
||||
private HashSet<TValue> Fetch()
|
||||
{
|
||||
return _queue.Count <= 0 ? new HashSet<TValue>() : _queue.Dequeue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回收一个哈希集合,将其清空并放入回收队列中。
|
||||
/// </summary>
|
||||
/// <param name="list">要回收的哈希集合。</param>
|
||||
private void Recycle(HashSet<TValue> list)
|
||||
{
|
||||
list.Clear();
|
||||
|
||||
if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return;
|
||||
|
||||
_queue.Enqueue(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写 Clear 方法,清空字典并清空回收队列。
|
||||
/// </summary>
|
||||
protected new void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
_queue.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NBC.Pool;
|
||||
|
||||
#pragma warning disable CS8603
|
||||
|
||||
namespace NBC.DataStructure.Collection
|
||||
{
|
||||
/// <summary>
|
||||
/// 基于排序字典实现的一对多映射列表的对象池包装类,继承自 <see cref="SortedOneToManyList{TKey, TValue}"/> 类,
|
||||
/// 同时实现了 <see cref="IDisposable"/> 接口,以支持对象的重用和释放。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">字典中键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">列表中值的类型。</typeparam>
|
||||
public class SortedOneToManyListPool<TKey, TValue> : SortedOneToManyList<TKey, TValue>, IDisposable, IPool where TKey : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="SortedOneToManyListPool{TKey, TValue}"/> 实例。
|
||||
/// </summary>
|
||||
/// <returns>新创建的实例。</returns>
|
||||
public static SortedOneToManyListPool<TKey, TValue> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var a = Pool<SortedOneToManyListPool<TKey, TValue>>.Rent();
|
||||
#else
|
||||
var a = MultiThreadPool.Rent<SortedOneToManyListPool<TKey, TValue>>();
|
||||
#endif
|
||||
a._isDispose = false;
|
||||
a._isPool = true;
|
||||
return a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放当前对象池实例,将其返回到对象池以供重用。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<SortedOneToManyListPool<TKey, TValue>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于排序字典实现的一对多关系的映射列表类,将唯一键映射到包含多个值的列表。
|
||||
/// 用于在多个值与一个键关联的情况下进行管理和存储。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">字典中键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">列表中值的类型。</typeparam>
|
||||
public class SortedOneToManyList<TKey, TValue> : SortedDictionary<TKey, List<TValue>> where TKey : notnull
|
||||
{
|
||||
private readonly Queue<List<TValue>> _queue = new Queue<List<TValue>>();
|
||||
private readonly int _recyclingLimit;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="SortedOneToManyList{TKey, TValue}"/> 实例。
|
||||
/// </summary>
|
||||
public SortedOneToManyList()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="SortedOneToManyList{TKey, TValue}"/> 实例,设置最大缓存数量
|
||||
/// </summary>
|
||||
/// <param name="recyclingLimit">
|
||||
/// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
|
||||
/// 2:设置成0不控制数量,全部缓存
|
||||
/// </param>
|
||||
public SortedOneToManyList(int recyclingLimit = 0)
|
||||
{
|
||||
_recyclingLimit = recyclingLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断列表中是否包含指定的键值对。
|
||||
/// </summary>
|
||||
/// <param name="key">要查找的键。</param>
|
||||
/// <param name="value">要查找的值。</param>
|
||||
/// <returns>如果键值对存在,则为 true;否则为 false。</returns>
|
||||
public bool Contains(TKey key, TValue value)
|
||||
{
|
||||
TryGetValue(key, out var list);
|
||||
|
||||
return list != null && list.Contains(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定值添加到给定键关联的列表中。
|
||||
/// </summary>
|
||||
/// <param name="key">要添加值的键。</param>
|
||||
/// <param name="value">要添加的值。</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定键关联的列表中的第一个值。
|
||||
/// </summary>
|
||||
/// <param name="key">要查找值的键。</param>
|
||||
/// <returns>指定键关联的列表中的第一个值,如果列表为空则返回默认值。</returns>
|
||||
public TValue First(TKey key)
|
||||
{
|
||||
return !TryGetValue(key, out var list) ? default : list.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从指定键关联的列表中移除特定值。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除值的键。</param>
|
||||
/// <param name="value">要移除的值。</param>
|
||||
|
||||
public void RemoveValue(TKey key, TValue value)
|
||||
{
|
||||
if (!TryGetValue(key, out var list))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
list.Remove(value);
|
||||
|
||||
if (list.Count == 0)
|
||||
{
|
||||
RemoveKey(key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字典中移除指定键以及关联的列表,并将列表进行回收。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除的键。</param>
|
||||
|
||||
public void RemoveKey(TKey key)
|
||||
{
|
||||
if (!TryGetValue(key, out var list))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Remove(key);
|
||||
Recycle(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个空的或回收的列表。
|
||||
/// </summary>
|
||||
/// <returns>获取的列表实例。</returns>
|
||||
private List<TValue> Fetch()
|
||||
{
|
||||
return _queue.Count <= 0 ? new List<TValue>() : _queue.Dequeue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回收一个列表,将其清空并放入回收队列中。如果缓存数量超过限制,则丢弃列表而不进行回收
|
||||
/// </summary>
|
||||
/// <param name="list">要回收的列表。</param>
|
||||
private void Recycle(List<TValue> list)
|
||||
{
|
||||
list.Clear();
|
||||
|
||||
if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_queue.Enqueue(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写 Clear 方法,清空字典并清空回收队列。
|
||||
/// </summary>
|
||||
protected new void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
_queue.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.Collections.Generic;
|
||||
#pragma warning disable CS8601 // Possible null reference assignment.
|
||||
|
||||
namespace NBC.DataStructure.Dictionary
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供对字典的扩展方法。
|
||||
/// </summary>
|
||||
public static class DictionaryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 尝试从字典中移除指定键,并返回相应的值。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">字典中键的类型。</typeparam>
|
||||
/// <typeparam name="TV">字典中值的类型。</typeparam>
|
||||
/// <param name="self">要操作的字典实例。</param>
|
||||
/// <param name="key">要移除的键。</param>
|
||||
/// <param name="value">从字典中移除的值(如果成功移除)。</param>
|
||||
/// <returns>如果成功移除键值对,则为 true;否则为 false。</returns>
|
||||
public static bool TryRemove<T, TV>(this IDictionary<T, TV> self, T key, out TV value)
|
||||
{
|
||||
if (!self.TryGetValue(key, out value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
self.Remove(key);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NBC.Pool;
|
||||
|
||||
namespace NBC.DataStructure.Dictionary
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供一个可以使用对象池管理的字典类。
|
||||
/// </summary>
|
||||
/// <typeparam name="TM">字典中键的类型。</typeparam>
|
||||
/// <typeparam name="TN">字典中值的类型。</typeparam>
|
||||
public sealed class DictionaryPool<TM, TN> : Dictionary<TM, TN>, IDisposable, IPool where TM : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 释放实例占用的资源。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<DictionaryPool<TM, TN>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="DictionaryPool{TM, TN}"/> 实例。
|
||||
/// </summary>
|
||||
/// <returns>新创建的实例。</returns>
|
||||
public static DictionaryPool<TM, TN> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var dictionary = Pool<DictionaryPool<TM, TN>>.Rent();
|
||||
#else
|
||||
var dictionary = MultiThreadPool.Rent<DictionaryPool<TM, TN>>();
|
||||
#endif
|
||||
dictionary._isDispose = false;
|
||||
dictionary._isPool = true;
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供一个双向映射字典对象池类,用于双向键值对映射。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">字典中键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">字典中值的类型。</typeparam>
|
||||
public class DoubleMapDictionaryPool<TKey, TValue> : DoubleMapDictionary<TKey, TValue>, IDisposable, IPool where TKey : notnull where TValue : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="DoubleMapDictionaryPool{TKey, TValue}"/> 实例。
|
||||
/// </summary>
|
||||
/// <returns>新创建的实例。</returns>
|
||||
public static DoubleMapDictionaryPool<TKey, TValue> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var a = Pool<DoubleMapDictionaryPool<TKey, TValue>>.Rent();
|
||||
#else
|
||||
var a = MultiThreadPool.Rent<DoubleMapDictionaryPool<TKey, TValue>>();
|
||||
#endif
|
||||
a._isDispose = false;
|
||||
a._isPool = true;
|
||||
return a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放实例占用的资源。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<DoubleMapDictionaryPool<TKey, TValue>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可以实现双向映射的字典类,用于将键和值进行双向映射。
|
||||
/// </summary>
|
||||
/// <typeparam name="TK">键的类型,不能为 null。</typeparam>
|
||||
/// <typeparam name="TV">值的类型,不能为 null。</typeparam>
|
||||
public class DoubleMapDictionary<TK, TV> where TK : notnull where TV : notnull
|
||||
{
|
||||
private readonly Dictionary<TK, TV> _kv = new Dictionary<TK, TV>();
|
||||
private readonly Dictionary<TV, TK> _vk = new Dictionary<TV, TK>();
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的空的 <see cref="DoubleMapDictionary{TK, TV}"/> 实例。
|
||||
/// </summary>
|
||||
public DoubleMapDictionary() { }
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的具有指定初始容量的 <see cref="DoubleMapDictionary{TK, TV}"/> 实例。
|
||||
/// </summary>
|
||||
/// <param name="capacity">初始容量。</param>
|
||||
public DoubleMapDictionary(int capacity)
|
||||
{
|
||||
_kv = new Dictionary<TK, TV>(capacity);
|
||||
_vk = new Dictionary<TV, TK>(capacity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取包含字典中所有键的列表。
|
||||
/// </summary>
|
||||
public List<TK> Keys => new List<TK>(_kv.Keys);
|
||||
|
||||
/// <summary>
|
||||
/// 获取包含字典中所有值的列表。
|
||||
/// </summary>
|
||||
public List<TV> Values => new List<TV>(_vk.Keys);
|
||||
|
||||
/// <summary>
|
||||
/// 对字典中的每个键值对执行指定的操作。
|
||||
/// </summary>
|
||||
/// <param name="action">要执行的操作。</param>
|
||||
public void ForEach(Action<TK, TV> action)
|
||||
{
|
||||
if (action == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var keys = _kv.Keys;
|
||||
foreach (var key in keys)
|
||||
{
|
||||
action(key, _kv[key]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定的键值对添加到字典中。
|
||||
/// </summary>
|
||||
/// <param name="key">要添加的键。</param>
|
||||
/// <param name="value">要添加的值。</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据指定的键获取相应的值。
|
||||
/// </summary>
|
||||
/// <param name="key">要查找值的键。</param>
|
||||
/// <returns>与指定键关联的值,如果找不到键,则返回默认值。</returns>
|
||||
public TV GetValueByKey(TK key)
|
||||
{
|
||||
if (key != null && _kv.ContainsKey(key))
|
||||
{
|
||||
return _kv[key];
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试根据指定的键获取相应的值。
|
||||
/// </summary>
|
||||
/// <param name="key">要查找值的键。</param>
|
||||
/// <param name="value">如果找到,则为与指定键关联的值;否则为值的默认值。</param>
|
||||
/// <returns>如果找到键,则为 true;否则为 false。</returns>
|
||||
public bool TryGetValueByKey(TK key, out TV value)
|
||||
{
|
||||
var result = key != null && _kv.ContainsKey(key);
|
||||
|
||||
value = result ? _kv[key] : default;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据指定的值获取相应的键。
|
||||
/// </summary>
|
||||
/// <param name="value">要查找键的值。</param>
|
||||
/// <returns>与指定值关联的键,如果找不到值,则返回默认键。</returns>
|
||||
public TK GetKeyByValue(TV value)
|
||||
{
|
||||
if (value != null && _vk.ContainsKey(value))
|
||||
{
|
||||
return _vk[value];
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试根据指定的值获取相应的键。
|
||||
/// </summary>
|
||||
/// <param name="value">要查找键的值。</param>
|
||||
/// <param name="key">如果找到,则为与指定值关联的键;否则为键的默认值。</param>
|
||||
/// <returns>如果找到值,则为 true;否则为 false。</returns>
|
||||
public bool TryGetKeyByValue(TV value, out TK key)
|
||||
{
|
||||
var result = value != null && _vk.ContainsKey(value);
|
||||
|
||||
key = result ? _vk[value] : default;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据指定的键移除键值对。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除的键。</param>
|
||||
public void RemoveByKey(TK key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_kv.TryGetValue(key, out var value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_kv.Remove(key);
|
||||
_vk.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据指定的值移除键值对。
|
||||
/// </summary>
|
||||
/// <param name="value">要移除的值。</param>
|
||||
public void RemoveByValue(TV value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_vk.TryGetValue(value, out var key))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_kv.Remove(key);
|
||||
_vk.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空字典中的所有键值对。
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_kv.Clear();
|
||||
_vk.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断字典是否包含指定的键。
|
||||
/// </summary>
|
||||
/// <param name="key">要检查的键。</param>
|
||||
/// <returns>如果字典包含指定的键,则为 true;否则为 false。</returns>
|
||||
public bool ContainsKey(TK key)
|
||||
{
|
||||
return key != null && _kv.ContainsKey(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断字典是否包含指定的值。
|
||||
/// </summary>
|
||||
/// <param name="value">要检查的值。</param>
|
||||
/// <returns>如果字典包含指定的值,则为 true;否则为 false。</returns>
|
||||
public bool ContainsValue(TV value)
|
||||
{
|
||||
return value != null && _vk.ContainsKey(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断字典是否包含指定的键值对。
|
||||
/// </summary>
|
||||
/// <param name="key">要检查的键。</param>
|
||||
/// <param name="value">要检查的值。</param>
|
||||
/// <returns>如果字典包含指定的键值对,则为 true;否则为 false。</returns>
|
||||
public bool Contains(TK key, TV value)
|
||||
{
|
||||
if (key == null || value == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _kv.ContainsKey(key) && _vk.ContainsKey(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NBC.Pool;
|
||||
|
||||
namespace NBC.DataStructure.Dictionary
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供一个带资源释放功能的实体字典类,支持使用对象池管理。
|
||||
/// </summary>
|
||||
/// <typeparam name="TM">字典中键的类型。</typeparam>
|
||||
/// <typeparam name="TN">字典中值的类型,必须实现 IDisposable 接口。</typeparam>
|
||||
public sealed class EntityDictionary<TM, TN> : Dictionary<TM, TN>, IDisposable, IPool where TN : IDisposable where TM : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="EntityDictionary{TM, TN}"/> 实例。
|
||||
/// </summary>
|
||||
/// <returns>新创建的实例。</returns>
|
||||
public static EntityDictionary<TM, TN> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var entityDictionary = Pool<EntityDictionary<TM, TN>>.Rent();
|
||||
#else
|
||||
var entityDictionary = MultiThreadPool.Rent<EntityDictionary<TM, TN>>();
|
||||
#endif
|
||||
entityDictionary._isDispose = false;
|
||||
entityDictionary._isPool = true;
|
||||
return entityDictionary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空字典中的所有键值对,并释放值的资源。
|
||||
/// </summary>
|
||||
public new void Clear()
|
||||
{
|
||||
foreach (var keyValuePair in this)
|
||||
{
|
||||
keyValuePair.Value.Dispose();
|
||||
}
|
||||
|
||||
base.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空字典中的所有键值对,但不释放值的资源。
|
||||
/// </summary>
|
||||
public void ClearNotDispose()
|
||||
{
|
||||
base.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放实例占用的资源。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<EntityDictionary<TM, TN>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 一对多映射关系的字典对象池。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">外部字典中的键类型。</typeparam>
|
||||
/// <typeparam name="TValueKey">内部字典中的键类型。</typeparam>
|
||||
/// <typeparam name="TValue">内部字典中的值类型。</typeparam>
|
||||
public class OneToManyDictionaryPool<TKey, TValueKey, TValue> : OneToManyDictionary<TKey, TValueKey, TValue>, IDisposable, IPool where TKey : notnull where TValueKey : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="OneToManyDictionaryPool{TKey, TValueKey, TValue}"/> 的实例。
|
||||
/// </summary>
|
||||
/// <returns>新创建的 OneToManyDictionaryPool 实例。</returns>
|
||||
public static OneToManyDictionaryPool<TKey, TValueKey, TValue> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var a = Pool<OneToManyDictionaryPool<TKey, TValueKey, TValue>>.Rent();
|
||||
#else
|
||||
var a = MultiThreadPool.Rent<OneToManyDictionaryPool<TKey, TValueKey, TValue>>();
|
||||
#endif
|
||||
a._isDispose = false;
|
||||
a._isPool = true;
|
||||
return a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放当前实例及其资源。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<OneToManyDictionaryPool<TKey, TValueKey, TValue>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一对多映射关系的字典。每个键都对应一个内部字典,该内部字典将键值映射到相应的值。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">外部字典中的键类型。</typeparam>
|
||||
/// <typeparam name="TValueKey">内部字典中的键类型。</typeparam>
|
||||
/// <typeparam name="TValue">内部字典中的值类型。</typeparam>
|
||||
public class OneToManyDictionary<TKey, TValueKey, TValue> : Dictionary<TKey, Dictionary<TValueKey, TValue>>
|
||||
where TKey : notnull where TValueKey : notnull
|
||||
{
|
||||
private readonly Queue<Dictionary<TValueKey, TValue>> _queue = new Queue<Dictionary<TValueKey, TValue>>();
|
||||
private readonly int _recyclingLimit = 120;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="OneToManyDictionary{TKey, TValueKey, TValue}"/> 实例。
|
||||
/// </summary>
|
||||
public OneToManyDictionary() { }
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="OneToManyDictionary{TKey, TValueKey, TValue}"/> 实例,并指定最大缓存数量。
|
||||
/// </summary>
|
||||
/// <param name="recyclingLimit">
|
||||
/// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
|
||||
/// 2:设置成0不控制数量,全部缓存
|
||||
/// </param>
|
||||
public OneToManyDictionary(int recyclingLimit = 0)
|
||||
{
|
||||
_recyclingLimit = recyclingLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否包含指定的键值对。
|
||||
/// </summary>
|
||||
/// <param name="key">外部字典中的键。</param>
|
||||
/// <param name="valueKey">内部字典中的键。</param>
|
||||
/// <returns>如果包含指定的键值对,则为 true;否则为 false。</returns>
|
||||
public bool Contains(TKey key, TValueKey valueKey)
|
||||
{
|
||||
TryGetValue(key, out var dic);
|
||||
|
||||
return dic != null && dic.ContainsKey(valueKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试获取指定键值对的值。
|
||||
/// </summary>
|
||||
/// <param name="key">外部字典中的键。</param>
|
||||
/// <param name="valueKey">内部字典中的键。</param>
|
||||
/// <param name="value">获取的值,如果操作成功,则为值;否则为默认值。</param>
|
||||
/// <returns>如果操作成功,则为 true;否则为 false。</returns>
|
||||
public bool TryGetValue(TKey key, TValueKey valueKey, out TValue value)
|
||||
{
|
||||
value = default;
|
||||
return TryGetValue(key, out var dic) && dic.TryGetValue(valueKey, out value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定键的第一个值。
|
||||
/// </summary>
|
||||
/// <param name="key">要获取第一个值的键。</param>
|
||||
public TValue First(TKey key)
|
||||
{
|
||||
return !TryGetValue(key, out var dic) ? default : dic.First().Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向字典中添加指定的键值对。
|
||||
/// </summary>
|
||||
/// <param name="key">要添加键值对的键。</param>
|
||||
/// <param name="valueKey">要添加键值对的内部字典键。</param>
|
||||
/// <param name="value">要添加的值。</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字典中移除指定的键值对。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除键值对的键。</param>
|
||||
/// <param name="valueKey">要移除键值对的内部字典键。</param>
|
||||
/// <returns>如果成功移除键值对,则为 true;否则为 false。</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字典中移除指定的键值对。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除键值对的键。</param>
|
||||
/// <param name="valueKey">要移除键值对的内部字典键。</param>
|
||||
/// <param name="value">如果成功移除键值对,则为移除的值;否则为默认值。</param>
|
||||
/// <returns>如果成功移除键值对,则为 true;否则为 false。</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除字典中的指定键及其相关的所有键值对。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除的键。</param>
|
||||
public void RemoveKey(TKey key)
|
||||
{
|
||||
if (!TryGetValue(key, out var dic)) return;
|
||||
|
||||
Remove(key);
|
||||
Recycle(dic);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从对象池中获取一个内部字典实例,如果池中没有,则创建一个新实例。
|
||||
/// </summary>
|
||||
/// <returns>获取的内部字典实例。</returns>
|
||||
private Dictionary<TValueKey, TValue> Fetch()
|
||||
{
|
||||
return _queue.Count <= 0 ? new Dictionary<TValueKey, TValue>() : _queue.Dequeue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将不再使用的内部字典实例放回对象池中,以便后续重用。
|
||||
/// </summary>
|
||||
/// <param name="dic">要放回对象池的内部字典实例。</param>
|
||||
private void Recycle(Dictionary<TValueKey, TValue> dic)
|
||||
{
|
||||
dic.Clear();
|
||||
|
||||
if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return;
|
||||
|
||||
_queue.Enqueue(dic);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空字典中的所有键值对,并将不再使用的内部字典实例放回对象池中。
|
||||
/// </summary>
|
||||
public new void Clear()
|
||||
{
|
||||
foreach (var keyValuePair in this) Recycle(keyValuePair.Value);
|
||||
|
||||
base.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NBC.Pool;
|
||||
|
||||
#pragma warning disable CS8601
|
||||
|
||||
namespace NBC.DataStructure.Dictionary
|
||||
{
|
||||
/// <summary>
|
||||
/// 一对多映射关系的排序字典对象池。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">外部字典中的键类型。</typeparam>
|
||||
/// <typeparam name="TSortedKey">内部字典中的排序键类型。</typeparam>
|
||||
/// <typeparam name="TValue">内部字典中的值类型。</typeparam>
|
||||
public class OneToManySortedDictionaryPool<TKey, TSortedKey, TValue> : OneToManySortedDictionary<TKey, TSortedKey, TValue>, IDisposable, IPool where TKey : notnull where TSortedKey : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="OneToManySortedDictionaryPool{TKey, TSortedKey, TValue}"/> 的实例。
|
||||
/// </summary>
|
||||
/// <returns>新创建的 OneToManySortedDictionaryPool 实例。</returns>
|
||||
public static OneToManySortedDictionaryPool<TKey, TSortedKey, TValue> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var a = Pool<OneToManySortedDictionaryPool<TKey, TSortedKey, TValue>>.Rent();
|
||||
#else
|
||||
var a = MultiThreadPool.Rent<OneToManySortedDictionaryPool<TKey, TSortedKey, TValue>>();
|
||||
#endif
|
||||
a._isDispose = false;
|
||||
a._isPool = true;
|
||||
return a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放当前实例及其资源。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<OneToManySortedDictionaryPool<TKey, TSortedKey, TValue>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一对多映射关系的排序字典。每个外部键映射到一个内部排序字典,该内部排序字典将排序键映射到相应的值。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">外部字典中的键类型。</typeparam>
|
||||
/// <typeparam name="TSortedKey">内部字典中的排序键类型。</typeparam>
|
||||
/// <typeparam name="TValue">内部字典中的值类型。</typeparam>
|
||||
public class
|
||||
OneToManySortedDictionary<TKey, TSortedKey, TValue> : Dictionary<TKey, SortedDictionary<TSortedKey, TValue>>
|
||||
where TSortedKey : notnull where TKey : notnull
|
||||
{
|
||||
/// 缓存队列的回收限制
|
||||
private readonly int _recyclingLimit = 120;
|
||||
/// 缓存队列,用于存储已回收的内部排序字典
|
||||
private readonly Queue<SortedDictionary<TSortedKey, TValue>> _queue = new Queue<SortedDictionary<TSortedKey, TValue>>();
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="OneToManySortedDictionary{TKey, TSortedKey, TValue}"/> 实例。
|
||||
/// </summary>
|
||||
protected OneToManySortedDictionary() { }
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="OneToManySortedDictionary{TKey, TSortedKey, TValue}"/> 实例。设置最大缓存数量
|
||||
/// </summary>
|
||||
/// <param name="recyclingLimit">
|
||||
/// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
|
||||
/// 2:设置成0不控制数量,全部缓存
|
||||
/// </param>
|
||||
public OneToManySortedDictionary(int recyclingLimit)
|
||||
{
|
||||
_recyclingLimit = recyclingLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查字典是否包含指定的外部键。
|
||||
/// </summary>
|
||||
/// <param name="key">要检查的外部键。</param>
|
||||
/// <returns>如果字典包含指定的外部键,则为 true;否则为 false。</returns>
|
||||
public bool Contains(TKey key)
|
||||
{
|
||||
return this.ContainsKey(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查字典是否包含指定的外部键和排序键。
|
||||
/// </summary>
|
||||
/// <param name="key">要检查的外部键。</param>
|
||||
/// <param name="sortedKey">要检查的排序键。</param>
|
||||
/// <returns>如果字典包含指定的外部键和排序键,则为 true;否则为 false。</returns>
|
||||
public bool Contains(TKey key, TSortedKey sortedKey)
|
||||
{
|
||||
return TryGetValue(key, out var dic) && dic.ContainsKey(sortedKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试从字典中获取指定外部键对应的内部排序字典。
|
||||
/// </summary>
|
||||
/// <param name="key">要获取内部排序字典的外部键。</param>
|
||||
/// <param name="dic">获取到的内部排序字典,如果找不到则为 null。</param>
|
||||
/// <returns>如果找到内部排序字典,则为 true;否则为 false。</returns>
|
||||
public new bool TryGetValue(TKey key, out SortedDictionary<TSortedKey, TValue> dic)
|
||||
{
|
||||
return base.TryGetValue(key, out dic);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试从字典中获取指定外部键和排序键对应的值。
|
||||
/// </summary>
|
||||
/// <param name="key">要获取值的外部键。</param>
|
||||
/// <param name="sortedKey">要获取值的排序键。</param>
|
||||
/// <param name="value">获取到的值,如果找不到则为 default。</param>
|
||||
/// <returns>如果找到值,则为 true;否则为 false。</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向字典中添加一个值,关联到指定的外部键和排序键。
|
||||
/// </summary>
|
||||
/// <param name="key">要关联值的外部键。</param>
|
||||
/// <param name="sortedKey">要关联值的排序键。</param>
|
||||
/// <param name="value">要添加的值。</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字典中移除指定外部键和排序键关联的值。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除值的外部键。</param>
|
||||
/// <param name="sortedKey">要移除值的排序键。</param>
|
||||
/// <returns>如果成功移除值,则为 true;否则为 false。</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字典中移除指定外部键及其关联的所有值。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除的外部键。</param>
|
||||
/// <returns>如果成功移除外部键及其关联的所有值,则为 true;否则为 false。</returns>
|
||||
public bool RemoveKey(TKey key)
|
||||
{
|
||||
if (!TryGetValue(key, out var list))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Remove(key);
|
||||
Recycle(list);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从缓存队列中获取一个内部排序字典。
|
||||
/// </summary>
|
||||
/// <returns>一个内部排序字典。</returns>
|
||||
private SortedDictionary<TSortedKey, TValue> Fetch()
|
||||
{
|
||||
return _queue.Count <= 0 ? new SortedDictionary<TSortedKey, TValue>() : _queue.Dequeue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回收一个内部排序字典到缓存队列。
|
||||
/// </summary>
|
||||
/// <param name="dic">要回收的内部排序字典。</param>
|
||||
private void Recycle(SortedDictionary<TSortedKey, TValue> dic)
|
||||
{
|
||||
dic.Clear();
|
||||
|
||||
if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_queue.Enqueue(dic);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空字典以及内部排序字典缓存队列,释放所有资源。
|
||||
/// </summary>
|
||||
protected new void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
_queue.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NBC.Pool;
|
||||
|
||||
namespace NBC.DataStructure.Dictionary
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供一个可以重用的字典类,支持使用对象池管理。
|
||||
/// </summary>
|
||||
/// <typeparam name="TM">字典中键的类型。</typeparam>
|
||||
/// <typeparam name="TN">字典中值的类型。</typeparam>
|
||||
public sealed class ReuseDictionary<TM, TN> : Dictionary<TM, TN>, IDisposable, IPool where TM : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="ReuseDictionary{TM, TN}"/> 实例。
|
||||
/// </summary>
|
||||
/// <returns>新创建的实例。</returns>
|
||||
public static ReuseDictionary<TM, TN> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var entityDictionary = Pool<ReuseDictionary<TM, TN>>.Rent();
|
||||
#else
|
||||
var entityDictionary = MultiThreadPool.Rent<ReuseDictionary<TM, TN>>();
|
||||
#endif
|
||||
entityDictionary._isDispose = false;
|
||||
entityDictionary._isPool = true;
|
||||
return entityDictionary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放实例占用的资源。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<ReuseDictionary<TM, TN>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NBC.Pool;
|
||||
|
||||
namespace NBC.DataStructure.Dictionary
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供一个可以使用对象池管理的排序字典类。
|
||||
/// </summary>
|
||||
/// <typeparam name="TM"></typeparam>
|
||||
/// <typeparam name="TN"></typeparam>
|
||||
public sealed class SortedDictionaryPool<TM, TN> : SortedDictionary<TM, TN>, IDisposable, IPool where TM : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 释放实例占用的资源。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<SortedDictionaryPool<TM, TN>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="SortedDictionaryPool{TM, TN}"/> 实例。
|
||||
/// </summary>
|
||||
/// <returns>新创建的实例。</returns>
|
||||
public static SortedDictionaryPool<TM, TN> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var dictionary = Pool<SortedDictionaryPool<TM, TN>>.Rent();
|
||||
#else
|
||||
var dictionary = MultiThreadPool.Rent<SortedDictionaryPool<TM, TN>>();
|
||||
#endif
|
||||
dictionary._isDispose = false;
|
||||
dictionary._isPool = true;
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 优先队列
|
||||
/// </summary>
|
||||
/// <typeparam name="TElement">节点数据</typeparam>
|
||||
/// <typeparam name="TPriority">排序的类型、</typeparam>
|
||||
public sealed class PriorityQueue<TElement, TPriority> where TPriority : IComparable<TPriority>
|
||||
{
|
||||
private readonly List<PriorityQueueItem<TElement, TPriority>> _heap;
|
||||
|
||||
public PriorityQueue(int initialCapacity = 16)
|
||||
{
|
||||
_heap = new List<PriorityQueueItem<TElement, TPriority>>(initialCapacity);
|
||||
}
|
||||
|
||||
public int Count => _heap.Count;
|
||||
|
||||
public void Enqueue(TElement element, TPriority priority)
|
||||
{
|
||||
_heap.Add(new PriorityQueueItem<TElement, TPriority>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<T>
|
||||
{
|
||||
public T Element { get; set; }
|
||||
public uint Priority { get; set; }
|
||||
|
||||
public PriorityQueueItemUint(T element, uint priority)
|
||||
{
|
||||
Element = element;
|
||||
Priority = priority;
|
||||
}
|
||||
}
|
||||
|
||||
public struct PriorityQueueItem<T, T1>
|
||||
{
|
||||
public T Element { get; }
|
||||
public T1 Priority { get; }
|
||||
|
||||
public PriorityQueueItem(T element, T1 priority)
|
||||
{
|
||||
Element = element;
|
||||
Priority = priority;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<T> where T : IComparable<T>
|
||||
{
|
||||
private readonly List<T> _heap;
|
||||
|
||||
public PriorityQueue(int initialCapacity = 16)
|
||||
{
|
||||
_heap = new List<T>(initialCapacity);
|
||||
}
|
||||
|
||||
public int Count => _heap.Count;
|
||||
|
||||
public void Enqueue(T item)
|
||||
{
|
||||
_heap.Add(item);
|
||||
HeapifyUp(_heap.Count - 1);
|
||||
}
|
||||
|
||||
public T Dequeue()
|
||||
{
|
||||
if (_heap.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("The queue is empty.");
|
||||
}
|
||||
|
||||
var item = _heap[0];
|
||||
var heapCount = _heap.Count - 1;
|
||||
_heap[0] = _heap[heapCount];
|
||||
_heap.RemoveAt(heapCount);
|
||||
HeapifyDown(0);
|
||||
return item;
|
||||
}
|
||||
|
||||
public bool TryDequeue(out T item)
|
||||
{
|
||||
if (_heap.Count == 0)
|
||||
{
|
||||
item = default(T);
|
||||
return false;
|
||||
}
|
||||
|
||||
item = Dequeue();
|
||||
return true;
|
||||
}
|
||||
|
||||
public T Peek()
|
||||
{
|
||||
if (_heap.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("The queue is empty.");
|
||||
}
|
||||
return _heap[0];
|
||||
}
|
||||
|
||||
// ReSharper disable once IdentifierTypo
|
||||
private void HeapifyUp(int index)
|
||||
{
|
||||
while (index > 0)
|
||||
{
|
||||
var parentIndex = (index - 1) / 2;
|
||||
if (_heap[index].CompareTo(_heap[parentIndex]) >= 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].CompareTo(_heap[smallestIndex]) < 0)
|
||||
{
|
||||
smallestIndex = leftChildIndex;
|
||||
}
|
||||
|
||||
if (rightChildIndex <= lastIndex && _heap[rightChildIndex].CompareTo(_heap[smallestIndex]) < 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
190
FantasyNetTest/NBC/Core/DataStructure/SkipTable/SkipTable.cs
Normal file
190
FantasyNetTest/NBC/Core/DataStructure/SkipTable/SkipTable.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
|
||||
#pragma warning disable CS8602 // Dereference of a possibly null reference.
|
||||
#pragma warning disable CS8601 // Possible null reference assignment.
|
||||
#pragma warning disable CS8604 // Possible null reference argument.
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
namespace NBC.DataStructure.SkipTable
|
||||
{
|
||||
/// <summary>
|
||||
/// 跳表数据结构(升序版)
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">跳表中存储的值的类型。</typeparam>
|
||||
public class SkipTable<TValue> : SkipTableBase<TValue>
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建一个新的跳表实例。
|
||||
/// </summary>
|
||||
/// <param name="maxLayer">跳表的最大层数。</param>
|
||||
public SkipTable(int maxLayer = 8) : base(maxLayer) { }
|
||||
|
||||
/// <summary>
|
||||
/// 向跳表中添加一个新节点。
|
||||
/// </summary>
|
||||
/// <param name="sortKey">节点的主排序键。</param>
|
||||
/// <param name="viceKey">节点的副排序键。</param>
|
||||
/// <param name="key">节点的唯一键。</param>
|
||||
/// <param name="value">要添加的值。</param>
|
||||
public override void Add(long sortKey, long viceKey, long key, TValue value)
|
||||
{
|
||||
var rLevel = 1;
|
||||
|
||||
while (rLevel <= MaxLayer && Random.Next(3) == 0)
|
||||
{
|
||||
++rLevel;
|
||||
}
|
||||
|
||||
SkipTableNode<TValue> cur = TopHeader, last = null;
|
||||
|
||||
for (var layer = MaxLayer; layer >= 1; --layer)
|
||||
{
|
||||
// 节点有next节点,且 (next主键 < 插入主键) 或 (next主键 == 插入主键 且 next副键 < 插入副键)
|
||||
while (cur.Right != null && ((cur.Right.SortKey < sortKey) ||
|
||||
(cur.Right.SortKey == sortKey && cur.Right.ViceKey < viceKey)))
|
||||
{
|
||||
cur = cur.Right;
|
||||
}
|
||||
|
||||
if (layer <= rLevel)
|
||||
{
|
||||
var currentRight = cur.Right;
|
||||
|
||||
// 在当前层插入新节点
|
||||
cur.Right = new SkipTableNode<TValue>(sortKey, viceKey, key, value, layer == 1 ? cur.Index + 1 : 0, cur, cur.Right, null);
|
||||
|
||||
if (currentRight != null)
|
||||
{
|
||||
currentRight.Left = cur.Right;
|
||||
}
|
||||
|
||||
if (last != null)
|
||||
{
|
||||
last.Down = cur.Right;
|
||||
}
|
||||
|
||||
if (layer == 1)
|
||||
{
|
||||
// 更新索引信息
|
||||
cur.Right.Index = cur.Index + 1;
|
||||
Node.Add(key, cur.Right);
|
||||
|
||||
SkipTableNode<TValue> v = cur.Right.Right;
|
||||
|
||||
while (v != null)
|
||||
{
|
||||
v.Index++;
|
||||
v = v.Right;
|
||||
}
|
||||
}
|
||||
|
||||
last = cur.Right;
|
||||
}
|
||||
|
||||
cur = cur.Down;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从跳表中移除一个节点。
|
||||
/// </summary>
|
||||
/// <param name="sortKey">节点的主排序键。</param>
|
||||
/// <param name="viceKey">节点的副排序键。</param>
|
||||
/// <param name="key">节点的唯一键。</param>
|
||||
/// <param name="value">被移除的节点的值。</param>
|
||||
/// <returns>如果成功移除节点,则为 true;否则为 false。</returns>
|
||||
public override bool Remove(long sortKey, long viceKey, long key, out TValue value)
|
||||
{
|
||||
value = default;
|
||||
var seen = false;
|
||||
var cur = TopHeader;
|
||||
|
||||
for (var layer = MaxLayer; layer >= 1; --layer)
|
||||
{
|
||||
// 先按照主键查找 再 按副键查找
|
||||
while (cur.Right != null && cur.Right.SortKey < sortKey && cur.Right.Key != key) cur = cur.Right;
|
||||
while (cur.Right != null && (cur.Right.SortKey == sortKey && cur.Right.ViceKey <= viceKey) &&
|
||||
cur.Right.Key != key) cur = cur.Right;
|
||||
|
||||
var isFind = false;
|
||||
var currentCur = cur;
|
||||
SkipTableNode<TValue> removeCur = null;
|
||||
// 如果当前不是要删除的节点、但主键和副键都一样、需要特殊处理下。
|
||||
if (cur.Right != null && cur.Right.Key == key)
|
||||
{
|
||||
isFind = true;
|
||||
removeCur = cur.Right;
|
||||
currentCur = cur;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 先向左查找下
|
||||
var currentNode = cur.Left;
|
||||
while (currentNode != null && currentNode.SortKey == sortKey && currentNode.ViceKey == viceKey)
|
||||
{
|
||||
if (currentNode.Key == key)
|
||||
{
|
||||
isFind = true;
|
||||
removeCur = currentNode;
|
||||
currentCur = currentNode.Left;
|
||||
break;
|
||||
}
|
||||
|
||||
currentNode = currentNode.Left;
|
||||
}
|
||||
|
||||
// 再向右查找下
|
||||
if (!isFind)
|
||||
{
|
||||
currentNode = cur.Right;
|
||||
while (currentNode != null && currentNode.SortKey == sortKey && currentNode.ViceKey == viceKey)
|
||||
{
|
||||
if (currentNode.Key == key)
|
||||
{
|
||||
isFind = true;
|
||||
removeCur = currentNode;
|
||||
currentCur = currentNode.Left;
|
||||
break;
|
||||
}
|
||||
|
||||
currentNode = currentNode.Right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isFind && currentCur != null)
|
||||
{
|
||||
value = removeCur.Value;
|
||||
currentCur.Right = removeCur.Right;
|
||||
|
||||
if (removeCur.Right != null)
|
||||
{
|
||||
removeCur.Right.Left = currentCur;
|
||||
removeCur.Right = null;
|
||||
}
|
||||
|
||||
removeCur.Left = null;
|
||||
removeCur.Down = null;
|
||||
removeCur.Value = default;
|
||||
|
||||
if (layer == 1)
|
||||
{
|
||||
var tempCur = currentCur.Right;
|
||||
while (tempCur != null)
|
||||
{
|
||||
tempCur.Index--;
|
||||
tempCur = tempCur.Right;
|
||||
}
|
||||
|
||||
Node.Remove(removeCur.Key);
|
||||
}
|
||||
|
||||
seen = true;
|
||||
}
|
||||
|
||||
cur = cur.Down;
|
||||
}
|
||||
|
||||
return seen;
|
||||
}
|
||||
}
|
||||
}
|
||||
282
FantasyNetTest/NBC/Core/DataStructure/SkipTable/SkipTableBase.cs
Normal file
282
FantasyNetTest/NBC/Core/DataStructure/SkipTable/SkipTableBase.cs
Normal file
@@ -0,0 +1,282 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using NBC.DataStructure.Collection;
|
||||
|
||||
#pragma warning disable CS8601
|
||||
#pragma warning disable CS8603
|
||||
#pragma warning disable CS8625
|
||||
#pragma warning disable CS8604
|
||||
|
||||
namespace NBC.DataStructure.SkipTable
|
||||
{
|
||||
/// <summary>
|
||||
/// 抽象的跳表基类,提供跳表的基本功能和操作。
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">跳表中存储的值的类型。</typeparam>
|
||||
public abstract class SkipTableBase<TValue> : IEnumerable<SkipTableNode<TValue>>
|
||||
{
|
||||
/// <summary>
|
||||
/// 跳表的最大层数
|
||||
/// </summary>
|
||||
public readonly int MaxLayer;
|
||||
/// <summary>
|
||||
/// 跳表的顶部头节点
|
||||
/// </summary>
|
||||
public readonly SkipTableNode<TValue> TopHeader;
|
||||
/// <summary>
|
||||
/// 跳表的底部头节点
|
||||
/// </summary>
|
||||
public SkipTableNode<TValue> BottomHeader;
|
||||
/// <summary>
|
||||
/// 跳表中节点的数量,使用了 Node 字典的计数
|
||||
/// </summary>
|
||||
public int Count => Node.Count;
|
||||
/// <summary>
|
||||
/// 用于生成随机数的随机数生成器
|
||||
/// </summary>
|
||||
protected readonly Random Random = new Random();
|
||||
/// <summary>
|
||||
/// 存储跳表节点的字典
|
||||
/// </summary>
|
||||
protected readonly Dictionary<long, SkipTableNode<TValue>> Node = new();
|
||||
/// <summary>
|
||||
/// 用于辅助反向查找的栈
|
||||
/// </summary>
|
||||
protected readonly Stack<SkipTableNode<TValue>> AntiFindStack = new Stack<SkipTableNode<TValue>>();
|
||||
|
||||
/// <summary>
|
||||
/// 初始化一个新的跳表实例。
|
||||
/// </summary>
|
||||
/// <param name="maxLayer">跳表的最大层数,默认为 8。</param>
|
||||
protected SkipTableBase(int maxLayer = 8)
|
||||
{
|
||||
MaxLayer = maxLayer;
|
||||
var cur = TopHeader = new SkipTableNode<TValue>(long.MinValue, 0, 0, default, 0, null, null, null);
|
||||
|
||||
for (var layer = MaxLayer - 1; layer >= 1; --layer)
|
||||
{
|
||||
cur.Down = new SkipTableNode<TValue>(long.MinValue, 0, 0, default, 0, null, null, null);
|
||||
cur = cur.Down;
|
||||
}
|
||||
|
||||
BottomHeader = cur;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定键的节点的值,若不存在则返回默认值。
|
||||
/// </summary>
|
||||
/// <param name="key">要查找的键。</param>
|
||||
public TValue this[long key] => !TryGetValueByKey(key, out TValue value) ? default : value;
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定键的节点在跳表中的排名。
|
||||
/// </summary>
|
||||
/// <param name="key">要查找的键。</param>
|
||||
/// <returns>节点的排名。</returns>
|
||||
public int GetRanking(long key)
|
||||
{
|
||||
if (!Node.TryGetValue(key, out var node))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return node.Index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定键的反向排名,即在比该键更大的节点中的排名。
|
||||
/// </summary>
|
||||
/// <param name="key">要查找的键。</param>
|
||||
/// <returns>反向排名。</returns>
|
||||
public int GetAntiRanking(long key)
|
||||
{
|
||||
var ranking = GetRanking(key);
|
||||
|
||||
if (ranking == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Count + 1 - ranking;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试通过键获取节点的值。
|
||||
/// </summary>
|
||||
/// <param name="key">要查找的键。</param>
|
||||
/// <param name="value">获取到的节点的值,如果键不存在则为默认值。</param>
|
||||
/// <returns>是否成功获取节点的值。</returns>
|
||||
public bool TryGetValueByKey(long key, out TValue value)
|
||||
{
|
||||
if (!Node.TryGetValue(key, out var node))
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = node.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试通过键获取节点。
|
||||
/// </summary>
|
||||
/// <param name="key">要查找的键。</param>
|
||||
/// <param name="node">获取到的节点,如果键不存在则为 <c>null</c>。</param>
|
||||
/// <returns>是否成功获取节点。</returns>
|
||||
public bool TryGetNodeByKey(long key, out SkipTableNode<TValue> node)
|
||||
{
|
||||
if (Node.TryGetValue(key, out node))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在跳表中查找节点,返回从起始位置到结束位置的节点列表。
|
||||
/// </summary>
|
||||
/// <param name="start">起始位置的排名。</param>
|
||||
/// <param name="end">结束位置的排名。</param>
|
||||
/// <param name="list">用于存储节点列表的 <see cref="ListPool{T}"/> 实例。</param>
|
||||
public void Find(int start, int end, ListPool<SkipTableNode<TValue>> list)
|
||||
{
|
||||
var cur = BottomHeader;
|
||||
var count = end - start;
|
||||
|
||||
for (var i = 0; i < start; i++)
|
||||
{
|
||||
cur = cur.Right;
|
||||
}
|
||||
|
||||
for (var i = 0; i <= count; i++)
|
||||
{
|
||||
if (cur == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
list.Add(cur);
|
||||
cur = cur.Right;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在跳表中进行反向查找节点,返回从结束位置到起始位置的节点列表。
|
||||
/// </summary>
|
||||
/// <param name="start">结束位置的排名。</param>
|
||||
/// <param name="end">起始位置的排名。</param>
|
||||
/// <param name="list">用于存储节点列表的 <see cref="ListPool{T}"/> 实例。</param>
|
||||
public void AntiFind(int start, int end, ListPool<SkipTableNode<TValue>> list)
|
||||
{
|
||||
var cur = BottomHeader;
|
||||
start = Count + 1 - start;
|
||||
end = start - end;
|
||||
|
||||
for (var i = 0; i < start; i++)
|
||||
{
|
||||
cur = cur.Right;
|
||||
|
||||
if (cur == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (i < end)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AntiFindStack.Push(cur);
|
||||
}
|
||||
|
||||
while (AntiFindStack.TryPop(out var node))
|
||||
{
|
||||
list.Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取跳表中最后一个节点的值。
|
||||
/// </summary>
|
||||
/// <returns>最后一个节点的值。</returns>
|
||||
public TValue GetLastValue()
|
||||
{
|
||||
var cur = TopHeader;
|
||||
|
||||
while (cur.Right != null || cur.Down != null)
|
||||
{
|
||||
while (cur.Right != null)
|
||||
{
|
||||
cur = cur.Right;
|
||||
}
|
||||
|
||||
if (cur.Down != null)
|
||||
{
|
||||
cur = cur.Down;
|
||||
}
|
||||
}
|
||||
|
||||
return cur.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除跳表中指定键的节点。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除的节点的键。</param>
|
||||
/// <returns>移除是否成功。</returns>
|
||||
public bool Remove(long key)
|
||||
{
|
||||
if (!Node.TryGetValue(key, out var node))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Remove(node.SortKey, node.ViceKey, key, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向跳表中添加节点。
|
||||
/// </summary>
|
||||
/// <param name="sortKey">节点的排序键。</param>
|
||||
/// <param name="viceKey">节点的副键。</param>
|
||||
/// <param name="key">节点的键。</param>
|
||||
/// <param name="value">节点的值。</param>
|
||||
public abstract void Add(long sortKey, long viceKey, long key, TValue value);
|
||||
|
||||
/// <summary>
|
||||
/// 从跳表中移除指定键的节点。
|
||||
/// </summary>
|
||||
/// <param name="sortKey">节点的排序键。</param>
|
||||
/// <param name="viceKey">节点的副键。</param>
|
||||
/// <param name="key">节点的键。</param>
|
||||
/// <param name="value">被移除的节点的值。</param>
|
||||
/// <returns>移除是否成功。</returns>
|
||||
public abstract bool Remove(long sortKey, long viceKey, long key, out TValue value);
|
||||
|
||||
/// <summary>
|
||||
/// 返回一个枚举器,用于遍历跳表中的节点。
|
||||
/// </summary>
|
||||
/// <returns>一个可用于遍历跳表节点的枚举器。</returns>
|
||||
public IEnumerator<SkipTableNode<TValue>> GetEnumerator()
|
||||
{
|
||||
var cur = BottomHeader.Right;
|
||||
while (cur != null)
|
||||
{
|
||||
yield return cur;
|
||||
cur = cur.Right;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回一个非泛型枚举器,用于遍历跳表中的节点。
|
||||
/// </summary>
|
||||
/// <returns>一个非泛型枚举器,可用于遍历跳表节点。</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
188
FantasyNetTest/NBC/Core/DataStructure/SkipTable/SkipTableDesc.cs
Normal file
188
FantasyNetTest/NBC/Core/DataStructure/SkipTable/SkipTableDesc.cs
Normal file
@@ -0,0 +1,188 @@
|
||||
|
||||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
#pragma warning disable CS8604 // Possible null reference argument.
|
||||
#pragma warning disable CS8602 // Dereference of a possibly null reference.
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
#pragma warning disable CS8601 // Possible null reference assignment.
|
||||
namespace NBC.DataStructure.SkipTable
|
||||
{
|
||||
/// <summary>
|
||||
/// 跳表降序版,用于存储降序排列的数据。
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">存储的值的类型。</typeparam>
|
||||
public class SkipTableDesc<TValue> : SkipTableBase<TValue>
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化跳表降序版的新实例。
|
||||
/// </summary>
|
||||
/// <param name="maxLayer">跳表的最大层数,默认为 8。</param>
|
||||
public SkipTableDesc(int maxLayer = 8) : base(maxLayer) { }
|
||||
|
||||
/// <summary>
|
||||
/// 向跳表中添加一个节点,根据降序规则进行插入。
|
||||
/// </summary>
|
||||
/// <param name="sortKey">排序主键。</param>
|
||||
/// <param name="viceKey">副键。</param>
|
||||
/// <param name="key">键。</param>
|
||||
/// <param name="value">值。</param>
|
||||
public override void Add(long sortKey, long viceKey, long key, TValue value)
|
||||
{
|
||||
var rLevel = 1;
|
||||
|
||||
while (rLevel <= MaxLayer && Random.Next(3) == 0)
|
||||
{
|
||||
++rLevel;
|
||||
}
|
||||
|
||||
SkipTableNode<TValue> cur = TopHeader, last = null;
|
||||
|
||||
for (var layer = MaxLayer; layer >= 1; --layer)
|
||||
{
|
||||
// 节点有next节点,且 (next主键 > 插入主键) 或 (next主键 == 插入主键 且 next副键 > 插入副键)
|
||||
while (cur.Right != null && ((cur.Right.SortKey > sortKey) ||
|
||||
(cur.Right.SortKey == sortKey && cur.Right.ViceKey > viceKey)))
|
||||
{
|
||||
cur = cur.Right;
|
||||
}
|
||||
|
||||
if (layer <= rLevel)
|
||||
{
|
||||
var currentRight = cur.Right;
|
||||
cur.Right = new SkipTableNode<TValue>(sortKey, viceKey, key, value,
|
||||
layer == 1 ? cur.Index + 1 : 0, cur, cur.Right, null);
|
||||
|
||||
if (currentRight != null)
|
||||
{
|
||||
currentRight.Left = cur.Right;
|
||||
}
|
||||
|
||||
if (last != null)
|
||||
{
|
||||
last.Down = cur.Right;
|
||||
}
|
||||
|
||||
if (layer == 1)
|
||||
{
|
||||
cur.Right.Index = cur.Index + 1;
|
||||
Node.Add(key, cur.Right);
|
||||
|
||||
SkipTableNode<TValue> v = cur.Right.Right;
|
||||
|
||||
while (v != null)
|
||||
{
|
||||
v.Index++;
|
||||
v = v.Right;
|
||||
}
|
||||
}
|
||||
|
||||
last = cur.Right;
|
||||
}
|
||||
|
||||
cur = cur.Down;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从跳表中移除一个节点,根据降序规则进行移除。
|
||||
/// </summary>
|
||||
/// <param name="sortKey">排序主键。</param>
|
||||
/// <param name="viceKey">副键。</param>
|
||||
/// <param name="key">键。</param>
|
||||
/// <param name="value">移除的节点值。</param>
|
||||
/// <returns>如果成功移除节点,则返回 true,否则返回 false。</returns>
|
||||
public override bool Remove(long sortKey, long viceKey, long key, out TValue value)
|
||||
{
|
||||
value = default;
|
||||
var seen = false;
|
||||
var cur = TopHeader;
|
||||
|
||||
for (var layer = MaxLayer; layer >= 1; --layer)
|
||||
{
|
||||
// 先按照主键查找 再 按副键查找
|
||||
while (cur.Right != null && cur.Right.SortKey > sortKey && cur.Right.Key != key) cur = cur.Right;
|
||||
while (cur.Right != null && (cur.Right.SortKey == sortKey && cur.Right.ViceKey >= viceKey) &&
|
||||
cur.Right.Key != key) cur = cur.Right;
|
||||
|
||||
var isFind = false;
|
||||
var currentCur = cur;
|
||||
SkipTableNode<TValue> removeCur = null;
|
||||
// 如果当前不是要删除的节点、但主键和副键都一样、需要特殊处理下。
|
||||
if (cur.Right != null && cur.Right.Key == key)
|
||||
{
|
||||
isFind = true;
|
||||
removeCur = cur.Right;
|
||||
currentCur = cur;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 先向左查找下
|
||||
var currentNode = cur.Left;
|
||||
while (currentNode != null && currentNode.SortKey == sortKey && currentNode.ViceKey == viceKey)
|
||||
{
|
||||
if (currentNode.Key == key)
|
||||
{
|
||||
isFind = true;
|
||||
removeCur = currentNode;
|
||||
currentCur = currentNode.Left;
|
||||
break;
|
||||
}
|
||||
|
||||
currentNode = currentNode.Left;
|
||||
}
|
||||
|
||||
// 再向右查找下
|
||||
if (!isFind)
|
||||
{
|
||||
currentNode = cur.Right;
|
||||
while (currentNode != null && currentNode.SortKey == sortKey && currentNode.ViceKey == viceKey)
|
||||
{
|
||||
if (currentNode.Key == key)
|
||||
{
|
||||
isFind = true;
|
||||
removeCur = currentNode;
|
||||
currentCur = currentNode.Left;
|
||||
break;
|
||||
}
|
||||
|
||||
currentNode = currentNode.Right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isFind && currentCur != null)
|
||||
{
|
||||
value = removeCur.Value;
|
||||
currentCur.Right = removeCur.Right;
|
||||
|
||||
if (removeCur.Right != null)
|
||||
{
|
||||
removeCur.Right.Left = currentCur;
|
||||
removeCur.Right = null;
|
||||
}
|
||||
|
||||
removeCur.Left = null;
|
||||
removeCur.Down = null;
|
||||
removeCur.Value = default;
|
||||
|
||||
if (layer == 1)
|
||||
{
|
||||
var tempCur = currentCur.Right;
|
||||
while (tempCur != null)
|
||||
{
|
||||
tempCur.Index--;
|
||||
tempCur = tempCur.Right;
|
||||
}
|
||||
|
||||
Node.Remove(removeCur.Key);
|
||||
}
|
||||
|
||||
seen = true;
|
||||
}
|
||||
|
||||
cur = cur.Down;
|
||||
}
|
||||
|
||||
return seen;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
namespace NBC.DataStructure.SkipTable
|
||||
{
|
||||
/// <summary>
|
||||
/// 跳跃表节点。
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">节点的值的类型。</typeparam>
|
||||
public class SkipTableNode<TValue>
|
||||
{
|
||||
/// <summary>
|
||||
/// 节点在跳跃表中的索引。
|
||||
/// </summary>
|
||||
public int Index;
|
||||
/// <summary>
|
||||
/// 节点的主键。
|
||||
/// </summary>
|
||||
public long Key;
|
||||
/// <summary>
|
||||
/// 节点的排序键。
|
||||
/// </summary>
|
||||
public long SortKey;
|
||||
/// <summary>
|
||||
/// 节点的副键。
|
||||
/// </summary>
|
||||
public long ViceKey;
|
||||
/// <summary>
|
||||
/// 节点存储的值。
|
||||
/// </summary>
|
||||
public TValue Value;
|
||||
/// <summary>
|
||||
/// 指向左侧节点的引用。
|
||||
/// </summary>
|
||||
public SkipTableNode<TValue> Left;
|
||||
/// <summary>
|
||||
/// 指向右侧节点的引用。
|
||||
/// </summary>
|
||||
public SkipTableNode<TValue> Right;
|
||||
/// <summary>
|
||||
/// 指向下一层节点的引用。
|
||||
/// </summary>
|
||||
public SkipTableNode<TValue> Down;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化跳跃表节点的新实例。
|
||||
/// </summary>
|
||||
/// <param name="sortKey">节点的排序键。</param>
|
||||
/// <param name="viceKey">节点的副键。</param>
|
||||
/// <param name="key">节点的主键。</param>
|
||||
/// <param name="value">节点存储的值。</param>
|
||||
/// <param name="index">节点在跳跃表中的索引。</param>
|
||||
/// <param name="l">指向左侧节点的引用。</param>
|
||||
/// <param name="r">指向右侧节点的引用。</param>
|
||||
/// <param name="d">指向下一层节点的引用。</param>
|
||||
public SkipTableNode(long sortKey, long viceKey, long key, TValue value, int index,
|
||||
SkipTableNode<TValue> l,
|
||||
SkipTableNode<TValue> r,
|
||||
SkipTableNode<TValue> d)
|
||||
{
|
||||
Left = l;
|
||||
Right = r;
|
||||
Down = d;
|
||||
Value = value;
|
||||
Key = key;
|
||||
Index = index;
|
||||
SortKey = sortKey;
|
||||
ViceKey = viceKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NBC.Pool;
|
||||
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
namespace NBC.Async
|
||||
{
|
||||
/// <summary>
|
||||
/// 协程锁专用的对象池
|
||||
/// </summary>
|
||||
public sealed class CoroutineLockPool : PoolCore<CoroutineLock>
|
||||
{
|
||||
/// <summary>
|
||||
/// 协程锁专用的对象池的构造函数
|
||||
/// </summary>
|
||||
public CoroutineLockPool() : base(2000) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 协程锁
|
||||
/// </summary>
|
||||
public sealed class CoroutineLock : IPool, IDisposable
|
||||
{
|
||||
private Scene _scene;
|
||||
private CoroutineLockComponent _coroutineLockComponent;
|
||||
private readonly Dictionary<long, CoroutineLockQueue> _queue = new Dictionary<long, CoroutineLockQueue>();
|
||||
/// <summary>
|
||||
/// 表示是否是对象池中创建的
|
||||
/// </summary>
|
||||
private bool _isPool;
|
||||
/// <summary>
|
||||
/// 协程锁的类型
|
||||
/// </summary>
|
||||
public long CoroutineLockType { get; private set; }
|
||||
|
||||
internal void Initialize(CoroutineLockComponent coroutineLockComponent, ref long coroutineLockType)
|
||||
{
|
||||
_scene = coroutineLockComponent.Scene;
|
||||
CoroutineLockType = coroutineLockType;
|
||||
_coroutineLockComponent = coroutineLockComponent;
|
||||
}
|
||||
/// <summary>
|
||||
/// 销毁协程锁,如果调用了该方法,所有使用当前协程锁等待的逻辑会按照顺序释放锁。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var (_, coroutineLockQueue) in _queue)
|
||||
{
|
||||
while (TryCoroutineLockQueueDequeue(coroutineLockQueue)) { }
|
||||
}
|
||||
|
||||
_queue.Clear();
|
||||
_scene = null;
|
||||
CoroutineLockType = 0;
|
||||
_coroutineLockComponent = null;
|
||||
}
|
||||
/// <summary>
|
||||
/// 等待上一个任务完成
|
||||
/// </summary>
|
||||
/// <param name="coroutineLockQueueKey">需要等待的Id</param>
|
||||
/// <param name="tag">用于查询协程锁的标记,可不传入,只有在超时的时候排查是哪个锁超时时使用</param>
|
||||
/// <param name="timeOut">等待多久会超时,当到达设定的时候会把当前锁给按照超时处理</param>
|
||||
/// <returns></returns>
|
||||
public async FTask<WaitCoroutineLock> Wait(long coroutineLockQueueKey, string tag = null, int timeOut = 30000)
|
||||
{
|
||||
var waitCoroutineLock = _coroutineLockComponent.WaitCoroutineLockPool.Rent(this, ref coroutineLockQueueKey, tag, timeOut);
|
||||
|
||||
if (!_queue.TryGetValue(coroutineLockQueueKey, out var queue))
|
||||
{
|
||||
queue = _coroutineLockComponent.CoroutineLockQueuePool.Rent();
|
||||
_queue.Add(coroutineLockQueueKey, queue);
|
||||
return waitCoroutineLock;
|
||||
}
|
||||
|
||||
queue.Enqueue(waitCoroutineLock);
|
||||
return await waitCoroutineLock.Tcs;
|
||||
}
|
||||
/// <summary>
|
||||
/// 按照先入先出的顺序,释放最早的一个协程锁
|
||||
/// </summary>
|
||||
/// <param name="coroutineLockQueueKey"></param>
|
||||
public void Release(long coroutineLockQueueKey)
|
||||
{
|
||||
if (!_queue.TryGetValue(coroutineLockQueueKey, out var coroutineLockQueue))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryCoroutineLockQueueDequeue(coroutineLockQueue))
|
||||
{
|
||||
_queue.Remove(coroutineLockQueueKey);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryCoroutineLockQueueDequeue(CoroutineLockQueue coroutineLockQueue)
|
||||
{
|
||||
if (!coroutineLockQueue.TryDequeue(out var waitCoroutineLock))
|
||||
{
|
||||
_coroutineLockComponent.CoroutineLockQueuePool.Return(coroutineLockQueue);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (waitCoroutineLock.TimerId != 0)
|
||||
{
|
||||
_scene.TimerComponent.Net.Remove(waitCoroutineLock.TimerId);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 放到下一帧执行,如果不这样会导致逻辑的顺序不正常。
|
||||
_scene.ThreadSynchronizationContext.Post(waitCoroutineLock.SetResult);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Error in disposing CoroutineLock: {e}");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2fe8fb8f49ad4565b47c840fb690d1f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,100 @@
|
||||
using System.Collections.Generic;
|
||||
using NBC.Entitas;
|
||||
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
|
||||
namespace NBC.Async
|
||||
{
|
||||
/// <summary>
|
||||
/// 协程锁组件
|
||||
/// </summary>
|
||||
public class CoroutineLockComponent : Entity
|
||||
{
|
||||
private long _lockId;
|
||||
private CoroutineLockPool _coroutineLockPool;
|
||||
internal WaitCoroutineLockPool WaitCoroutineLockPool { get; private set; }
|
||||
internal CoroutineLockQueuePool CoroutineLockQueuePool { get; private set; }
|
||||
private readonly Dictionary<long, CoroutineLock> _coroutineLocks = new Dictionary<long, CoroutineLock>();
|
||||
internal CoroutineLockComponent Initialize()
|
||||
{
|
||||
_coroutineLockPool = new CoroutineLockPool();
|
||||
CoroutineLockQueuePool = new CoroutineLockQueuePool();
|
||||
WaitCoroutineLockPool = new WaitCoroutineLockPool(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
internal long LockId => ++_lockId;
|
||||
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
public override void Dispose()
|
||||
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_lockId = 0;
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的协程锁
|
||||
/// 使用这个方法创建的协程锁,需要手动释放管理CoroutineLock。
|
||||
/// 不会再CoroutineLockComponent理进行管理。
|
||||
/// </summary>
|
||||
/// <param name="coroutineLockType"></param>
|
||||
/// <returns></returns>
|
||||
public CoroutineLock Create(long coroutineLockType)
|
||||
{
|
||||
var coroutineLock = _coroutineLockPool.Rent();
|
||||
coroutineLock.Initialize(this, ref coroutineLockType);
|
||||
return coroutineLock;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求一个协程锁。
|
||||
/// 使用这个方法创建的协程锁,会自动释放CoroutineLockQueueType。
|
||||
/// </summary>
|
||||
/// <param name="coroutineLockType">锁类型</param>
|
||||
/// <param name="coroutineLockQueueKey">锁队列Id</param>
|
||||
/// <param name="tag">当某些锁超时,需要一个标记来方便排查问题,正常的情况下这个默认为null就可以。</param>
|
||||
/// <param name="time">设置锁的超时时间,让超过设置的时间会触发超时,保证锁不会因为某一个锁一直不解锁导致卡住的问题。</param>
|
||||
/// <returns>
|
||||
/// 返回的WaitCoroutineLock通过Dispose来解除这个锁、建议用using来保住这个锁。
|
||||
/// 也可以返回的WaitCoroutineLock通过CoroutineLockComponent.UnLock来解除这个锁。
|
||||
/// </returns>
|
||||
public FTask<WaitCoroutineLock> Wait(long coroutineLockType, long coroutineLockQueueKey, string tag = null, int time = 30000)
|
||||
{
|
||||
if (!_coroutineLocks.TryGetValue(coroutineLockType, out var coroutineLock))
|
||||
{
|
||||
coroutineLock = _coroutineLockPool.Rent();
|
||||
coroutineLock.Initialize(this, ref coroutineLockType);
|
||||
_coroutineLocks.Add(coroutineLockType, coroutineLock);
|
||||
}
|
||||
|
||||
return coroutineLock.Wait(coroutineLockQueueKey, tag, time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解除一个协程锁。
|
||||
/// </summary>
|
||||
/// <param name="coroutineLockType"></param>
|
||||
/// <param name="coroutineLockQueueKey"></param>
|
||||
public void Release(int coroutineLockType, long coroutineLockQueueKey)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_coroutineLocks.TryGetValue(coroutineLockType, out var coroutineLock))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
coroutineLock.Release(coroutineLockQueueKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8abfde11ad4d344eda0bf16b249a0dae
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,35 @@
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
using System.Collections.Generic;
|
||||
using NBC.Pool;
|
||||
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
namespace NBC.Async
|
||||
{
|
||||
internal sealed class CoroutineLockQueuePool : PoolCore<CoroutineLockQueue>
|
||||
{
|
||||
public CoroutineLockQueuePool() : base(2000) { }
|
||||
}
|
||||
|
||||
internal sealed class CoroutineLockQueue : Queue<WaitCoroutineLock>, IPool
|
||||
{
|
||||
private bool _isPool;
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1a15c49a7b7b488c8aac3d7720d506e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using NBC.Event;
|
||||
using NBC.Pool;
|
||||
|
||||
#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.Async
|
||||
{
|
||||
internal sealed class WaitCoroutineLockPool : PoolCore<WaitCoroutineLock>
|
||||
{
|
||||
private readonly Scene _scene;
|
||||
private readonly CoroutineLockComponent _coroutineLockComponent;
|
||||
|
||||
public WaitCoroutineLockPool(CoroutineLockComponent coroutineLockComponent) : base(2000)
|
||||
{
|
||||
_scene = coroutineLockComponent.Scene;
|
||||
_coroutineLockComponent = coroutineLockComponent;
|
||||
}
|
||||
|
||||
public WaitCoroutineLock Rent(CoroutineLock coroutineLock, ref long coroutineLockQueueKey, string tag = null, int timeOut = 30000)
|
||||
{
|
||||
var timerId = 0L;
|
||||
var lockId = _coroutineLockComponent.LockId;
|
||||
var waitCoroutineLock = _coroutineLockComponent.WaitCoroutineLockPool.Rent();
|
||||
|
||||
if (timeOut > 0)
|
||||
{
|
||||
timerId = _scene.TimerComponent.Net.OnceTimer(timeOut, new CoroutineLockTimeout(ref lockId, waitCoroutineLock));
|
||||
}
|
||||
|
||||
waitCoroutineLock.Initialize(coroutineLock, this, ref coroutineLockQueueKey, ref timerId, ref lockId, tag);
|
||||
return waitCoroutineLock;
|
||||
}
|
||||
}
|
||||
|
||||
internal struct CoroutineLockTimeout
|
||||
{
|
||||
public readonly long LockId;
|
||||
public readonly WaitCoroutineLock WaitCoroutineLock;
|
||||
|
||||
public CoroutineLockTimeout(ref long lockId, WaitCoroutineLock waitCoroutineLock)
|
||||
{
|
||||
LockId = lockId;
|
||||
WaitCoroutineLock = waitCoroutineLock;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class OnCoroutineLockTimeout : EventSystem<CoroutineLockTimeout>
|
||||
{
|
||||
protected override void Handler(CoroutineLockTimeout self)
|
||||
{
|
||||
var selfWaitCoroutineLock = self.WaitCoroutineLock;
|
||||
|
||||
if (self.LockId != selfWaitCoroutineLock.LockId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Error($"coroutine lock timeout CoroutineLockQueueType:{selfWaitCoroutineLock.CoroutineLock.CoroutineLockType} Key:{selfWaitCoroutineLock.CoroutineLockQueueKey} Tag:{selfWaitCoroutineLock.Tag}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一个协程锁的实例,用户可以用过这个手动释放锁
|
||||
/// </summary>
|
||||
public sealed class WaitCoroutineLock : IPool, IDisposable
|
||||
{
|
||||
private bool _isPool;
|
||||
internal string Tag { get; private set; }
|
||||
internal long LockId { get; private set; }
|
||||
internal long TimerId { get; private set; }
|
||||
internal long CoroutineLockQueueKey { get; private set; }
|
||||
internal CoroutineLock CoroutineLock { get; private set; }
|
||||
|
||||
private bool _isSetResult;
|
||||
private FTask<WaitCoroutineLock> _tcs;
|
||||
private WaitCoroutineLockPool _waitCoroutineLockPool;
|
||||
internal void Initialize(CoroutineLock coroutineLock, WaitCoroutineLockPool waitCoroutineLockPool, ref long coroutineLockQueueKey, ref long timerId, ref long lockId, string tag)
|
||||
{
|
||||
Tag = tag;
|
||||
LockId = lockId;
|
||||
TimerId = timerId;
|
||||
CoroutineLock = coroutineLock;
|
||||
CoroutineLockQueueKey = coroutineLockQueueKey;
|
||||
_waitCoroutineLockPool = waitCoroutineLockPool;
|
||||
}
|
||||
/// <summary>
|
||||
/// 释放协程锁
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (LockId == 0)
|
||||
{
|
||||
Log.Error("WaitCoroutineLock is already disposed");
|
||||
return;
|
||||
}
|
||||
|
||||
CoroutineLock.Release(CoroutineLockQueueKey);
|
||||
|
||||
_tcs = null;
|
||||
Tag = null;
|
||||
LockId = 0;
|
||||
TimerId = 0;
|
||||
_isSetResult = false;
|
||||
CoroutineLockQueueKey = 0;
|
||||
_waitCoroutineLockPool.Return(this);
|
||||
CoroutineLock = null;
|
||||
_waitCoroutineLockPool = null;
|
||||
}
|
||||
|
||||
internal FTask<WaitCoroutineLock> Tcs
|
||||
{
|
||||
get { return _tcs ??= FTask<WaitCoroutineLock>.Create(); }
|
||||
}
|
||||
|
||||
internal void SetResult()
|
||||
{
|
||||
if (_isSetResult)
|
||||
{
|
||||
Log.Error("WaitCoroutineLock is already SetResult");
|
||||
return;
|
||||
}
|
||||
|
||||
_isSetResult = true;
|
||||
Tcs.SetResult(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c6d5307f677ba48f891c12ac183a0128
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
472
FantasyNetTest/NBC/Core/Entitas/Component/EntityComponent.cs
Normal file
472
FantasyNetTest/NBC/Core/Entitas/Component/EntityComponent.cs
Normal file
@@ -0,0 +1,472 @@
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
using NBC.Entitas;
|
||||
using NBC.Assembly;
|
||||
using NBC.Async;
|
||||
using NBC.DataStructure.Collection;
|
||||
using NBC.Entitas.Interface;
|
||||
using NBC.Helper;
|
||||
|
||||
#pragma warning disable CS8602 // Dereference of a possibly null reference.
|
||||
#pragma warning disable CS8765 // Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes).
|
||||
|
||||
#pragma warning disable CS8604 // Possible null reference argument.
|
||||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
namespace NBC.Entitas
|
||||
{
|
||||
internal sealed class UpdateQueueInfo
|
||||
{
|
||||
public bool IsStop;
|
||||
public readonly Type Type;
|
||||
public readonly long RunTimeId;
|
||||
|
||||
public UpdateQueueInfo(Type type, long runTimeId)
|
||||
{
|
||||
Type = type;
|
||||
IsStop = false;
|
||||
RunTimeId = runTimeId;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class FrameUpdateQueueInfo
|
||||
{
|
||||
public readonly Type Type;
|
||||
public readonly long RunTimeId;
|
||||
|
||||
public FrameUpdateQueueInfo(Type type, long runTimeId)
|
||||
{
|
||||
Type = type;
|
||||
RunTimeId = runTimeId;
|
||||
}
|
||||
}
|
||||
|
||||
internal struct CustomEntitiesSystemKey : IEquatable<CustomEntitiesSystemKey>
|
||||
{
|
||||
public int CustomEventType { get; }
|
||||
public Type EntitiesType { get; }
|
||||
public CustomEntitiesSystemKey(int customEventType, Type entitiesType)
|
||||
{
|
||||
CustomEventType = customEventType;
|
||||
EntitiesType = entitiesType;
|
||||
}
|
||||
public bool Equals(CustomEntitiesSystemKey other)
|
||||
{
|
||||
return CustomEventType == other.CustomEventType && EntitiesType == other.EntitiesType;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is CustomEntitiesSystemKey other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(CustomEventType, EntitiesType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entity管理组件
|
||||
/// </summary>
|
||||
public sealed class EntityComponent : Entity, ISceneUpdate, IAssembly
|
||||
{
|
||||
private readonly OneToManyList<long, Type> _assemblyList = new();
|
||||
private readonly OneToManyList<long, Type> _assemblyHashCodes = new();
|
||||
|
||||
private readonly Dictionary<Type, IAwakeSystem> _awakeSystems = new();
|
||||
private readonly Dictionary<Type, IUpdateSystem> _updateSystems = new();
|
||||
private readonly Dictionary<Type, IDestroySystem> _destroySystems = new();
|
||||
private readonly Dictionary<Type, IDeserializeSystem> _deserializeSystems = new();
|
||||
private readonly Dictionary<Type, IFrameUpdateSystem> _frameUpdateSystem = new();
|
||||
|
||||
private readonly OneToManyList<long, CustomEntitiesSystemKey> _assemblyCustomSystemList = new();
|
||||
private readonly Dictionary<CustomEntitiesSystemKey, ICustomEntitiesSystem> _customEntitiesSystems = new Dictionary<CustomEntitiesSystemKey, ICustomEntitiesSystem>();
|
||||
|
||||
private readonly Dictionary<Type, long> _hashCodes = new Dictionary<Type, long>();
|
||||
private readonly Queue<UpdateQueueInfo> _updateQueue = new Queue<UpdateQueueInfo>();
|
||||
private readonly Queue<FrameUpdateQueueInfo> _frameUpdateQueue = new Queue<FrameUpdateQueueInfo>();
|
||||
private readonly Dictionary<long, UpdateQueueInfo> _updateQueueDic = new Dictionary<long, UpdateQueueInfo>();
|
||||
|
||||
internal async FTask<EntityComponent> Initialize()
|
||||
{
|
||||
await AssemblySystem.Register(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
#region Assembly
|
||||
|
||||
public FTask Load(long assemblyIdentity)
|
||||
{
|
||||
var task = FTask.Create(false);
|
||||
Scene?.ThreadSynchronizationContext.Post(() =>
|
||||
{
|
||||
LoadInner(assemblyIdentity);
|
||||
task.SetResult();
|
||||
});
|
||||
return task;
|
||||
}
|
||||
|
||||
public FTask ReLoad(long assemblyIdentity)
|
||||
{
|
||||
var task = FTask.Create(false);
|
||||
Scene?.ThreadSynchronizationContext.Post(() =>
|
||||
{
|
||||
OnUnLoadInner(assemblyIdentity);
|
||||
LoadInner(assemblyIdentity);
|
||||
task.SetResult();
|
||||
});
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
public FTask OnUnLoad(long assemblyIdentity)
|
||||
{
|
||||
var task = FTask.Create(false);
|
||||
Scene?.ThreadSynchronizationContext.Post(() =>
|
||||
{
|
||||
OnUnLoadInner(assemblyIdentity);
|
||||
task.SetResult();
|
||||
});
|
||||
return task;
|
||||
}
|
||||
|
||||
private void LoadInner(long assemblyIdentity)
|
||||
{
|
||||
foreach (var entityType in AssemblySystem.ForEach(assemblyIdentity, typeof(IEntity)))
|
||||
{
|
||||
_hashCodes.Add(entityType, HashCodeHelper.ComputeHash64(entityType.FullName));
|
||||
_assemblyHashCodes.Add(assemblyIdentity, entityType);
|
||||
}
|
||||
|
||||
foreach (var entitiesSystemType in AssemblySystem.ForEach(assemblyIdentity, typeof(IEntitiesSystem)))
|
||||
{
|
||||
Type entitiesType = null;
|
||||
var entity = Activator.CreateInstance(entitiesSystemType);
|
||||
|
||||
switch (entity)
|
||||
{
|
||||
case IAwakeSystem iAwakeSystem:
|
||||
{
|
||||
entitiesType = iAwakeSystem.EntitiesType();
|
||||
_awakeSystems.Add(entitiesType, iAwakeSystem);
|
||||
break;
|
||||
}
|
||||
case IDestroySystem iDestroySystem:
|
||||
{
|
||||
entitiesType = iDestroySystem.EntitiesType();
|
||||
_destroySystems.Add(entitiesType, iDestroySystem);
|
||||
break;
|
||||
}
|
||||
case IDeserializeSystem iDeserializeSystem:
|
||||
{
|
||||
entitiesType = iDeserializeSystem.EntitiesType();
|
||||
_deserializeSystems.Add(entitiesType, iDeserializeSystem);
|
||||
break;
|
||||
}
|
||||
case IUpdateSystem iUpdateSystem:
|
||||
{
|
||||
entitiesType = iUpdateSystem.EntitiesType();
|
||||
_updateSystems.Add(entitiesType, iUpdateSystem);
|
||||
break;
|
||||
}
|
||||
case IFrameUpdateSystem iFrameUpdateSystem:
|
||||
{
|
||||
entitiesType = iFrameUpdateSystem.EntitiesType();
|
||||
_frameUpdateSystem.Add(entitiesType, iFrameUpdateSystem);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
Log.Error($"IEntitiesSystem not support type {entitiesSystemType}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_assemblyList.Add(assemblyIdentity, entitiesType);
|
||||
}
|
||||
|
||||
foreach (var customEntitiesSystemType in AssemblySystem.ForEach(assemblyIdentity, typeof(ICustomEntitiesSystem)))
|
||||
{
|
||||
var entity = (ICustomEntitiesSystem)Activator.CreateInstance(customEntitiesSystemType);
|
||||
var customEntitiesSystemKey = new CustomEntitiesSystemKey(entity.CustomEventType, entity.EntitiesType());
|
||||
_customEntitiesSystems.Add(customEntitiesSystemKey, entity);
|
||||
_assemblyCustomSystemList.Add(assemblyIdentity, customEntitiesSystemKey);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUnLoadInner(long assemblyIdentity)
|
||||
{
|
||||
if (_assemblyHashCodes.TryGetValue(assemblyIdentity, out var entityType))
|
||||
{
|
||||
foreach (var type in entityType)
|
||||
{
|
||||
_hashCodes.Remove(type);
|
||||
}
|
||||
|
||||
_assemblyHashCodes.RemoveByKey(assemblyIdentity);
|
||||
}
|
||||
|
||||
if (_assemblyList.TryGetValue(assemblyIdentity, out var assembly))
|
||||
{
|
||||
foreach (var type in assembly)
|
||||
{
|
||||
_awakeSystems.Remove(type);
|
||||
_updateSystems.Remove(type);
|
||||
_destroySystems.Remove(type);
|
||||
_deserializeSystems.Remove(type);
|
||||
_frameUpdateSystem.Remove(type);
|
||||
}
|
||||
|
||||
_assemblyList.RemoveByKey(assemblyIdentity);
|
||||
}
|
||||
|
||||
if (_assemblyCustomSystemList.TryGetValue(assemblyIdentity, out var customSystemAssembly))
|
||||
{
|
||||
foreach (var customEntitiesSystemKey in customSystemAssembly)
|
||||
{
|
||||
_customEntitiesSystems.Remove(customEntitiesSystemKey);
|
||||
}
|
||||
|
||||
_assemblyCustomSystemList.RemoveByKey(assemblyIdentity);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event
|
||||
|
||||
/// <summary>
|
||||
/// 触发实体的唤醒方法
|
||||
/// </summary>
|
||||
/// <param name="entity">实体对象</param>
|
||||
public void Awake(Entity entity)
|
||||
{
|
||||
if (!_awakeSystems.TryGetValue(entity.Type, out var awakeSystem))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
awakeSystem.Invoke(entity);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"{entity.Type.FullName} Error {e}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发实体的销毁方法
|
||||
/// </summary>
|
||||
/// <param name="entity">实体对象</param>
|
||||
public void Destroy(Entity entity)
|
||||
{
|
||||
if (!_destroySystems.TryGetValue(entity.Type, out var system))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
system.Invoke(entity);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"{entity.Type.FullName} Destroy Error {e}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发实体的反序列化方法
|
||||
/// </summary>
|
||||
/// <param name="entity">实体对象</param>
|
||||
public void Deserialize(Entity entity)
|
||||
{
|
||||
if (!_deserializeSystems.TryGetValue(entity.Type, out var system))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
system.Invoke(entity);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"{entity.Type.FullName} Deserialize Error {e}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region CustomEvent
|
||||
|
||||
public void CustomSystem(Entity entity, int customEventType)
|
||||
{
|
||||
var customEntitiesSystemKey = new CustomEntitiesSystemKey(customEventType, entity.Type);
|
||||
|
||||
if (!_customEntitiesSystems.TryGetValue(customEntitiesSystemKey, out var system))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
system.Invoke(entity);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"{entity.Type.FullName} CustomSystem Error {e}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Update
|
||||
|
||||
/// <summary>
|
||||
/// 将实体加入更新队列,准备进行更新
|
||||
/// </summary>
|
||||
/// <param name="entity">实体对象</param>
|
||||
public void StartUpdate(Entity entity)
|
||||
{
|
||||
var type = entity.Type;
|
||||
var entityRuntimeId = entity.RuntimeId;
|
||||
|
||||
if (_updateSystems.ContainsKey(type))
|
||||
{
|
||||
var updateQueueInfo = new UpdateQueueInfo(type, entityRuntimeId);
|
||||
_updateQueue.Enqueue(updateQueueInfo);
|
||||
_updateQueueDic.Add(entityRuntimeId, updateQueueInfo);
|
||||
}
|
||||
|
||||
if (_frameUpdateSystem.ContainsKey(type))
|
||||
{
|
||||
_frameUpdateQueue.Enqueue(new FrameUpdateQueueInfo(type, entityRuntimeId));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止实体进行更新
|
||||
/// </summary>
|
||||
/// <param name="entity">实体对象</param>
|
||||
public void StopUpdate(Entity entity)
|
||||
{
|
||||
if (!_updateQueueDic.Remove(entity.RuntimeId, out var updateQueueInfo))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
updateQueueInfo.IsStop = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行实体系统的更新逻辑
|
||||
/// </summary>
|
||||
public void Update()
|
||||
{
|
||||
var updateQueueCount = _updateQueue.Count;
|
||||
|
||||
while (updateQueueCount-- > 0)
|
||||
{
|
||||
var updateQueueStruct = _updateQueue.Dequeue();
|
||||
|
||||
if (updateQueueStruct.IsStop)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_updateSystems.TryGetValue(updateQueueStruct.Type, out var updateSystem))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var entity = Scene.GetEntity(updateQueueStruct.RunTimeId);
|
||||
|
||||
if (entity == null || entity.IsDisposed)
|
||||
{
|
||||
_updateQueueDic.Remove(updateQueueStruct.RunTimeId);
|
||||
continue;
|
||||
}
|
||||
|
||||
_updateQueue.Enqueue(updateQueueStruct);
|
||||
|
||||
try
|
||||
{
|
||||
updateSystem.Invoke(entity);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"{updateQueueStruct.Type.FullName} Update Error {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行实体系统的帧更新逻辑
|
||||
/// </summary>
|
||||
public void FrameUpdate()
|
||||
{
|
||||
var count = _frameUpdateQueue.Count;
|
||||
|
||||
while (count-- > 0)
|
||||
{
|
||||
var frameUpdateQueueStruct = _frameUpdateQueue.Dequeue();
|
||||
|
||||
if (!_frameUpdateSystem.TryGetValue(frameUpdateQueueStruct.Type, out var frameUpdateSystem))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var entity = Scene.GetEntity(frameUpdateQueueStruct.RunTimeId);
|
||||
|
||||
if (entity == null || entity.IsDisposed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_frameUpdateQueue.Enqueue(frameUpdateQueueStruct);
|
||||
|
||||
try
|
||||
{
|
||||
frameUpdateSystem.Invoke(entity);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"{frameUpdateQueueStruct.Type.FullName} FrameUpdate Error {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public long GetHashCode(Type type)
|
||||
{
|
||||
return _hashCodes[type];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放实体系统管理器资源
|
||||
/// </summary>
|
||||
public override void Dispose()
|
||||
{
|
||||
_updateQueue.Clear();
|
||||
_frameUpdateQueue.Clear();
|
||||
|
||||
_assemblyList.Clear();
|
||||
_awakeSystems.Clear();
|
||||
_updateSystems.Clear();
|
||||
_destroySystems.Clear();
|
||||
_deserializeSystems.Clear();
|
||||
_frameUpdateSystem.Clear();
|
||||
|
||||
AssemblySystem.UnRegister(this);
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using NBC.Assembly;
|
||||
using NBC.Async;
|
||||
using NBC.DataStructure.Collection;
|
||||
using NBC.Entitas;
|
||||
|
||||
// ReSharper disable PossibleMultipleEnumeration
|
||||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
// ReSharper disable MethodOverloadWithOptionalParameter
|
||||
|
||||
namespace NBC.Event
|
||||
{
|
||||
internal sealed class EventCache
|
||||
{
|
||||
public readonly Type EnventType;
|
||||
public readonly object Obj;
|
||||
public EventCache(Type enventType, object obj)
|
||||
{
|
||||
EnventType = enventType;
|
||||
Obj = obj;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class EventComponent : Entity, IAssembly
|
||||
{
|
||||
private readonly OneToManyList<Type, IEvent> _events = new();
|
||||
private readonly OneToManyList<Type, IAsyncEvent> _asyncEvents = new();
|
||||
private readonly OneToManyList<long, EventCache> _assemblyEvents = new();
|
||||
private readonly OneToManyList<long, EventCache> _assemblyAsyncEvents = new();
|
||||
|
||||
internal async FTask<EventComponent> Initialize()
|
||||
{
|
||||
await AssemblySystem.Register(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
#region Assembly
|
||||
|
||||
public async FTask Load(long assemblyIdentity)
|
||||
{
|
||||
var tcs = FTask.Create(false);
|
||||
Scene?.ThreadSynchronizationContext.Post(() =>
|
||||
{
|
||||
LoadInner(assemblyIdentity);
|
||||
tcs.SetResult();
|
||||
});
|
||||
await tcs;
|
||||
}
|
||||
|
||||
public async FTask ReLoad(long assemblyIdentity)
|
||||
{
|
||||
var tcs = FTask.Create(false);
|
||||
Scene?.ThreadSynchronizationContext.Post(() =>
|
||||
{
|
||||
OnUnLoadInner(assemblyIdentity);
|
||||
LoadInner(assemblyIdentity);
|
||||
tcs.SetResult();
|
||||
});
|
||||
await tcs;
|
||||
}
|
||||
|
||||
public async FTask OnUnLoad(long assemblyIdentity)
|
||||
{
|
||||
var tcs = FTask.Create(false);
|
||||
Scene?.ThreadSynchronizationContext.Post(() =>
|
||||
{
|
||||
OnUnLoadInner(assemblyIdentity);
|
||||
tcs.SetResult();
|
||||
});
|
||||
await tcs;
|
||||
}
|
||||
|
||||
private void LoadInner(long assemblyIdentity)
|
||||
{
|
||||
|
||||
foreach (var type in AssemblySystem.ForEach(assemblyIdentity, typeof(IEvent)))
|
||||
{
|
||||
var @event = (IEvent)Activator.CreateInstance(type);
|
||||
|
||||
if (@event == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var eventType = @event.EventType();
|
||||
_events.Add(eventType, @event);
|
||||
_assemblyEvents.Add(assemblyIdentity, new EventCache(eventType, @event));
|
||||
}
|
||||
_events.SortAll();
|
||||
|
||||
foreach (var type in AssemblySystem.ForEach(assemblyIdentity, typeof(IAsyncEvent)))
|
||||
{
|
||||
var @event = (IAsyncEvent)Activator.CreateInstance(type);
|
||||
|
||||
if (@event == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var eventType = @event.EventType();
|
||||
_asyncEvents.Add(eventType, @event);
|
||||
_assemblyAsyncEvents.Add(assemblyIdentity, new EventCache(eventType, @event));
|
||||
}
|
||||
_asyncEvents.SortAll();
|
||||
}
|
||||
|
||||
private void OnUnLoadInner(long assemblyIdentity)
|
||||
{
|
||||
if (_assemblyEvents.TryGetValue(assemblyIdentity, out var events))
|
||||
{
|
||||
foreach (var @event in events)
|
||||
{
|
||||
_events.RemoveValue(@event.EnventType, (IEvent)@event.Obj);
|
||||
}
|
||||
|
||||
_assemblyEvents.RemoveByKey(assemblyIdentity);
|
||||
}
|
||||
|
||||
if (_assemblyAsyncEvents.TryGetValue(assemblyIdentity, out var asyncEvents))
|
||||
{
|
||||
foreach (var @event in asyncEvents)
|
||||
{
|
||||
_asyncEvents.RemoveValue(@event.EnventType, (IAsyncEvent)@event.Obj);
|
||||
}
|
||||
|
||||
_assemblyAsyncEvents.RemoveByKey(assemblyIdentity);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Publish
|
||||
|
||||
/// <summary>
|
||||
/// 发布一个值类型的事件数据。
|
||||
/// </summary>
|
||||
/// <typeparam name="TEventData">事件数据类型(值类型)。</typeparam>
|
||||
/// <param name="eventData">事件数据实例。</param>
|
||||
public void Publish<TEventData>(TEventData eventData) where TEventData : struct
|
||||
{
|
||||
if (!_events.TryGetValue(typeof(TEventData), out var list))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var @event in list)
|
||||
{
|
||||
try
|
||||
{
|
||||
@event.Invoke(eventData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发布一个继承自 Entity 的事件数据。
|
||||
/// </summary>
|
||||
/// <typeparam name="TEventData">事件数据类型(继承自 Entity)。</typeparam>
|
||||
/// <param name="eventData">事件数据实例。</param>
|
||||
/// <param name="isDisposed">是否释放事件数据。</param>
|
||||
public void Publish<TEventData>(TEventData eventData, bool isDisposed = true) where TEventData : Entity
|
||||
{
|
||||
if (!_events.TryGetValue(typeof(TEventData), out var list))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var @event in list)
|
||||
{
|
||||
try
|
||||
{
|
||||
@event.Invoke(eventData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (isDisposed)
|
||||
{
|
||||
eventData.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步发布一个值类型的事件数据。
|
||||
/// </summary>
|
||||
/// <typeparam name="TEventData">事件数据类型(值类型)。</typeparam>
|
||||
/// <param name="eventData">事件数据实例。</param>
|
||||
/// <returns>表示异步操作的任务。</returns>
|
||||
public async FTask PublishAsync<TEventData>(TEventData eventData) where TEventData : struct
|
||||
{
|
||||
if (!_asyncEvents.TryGetValue(typeof(TEventData), out var list))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var tasks = ListPool<FTask>.Create();
|
||||
|
||||
foreach (var @event in list)
|
||||
{
|
||||
tasks.Add(@event.InvokeAsync(eventData));
|
||||
}
|
||||
|
||||
await FTask.WaitAll(tasks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步发布一个继承自 Entity 的事件数据。
|
||||
/// </summary>
|
||||
/// <typeparam name="TEventData">事件数据类型(继承自 Entity)。</typeparam>
|
||||
/// <param name="eventData">事件数据实例。</param>
|
||||
/// <param name="isDisposed">是否释放事件数据。</param>
|
||||
/// <returns>表示异步操作的任务。</returns>
|
||||
public async FTask PublishAsync<TEventData>(TEventData eventData, bool isDisposed = true) where TEventData : Entity
|
||||
{
|
||||
if (!_asyncEvents.TryGetValue(eventData.GetType(), out var list))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var tasks = ListPool<FTask>.Create();
|
||||
|
||||
foreach (var @event in list)
|
||||
{
|
||||
tasks.Add(@event.InvokeAsync(eventData));
|
||||
}
|
||||
|
||||
await FTask.WaitAll(tasks);
|
||||
|
||||
if (isDisposed)
|
||||
{
|
||||
eventData.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
_events.Clear();
|
||||
_asyncEvents.Clear();
|
||||
_assemblyEvents.Clear();
|
||||
_assemblyAsyncEvents.Clear();
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bc6c3f6b01eb14a35934175e36eeb544
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 10dcbb383d66a4915b44d6f20331a7bf
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using NBC.Async;
|
||||
|
||||
namespace NBC.Event
|
||||
{
|
||||
/// <summary>
|
||||
/// 事件的接口
|
||||
/// </summary>
|
||||
public interface IEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// 用于指定事件的Type
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Type EventType();
|
||||
/// <summary>
|
||||
/// 时间内部使用的入口
|
||||
/// </summary>
|
||||
/// <param name="self"></param>
|
||||
void Invoke(object self);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步事件的接口
|
||||
/// </summary>
|
||||
public interface IAsyncEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IEvent.EventType"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Type EventType();
|
||||
/// <summary>
|
||||
/// <see cref="IEvent.Invoke"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
FTask InvokeAsync(object self);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 事件的抽象类,要使用事件必须要继承这个抽象接口。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">要监听的事件泛型类型</typeparam>
|
||||
public abstract class EventSystem<T> : IEvent
|
||||
{
|
||||
private readonly Type _selfType = typeof(T);
|
||||
/// <summary>
|
||||
/// <see cref="IEvent.EventType"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Type EventType()
|
||||
{
|
||||
return _selfType;
|
||||
}
|
||||
/// <summary>
|
||||
/// 事件调用的方法,要在这个方法里编写事件发生的逻辑
|
||||
/// </summary>
|
||||
/// <param name="self"></param>
|
||||
protected abstract void Handler(T self);
|
||||
/// <summary>
|
||||
/// <see cref="IEvent.Invoke"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public void Invoke(object self)
|
||||
{
|
||||
try
|
||||
{
|
||||
Handler((T) self);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"{_selfType.Name} Error {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 异步事件的抽象类,要使用事件必须要继承这个抽象接口。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">要监听的事件泛型类型</typeparam>
|
||||
public abstract class AsyncEventSystem<T> : IAsyncEvent
|
||||
{
|
||||
private readonly Type _selfType = typeof(T);
|
||||
/// <summary>
|
||||
/// <see cref="IEvent.EventType"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Type EventType()
|
||||
{
|
||||
return _selfType;
|
||||
}
|
||||
/// <summary>
|
||||
/// 事件调用的方法,要在这个方法里编写事件发生的逻辑
|
||||
/// </summary>
|
||||
/// <param name="self"></param>
|
||||
protected abstract FTask Handler(T self);
|
||||
/// <summary>
|
||||
/// <see cref="IEvent.Invoke"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async FTask InvokeAsync(object self)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Handler((T) self);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"{_selfType.Name} Error {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1e46ad748051f4277a8f660fa842b19d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,139 @@
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using NBC.Entitas;
|
||||
using NBC.DataStructure.Collection;
|
||||
using NBC.Pool;
|
||||
using NBC.Serialize;
|
||||
|
||||
namespace NBC.Entitas
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息的对象池组件
|
||||
/// </summary>
|
||||
public sealed class MessagePoolComponent : Entity
|
||||
{
|
||||
private int _poolCount;
|
||||
private const int MaxCapacity = ushort.MaxValue;
|
||||
private readonly OneToManyQueue<Type, AMessage> _poolQueue = new OneToManyQueue<Type, AMessage>();
|
||||
private readonly Dictionary<Type, Func<AMessage>> _typeCheckCache = new Dictionary<Type, Func<AMessage>>();
|
||||
/// <summary>
|
||||
/// 销毁组件
|
||||
/// </summary>
|
||||
public override void Dispose()
|
||||
{
|
||||
_poolCount = 0;
|
||||
_poolQueue.Clear();
|
||||
_typeCheckCache.Clear();
|
||||
base.Dispose();
|
||||
}
|
||||
/// <summary>
|
||||
/// 从对象池里获取一个消息,如果没有就创建一个新的
|
||||
/// </summary>
|
||||
/// <typeparam name="T">消息的泛型类型</typeparam>
|
||||
/// <returns></returns>
|
||||
public T Rent<T>() where T : AMessage, new()
|
||||
{
|
||||
if (!_poolQueue.TryDequeue(typeof(T), out var queue))
|
||||
{
|
||||
var instance = new T();
|
||||
instance.SetScene(Scene);
|
||||
instance.SetIsPool(true);
|
||||
return instance;
|
||||
}
|
||||
|
||||
queue.SetIsPool(true);
|
||||
_poolCount--;
|
||||
return (T)queue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Rent"/>
|
||||
/// </summary>
|
||||
/// <param name="type">消息的类型</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotSupportedException"></exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public AMessage Rent(Type type)
|
||||
{
|
||||
if (!_poolQueue.TryDequeue(type, out var queue))
|
||||
{
|
||||
if (!_typeCheckCache.TryGetValue(type, out var createInstance))
|
||||
{
|
||||
if (!typeof(AMessage).IsAssignableFrom(type))
|
||||
{
|
||||
throw new NotSupportedException($"{this.GetType().FullName} Type:{type.FullName} must inherit from IPool");
|
||||
}
|
||||
else
|
||||
{
|
||||
createInstance = CreateInstance.CreateMessage(type);
|
||||
_typeCheckCache[type] = createInstance;
|
||||
}
|
||||
}
|
||||
|
||||
var instance = createInstance();
|
||||
instance.SetScene(Scene);
|
||||
instance.SetIsPool(true);
|
||||
return instance;
|
||||
}
|
||||
|
||||
queue.SetIsPool(true);
|
||||
_poolCount--;
|
||||
return queue;
|
||||
}
|
||||
/// <summary>
|
||||
/// 返还一个消息到对象池中
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
public void Return(AMessage obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!obj.IsPool())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_poolCount >= MaxCapacity)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_poolCount++;
|
||||
obj.SetIsPool(false);
|
||||
_poolQueue.Enqueue(obj.GetType(), obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Return"/>
|
||||
/// </summary>
|
||||
/// <param name="obj">返还的消息</param>
|
||||
/// <typeparam name="T">返还的消息泛型类型</typeparam>
|
||||
public void Return<T>(T obj) where T : AMessage
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!obj.IsPool())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_poolCount >= MaxCapacity)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_poolCount++;
|
||||
obj.SetIsPool(false);
|
||||
_poolQueue.Enqueue(typeof(T), obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using NBC.Event;
|
||||
|
||||
namespace NBC
|
||||
{
|
||||
/// <summary>
|
||||
/// 计时器抽象类,提供了一个基础框架,用于创建处理计时器事件的具体类。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件的类型参数</typeparam>
|
||||
public abstract class TimerHandler<T> : EventSystem<T> { }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0bb411c4d246b43aba54eb60b7654492
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
#pragma warning disable CS8625
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace NBC
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct TimerAction
|
||||
{
|
||||
public long TimerId;
|
||||
public long StartTime;
|
||||
public long TriggerTime;
|
||||
public readonly object Callback;
|
||||
public readonly TimerType TimerType;
|
||||
public TimerAction(long timerId, TimerType timerType, long startTime, long triggerTime, object callback)
|
||||
{
|
||||
TimerId = timerId;
|
||||
Callback = callback;
|
||||
TimerType = timerType;
|
||||
StartTime = startTime;
|
||||
TriggerTime = triggerTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// ReSharper disable ForCanBeConvertedToForeach
|
||||
|
||||
using NBC.Entitas;
|
||||
using NBC.Entitas.Interface;
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
using UnityEngine;
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
namespace NBC
|
||||
{
|
||||
public sealed class TimerComponentUpdateSystem : UpdateSystem<TimerComponent>
|
||||
{
|
||||
protected override void Update(TimerComponent self)
|
||||
{
|
||||
self.Update();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 时间调度组件
|
||||
/// </summary>
|
||||
public sealed class TimerComponent : Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 使用系统时间创建的计时器核心。
|
||||
/// </summary>
|
||||
public TimerSchedulerNet Net { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 使用 Unity 时间创建的计时器核心。
|
||||
/// </summary>
|
||||
public TimerSchedulerNetUnity Unity { get; private set; }
|
||||
|
||||
internal TimerComponent Initialize()
|
||||
{
|
||||
Net = new TimerSchedulerNet(Scene);
|
||||
Unity = new TimerSchedulerNetUnity(Scene);
|
||||
return this;
|
||||
}
|
||||
public void Update()
|
||||
{
|
||||
Net.Update();
|
||||
Unity.Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,383 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NBC.Async;
|
||||
using NBC.DataStructure.Collection;
|
||||
using NBC.Helper;
|
||||
|
||||
// ReSharper disable UnusedParameter.Global
|
||||
|
||||
#pragma warning disable CS8602 // Dereference of a possibly null reference.
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
|
||||
namespace NBC
|
||||
{
|
||||
/// <summary>
|
||||
/// 基于系统事件的任务调度系统
|
||||
/// </summary>
|
||||
public sealed class TimerSchedulerNet
|
||||
{
|
||||
private readonly Scene _scene;
|
||||
private long _idGenerator;
|
||||
private long _minTime; // 最小时间
|
||||
private readonly Queue<long> _timeOutTime = new Queue<long>();
|
||||
private readonly Queue<long> _timeOutTimerIds = new Queue<long>();
|
||||
private readonly Dictionary<long, TimerAction> _timerActions = new Dictionary<long, TimerAction>();
|
||||
private readonly SortedOneToManyList<long, long> _timeId = new(); // 时间与计时器ID的有序一对多列表
|
||||
private long GetId => ++_idGenerator;
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="scene">当前的Scene</param>
|
||||
public TimerSchedulerNet(Scene scene)
|
||||
{
|
||||
_scene = scene;
|
||||
}
|
||||
|
||||
private long Now()
|
||||
{
|
||||
return TimeHelper.Now;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 驱动方法,只有调用这个方法任务系统才会正常运转。
|
||||
/// </summary>
|
||||
public void Update()
|
||||
{
|
||||
if (_timeId.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var currentTime = Now();
|
||||
|
||||
if (currentTime < _minTime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历时间ID列表,查找超时的计时器任务
|
||||
foreach (var (key, _) in _timeId)
|
||||
{
|
||||
if (key > currentTime)
|
||||
{
|
||||
_minTime = key;
|
||||
break;
|
||||
}
|
||||
|
||||
_timeOutTime.Enqueue(key);
|
||||
}
|
||||
|
||||
// 处理超时的计时器任务
|
||||
while (_timeOutTime.TryDequeue(out var time))
|
||||
{
|
||||
var timerIds = _timeId[time];
|
||||
for (var i = 0; i < timerIds.Count; ++i)
|
||||
{
|
||||
_timeOutTimerIds.Enqueue(timerIds[i]);
|
||||
}
|
||||
|
||||
_timeId.Remove(time);
|
||||
// _timeId.RemoveKey(time);
|
||||
}
|
||||
|
||||
if (_timeId.Count == 0)
|
||||
{
|
||||
_minTime = long.MaxValue;
|
||||
}
|
||||
|
||||
// 执行超时的计时器任务的回调操作
|
||||
while (_timeOutTimerIds.TryDequeue(out var timerId))
|
||||
{
|
||||
if (!_timerActions.Remove(timerId, out var timerAction))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 根据计时器类型执行不同的操作
|
||||
switch (timerAction.TimerType)
|
||||
{
|
||||
case TimerType.OnceWaitTimer:
|
||||
{
|
||||
var tcs = (FTask<bool>)timerAction.Callback;
|
||||
tcs.SetResult(true);
|
||||
break;
|
||||
}
|
||||
case TimerType.OnceTimer:
|
||||
{
|
||||
if (timerAction.Callback is not Action action)
|
||||
{
|
||||
Log.Error($"timerAction {timerAction.ToJson()}");
|
||||
break;
|
||||
}
|
||||
|
||||
action();
|
||||
break;
|
||||
}
|
||||
case TimerType.RepeatedTimer:
|
||||
{
|
||||
if (timerAction.Callback is not Action action)
|
||||
{
|
||||
Log.Error($"timerAction {timerAction.ToJson()}");
|
||||
break;
|
||||
}
|
||||
|
||||
timerAction.StartTime = Now();
|
||||
AddTimer(ref timerAction);
|
||||
action();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddTimer(ref TimerAction timer)
|
||||
{
|
||||
var tillTime = timer.StartTime + timer.TriggerTime;
|
||||
_timeId.Add(tillTime, timer.TimerId);
|
||||
_timerActions.Add(timer.TimerId, timer);
|
||||
|
||||
if (tillTime < _minTime)
|
||||
{
|
||||
_minTime = tillTime;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步等待指定时间。
|
||||
/// </summary>
|
||||
/// <param name="time">等待的时间长度。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>等待是否成功。</returns>
|
||||
public async FTask<bool> WaitAsync(long time, FCancellationToken cancellationToken = null)
|
||||
{
|
||||
if (time <= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var now = Now();
|
||||
var timerId = GetId;
|
||||
var tcs = FTask<bool>.Create();
|
||||
var timerAction = new TimerAction(timerId, TimerType.OnceWaitTimer, now, time, tcs);
|
||||
|
||||
void CancelActionVoid()
|
||||
{
|
||||
if (Remove(timerId))
|
||||
{
|
||||
tcs.SetResult(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool result;
|
||||
|
||||
try
|
||||
{
|
||||
cancellationToken?.Add(CancelActionVoid);
|
||||
AddTimer(ref timerAction);
|
||||
result = await tcs;
|
||||
}
|
||||
finally
|
||||
{
|
||||
cancellationToken?.Remove(CancelActionVoid);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步等待直到指定时间。
|
||||
/// </summary>
|
||||
/// <param name="tillTime">等待的目标时间。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>等待是否成功。</returns>
|
||||
public async FTask<bool> WaitTillAsync(long tillTime, FCancellationToken cancellationToken = null)
|
||||
{
|
||||
var now = Now();
|
||||
|
||||
if (now >= tillTime)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var timerId = GetId;
|
||||
var tcs = FTask<bool>.Create();
|
||||
var timerAction = new TimerAction(timerId, TimerType.OnceWaitTimer, now, tillTime - now, tcs);
|
||||
|
||||
void CancelActionVoid()
|
||||
{
|
||||
if (Remove(timerId))
|
||||
{
|
||||
tcs.SetResult(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool result;
|
||||
|
||||
try
|
||||
{
|
||||
cancellationToken?.Add(CancelActionVoid);
|
||||
AddTimer(ref timerAction);
|
||||
result = await tcs;
|
||||
}
|
||||
finally
|
||||
{
|
||||
cancellationToken?.Remove(CancelActionVoid);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步等待一帧时间。
|
||||
/// </summary>
|
||||
/// <returns>等待是否成功。</returns>
|
||||
public async FTask WaitFrameAsync()
|
||||
{
|
||||
await WaitAsync(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只执行一次的计时器,直到指定时间
|
||||
/// </summary>
|
||||
/// <param name="time">计时器执行的目标时间。</param>
|
||||
/// <param name="action">计时器回调方法。</param>
|
||||
/// <returns></returns>
|
||||
public long OnceTimer(long time, Action action)
|
||||
{
|
||||
var now = Now();
|
||||
var timerId = GetId;
|
||||
var timerAction = new TimerAction(timerId, TimerType.OnceTimer, now, time, action);
|
||||
AddTimer(ref timerAction);
|
||||
return timerId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只执行一次的计时器,直到指定时间。
|
||||
/// </summary>
|
||||
/// <param name="tillTime">计时器执行的目标时间。</param>
|
||||
/// <param name="action">计时器回调方法。</param>
|
||||
/// <returns>计时器的 ID。</returns>
|
||||
public long OnceTillTimer(long tillTime, Action action)
|
||||
{
|
||||
var now = Now();
|
||||
|
||||
if (tillTime < now)
|
||||
{
|
||||
Log.Error($"new once time too small tillTime:{tillTime} Now:{now}");
|
||||
}
|
||||
|
||||
var timerId = GetId;
|
||||
var timerAction = new TimerAction(timerId, TimerType.OnceTimer, now, tillTime - now, action);
|
||||
AddTimer(ref timerAction);
|
||||
return timerId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只执行一次的计时器,用于发布指定类型的事件。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型。</typeparam>
|
||||
/// <param name="time">计时器执行的延迟时间。</param>
|
||||
/// <param name="timerHandlerType">事件处理器类型。</param>
|
||||
/// <returns>计时器的 ID。</returns>
|
||||
public long OnceTimer<T>(long time, T timerHandlerType) where T : struct
|
||||
{
|
||||
void OnceTimerVoid()
|
||||
{
|
||||
_scene.EventComponent.Publish(timerHandlerType);
|
||||
}
|
||||
|
||||
return OnceTimer(time, OnceTimerVoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只执行一次的计时器,直到指定时间,用于发布指定类型的事件。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型。</typeparam>
|
||||
/// <param name="tillTime">计时器执行的目标时间。</param>
|
||||
/// <param name="timerHandlerType">事件处理器类型。</param>
|
||||
/// <returns>计时器的 ID。</returns>
|
||||
public long OnceTillTimer<T>(long tillTime, T timerHandlerType) where T : struct
|
||||
{
|
||||
void OnceTillTimerVoid()
|
||||
{
|
||||
_scene.EventComponent.Publish(timerHandlerType);
|
||||
}
|
||||
|
||||
return OnceTillTimer(tillTime, OnceTillTimerVoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个帧任务
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
/// <returns></returns>
|
||||
public long FrameTimer(Action action)
|
||||
{
|
||||
return RepeatedTimerInner(0, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个重复执行的计时器。
|
||||
/// </summary>
|
||||
/// <param name="time">计时器重复间隔的时间。</param>
|
||||
/// <param name="action">计时器回调方法。</param>
|
||||
/// <returns>计时器的 ID。</returns>
|
||||
public long RepeatedTimer(long time, Action action)
|
||||
{
|
||||
if (time < 0)
|
||||
{
|
||||
Log.Error($"time too small: {time}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return RepeatedTimerInner(time, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个重复执行的计时器,用于发布指定类型的事件。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型。</typeparam>
|
||||
/// <param name="time">计时器重复间隔的时间。</param>
|
||||
/// <param name="timerHandlerType">事件处理器类型。</param>
|
||||
/// <returns>计时器的 ID。</returns>
|
||||
public long RepeatedTimer<T>(long time, T timerHandlerType) where T : struct
|
||||
{
|
||||
void RepeatedTimerVoid()
|
||||
{
|
||||
_scene.EventComponent.Publish(timerHandlerType);
|
||||
}
|
||||
|
||||
return RepeatedTimer(time, RepeatedTimerVoid);
|
||||
}
|
||||
|
||||
private long RepeatedTimerInner(long time, Action action)
|
||||
{
|
||||
var now = Now();
|
||||
var timerId = GetId;
|
||||
var timerAction = new TimerAction(timerId, TimerType.RepeatedTimer, now, time, action);
|
||||
AddTimer(ref timerAction);
|
||||
return timerId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除指定 ID 的计时器。
|
||||
/// </summary>
|
||||
/// <param name="timerId"></param>
|
||||
/// <returns></returns>
|
||||
public bool Remove(ref long timerId)
|
||||
{
|
||||
var id = timerId;
|
||||
timerId = 0;
|
||||
return Remove(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除指定 ID 的计时器。
|
||||
/// </summary>
|
||||
/// <param name="timerId">计时器的 ID。</param>
|
||||
public bool Remove(long timerId)
|
||||
{
|
||||
return timerId != 0 && _timerActions.Remove(timerId, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,371 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NBC.Helper;
|
||||
using NBC.Async;
|
||||
using NBC.DataStructure.Collection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBC
|
||||
{
|
||||
public sealed class TimerSchedulerNetUnity
|
||||
{
|
||||
private readonly Scene _scene;
|
||||
private long _idGenerator;
|
||||
private long _minTime; // 最小时间
|
||||
private readonly Queue<long> _timeOutTime = new Queue<long>();
|
||||
private readonly Queue<long> _timeOutTimerIds = new Queue<long>();
|
||||
private readonly Dictionary<long, TimerAction> _timerActions = new Dictionary<long, TimerAction>();
|
||||
private readonly SortedOneToManyList<long, long> _timeId = new(); // 时间与计时器ID的有序一对多列表
|
||||
private long GetId => ++_idGenerator;
|
||||
public TimerSchedulerNetUnity(Scene scene)
|
||||
{
|
||||
_scene = scene;
|
||||
}
|
||||
|
||||
private long Now()
|
||||
{
|
||||
return (long)(Time.time * 1000);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (_timeId.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var currentTime = Now();
|
||||
|
||||
if (currentTime < _minTime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历时间ID列表,查找超时的计时器任务
|
||||
foreach (var (key, _) in _timeId)
|
||||
{
|
||||
if (key > currentTime)
|
||||
{
|
||||
_minTime = key;
|
||||
break;
|
||||
}
|
||||
|
||||
_timeOutTime.Enqueue(key);
|
||||
}
|
||||
|
||||
// 处理超时的计时器任务
|
||||
while (_timeOutTime.TryDequeue(out var time))
|
||||
{
|
||||
var timerIds = _timeId[time];
|
||||
for (var i = 0; i < timerIds.Count; ++i)
|
||||
{
|
||||
_timeOutTimerIds.Enqueue(timerIds[i]);
|
||||
}
|
||||
|
||||
_timeId.Remove(time);
|
||||
// _timeId.RemoveKey(time);
|
||||
}
|
||||
|
||||
if (_timeId.Count == 0)
|
||||
{
|
||||
_minTime = long.MaxValue;
|
||||
}
|
||||
|
||||
// 执行超时的计时器任务的回调操作
|
||||
while (_timeOutTimerIds.TryDequeue(out var timerId))
|
||||
{
|
||||
if (!_timerActions.Remove(timerId, out var timerAction))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 根据计时器类型执行不同的操作
|
||||
switch (timerAction.TimerType)
|
||||
{
|
||||
case TimerType.OnceWaitTimer:
|
||||
{
|
||||
var tcs = (FTask<bool>)timerAction.Callback;
|
||||
tcs.SetResult(true);
|
||||
break;
|
||||
}
|
||||
case TimerType.OnceTimer:
|
||||
{
|
||||
if (timerAction.Callback is not Action action)
|
||||
{
|
||||
Log.Error($"timerAction {timerAction.ToJson()}");
|
||||
break;
|
||||
}
|
||||
|
||||
action();
|
||||
break;
|
||||
}
|
||||
case TimerType.RepeatedTimer:
|
||||
{
|
||||
if (timerAction.Callback is not Action action)
|
||||
{
|
||||
Log.Error($"timerAction {timerAction.ToJson()}");
|
||||
break;
|
||||
}
|
||||
|
||||
timerAction.StartTime = Now();
|
||||
AddTimer(ref timerAction);
|
||||
action();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddTimer(ref TimerAction timer)
|
||||
{
|
||||
var tillTime = timer.StartTime + timer.TriggerTime;
|
||||
_timeId.Add(tillTime, timer.TimerId);
|
||||
_timerActions.Add(timer.TimerId, timer);
|
||||
|
||||
if (tillTime < _minTime)
|
||||
{
|
||||
_minTime = tillTime;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步等待指定时间。
|
||||
/// </summary>
|
||||
/// <param name="time">等待的时间长度。</param>
|
||||
/// <param name="cancellationToken">可选的取消令牌。</param>
|
||||
/// <returns>等待是否成功。</returns>
|
||||
public async FTask<bool> WaitAsync(long time, FCancellationToken cancellationToken = null)
|
||||
{
|
||||
if (time <= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var now = Now();
|
||||
var timerId = GetId;
|
||||
var tcs = FTask<bool>.Create();
|
||||
var timerAction = new TimerAction(timerId, TimerType.OnceWaitTimer, now, time, tcs);
|
||||
|
||||
|
||||
void CancelActionVoid()
|
||||
{
|
||||
if (Remove(timerId))
|
||||
{
|
||||
tcs.SetResult(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool result;
|
||||
|
||||
try
|
||||
{
|
||||
cancellationToken?.Add(CancelActionVoid);
|
||||
AddTimer(ref timerAction);
|
||||
result =await tcs;
|
||||
}
|
||||
finally
|
||||
{
|
||||
cancellationToken?.Remove(CancelActionVoid);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步等待直到指定时间。
|
||||
/// </summary>
|
||||
/// <param name="tillTime">等待的目标时间。</param>
|
||||
/// <param name="cancellationToken">可选的取消令牌。</param>
|
||||
/// <returns>等待是否成功。</returns>
|
||||
public async FTask<bool> WaitTillAsync(long tillTime, FCancellationToken cancellationToken = null)
|
||||
{
|
||||
var now = Now();
|
||||
|
||||
if (now >= tillTime)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var timerId = GetId;
|
||||
var tcs = FTask<bool>.Create();
|
||||
var timerAction = new TimerAction(timerId, TimerType.OnceWaitTimer, now, tillTime - now, tcs);
|
||||
|
||||
void CancelActionVoid()
|
||||
{
|
||||
if (Remove(timerId))
|
||||
{
|
||||
tcs.SetResult(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool result;
|
||||
|
||||
try
|
||||
{
|
||||
cancellationToken?.Add(CancelActionVoid);
|
||||
AddTimer(ref timerAction);
|
||||
result = await tcs;
|
||||
}
|
||||
finally
|
||||
{
|
||||
cancellationToken?.Remove(CancelActionVoid);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步等待一帧时间。
|
||||
/// </summary>
|
||||
/// <returns>等待是否成功。</returns>
|
||||
public async FTask WaitFrameAsync(FCancellationToken cancellationToken = null)
|
||||
{
|
||||
await WaitAsync(1, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只执行一次的计时器,直到指定时间
|
||||
/// </summary>
|
||||
/// <param name="time">计时器执行的目标时间。</param>
|
||||
/// <param name="action">计时器回调方法。</param>
|
||||
/// <returns></returns>
|
||||
public long OnceTimer(long time, Action action)
|
||||
{
|
||||
var now = Now();
|
||||
var timerId = GetId;
|
||||
var timerAction = new TimerAction(timerId, TimerType.OnceTimer, now, time, action);
|
||||
AddTimer(ref timerAction);
|
||||
return timerId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只执行一次的计时器,直到指定时间。
|
||||
/// </summary>
|
||||
/// <param name="tillTime">计时器执行的目标时间。</param>
|
||||
/// <param name="action">计时器回调方法。</param>
|
||||
/// <returns>计时器的 ID。</returns>
|
||||
public long OnceTillTimer(long tillTime, Action action)
|
||||
{
|
||||
var now = Now();
|
||||
|
||||
if (tillTime < now)
|
||||
{
|
||||
Log.Error($"new once time too small tillTime:{tillTime} Now:{now}");
|
||||
}
|
||||
|
||||
var timerId = GetId;
|
||||
var timerAction = new TimerAction(timerId, TimerType.OnceTimer, now, tillTime - now, action);
|
||||
AddTimer(ref timerAction);
|
||||
return timerId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只执行一次的计时器,用于发布指定类型的事件。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型。</typeparam>
|
||||
/// <param name="time">计时器执行的延迟时间。</param>
|
||||
/// <param name="timerHandlerType">事件处理器类型。</param>
|
||||
/// <returns>计时器的 ID。</returns>
|
||||
public long OnceTimer<T>(long time, T timerHandlerType) where T : struct
|
||||
{
|
||||
void OnceTimerVoid()
|
||||
{
|
||||
_scene.EventComponent.Publish(timerHandlerType);
|
||||
}
|
||||
|
||||
return OnceTimer(time, OnceTimerVoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只执行一次的计时器,直到指定时间,用于发布指定类型的事件。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型。</typeparam>
|
||||
/// <param name="tillTime">计时器执行的目标时间。</param>
|
||||
/// <param name="timerHandlerType">事件处理器类型。</param>
|
||||
/// <returns>计时器的 ID。</returns>
|
||||
public long OnceTillTimer<T>(long tillTime, T timerHandlerType) where T : struct
|
||||
{
|
||||
void OnceTillTimerVoid()
|
||||
{
|
||||
_scene.EventComponent.Publish(timerHandlerType);
|
||||
}
|
||||
|
||||
return OnceTillTimer(tillTime, OnceTillTimerVoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个帧任务
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
/// <returns></returns>
|
||||
public long FrameTimer(Action action)
|
||||
{
|
||||
return RepeatedTimerInner(1, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个重复执行的计时器。
|
||||
/// </summary>
|
||||
/// <param name="time">计时器重复间隔的时间。</param>
|
||||
/// <param name="action">计时器回调方法。</param>
|
||||
/// <returns>计时器的 ID。</returns>
|
||||
public long RepeatedTimer(long time, Action action)
|
||||
{
|
||||
if (time < 0)
|
||||
{
|
||||
Log.Error($"time too small: {time}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return RepeatedTimerInner(time, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个重复执行的计时器,用于发布指定类型的事件。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型。</typeparam>
|
||||
/// <param name="time">计时器重复间隔的时间。</param>
|
||||
/// <param name="timerHandlerType">事件处理器类型。</param>
|
||||
/// <returns>计时器的 ID。</returns>
|
||||
public long RepeatedTimer<T>(long time, T timerHandlerType) where T : struct
|
||||
{
|
||||
void RepeatedTimerVoid()
|
||||
{
|
||||
_scene.EventComponent.Publish(timerHandlerType);
|
||||
}
|
||||
|
||||
return RepeatedTimer(time, RepeatedTimerVoid);
|
||||
}
|
||||
|
||||
private long RepeatedTimerInner(long time, Action action)
|
||||
{
|
||||
var now = Now();
|
||||
var timerId = GetId;
|
||||
var timerAction = new TimerAction(timerId, TimerType.RepeatedTimer, now, time, action);
|
||||
AddTimer(ref timerAction);
|
||||
return timerId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除指定 ID 的计时器。
|
||||
/// </summary>
|
||||
/// <param name="timerId"></param>
|
||||
/// <returns></returns>
|
||||
public bool Remove(ref long timerId)
|
||||
{
|
||||
var id = timerId;
|
||||
timerId = 0;
|
||||
return Remove(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除指定 ID 的计时器。
|
||||
/// </summary>
|
||||
/// <param name="timerId">计时器的 ID。</param>
|
||||
public bool Remove(long timerId)
|
||||
{
|
||||
return timerId != 0 && _timerActions.Remove(timerId, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace NBC
|
||||
{
|
||||
/// <summary>
|
||||
/// 枚举对象TimerType
|
||||
/// </summary>
|
||||
public enum TimerType
|
||||
{
|
||||
/// <summary>
|
||||
/// None
|
||||
/// </summary>
|
||||
None,
|
||||
/// <summary>
|
||||
/// 一次等待定时器
|
||||
/// </summary>
|
||||
OnceWaitTimer,
|
||||
/// <summary>
|
||||
/// 一次性定时器
|
||||
/// </summary>
|
||||
OnceTimer,
|
||||
/// <summary>
|
||||
/// 重复定时器
|
||||
/// </summary>
|
||||
RepeatedTimer
|
||||
}
|
||||
}
|
||||
837
FantasyNetTest/NBC/Core/Entitas/Entity.cs
Normal file
837
FantasyNetTest/NBC/Core/Entitas/Entity.cs
Normal file
@@ -0,0 +1,837 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using MongoDB.Bson.Serialization.Attributes;
|
||||
using NBC.Entitas.Interface;
|
||||
using NBC.Pool;
|
||||
using Newtonsoft.Json;
|
||||
using ProtoBuf;
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
// ReSharper disable MergeIntoPattern
|
||||
// ReSharper disable SuspiciousTypeConversion.Global
|
||||
// ReSharper disable NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
|
||||
// ReSharper disable CheckNamespace
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
#pragma warning disable CS8602 // Dereference of a possibly null reference.
|
||||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
|
||||
namespace NBC.Entitas
|
||||
{
|
||||
/// <summary>
|
||||
/// 用来表示一个Entity
|
||||
/// </summary>
|
||||
public interface IEntity : IDisposable, IPool { }
|
||||
|
||||
/// <summary>
|
||||
/// Entity的抽象类,任何Entity必须继承这个接口才可以使用
|
||||
/// </summary>
|
||||
public abstract partial class Entity : IEntity
|
||||
{
|
||||
#region Members
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,表示实体是否支持对象池。
|
||||
/// </summary>
|
||||
[BsonIgnore]
|
||||
[JsonIgnore]
|
||||
[ProtoIgnore]
|
||||
[IgnoreDataMember]
|
||||
private bool _isPool;
|
||||
/// <summary>
|
||||
/// 实体的Id
|
||||
/// </summary>
|
||||
[BsonId]
|
||||
[BsonElement]
|
||||
[BsonIgnoreIfDefault]
|
||||
[BsonDefaultValue(0L)]
|
||||
public long Id { get; protected set; }
|
||||
/// <summary>
|
||||
/// 实体的RunTimeId,其他系统可以通过这个Id发送Route消息,这个Id也可以理解为RouteId
|
||||
/// </summary>
|
||||
[BsonIgnore]
|
||||
[IgnoreDataMember]
|
||||
[ProtoIgnore]
|
||||
public long RuntimeId { get; protected set; }
|
||||
/// <summary>
|
||||
/// 当前实体是否已经被销毁
|
||||
/// </summary>
|
||||
[BsonIgnore]
|
||||
[JsonIgnore]
|
||||
[IgnoreDataMember]
|
||||
[ProtoIgnore]
|
||||
public bool IsDisposed => RuntimeId == 0;
|
||||
/// <summary>
|
||||
/// 当前实体所归属的Scene
|
||||
/// </summary>
|
||||
[BsonIgnore]
|
||||
[JsonIgnore]
|
||||
[IgnoreDataMember]
|
||||
[ProtoIgnore]
|
||||
public Scene Scene { get; protected set; }
|
||||
/// <summary>
|
||||
/// 实体的父实体
|
||||
/// </summary>
|
||||
[BsonIgnore]
|
||||
[JsonIgnore]
|
||||
[IgnoreDataMember]
|
||||
[ProtoIgnore]
|
||||
public Entity Parent { get; protected set; }
|
||||
/// <summary>
|
||||
/// 实体的真实Type
|
||||
/// </summary>
|
||||
[BsonIgnore]
|
||||
[JsonIgnore]
|
||||
[IgnoreDataMember]
|
||||
[ProtoIgnore]
|
||||
public Type Type { get; protected set; }
|
||||
|
||||
[BsonIgnore] [IgnoreDataMember] [ProtoIgnore] private EntitySortedDictionary<long, Entity> _tree;
|
||||
[BsonIgnore] [IgnoreDataMember] [ProtoIgnore] private EntitySortedDictionary<long, Entity> _multi;
|
||||
|
||||
/// <summary>
|
||||
/// 获得父Entity
|
||||
/// </summary>
|
||||
/// <typeparam name="T">父实体的泛型类型</typeparam>
|
||||
/// <returns></returns>
|
||||
public T GetParent<T>() where T : Entity, new()
|
||||
{
|
||||
return Parent as T;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前实体的RouteId。
|
||||
/// </summary>
|
||||
public long RouteId => RuntimeId;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Create
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个实体
|
||||
/// </summary>
|
||||
/// <param name="scene">所属的Scene</param>
|
||||
/// <param name="type">实体的Type</param>
|
||||
/// <param name="isPool">是否从对象池创建,如果选择的是,销毁的时候同样会进入对象池</param>
|
||||
/// <param name="isRunEvent">是否执行实体事件</param>
|
||||
/// <returns></returns>
|
||||
public static Entity Create(Scene scene, Type type, bool isPool, bool isRunEvent)
|
||||
{
|
||||
return Create(scene, type, scene.EntityIdFactory.Create, isPool, isRunEvent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个实体
|
||||
/// </summary>
|
||||
/// <param name="scene">所属的Scene</param>
|
||||
/// <param name="type">实体的Type</param>
|
||||
/// <param name="id">指定实体的Id</param>
|
||||
/// <param name="isPool">是否从对象池创建,如果选择的是,销毁的时候同样会进入对象池</param>
|
||||
/// <param name="isRunEvent">是否执行实体事件</param>
|
||||
/// <returns></returns>
|
||||
public static Entity Create(Scene scene, Type type, long id, bool isPool, bool isRunEvent)
|
||||
{
|
||||
if (!typeof(Entity).IsAssignableFrom(type))
|
||||
{
|
||||
throw new NotSupportedException($"{type.FullName} Type:{type.FullName} must inherit from Entity");
|
||||
}
|
||||
|
||||
Entity entity = null;
|
||||
|
||||
if (isPool)
|
||||
{
|
||||
entity = (Entity)scene.EntityPool.Rent(type);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!scene.TypeInstance.TryGetValue(type, out var createInstance))
|
||||
{
|
||||
createInstance = CreateInstance.CreateIPool(type);
|
||||
scene.TypeInstance[type] = createInstance;
|
||||
}
|
||||
|
||||
entity = (Entity)createInstance();
|
||||
}
|
||||
|
||||
entity.Scene = scene;
|
||||
entity.Type = type;
|
||||
entity.SetIsPool(isPool);
|
||||
entity.Id = id;
|
||||
entity.RuntimeId = scene.RuntimeIdFactory.Create;
|
||||
scene.AddEntity(entity);
|
||||
|
||||
if (isRunEvent)
|
||||
{
|
||||
scene.EntityComponent.Awake(entity);
|
||||
scene.EntityComponent.StartUpdate(entity);
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个实体
|
||||
/// </summary>
|
||||
/// <param name="scene">所属的Scene</param>
|
||||
/// <param name="isPool">是否从对象池创建,如果选择的是,销毁的时候同样会进入对象池</param>
|
||||
/// <param name="isRunEvent">是否执行实体事件</param>
|
||||
/// <typeparam name="T">要创建的实体泛型类型</typeparam>
|
||||
/// <returns></returns>
|
||||
public static T Create<T>(Scene scene, bool isPool, bool isRunEvent) where T : Entity, new()
|
||||
{
|
||||
return Create<T>(scene, scene.EntityIdFactory.Create, isPool, isRunEvent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个实体
|
||||
/// </summary>
|
||||
/// <param name="scene">所属的Scene</param>
|
||||
/// <param name="id">指定实体的Id</param>
|
||||
/// <param name="isPool">是否从对象池创建,如果选择的是,销毁的时候同样会进入对象池</param>
|
||||
/// <param name="isRunEvent">是否执行实体事件</param>
|
||||
/// <typeparam name="T">要创建的实体泛型类型</typeparam>
|
||||
/// <returns></returns>
|
||||
public static T Create<T>(Scene scene, long id, bool isPool, bool isRunEvent) where T : Entity, new()
|
||||
{
|
||||
var entity = isPool ? scene.EntityPool.Rent<T>() : new T();
|
||||
entity.Scene = scene;
|
||||
entity.Type = typeof(T);
|
||||
entity.SetIsPool(isPool);
|
||||
entity.Id = id;
|
||||
entity.RuntimeId = scene.RuntimeIdFactory.Create;
|
||||
scene.AddEntity(entity);
|
||||
|
||||
if (isRunEvent)
|
||||
{
|
||||
scene.EntityComponent.Awake(entity);
|
||||
scene.EntityComponent.StartUpdate(entity);
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AddComponent
|
||||
|
||||
/// <summary>
|
||||
/// 添加一个组件到当前实体上
|
||||
/// </summary>
|
||||
/// <param name="isPool">是否从对象池里创建</param>
|
||||
/// <typeparam name="T">要添加组件的泛型类型</typeparam>
|
||||
/// <returns>返回添加到实体上组件的实例</returns>
|
||||
public T AddComponent<T>(bool isPool = true) where T : Entity, new()
|
||||
{
|
||||
var id = SupportedMultiEntityChecker<T>.IsSupported ? Scene.EntityIdFactory.Create : Id;
|
||||
var entity = Create<T>(Scene, id, isPool, false);
|
||||
AddComponent(entity);
|
||||
Scene.EntityComponent.Awake(entity);
|
||||
Scene.EntityComponent.StartUpdate(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加一个组件到当前实体上
|
||||
/// </summary>
|
||||
/// <param name="id">要添加组件的Id</param>
|
||||
/// <param name="isPool">是否从对象池里创建</param>
|
||||
/// <typeparam name="T">要添加组件的泛型类型</typeparam>
|
||||
/// <returns>返回添加到实体上组件的实例</returns>
|
||||
public T AddComponent<T>(long id, bool isPool = true) where T : Entity, new()
|
||||
{
|
||||
var entity = Create<T>(Scene, id, isPool, false);
|
||||
AddComponent(entity);
|
||||
Scene.EntityComponent.Awake(entity);
|
||||
Scene.EntityComponent.StartUpdate(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加一个组件到当前实体上
|
||||
/// </summary>
|
||||
/// <param name="component">要添加的实体实例</param>
|
||||
public void AddComponent(Entity component)
|
||||
{
|
||||
if (this == component)
|
||||
{
|
||||
Log.Error("Cannot add oneself to one's own components");
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.IsDisposed)
|
||||
{
|
||||
Log.Error($"component is Disposed {component.Type.FullName}");
|
||||
return;
|
||||
}
|
||||
|
||||
var type = component.Type;
|
||||
component.Parent?.RemoveComponent(component, false);
|
||||
|
||||
if (component is ISupportedMultiEntity)
|
||||
{
|
||||
_multi ??= Scene.EntitySortedDictionaryPool.Rent();
|
||||
_multi.Add(component.Id, component);
|
||||
}
|
||||
else
|
||||
{
|
||||
var typeHashCode = Scene.EntityComponent.GetHashCode(type);;
|
||||
|
||||
if (_tree == null)
|
||||
{
|
||||
_tree = Scene.EntitySortedDictionaryPool.Rent();
|
||||
}
|
||||
else if (_tree.ContainsKey(typeHashCode))
|
||||
{
|
||||
Log.Error($"type:{type.FullName} If you want to add multiple components of the same type, please implement IMultiEntity");
|
||||
return;
|
||||
}
|
||||
|
||||
_tree.Add(typeHashCode, component);
|
||||
}
|
||||
|
||||
component.Parent = this;
|
||||
component.Scene = Scene;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加一个组件到当前实体上
|
||||
/// </summary>
|
||||
/// <param name="component">要添加的实体实例</param>
|
||||
/// <typeparam name="T">要添加组件的泛型类型</typeparam>
|
||||
public void AddComponent<T>(T component) where T : Entity
|
||||
{
|
||||
var type = typeof(T);
|
||||
|
||||
if (type == typeof(Entity))
|
||||
{
|
||||
Log.Error("Cannot add a generic Entity type as a component. Specify a more specific type.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this == component)
|
||||
{
|
||||
Log.Error("Cannot add oneself to one's own components");
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.IsDisposed)
|
||||
{
|
||||
Log.Error($"component is Disposed {type.FullName}");
|
||||
return;
|
||||
}
|
||||
|
||||
component.Parent?.RemoveComponent(component, false);
|
||||
|
||||
if (SupportedMultiEntityChecker<T>.IsSupported)
|
||||
{
|
||||
_multi ??= Scene.EntitySortedDictionaryPool.Rent();
|
||||
_multi.Add(component.Id, component);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
var typeHashCode = Scene.EntityComponent.GetHashCode(type);
|
||||
|
||||
if (_tree == null)
|
||||
{
|
||||
_tree = Scene.EntitySortedDictionaryPool.Rent();
|
||||
}
|
||||
else if (_tree.ContainsKey(typeHashCode))
|
||||
{
|
||||
Log.Error($"type:{type.FullName} If you want to add multiple components of the same type, please implement IMultiEntity");
|
||||
return;
|
||||
}
|
||||
|
||||
_tree.Add(typeHashCode, component);
|
||||
}
|
||||
|
||||
component.Parent = this;
|
||||
component.Scene = Scene;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加一个组件到当前实体上
|
||||
/// </summary>
|
||||
/// <param name="type">组件的类型</param>
|
||||
/// <param name="isPool">是否在对象池创建</param>
|
||||
/// <returns></returns>
|
||||
public Entity AddComponent(Type type, bool isPool = true)
|
||||
{
|
||||
var id = typeof(ISupportedMultiEntity).IsAssignableFrom(type) ? Scene.EntityIdFactory.Create : Id;
|
||||
var entity = Entity.Create(Scene, type, id, isPool, false);
|
||||
AddComponent(entity);
|
||||
Scene.EntityComponent.Awake(entity);
|
||||
Scene.EntityComponent.StartUpdate(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region HasComponent
|
||||
|
||||
/// <summary>
|
||||
/// 当前实体上是否有指定类型的组件
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public bool HasComponent<T>() where T : Entity, new()
|
||||
{
|
||||
return HasComponent(typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前实体上是否有指定类型的组件
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public bool HasComponent(Type type)
|
||||
{
|
||||
if (_tree == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _tree.ContainsKey(Scene.EntityComponent.GetHashCode(type));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前实体上是否有指定类型的组件
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public bool HasComponent<T>(long id) where T : Entity, ISupportedMultiEntity, new()
|
||||
{
|
||||
if (_multi == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _multi.ContainsKey(id);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetComponent
|
||||
|
||||
/// <summary>
|
||||
/// 当前实体上查找一个字实体
|
||||
/// </summary>
|
||||
/// <typeparam name="T">要查找实体泛型类型</typeparam>
|
||||
/// <returns>查找的实体实例</returns>
|
||||
public T GetComponent<T>() where T : Entity, new()
|
||||
{
|
||||
if (_tree == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var typeHashCode = Scene.EntityComponent.GetHashCode(typeof(T));
|
||||
return _tree.TryGetValue(typeHashCode, out var component) ? (T)component : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前实体上查找一个字实体
|
||||
/// </summary>
|
||||
/// <param name="type">要查找实体类型</param>
|
||||
/// <returns>查找的实体实例</returns>
|
||||
public Entity GetComponent(Type type)
|
||||
{
|
||||
if (_tree == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var typeHashCode = Scene.EntityComponent.GetHashCode(type);
|
||||
return _tree.TryGetValue(typeHashCode, out var component) ? component : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前实体上查找一个字实体
|
||||
/// </summary>
|
||||
/// <param name="id">要查找实体的Id</param>
|
||||
/// <typeparam name="T">要查找实体泛型类型</typeparam>
|
||||
/// <returns>查找的实体实例</returns>
|
||||
public T GetComponent<T>(long id) where T : Entity, ISupportedMultiEntity, new()
|
||||
{
|
||||
if (_multi == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return _multi.TryGetValue(id, out var entity) ? (T)entity : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前实体上查找一个字实体,如果没有就创建一个新的并添加到当前实体上
|
||||
/// </summary>
|
||||
/// <param name="isPool">是否从对象池创建</param>
|
||||
/// <typeparam name="T">要查找或添加实体泛型类型</typeparam>
|
||||
/// <returns>查找的实体实例</returns>
|
||||
public T GetOrAddComponent<T>(bool isPool = true) where T : Entity, new()
|
||||
{
|
||||
return GetComponent<T>() ?? AddComponent<T>(isPool);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region RemoveComponent
|
||||
|
||||
/// <summary>
|
||||
/// 当前实体下删除一个实体
|
||||
/// </summary>
|
||||
/// <param name="isDispose">是否执行删除实体的Dispose方法</param>
|
||||
/// <typeparam name="T">实体的泛型类型</typeparam>
|
||||
/// <exception cref="NotSupportedException"></exception>
|
||||
public void RemoveComponent<T>(bool isDispose = true) where T : Entity, new()
|
||||
{
|
||||
if (SupportedMultiEntityChecker<T>.IsSupported)
|
||||
{
|
||||
throw new NotSupportedException($"{typeof(T).FullName} message:Cannot delete components that implement the ISupportedMultiEntity interface");
|
||||
}
|
||||
|
||||
if (_tree == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var type = typeof(T);
|
||||
var typeHashCode = Scene.EntityComponent.GetHashCode(type);
|
||||
if (!_tree.TryGetValue(typeHashCode, out var component))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_tree.Remove(typeHashCode);
|
||||
|
||||
if (_tree.Count == 0)
|
||||
{
|
||||
Scene.EntitySortedDictionaryPool.Return(_tree);
|
||||
_tree = null;
|
||||
}
|
||||
|
||||
if (isDispose)
|
||||
{
|
||||
component.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前实体下删除一个实体
|
||||
/// </summary>
|
||||
/// <param name="id">要删除的实体Id</param>
|
||||
/// <param name="isDispose">是否执行删除实体的Dispose方法</param>
|
||||
/// <typeparam name="T">实体的泛型类型</typeparam>
|
||||
public void RemoveComponent<T>(long id, bool isDispose = true) where T : Entity, ISupportedMultiEntity, new()
|
||||
{
|
||||
if (_multi == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_multi.TryGetValue(id, out var component))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_multi.Remove(component.Id);
|
||||
if (_multi.Count == 0)
|
||||
{
|
||||
Scene.EntitySortedDictionaryPool.Return(_multi);
|
||||
_multi = null;
|
||||
}
|
||||
|
||||
if (isDispose)
|
||||
{
|
||||
component.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前实体下删除一个实体
|
||||
/// </summary>
|
||||
/// <param name="component">要删除的实体实例</param>
|
||||
/// <param name="isDispose">是否执行删除实体的Dispose方法</param>
|
||||
public void RemoveComponent(Entity component, bool isDispose = true)
|
||||
{
|
||||
if (this == component)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (component is ISupportedMultiEntity)
|
||||
{
|
||||
if (_multi != null)
|
||||
{
|
||||
if (!_multi.ContainsKey(component.Id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_multi.Remove(component.Id);
|
||||
if (_multi.Count == 0)
|
||||
{
|
||||
Scene.EntitySortedDictionaryPool.Return(_multi);
|
||||
_multi = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_tree != null)
|
||||
{
|
||||
var typeHashCode = Scene.EntityComponent.GetHashCode(component.Type);
|
||||
if (!_tree.ContainsKey(typeHashCode))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_tree.Remove(typeHashCode);
|
||||
|
||||
if (_tree.Count == 0)
|
||||
{
|
||||
Scene.EntitySortedDictionaryPool.Return(_tree);
|
||||
_tree = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (isDispose)
|
||||
{
|
||||
component.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前实体下删除一个实体
|
||||
/// </summary>
|
||||
/// <param name="component">要删除的实体实例</param>
|
||||
/// <param name="isDispose">是否执行删除实体的Dispose方法</param>
|
||||
/// <typeparam name="T">实体的泛型类型</typeparam>
|
||||
public void RemoveComponent<T>(T component, bool isDispose = true) where T : Entity
|
||||
{
|
||||
if (this == component)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(Entity))
|
||||
{
|
||||
Log.Error("Cannot remove a generic Entity type as a component. Specify a more specific type.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (SupportedMultiEntityChecker<T>.IsSupported)
|
||||
{
|
||||
if (_multi != null)
|
||||
{
|
||||
if (!_multi.ContainsKey(component.Id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_multi.Remove(component.Id);
|
||||
if (_multi.Count == 0)
|
||||
{
|
||||
Scene.EntitySortedDictionaryPool.Return(_multi);
|
||||
_multi = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_tree != null)
|
||||
{
|
||||
var typeHashCode = Scene.EntityComponent.GetHashCode(typeof(T));
|
||||
if (!_tree.ContainsKey(typeHashCode))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_tree.Remove(typeHashCode);
|
||||
|
||||
if (_tree.Count == 0)
|
||||
{
|
||||
Scene.EntitySortedDictionaryPool.Return(_tree);
|
||||
_tree = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (isDispose)
|
||||
{
|
||||
component.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Deserialize
|
||||
|
||||
/// <summary>
|
||||
/// 反序列化当前实体,因为在数据库加载过来的或通过协议传送过来的实体并没有跟当前Scene做关联。
|
||||
/// 所以必须要执行一下这个反序列化的方法才可以使用。
|
||||
/// </summary>
|
||||
/// <param name="scene">Scene</param>
|
||||
/// <param name="resetId">是否是重新生成实体的Id,如果是数据库加载过来的一般是不需要的</param>
|
||||
public void Deserialize(Scene scene, bool resetId = false)
|
||||
{
|
||||
if (RuntimeId != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Scene = scene;
|
||||
Type ??= GetType();
|
||||
RuntimeId = Scene.RuntimeIdFactory.Create;
|
||||
if (resetId)
|
||||
{
|
||||
Id = RuntimeId;
|
||||
}
|
||||
scene.AddEntity(this);
|
||||
scene.EntityComponent.Deserialize(this);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (RuntimeId != 0)
|
||||
{
|
||||
scene.RemoveEntity(RuntimeId);
|
||||
}
|
||||
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ForEach
|
||||
|
||||
/// <summary>
|
||||
/// 查询当前实体下的实现了ISupportedMultiEntity接口的实体
|
||||
/// </summary>
|
||||
[BsonIgnore]
|
||||
[JsonIgnore]
|
||||
[IgnoreDataMember]
|
||||
[ProtoIgnore]
|
||||
public IEnumerable<Entity> ForEachMultiEntity
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_multi == null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var (_, supportedMultiEntity) in _multi)
|
||||
{
|
||||
yield return supportedMultiEntity;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 查找当前实体下的所有实体,不包括实现ISupportedMultiEntity接口的实体
|
||||
/// </summary>
|
||||
[BsonIgnore]
|
||||
[JsonIgnore]
|
||||
[IgnoreDataMember]
|
||||
[ProtoIgnore]
|
||||
public IEnumerable<Entity> ForEachEntity
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_tree == null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var (_, entity) in _tree)
|
||||
{
|
||||
yield return entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Dispose
|
||||
|
||||
/// <summary>
|
||||
/// 销毁当前实体,销毁后会自动销毁当前实体下的所有实体。
|
||||
/// </summary>
|
||||
public virtual void Dispose()
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var scene = Scene;
|
||||
var runTimeId = RuntimeId;
|
||||
RuntimeId = 0;
|
||||
|
||||
if (_tree != null)
|
||||
{
|
||||
foreach (var (_, entity) in _tree)
|
||||
{
|
||||
entity.Dispose();
|
||||
}
|
||||
|
||||
_tree.Clear();
|
||||
scene.EntitySortedDictionaryPool.Return(_tree);
|
||||
_tree = null;
|
||||
}
|
||||
|
||||
if (_multi != null)
|
||||
{
|
||||
foreach (var (_, entity) in _multi)
|
||||
{
|
||||
entity.Dispose();
|
||||
}
|
||||
|
||||
_multi.Clear();
|
||||
scene.EntitySortedDictionaryPool.Return(_multi);
|
||||
_multi = null;
|
||||
}
|
||||
scene.EntityComponent.Destroy(this);
|
||||
|
||||
if (Parent != null && Parent != this && !Parent.IsDisposed)
|
||||
{
|
||||
Parent.RemoveComponent(this, false);
|
||||
Parent = null;
|
||||
}
|
||||
|
||||
Id = 0;
|
||||
Scene = null;
|
||||
Parent = null;
|
||||
scene.RemoveEntity(runTimeId);
|
||||
|
||||
if (IsPool())
|
||||
{
|
||||
scene.EntityPool.Return(Type, this);
|
||||
}
|
||||
|
||||
Type = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Pool
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
66
FantasyNetTest/NBC/Core/Entitas/EntityPool.cs
Normal file
66
FantasyNetTest/NBC/Core/Entitas/EntityPool.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System.Collections.Generic;
|
||||
using NBC.Pool;
|
||||
|
||||
#pragma warning disable CS8714 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint.
|
||||
|
||||
namespace NBC.Entitas
|
||||
{
|
||||
internal sealed class EntityPool : PoolCore
|
||||
{
|
||||
public EntityPool() : base(4096) { }
|
||||
}
|
||||
|
||||
internal sealed class EntityList<T> : List<T>, IPool where T : Entity
|
||||
{
|
||||
private bool _isPool;
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class EntityListPool<T> : PoolCore<EntityList<T>> where T : Entity
|
||||
{
|
||||
public EntityListPool() : base(4096) { }
|
||||
}
|
||||
|
||||
internal sealed class EntitySortedDictionary<TM, TN> : SortedDictionary<TM, TN>, IPool where TN : Entity
|
||||
{
|
||||
private bool _isPool;
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class EntitySortedDictionaryPool<TM, TN> : PoolCore<EntitySortedDictionary<TM, TN>> where TN : Entity
|
||||
{
|
||||
public EntitySortedDictionaryPool() : base(4096) { }
|
||||
}
|
||||
}
|
||||
59
FantasyNetTest/NBC/Core/Entitas/EntityReference.cs
Normal file
59
FantasyNetTest/NBC/Core/Entitas/EntityReference.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
namespace NBC.Entitas
|
||||
{
|
||||
/// <summary>
|
||||
/// 实体引用检查组件
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public struct EntityReference<T> where T : Entity
|
||||
{
|
||||
private T _entity;
|
||||
private readonly long _runTimeId;
|
||||
|
||||
private EntityReference(T t)
|
||||
{
|
||||
if (t == null)
|
||||
{
|
||||
_entity = null;
|
||||
_runTimeId = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
_entity = t;
|
||||
_runTimeId = t.RuntimeId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一个实体转换为EntityReference
|
||||
/// </summary>
|
||||
/// <param name="t">实体泛型类型</param>
|
||||
/// <returns>返回一个EntityReference</returns>
|
||||
public static implicit operator EntityReference<T>(T t)
|
||||
{
|
||||
return new EntityReference<T>(t);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一个EntityReference转换为实体
|
||||
/// </summary>
|
||||
/// <param name="v">实体泛型类型</param>
|
||||
/// <returns>当实体已经被销毁过会返回null</returns>
|
||||
public static implicit operator T(EntityReference<T> v)
|
||||
{
|
||||
if (v._entity == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (v._entity.RuntimeId != v._runTimeId)
|
||||
{
|
||||
v._entity = null;
|
||||
}
|
||||
|
||||
return v._entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
namespace NBC.Entitas.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// Entity保存到数据库的时候会根据子组件设置分离存储特性分表存储在不同的集合表中
|
||||
/// </summary>
|
||||
public interface ISingleCollectionRoot { }
|
||||
public static class SingleCollectionRootChecker<T> where T : Entity
|
||||
{
|
||||
public static bool IsSupported { get; }
|
||||
|
||||
static SingleCollectionRootChecker()
|
||||
{
|
||||
IsSupported = typeof(ISingleCollectionRoot).IsAssignableFrom(typeof(T));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 57298dd8265554d6087d79ef5a6e89cc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
namespace NBC.Entitas.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// Entity支持数据库
|
||||
/// </summary>
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public interface ISupportedDataBase { }
|
||||
|
||||
public static class SupportedDataBaseChecker<T> where T : Entity
|
||||
{
|
||||
public static bool IsSupported { get; }
|
||||
|
||||
static SupportedDataBaseChecker()
|
||||
{
|
||||
IsSupported = typeof(ISupportedDataBase).IsAssignableFrom(typeof(T));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae64989e8b146443e99c66290b20a3c8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
namespace NBC.Entitas.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// 支持再一个组件里添加多个同类型组件
|
||||
/// </summary>
|
||||
public interface ISupportedMultiEntity : IDisposable { }
|
||||
|
||||
public static class SupportedMultiEntityChecker<T> where T : Entity
|
||||
{
|
||||
public static bool IsSupported { get; }
|
||||
|
||||
static SupportedMultiEntityChecker()
|
||||
{
|
||||
IsSupported = typeof(ISupportedMultiEntity).IsAssignableFrom(typeof(T));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36105e7ed5c974fdabf37eead97ef95c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
namespace NBC.Entitas.Interface
|
||||
{
|
||||
// Entity是单一集合、保存到数据库的时候不会跟随父组件保存在一个集合里、会单独保存在一个集合里
|
||||
// 需要配合SingleCollectionAttribute一起使用、如在Entity类头部定义SingleCollectionAttribute(typeOf(Unit))
|
||||
// SingleCollectionAttribute用来定义这个Entity是属于哪个Entity的子集
|
||||
/// <summary>
|
||||
/// 定义实体支持单一集合存储的接口。当实体需要单独存储在一个集合中,并且在保存到数据库时不会与父组件一起保存在同一个集合中时,应实现此接口。
|
||||
/// </summary>
|
||||
public interface ISupportedSingleCollection { }
|
||||
public static class SupportedSingleCollectionChecker<T> where T : Entity
|
||||
{
|
||||
public static bool IsSupported { get; }
|
||||
|
||||
static SupportedSingleCollectionChecker()
|
||||
{
|
||||
IsSupported = typeof(ISupportedSingleCollection).IsAssignableFrom(typeof(T));
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 表示用于指定实体的单一集合存储属性。此属性用于配合 <see cref="ISupportedSingleCollection"/> 接口使用,
|
||||
/// 用于定义实体属于哪个父实体的子集合,以及在数据库中使用的集合名称。
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
|
||||
public class SingleCollectionAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取父实体的类型,指示此实体是属于哪个父实体的子集合。
|
||||
/// </summary>
|
||||
public readonly Type RootType;
|
||||
/// <summary>
|
||||
/// 获取在数据库中使用的集合名称。
|
||||
/// </summary>
|
||||
public readonly string CollectionName;
|
||||
/// <summary>
|
||||
/// 初始化 <see cref="SingleCollectionAttribute"/> 类的新实例,指定父实体类型和集合名称。
|
||||
/// </summary>
|
||||
/// <param name="rootType">父实体的类型。</param>
|
||||
/// <param name="collectionName">在数据库中使用的集合名称。</param>
|
||||
public SingleCollectionAttribute(Type rootType, string collectionName)
|
||||
{
|
||||
RootType = rootType;
|
||||
CollectionName = collectionName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: afd453d04ac254206a9bb83664a888a0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace NBC.Entitas.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// Entity支持传送
|
||||
/// </summary>
|
||||
public interface ISupportedTransfer { }
|
||||
public static class SupportedTransferChecker<T> where T : Entity
|
||||
{
|
||||
public static bool IsSupported { get; }
|
||||
|
||||
static SupportedTransferChecker()
|
||||
{
|
||||
IsSupported = typeof(ISupportedTransfer).IsAssignableFrom(typeof(T));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a127a7c6e345a42e3860b804457f0fe5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using NBC.Async;
|
||||
|
||||
namespace NBC.Entitas.Interface
|
||||
{
|
||||
internal interface IAwakeSystem : IEntitiesSystem { }
|
||||
/// <summary>
|
||||
/// 实体的Awake事件的抽象接口
|
||||
/// </summary>
|
||||
/// <typeparam name="T">实体的泛型类型</typeparam>
|
||||
public abstract class AwakeSystem<T> : IAwakeSystem where T : Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 实体的类型
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Type EntitiesType() => typeof(T);
|
||||
/// <summary>
|
||||
/// 事件的抽象方法,需要自己实现这个方法
|
||||
/// </summary>
|
||||
/// <param name="self">触发事件的实体实例</param>
|
||||
protected abstract void Awake(T self);
|
||||
/// <summary>
|
||||
/// 框架内部调用的触发Awake的方法。
|
||||
/// </summary>
|
||||
/// <param name="self">触发事件的实体实例</param>
|
||||
public void Invoke(Entity self)
|
||||
{
|
||||
Awake((T) self);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe2caf80007df454bb76729547780d28
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
|
||||
namespace NBC.Entitas.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// 自定义组件事件系统接口
|
||||
/// 如果需要自定义组件事件系统,请继承此接口。
|
||||
/// 这个接口内部使用。不对外开放。
|
||||
/// </summary>
|
||||
internal interface ICustomEntitiesSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// 事件类型
|
||||
/// 用于触发这个组件事件关键因素。
|
||||
/// </summary>
|
||||
int CustomEventType { get; }
|
||||
/// <summary>
|
||||
/// 实体的类型
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Type EntitiesType();
|
||||
/// <summary>
|
||||
/// 框架内部调用的触发事件方法
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
void Invoke(Entity entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自定义组件事件系统抽象类
|
||||
/// 如果需要自定义组件事件系统,请继承此抽象类。
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public abstract class CustomSystem<T> : ICustomEntitiesSystem where T : Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 这个1表示是一个自定义事件类型,执行这个事件是时候需要用到这个1.
|
||||
/// </summary>
|
||||
public abstract int CustomEventType { get; }
|
||||
/// <summary>
|
||||
/// 事件的抽象方法,需要自己实现这个方法
|
||||
/// </summary>
|
||||
/// <param name="self">触发事件的实体实例</param>
|
||||
protected abstract void Custom(T self);
|
||||
/// <summary>
|
||||
/// 实体的类型
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public abstract Type EntitiesType();
|
||||
/// <summary>
|
||||
/// 框架内部调用的触发Awake的方法。
|
||||
/// </summary>
|
||||
/// <param name="self">触发事件的实体实例</param>
|
||||
public void Invoke(Entity self)
|
||||
{
|
||||
Custom((T) self);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8babd948f5d51430db4d8ac333b25d54
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using NBC.Async;
|
||||
|
||||
namespace NBC.Entitas.Interface
|
||||
{
|
||||
internal interface IDeserializeSystem : IEntitiesSystem { }
|
||||
/// <summary>
|
||||
/// 实体的反序列化事件的抽象接口
|
||||
/// </summary>
|
||||
/// <typeparam name="T">实体的泛型数据</typeparam>
|
||||
public abstract class DeserializeSystem<T> : IDeserializeSystem where T : Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 实体的类型
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Type EntitiesType() => typeof(T);
|
||||
/// <summary>
|
||||
/// 事件的抽象方法,需要自己实现这个方法
|
||||
/// </summary>
|
||||
/// <param name="self">触发事件的实体实例</param>
|
||||
protected abstract void Deserialize(T self);
|
||||
/// <summary>
|
||||
/// 框架内部调用的触发Deserialize的方法
|
||||
/// </summary>
|
||||
/// <param name="self">触发事件的实体实例</param>
|
||||
public void Invoke(Entity self)
|
||||
{
|
||||
Deserialize((T) self);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9d2fff74aed249e8aa0acbb122614c2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user