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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,18 @@

### Breaking

* Capability 能力注入机制重设计(C294,GFX-998,破坏性一次性切换,能力未上线无兼容层):
- 模块契约改为三段生命周期:`ICapabilityModule` 新增 `ActivateAsync(CapabilityActivationContext, CancellationToken)` / `DeactivateAsync(CancellationToken)`;激活按拓扑序执行、中途失败逆序回滚,停止为停调度 → 逆序 Deactivate → 机制自动撤销全部桥。四桥发布、榜单预建、撮合通知订阅、旧好友数据迁移迁入所属能力模块。
- 桥机制化:泛型 `CapabilityBridge<T>.TryAcquire` / `CapabilityBridgeLease<T>`(撤销 / 替换后抛 `CapabilityBridgeUnavailableException`,族层映射 8002),发布只经 `CapabilityActivationContext.Bridges.Publish<T>`,同契约重复发布 fail-fast;删除 `Online.Abstractions/Bridges/` 四对 holder/lease 与 Launcher 四个 `*BridgeWiring`。
- 服务注册改为描述符表(类型键 + 命名键 + 懒工厂 + 来源),替换实现走显式 `CapabilityCompositionOptions.Override<T>`;删除 `TryGetService` / `AddService(旧)` / `UseService` 与全部探测回退,可选依赖改 `GetOptionalService<T>`(仅 manifest 声明的可选能力缺席时返回 null),扩展点枚举 `GetServices<T>` 仅 Activate 期可用。
- 族组合骨架:`CapabilityComposition.Compose(ICapabilityFamily, CapabilityCompositionOptions)` 取代 `AddOnlineCapabilities` / `OnlineCapabilityComposition(Options)` / `OnlineAdminApiComposition`;Online 族实现 `OnlineCapabilityFamily`(族基建含管理面适配器);admin 组装上移 `Hosting.AdminApi`,同名 action 直接失败,声明 / 实注册双向对账;新增元端点 `list_capability_services` / `list_capability_settings`,`list_capabilities` 增依赖状态与缺失功能。manifest 补声明 `query_leaderboard`(online-competition)、`query_match_settlement`(online-match),admin action 共 35 个。
- 依赖分必选 / 可选,任何 `Register` 前统一检查:必选缺失或成环打 Error 并抛 `CapabilityAssemblyException`(一次报全、环路列完整路径),可选缺失打 Info「缺失功能「…」,不影响 … 其余功能运行」;同层按能力 Id 序数定序。依赖边:match → assets / matchmaking、competition → assets / match、operations → assets / social / match / matchmaking 由必选改为可选。
- 参数:默认值只在能力包 Options 根类型定义(新增 `OnlineFriendOptions`,删除 `OnlineAssetsOptions` 与全部 `?? new XxxOptions()` 兜底),装配按「包内默认 → `Online.Settings.<能力Id>` → 代码 `Configure<T>`」逐字段覆盖后 `Validate`,未知能力 Id / 未知字段 / 类型不符 fail-fast。回调:宿主扩展点 `RequireHostService<T>`、Hotfix 钩子 `DeclareHook<T>` + `CapabilityHook<T>.Attach`(逐调用读取当前实现,聊天审核钩子已接线)。
- 事件:删除 `CapabilityEventJob` / `CapabilityEventNotification` / `RaiseEvent`,业务事件统一机制层 `CapabilityEventBus`(Register 期订阅、Activate 前拒绝发布),topic 重整为 `<族>.<域>.<事件>`。调度器支持 `ISchedulerClock` 注入、tick job 逐个隔离并记录日志与指标,停止后再 `Start` 抛 `InvalidOperationException`。
- 启动:`AppStartUpGameServerBase` 模板新增 `ComposeCapabilitiesAsync`(热更装载之后、网络监听之前,同族进程内单例);`AppStartUpGame` 删除三个 Online 编排方法与两个字段。
- 配置:`AppSetting.IsEnableOnline` / `IsEnableOnlineAdmin` 删除,并入 "Online" 段 `EnableRuntime` / `EnableAdminApi`,新增 `EnableRoles`(缺省 `["Game"]`)与 `Settings`;删除键 `EnableMatchGameBridge` / `FriendBridgeSeedPlayerIds` / `MatchBridgeLeaderboardId`(迁入 `Settings.online-competition.LeaderboardProjection`)。迁移写法:CLI `--IsEnableOnline` / `--IsEnableOnlineAdmin` 改为 `Configs/app_config.json` 对应 Role 段 `"Online": { "EnableRuntime": true, "EnableAdminApi": true }`。
- 护栏:GFX0018 改为按泛型符号判定(替代名字清单),覆盖 `Bridges.Publish<>` / `TryAcquire` / `Attach` / `Override<>` / `Configure<>` / Register 期专用 API;GFX0017 机制白名单与 `IsMechanism` 收敛为同一集合(`Hosting.Core` 属机制包);`IOnlineChatModerationHook` 及消息 / 裁决类型上移 `Online.Abstractions`,Hotfix 不再引用 Social 实现包。退役 22 个 `Launcher.Online*` 本地化键。

