@esengine/server 4.0.0 → 4.1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@esengine/server",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "description": "Game server framework for ESEngine with file-based routing",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -37,12 +37,12 @@
37
37
  "module.json"
38
38
  ],
39
39
  "dependencies": {
40
- "@esengine/rpc": "1.1.1"
40
+ "@esengine/rpc": "1.1.2"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "ws": ">=8.0.0",
44
44
  "jsonwebtoken": ">=9.0.0",
45
- "@esengine/ecs-framework": ">=2.7.0"
45
+ "@esengine/ecs-framework": ">=2.7.1"
46
46
  },
47
47
  "peerDependenciesMeta": {
48
48
  "jsonwebtoken": {
@@ -62,7 +62,7 @@
62
62
  "typescript": "^5.7.0",
63
63
  "vitest": "^2.0.0",
64
64
  "ws": "^8.18.0",
65
- "@esengine/ecs-framework": "2.7.0"
65
+ "@esengine/ecs-framework": "2.7.1"
66
66
  },
67
67
  "publishConfig": {
68
68
  "access": "public"
@@ -1,412 +0,0 @@
1
- import { __name, __publicField } from './chunk-T626JPC7.js';
2
- import * as path from 'path';
3
- import { serve } from '@esengine/rpc/server';
4
- import { rpc } from '@esengine/rpc';
5
- import * as fs from 'fs';
6
- import { pathToFileURL } from 'url';
7
-
8
- function fileNameToHandlerName(fileName) {
9
- const baseName = fileName.replace(/\.(ts|js|mts|mjs)$/, "");
10
- return baseName.split(/[-_]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
11
- }
12
- __name(fileNameToHandlerName, "fileNameToHandlerName");
13
- function scanDirectory(dir) {
14
- if (!fs.existsSync(dir)) {
15
- return [];
16
- }
17
- const files = [];
18
- const entries = fs.readdirSync(dir, {
19
- withFileTypes: true
20
- });
21
- for (const entry of entries) {
22
- if (entry.isFile() && /\.(ts|js|mts|mjs)$/.test(entry.name)) {
23
- if (entry.name.startsWith("_") || entry.name.startsWith("index.")) {
24
- continue;
25
- }
26
- files.push(path.join(dir, entry.name));
27
- }
28
- }
29
- return files;
30
- }
31
- __name(scanDirectory, "scanDirectory");
32
- async function loadApiHandlers(apiDir) {
33
- const files = scanDirectory(apiDir);
34
- const handlers = [];
35
- for (const filePath of files) {
36
- try {
37
- const fileUrl = pathToFileURL(filePath).href;
38
- const module = await import(fileUrl);
39
- const definition = module.default;
40
- if (definition && typeof definition.handler === "function") {
41
- const name = fileNameToHandlerName(path.basename(filePath));
42
- handlers.push({
43
- name,
44
- path: filePath,
45
- definition
46
- });
47
- }
48
- } catch (err) {
49
- console.warn(`[Server] Failed to load API handler: ${filePath}`, err);
50
- }
51
- }
52
- return handlers;
53
- }
54
- __name(loadApiHandlers, "loadApiHandlers");
55
- async function loadMsgHandlers(msgDir) {
56
- const files = scanDirectory(msgDir);
57
- const handlers = [];
58
- for (const filePath of files) {
59
- try {
60
- const fileUrl = pathToFileURL(filePath).href;
61
- const module = await import(fileUrl);
62
- const definition = module.default;
63
- if (definition && typeof definition.handler === "function") {
64
- const name = fileNameToHandlerName(path.basename(filePath));
65
- handlers.push({
66
- name,
67
- path: filePath,
68
- definition
69
- });
70
- }
71
- } catch (err) {
72
- console.warn(`[Server] Failed to load msg handler: ${filePath}`, err);
73
- }
74
- }
75
- return handlers;
76
- }
77
- __name(loadMsgHandlers, "loadMsgHandlers");
78
-
79
- // src/room/RoomManager.ts
80
- var _RoomManager = class _RoomManager {
81
- constructor(sendFn) {
82
- __publicField(this, "_definitions", /* @__PURE__ */ new Map());
83
- __publicField(this, "_rooms", /* @__PURE__ */ new Map());
84
- __publicField(this, "_playerToRoom", /* @__PURE__ */ new Map());
85
- __publicField(this, "_nextRoomId", 1);
86
- __publicField(this, "_sendFn");
87
- this._sendFn = sendFn;
88
- }
89
- /**
90
- * @zh 注册房间类型
91
- * @en Define room type
92
- */
93
- define(name, roomClass) {
94
- this._definitions.set(name, {
95
- roomClass
96
- });
97
- }
98
- /**
99
- * @zh 创建房间
100
- * @en Create room
101
- */
102
- async create(name, options) {
103
- const def = this._definitions.get(name);
104
- if (!def) {
105
- console.warn(`[RoomManager] Room type not found: ${name}`);
106
- return null;
107
- }
108
- const roomId = this._generateRoomId();
109
- const room = new def.roomClass();
110
- room._init({
111
- id: roomId,
112
- sendFn: this._sendFn,
113
- broadcastFn: /* @__PURE__ */ __name((type, data) => {
114
- for (const player of room.players) {
115
- player.send(type, data);
116
- }
117
- }, "broadcastFn"),
118
- disposeFn: /* @__PURE__ */ __name(() => {
119
- this._rooms.delete(roomId);
120
- }, "disposeFn")
121
- });
122
- this._rooms.set(roomId, room);
123
- await room._create(options);
124
- console.log(`[Room] Created: ${name} (${roomId})`);
125
- return room;
126
- }
127
- /**
128
- * @zh 加入或创建房间
129
- * @en Join or create room
130
- */
131
- async joinOrCreate(name, playerId, conn, options) {
132
- let room = this._findAvailableRoom(name);
133
- if (!room) {
134
- room = await this.create(name, options);
135
- if (!room) return null;
136
- }
137
- const player = await room._addPlayer(playerId, conn);
138
- if (!player) return null;
139
- this._playerToRoom.set(playerId, room.id);
140
- console.log(`[Room] Player ${playerId} joined ${room.id}`);
141
- return {
142
- room,
143
- player
144
- };
145
- }
146
- /**
147
- * @zh 加入指定房间
148
- * @en Join specific room
149
- */
150
- async joinById(roomId, playerId, conn) {
151
- const room = this._rooms.get(roomId);
152
- if (!room) return null;
153
- const player = await room._addPlayer(playerId, conn);
154
- if (!player) return null;
155
- this._playerToRoom.set(playerId, room.id);
156
- console.log(`[Room] Player ${playerId} joined ${room.id}`);
157
- return {
158
- room,
159
- player
160
- };
161
- }
162
- /**
163
- * @zh 玩家离开
164
- * @en Player leave
165
- */
166
- async leave(playerId, reason) {
167
- const roomId = this._playerToRoom.get(playerId);
168
- if (!roomId) return;
169
- const room = this._rooms.get(roomId);
170
- if (room) {
171
- await room._removePlayer(playerId, reason);
172
- }
173
- this._playerToRoom.delete(playerId);
174
- console.log(`[Room] Player ${playerId} left ${roomId}`);
175
- }
176
- /**
177
- * @zh 处理消息
178
- * @en Handle message
179
- */
180
- handleMessage(playerId, type, data) {
181
- const roomId = this._playerToRoom.get(playerId);
182
- if (!roomId) return;
183
- const room = this._rooms.get(roomId);
184
- if (room) {
185
- room._handleMessage(type, data, playerId);
186
- }
187
- }
188
- /**
189
- * @zh 获取房间
190
- * @en Get room
191
- */
192
- getRoom(roomId) {
193
- return this._rooms.get(roomId);
194
- }
195
- /**
196
- * @zh 获取玩家所在房间
197
- * @en Get player's room
198
- */
199
- getPlayerRoom(playerId) {
200
- const roomId = this._playerToRoom.get(playerId);
201
- return roomId ? this._rooms.get(roomId) : void 0;
202
- }
203
- /**
204
- * @zh 获取所有房间
205
- * @en Get all rooms
206
- */
207
- getRooms() {
208
- return Array.from(this._rooms.values());
209
- }
210
- /**
211
- * @zh 获取指定类型的所有房间
212
- * @en Get all rooms of a type
213
- */
214
- getRoomsByType(name) {
215
- const def = this._definitions.get(name);
216
- if (!def) return [];
217
- return Array.from(this._rooms.values()).filter((room) => room instanceof def.roomClass);
218
- }
219
- _findAvailableRoom(name) {
220
- const def = this._definitions.get(name);
221
- if (!def) return null;
222
- for (const room of this._rooms.values()) {
223
- if (room instanceof def.roomClass && !room.isFull && !room.isLocked && !room.isDisposed) {
224
- return room;
225
- }
226
- }
227
- return null;
228
- }
229
- _generateRoomId() {
230
- return `room_${this._nextRoomId++}`;
231
- }
232
- };
233
- __name(_RoomManager, "RoomManager");
234
- var RoomManager = _RoomManager;
235
-
236
- // src/core/server.ts
237
- var DEFAULT_CONFIG = {
238
- port: 3e3,
239
- apiDir: "src/api",
240
- msgDir: "src/msg",
241
- tickRate: 20
242
- };
243
- async function createServer(config = {}) {
244
- const opts = {
245
- ...DEFAULT_CONFIG,
246
- ...config
247
- };
248
- const cwd = process.cwd();
249
- const apiHandlers = await loadApiHandlers(path.resolve(cwd, opts.apiDir));
250
- const msgHandlers = await loadMsgHandlers(path.resolve(cwd, opts.msgDir));
251
- if (apiHandlers.length > 0) {
252
- console.log(`[Server] Loaded ${apiHandlers.length} API handlers`);
253
- }
254
- if (msgHandlers.length > 0) {
255
- console.log(`[Server] Loaded ${msgHandlers.length} message handlers`);
256
- }
257
- const apiDefs = {
258
- // 内置 API
259
- JoinRoom: rpc.api(),
260
- LeaveRoom: rpc.api()
261
- };
262
- const msgDefs = {
263
- // 内置消息(房间消息透传)
264
- RoomMessage: rpc.msg()
265
- };
266
- for (const handler of apiHandlers) {
267
- apiDefs[handler.name] = rpc.api();
268
- }
269
- for (const handler of msgHandlers) {
270
- msgDefs[handler.name] = rpc.msg();
271
- }
272
- const protocol = rpc.define({
273
- api: apiDefs,
274
- msg: msgDefs
275
- });
276
- let currentTick = 0;
277
- let tickInterval = null;
278
- let rpcServer = null;
279
- const roomManager = new RoomManager((conn, type, data) => {
280
- rpcServer?.send(conn, "RoomMessage", {
281
- type,
282
- data
283
- });
284
- });
285
- const apiMap = {};
286
- for (const handler of apiHandlers) {
287
- apiMap[handler.name] = handler;
288
- }
289
- const msgMap = {};
290
- for (const handler of msgHandlers) {
291
- msgMap[handler.name] = handler;
292
- }
293
- const gameServer = {
294
- get connections() {
295
- return rpcServer?.connections ?? [];
296
- },
297
- get tick() {
298
- return currentTick;
299
- },
300
- get rooms() {
301
- return roomManager;
302
- },
303
- /**
304
- * @zh 注册房间类型
305
- * @en Define room type
306
- */
307
- define(name, roomClass) {
308
- roomManager.define(name, roomClass);
309
- },
310
- async start() {
311
- const apiHandlersObj = {};
312
- apiHandlersObj["JoinRoom"] = async (input, conn) => {
313
- const { roomType, roomId, options } = input;
314
- if (roomId) {
315
- const result = await roomManager.joinById(roomId, conn.id, conn);
316
- if (!result) {
317
- throw new Error("Failed to join room");
318
- }
319
- return {
320
- roomId: result.room.id,
321
- playerId: result.player.id
322
- };
323
- }
324
- if (roomType) {
325
- const result = await roomManager.joinOrCreate(roomType, conn.id, conn, options);
326
- if (!result) {
327
- throw new Error("Failed to join or create room");
328
- }
329
- return {
330
- roomId: result.room.id,
331
- playerId: result.player.id
332
- };
333
- }
334
- throw new Error("roomType or roomId required");
335
- };
336
- apiHandlersObj["LeaveRoom"] = async (_input, conn) => {
337
- await roomManager.leave(conn.id);
338
- return {
339
- success: true
340
- };
341
- };
342
- for (const [name, handler] of Object.entries(apiMap)) {
343
- apiHandlersObj[name] = async (input, conn) => {
344
- const ctx = {
345
- conn,
346
- server: gameServer
347
- };
348
- return handler.definition.handler(input, ctx);
349
- };
350
- }
351
- const msgHandlersObj = {};
352
- msgHandlersObj["RoomMessage"] = async (data, conn) => {
353
- const { type, data: payload } = data;
354
- roomManager.handleMessage(conn.id, type, payload);
355
- };
356
- for (const [name, handler] of Object.entries(msgMap)) {
357
- msgHandlersObj[name] = async (data, conn) => {
358
- const ctx = {
359
- conn,
360
- server: gameServer
361
- };
362
- await handler.definition.handler(data, ctx);
363
- };
364
- }
365
- rpcServer = serve(protocol, {
366
- port: opts.port,
367
- createConnData: /* @__PURE__ */ __name(() => ({}), "createConnData"),
368
- onStart: /* @__PURE__ */ __name((p) => {
369
- console.log(`[Server] Started on ws://localhost:${p}`);
370
- opts.onStart?.(p);
371
- }, "onStart"),
372
- onConnect: /* @__PURE__ */ __name(async (conn) => {
373
- await config.onConnect?.(conn);
374
- }, "onConnect"),
375
- onDisconnect: /* @__PURE__ */ __name(async (conn) => {
376
- await roomManager?.leave(conn.id, "disconnected");
377
- await config.onDisconnect?.(conn);
378
- }, "onDisconnect"),
379
- api: apiHandlersObj,
380
- msg: msgHandlersObj
381
- });
382
- await rpcServer.start();
383
- if (opts.tickRate > 0) {
384
- tickInterval = setInterval(() => {
385
- currentTick++;
386
- }, 1e3 / opts.tickRate);
387
- }
388
- },
389
- async stop() {
390
- if (tickInterval) {
391
- clearInterval(tickInterval);
392
- tickInterval = null;
393
- }
394
- if (rpcServer) {
395
- await rpcServer.stop();
396
- rpcServer = null;
397
- }
398
- },
399
- broadcast(name, data) {
400
- rpcServer?.broadcast(name, data);
401
- },
402
- send(conn, name, data) {
403
- rpcServer?.send(conn, name, data);
404
- }
405
- };
406
- return gameServer;
407
- }
408
- __name(createServer, "createServer");
409
-
410
- export { createServer };
411
- //# sourceMappingURL=chunk-QWEIP5QH.js.map
412
- //# sourceMappingURL=chunk-QWEIP5QH.js.map
@@ -1 +0,0 @@
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"]}
@@ -1,170 +0,0 @@
1
- import { Connection } from '@esengine/rpc';
2
-
3
- /**
4
- * @zh ESEngine Server 类型定义
5
- * @en ESEngine Server type definitions
6
- */
7
-
8
- /**
9
- * @zh 服务器配置
10
- * @en Server configuration
11
- */
12
- interface ServerConfig {
13
- /**
14
- * @zh 监听端口
15
- * @en Listen port
16
- * @default 3000
17
- */
18
- port?: number;
19
- /**
20
- * @zh API 目录路径
21
- * @en API directory path
22
- * @default 'src/api'
23
- */
24
- apiDir?: string;
25
- /**
26
- * @zh 消息处理器目录路径
27
- * @en Message handlers directory path
28
- * @default 'src/msg'
29
- */
30
- msgDir?: string;
31
- /**
32
- * @zh 游戏 Tick 速率 (每秒)
33
- * @en Game tick rate (per second)
34
- * @default 20
35
- */
36
- tickRate?: number;
37
- /**
38
- * @zh 服务器启动回调
39
- * @en Server start callback
40
- */
41
- onStart?: (port: number) => void;
42
- /**
43
- * @zh 连接建立回调
44
- * @en Connection established callback
45
- */
46
- onConnect?: (conn: ServerConnection) => void | Promise<void>;
47
- /**
48
- * @zh 连接断开回调
49
- * @en Connection closed callback
50
- */
51
- onDisconnect?: (conn: ServerConnection) => void | Promise<void>;
52
- }
53
- /**
54
- * @zh 服务器连接(扩展 RPC Connection)
55
- * @en Server connection (extends RPC Connection)
56
- */
57
- interface ServerConnection<TData = Record<string, unknown>> extends Connection<TData> {
58
- /**
59
- * @zh 连接唯一标识(继承自 Connection)
60
- * @en Connection unique identifier (inherited from Connection)
61
- */
62
- readonly id: string;
63
- /**
64
- * @zh 用户自定义数据
65
- * @en User-defined data
66
- */
67
- data: TData;
68
- }
69
- /**
70
- * @zh API 处理器上下文
71
- * @en API handler context
72
- */
73
- interface ApiContext<TData = Record<string, unknown>> {
74
- /**
75
- * @zh 当前连接
76
- * @en Current connection
77
- */
78
- conn: ServerConnection<TData>;
79
- /**
80
- * @zh 服务器实例
81
- * @en Server instance
82
- */
83
- server: GameServer;
84
- }
85
- /**
86
- * @zh API 定义选项
87
- * @en API definition options
88
- */
89
- interface ApiDefinition<TReq = unknown, TRes = unknown, TData = Record<string, unknown>> {
90
- /**
91
- * @zh API 处理函数
92
- * @en API handler function
93
- */
94
- handler: (req: TReq, ctx: ApiContext<TData>) => TRes | Promise<TRes>;
95
- /**
96
- * @zh 请求验证函数(可选)
97
- * @en Request validation function (optional)
98
- */
99
- validate?: (req: unknown) => req is TReq;
100
- }
101
- /**
102
- * @zh 消息处理器上下文
103
- * @en Message handler context
104
- */
105
- interface MsgContext<TData = Record<string, unknown>> {
106
- /**
107
- * @zh 当前连接
108
- * @en Current connection
109
- */
110
- conn: ServerConnection<TData>;
111
- /**
112
- * @zh 服务器实例
113
- * @en Server instance
114
- */
115
- server: GameServer;
116
- }
117
- /**
118
- * @zh 消息定义选项
119
- * @en Message definition options
120
- */
121
- interface MsgDefinition<TMsg = unknown, TData = Record<string, unknown>> {
122
- /**
123
- * @zh 消息处理函数
124
- * @en Message handler function
125
- */
126
- handler: (msg: TMsg, ctx: MsgContext<TData>) => void | Promise<void>;
127
- }
128
- /**
129
- * @zh 游戏服务器接口
130
- * @en Game server interface
131
- */
132
- interface GameServer {
133
- /**
134
- * @zh 启动服务器
135
- * @en Start server
136
- */
137
- start(): Promise<void>;
138
- /**
139
- * @zh 停止服务器
140
- * @en Stop server
141
- */
142
- stop(): Promise<void>;
143
- /**
144
- * @zh 广播消息
145
- * @en Broadcast message
146
- */
147
- broadcast<T>(name: string, data: T): void;
148
- /**
149
- * @zh 发送消息给指定连接
150
- * @en Send message to specific connection
151
- */
152
- send<T>(conn: ServerConnection, name: string, data: T): void;
153
- /**
154
- * @zh 获取所有连接
155
- * @en Get all connections
156
- */
157
- readonly connections: ReadonlyArray<ServerConnection>;
158
- /**
159
- * @zh 当前 Tick
160
- * @en Current tick
161
- */
162
- readonly tick: number;
163
- /**
164
- * @zh 注册房间类型
165
- * @en Define room type
166
- */
167
- define(name: string, roomClass: new () => unknown): void;
168
- }
169
-
170
- export type { ApiDefinition as A, GameServer as G, MsgDefinition as M, ServerConfig as S, ServerConnection as a, ApiContext as b, MsgContext as c };