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
5 changes: 5 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ dotnet_diagnostic.GFX0019.severity = error
dotnet_diagnostic.GFX0020.severity = error
dotnet_diagnostic.GFX0021.severity = error

# Hotfix Wrapper 源码生成器诊断(GameFrameX.Hotfix.WrapperGenerator/Utils/Logger.cs 分配):
# GFW 前缀与分析器 GFX0001-0021 错开;GFW0001=契约违规(Error)、GFW0002=契约警示(Warning),可按 ID 抑制或升级
dotnet_diagnostic.GFW0001.severity = error
dotnet_diagnostic.GFW0002.severity = warning

# PB 库(vendored 生态移植,不按本仓风格改造,整目录排除)
[GameFrameX.ProtoBuf.Net/**.cs]
csharp_prefer_braces = true:none
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +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` 死条目。
* 撮合通知异步执行与停用退订(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
87 changes: 37 additions & 50 deletions GameFrameX.Hotfix.WrapperGenerator/Agent/AgentTemplate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,6 @@ namespace GameFrameX.Hotfix.WrapperGenerator.Agent;
/// </remarks>
public static class AgentTemplate
{
/// <summary>
/// 用于拼接生成代码的字符串构建器。
/// </summary>
/// <remarks>
/// String builder for assembling generated code.
/// </remarks>
/// <summary>
/// 每次构建局部的 StringBuilder(并发驱动运行互不共享状态,产出各自完整一致)。
/// </summary>
/// <remarks>
/// A local StringBuilder per run: concurrent driver executions share no state and each produces complete, consistent output.
/// </remarks>
[ThreadStatic]
private static StringBuilder TemplateStringBuilder;

/// <summary>
/// 生成器标识:写入生成类 GeneratedCodeAttribute 的 tool 名。
/// </summary>
Expand All @@ -71,8 +56,6 @@ public static class AgentTemplate
/// The version argument written into the GeneratedCodeAttribute on generated wrapper classes.
/// </remarks>
private const string GeneratedCodeVersion = "1.0";


/// <summary>
/// 根据代理信息生成包装器分部类的源代码。
/// </summary>
Expand All @@ -83,33 +66,34 @@ public static class AgentTemplate
/// <returns>生成的 C# 源代码字符串 / Generated C# source code string</returns>
public static string Run(AgentInfo info)
{
TemplateStringBuilder = new StringBuilder();

// C432:局部 StringBuilder 逐级传参——模板无任何跨调用静态状态(并发驱动运行互不共享,
// 产出各自完整一致);替代历史 [ThreadStatic] 静态字段形态。
var templateStringBuilder = new StringBuilder();

foreach (var value in info.UsingSpaces)
{
TemplateStringBuilder.AppendLine("using " + value + ";");
templateStringBuilder.AppendLine("using " + value + ";");
}

TemplateStringBuilder.AppendLine();
templateStringBuilder.AppendLine();

TemplateStringBuilder.AppendLine("namespace " + info.Space);
TemplateStringBuilder.AppendLine("{");
templateStringBuilder.AppendLine("namespace " + info.Space);
templateStringBuilder.AppendLine("{");
if (info.EmitGeneratedCodeMarker)
{
TemplateStringBuilder.AppendLine("\t[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"" + GeneratedCodeToolName + "\", \"" + GeneratedCodeVersion + "\")]");
templateStringBuilder.AppendLine("\t[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"" + GeneratedCodeToolName + "\", \"" + GeneratedCodeVersion + "\")]");
}
TemplateStringBuilder.AppendLine("\tpublic partial class " + info.Name + " : " + info.Super);
TemplateStringBuilder.AppendLine("\t{");
templateStringBuilder.AppendLine("\tpublic partial class " + info.Name + " : " + info.Super);
templateStringBuilder.AppendLine("\t{");
foreach (var infoMethod in info.Methods)
{
AppendMethod(infoMethod);
AppendMethod(templateStringBuilder, infoMethod);
}

TemplateStringBuilder.AppendLine("\t}");
TemplateStringBuilder.AppendLine("}");
templateStringBuilder.AppendLine("\t}");
templateStringBuilder.AppendLine("}");

return TemplateStringBuilder.ToString();
return templateStringBuilder.ToString();
}

/// <summary>
Expand All @@ -118,23 +102,24 @@ public static string Run(AgentInfo info)
/// <remarks>
/// Appends the method declaration and body for a single method.
/// </remarks>
/// <param name="templateStringBuilder">生成代码的字符串构建器 / String builder for the generated code</param>
/// <param name="infoMethod">方法元数据 / Method metadata</param>
private static void AppendMethod(MethodInfoData infoMethod)
private static void AppendMethod(StringBuilder templateStringBuilder, MethodInfoData infoMethod)
{
TemplateStringBuilder.AppendLine("\t\t" + infoMethod.Declare);
TemplateStringBuilder.AppendLine("\t\t{");
templateStringBuilder.AppendLine("\t\t" + infoMethod.Declare);
templateStringBuilder.AppendLine("\t\t{");

if (infoMethod.Discard)
{
AppendDiscardBody(infoMethod);
AppendDiscardBody(templateStringBuilder, infoMethod);
}
else
{
AppendInvocationBody(infoMethod);
AppendInvocationBody(templateStringBuilder, infoMethod);
}

TemplateStringBuilder.AppendLine("\t\t}");
TemplateStringBuilder.AppendLine();
templateStringBuilder.AppendLine("\t\t}");
templateStringBuilder.AppendLine();
}

/// <summary>
Expand All @@ -143,20 +128,21 @@ private static void AppendMethod(MethodInfoData infoMethod)
/// <remarks>
/// Appends the method body for discard (fire-and-forget) invocations.
/// </remarks>
/// <param name="templateStringBuilder">生成代码的字符串构建器 / String builder for the generated code</param>
/// <param name="infoMethod">方法元数据 / Method metadata</param>
private static void AppendDiscardBody(MethodInfoData infoMethod)
private static void AppendDiscardBody(StringBuilder templateStringBuilder, MethodInfoData infoMethod)
{
if (infoMethod.IsThreadSafe)
{
TemplateStringBuilder.AppendLine($"\t\t\t_ = base.{infoMethod.Name}{infoMethod.Typeparams}({infoMethod.ParamString});");
templateStringBuilder.AppendLine($"\t\t\t_ = base.{infoMethod.Name}{infoMethod.Typeparams}({infoMethod.ParamString});");
}
else
{
TemplateStringBuilder.AppendLine("\t\t\tlong callChainId = GameFrameX.Core.Actors.Impl.WorkerActor.NextChainId();");
TemplateStringBuilder.AppendLine($"\t\t\t_ = base.Actor.WorkerActor.EnqueueAsync(async ()=> await base.{infoMethod.Name}{infoMethod.Typeparams}({infoMethod.ParamString}), callChainId, true{BuildTimeoutArgument(infoMethod)});");
templateStringBuilder.AppendLine("\t\t\tlong callChainId = GameFrameX.Core.Actors.Impl.WorkerActor.NextChainId();");
templateStringBuilder.AppendLine($"\t\t\t_ = base.Actor.WorkerActor.EnqueueAsync(async ()=> await base.{infoMethod.Name}{infoMethod.Typeparams}({infoMethod.ParamString}), callChainId, true{BuildTimeoutArgument(infoMethod)});");
}

TemplateStringBuilder.AppendLine($"\t\t\treturn {infoMethod.ReturnType}.CompletedTask;");
templateStringBuilder.AppendLine($"\t\t\treturn {infoMethod.ReturnType}.CompletedTask;");
}

/// <summary>
Expand All @@ -165,24 +151,25 @@ private static void AppendDiscardBody(MethodInfoData infoMethod)
/// <remarks>
/// Appends the method body for invocations that await a result.
/// </remarks>
/// <param name="templateStringBuilder">生成代码的字符串构建器 / String builder for the generated code</param>
/// <param name="infoMethod">方法元数据 / Method metadata</param>
private static void AppendInvocationBody(MethodInfoData infoMethod)
private static void AppendInvocationBody(StringBuilder templateStringBuilder, MethodInfoData infoMethod)
{
// async 方法转发底层任务必须 await(非泛型 Task/ValueTask 同样如此);是否泛型与该判定无关
var needAwait = infoMethod.IsAsync;
TemplateStringBuilder.AppendLine("\t\t\t(bool needEnqueue, long chainId)= base.Actor.WorkerActor.IsNeedEnqueue();");
TemplateStringBuilder.AppendLine("\t\t\tif (!needEnqueue)");
TemplateStringBuilder.AppendLine("\t\t\t{");
TemplateStringBuilder.AppendLine($"\t\t\t\treturn {(needAwait ? "await " : string.Empty)} base.{infoMethod.Name}{infoMethod.Typeparams}({infoMethod.ParamString});");
TemplateStringBuilder.AppendLine("\t\t\t}");
TemplateStringBuilder.AppendLine($"\t\t\treturn {(needAwait ? "await " : string.Empty)} base.Actor.WorkerActor.EnqueueAsync(()=>base.{infoMethod.Name}{infoMethod.Typeparams}({infoMethod.ParamString}), chainId, {(infoMethod.Discard ? "true" : "false")}{BuildTimeoutArgument(infoMethod)});");
templateStringBuilder.AppendLine("\t\t\t(bool needEnqueue, long chainId)= base.Actor.WorkerActor.IsNeedEnqueue();");
templateStringBuilder.AppendLine("\t\t\tif (!needEnqueue)");
templateStringBuilder.AppendLine("\t\t\t{");
templateStringBuilder.AppendLine($"\t\t\t\treturn {(needAwait ? "await " : string.Empty)} base.{infoMethod.Name}{infoMethod.Typeparams}({infoMethod.ParamString});");
templateStringBuilder.AppendLine("\t\t\t}");
templateStringBuilder.AppendLine($"\t\t\treturn {(needAwait ? "await " : string.Empty)} base.Actor.WorkerActor.EnqueueAsync(()=>base.{infoMethod.Name}{infoMethod.Typeparams}({infoMethod.ParamString}), chainId, {(infoMethod.Discard ? "true" : "false")}{BuildTimeoutArgument(infoMethod)});");
}

/// <summary>
/// 构建超时实参片段:显式配置 [TimeOut] 时发射「, 超时值」,未配置时省略实参走 EnqueueAsync 框架默认值。
/// </summary>
/// <remarks>
/// Builds the timeout argument fragment: emits ", <ms>" only when [TimeOut] was explicitly configured;
/// Builds the timeout argument fragment: emits ", &lt;ms&gt;" only when [TimeOut] was explicitly configured;
/// otherwise the argument is omitted so EnqueueAsync's framework default applies.
/// </remarks>
/// <param name="infoMethod">方法元数据 / Method metadata</param>
Expand Down
Loading
Loading