* 全解决方案 Task/ValueTask 成员方法统一 Async 后缀(C290,204 处、15 工程,零行为变更):返回 `Task` / `ValueTask` 的接口与类成员方法(任意可见性,含 `Task<T>` / `ValueTask<T>` 任意泛型深度与元组形态)一律 `Async` 后缀,接口 / 虚链整链一致改名——`IActor` / `IComponentAgent` / `IEventListener` / `ITimerHandler` / `IMessageHandler` / `IHotfixBridge` / `IDatabaseService` / `IHttpHandler` / `IPlayerSessionManager` / `IWorkerActor` 及其全部实现链(如 `SaveAll→SaveAllAsync`、`TimerSave→TimerSaveAsync`、`InnerAction→InnerActionAsync`、`Enqueue→EnqueueAsync`、`IDatabaseService.Open/Close→OpenAsync/CloseAsync`、`GameDb.Init→InitAsync`、`UpdateSession→UpdateSessionAsync`、`HttpHandler.HandleRequest→HandleRequestAsync`、`IHttpHandler.Action/ActionMessageObject→ActionAsync/ActionMessageObjectAsync`,权威映射 = gfx-doc archived C290 rename-map.md)。撞名配对按 `InitCore`/`CloseCoreAsync` 先例以 `CoreAsync` 消解:`QuartzTimer` 私有 `StopAsync→StopCoreAsync` 让位公开 `Stop→StopAsync`、`AppStartUpByServer.StartServer→StartServerCoreAsync`、Base Handler 既有 `InnerActionAsync→InnerActionCoreAsync` 让位公开编排入口。豁免:外部契约实现(Quartz `IJob.Execute` / TouchSocket `MiddlewareBase.RegisterSession`/`UnRegisterSession`)、名已含 `Async` 记号、局部函数、测试方法、`GameFrameX.ProtoBuf.Net`(fork,用户裁定不处理)。对外破坏性(公开 API 批量更名):受影响 API 全部在解决方案内(含 Tests)闭环消费、无外部消费者;XML doc / cref、本地化值内嵌方法名(键名不动,en + zh-CN 双 resx)、WrapperGenerator 发射模板与跳过表、测试反射字符串随迁;验证 = 构建 0 错 0 新增警告 + 全量测试与基线零漂移 + 语义级复扫生产代码归零 + 605 hunk diff 逐 hunk 纯改名审计(0 控制流丢失)。

* 组件代理事件派发扩展下沉 Core 并退役服务器段广播(C289,GFX-991):`EventDispatcherExtensions`(`agent.Dispatch(eventArgs)`)自 `GameFrameX.Hotfix/Common/Events/` `git mv` 至 `GameFrameX.Core/Events/`(命名空间 `GameFrameX.Hotfix.Common.Events → GameFrameX.Core.Events`),删除生产零调用的服务器段广播分支(原 `IServerScopeEvent` 载荷经 `ServerComponentAgent.OnlineRoleForeach` 遍历在线玩家派发),自处理与静态 `EventDispatcher` 共用同一监听器循环 `DispatchToActorAsync`(形参 `Actor → IActor`、`private → internal`),日志键统一 `Core.EventDispatcher.*`(退役 `Hotfix.Event.SelfHandle*`)。对外破坏性(公开 API 删除):`IServerScopeEvent`、`ServerComponent.OnlineSet`、`ServerComponentAgent.AddOnlinePlayer` / `RemoveOnlinePlayer` / `IsOnline` / `OnlineRoleForeach`、扩展命名空间变化;迁移写法——在线判定改 `PlayerSessionManager.Instance.GetByPlayerId(playerId) != null`(在线人数 / 列表 `Count()` / `GetPageList()`,跨服路由 `TryGetPlayerRoute`),扩展引用改 `using GameFrameX.Core.Events;`(组件代理侧调用写法 `this.Dispatch(...)` 零变化),`IServerScopeEvent` 无替代(组件代理侧不再广播,需要遍历在线玩家时以会话层为在线事实源自行派发);`ServiceOnlineEventArgs` / `ServiceOfflineEventArgs` 去掉该接口实现后静态派发行为不变。
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// ==========================================================================================
// GameFrameX 组织及其衍生项目的版权、商标、专利及其他相关权利
// GameFrameX organization and its derivative projects' copyrights, trademarks, patents and related rights
// 均受中华人民共和国及相关国际法律法规保护。
// are protected by the laws of the People's Republic of China and relevant international regulations.
// 使用本项目须严格遵守相应法律法规与开源许可证之规定。
// Usage of this project must strictly comply with applicable laws, regulations, and open-source licenses.
// 本项目采用 Apache License 2.0 单协议分发,
// This project is licensed solely under the Apache License 2.0,
// 完整许可证文本请参见源代码根目录下的 LICENSE 文件。
// please refer to the LICENSE file in the root directory of the source code for the full license text.
// 禁止利用本项目实施任何危害国家安全、破坏社会秩序、
// It is prohibited to use this project to engage in any activities that endanger national security, disrupt social order,
// 侵犯他人合法权益等法律法规所禁止的行为!
// or infringe upon the legitimate rights and interests of others, as prohibited by laws and regulations!
// 因基于本项目二次开发所产生的一切法律纠纷与责任,
// Any legal disputes or liabilities arising from secondary development based on this project
// 本组织与贡献者概不承担。
// shall be borne solely by the developer; the project organization and contributors assume no responsibility.
// GitHub 仓库:https://github.com/GameFrameX
// GitHub Repository: https://github.com/GameFrameX
// Gitee 仓库:https://gitee.com/GameFrameX
// Gitee Repository: https://gitee.com/GameFrameX
// CNB 仓库:https://cnb.cool/GameFrameX
// CNB Repository: https://cnb.cool/GameFrameX
// 官方文档:https://gameframex.doc.alianblank.com/
// Official Documentation: https://gameframex.doc.alianblank.com/
// ==========================================================================================

