@esengine/server 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,338 +5,6 @@ import { rpc } from '@esengine/rpc';
5
5
  import * as fs from 'fs';
6
6
  import { pathToFileURL } from 'url';
7
7
 
8
- // src/room/Player.ts
9
- var _Player = class _Player {
10
- constructor(options) {
11
- __publicField(this, "id");
12
- __publicField(this, "roomId");
13
- __publicField(this, "data");
14
- __publicField(this, "_conn");
15
- __publicField(this, "_sendFn");
16
- __publicField(this, "_leaveFn");
17
- this.id = options.id;
18
- this.roomId = options.roomId;
19
- this._conn = options.conn;
20
- this._sendFn = options.sendFn;
21
- this._leaveFn = options.leaveFn;
22
- this.data = options.initialData ?? {};
23
- }
24
- /**
25
- * @zh 获取底层连接
26
- * @en Get underlying connection
27
- */
28
- get connection() {
29
- return this._conn;
30
- }
31
- /**
32
- * @zh 发送消息给玩家
33
- * @en Send message to player
34
- */
35
- send(type, data) {
36
- this._sendFn(this._conn, type, data);
37
- }
38
- /**
39
- * @zh 让玩家离开房间
40
- * @en Make player leave the room
41
- */
42
- leave(reason) {
43
- this._leaveFn(this, reason);
44
- }
45
- };
46
- __name(_Player, "Player");
47
- var Player = _Player;
48
-
49
- // src/room/Room.ts
50
- var MESSAGE_HANDLERS = /* @__PURE__ */ Symbol("messageHandlers");
51
- var _Room = class _Room {
52
- constructor() {
53
- // ========================================================================
54
- // 配置 | Configuration
55
- // ========================================================================
56
- /**
57
- * @zh 最大玩家数
58
- * @en Maximum players
59
- */
60
- __publicField(this, "maxPlayers", 16);
61
- /**
62
- * @zh Tick 速率(每秒),0 = 不自动 tick
63
- * @en Tick rate (per second), 0 = no auto tick
64
- */
65
- __publicField(this, "tickRate", 0);
66
- /**
67
- * @zh 空房间自动销毁
68
- * @en Auto dispose when empty
69
- */
70
- __publicField(this, "autoDispose", true);
71
- // ========================================================================
72
- // 状态 | State
73
- // ========================================================================
74
- /**
75
- * @zh 房间状态
76
- * @en Room state
77
- */
78
- __publicField(this, "state", {});
79
- // ========================================================================
80
- // 内部属性 | Internal properties
81
- // ========================================================================
82
- __publicField(this, "_id", "");
83
- __publicField(this, "_players", /* @__PURE__ */ new Map());
84
- __publicField(this, "_locked", false);
85
- __publicField(this, "_disposed", false);
86
- __publicField(this, "_tickInterval", null);
87
- __publicField(this, "_lastTickTime", 0);
88
- __publicField(this, "_broadcastFn", null);
89
- __publicField(this, "_sendFn", null);
90
- __publicField(this, "_disposeFn", null);
91
- }
92
- // ========================================================================
93
- // 只读属性 | Readonly properties
94
- // ========================================================================
95
- /**
96
- * @zh 房间 ID
97
- * @en Room ID
98
- */
99
- get id() {
100
- return this._id;
101
- }
102
- /**
103
- * @zh 所有玩家
104
- * @en All players
105
- */
106
- get players() {
107
- return Array.from(this._players.values());
108
- }
109
- /**
110
- * @zh 玩家数量
111
- * @en Player count
112
- */
113
- get playerCount() {
114
- return this._players.size;
115
- }
116
- /**
117
- * @zh 是否已满
118
- * @en Is full
119
- */
120
- get isFull() {
121
- return this._players.size >= this.maxPlayers;
122
- }
123
- /**
124
- * @zh 是否已锁定
125
- * @en Is locked
126
- */
127
- get isLocked() {
128
- return this._locked;
129
- }
130
- /**
131
- * @zh 是否已销毁
132
- * @en Is disposed
133
- */
134
- get isDisposed() {
135
- return this._disposed;
136
- }
137
- // ========================================================================
138
- // 生命周期 | Lifecycle
139
- // ========================================================================
140
- /**
141
- * @zh 房间创建时调用
142
- * @en Called when room is created
143
- */
144
- onCreate(options) {
145
- }
146
- /**
147
- * @zh 玩家加入时调用
148
- * @en Called when player joins
149
- */
150
- onJoin(player) {
151
- }
152
- /**
153
- * @zh 玩家离开时调用
154
- * @en Called when player leaves
155
- */
156
- onLeave(player, reason) {
157
- }
158
- /**
159
- * @zh 游戏循环
160
- * @en Game tick
161
- */
162
- onTick(dt) {
163
- }
164
- /**
165
- * @zh 房间销毁时调用
166
- * @en Called when room is disposed
167
- */
168
- onDispose() {
169
- }
170
- // ========================================================================
171
- // 公共方法 | Public methods
172
- // ========================================================================
173
- /**
174
- * @zh 广播消息给所有玩家
175
- * @en Broadcast message to all players
176
- */
177
- broadcast(type, data) {
178
- for (const player of this._players.values()) {
179
- player.send(type, data);
180
- }
181
- }
182
- /**
183
- * @zh 广播消息给除指定玩家外的所有玩家
184
- * @en Broadcast message to all players except one
185
- */
186
- broadcastExcept(except, type, data) {
187
- for (const player of this._players.values()) {
188
- if (player.id !== except.id) {
189
- player.send(type, data);
190
- }
191
- }
192
- }
193
- /**
194
- * @zh 获取玩家
195
- * @en Get player by id
196
- */
197
- getPlayer(id) {
198
- return this._players.get(id);
199
- }
200
- /**
201
- * @zh 踢出玩家
202
- * @en Kick player
203
- */
204
- kick(player, reason) {
205
- player.leave(reason ?? "kicked");
206
- }
207
- /**
208
- * @zh 锁定房间
209
- * @en Lock room
210
- */
211
- lock() {
212
- this._locked = true;
213
- }
214
- /**
215
- * @zh 解锁房间
216
- * @en Unlock room
217
- */
218
- unlock() {
219
- this._locked = false;
220
- }
221
- /**
222
- * @zh 手动销毁房间
223
- * @en Manually dispose room
224
- */
225
- dispose() {
226
- if (this._disposed) return;
227
- this._disposed = true;
228
- this._stopTick();
229
- for (const player of this._players.values()) {
230
- player.leave("room_disposed");
231
- }
232
- this._players.clear();
233
- this.onDispose();
234
- this._disposeFn?.();
235
- }
236
- // ========================================================================
237
- // 内部方法 | Internal methods
238
- // ========================================================================
239
- /**
240
- * @internal
241
- */
242
- _init(options) {
243
- this._id = options.id;
244
- this._sendFn = options.sendFn;
245
- this._broadcastFn = options.broadcastFn;
246
- this._disposeFn = options.disposeFn;
247
- }
248
- /**
249
- * @internal
250
- */
251
- async _create(options) {
252
- await this.onCreate(options);
253
- this._startTick();
254
- }
255
- /**
256
- * @internal
257
- */
258
- async _addPlayer(id, conn) {
259
- if (this._locked || this.isFull || this._disposed) {
260
- return null;
261
- }
262
- const player = new Player({
263
- id,
264
- roomId: this._id,
265
- conn,
266
- sendFn: this._sendFn,
267
- leaveFn: /* @__PURE__ */ __name((p, reason) => this._removePlayer(p.id, reason), "leaveFn")
268
- });
269
- this._players.set(id, player);
270
- await this.onJoin(player);
271
- return player;
272
- }
273
- /**
274
- * @internal
275
- */
276
- async _removePlayer(id, reason) {
277
- const player = this._players.get(id);
278
- if (!player) return;
279
- this._players.delete(id);
280
- await this.onLeave(player, reason);
281
- if (this.autoDispose && this._players.size === 0) {
282
- this.dispose();
283
- }
284
- }
285
- /**
286
- * @internal
287
- */
288
- _handleMessage(type, data, playerId) {
289
- const player = this._players.get(playerId);
290
- if (!player) return;
291
- const handlers = this.constructor[MESSAGE_HANDLERS];
292
- if (handlers) {
293
- for (const handler of handlers) {
294
- if (handler.type === type) {
295
- const method = this[handler.method];
296
- if (typeof method === "function") {
297
- method.call(this, data, player);
298
- }
299
- }
300
- }
301
- }
302
- }
303
- _startTick() {
304
- if (this.tickRate <= 0) return;
305
- this._lastTickTime = performance.now();
306
- this._tickInterval = setInterval(() => {
307
- const now = performance.now();
308
- const dt = (now - this._lastTickTime) / 1e3;
309
- this._lastTickTime = now;
310
- this.onTick(dt);
311
- }, 1e3 / this.tickRate);
312
- }
313
- _stopTick() {
314
- if (this._tickInterval) {
315
- clearInterval(this._tickInterval);
316
- this._tickInterval = null;
317
- }
318
- }
319
- };
320
- __name(_Room, "Room");
321
- var Room = _Room;
322
- function registerMessageHandler(target, type, method) {
323
- if (!target[MESSAGE_HANDLERS]) {
324
- target[MESSAGE_HANDLERS] = [];
325
- }
326
- target[MESSAGE_HANDLERS].push({
327
- type,
328
- method
329
- });
330
- }
331
- __name(registerMessageHandler, "registerMessageHandler");
332
-
333
- // src/room/decorators.ts
334
- function onMessage(type) {
335
- return function(target, propertyKey, _descriptor) {
336
- registerMessageHandler(target.constructor, type, propertyKey);
337
- };
338
- }
339
- __name(onMessage, "onMessage");
340
8
  function fileNameToHandlerName(fileName) {
341
9
  const baseName = fileName.replace(/\.(ts|js|mts|mjs)$/, "");
342
10
  return baseName.split(/[-_]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
@@ -739,6 +407,6 @@ async function createServer(config = {}) {
739
407
  }
740
408
  __name(createServer, "createServer");
741
409
 
742
- export { Player, Room, createServer, onMessage };
743
- //# sourceMappingURL=chunk-7C6JZO4O.js.map
744
- //# sourceMappingURL=chunk-7C6JZO4O.js.map
410
+ export { createServer };
411
+ //# sourceMappingURL=chunk-QWEIP5QH.js.map
412
+ //# sourceMappingURL=chunk-QWEIP5QH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/router/loader.ts","../src/room/RoomManager.ts","../src/core/server.ts"],"names":["fileNameToHandlerName","fileName","baseName","replace","split","map","part","charAt","toUpperCase","slice","join","scanDirectory","dir","existsSync","files","entries","readdirSync","withFileTypes","entry","isFile","test","name","startsWith","push","loadApiHandlers","apiDir","handlers","filePath","fileUrl","pathToFileURL","href","module","definition","default","handler","basename","path","err","console","warn","loadMsgHandlers","msgDir","RoomManager","sendFn","_definitions","Map","_rooms","_playerToRoom","_nextRoomId","_sendFn","define","roomClass","set","create","options","def","get","roomId","_generateRoomId","room","_init","id","broadcastFn","type","data","player","players","send","disposeFn","delete","_create","log","joinOrCreate","playerId","conn","_findAvailableRoom","_addPlayer","joinById","leave","reason","_removePlayer","handleMessage","_handleMessage","getRoom","getPlayerRoom","undefined","getRooms","Array","from","values","getRoomsByType","filter","isFull","isLocked","isDisposed","DEFAULT_CONFIG","port","tickRate","createServer","config","opts","cwd","process","apiHandlers","resolve","msgHandlers","length","apiDefs","JoinRoom","rpc","api","LeaveRoom","msgDefs","RoomMessage","msg","protocol","currentTick","tickInterval","rpcServer","roomManager","apiMap","msgMap","gameServer","connections","tick","rooms","start","apiHandlersObj","input","roomType","result","Error","_input","success","Object","ctx","server","msgHandlersObj","payload","serve","createConnData","onStart","p","onConnect","onDisconnect","setInterval","stop","clearInterval","broadcast"],"mappings":"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","file":"chunk-QWEIP5QH.js","sourcesContent":["/**\n * @zh 文件路由加载器\n * @en File-based router loader\n */\n\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport type { ApiDefinition, MsgDefinition, LoadedApiHandler, LoadedMsgHandler } from '../types/index.js'\n\n/**\n * @zh 将文件名转换为 API/消息名称\n * @en Convert filename to API/message name\n *\n * @example\n * 'join.ts' -> 'Join'\n * 'spawn-agent.ts' -> 'SpawnAgent'\n * 'save_blueprint.ts' -> 'SaveBlueprint'\n */\nfunction fileNameToHandlerName(fileName: string): string {\n const baseName = fileName.replace(/\\.(ts|js|mts|mjs)$/, '')\n\n return baseName\n .split(/[-_]/)\n .map(part => part.charAt(0).toUpperCase() + part.slice(1))\n .join('')\n}\n\n/**\n * @zh 扫描目录获取所有处理器文件\n * @en Scan directory for all handler files\n */\nfunction scanDirectory(dir: string): string[] {\n if (!fs.existsSync(dir)) {\n return []\n }\n\n const files: string[] = []\n const entries = fs.readdirSync(dir, { withFileTypes: true })\n\n for (const entry of entries) {\n if (entry.isFile() && /\\.(ts|js|mts|mjs)$/.test(entry.name)) {\n // 跳过 index 和下划线开头的文件\n if (entry.name.startsWith('_') || entry.name.startsWith('index.')) {\n continue\n }\n files.push(path.join(dir, entry.name))\n }\n }\n\n return files\n}\n\n/**\n * @zh 加载 API 处理器\n * @en Load API handlers\n */\nexport async function loadApiHandlers(apiDir: string): Promise<LoadedApiHandler[]> {\n const files = scanDirectory(apiDir)\n const handlers: LoadedApiHandler[] = []\n\n for (const filePath of files) {\n try {\n const fileUrl = pathToFileURL(filePath).href\n const module = await import(fileUrl)\n const definition = module.default as ApiDefinition<unknown, unknown, unknown>\n\n if (definition && typeof definition.handler === 'function') {\n const name = fileNameToHandlerName(path.basename(filePath))\n handlers.push({\n name,\n path: filePath,\n definition,\n })\n }\n } catch (err) {\n console.warn(`[Server] Failed to load API handler: ${filePath}`, err)\n }\n }\n\n return handlers\n}\n\n/**\n * @zh 加载消息处理器\n * @en Load message handlers\n */\nexport async function loadMsgHandlers(msgDir: string): Promise<LoadedMsgHandler[]> {\n const files = scanDirectory(msgDir)\n const handlers: LoadedMsgHandler[] = []\n\n for (const filePath of files) {\n try {\n const fileUrl = pathToFileURL(filePath).href\n const module = await import(fileUrl)\n const definition = module.default as MsgDefinition<unknown, unknown>\n\n if (definition && typeof definition.handler === 'function') {\n const name = fileNameToHandlerName(path.basename(filePath))\n handlers.push({\n name,\n path: filePath,\n definition,\n })\n }\n } catch (err) {\n console.warn(`[Server] Failed to load msg handler: ${filePath}`, err)\n }\n }\n\n return handlers\n}\n","/**\n * @zh 房间管理器\n * @en Room manager\n */\n\nimport { Room, type RoomOptions } from './Room.js'\nimport type { Player } from './Player.js'\n\n/**\n * @zh 房间类型\n * @en Room class type\n */\nexport type RoomClass<T extends Room = Room> = new () => T\n\n/**\n * @zh 房间定义\n * @en Room definition\n */\ninterface RoomDefinition {\n roomClass: RoomClass\n}\n\n/**\n * @zh 房间管理器\n * @en Room manager\n */\nexport class RoomManager {\n private _definitions: Map<string, RoomDefinition> = new Map()\n private _rooms: Map<string, Room> = new Map()\n private _playerToRoom: Map<string, string> = new Map()\n private _nextRoomId = 1\n\n private _sendFn: (conn: any, type: string, data: unknown) => void\n\n constructor(sendFn: (conn: any, type: string, data: unknown) => void) {\n this._sendFn = sendFn\n }\n\n /**\n * @zh 注册房间类型\n * @en Define room type\n */\n define<T extends Room>(name: string, roomClass: RoomClass<T>): void {\n this._definitions.set(name, { roomClass })\n }\n\n /**\n * @zh 创建房间\n * @en Create room\n */\n async create(name: string, options?: RoomOptions): Promise<Room | null> {\n const def = this._definitions.get(name)\n if (!def) {\n console.warn(`[RoomManager] Room type not found: ${name}`)\n return null\n }\n\n const roomId = this._generateRoomId()\n const room = new def.roomClass()\n\n room._init({\n id: roomId,\n sendFn: this._sendFn,\n broadcastFn: (type, data) => {\n for (const player of room.players) {\n player.send(type, data)\n }\n },\n disposeFn: () => {\n this._rooms.delete(roomId)\n },\n })\n\n this._rooms.set(roomId, room)\n await room._create(options)\n\n console.log(`[Room] Created: ${name} (${roomId})`)\n return room\n }\n\n /**\n * @zh 加入或创建房间\n * @en Join or create room\n */\n async joinOrCreate(\n name: string,\n playerId: string,\n conn: any,\n options?: RoomOptions\n ): Promise<{ room: Room; player: Player } | null> {\n // 查找可加入的房间\n let room = this._findAvailableRoom(name)\n\n // 没有则创建\n if (!room) {\n room = await this.create(name, options)\n if (!room) return null\n }\n\n // 加入房间\n const player = await room._addPlayer(playerId, conn)\n if (!player) return null\n\n this._playerToRoom.set(playerId, room.id)\n\n console.log(`[Room] Player ${playerId} joined ${room.id}`)\n return { room, player }\n }\n\n /**\n * @zh 加入指定房间\n * @en Join specific room\n */\n async joinById(\n roomId: string,\n playerId: string,\n conn: any\n ): Promise<{ room: Room; player: Player } | null> {\n const room = this._rooms.get(roomId)\n if (!room) return null\n\n const player = await room._addPlayer(playerId, conn)\n if (!player) return null\n\n this._playerToRoom.set(playerId, room.id)\n\n console.log(`[Room] Player ${playerId} joined ${room.id}`)\n return { room, player }\n }\n\n /**\n * @zh 玩家离开\n * @en Player leave\n */\n async leave(playerId: string, reason?: string): Promise<void> {\n const roomId = this._playerToRoom.get(playerId)\n if (!roomId) return\n\n const room = this._rooms.get(roomId)\n if (room) {\n await room._removePlayer(playerId, reason)\n }\n\n this._playerToRoom.delete(playerId)\n console.log(`[Room] Player ${playerId} left ${roomId}`)\n }\n\n /**\n * @zh 处理消息\n * @en Handle message\n */\n handleMessage(playerId: string, type: string, data: unknown): void {\n const roomId = this._playerToRoom.get(playerId)\n if (!roomId) return\n\n const room = this._rooms.get(roomId)\n if (room) {\n room._handleMessage(type, data, playerId)\n }\n }\n\n /**\n * @zh 获取房间\n * @en Get room\n */\n getRoom(roomId: string): Room | undefined {\n return this._rooms.get(roomId)\n }\n\n /**\n * @zh 获取玩家所在房间\n * @en Get player's room\n */\n getPlayerRoom(playerId: string): Room | undefined {\n const roomId = this._playerToRoom.get(playerId)\n return roomId ? this._rooms.get(roomId) : undefined\n }\n\n /**\n * @zh 获取所有房间\n * @en Get all rooms\n */\n getRooms(): ReadonlyArray<Room> {\n return Array.from(this._rooms.values())\n }\n\n /**\n * @zh 获取指定类型的所有房间\n * @en Get all rooms of a type\n */\n getRoomsByType(name: string): Room[] {\n const def = this._definitions.get(name)\n if (!def) return []\n\n return Array.from(this._rooms.values()).filter(\n room => room instanceof def.roomClass\n )\n }\n\n private _findAvailableRoom(name: string): Room | null {\n const def = this._definitions.get(name)\n if (!def) return null\n\n for (const room of this._rooms.values()) {\n if (\n room instanceof def.roomClass &&\n !room.isFull &&\n !room.isLocked &&\n !room.isDisposed\n ) {\n return room\n }\n }\n\n return null\n }\n\n private _generateRoomId(): string {\n return `room_${this._nextRoomId++}`\n }\n}\n","/**\n * @zh 游戏服务器核心\n * @en Game server core\n */\n\nimport * as path from 'node:path'\nimport { serve, type RpcServer } from '@esengine/rpc/server'\nimport { rpc } from '@esengine/rpc'\nimport type {\n ServerConfig,\n ServerConnection,\n GameServer,\n ApiContext,\n MsgContext,\n LoadedApiHandler,\n LoadedMsgHandler,\n} from '../types/index.js'\nimport { loadApiHandlers, loadMsgHandlers } from '../router/loader.js'\nimport { RoomManager, type RoomClass, type Room } from '../room/index.js'\n\n/**\n * @zh 默认配置\n * @en Default configuration\n */\nconst DEFAULT_CONFIG: Required<Omit<ServerConfig, 'onStart' | 'onConnect' | 'onDisconnect'>> = {\n port: 3000,\n apiDir: 'src/api',\n msgDir: 'src/msg',\n tickRate: 20,\n}\n\n/**\n * @zh 创建游戏服务器\n * @en Create game server\n *\n * @example\n * ```typescript\n * import { createServer, Room, onMessage } from '@esengine/server'\n *\n * class GameRoom extends Room {\n * onJoin(player) {\n * this.broadcast('Joined', { id: player.id })\n * }\n * }\n *\n * const server = await createServer({ port: 3000 })\n * server.define('game', GameRoom)\n * await server.start()\n * ```\n */\nexport async function createServer(config: ServerConfig = {}): Promise<GameServer> {\n const opts = { ...DEFAULT_CONFIG, ...config }\n const cwd = process.cwd()\n\n // 加载文件路由处理器\n const apiHandlers = await loadApiHandlers(path.resolve(cwd, opts.apiDir))\n const msgHandlers = await loadMsgHandlers(path.resolve(cwd, opts.msgDir))\n\n if (apiHandlers.length > 0) {\n console.log(`[Server] Loaded ${apiHandlers.length} API handlers`)\n }\n if (msgHandlers.length > 0) {\n console.log(`[Server] Loaded ${msgHandlers.length} message handlers`)\n }\n\n // 动态构建协议\n const apiDefs: Record<string, ReturnType<typeof rpc.api>> = {\n // 内置 API\n JoinRoom: rpc.api(),\n LeaveRoom: rpc.api(),\n }\n const msgDefs: Record<string, ReturnType<typeof rpc.msg>> = {\n // 内置消息(房间消息透传)\n RoomMessage: rpc.msg(),\n }\n\n for (const handler of apiHandlers) {\n apiDefs[handler.name] = rpc.api()\n }\n for (const handler of msgHandlers) {\n msgDefs[handler.name] = rpc.msg()\n }\n\n const protocol = rpc.define({\n api: apiDefs,\n msg: msgDefs,\n })\n\n // 服务器状态\n let currentTick = 0\n let tickInterval: ReturnType<typeof setInterval> | null = null\n let rpcServer: RpcServer<typeof protocol, Record<string, unknown>> | null = null\n\n // 房间管理器(立即初始化,以便 define() 可在 start() 前调用)\n const roomManager = new RoomManager((conn, type, data) => {\n rpcServer?.send(conn, 'RoomMessage' as any, { type, data } as any)\n })\n\n // 构建 API 处理器映射\n const apiMap: Record<string, LoadedApiHandler> = {}\n for (const handler of apiHandlers) {\n apiMap[handler.name] = handler\n }\n\n // 构建消息处理器映射\n const msgMap: Record<string, LoadedMsgHandler> = {}\n for (const handler of msgHandlers) {\n msgMap[handler.name] = handler\n }\n\n // 游戏服务器实例\n const gameServer: GameServer & {\n rooms: RoomManager\n } = {\n get connections() {\n return (rpcServer?.connections ?? []) as ReadonlyArray<ServerConnection>\n },\n\n get tick() {\n return currentTick\n },\n\n get rooms() {\n return roomManager\n },\n\n /**\n * @zh 注册房间类型\n * @en Define room type\n */\n define(name: string, roomClass: new () => unknown): void {\n roomManager.define(name, roomClass as RoomClass)\n },\n\n async start() {\n // 构建 API handlers\n const apiHandlersObj: Record<string, (input: unknown, conn: any) => Promise<unknown>> = {}\n\n // 内置 JoinRoom API\n apiHandlersObj['JoinRoom'] = async (input: any, conn) => {\n const { roomType, roomId, options } = input as {\n roomType?: string\n roomId?: string\n options?: Record<string, unknown>\n }\n\n if (roomId) {\n const result = await roomManager.joinById(roomId, conn.id, conn)\n if (!result) {\n throw new Error('Failed to join room')\n }\n return { roomId: result.room.id, playerId: result.player.id }\n }\n\n if (roomType) {\n const result = await roomManager.joinOrCreate(roomType, conn.id, conn, options)\n if (!result) {\n throw new Error('Failed to join or create room')\n }\n return { roomId: result.room.id, playerId: result.player.id }\n }\n\n throw new Error('roomType or roomId required')\n }\n\n // 内置 LeaveRoom API\n apiHandlersObj['LeaveRoom'] = async (_input, conn) => {\n await roomManager.leave(conn.id)\n return { success: true }\n }\n\n // 文件路由 API\n for (const [name, handler] of Object.entries(apiMap)) {\n apiHandlersObj[name] = async (input, conn) => {\n const ctx: ApiContext = {\n conn: conn as ServerConnection,\n server: gameServer,\n }\n return handler.definition.handler(input, ctx)\n }\n }\n\n // 构建消息 handlers\n const msgHandlersObj: Record<string, (data: unknown, conn: any) => void | Promise<void>> = {}\n\n // 内置 RoomMessage 处理\n msgHandlersObj['RoomMessage'] = async (data: any, conn) => {\n const { type, data: payload } = data as { type: string; data: unknown }\n roomManager.handleMessage(conn.id, type, payload)\n }\n\n // 文件路由消息\n for (const [name, handler] of Object.entries(msgMap)) {\n msgHandlersObj[name] = async (data, conn) => {\n const ctx: MsgContext = {\n conn: conn as ServerConnection,\n server: gameServer,\n }\n await handler.definition.handler(data, ctx)\n }\n }\n\n rpcServer = serve(protocol, {\n port: opts.port,\n createConnData: () => ({}),\n onStart: (p) => {\n console.log(`[Server] Started on ws://localhost:${p}`)\n opts.onStart?.(p)\n },\n onConnect: async (conn) => {\n await config.onConnect?.(conn as ServerConnection)\n },\n onDisconnect: async (conn) => {\n // 玩家断线时自动离开房间\n await roomManager?.leave(conn.id, 'disconnected')\n await config.onDisconnect?.(conn as ServerConnection)\n },\n api: apiHandlersObj as any,\n msg: msgHandlersObj as any,\n })\n\n await rpcServer.start()\n\n // 启动 tick 循环\n if (opts.tickRate > 0) {\n tickInterval = setInterval(() => {\n currentTick++\n }, 1000 / opts.tickRate)\n }\n },\n\n async stop() {\n if (tickInterval) {\n clearInterval(tickInterval)\n tickInterval = null\n }\n if (rpcServer) {\n await rpcServer.stop()\n rpcServer = null\n }\n },\n\n broadcast(name, data) {\n rpcServer?.broadcast(name as any, data as any)\n },\n\n send(conn, name, data) {\n rpcServer?.send(conn as any, name as any, data as any)\n },\n }\n\n return gameServer as GameServer\n}\n"]}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @zh 房间装饰器
3
+ * @en Room decorators
4
+ */
5
+ /**
6
+ * @zh 消息处理器装饰器
7
+ * @en Message handler decorator
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * class GameRoom extends Room {
12
+ * @onMessage('Move')
13
+ * handleMove(data: { x: number, y: number }, player: Player) {
14
+ * // handle move
15
+ * }
16
+ *
17
+ * @onMessage('Chat')
18
+ * handleChat(data: { text: string }, player: Player) {
19
+ * this.broadcast('Chat', { from: player.id, text: data.text })
20
+ * }
21
+ * }
22
+ * ```
23
+ */
24
+ declare function onMessage(type: string): MethodDecorator;
25
+
26
+ export { onMessage as o };
@@ -0,0 +1,159 @@
1
+ import { World, Scene, EntitySystem, Entity } from '@esengine/ecs-framework';
2
+ export { Component, Entity, EntitySystem, Scene, SyncFieldMetadata, SyncMetadata, SyncOperation, SyncType, World, clearChanges, getSyncMetadata, hasChanges, hasSyncFields, initChangeTracker, sync } from '@esengine/ecs-framework';
3
+ import { R as Room, P as Player } from '../Room-BnKpl5Sj.js';
4
+ export { o as onMessage } from '../decorators-DY8nZ8Nh.js';
5
+ import '@esengine/rpc';
6
+
7
+ /**
8
+ * @zh ECS 房间基类
9
+ * @en ECS Room base class
10
+ */
11
+
12
+ /**
13
+ * @zh ECS 房间配置
14
+ * @en ECS room configuration
15
+ */
16
+ interface ECSRoomConfig {
17
+ /**
18
+ * @zh 状态同步间隔(毫秒)
19
+ * @en State sync interval in milliseconds
20
+ */
21
+ syncInterval: number;
22
+ /**
23
+ * @zh 是否启用增量同步
24
+ * @en Whether to enable delta sync
25
+ */
26
+ enableDeltaSync: boolean;
27
+ }
28
+ /**
29
+ * @zh ECS 房间基类,带有 ECS World 支持和自动状态同步
30
+ * @en ECS Room base class with ECS World support and automatic state synchronization
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * // 服务端启动
35
+ * Core.create();
36
+ * setInterval(() => Core.update(1/60), 16);
37
+ *
38
+ * // 定义房间
39
+ * class GameRoom extends ECSRoom {
40
+ * onCreate() {
41
+ * this.addSystem(new PhysicsSystem());
42
+ * }
43
+ *
44
+ * onJoin(player: Player) {
45
+ * const entity = this.createPlayerEntity(player.id);
46
+ * entity.addComponent(new PlayerComponent());
47
+ * }
48
+ * }
49
+ * ```
50
+ */
51
+ declare abstract class ECSRoom<TState = any, TPlayerData = Record<string, unknown>> extends Room<TState, TPlayerData> {
52
+ /**
53
+ * @zh ECS World(由 Core.worldManager 管理)
54
+ * @en ECS World (managed by Core.worldManager)
55
+ */
56
+ protected readonly world: World;
57
+ /**
58
+ * @zh World 在 WorldManager 中的 ID
59
+ * @en World ID in WorldManager
60
+ */
61
+ protected readonly worldId: string;
62
+ /**
63
+ * @zh 房间的主场景
64
+ * @en Room's main scene
65
+ */
66
+ protected readonly scene: Scene;
67
+ /**
68
+ * @zh ECS 配置
69
+ * @en ECS configuration
70
+ */
71
+ protected readonly ecsConfig: ECSRoomConfig;
72
+ /**
73
+ * @zh 玩家 ID 到实体的映射
74
+ * @en Player ID to Entity mapping
75
+ */
76
+ private readonly _playerEntities;
77
+ /**
78
+ * @zh 上次同步时间
79
+ * @en Last sync time
80
+ */
81
+ private _lastSyncTime;
82
+ constructor(ecsConfig?: Partial<ECSRoomConfig>);
83
+ /**
84
+ * @zh 添加系统到场景
85
+ * @en Add system to scene
86
+ */
87
+ protected addSystem(system: EntitySystem): void;
88
+ /**
89
+ * @zh 创建实体
90
+ * @en Create entity
91
+ */
92
+ protected createEntity(name?: string): Entity;
93
+ /**
94
+ * @zh 为玩家创建实体
95
+ * @en Create entity for player
96
+ *
97
+ * @param playerId - @zh 玩家 ID @en Player ID
98
+ * @param name - @zh 实体名称 @en Entity name
99
+ * @returns @zh 创建的实体 @en Created entity
100
+ */
101
+ protected createPlayerEntity(playerId: string, name?: string): Entity;
102
+ /**
103
+ * @zh 获取玩家的实体
104
+ * @en Get player's entity
105
+ */
106
+ protected getPlayerEntity(playerId: string): Entity | undefined;
107
+ /**
108
+ * @zh 销毁玩家的实体
109
+ * @en Destroy player's entity
110
+ */
111
+ protected destroyPlayerEntity(playerId: string): void;
112
+ /**
113
+ * @zh 广播二进制数据
114
+ * @en Broadcast binary data
115
+ */
116
+ protected broadcastBinary(data: Uint8Array): void;
117
+ /**
118
+ * @zh 发送二进制数据给指定玩家
119
+ * @en Send binary data to specific player
120
+ */
121
+ protected sendBinary(player: Player<TPlayerData>, data: Uint8Array): void;
122
+ /**
123
+ * @zh 发送完整状态给玩家(用于玩家刚加入时)
124
+ * @en Send full state to player (for when player just joined)
125
+ */
126
+ protected sendFullState(player: Player<TPlayerData>): void;
127
+ /**
128
+ * @zh 广播实体生成
129
+ * @en Broadcast entity spawn
130
+ */
131
+ protected broadcastSpawn(entity: Entity, prefabType?: string): void;
132
+ /**
133
+ * @zh 广播增量状态更新
134
+ * @en Broadcast delta state update
135
+ */
136
+ protected broadcastDelta(): void;
137
+ /**
138
+ * @zh 游戏循环,处理状态同步
139
+ * @en Game tick, handles state sync
140
+ */
141
+ onTick(_dt: number): void;
142
+ /**
143
+ * @zh 玩家离开时自动销毁其实体
144
+ * @en Auto destroy player entity when leaving
145
+ */
146
+ onLeave(player: Player<TPlayerData>, reason?: string): Promise<void>;
147
+ /**
148
+ * @zh 房间销毁时从 WorldManager 移除 World
149
+ * @en Remove World from WorldManager when room is disposed
150
+ */
151
+ onDispose(): void;
152
+ private _getSyncEntities;
153
+ private _hasSyncComponents;
154
+ private _hasChanges;
155
+ private _initComponentTrackers;
156
+ private _clearChangeTrackers;
157
+ }
158
+
159
+ export { ECSRoom, type ECSRoomConfig };