feat(framework): sdk 子游戏对接边界(IGameModule + GameContext)
GameContext(子游戏调用框架的唯一入口): - 受限 net.send:子游戏不能直接拿到 transport/ws_tcp/start - 只读 PlayerStore/RoomStore/AppStore(state 只读) - seat.toView/fromView:替代旧 ChangeToStatus - events: 子游戏自定义事件总线 IGameModule(子游戏实现,被框架调用): - route: 本游戏的 game route(框架 Router 据此分发对局包) - onEnter/onExit: 进入/离开牌桌 - onReceive(rpc, data): 接收对局包(框架已按 route 过滤) - onReconnect(deskinfo): login 含 deskinfo 时触发(平台层不解析) - serialize?(): 对局快照(断线重连用) - 平台钩子 onPlayerJoin/onPlayerLeave/onReady/onDissolve/onOffline 默认空实现 架构零耦合规则 §3 保证:子游戏只能 import sdk + core 类型, 无法触碰 platform/net 内部实现。 118/118 tests pass, typecheck exit 0。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import type { EventBus } from '../core/events.ts';
|
||||
import type { ReadonlyReactive } from '../core/reactive.ts';
|
||||
import type { ReadonlyPlayerStore } from '../platform/readonly.ts';
|
||||
import type { ReadonlyRoomStore } from '../platform/readonly.ts';
|
||||
import type { ReadonlyAppStore } from '../platform/readonly.ts';
|
||||
import type { PlayerState, RoomState, AppState } from '../platform/stores/types.ts';
|
||||
|
||||
/**
|
||||
* 受限的发包通道(架构零耦合规则 §3)。
|
||||
*
|
||||
* 子游戏只能通过这个接口发包,**不能**直接拿到 transport/ws/start 等内部能力。
|
||||
* 实现由 Router 在创建 GameContext 时注入(生产环境转发到 NetClient)。
|
||||
*/
|
||||
export interface GameNet {
|
||||
send(route: string, rpc: string, data: unknown): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 座位↔视图工具(C §3 旧 ChangeToStatus 替代)。
|
||||
*/
|
||||
export interface GameSeat {
|
||||
/** 绝对座位 → 视图位(自己=0,其余环形顺延)。 */
|
||||
toView(mySeat: number, targetSeat: number, seatCount: number): number;
|
||||
/** 视图位 → 绝对座位。 */
|
||||
fromView(mySeat: number, viewSeat: number, seatCount: number): number;
|
||||
}
|
||||
|
||||
/**
|
||||
* GameContext:子游戏调用框架能力的唯一入口。
|
||||
*
|
||||
* 只暴露只读 Store + 受限 net.send + 座位工具 + 事件总线。
|
||||
* 子游戏**不能**通过本接口反改 Store;Store 的修改由平台层负责(第二准则:单一来源)。
|
||||
*/
|
||||
export interface GameContext {
|
||||
/** 受限发包。 */
|
||||
readonly net: GameNet;
|
||||
/** 只读 PlayerStore。 */
|
||||
readonly player: ReadonlyPlayerStore & { readonly state: ReadonlyReactive<PlayerState> };
|
||||
/** 只读 RoomStore(含 deskinfo 透传引用)。 */
|
||||
readonly room: ReadonlyRoomStore & { readonly state: ReadonlyReactive<RoomState> };
|
||||
/** 只读 AppStore(连接相位 + 身份)。 */
|
||||
readonly app: ReadonlyAppStore & { readonly state: ReadonlyReactive<AppState> };
|
||||
/** 座位↔视图工具。 */
|
||||
readonly seat: GameSeat;
|
||||
/** 事件总线(子游戏可发自定义事件供自己订阅,框架不消费)。 */
|
||||
readonly events: EventBus<Record<string, unknown[]>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* IGameModule:子游戏实现,被框架调用(框架 spec §4)。
|
||||
*
|
||||
* 子游戏实现这个接口并注册自己;框架 Router 据 route 分发对局包。
|
||||
* 子游戏通过 GameContext 调用框架能力(不可 import platform/net 内部)。
|
||||
*/
|
||||
export interface IGameModule {
|
||||
/** 本游戏的 game route(与 protocol/routes.ts 的 Route 同名)。 */
|
||||
readonly route: string;
|
||||
|
||||
/** 进入牌桌场景,框架传入 GameContext。 */
|
||||
onEnter(ctx: GameContext): void;
|
||||
|
||||
/** 离开牌桌场景。 */
|
||||
onExit(): void;
|
||||
|
||||
/**
|
||||
* 接收对局包(route = 本游戏 game route 的包)。
|
||||
* 框架 router 已按 route 过滤;这里只按 rpc 分发。
|
||||
*/
|
||||
onReceive(rpc: string, data: unknown): void;
|
||||
|
||||
/**
|
||||
* 登录回包带 deskinfo 时触发(C 规范 §5 边界判据 2:deskinfo 平台层不解析)。
|
||||
* 子游戏负责反序列化对局态。
|
||||
*/
|
||||
onReconnect(deskinfo: unknown): void;
|
||||
|
||||
/**
|
||||
* 可选:返回对局快照。框架在断线重连时收集 → 走 server player_login 响应 deskinfo。
|
||||
* 若不实现则重连后从服务器拉取(默认行为)。
|
||||
*/
|
||||
serialize?(): unknown;
|
||||
|
||||
// ---- 平台事件钩子(全部可选,默认空实现)----
|
||||
|
||||
/** 玩家加入房间(seat = 对方座位)。 */
|
||||
onPlayerJoin?(seat: number): void;
|
||||
/** 玩家离开房间。 */
|
||||
onPlayerLeave?(seat: number): void;
|
||||
/** 玩家准备。 */
|
||||
onReady?(seat: number): void;
|
||||
/** 房间解散。 */
|
||||
onDissolve?(): void;
|
||||
/** 玩家离线/上线(在线由 onPlayerJoin 重复触发,离线由本钩子)。 */
|
||||
onOffline?(seat: number): void;
|
||||
}
|
||||
Reference in New Issue
Block a user