namespace GameFrameX.Capability.Hosting.Abstractions;

/// <summary>
/// 能力激活上下文(宿主启动期对单个能力模块开放的激活面:只读解析 + 桥发布)。
/// <para>
/// 维护约束:全部模块已完成 Register,本上下文的 <see cref="Services"/> 可枚举依赖关系之外的扩展点
///(<c>GetServices</c> 此时才可用);单实例解析仍做来源检查(枚举之外不允许偷用未声明依赖的单实现);
/// 桥的发布者身份 = 当前模块 manifest Id,由机制注入;撤销由宿主停止期自动完成,模块无撤销入口。
/// </para>
/// </summary>
public sealed class CapabilityActivationContext
{
/// <summary>
/// 只读解析面(按本能力限定来源检查;<c>GetServices</c> 在本上下文可用)。
/// </summary>
public CapabilityServiceResolver Services { get; }

/// <summary>
/// 桥发布面(发布者身份 = 本能力)。
/// </summary>
public CapabilityBridgePublisher Bridges { get; }

/// <summary>
/// 初始化 <see cref="CapabilityActivationContext"/>(仅机制构造)。
/// </summary>
/// <param name="services">按本能力限定的解析器。</param>
/// <param name="bridges">桥发布面。</param>
internal CapabilityActivationContext(CapabilityServiceResolver services, CapabilityBridgePublisher bridges)
{
Services = services ?? throw new ArgumentNullException(nameof(services));
Bridges = bridges ?? throw new ArgumentNullException(nameof(bridges));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,37 @@
/// <para>
/// 维护约束:<see cref="EnabledIds"/> 缺省(null)= 装配全部已发现能力;
/// <see cref="DisabledIds"/> 恒为排除语义且优先于清单段;过滤器引用未发现的能力 Id 时装配 fail-fast。
/// 构造期规整(Trim、丢弃空白条目):规整后为空的清单段视为 null、排除表视为空——保证「空过滤器 = 全装配」红线
/// 在机制层成立,配置写成 <c>"Capabilities": []</c> 或含空白条目时不会静默装配零能力,也不会把空白条目误报为未发现能力。
/// </para>
/// </summary>
public sealed class CapabilityAssemblyFilter
{
/// <summary>
/// 初始化 <see cref="CapabilityAssemblyFilter"/>。
/// </summary>
/// <param name="enabledIds">能力清单段(null = 全部)。</param>
/// <param name="disabledIds">配置排除表。</param>
/// <param name="enabledIds">能力清单段(null、空或仅含空白条目 = 全部)。</param>
/// <param name="disabledIds">配置排除表(null、空或仅含空白条目 = 无排除)。</param>
public CapabilityAssemblyFilter(IReadOnlyList<string> enabledIds = null, IReadOnlyList<string> disabledIds = null)
{
EnabledIds = enabledIds;
DisabledIds = disabledIds ?? Array.Empty<string>();
EnabledIds = NormalizeIds(enabledIds);
DisabledIds = NormalizeIds(disabledIds) ?? Array.Empty<string>();
}

/// <summary>
/// 规整能力 Id 列表(Trim、丢弃空白条目);无有效条目时返回 null(该维度不过滤)。
/// </summary>
/// <param name="source">原始列表(可为 null)。</param>
/// <returns>规整后的数组;无有效条目时为 null。</returns>
private static string[] NormalizeIds(IReadOnlyList<string> source)
{
if (source == null)
{
return null;

Check warning on line 64 in Capability/GameFrameX.Capability.Hosting.Abstractions/CapabilityAssemblyFilter.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Return an empty collection instead of null.

See more on https://sonarcloud.io/project/issues?id=GameFrameX.Server.Source&issues=AaFqRAlQH84yoHCiuL1q&open=AaFqRAlQH84yoHCiuL1q&pullRequest=623
}

var ids = source.Where(id => !string.IsNullOrWhiteSpace(id)).Select(id => id.Trim()).ToArray();
return ids.Length == 0 ? null : ids;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// ==========================================================================================
// GameFrameX 组织及其衍生项目的版权、商标、专利及其他相关权利
// GameFrameX organization and its derivative projects' copyrights, trademarks, patents and related rights
// 均受中华人民共和国及相关国际法律法规保护。
// are protected by the laws of the People's Republic of China and relevant international regulations.
// 使用本项目须严格遵守相应法律法规与开源许可证之规定。
// Usage of this project must strictly comply with applicable laws, regulations, and open-source licenses.
// 本项目采用 Apache License 2.0 单协议分发,
// This project is licensed solely under the Apache License 2.0,
// 完整许可证文本请参见源代码根目录下的 LICENSE 文件。
// please refer to the LICENSE file in the root directory of the source code for the full license text.
// 禁止利用本项目实施任何危害国家安全、破坏社会秩序、
// It is prohibited to use this project to engage in any activities that endanger national security, disrupt social order,
// 侵犯他人合法权益等法律法规所禁止的行为!
// or infringe upon the legitimate rights and interests of others, as prohibited by laws and regulations!
// 因基于本项目二次开发所产生的一切法律纠纷与责任,
// Any legal disputes or liabilities arising from secondary development based on this project
// 本组织与贡献者概不承担。
// shall be borne solely by the developer; the project organization and contributors assume no responsibility.
// GitHub 仓库:https://github.com/GameFrameX
// GitHub Repository: https://github.com/GameFrameX
// Gitee 仓库:https://gitee.com/GameFrameX
// Gitee Repository: https://gitee.com/GameFrameX
// CNB 仓库:https://cnb.cool/GameFrameX
// CNB Repository: https://cnb.cool/GameFrameX
// 官方文档:https://gameframex.doc.alianblank.com/
// Official Documentation: https://gameframex.doc.alianblank.com/
// ==========================================================================================

namespace GameFrameX.Capability.Hosting.Abstractions;

/// <summary>
/// 能力桥消费入口(按契约类型的进程内定位;发布侧只经
/// <see cref="CapabilityActivationContext"/> 的 <c>Bridges.Publish&lt;TContract&gt;</c>,撤销由宿主停止期自动完成)。
/// <para>
/// 维护约束(桥显式失败红线):消费方逐调用复查活跃位;发布者缺席 <see cref="TryAcquire"/> 返回 false、
/// 已撤销 / 已替换抛 <see cref="CapabilityBridgeUnavailableException"/>(族层映射为 8002)——不回旧数据、不降级、
/// 各域不得自写缺席日志。本入口只在 Hotfix / Apps 消费侧合法(GFX0018 符号检查)。
/// </para>
/// </summary>
/// <typeparam name="TContract">契约类型(每域一个契约接口)。</typeparam>
public static class CapabilityBridge<TContract> where TContract : class
{
/// <summary>
/// 契约槽位(与 <see cref="CapabilityHook{TContract}"/> 各自独立、共用同一套槽位实现)。
/// </summary>
private static readonly CapabilityContractSlot<TContract> Slot = new CapabilityContractSlot<TContract>();

/// <summary>
/// 尝试获取契约租约。
/// </summary>
/// <param name="lease">输出:契约租约(发布者缺席为 null)。</param>
/// <returns>发布者在场返回 true;缺席返回 false(消费方按依赖不可用显式失败处理)。</returns>
public static bool TryAcquire(out CapabilityBridgeLease<TContract> lease)
{
return Slot.TryAcquire(out lease);
}

/// <summary>
/// 发布契约实例(仅激活上下文的桥发布面调用;发布者身份 = 当前模块 Id;同一契约重复发布 fail-fast)。
/// </summary>
/// <param name="contract">契约实现。</param>
/// <param name="publisherId">发布者能力 Id。</param>
/// <returns>撤销凭据(宿主停止期统一撤销)。</returns>
internal static IDisposable Publish(TContract contract, string publisherId)
{
return Slot.Publish(contract, publisherId);
}
}
Loading
Loading