提交修改

This commit is contained in:
Bob.Song
2025-10-29 17:59:36 +08:00
parent b390cf2051
commit 5750c4fe56
2148 changed files with 13962 additions and 5549 deletions

View File

@@ -0,0 +1,11 @@
# Unity YAML 文件使用 LF 换行符
*.meta text eol=lf
*.asmdef text eol=lf
*.unity text eol=lf
*.prefab text eol=lf
*.mat text eol=lf
*.asset text eol=lf
*.json text eol=lf
# Source Generator DLL 是二进制文件
*.dll binary

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 852fc04751ab07349a96fb31b98b9bf7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 78c51fb0052fac049a29ae6289cda649
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,16 @@
using System;
using UnityEditor;
namespace Fantasy
{
internal static class CheckUnityVersion
{
[InitializeOnLoadMethod]
private static void OnInitializeOnLoad()
{
#if !UNITY_2022_3_OR_NEWER
Debug.LogError("Fantasy支持的最低版本为Unity2022.3.622f2");
#endif
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 208434147045e0d4bae1287de7e28fa3
timeCreated: 1725943424

View File

@@ -0,0 +1,18 @@
{
"name": "Fantasy.Editor",
"rootNamespace": "",
"references": [
"GUID:0b7224b83ba514121aa026f3857f820a"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: b5d7370e11934b547a4e8dac960ea72e
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,56 @@
using System.IO;
using UnityEditor;
using UnityEngine;
namespace Fantasy
{
[InitializeOnLoad]
public static class FantasyStartup
{
private const string ScriptAssemblies = "Library/ScriptAssemblies/";
static FantasyStartup()
{
if (!FantasySettingsScriptableObject.Instance.autoCopyAssembly)
{
return;
}
var hotUpdatePath = FantasySettingsScriptableObject.Instance.hotUpdatePath;
if (string.IsNullOrEmpty(hotUpdatePath))
{
Debug.LogError("请先在菜单Fantasy-Fantasy Settings里设置自动拷贝程序集输出目录位置");
return;
}
if (!Directory.Exists(hotUpdatePath))
{
Directory.CreateDirectory(hotUpdatePath);
}
else
{
foreach (var file in Directory.GetFiles(hotUpdatePath))
{
File.Delete(file);
}
}
// ReSharper disable once StringLastIndexOfIsCultureSpecific.1
if (hotUpdatePath.LastIndexOf("/") != hotUpdatePath.Length - 1)
{
FantasySettingsScriptableObject.Instance.hotUpdatePath += "/";
hotUpdatePath = FantasySettingsScriptableObject.Instance.hotUpdatePath;
}
foreach (var instanceHotUpdateAssemblyDefinition in FantasySettingsScriptableObject.Instance.hotUpdateAssemblyDefinitions)
{
var dll = instanceHotUpdateAssemblyDefinition.name;
File.Copy($"{ScriptAssemblies}{dll}.dll", $"{hotUpdatePath}/{dll}.dll.bytes", true);
File.Copy($"{ScriptAssemblies}{dll}.pdb", $"{hotUpdatePath}/{dll}.pdb.bytes", true);
}
AssetDatabase.Refresh();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 13e9d2b99522b124680b7723aaded8d9
timeCreated: 1688276977

View File

@@ -0,0 +1,64 @@
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
namespace Fantasy
{
public class LinkXmlGenerator
{
private const string LinkPath = "Assets/link.xml";
// 在Unity编辑器中运行该方法来生成link.xml文件
public static void GenerateLinkXml()
{
using (var writer = new StreamWriter("Assets/link.xml"))
{
var assemblyHashSet = new HashSet<string>();
foreach (var assembly in FantasySettingsScriptableObject.Instance.includeAssembly)
{
assemblyHashSet.Add(assembly);
}
if (FantasySettingsScriptableObject.Instance?.linkAssemblyDefinitions != null)
{
foreach (var assemblyDefinition in FantasySettingsScriptableObject.Instance.linkAssemblyDefinitions)
{
assemblyHashSet.Add(assemblyDefinition.name);
}
}
if (assemblyHashSet.Count == 0)
{
return;
}
writer.WriteLine("<linker>");
foreach (var assembly in assemblyHashSet)
{
GenerateLinkXml(writer, assembly, LinkPath);
Debug.Log($"{assembly} Link generation completed");
}
writer.WriteLine("</linker>");
}
AssetDatabase.Refresh();
Debug.Log("link.xml generated successfully!");
}
private static void GenerateLinkXml(StreamWriter writer, string assemblyName, string outputPath)
{
var assembly = System.Reflection.Assembly.Load(assemblyName);
var types = assembly.GetTypes();
writer.WriteLine($" <assembly fullname=\"{assembly.GetName().Name}\">");
foreach (var type in types)
{
var typeName = type.FullName.Replace('<', '+').Replace('>', '+');
writer.WriteLine($" <type fullname=\"{typeName}\" preserve=\"all\"/>");
}
writer.WriteLine(" </assembly>");
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3360ff806a3d27d478e0e554ebbb291b
timeCreated: 1722743236

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4481b9186fbddb04f96e2e6e56e9ebfc
timeCreated: 1688277110

View File

@@ -0,0 +1,13 @@
using UnityEditor;
namespace Fantasy
{
public class FantasySettings
{
[MenuItem("Fantasy/Fantasy Settings")]
public static void OpenFantasySettings()
{
SettingsService.OpenProjectSettings("Project/Fantasy Settings");
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6a31e33b6eab0e847a6e836cf0f9835d
timeCreated: 1686913667

View File

@@ -0,0 +1,725 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace Fantasy
{
public class FantasySettingsProvider : SettingsProvider
{
private SerializedObject _serializedObject;
private SerializedProperty _autoCopyAssembly;
private SerializedProperty _hotUpdatePath;
private SerializedProperty _hotUpdateAssemblyDefinitions;
private SerializedProperty _linkAssemblyDefinitions;
private SerializedProperty _includeAssembly;
private bool _showLinkXmlConfig = true; // 控制是否显示 Link.xml 配置
public FantasySettingsProvider() : base("Project/Fantasy Settings", SettingsScope.Project) { }
public override void OnActivate(string searchContext, VisualElement rootElement)
{
Init();
base.OnActivate(searchContext, rootElement);
}
public override void OnDeactivate()
{
base.OnDeactivate();
FantasySettingsScriptableObject.Save();
}
private void Init()
{
_serializedObject?.Dispose();
_serializedObject = new SerializedObject(FantasySettingsScriptableObject.Instance);
_autoCopyAssembly = _serializedObject.FindProperty("autoCopyAssembly");
_hotUpdatePath = _serializedObject.FindProperty("hotUpdatePath");
_hotUpdateAssemblyDefinitions = _serializedObject.FindProperty("hotUpdateAssemblyDefinitions");
_linkAssemblyDefinitions = _serializedObject.FindProperty("linkAssemblyDefinitions");
_includeAssembly = _serializedObject.FindProperty("includeAssembly");
}
public override void OnGUI(string searchContext)
{
if (_serializedObject == null || !_serializedObject.targetObject)
{
Init();
}
using (CreateSettingsWindowGUIScope())
{
_serializedObject!.Update();
// ============ Fantasy 框架集成检测 ============
DrawCscRspInstallationSection();
DrawSectionDivider();
EditorGUILayout.Space(10);
// ============ 程序集自动拷贝设置 ============
EditorGUI.BeginChangeCheck();
DrawAssemblyCopySection();
DrawSectionDivider();
// ============ Link.xml 生成设置 ============
DrawLinkXmlSection();
if (EditorGUI.EndChangeCheck())
{
_serializedObject.ApplyModifiedProperties();
FantasySettingsScriptableObject.Save();
EditorApplication.RepaintHierarchyWindow();
}
base.OnGUI(searchContext);
}
}
private IDisposable CreateSettingsWindowGUIScope()
{
var unityEditorAssembly = System.Reflection.Assembly.GetAssembly(typeof(EditorWindow));
var type = unityEditorAssembly.GetType("UnityEditor.SettingsWindow+GUIScope");
return Activator.CreateInstance(type) as IDisposable;
}
/// <summary>
/// 绘制 csc.rsp 安装状态区域
/// </summary>
private void DrawCscRspInstallationSection()
{
bool isInstalled = CheckCscRspStatus(out bool fileExists, out bool hasDefine);
// 状态框
if (isInstalled)
{
// 已安装 - 绿色背景框
GUIStyle boxStyle = new GUIStyle(GUI.skin.box)
{
padding = new RectOffset(10, 10, 10, 10),
margin = new RectOffset(0, 0, 5, 5)
};
Color originalColor = GUI.color;
GUI.color = new Color(0.7f, 1f, 0.7f); // 绿色背景
EditorGUILayout.BeginVertical(boxStyle);
GUI.color = originalColor;
GUIStyle installedButtonStyle = new GUIStyle(GUI.skin.button)
{
fontSize = 13,
fontStyle = FontStyle.Bold,
fixedHeight = 35
};
Color originalBgColor = GUI.backgroundColor;
GUI.backgroundColor = new Color(0.3f, 0.8f, 0.3f); // 绿色
GUI.enabled = false; // 禁用按钮,只显示状态
GUILayout.Button("✓ FANTASY_UNITY 已安装", installedButtonStyle);
GUI.enabled = true;
GUI.backgroundColor = originalBgColor;
EditorGUILayout.Space(3);
EditorGUILayout.HelpBox("编译器配置正确,框架功能已启用", MessageType.Info);
EditorGUILayout.EndVertical();
}
else
{
// 未安装 - 橙黄色背景框
GUIStyle boxStyle = new GUIStyle(GUI.skin.box)
{
padding = new RectOffset(10, 10, 10, 10),
margin = new RectOffset(0, 0, 5, 5)
};
Color originalColor = GUI.color;
GUI.color = Color.red; // 橙黄色背景
EditorGUILayout.BeginVertical(boxStyle);
GUI.color = originalColor;
EditorGUILayout.Space(8);
// 醒目的大按钮
GUIStyle buttonStyle = new GUIStyle(GUI.skin.button)
{
fontSize = 13,
fontStyle = FontStyle.Bold,
fixedHeight = 35
};
Color originalBgColor = GUI.backgroundColor;
GUI.backgroundColor = new Color(0.3f, 0.8f, 1f);
if (GUILayout.Button("点击安装 FANTASY_UNITY", buttonStyle))
{
InstallFantasyUnityDefine();
}
GUI.backgroundColor = originalBgColor;
EditorGUILayout.Space(3);
EditorGUILayout.HelpBox("安装后可能需要重新编译项目才能生效", MessageType.Info);
EditorGUILayout.EndVertical();
}
}
/// <summary>
/// 检测 csc.rsp 文件状态
/// </summary>
/// <param name="fileExists">文件是否存在</param>
/// <param name="hasDefine">是否包含 FANTASY_UNITY 定义</param>
/// <returns>是否已正确安装</returns>
private bool CheckCscRspStatus(out bool fileExists, out bool hasDefine)
{
string cscRspPath = Path.Combine(Application.dataPath, "csc.rsp");
fileExists = File.Exists(cscRspPath);
hasDefine = false;
if (fileExists)
{
string content = File.ReadAllText(cscRspPath);
// 使用正则表达式精确匹配 FANTASY_UNITY确保是完整的单词不是其他定义的一部分
// 匹配条件FANTASY_UNITY 后面是分号、空白字符、换行或文件结束
hasDefine = System.Text.RegularExpressions.Regex.IsMatch(
content,
@"\bFANTASY_UNITY\b"
);
}
return fileExists && hasDefine;
}
/// <summary>
/// 安装 FANTASY_UNITY 定义到 csc.rsp 文件
/// </summary>
private void InstallFantasyUnityDefine()
{
string cscRspPath = Path.Combine(Application.dataPath, "csc.rsp");
try
{
if (!File.Exists(cscRspPath))
{
// 创建新文件
File.WriteAllText(cscRspPath, "-define:FANTASY_UNITY\n");
}
else
{
// 读取现有内容
string content = File.ReadAllText(cscRspPath);
// 使用正则表达式精确检测,避免误判(例如 FANTASY_UNITY123
bool hasFantasyUnity = System.Text.RegularExpressions.Regex.IsMatch(
content,
@"\bFANTASY_UNITY\b"
);
if (!hasFantasyUnity)
{
// 查找是否已有 -define: 行
string[] lines = content.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
bool defineLineFound = false;
for (int i = 0; i < lines.Length; i++)
{
if (lines[i].TrimStart().StartsWith("-define:"))
{
// 添加到现有的 define 行
if (lines[i].EndsWith(";"))
{
lines[i] = lines[i] + "FANTASY_UNITY";
}
else
{
lines[i] = lines[i] + ";FANTASY_UNITY";
}
defineLineFound = true;
break;
}
}
if (defineLineFound)
{
content = string.Join("\n", lines) + "\n";
}
else
{
// 添加新的 define 行到文件开头
content = "-define:FANTASY_UNITY\n" + content;
}
File.WriteAllText(cscRspPath, content);
}
}
AssetDatabase.Refresh();
EditorUtility.DisplayDialog("成功", "FANTASY_UNITY 已经安装成功。\n\n重新编译后生效。", "确定");
}
catch (Exception ex)
{
Debug.LogError($"安装 FANTASY_UNITY 失败: {ex.Message}");
EditorUtility.DisplayDialog("错误", $"安装失败:\n{ex.Message}", "确定");
}
}
/// <summary>
/// 绘制分隔线
/// </summary>
private void DrawSectionDivider()
{
EditorGUILayout.Space(5);
Rect rect = EditorGUILayout.GetControlRect(false, 1);
rect.height = 1;
// 绘制细线
EditorGUI.DrawRect(rect, new Color(0.5f, 0.5f, 0.5f, 0.3f));
EditorGUILayout.Space(5);
}
/// <summary>
/// 绘制程序集自动拷贝设置区域
/// </summary>
private void DrawAssemblyCopySection()
{
// 标题
GUIStyle titleStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 14,
alignment = TextAnchor.MiddleLeft
};
EditorGUILayout.LabelField("程序集自动拷贝", titleStyle);
EditorGUILayout.Space(5);
// 功能说明
GUIStyle descStyle = new GUIStyle(EditorStyles.wordWrappedLabel)
{
fontSize = 11,
normal = { textColor = Color.gray }
};
EditorGUILayout.LabelField("Unity 编译后自动将指定的程序集文件DLL + PDB复制到目标目录用于热更新或其他用途", descStyle);
EditorGUILayout.Space(8);
// 主开关区域
GUIStyle boxStyle = new GUIStyle(GUI.skin.box)
{
padding = new RectOffset(12, 12, 12, 12),
margin = new RectOffset(0, 0, 0, 8)
};
EditorGUILayout.BeginVertical(boxStyle);
// 开关 - 更大更明显
EditorGUILayout.BeginHorizontal();
GUIStyle bigToggleStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 13
};
bool newValue = EditorGUILayout.ToggleLeft("启用自动拷贝", _autoCopyAssembly.boolValue, bigToggleStyle);
if (newValue != _autoCopyAssembly.boolValue)
{
_autoCopyAssembly.boolValue = newValue;
}
GUILayout.FlexibleSpace();
if (_autoCopyAssembly.boolValue)
{
GUIStyle statusLabel = new GUIStyle(EditorStyles.miniLabel)
{
fontSize = 11,
normal = { textColor = new Color(0f, 0.6f, 0f) }
};
EditorGUILayout.LabelField("● 已启用", statusLabel, GUILayout.Width(60));
}
else
{
GUIStyle statusLabel = new GUIStyle(EditorStyles.miniLabel)
{
fontSize = 11,
normal = { textColor = Color.gray }
};
EditorGUILayout.LabelField("○ 已禁用", statusLabel, GUILayout.Width(60));
}
EditorGUILayout.EndHorizontal();
// 如果启用了自动拷贝,显示详细配置
if (_autoCopyAssembly.boolValue)
{
EditorGUILayout.Space(15);
// 步骤 1设置输出目录
DrawStepHeader("1", "设置输出目录");
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PropertyField(_hotUpdatePath, GUIContent.none);
if (GUILayout.Button("选择文件夹", GUILayout.Width(80)))
{
string path = EditorUtility.OpenFolderPanel("选择程序集输出目录", _hotUpdatePath.stringValue, "");
if (!string.IsNullOrEmpty(path))
{
_hotUpdatePath.stringValue = path;
}
}
EditorGUILayout.EndHorizontal();
// 路径状态提示
if (string.IsNullOrEmpty(_hotUpdatePath.stringValue))
{
EditorGUILayout.Space(3);
GUIStyle warningStyle = new GUIStyle(EditorStyles.miniLabel)
{
normal = { textColor = new Color(0.8f, 0.4f, 0f) }
};
EditorGUILayout.LabelField("⚠ 请先设置输出目录", warningStyle);
}
else if (!Directory.Exists(_hotUpdatePath.stringValue))
{
EditorGUILayout.Space(3);
GUIStyle warningStyle = new GUIStyle(EditorStyles.miniLabel)
{
normal = { textColor = new Color(0.6f, 0.6f, 0f) }
};
EditorGUILayout.LabelField(" 目录不存在,编译时将自动创建", warningStyle);
}
else
{
EditorGUILayout.Space(3);
GUIStyle successStyle = new GUIStyle(EditorStyles.miniLabel)
{
normal = { textColor = new Color(0f, 0.6f, 0f) }
};
EditorGUILayout.LabelField("✓ 目录已配置", successStyle);
}
EditorGUILayout.Space(15);
// 步骤 2选择要拷贝的程序集
DrawStepHeader("2", "程序集列表");
GUIStyle hintStyle = new GUIStyle(EditorStyles.miniLabel)
{
fontSize = 10,
normal = { textColor = Color.gray }
};
EditorGUILayout.LabelField("选择需要自动拷贝的热更新程序集", hintStyle);
EditorGUILayout.Space(10);
// 程序集列表区域
if (_hotUpdateAssemblyDefinitions.arraySize == 0)
{
// 空状态提示框
Color originalColor = GUI.color;
GUI.color = new Color(0.95f, 0.95f, 0.95f);
EditorGUILayout.BeginVertical(GUI.skin.box);
GUI.color = originalColor;
EditorGUILayout.Space(10);
GUIStyle emptyIconStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 20,
alignment = TextAnchor.MiddleCenter,
normal = { textColor = new Color(0.6f, 0.6f, 0.6f) }
};
EditorGUILayout.LabelField("📦", emptyIconStyle);
GUIStyle emptyTextStyle = new GUIStyle(EditorStyles.label)
{
fontSize = 11,
alignment = TextAnchor.MiddleCenter,
normal = { textColor = Color.gray }
};
EditorGUILayout.LabelField("暂未添加任何程序集", emptyTextStyle);
EditorGUILayout.Space(5);
GUIStyle addHintStyle = new GUIStyle(EditorStyles.miniLabel)
{
fontSize = 10,
alignment = TextAnchor.MiddleCenter,
normal = { textColor = new Color(0.3f, 0.6f, 1f) }
};
EditorGUILayout.LabelField("点击下方的 + 按钮添加程序集", addHintStyle);
EditorGUILayout.Space(10);
EditorGUILayout.EndVertical();
EditorGUILayout.Space(5);
}
// 程序集列表
EditorGUILayout.PropertyField(_hotUpdateAssemblyDefinitions, new GUIContent(""), true);
if (_hotUpdateAssemblyDefinitions.arraySize > 0)
{
EditorGUILayout.Space(5);
GUIStyle successStyle = new GUIStyle(EditorStyles.miniLabel)
{
normal = { textColor = new Color(0f, 0.6f, 0f) }
};
EditorGUILayout.LabelField($"✓ 已添加 {_hotUpdateAssemblyDefinitions.arraySize} 个程序集", successStyle);
}
// 配置完成状态总结
bool isConfigured = !string.IsNullOrEmpty(_hotUpdatePath.stringValue) && _hotUpdateAssemblyDefinitions.arraySize > 0;
if (isConfigured)
{
Color originalColor = GUI.color;
GUI.color = new Color(0.8f, 1f, 0.8f);
EditorGUILayout.BeginVertical(GUI.skin.box);
GUI.color = originalColor;
GUIStyle summaryStyle = new GUIStyle(EditorStyles.miniLabel)
{
fontSize = 11,
normal = { textColor = new Color(0f, 0.5f, 0f) }
};
EditorGUILayout.LabelField($"✓ 配置完成!编译后将自动拷贝 {_hotUpdateAssemblyDefinitions.arraySize} 个程序集到目标目录", summaryStyle);
EditorGUILayout.EndVertical();
}
}
else
{
EditorGUILayout.Space(8);
EditorGUILayout.HelpBox("启用后可在 Unity 编译完成时自动拷贝程序集文件", MessageType.Info);
}
EditorGUILayout.EndVertical();
}
/// <summary>
/// 绘制步骤标题
/// </summary>
private void DrawStepHeader(string stepNumber, string title)
{
EditorGUILayout.BeginHorizontal();
// 步骤编号
GUIStyle stepStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 12,
normal = { textColor = new Color(0.3f, 0.6f, 1f) }
};
EditorGUILayout.LabelField($"步骤 {stepNumber}:", stepStyle, GUILayout.Width(60));
// 步骤标题
GUIStyle titleStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 12
};
EditorGUILayout.LabelField(title, titleStyle);
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(5);
}
/// <summary>
/// 绘制 Link.xml 生成设置区域
/// </summary>
private void DrawLinkXmlSection()
{
// 标题
GUIStyle titleStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 14,
alignment = TextAnchor.MiddleLeft
};
EditorGUILayout.LabelField("Link.xml 代码裁剪配置", titleStyle);
EditorGUILayout.Space(5);
// 功能说明
GUIStyle descStyle = new GUIStyle(EditorStyles.wordWrappedLabel)
{
fontSize = 11,
normal = { textColor = Color.gray }
};
EditorGUILayout.LabelField("配置 Link.xml 文件以防止 IL2CPP 编译时过度裁剪反射或动态调用的代码,确保运行时正常", descStyle);
EditorGUILayout.Space(8);
GUIStyle boxStyle = new GUIStyle(GUI.skin.box)
{
padding = new RectOffset(12, 12, 12, 12),
margin = new RectOffset(0, 0, 0, 8)
};
EditorGUILayout.BeginVertical(boxStyle);
// 显示配置开关
EditorGUILayout.BeginHorizontal();
GUIStyle bigToggleStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 13
};
_showLinkXmlConfig = EditorGUILayout.ToggleLeft("显示配置", _showLinkXmlConfig, bigToggleStyle);
GUILayout.FlexibleSpace();
if (_showLinkXmlConfig)
{
GUIStyle statusLabel = new GUIStyle(EditorStyles.miniLabel)
{
fontSize = 11,
normal = { textColor = new Color(0.3f, 0.6f, 1f) }
};
EditorGUILayout.LabelField("● 已展开", statusLabel, GUILayout.Width(60));
}
else
{
GUIStyle statusLabel = new GUIStyle(EditorStyles.miniLabel)
{
fontSize = 11,
normal = { textColor = Color.gray }
};
EditorGUILayout.LabelField("○ 已折叠", statusLabel, GUILayout.Width(60));
}
EditorGUILayout.EndHorizontal();
// 如果显示配置,则展示详细内容
if (_showLinkXmlConfig)
{
EditorGUILayout.Space(15);
// 程序集定义配置标题
GUIStyle sectionTitleStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 13
};
EditorGUILayout.LabelField("需要保护的程序集", sectionTitleStyle);
EditorGUILayout.Space(3);
GUIStyle hintStyle = new GUIStyle(EditorStyles.miniLabel)
{
fontSize = 10,
normal = { textColor = Color.gray }
};
EditorGUILayout.LabelField("选择需要在 IL2CPP 编译时防止代码裁剪的程序集", hintStyle);
EditorGUILayout.Space(3);
// 默认包含程序集说明
EditorGUILayout.BeginHorizontal();
GUILayout.Space(5);
GUIStyle defaultInfoStyle = new GUIStyle(EditorStyles.miniLabel)
{
fontSize = 10,
normal = { textColor = new Color(0.4f, 0.6f, 0.8f) }
};
EditorGUILayout.LabelField(" 默认包含Assembly-CSharp、Fantasy.Unity", defaultInfoStyle);
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(10);
// 程序集列表区域
if (_linkAssemblyDefinitions.arraySize == 0)
{
// 空状态提示框
Color originalColor = GUI.color;
GUI.color = new Color(0.95f, 0.95f, 0.95f);
EditorGUILayout.BeginVertical(GUI.skin.box);
GUI.color = originalColor;
EditorGUILayout.Space(10);
GUIStyle emptyIconStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 20,
alignment = TextAnchor.MiddleCenter,
normal = { textColor = new Color(0.6f, 0.6f, 0.6f) }
};
EditorGUILayout.LabelField("📋", emptyIconStyle);
GUIStyle emptyTextStyle = new GUIStyle(EditorStyles.label)
{
fontSize = 11,
alignment = TextAnchor.MiddleCenter,
normal = { textColor = Color.gray }
};
EditorGUILayout.LabelField("暂未添加任何程序集", emptyTextStyle);
EditorGUILayout.Space(5);
GUIStyle addHintStyle = new GUIStyle(EditorStyles.miniLabel)
{
fontSize = 10,
alignment = TextAnchor.MiddleCenter,
normal = { textColor = new Color(0.3f, 0.6f, 1f) }
};
EditorGUILayout.LabelField("点击下方的 + 按钮添加程序集", addHintStyle);
EditorGUILayout.Space(10);
EditorGUILayout.EndVertical();
EditorGUILayout.Space(5);
}
// 程序集列表
EditorGUILayout.PropertyField(_linkAssemblyDefinitions, new GUIContent(""), true);
if (_linkAssemblyDefinitions.arraySize > 0)
{
EditorGUILayout.Space(5);
GUIStyle successStyle = new GUIStyle(EditorStyles.miniLabel)
{
normal = { textColor = new Color(0f, 0.6f, 0f) }
};
EditorGUILayout.LabelField($"✓ 已添加 {_linkAssemblyDefinitions.arraySize} 个程序集", successStyle);
}
EditorGUILayout.Space(15);
// 生成按钮区域
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
GUIStyle buttonStyle = new GUIStyle(GUI.skin.button)
{
fontSize = 13,
fontStyle = FontStyle.Bold,
fixedHeight = 35,
fixedWidth = 200
};
Color originalBgColor = GUI.backgroundColor;
GUI.backgroundColor = new Color(0.4f, 0.7f, 1f);
if (GUILayout.Button("生成 Link.xml 文件", buttonStyle))
{
LinkXmlGenerator.GenerateLinkXml();
EditorUtility.DisplayDialog("操作成功", "Link.xml 文件已生成", "确定");
}
GUI.backgroundColor = originalBgColor;
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
}
else
{
EditorGUILayout.Space(8);
EditorGUILayout.HelpBox("点击上方开关展开配置", MessageType.Info);
}
EditorGUILayout.EndVertical();
}
static FantasySettingsProvider _provider;
[SettingsProvider]
public static SettingsProvider CreateMyCustomSettingsProvider()
{
if (FantasySettingsScriptableObject.Instance && _provider == null)
{
_provider = new FantasySettingsProvider();
using (var so = new SerializedObject(FantasySettingsScriptableObject.Instance))
{
_provider.keywords = GetSearchKeywordsFromSerializedObject(so);
}
}
return _provider;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 19a618703b49e3d4fbcd8223540a77f3
timeCreated: 1688380387

View File

@@ -0,0 +1,25 @@
using UnityEditor;
using UnityEditor.Compilation;
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.Serialization;
namespace Fantasy
{
[ScriptableObjectPath("ProjectSettings/FantasySettings.asset")]
public class FantasySettingsScriptableObject : ScriptableObjectSingleton<FantasySettingsScriptableObject>, ISerializationCallbackReceiver
{
[FormerlySerializedAs("AutoCopyAssembly")]
public bool autoCopyAssembly = false;
[FormerlySerializedAs("HotUpdatePath")]
public string hotUpdatePath;
[FormerlySerializedAs("HotUpdateAssemblyDefinitions")]
public AssemblyDefinitionAsset[] hotUpdateAssemblyDefinitions;
[FormerlySerializedAs("LinkAssemblyDefinitions")]
public AssemblyDefinitionAsset[] linkAssemblyDefinitions;
[FormerlySerializedAs("IncludeAssembly")]
public string[] includeAssembly = new[] { "Assembly-CSharp", "Fantasy.Unity" };
public void OnBeforeSerialize() { }
public void OnAfterDeserialize() { }
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6733837e208a20a488d61c50fb71919c
timeCreated: 1688277120

View File

@@ -0,0 +1,100 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEditorInternal;
using UnityEngine;
// ReSharper disable AssignNullToNotNullAttribute
namespace Fantasy
{
public class ScriptableObjectSingleton<T> : ScriptableObject where T : ScriptableObject
{
private static T _instance;
public static T Instance
{
get
{
if (_instance == null)
{
_instance = Load();
}
return _instance;
}
}
private static T Load()
{
var scriptableObjectPath = GetScriptableObjectPath();
if (string.IsNullOrEmpty(scriptableObjectPath))
{
return null;
}
var loadSerializedFileAndForget = InternalEditorUtility.LoadSerializedFileAndForget(scriptableObjectPath);
if (loadSerializedFileAndForget.Length <= 0)
{
return CreateInstance<T>();
}
return loadSerializedFileAndForget[0] as T;
}
public static void Save(bool saveAsText = true)
{
if (_instance == null)
{
Debug.LogError("Cannot save ScriptableObjectSingleton: no instance!");
return;
}
var scriptableObjectPath = GetScriptableObjectPath();
if (string.IsNullOrEmpty(scriptableObjectPath))
{
return;
}
var directoryName = Path.GetDirectoryName(scriptableObjectPath);
if (!Directory.Exists(directoryName))
{
Directory.CreateDirectory(directoryName);
}
UnityEngine.Object[] obj = { _instance };
InternalEditorUtility.SaveToSerializedFileAndForget(obj, scriptableObjectPath, saveAsText);
}
private static string GetScriptableObjectPath()
{
var scriptableObjectPathAttribute = typeof(T).GetCustomAttribute(typeof(ScriptableObjectPathAttribute)) as ScriptableObjectPathAttribute;
return scriptableObjectPathAttribute?.ScriptableObjectPath;
}
}
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class ScriptableObjectPathAttribute : Attribute
{
internal readonly string ScriptableObjectPath;
public ScriptableObjectPathAttribute(string scriptableObjectPath)
{
if (string.IsNullOrEmpty(scriptableObjectPath))
{
throw new ArgumentException("Invalid relative path (it is empty)");
}
if (scriptableObjectPath[0] == '/')
{
scriptableObjectPath = scriptableObjectPath.Substring(1);
}
ScriptableObjectPath = scriptableObjectPath;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a8963b5817140ad42a1a5cae9d6a4336
timeCreated: 1688278016

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c6603a5cb5b34f44fb04cfca6e80092d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,229 @@
using UnityEngine;
using UnityEditor;
using UnityEngine.Networking;
using System.IO;
using System;
namespace UnityWebSocket.Editor
{
internal class SettingsWindow : EditorWindow
{
static SettingsWindow window = null;
[MenuItem("Tools/UnityWebSocket", priority = 100)]
internal static void Open()
{
if (window != null)
{
window.Close();
}
window = GetWindow<SettingsWindow>(true, "UnityWebSocket");
window.minSize = window.maxSize = new Vector2(600, 310);
window.Show();
window.BeginCheck();
}
private void OnGUI()
{
DrawLogo();
DrawVersion();
DrawSeparator(80);
DrawSeparator(186);
DrawHelper();
DrawFooter();
}
Texture2D logoTex = null;
private void DrawLogo()
{
if (logoTex == null)
{
logoTex = new Texture2D(66, 66);
logoTex.LoadImage(Convert.FromBase64String(LOGO_BASE64.VALUE));
for (int i = 0; i < 66; i++) for (int j = 0; j < 15; j++) logoTex.SetPixel(i, j, Color.clear);
logoTex.Apply();
}
var logoPos = new Rect(10, 10, 66, 66);
GUI.DrawTexture(logoPos, logoTex);
var title = "<color=#3A9AD8><b>UnityWebSocket</b></color>";
var titlePos = new Rect(80, 20, 500, 50);
GUI.Label(titlePos, title, TextStyle(24));
}
private void DrawSeparator(int y)
{
EditorGUI.DrawRect(new Rect(10, y, 580, 1), Color.white * 0.5f);
}
private GUIStyle TextStyle(int fontSize = 10, TextAnchor alignment = TextAnchor.UpperLeft, float alpha = 0.85f)
{
var style = new GUIStyle();
style.fontSize = fontSize;
style.normal.textColor = (EditorGUIUtility.isProSkin ? Color.white : Color.black) * alpha;
style.alignment = alignment;
style.richText = true;
return style;
}
private void DrawVersion()
{
GUI.Label(new Rect(440, 10, 150, 10), "Current Version: " + Settings.VERSION, TextStyle(alignment: TextAnchor.MiddleLeft));
if (string.IsNullOrEmpty(latestVersion))
{
GUI.Label(new Rect(440, 30, 150, 10), "Checking for Updates...", TextStyle(alignment: TextAnchor.MiddleLeft));
}
else if (latestVersion == "unknown")
{
}
else
{
GUI.Label(new Rect(440, 30, 150, 10), "Latest Version: " + latestVersion, TextStyle(alignment: TextAnchor.MiddleLeft));
if (Settings.VERSION == latestVersion)
{
if (GUI.Button(new Rect(440, 50, 150, 18), "Check Update"))
{
latestVersion = "";
changeLog = "";
BeginCheck();
}
}
else
{
if (GUI.Button(new Rect(440, 50, 150, 18), "Update to | " + latestVersion))
{
ShowUpdateDialog();
}
}
}
}
private void ShowUpdateDialog()
{
var isOK = EditorUtility.DisplayDialog("UnityWebSocket",
"Update UnityWebSocket now?\n" + changeLog,
"Update Now", "Cancel");
if (isOK)
{
UpdateVersion();
}
}
private void UpdateVersion()
{
Application.OpenURL(Settings.GITHUB + "/releases");
}
private void DrawHelper()
{
GUI.Label(new Rect(330, 200, 100, 18), "GitHub:", TextStyle(10, TextAnchor.MiddleRight));
if (GUI.Button(new Rect(440, 200, 150, 18), "UnityWebSocket"))
{
Application.OpenURL(Settings.GITHUB);
}
GUI.Label(new Rect(330, 225, 100, 18), "Report:", TextStyle(10, TextAnchor.MiddleRight));
if (GUI.Button(new Rect(440, 225, 150, 18), "Report an Issue"))
{
Application.OpenURL(Settings.GITHUB + "/issues/new");
}
GUI.Label(new Rect(330, 250, 100, 18), "Email:", TextStyle(10, TextAnchor.MiddleRight));
if (GUI.Button(new Rect(440, 250, 150, 18), Settings.EMAIL))
{
var uri = new Uri(string.Format("mailto:{0}?subject={1}", Settings.EMAIL, "UnityWebSocket Feedback"));
Application.OpenURL(uri.AbsoluteUri);
}
GUI.Label(new Rect(330, 275, 100, 18), "QQ群:", TextStyle(10, TextAnchor.MiddleRight));
if (GUI.Button(new Rect(440, 275, 150, 18), Settings.QQ_GROUP))
{
Application.OpenURL(Settings.QQ_GROUP_LINK);
}
}
private void DrawFooter()
{
EditorGUI.DropShadowLabel(new Rect(10, 230, 400, 20), "Developed by " + Settings.AUHTOR);
EditorGUI.DropShadowLabel(new Rect(10, 250, 400, 20), "All rights reserved");
}
UnityWebRequest req;
string changeLog = "";
string latestVersion = "";
void BeginCheck()
{
EditorApplication.update -= VersionCheckUpdate;
EditorApplication.update += VersionCheckUpdate;
req = UnityWebRequest.Get(Settings.GITHUB + "/releases/latest");
req.SendWebRequest();
}
private void VersionCheckUpdate()
{
#if UNITY_2020_3_OR_NEWER
if (req == null
|| req.result == UnityWebRequest.Result.ConnectionError
|| req.result == UnityWebRequest.Result.DataProcessingError
|| req.result == UnityWebRequest.Result.ProtocolError)
#elif UNITY_2018_1_OR_NEWER
if (req == null || req.isNetworkError || req.isHttpError)
#else
if (req == null || req.isError)
#endif
{
EditorApplication.update -= VersionCheckUpdate;
latestVersion = "unknown";
return;
}
if (req.isDone)
{
EditorApplication.update -= VersionCheckUpdate;
latestVersion = req.url.Substring(req.url.LastIndexOf("/") + 1).TrimStart('v');
if (Settings.VERSION != latestVersion)
{
var text = req.downloadHandler.text;
var st = text.IndexOf("content=\"" + latestVersion);
st = st > 0 ? text.IndexOf("\n", st) : -1;
var end = st > 0 ? text.IndexOf("\" />", st) : -1;
if (st > 0 && end > st)
{
changeLog = text.Substring(st + 1, end - st - 1).Trim();
changeLog = changeLog.Replace("\r", "");
changeLog = changeLog.Replace("\n", "\n- ");
changeLog = "\nCHANGE LOG: \n- " + changeLog + "\n";
}
}
Repaint();
}
}
}
internal static class LOGO_BASE64
{
internal const string VALUE = "iVBORw0KGgoAAAANSUhEUgAAAEIAAABCCAMAAADUivDaAAAAq1BMVEUAAABKmtcvjtYzl" +
"9szmNszl9syl9k0mNs0mNwzmNs0mNszl9szl9s0mNs0mNwzmNw0mNwyltk0mNw0mNwzl9s0mNsymNs0mNszmNwzmNwzm" +
"NszmNs0mNwzl9w0mNwzmNw0mNs0mNs0mNwzl9wzmNs0mNwzmNs0mNwzl90zmNszmNszl9szmNsxmNszmNszmNw0mNwzm" +
"Nw0mNs2neM4pe41mt43ouo2oOY5qfM+UHlaAAAAMnRSTlMAAwXN3sgI+/069MSCK6M/MA74h9qfFHB8STWMJ9OSdmNcI" +
"8qya1IeF+/U0EIa57mqmFTYJe4AAAN3SURBVFjD7ZbpkppAFEa/bgVBREF2kEVGFNeZsM77P1kadURnJkr8k1Qlx1Khu" +
"/pw7+2lwH/+YcgfMBBLG7VocwDamzH+wJBB8Qhjve2f0TdrGwjei6o4Ub/nM/APw5Z7vvSB/qrCrqbD6fBEVtigeMxks" +
"fX9zWbj+z1jhqgSBplQ50eGo4614WXlRAzgrRhmtSfvxAn7pB0N5ObaKKZZuU5/d37IBcBgUQwqDuf7Z2gUmVAl4NGNr" +
"/UeHxV5n39ulbaKLI86h6HilmM5M1aN126lpNhtl59yeTsp8nUMvpNC1J3bh5FtfVRk+bJrJunn5d4U4piJ/Vw9eXgsj" +
"4ZpZaCjg9waZkIpnBWLJ44OwoNu60F2UnSaEkKv4XnAlCpm6B4F/aKMDiyGi2L8SEEAVdxNLuzmgV7nFwObEe2xQVuX+" +
"RV1lWetga3w+cN1sXgvm4cJH8OEgZC1DPKhfF/BIymmQrMjq/x65FUeEkDup8GxoexZmznHCvANtXU/CAq13yimhQGtm" +
"H4VCPnBBL1fTKo3CqEcvq7Lb/OwHxWTYlyw+JmjKoVvDLVOQB4pVsM8K8smgvLCxZDlIijwyOEc+nr/msMwK0+GQWGBd" +
"tmhjv8icTds1s2ammaFh04QLLe69NK7guP6mTDMaw3o6nAX/Z7EXUskPSvWEWg4srVlp5NTDXv9Lce9HGN5eeG4nj5Yz" +
"ACteU2wQLo4MBtJfd1nw5nG1/s9zwUQ6pykL1TQjqdeuvQW0naz2XKLYL4Cwzr4vj+OQdD96CSp7Lrynp4aeFF0xdm5q" +
"6OFtFfPv7URxpWJNjd/N+3+I9+1klMav12Qtgbt9R2JaIopjkzaPtOFq4KxUpqfUMSFnQrySWjLoQzRZS4HMH84ME1ej" +
"S1YJpQZ3B+sR1uCQJSBdGdCk1eAEgORR88KK05W8dh2MA+A/SKCYu3mCJ0Ek7HBx4HHeuwYy5G3x8hSMTJcOMFbinCsn" +
"hO1V1aszGULvA0g4UFsb4VA0hAFcyo6cgLsAoT7uUtGAH5wQKQle0wuLyxLTaNyJEYwxw4wSljLK1TP8CAaOyhBMMEsj" +
"OBoXgo7VGElFkSWL+vef1RF2YNXeRWYzQBTpkhC8KaZHhuIogArkQLKClBZjU26B2IZgGz+cpZkHl8g3fYUaW/YP2kb2" +
"M/V97JY/vZN859n+QmO7XtC9Bf2jAAAAABJRU5ErkJggg==";
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c797632152a42a04e944c54c3bdacad8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,17 @@
{
"name": "Fantasy.Unity",
"rootNamespace": "",
"references": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false,
"defineSymbols": [
"FANTASY_UNITY"
]
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 705397a8cd8604d49b6c4a4bf342f3fd
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,12 @@
MIT License
Copyright (c) 2023 qq362946
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
However, the following entity is explicitly prohibited from using, copying, modifying, or distributing the Software or any of its portions:
泰课在线https://www.taikr.com/
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 17eeea30b6cc1f44f99ca12d5eff0726
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,45 @@
# Fantasy
#### Fantasy是基于.NET的高性能网络开发框架支持主流协议前后端分离。
#### 支持Unity/Godot/WinFrom/WPF/Console等C#客户端接入。
#### 框架支持TCP\KCP\WebSocket\http支持Unity发布成H5三种网络协议。
#### 适合需要快速上手、可扩展、分布式全平台商业级解决方案的框架。
## 导航
* [Fantasy介绍网站](https://www.code-fantasy.com/)
* [Fantasy的API文档](https://www.code-fantasy.com/doc/api/Fantasy.html)
* [入门视频观看地址](https://space.bilibili.com/382126312)
## 快速上手
* 01.快速入门
* [1.1.获得Fantasy](https://www.code-fantasy.com/top/download-fantasy/)
* [1.2.配置文件](https://www.code-fantasy.com/top/config-file/)
* [1.3.Fantasy的网络](https://www.code-fantasy.com/top/use-network/)
* [1.4.Fantasy的配置文件](https://www.code-fantasy.com/top/config-file/)
* [1.5.Fantasy的命令行参数](https://www.code-fantasy.com/top/command-line-parameter/)
* [1.6.如何升级到最新版](https://www.code-fantasy.com/top/upgrade/)
* 02.网络通信
* [2.1.配置网络协议](https://www.code-fantasy.com/network/network-protocols/)
* [2.2.客户端服务器之间发送消息](https://www.code-fantasy.com/network/session/)
* [2.3.服务器之间发送消息](https://www.code-fantasy.com/network/networkmessagingomponent/)
* [2.4.Route通信协议](https://www.code-fantasy.com/network/network-route/)
* [2.5.Addressable通信协议](https://www.code-fantasy.com/network/network-addressable/)
* 03.系统组件
* [3.1.ECS系统](https://www.code-fantasy.com/core/ecs/)
* [3.2.事件系统](https://www.code-fantasy.com/core/event/)
* [3.3.任务系统](https://www.code-fantasy.com/core/task/)
* [3.4.异步协程锁](https://www.code-fantasy.com/core/lock/)
* [3.5.数据库](https://www.code-fantasy.com/core/db/)
* [更新日志](https://www.code-fantasy.com/changelog/)
* [API文档](https://www.code-fantasy.com/doc/api/Fantasy.html)
* [常见问题](https://www.code-fantasy.com/question/)
## 优质开源项目推荐
#### <a href="https://github.com/egametang/ET"><strong>ET</strong></a> - ET框架是一整套完善的游戏开发框架。
#### <a href="https://github.com/ALEXTANGXIAO/TEngine"><strong>TEngine</strong></a> - TEngine是一个简单(新手友好开箱即用)且强大的Unity框架全平台解决方案。
#### <a href="https://github.com/FlameskyDexive/Legends-Of-Heroes"><strong>Legends-Of-Heroes</strong></a> - 一个LOL风格的球球大作战游戏基于ET使用状态同步。
## 交流与讨论:
__讨论QQ群 : Fantasy服务器开发交流群 569888673 __
## 特别鸣谢
感谢JetBrains公司提供的使用许可证
<p><a href="https://www.jetbrains.com/?from=fantasy">
<img src="https://user-images.githubusercontent.com/8274346/223466125-611c027a-61f3-4ea0-a96d-4052283da746.png" alt="JetBrains的Logo" width="20%" height="20%"></a></p>

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 14fc44c000caf3c479b744d281d3696e
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f4307bf646a547f41b0c813349fce9ed
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,84 @@
fileFormatVersion: 2
guid: c8d353ed0a972a74f9eae2f1f2d3038b
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 0
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 1
Exclude Editor: 1
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude WebGL: 1
Exclude Win: 1
Exclude Win64: 1
- first:
Android: Android
second:
enabled: 0
settings:
CPU: ARMv7
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: None
- first:
WebGL: WebGL
second:
enabled: 0
settings: {}
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
labels:
- RoslynAnalyzer

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 017fdce3acc39044f8f22b0b65dc5b70
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 37819076a3bcb9c48ab7d3856f48f94f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b5b24e6eec64b4702b871053139f8add
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,91 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using Fantasy.Async;
namespace Fantasy.Assembly
{
/// <summary>
/// 程序集生命周期管理类
/// 管理所有注册的程序集生命周期回调,在程序集加载、卸载时触发相应的回调方法
/// </summary>
public static class AssemblyLifecycle
{
#if FANTASY_WEBGL
/// <summary>
/// 程序集生命周期回调集合WebGL 单线程版本)
/// </summary>
private static readonly Dictionary<IAssemblyLifecycle, byte> AssemblyLifecycles = new Dictionary<IAssemblyLifecycle, byte>();
#else
/// <summary>
/// 程序集生命周期回调集合(线程安全版本)
/// 使用 ConcurrentDictionary 当作 Set 使用Value 无实际意义
/// </summary>
private static readonly ConcurrentDictionary<IAssemblyLifecycle, byte> AssemblyLifecycles = new ConcurrentDictionary<IAssemblyLifecycle, byte>();
#endif
/// <summary>
/// 触发程序集加载事件
/// 遍历所有已注册的生命周期回调,调用其 OnLoad 方法
/// </summary>
/// <param name="assemblyManifest">程序集清单对象</param>
/// <returns>异步任务</returns>
internal static async FTask OnLoad(AssemblyManifest assemblyManifest)
{
foreach (var (assemblyLifecycle, _) in AssemblyLifecycles)
{
await assemblyLifecycle.OnLoad(assemblyManifest);
}
}
/// <summary>
/// 触发程序集卸载事件
/// 遍历所有已注册的生命周期回调,调用其 OnUnload 方法,并清理程序集清单
/// </summary>
/// <param name="assemblyManifest">程序集清单对象</param>
/// <returns>异步任务</returns>
internal static async FTask OnUnLoad(AssemblyManifest assemblyManifest)
{
foreach (var (assemblyLifecycle, _) in AssemblyLifecycles)
{
await assemblyLifecycle.OnUnload(assemblyManifest);
}
assemblyManifest.Clear();
}
/// <summary>
/// 添加程序集生命周期回调
/// 添加后会立即对所有已加载的程序集触发 Load 回调
/// </summary>
/// <param name="assemblyLifecycle">实现 IAssemblyLifecycle 接口的生命周期回调对象</param>
internal static async FTask Add(IAssemblyLifecycle assemblyLifecycle)
{
#if FANTASY_WEBGL
AssemblyLifecycles.Add(assemblyLifecycle, 0);
#else
AssemblyLifecycles.TryAdd(assemblyLifecycle, 0);
#endif
foreach (var (_, assemblyManifest) in AssemblyManifest.Manifests)
{
await assemblyLifecycle.OnLoad(assemblyManifest);
}
}
/// <summary>
/// 移除程序集生命周期回调
/// 移除后该回调将不再接收程序集的加载、卸载、重载事件
/// </summary>
/// <param name="assemblyLifecycle">要移除的生命周期回调对象</param>
internal static void Remove(IAssemblyLifecycle assemblyLifecycle)
{
AssemblyLifecycles.Remove(assemblyLifecycle, out _);
}
/// <summary>
/// 释放所有程序集生命周期回调
/// 清空所有已注册的生命周期回调集合
/// </summary>
public static void Dispose()
{
AssemblyLifecycles.Clear();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cc34b85d59dac6a4dad420cfa302e4c7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,256 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using Fantasy.Async;
using Fantasy.DataStructure.Collection;
using Fantasy.Entitas;
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
#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.
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
#pragma warning disable CS8603 // Possible null reference return.
namespace Fantasy.Assembly
{
/// <summary>
/// 程序集清单类
/// 封装程序集的元数据和各种系统注册器,用于统一管理程序集的生命周期和系统注册
/// </summary>
public sealed class AssemblyManifest
{
/// <summary>
/// 程序集唯一标识符
/// 通过程序集名称的哈希值生成
/// </summary>
public long AssemblyManifestId { get; private set; }
/// <summary>
/// 程序集实例
/// </summary>
public System.Reflection.Assembly Assembly { get; private set; }
/// <summary>
/// ProtoBuf 序列化类型注册器
/// </summary>
internal INetworkProtocolRegistrar NetworkProtocolRegistrar { get; set; }
/// <summary>
/// 事件系统注册器
/// </summary>
internal IEventSystemRegistrar EventSystemRegistrar { get; set; }
/// <summary>
/// 实体系统注册器
/// </summary>
internal IEntitySystemRegistrar EntitySystemRegistrar { get; set; }
/// <summary>
/// 消息分发器注册器
/// </summary>
internal IMessageHandlerResolver MessageHandlerResolver { get; set; }
/// <summary>
/// 实体类型集合注册器
/// </summary>
internal IEntityTypeCollectionRegistrar EntityTypeCollectionRegistrar { get; set; }
/// <summary>
/// 网络协议 OpCode 解析器接口
/// </summary>
internal INetworkProtocolOpCodeResolver NetworkProtocolOpCodeResolver { get; set; }
/// <summary>
/// 网络协议 Response 解析器接口
/// </summary>
internal INetworkProtocolResponseTypeResolver NetworkProtocolResponseTypeResolver { get; set; }
#if FANTASY_NET
/// <summary>
/// 分表注册器
/// </summary>
internal ISeparateTableRegistrar SeparateTableRegistrar { get; set; }
#endif
#if FANTASY_WEBGL
/// <summary>
/// 程序集清单集合WebGL 单线程版本)
/// Key: 程序集唯一标识, Value: 程序集清单对象
/// </summary>
private static readonly Dictionary<long, AssemblyManifest> Manifests = new Dictionary<long, AssemblyManifest>();
#else
/// <summary>
/// 程序集清单集合(线程安全版本)
/// Key: 程序集唯一标识, Value: 程序集清单对象
/// </summary>
internal static readonly ConcurrentDictionary<long, AssemblyManifest> Manifests = new ConcurrentDictionary<long, AssemblyManifest>();
#endif
/// <summary>
/// 清理程序集清单内部资源
/// 释放所有注册器并清空引用
/// </summary>
internal void Clear()
{
EventSystemRegistrar?.Dispose();
Assembly = null;
NetworkProtocolRegistrar = null;
EventSystemRegistrar = null;
EntitySystemRegistrar = null;
MessageHandlerResolver = null;
EntityTypeCollectionRegistrar = null;
#if FANTASY_NET
SeparateTableRegistrar = null;
#endif
}
#region static
#if FANTASY_NET
/// <summary>
/// 注册程序集清单
/// 此方法由 Source Generator 生成的 ModuleInitializer 自动调用
/// 直接创建并缓存完整的 AssemblyManifest
/// </summary>
/// <param name="assemblyManifestId">程序集唯一标识(通过程序集名称哈希生成)</param>
/// <param name="assembly">程序集实例</param>
/// <param name="networkProtocolRegistrar">网络协议注册器</param>
/// <param name="eventSystemRegistrar">事件系统注册器</param>
/// <param name="entitySystemRegistrar">实体系统注册器</param>
/// <param name="messageHandlerResolver">消息分发器注册器</param>
/// <param name="entityTypeCollectionRegistrar">实体类型集合注册器</param>
/// <param name="separateTableRegistrar">分表注册器</param>
/// <param name="networkProtocolOpCodeResolver">网络协议 OpCode 解析器接口</param>
/// <param name="networkProtocolResponseTypeResolver">网络协议 Response 解析器接口</param>
public static void Register(
long assemblyManifestId,
System.Reflection.Assembly assembly,
INetworkProtocolRegistrar networkProtocolRegistrar,
IEventSystemRegistrar eventSystemRegistrar,
IEntitySystemRegistrar entitySystemRegistrar,
IMessageHandlerResolver messageHandlerResolver,
IEntityTypeCollectionRegistrar entityTypeCollectionRegistrar,
ISeparateTableRegistrar separateTableRegistrar,
INetworkProtocolOpCodeResolver networkProtocolOpCodeResolver,
INetworkProtocolResponseTypeResolver networkProtocolResponseTypeResolver)
{
var manifest = new AssemblyManifest
{
Assembly = assembly,
AssemblyManifestId = assemblyManifestId,
NetworkProtocolRegistrar = networkProtocolRegistrar,
EventSystemRegistrar = eventSystemRegistrar,
EntitySystemRegistrar = entitySystemRegistrar,
MessageHandlerResolver = messageHandlerResolver,
EntityTypeCollectionRegistrar = entityTypeCollectionRegistrar,
SeparateTableRegistrar = separateTableRegistrar,
NetworkProtocolOpCodeResolver = networkProtocolOpCodeResolver,
NetworkProtocolResponseTypeResolver = networkProtocolResponseTypeResolver
};
Manifests.TryAdd(assemblyManifestId, manifest);
AssemblyLifecycle.OnLoad(manifest).Coroutine();
}
#endif
#if FANTASY_UNITY
/// <summary>
/// 注册程序集清单
/// 此方法由 Source Generator 生成的 ModuleInitializer 自动调用
/// 直接创建并缓存完整的 AssemblyManifest
/// </summary>
/// <param name="assemblyManifestId">程序集唯一标识(通过程序集名称哈希生成)</param>
/// <param name="assembly">程序集实例</param>
/// <param name="networkProtocolRegistrar">网络协议注册器</param>
/// <param name="eventSystemRegistrar">事件系统注册器</param>
/// <param name="entitySystemRegistrar">实体系统注册器</param>
/// <param name="messageHandlerResolver">消息分发器注册器</param>
/// <param name="entityTypeCollectionRegistrar">实体类型集合注册器</param>
/// <param name="networkProtocolOpCodeResolver">网络协议 OpCode 解析器接口</param>
/// <param name="networkProtocolResponseTypeResolver">网络协议 Response 解析器接口</param>
public static void Register(
long assemblyManifestId,
System.Reflection.Assembly assembly,
INetworkProtocolRegistrar networkProtocolRegistrar,
IEventSystemRegistrar eventSystemRegistrar,
IEntitySystemRegistrar entitySystemRegistrar,
IMessageHandlerResolver messageHandlerResolver,
IEntityTypeCollectionRegistrar entityTypeCollectionRegistrar,
INetworkProtocolOpCodeResolver networkProtocolOpCodeResolver,
INetworkProtocolResponseTypeResolver networkProtocolResponseTypeResolver)
{
var manifest = new AssemblyManifest
{
Assembly = assembly,
AssemblyManifestId = assemblyManifestId,
NetworkProtocolRegistrar = networkProtocolRegistrar,
EventSystemRegistrar = eventSystemRegistrar,
EntitySystemRegistrar = entitySystemRegistrar,
MessageHandlerResolver = messageHandlerResolver,
EntityTypeCollectionRegistrar = entityTypeCollectionRegistrar,
NetworkProtocolOpCodeResolver = networkProtocolOpCodeResolver,
NetworkProtocolResponseTypeResolver = networkProtocolResponseTypeResolver
};
#if FANTASY_WEBGL
Manifests[assemblyManifestId] = manifest;
#else
Manifests.TryAdd(assemblyManifestId, manifest);
#endif
AssemblyLifecycle.OnLoad(manifest).Coroutine();
}
#endif
/// <summary>
/// 取消注册指定程序集的清单
/// </summary>
/// <param name="assemblyManifestId">程序集唯一标识</param>
public static void Unregister(long assemblyManifestId)
{
#if FANTASY_WEBGL
if (Manifests.TryGetValue(assemblyManifestId, out var manifest))
{
AssemblyLifecycle.OnUnLoad(manifest).Coroutine();
Manifests.Remove(assemblyManifestId);
}
#else
if (Manifests.TryRemove(assemblyManifestId, out var manifest))
{
AssemblyLifecycle.OnUnLoad(manifest).Coroutine();
}
#endif
}
/// <summary>
/// 获取当前框架注册的所有程序集清单
/// 通过迭代器模式返回所有已注册的程序集清单对象
/// </summary>
public static IEnumerable<AssemblyManifest> GetAssemblyManifest
{
get
{
foreach (var (_, assemblyManifest) in Manifests)
{
yield return assemblyManifest;
}
}
}
/// <summary>
/// 释放所有程序集清单资源
/// 卸载所有已注册的程序集,触发卸载事件,清理所有注册器和生命周期回调
/// </summary>
/// <returns>异步任务</returns>
public static async FTask Dispose()
{
foreach (var (_, assemblyManifest) in Manifests)
{
await AssemblyLifecycle.OnUnLoad(assemblyManifest);
assemblyManifest.Clear();
}
Manifests.Clear();
AssemblyLifecycle.Dispose();
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0b3ec9cf56a12434a80505dca842a833
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 21c355b8a670428478a4d839823fda39
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,29 @@
using System;
using Fantasy.Async;
namespace Fantasy.Assembly
{
/// <summary>
/// 程序集生命周期回调接口
/// 实现此接口的类型可以接收程序集的加载、卸载、重载事件通知
/// 通过 AssemblySystem.Add() 注册后,在程序集状态变化时会自动调用对应的生命周期方法
/// </summary>
internal interface IAssemblyLifecycle
{
/// <summary>
/// 程序集加载或重载时调用
/// 当新的程序集被加载到框架中时触发此回调,重新加载已存在的程序集时也会调用
/// </summary>
/// <param name="assemblyManifest">程序集清单对象,包含程序集的元数据和注册器</param>
/// <returns>异步任务</returns>
FTask OnLoad(AssemblyManifest assemblyManifest);
/// <summary>
/// 程序集卸载时调用
/// 当程序集从框架中卸载时触发此回调,应在此方法中清理该程序集相关的资源
/// </summary>
/// <param name="assemblyManifest">程序集清单对象,包含程序集的元数据和注册器</param>
/// <returns>异步任务</returns>
FTask OnUnload(AssemblyManifest assemblyManifest);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3c72e8392156fd34db27c23ffce41bbb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using Fantasy.Entitas;
using Fantasy.Entitas.Interface;
namespace Fantasy.Assembly
{
/// <summary>
/// 实体系统注册器接口
/// 由 Source Generator 自动生成实现类,用于在程序集加载时注册实体系统
/// </summary>
public interface IEntitySystemRegistrar
{
#if FANTASY_NET
/// <summary>
/// 注册该程序集中的所有实体系统
/// </summary>
/// <param name="awakeSystems">Awake 系统容器</param>
/// <param name="updateSystems">Update 系统容器</param>
/// <param name="destroySystems">Destroy 系统容器</param>
/// <param name="deserializeSystems">Deserialize 系统容器</param>
void RegisterSystems(
Dictionary<long, Action<Entity>> awakeSystems,
Dictionary<long, Action<Entity>> updateSystems,
Dictionary<long, Action<Entity>> destroySystems,
Dictionary<long, Action<Entity>> deserializeSystems);
/// <summary>
/// 取消注册该程序集中的所有实体系统(热重载卸载时调用)
/// </summary>
/// <param name="awakeSystems">Awake 系统容器</param>
/// <param name="updateSystems">Update 系统容器</param>
/// <param name="destroySystems">Destroy 系统容器</param>
/// <param name="deserializeSystems">Deserialize 系统容器</param>
void UnRegisterSystems(
Dictionary<long, Action<Entity>> awakeSystems,
Dictionary<long, Action<Entity>> updateSystems,
Dictionary<long, Action<Entity>> destroySystems,
Dictionary<long, Action<Entity>> deserializeSystems);
#endif
#if FANTASY_UNITY
/// <summary>
/// 注册该程序集中的所有实体系统
/// </summary>
/// <param name="awakeSystems">Awake 系统容器</param>
/// <param name="updateSystems">Update 系统容器</param>
/// <param name="destroySystems">Destroy 系统容器</param>
/// <param name="deserializeSystems">Deserialize 系统容器</param>
/// <param name="lateUpdateSystems">LateUpdate 系统容器</param>
void RegisterSystems(
Dictionary<long, Action<Entity>> awakeSystems,
Dictionary<long, Action<Entity>> updateSystems,
Dictionary<long, Action<Entity>> destroySystems,
Dictionary<long, Action<Entity>> deserializeSystems,
Dictionary<long, Action<Entity>> lateUpdateSystems);
/// <summary>
/// 取消注册该程序集中的所有实体系统(热重载卸载时调用)
/// </summary>
/// <param name="awakeSystems">Awake 系统容器</param>
/// <param name="updateSystems">Update 系统容器</param>
/// <param name="destroySystems">Destroy 系统容器</param>
/// <param name="deserializeSystems">Deserialize 系统容器</param>
/// <param name="lateUpdateSystems">LateUpdate 系统容器</param>
void UnRegisterSystems(
Dictionary<long, Action<Entity>> awakeSystems,
Dictionary<long, Action<Entity>> updateSystems,
Dictionary<long, Action<Entity>> destroySystems,
Dictionary<long, Action<Entity>> deserializeSystems,
Dictionary<long, Action<Entity>> lateUpdateSystems);
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 90f03af4cb2313c4e9891773de0ba9c8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
namespace Fantasy.Assembly
{
/// <summary>
/// 实体类型集合注册器接口
/// 由 Source Generator 自动生成实现类,用于收集和提供程序集中定义的所有实体类型
/// </summary>
public interface IEntityTypeCollectionRegistrar
{
/// <summary>
/// 获取该程序集中定义的所有实体类型
/// 返回继承自 Entity 的所有类型列表
/// </summary>
/// <returns>实体类型列表</returns>
List<Type> GetEntityTypes();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 15eca5c104b90d548b156fe72a8ab7df
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,35 @@
using System;
using Fantasy.DataStructure.Collection;
using Fantasy.Event;
namespace Fantasy.Assembly
{
/// <summary>
/// 事件系统注册器接口
/// 由 Source Generator 自动生成实现类,用于在程序集加载时注册事件系统
/// </summary>
public interface IEventSystemRegistrar : IDisposable
{
/// <summary>
/// 注册该程序集中的所有事件系统
/// </summary>
/// <param name="events">同步事件容器</param>
/// <param name="asyncEvents">异步事件容器</param>
/// <param name="sphereEvents">领域事件容器</param>
void RegisterSystems(
OneToManyList<RuntimeTypeHandle, IEvent> events,
OneToManyList<RuntimeTypeHandle, IEvent> asyncEvents,
OneToManyList<RuntimeTypeHandle, IEvent> sphereEvents);
/// <summary>
/// 取消注册该程序集中的所有事件系统(热重载卸载时调用)
/// </summary>
/// <param name="events">同步事件容器</param>
/// <param name="asyncEvents">异步事件容器</param>
/// <param name="sphereEvents">领域事件容器</param>
void UnRegisterSystems(
OneToManyList<RuntimeTypeHandle, IEvent> events,
OneToManyList<RuntimeTypeHandle, IEvent> asyncEvents,
OneToManyList<RuntimeTypeHandle, IEvent> sphereEvents);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 54d9defaadec9b043bf8b3efea1f0373
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using Fantasy.DataStructure.Dictionary;
using Fantasy.Entitas;
using Fantasy.Network;
using Fantasy.Network.Interface;
using Fantasy.Async;
namespace Fantasy.Assembly
{
/// <summary>
/// 消息分发器注册器接口
/// 由 Source Generator 自动生成实现类,用于在程序集加载时注册网络消息处理器
/// </summary>
public interface IMessageHandlerResolver
{
/// <summary>
///
/// </summary>
/// <returns></returns>
int GetMessageHandlerCount();
/// <summary>
///
/// </summary>
/// <param name="session"></param>
/// <param name="rpcId"></param>
/// <param name="protocolCode"></param>
/// <param name="message"></param>
/// <returns></returns>
bool MessageHandler(Session session, uint rpcId, uint protocolCode, object message);
#if FANTASY_NET
/// <summary>
///
/// </summary>
/// <returns></returns>
int GetRouteMessageHandlerCount();
/// <summary>
///
/// </summary>
/// <param name="session"></param>
/// <param name="entity"></param>
/// <param name="rpcId"></param>
/// <param name="protocolCode"></param>
/// <param name="message"></param>
/// <returns></returns>
FTask<bool> RouteMessageHandler(Session session, Entity entity, uint rpcId, uint protocolCode, object message);
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2fa889cb07e1d344ca1b5e592c991575
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,40 @@
using System;
namespace Fantasy.Assembly
{
/// <summary>
/// 网络协议 OpCode 解析器接口
/// 用于通过生成的 switch 表达式实现高性能的 OpCode 到 Type 的解析
/// </summary>
/// <remarks>
/// 此接口由 SourceGenerator 自动生成的类实现。
/// 每个包含网络协议的程序集都会生成自己的解析器实现。
/// 生成的实现使用 switch 表达式而不是字典查找,以获得更好的性能。
/// </remarks>
public interface INetworkProtocolOpCodeResolver
{
/// <summary>
/// 获取当前OpCode数量
/// </summary>
/// <returns>返回对应的OpCode数量</returns>
int GetOpCodeCount();
/// <summary>
/// 获取当前RouteType数量
/// </summary>
/// <returns>返回对应的RouteType数量</returns>
int GetCustomRouteTypeCount();
/// <summary>
/// 根据指定的 OpCode 获取对应的 Type
/// </summary>
/// <param name="opCode">网络协议操作码</param>
/// <returns>OpCode 对应的类型;如果未找到则返回 null</returns>
Type GetOpCodeType(uint opCode);
/// <summary>
/// 根据指定的 OpCode 获取对应的 CustomRouteType
/// </summary>
/// <param name="opCode">网络协议操作码</param>
/// <returns>OpCode 对应的CustomRouteType如果未找到则返回 null</returns>
int? GetCustomRouteType(uint opCode);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5bc0a3c95fad0b54bafc40ad4efd9df3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
namespace Fantasy.Assembly
{
/// <summary>
/// NetworkProtocol 类型注册器接口
/// 由 Source Generator 自动生成实现类,用于收集和提供程序集中需要 NetworkProtocol 序列化的类型
/// </summary>
public interface INetworkProtocolRegistrar
{
/// <summary>
/// 获取该程序集中需要 NetworkProtocol 序列化的所有类型
/// 返回所有使用 NetworkProtocol 序列化特性标记的类型列表
/// </summary>
/// <returns>NetworkProtocol 序列化类型列表</returns>
List<Type> GetNetworkProtocolTypes();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 455bad26fa79b644ba8415cb1ba700fd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,26 @@
using System;
namespace Fantasy.Assembly
{
/// <summary>
/// 网络协议响应类型解析器接口
/// 用于根据 OpCode 解析对应的响应消息类型
/// 此接口通常由 NetworkProtocol SourceGenerator 自动生成实现
/// </summary>
public interface INetworkProtocolResponseTypeResolver
{
/// <summary>
/// 获取已注册的 Response 总数
/// </summary>
/// <returns>协议系统中可用的 OpCode 数量</returns>
int GetRequestCount();
/// <summary>
/// 获取指定 OpCode 对应的响应消息类型
/// 用于在处理网络消息时确定期望的响应类型
/// </summary>
/// <param name="opCode">要解析的 OpCode</param>
/// <returns>响应消息的类型,如果该 OpCode 没有关联响应类型则返回 null</returns>
Type GetResponseType(uint opCode);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 214fdb11ed5d07649973f5d6ad14a792
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,34 @@
#if FANTASY_NET
namespace Fantasy.Assembly;
/// <summary>
/// 分表注册器接口,用于自动注册标记了 SeparateTableAttribute 的实体类型。
/// 通过 Source Generator 自动生成实现类,替代运行时反射,提升性能。
/// </summary>
public interface ISeparateTableRegistrar
{
/// <summary>
/// 分表信息记录,包含父实体类型、子实体类型和数据库集合名称。
/// </summary>
/// <param name="RootType">父实体的类型,表示子实体属于哪个父实体。</param>
/// <param name="EntityType">子实体的类型,即标记了 SeparateTableAttribute 的实体类型。</param>
/// <param name="TableName">在数据库中使用的集合名称。</param>
public sealed record SeparateTableInfo(Type RootType, Type EntityType, string TableName);
/// <summary>
/// 注册所有分表信息。
/// 返回包含所有标记了 SeparateTableAttribute 的实体及其配置信息的列表。
/// </summary>
/// <returns>分表信息列表。</returns>
List<SeparateTableInfo> Register();
/// <summary>
/// 反注册所有分表信息。
/// 返回需要移除的分表信息列表。
/// </summary>
/// <returns>分表信息列表。</returns>
List<SeparateTableInfo> UnRegister();
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e44ea41d753b8454bba7ca941bc4e3d6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 40a9658e852cd49428c3c3d2495f92b7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 38db8c2397499654db6dd3b6c3711060
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,25 @@
using Fantasy.Async;
using Fantasy.InnerMessage;
using Fantasy.Network.Interface;
#if FANTASY_NET
namespace Fantasy.Network.Benchmark.Handler;
/// <summary>
/// BenchmarkRequestHandler
/// </summary>
public sealed class BenchmarkRequestHandler : MessageRPC<BenchmarkRequest, BenchmarkResponse>
{
/// <summary>
/// Run方法
/// </summary>
/// <param name="session"></param>
/// <param name="request"></param>
/// <param name="response"></param>
/// <param name="reply"></param>
protected override async FTask Run(Session session, BenchmarkRequest request, BenchmarkResponse response, Action reply)
{
await FTask.CompletedTask;
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 819edfe1cb956754986be6db2e67ba07
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4961a2a852e804ad69bad45492f22555
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,20 @@
// ReSharper disable CheckNamespace
// ReSharper disable InconsistentNaming
#if FANTASY_NET
namespace Fantasy.DataBase;
/// <summary>
/// 数据库类型
/// </summary>
public enum DataBaseType
{
/// <summary>
/// 默认
/// </summary>
None = 0,
/// <summary>
/// MongoDB
/// </summary>
MongoDB = 1
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: abd508d980707405bb2d5bbfee14b6b8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,210 @@
#if FANTASY_NET
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using Fantasy.Async;
using Fantasy.Entitas;
using MongoDB.Driver;
// ReSharper disable InconsistentNaming
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
#pragma warning disable CS8625
namespace Fantasy.DataBase
{
/// <summary>
/// 数据库设置助手
/// </summary>
public static class DataBaseSetting
{
/// <summary>
/// 初始化自定义委托当设置了这个委托后就不会自动创建MongoClient需要自己在委托里创建MongoClient。
/// </summary>
public static Func<DataBaseCustomConfig, MongoClient>? MongoDBCustomInitialize;
}
/// <summary>
/// MongoDB自定义连接参数
/// </summary>
public sealed class DataBaseCustomConfig
{
/// <summary>
/// 当前Scene
/// </summary>
public Scene Scene;
/// <summary>
/// 连接字符串
/// </summary>
public string ConnectionString;
/// <summary>
/// 数据库名字
/// </summary>
public string DBName;
}
/// <summary>
/// 表示用于执行各种数据库操作的数据库接口。
/// </summary>
public interface IDataBase : IDisposable
{
/// <summary>
/// 获得当前数据的类型
/// </summary>
public DataBaseType GetDataBaseType { get;}
/// <summary>
/// 获得对应数据的操作实例
/// </summary>
/// <returns>如MongoDB就是IMongoDatabase</returns>
public object GetDataBaseInstance { get;}
/// <summary>
/// 初始化数据库连接。
/// </summary>
IDataBase Initialize(Scene scene, string connectionString, string dbName);
/// <summary>
/// 在指定的集合中检索类型 <typeparamref name="T"/> 的实体数量。
/// </summary>
FTask<long> Count<T>(string collection = null) where T : Entity;
/// <summary>
/// 在指定的集合中检索满足给定筛选条件的类型 <typeparamref name="T"/> 的实体数量。
/// </summary>
FTask<long> Count<T>(Expression<Func<T, bool>> filter, string collection = null) where T : Entity;
/// <summary>
/// 检查指定集合中是否存在类型 <typeparamref name="T"/> 的实体。
/// </summary>
FTask<bool> Exist<T>(string collection = null) where T : Entity;
/// <summary>
/// 检查指定集合中是否存在满足给定筛选条件的类型 <typeparamref name="T"/> 的实体。
/// </summary>
FTask<bool> Exist<T>(Expression<Func<T, bool>> filter, string collection = null) where T : Entity;
/// <summary>
/// 从指定集合中检索指定 ID 的类型 <typeparamref name="T"/> 的实体,不锁定。
/// </summary>
FTask<T> QueryNotLock<T>(long id, bool isDeserialize = false, string collection = null) where T : Entity;
/// <summary>
/// 从指定集合中检索指定 ID 的类型 <typeparamref name="T"/> 的实体。
/// </summary>
FTask<T> Query<T>(long id, bool isDeserialize = false, string collection = null) where T : Entity;
/// <summary>
/// 按页查询满足给定筛选条件的类型 <typeparamref name="T"/> 的实体数量和日期。
/// </summary>
FTask<(int count, List<T> dates)> QueryCountAndDatesByPage<T>(Expression<Func<T, bool>> filter, int pageIndex, int pageSize, bool isDeserialize = false, string collection = null) where T : Entity;
/// <summary>
/// 按页查询满足给定筛选条件的类型 <typeparamref name="T"/> 的实体数量和日期。
/// </summary>
FTask<(int count, List<T> dates)> QueryCountAndDatesByPage<T>(Expression<Func<T, bool>> filter, int pageIndex, int pageSize, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity;
/// <summary>
/// 分页查询指定集合中满足给定筛选条件的类型 <typeparamref name="T"/> 的实体列表。
/// </summary>
FTask<List<T>> QueryByPage<T>(Expression<Func<T, bool>> filter, int pageIndex, int pageSize, bool isDeserialize = false, string collection = null) where T : Entity;
/// <summary>
/// 分页查询指定集合中满足给定筛选条件的类型 <typeparamref name="T"/> 的实体列表,仅返回指定列的数据。
/// </summary>
FTask<List<T>> QueryByPage<T>(Expression<Func<T, bool>> filter, int pageIndex, int pageSize, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity;
/// <summary>
/// 从指定集合中按页查询满足给定筛选条件的类型 <typeparamref name="T"/> 的实体列表,按指定字段排序。
/// </summary>
FTask<List<T>> QueryByPageOrderBy<T>(Expression<Func<T, bool>> filter, int pageIndex, int pageSize, Expression<Func<T, object>> orderByExpression, bool isAsc = true, bool isDeserialize = false, string collection = null) where T : Entity;
/// <summary>
/// 检索满足给定筛选条件的类型 <typeparamref name="T"/> 的第一个实体,从指定集合中。
/// </summary>
FTask<T?> First<T>(Expression<Func<T, bool>> filter, bool isDeserialize = false, string collection = null) where T : Entity;
/// <summary>
/// 查询指定集合中满足给定 JSON 查询字符串的类型 <typeparamref name="T"/> 的第一个实体,仅返回指定列的数据。
/// </summary>
FTask<T> First<T>(string json, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity;
/// <summary>
/// 从指定集合中按页查询满足给定筛选条件的类型 <typeparamref name="T"/> 的实体列表,按指定字段排序。
/// </summary>
FTask<List<T>> QueryOrderBy<T>(Expression<Func<T, bool>> filter, Expression<Func<T, object>> orderByExpression, bool isAsc = true, bool isDeserialize = false, string collection = null) where T : Entity;
/// <summary>
/// 从指定集合中按页查询满足给定筛选条件的类型 <typeparamref name="T"/> 的实体列表。
/// </summary>
FTask<List<T>> Query<T>(Expression<Func<T, bool>> filter, bool isDeserialize = false, string collection = null) where T : Entity;
/// <summary>
/// 查询指定集合中满足给定筛选条件的类型 <typeparamref name="T"/> 实体列表,仅返回指定字段的数据。
/// </summary>
FTask<List<T>> Query<T>(Expression<Func<T, bool>> filter, Expression<Func<T, object>>[] cols, bool isDeserialize = false, string collection = null) where T : Entity;
/// <summary>
/// 查询指定 ID 的多个集合,将结果存储在给定的实体列表中。
/// </summary>
FTask Query(long id, List<string> collectionNames, List<Entity> result, bool isDeserialize = false);
/// <summary>
/// 根据给定的 JSON 查询字符串查询指定集合中的类型 <typeparamref name="T"/> 实体列表。
/// </summary>
FTask<List<T>> QueryJson<T>(string json, bool isDeserialize = false, string collection = null) where T : Entity;
/// <summary>
/// 根据给定的 JSON 查询字符串查询指定集合中的类型 <typeparamref name="T"/> 实体列表,仅返回指定列的数据。
/// </summary>
FTask<List<T>> QueryJson<T>(string json, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity;
/// <summary>
/// 根据给定的 JSON 查询字符串查询指定集合中的类型 <typeparamref name="T"/> 实体列表,通过指定的任务 ID 进行标识。
/// </summary>
FTask<List<T>> QueryJson<T>(long taskId, string json, bool isDeserialize = false, string collection = null) where T : Entity;
/// <summary>
/// 查询指定集合中满足给定筛选条件的类型 <typeparamref name="T"/> 实体列表,仅返回指定列的数据。
/// </summary>
FTask<List<T>> Query<T>(Expression<Func<T, bool>> filter, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity;
/// <summary>
/// 保存类型 <typeparamref name="T"/> 实体到指定集合中,如果集合不存在将自动创建。
/// </summary>
FTask Save<T>(T entity, string collection = null) where T : Entity, new();
/// <summary>
/// 保存一组实体到数据库中,根据实体列表的 ID 进行区分和存储。
/// </summary>
FTask Save(long id, List<Entity> entities);
/// <summary>
/// 通过事务会话将类型 <typeparamref name="T"/> 实体保存到指定集合中,如果集合不存在将自动创建。
/// </summary>
FTask Save<T>(object transactionSession, T entity, string collection = null) where T : Entity;
/// <summary>
/// 向指定集合中插入一个类型 <typeparamref name="T"/> 实体,如果集合不存在将自动创建。
/// </summary>
FTask Insert<T>(T entity, string collection = null) where T : Entity, new();
/// <summary>
/// 批量插入一组类型 <typeparamref name="T"/> 实体到指定集合中,如果集合不存在将自动创建。
/// </summary>
FTask InsertBatch<T>(IEnumerable<T> list, string collection = null) where T : Entity, new();
/// <summary>
/// 通过事务会话,批量插入一组类型 <typeparamref name="T"/> 实体到指定集合中,如果集合不存在将自动创建。
/// </summary>
FTask InsertBatch<T>(object transactionSession, IEnumerable<T> list, string collection = null) where T : Entity, new();
/// <summary>
/// 通过事务会话,根据指定的 ID 从数据库中删除指定类型 <typeparamref name="T"/> 实体。
/// </summary>
FTask<long> Remove<T>(object transactionSession, long id, string collection = null) where T : Entity, new();
/// <summary>
/// 根据指定的 ID 从数据库中删除指定类型 <typeparamref name="T"/> 实体。
/// </summary>
FTask<long> Remove<T>(long id, string collection = null) where T : Entity, new();
/// <summary>
/// 通过事务会话,根据给定的筛选条件从数据库中删除指定类型 <typeparamref name="T"/> 实体。
/// </summary>
FTask<long> Remove<T>(long coroutineLockQueueKey, object transactionSession, Expression<Func<T, bool>> filter, string collection = null) where T : Entity, new();
/// <summary>
/// 根据给定的筛选条件从数据库中删除指定类型 <typeparamref name="T"/> 实体。
/// </summary>
FTask<long> Remove<T>(long coroutineLockQueueKey, Expression<Func<T, bool>> filter, string collection = null) where T : Entity, new();
/// <summary>
/// 根据给定的筛选条件计算指定集合中类型 <typeparamref name="T"/> 实体某个属性的总和。
/// </summary>
FTask<long> Sum<T>(Expression<Func<T, bool>> filter, Expression<Func<T, object>> sumExpression, string collection = null) where T : Entity;
/// <summary>
/// 在指定的集合中创建索引,以提高类型 <typeparamref name="T"/> 实体的查询性能。
/// </summary>
FTask CreateIndex<T>(string collection, params object[] keys) where T : Entity;
/// <summary>
/// 在默认集合中创建索引,以提高类型 <typeparamref name="T"/> 实体的查询性能。
/// </summary>
FTask CreateIndex<T>(params object[] keys) where T : Entity;
/// <summary>
/// 创建指定类型 <typeparamref name="T"/> 的数据库,用于存储实体。
/// </summary>
FTask CreateDB<T>() where T : Entity;
/// <summary>
/// 根据指定类型创建数据库,用于存储实体。
/// </summary>
FTask CreateDB(Type type);
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 83202aeeb0df040de8735030956ca861
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 366dcc82090f0466da2c2b8d0e6fa2c2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,77 @@
#pragma warning disable CS8603 // Possible null reference return.
#if FANTASY_NET
using Fantasy.Platform.Net;
namespace Fantasy.DataBase
{
/// <summary>
/// 表示一个游戏世界。
/// </summary>
public sealed class World : IDisposable
{
/// <summary>
/// 获取游戏世界的唯一标识。
/// </summary>
public byte Id { get; private init; }
/// <summary>
/// 获取游戏世界的数据库接口。
/// </summary>
public IDataBase DataBase { get; private init; }
/// <summary>
/// 获取游戏世界的配置信息。
/// </summary>
public WorldConfig Config => WorldConfigData.Instance.Get(Id);
/// <summary>
/// 使用指定的配置信息创建一个游戏世界实例。
/// </summary>
/// <param name="scene"></param>
/// <param name="worldConfigId"></param>
private World(Scene scene, byte worldConfigId)
{
Id = worldConfigId;
var worldConfig = Config;
var dbType = worldConfig.DbType.ToLower();
switch (dbType)
{
case "mongodb":
{
DataBase = new MongoDataBase();
DataBase.Initialize(scene, worldConfig.DbConnection, worldConfig.DbName);
break;
}
default:
{
throw new Exception("No supported database");
}
}
}
/// <summary>
/// 创建一个指定唯一标识的游戏世界实例。
/// </summary>
/// <param name="scene"></param>
/// <param name="id">游戏世界的唯一标识。</param>
/// <returns>游戏世界实例。</returns>
internal static World Create(Scene scene, byte id)
{
if (!WorldConfigData.Instance.TryGet(id, out var worldConfigData))
{
return null;
}
return string.IsNullOrEmpty(worldConfigData.DbConnection) ? null : new World(scene, id);
}
/// <summary>
/// 释放游戏世界资源。
/// </summary>
public void Dispose()
{
DataBase.Dispose();
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6f02362ef34274e508386e3cdc247927
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: afe876696fb5f4779abe3289a3bf4f9f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0b3d477f03f404310b5ad48f89d1f193
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,346 @@
using System;
using System.Collections.Generic;
using System.IO;
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
namespace Fantasy.DataStructure.Collection
{
/// 环形缓存自增式缓存自动扩充、不会收缩缓存、所以不要用这个操作过大的IO流
/// 1、环大小8192溢出的会自动增加环的大小。
/// 2、每个块都是一个环形缓存当溢出的时候会自动添加到下一个环中。
/// 3、当读取完成后用过的环会放在缓存中不会销毁掉。
/// <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();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 38264758f23d1448a975efc4f4b7da63
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,197 @@
#if !FANTASY_WEBGL
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Fantasy.Pool;
#pragma warning disable CS8603 // Possible null reference return.
namespace Fantasy.DataStructure.Collection
{
/// <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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 21b2d3135b133441aa08c5eb1768a103
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,194 @@
#if !FANTASY_WEBGL
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using Fantasy.Pool;
#pragma warning disable CS8603
namespace Fantasy.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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 50a0f004d8b7b4075a4ce01376e49375
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using Fantasy.Pool;
namespace Fantasy.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();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c49468bddc6524c419ace92b9ad68a5d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using Fantasy.Pool;
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
namespace Fantasy.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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f629886e3686046c6b00225679a7dbf5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,208 @@
using System;
using System.Collections.Generic;
using Fantasy.Pool;
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
namespace Fantasy.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();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4e0cac58c09d6429faba279d03ef2317
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,232 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Fantasy.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 Fantasy.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>
/// 设置最大缓存数量
/// </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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2c48383c842aa40b683f03b79f845932
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,204 @@
using System;
using System.Collections.Generic;
using Fantasy.Pool;
#pragma warning disable CS8603
namespace Fantasy.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();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8b6455b3359394987af4c0a4855189e9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using Fantasy.Pool;
namespace Fantasy.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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9826877496b99479892a7359519298d5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,226 @@
#if !FANTASY_WEBGL
using System;
using System.Collections.Generic;
using System.Linq;
using Fantasy.Pool;
#pragma warning disable CS8603
namespace Fantasy.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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fef1d9dea81914fcf9fc9f5b1e5989d5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,192 @@
using System;
using System.Collections.Generic;
using Fantasy.Pool;
namespace Fantasy.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();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4fe9032846c3f4a11be30459a9e12d7a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,217 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Fantasy.Pool;
#pragma warning disable CS8603
namespace Fantasy.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();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8e0621d52f951402daa68b108d7129f4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4a444c902ff594bbaa3a3fb394e7f53a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,31 @@
using System.Collections.Generic;
#pragma warning disable CS8601 // Possible null reference assignment.
namespace Fantasy.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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6975fd47ec81d474cbd1ba996d01e864
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