Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

* 架构分析器守卫三项收口(C429/C430,GFX-1170,GitHub #916):GFX0016 候选集从仅 Class 放宽为全部命名类型种类——struct/interface/record 声明面引用 `MongoDbService`/`PostgreSqlDbService`/`MultiDbRegistry` 不再漏报(自行注册 NamedType 符号分析,实现层白名单与豁免不变);GFX0001 补抽象类豁免(与 GFX0005/0008/0009 一致,非 Apps 程序集的抽象中间状态类不再被误拦);生成 Wrapper 豁免改为「`GeneratedCodeAttribute` 标记(tool 名精确匹配)+ `.g.cs` 文件路径 + 仅 Hotfix 内名称后缀兜底」三段判定,Hotfix 之外手写 `ComponentAgentWrapper` 后缀类不再同时逃过 GFX0005/GFX0014;WrapperGenerator 生成类头追加 `GeneratedCodeAttribute` 标记(与分析器 `WrapperGeneratorToolName` 同值契约)。
* 入队与定时撮合解耦(C406,GFX-1104):删除每次入队的全量撮合及最新票据扫描,复用 `matchmaker-run` 按宿主调度节奏批量处理队列(默认 1000ms)。单轮撮合算法保持不变;桥测试覆盖四名玩家单批次形成两组及批次前取消不参与分配。
* Hotfix Wrapper 生成器诊断体验、增量管道与低风险清理(C432,GFX-1172,GitHub #918,CODE_REVIEW CR-M06/M07/L03/L04/L05):①诊断改为静态描述符 + 稳定 ID `GFW0001`(契约违规 Error)/ `GFW0002`(契约警示 Warning),携带违规方法 `GetLocation()` 位置(错误列表可点击定位、可经 `.editorconfig` `dotnet_diagnostic_GFW000x.severity` 按 ID 抑制),消息统一引用 `Service`/`ThreadSafe`/`Discard`/`TimeOut` 真实特性常量(消灭不存在的【Api】/【AsyncApi】幻觉名);②增量管道按「代理类声明 → 输出」粒度注册(`WithComparer` 文本等值缓存 + `WithTrackingName`),替代 `Collect()` 全量折叠——单个代理类编辑只重算该代理 wrapper(生产 25 个代理全量生成产物逐字符不变,非 partial hintName 逐字节不变),partial 部件 hintName 序号改声明位置稳定键(FNV-1a 64,跨进程确定);语义调整:无基类列表的 partial 类不再按简单名并入生成面(生产零实例,需要拆分的代理应每部件自带相同基类列表);③`AgentTemplate` 删 `[ThreadStatic]` 静态 `StringBuilder`(局部实例参数化传递,消除矛盾注释);④`ComponentAgentBaseNames` 删除全仓不存在的 `QueryComponentAgent` 死条目。
* Hotfix Wrapper 生成器诊断体验、增量管道与低风险清理(C432,GFX-1172,GitHub #918,CODE_REVIEW CR-M06/M07/L03/L04/L05):①诊断改为静态描述符 + 稳定 ID `GFW0001`(契约违规 Error)/ `GFW0002`(契约警示 Warning),携带违规方法 `GetLocation()` 位置(错误列表可点击定位、可经 `.editorconfig` `dotnet_diagnostic_GFW000x.severity` 按 ID 抑制),消息统一引用 `Service`/`ThreadSafe`/`Discard`/`TimeOut` 真实特性常量(消灭不存在的【Api】/【AsyncApi】幻觉名);②增量管道按「代理类声明 → 输出」粒度注册(`WithComparer` 文本等值缓存 + `WithTrackingName`),替代 `Collect()` 全量折叠——单个代理类编辑只重算该代理 wrapper(生产 25 个代理全量生成产物逐字符不变,非 partial hintName 逐字节不变),partial 部件 hintName 序号改声明位置稳定键(FNV-1a 64,跨进程确定);语义调整:无基类列表的 partial 类不再按简单名并入生成面(生产零实例,需要拆分的代理应每部件自带相同基类列表);③`AgentTemplate` 删 `[ThreadStatic]` 静态 `StringBuilder`(局部实例参数化传递,消除矛盾注释);④`ComponentAgentBaseNames` 删除全仓不存在的 `QueryComponentAgent` 死条目。与 C430 合并调停:`GeneratedCodeAttribute` 标记仅由非 partial 单体声明发射(逐声明独立缓存无法稳定选举唯一携带者,partial 分部一律省略标记以杜绝 CS0579,分析器豁免由 `.g.cs` 路径段与名称后缀兜底),标记常量移至 `HotfixWrapperGenerator`(`AgentTemplate` 保持零静态字段契约)。
* 撮合通知异步执行与停用退订(C407,GFX-1105,GitHub #733):`ICapabilityEventBus.Subscribe` 返回幂等 `IDisposable` 退订句柄(void → IDisposable 为二进制破坏性变更——包族消费方需重新编译;源码侧忽略返回值的既有调用不受影响);online-match 模块撮合分配事件回调改为纯入队(移除同步总线回调内 `ResolveAssignmentAsync` / `session.WriteAsync` 两处 `GetAwaiter().GetResult()` 死锁面),新增 `match-assignment-notification-tick` 每轮有限批次(64)异步解析并推送(条目级失败不阻断同批其余条目,异常交调度器既有日志路径);`DeactivateAsync` 停用收口——关闭入队、退订、清空待处理并等待在途批次完成,热更/停机后旧模块零通知。回归 13 例:总线退订契约 5 + 模块生命周期 8(含对旧同步实现确定性红的发布不同步解析 / 停用退订 / 停用清空 / 容错批次)。

* 举报入口频控与未结案去重(C405,GFX-1115):举报提交接入双固定窗口限流(`OnlineSocialOptions.Report.MaximumSubmissionsPerMinute=5` / `MaximumSubmissionsPerDay=50`,键=租户/App/举报人,超限 7001 且不落案件不发事件,分钟窗惰性清扫仅删双窗均过期的桶);`IOnlineReportStore.SaveIfAbsentAsync` 升级为两层原子唯一(案件标识重放 + 未结案组合键「举报人+被举报人+场景」,Submitted/Reviewing 占位、终态释放),重复举报返回原案件原证据且仅首建发 `ReportChanged`;`ReplaceAsync` 身份三要素不可改、终态释放与新占位归属在 CAS 同锁维护;`OnlineSocialDecisionService` 构造新增必填 `OnlineReportOptions`,宿主自定义举报存储必须遵循同一原子唯一性语义
Expand Down
19 changes: 1 addition & 18 deletions GameFrameX.Hotfix.WrapperGenerator/Agent/AgentTemplate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,23 +39,6 @@ namespace GameFrameX.Hotfix.WrapperGenerator.Agent;
/// </remarks>
public static class AgentTemplate
{
/// <summary>
/// 生成器标识:写入生成类 GeneratedCodeAttribute 的 tool 名。
/// </summary>
/// <remarks>
/// 架构分析器按此值精确匹配豁免生成产物(与 Architecture.Analyzers 侧
/// ArchitectureAnalyzerConstants.WrapperGeneratorToolName 为同值契约,改名须双侧同步)。
/// The tool name written into the GeneratedCodeAttribute on generated wrapper classes.
/// </remarks>
private const string GeneratedCodeToolName = "GameFrameX.Hotfix.WrapperGenerator";

/// <summary>
/// 生成器版本:写入生成类 GeneratedCodeAttribute 的 version 参数。
/// </summary>
/// <remarks>
/// The version argument written into the GeneratedCodeAttribute on generated wrapper classes.
/// </remarks>
private const string GeneratedCodeVersion = "1.0";
/// <summary>
/// 根据代理信息生成包装器分部类的源代码。
/// </summary>
Expand All @@ -81,7 +64,7 @@ public static string Run(AgentInfo info)
templateStringBuilder.AppendLine("{");
if (info.EmitGeneratedCodeMarker)
{
templateStringBuilder.AppendLine("\t[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"" + GeneratedCodeToolName + "\", \"" + GeneratedCodeVersion + "\")]");
templateStringBuilder.AppendLine("\t[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"" + HotfixWrapperGenerator.GeneratedCodeToolName + "\", \"" + HotfixWrapperGenerator.GeneratedCodeVersion + "\")]");
}
templateStringBuilder.AppendLine("\tpublic partial class " + info.Name + " : " + info.Super);
templateStringBuilder.AppendLine("\t{");
Expand Down
19 changes: 19 additions & 0 deletions GameFrameX.Hotfix.WrapperGenerator/Agent/HotfixWrapperGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,25 @@ public class HotfixWrapperGenerator : IIncrementalGenerator
/// </remarks>
private const string TimeOutAttributeName = "TimeOut";

/// <summary>
/// 生成器标识:写入生成类 GeneratedCodeAttribute 的 tool 名。
/// </summary>
/// <remarks>
/// 架构分析器按此值精确匹配豁免生成产物(与 Architecture.Analyzers 侧
/// ArchitectureAnalyzerConstants.WrapperGeneratorToolName 为同值契约,改名须双侧同步)。
/// 放在生成器而非 AgentTemplate:模板类契约「无任何静态字段」(见 HotfixWrapperGeneratorDiagnosticReportingTests)。
/// The tool name written into the GeneratedCodeAttribute on generated wrapper classes.
/// </remarks>
internal const string GeneratedCodeToolName = "GameFrameX.Hotfix.WrapperGenerator";

/// <summary>
/// 生成器版本:写入生成类 GeneratedCodeAttribute 的 version 参数。
/// </summary>
/// <remarks>
/// The version argument written into the GeneratedCodeAttribute on generated wrapper classes.
/// </remarks>
internal const string GeneratedCodeVersion = "1.0";

/// <summary>
/// 增量管线输出步骤的跟踪名:测试与诊断经 <c>TrackedSteps[该名]</c> 断言逐声明的缓存/重算行为。
/// </summary>
Expand Down
50 changes: 50 additions & 0 deletions GameFrameX.Localization/Keys.Utility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,56 @@ public static class RandomHelper
/// 参数: {0} - 需求的数量, {1} - 数组长度
/// </remarks>
public const string CantRepeatRandomArgError = "Utility.RandomHelper.CantRepeatRandomArgError";

/// <summary>
/// 权重为负数
/// </summary>
/// <remarks>
/// 键名: Utility.RandomHelper.NegativeWeight
/// 用途: 权重随机入参包含负权重时抛出异常
/// 参数: {0} - 非法权重值
/// </remarks>
public const string NegativeWeight = "Utility.RandomHelper.NegativeWeight";

/// <summary>
/// 全部权重为 0
/// </summary>
/// <remarks>
/// 键名: Utility.RandomHelper.ZeroTotalWeight
/// 用途: 参与随机的权重总和为 0(无可选目标)时抛出异常
/// 参数: 无
/// </remarks>
public const string ZeroTotalWeight = "Utility.RandomHelper.ZeroTotalWeight";

/// <summary>
/// 权重总和超出 int 上限
/// </summary>
/// <remarks>
/// 键名: Utility.RandomHelper.WeightSumOverflow
/// 用途: 权重求和超出随机源可表达的 32 位范围时抛出异常
/// 参数: {0} - 实际总权重
/// </remarks>
public const string WeightSumOverflow = "Utility.RandomHelper.WeightSumOverflow";

/// <summary>
/// 权重索引越界
/// </summary>
/// <remarks>
/// 键名: Utility.RandomHelper.WeightIndexOutOfRange
/// 用途: weightIndex 超出权重条目长度时抛出异常
/// 参数: {0} - 权重索引, {1} - 条目长度
/// </remarks>
public const string WeightIndexOutOfRange = "Utility.RandomHelper.WeightIndexOutOfRange";

/// <summary>
/// 不可重复抽样时正权重条目不足
/// </summary>
/// <remarks>
/// 键名: Utility.RandomHelper.InsufficientPositiveWeights
/// 用途: 不可重复抽取的目标数大于权重为正的条目数时抛出异常
/// 参数: {0} - 需求数量, {1} - 正权重条目数
/// </remarks>
public const string InsufficientPositiveWeights = "Utility.RandomHelper.InsufficientPositiveWeights";
}


Expand Down
20 changes: 20 additions & 0 deletions GameFrameX.Localization/Localization/Messages/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,26 @@
<value>Cannot repeat random argument error, requested count: {0} is greater than ID count: {1}</value>
</data>

<data name="Utility.RandomHelper.NegativeWeight" xml:space="preserve">
<value>Weight must not be negative, actual weight: {0}</value>
</data>

<data name="Utility.RandomHelper.ZeroTotalWeight" xml:space="preserve">
<value>Total weight must be greater than zero</value>
</data>

<data name="Utility.RandomHelper.WeightSumOverflow" xml:space="preserve">
<value>Total weight {0} exceeds the supported 32-bit range</value>
</data>

<data name="Utility.RandomHelper.WeightIndexOutOfRange" xml:space="preserve">
<value>Weight index {0} is out of range for an entry with {1} element(s)</value>
</data>

<data name="Utility.RandomHelper.InsufficientPositiveWeights" xml:space="preserve">
<value>Cannot pick {0} distinct weighted items, only {1} item(s) have a positive weight</value>
</data>

<!-- Utility.BigIntegerDebug related log messages -->
<data name="Utility.BigIntegerDebug.DebugA" xml:space="preserve">
<value>a = {0}</value>
Expand Down
20 changes: 20 additions & 0 deletions GameFrameX.Localization/Localization/Messages/Resources.zh-CN.resx
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,26 @@
<value>不能重复随机参数错误,需求数量:{0} 大于ID数量:{1}</value>
</data>

<data name="Utility.RandomHelper.NegativeWeight" xml:space="preserve">
<value>权重不能为负数,实际权重:{0}</value>
</data>

<data name="Utility.RandomHelper.ZeroTotalWeight" xml:space="preserve">
<value>权重总和必须大于 0</value>
</data>

<data name="Utility.RandomHelper.WeightSumOverflow" xml:space="preserve">
<value>权重总和 {0} 超出支持的 32 位范围</value>
</data>

<data name="Utility.RandomHelper.WeightIndexOutOfRange" xml:space="preserve">
<value>权重索引 {0} 超出条目长度 {1}</value>
</data>

<data name="Utility.RandomHelper.InsufficientPositiveWeights" xml:space="preserve">
<value>无法不重复抽取 {0} 个权重目标,只有 {1} 个条目的权重为正</value>
</data>

<!-- Utility.BigIntegerDebug 相关日志消息 -->
<data name="Utility.BigIntegerDebug.DebugA" xml:space="preserve">
<value>a = {0}</value>
Expand Down
91 changes: 63 additions & 28 deletions GameFrameX.Utility/AssemblyHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,19 @@ namespace GameFrameX.Utility;
/// </remarks>
public static class AssemblyHelper
{
/// <summary>
/// 类型缓存的最大条目数;达到上限后只返回解析结果、不再写入缓存。
/// </summary>
private const int MaxCachedTypeCount = 1024;

/// <summary>
/// Type cache dictionary using type full name as key, thread-safe
/// Uses StringComparer.Ordinal for optimal string comparison performance
/// Only successful resolutions are cached: a miss is never cached so that types
/// from assemblies loaded later (hotfix) can still be found.
/// </summary>
/// <remarks>
/// 类型缓存字典,使用类型全名作为键,线程安全
/// 仅缓存命中结果:未命中不缓存,热更程序集后续加载后仍可解析
/// 使用 StringComparer.Ordinal 提供最佳性能的字符串比较
/// </remarks>
private static readonly ConcurrentDictionary<string, Type> CachedTypes = new(StringComparer.Ordinal);
Expand Down Expand Up @@ -219,6 +226,9 @@ public static void GetTypes(List<Type> results)
/// <remarks>
/// 获取已加载的程序集中的指定类型
/// 支持完全限定名和简单类型名,使用缓存机制提高查找性能
/// 仅缓存命中结果,未命中(miss)不缓存,热更程序集后续加载后仍可解析。
/// 安全约束:类型名必须是受信任的配置/代码常量;含逗号的程序集限定名会触发程序集加载,
/// 不得直接使用外部/玩家输入。
/// </remarks>
/// <param name="typeName">Type name to get, supports fully qualified names (e.g., "System.String") / 要获取的类型名,支持完全限定名(如 "System.String")</param>
/// <returns>Specified type from loaded assemblies, returns null if not found / 已加载的程序集中的指定类型,如果未找到则返回 null</returns>
Expand All @@ -227,40 +237,65 @@ public static Type GetType(string typeName)
{
ArgumentException.ThrowIfNullOrWhiteSpace(typeName);

return CachedTypes.GetOrAdd(typeName, name =>
if (CachedTypes.TryGetValue(typeName, out var cachedType))
{
// 首先尝试直接获取类型(适用于系统类型和当前程序集中的类型)
var type = Type.GetType(name);
if (type != null)
{
return type;
}
return cachedType;
}

var resolvedType = ResolveType(typeName);
if (resolvedType == null)
{
// 未命中不入缓存:热更程序集可能在后续运行期加载,miss 缓存会给出永久错误答案。
return null;
}

if (CachedTypes.Count < MaxCachedTypeCount)
{
CachedTypes.TryAdd(typeName, resolvedType);
}

return resolvedType;
}

/// <summary>
/// Resolve a type by name from the loaded assemblies without touching the cache.
/// </summary>
/// <remarks>
/// 不经过缓存解析类型:先按直接名称,再遍历已加载程序集。
/// </remarks>
/// <param name="typeName">Type name to resolve / 要解析的类型名</param>
/// <returns>Resolved type, or null if not found / 解析到的类型,未找到返回 null</returns>
private static Type ResolveType(string typeName)
{
// 首先尝试直接获取类型(适用于系统类型和当前程序集中的类型)
var type = Type.GetType(typeName);
if (type != null)
{
return type;
}

// 如果直接获取失败,遍历所有程序集查找类型
// 这种方式适用于跨程序集的类型查找
var assemblies = GetCurrentAssemblies();
foreach (var assembly in assemblies)
// 如果直接获取失败,遍历所有程序集查找类型
// 这种方式适用于跨程序集的类型查找
var assemblies = GetCurrentAssemblies();
foreach (var assembly in assemblies)
{
try
{
try
{
// 使用程序集限定名称查找类型
type = Type.GetType($"{name}, {assembly.FullName}");
if (type != null)
{
return type;
}
}
catch (Exception)
// 使用程序集限定名称查找类型
type = Type.GetType($"{typeName}, {assembly.FullName}");
if (type != null)
{
// 忽略异常(如类型不存在、安全异常等)
// 继续查找下一个程序集
continue;
return type;
}
}
catch (Exception)
{
// 忽略异常(如类型不存在、安全异常等)
// 继续查找下一个程序集
}
}

// 如果在所有程序集中都找不到该类型,返回 null
return null;
});
return null;
}

/// <summary>
Expand Down
9 changes: 9 additions & 0 deletions GameFrameX.Utility/GameFrameX.Utility.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@
<DefineConstants>TRACE;FP</DefineConstants>
</PropertyGroup>

<ItemGroup>
<!--
浮点版 FP 实现(Math/FP.Float.cs,整文件 #if !FP)在本仓两种配置下都不参与编译:
Debug/Release 均定义 FP。此处显式排除,避免把它当作本工程源文件修改(改动无法被
本仓编译器验证)。浮点实现供客户端工程(不定义 FP)共享,修改需同步客户端仓。
-->
<Compile Remove="Math/FP.Float.cs"/>
</ItemGroup>

<ItemGroup>
<PackageReference Include="GameFrameX.Foundation.Logger" Version="5.0.0"/>
<PackageReference Include="GameFrameX.Foundation.Options" Version="5.0.0"/>
Expand Down
Loading
Loading