@esengine/server 4.0.0 → 4.2.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.
@@ -0,0 +1,729 @@
1
+ import { __name, __publicField } from './chunk-T626JPC7.js';
2
+ import * as path from 'path';
3
+ import { createServer as createServer$1 } from 'http';
4
+ import { serve } from '@esengine/rpc/server';
5
+ import { rpc } from '@esengine/rpc';
6
+ import * as fs from 'fs';
7
+ import { pathToFileURL } from 'url';
8
+
9
+ // src/http/router.ts
10
+ async function createRequest(req) {
11
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
12
+ const query = {};
13
+ url.searchParams.forEach((value, key) => {
14
+ query[key] = value;
15
+ });
16
+ let body = null;
17
+ if (req.method === "POST" || req.method === "PUT" || req.method === "PATCH") {
18
+ body = await parseBody(req);
19
+ }
20
+ const ip = req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.socket?.remoteAddress || "unknown";
21
+ return {
22
+ raw: req,
23
+ method: req.method ?? "GET",
24
+ path: url.pathname,
25
+ query,
26
+ headers: req.headers,
27
+ body,
28
+ ip
29
+ };
30
+ }
31
+ __name(createRequest, "createRequest");
32
+ function parseBody(req) {
33
+ return new Promise((resolve2) => {
34
+ const chunks = [];
35
+ req.on("data", (chunk) => {
36
+ chunks.push(chunk);
37
+ });
38
+ req.on("end", () => {
39
+ const rawBody = Buffer.concat(chunks).toString("utf-8");
40
+ if (!rawBody) {
41
+ resolve2(null);
42
+ return;
43
+ }
44
+ const contentType = req.headers["content-type"] ?? "";
45
+ if (contentType.includes("application/json")) {
46
+ try {
47
+ resolve2(JSON.parse(rawBody));
48
+ } catch {
49
+ resolve2(rawBody);
50
+ }
51
+ } else if (contentType.includes("application/x-www-form-urlencoded")) {
52
+ const params = new URLSearchParams(rawBody);
53
+ const result = {};
54
+ params.forEach((value, key) => {
55
+ result[key] = value;
56
+ });
57
+ resolve2(result);
58
+ } else {
59
+ resolve2(rawBody);
60
+ }
61
+ });
62
+ req.on("error", () => {
63
+ resolve2(null);
64
+ });
65
+ });
66
+ }
67
+ __name(parseBody, "parseBody");
68
+ function createResponse(res) {
69
+ let statusCode = 200;
70
+ const response = {
71
+ raw: res,
72
+ status(code) {
73
+ statusCode = code;
74
+ return response;
75
+ },
76
+ header(name, value) {
77
+ res.setHeader(name, value);
78
+ return response;
79
+ },
80
+ json(data) {
81
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
82
+ res.statusCode = statusCode;
83
+ res.end(JSON.stringify(data));
84
+ },
85
+ text(data) {
86
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
87
+ res.statusCode = statusCode;
88
+ res.end(data);
89
+ },
90
+ error(code, message) {
91
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
92
+ res.statusCode = code;
93
+ res.end(JSON.stringify({
94
+ error: message
95
+ }));
96
+ }
97
+ };
98
+ return response;
99
+ }
100
+ __name(createResponse, "createResponse");
101
+ function applyCors(res, req, cors) {
102
+ const origin = req.headers.origin;
103
+ if (cors.origin === true || cors.origin === "*") {
104
+ res.setHeader("Access-Control-Allow-Origin", origin ?? "*");
105
+ } else if (typeof cors.origin === "string") {
106
+ res.setHeader("Access-Control-Allow-Origin", cors.origin);
107
+ } else if (Array.isArray(cors.origin) && origin && cors.origin.includes(origin)) {
108
+ res.setHeader("Access-Control-Allow-Origin", origin);
109
+ }
110
+ if (cors.methods) {
111
+ res.setHeader("Access-Control-Allow-Methods", cors.methods.join(", "));
112
+ } else {
113
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
114
+ }
115
+ if (cors.allowedHeaders) {
116
+ res.setHeader("Access-Control-Allow-Headers", cors.allowedHeaders.join(", "));
117
+ } else {
118
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
119
+ }
120
+ if (cors.credentials) {
121
+ res.setHeader("Access-Control-Allow-Credentials", "true");
122
+ }
123
+ if (cors.maxAge) {
124
+ res.setHeader("Access-Control-Max-Age", String(cors.maxAge));
125
+ }
126
+ }
127
+ __name(applyCors, "applyCors");
128
+ function createHttpRouter(routes, cors) {
129
+ const parsedRoutes = [];
130
+ for (const [path3, handlerOrMethods] of Object.entries(routes)) {
131
+ if (typeof handlerOrMethods === "function") {
132
+ parsedRoutes.push({
133
+ method: "*",
134
+ path: path3,
135
+ handler: handlerOrMethods
136
+ });
137
+ } else {
138
+ for (const [method, handler] of Object.entries(handlerOrMethods)) {
139
+ if (handler !== void 0) {
140
+ parsedRoutes.push({
141
+ method,
142
+ path: path3,
143
+ handler
144
+ });
145
+ }
146
+ }
147
+ }
148
+ }
149
+ const corsOptions = cors === true ? {
150
+ origin: true,
151
+ credentials: true
152
+ } : cors === false ? null : cors ?? null;
153
+ return /* @__PURE__ */ __name(async function handleRequest(req, res) {
154
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
155
+ const path3 = url.pathname;
156
+ const method = req.method ?? "GET";
157
+ if (corsOptions) {
158
+ applyCors(res, req, corsOptions);
159
+ if (method === "OPTIONS") {
160
+ res.statusCode = 204;
161
+ res.end();
162
+ return true;
163
+ }
164
+ }
165
+ const route = parsedRoutes.find((r) => r.path === path3 && (r.method === "*" || r.method === method));
166
+ if (!route) {
167
+ return false;
168
+ }
169
+ try {
170
+ const httpReq = await createRequest(req);
171
+ const httpRes = createResponse(res);
172
+ await route.handler(httpReq, httpRes);
173
+ return true;
174
+ } catch (error) {
175
+ console.error("[HTTP] Route handler error:", error);
176
+ res.statusCode = 500;
177
+ res.setHeader("Content-Type", "application/json");
178
+ res.end(JSON.stringify({
179
+ error: "Internal Server Error"
180
+ }));
181
+ return true;
182
+ }
183
+ }, "handleRequest");
184
+ }
185
+ __name(createHttpRouter, "createHttpRouter");
186
+ function fileNameToHandlerName(fileName) {
187
+ const baseName = fileName.replace(/\.(ts|js|mts|mjs)$/, "");
188
+ return baseName.split(/[-_]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
189
+ }
190
+ __name(fileNameToHandlerName, "fileNameToHandlerName");
191
+ function scanDirectory(dir) {
192
+ if (!fs.existsSync(dir)) {
193
+ return [];
194
+ }
195
+ const files = [];
196
+ const entries = fs.readdirSync(dir, {
197
+ withFileTypes: true
198
+ });
199
+ for (const entry of entries) {
200
+ if (entry.isFile() && /\.(ts|js|mts|mjs)$/.test(entry.name)) {
201
+ if (entry.name.startsWith("_") || entry.name.startsWith("index.")) {
202
+ continue;
203
+ }
204
+ files.push(path.join(dir, entry.name));
205
+ }
206
+ }
207
+ return files;
208
+ }
209
+ __name(scanDirectory, "scanDirectory");
210
+ async function loadApiHandlers(apiDir) {
211
+ const files = scanDirectory(apiDir);
212
+ const handlers = [];
213
+ for (const filePath of files) {
214
+ try {
215
+ const fileUrl = pathToFileURL(filePath).href;
216
+ const module = await import(fileUrl);
217
+ const definition = module.default;
218
+ if (definition && typeof definition.handler === "function") {
219
+ const name = fileNameToHandlerName(path.basename(filePath));
220
+ handlers.push({
221
+ name,
222
+ path: filePath,
223
+ definition
224
+ });
225
+ }
226
+ } catch (err) {
227
+ console.warn(`[Server] Failed to load API handler: ${filePath}`, err);
228
+ }
229
+ }
230
+ return handlers;
231
+ }
232
+ __name(loadApiHandlers, "loadApiHandlers");
233
+ async function loadMsgHandlers(msgDir) {
234
+ const files = scanDirectory(msgDir);
235
+ const handlers = [];
236
+ for (const filePath of files) {
237
+ try {
238
+ const fileUrl = pathToFileURL(filePath).href;
239
+ const module = await import(fileUrl);
240
+ const definition = module.default;
241
+ if (definition && typeof definition.handler === "function") {
242
+ const name = fileNameToHandlerName(path.basename(filePath));
243
+ handlers.push({
244
+ name,
245
+ path: filePath,
246
+ definition
247
+ });
248
+ }
249
+ } catch (err) {
250
+ console.warn(`[Server] Failed to load msg handler: ${filePath}`, err);
251
+ }
252
+ }
253
+ return handlers;
254
+ }
255
+ __name(loadMsgHandlers, "loadMsgHandlers");
256
+ function scanDirectoryRecursive(dir, baseDir = dir) {
257
+ if (!fs.existsSync(dir)) {
258
+ return [];
259
+ }
260
+ const files = [];
261
+ const entries = fs.readdirSync(dir, {
262
+ withFileTypes: true
263
+ });
264
+ for (const entry of entries) {
265
+ const fullPath = path.join(dir, entry.name);
266
+ if (entry.isDirectory()) {
267
+ files.push(...scanDirectoryRecursive(fullPath, baseDir));
268
+ } else if (entry.isFile() && /\.(ts|js|mts|mjs)$/.test(entry.name)) {
269
+ if (entry.name.startsWith("_") || entry.name.startsWith("index.")) {
270
+ continue;
271
+ }
272
+ const relativePath = path.relative(baseDir, fullPath);
273
+ files.push({
274
+ filePath: fullPath,
275
+ relativePath
276
+ });
277
+ }
278
+ }
279
+ return files;
280
+ }
281
+ __name(scanDirectoryRecursive, "scanDirectoryRecursive");
282
+ function filePathToRoute(relativePath, prefix) {
283
+ let route = relativePath.replace(/\.(ts|js|mts|mjs)$/, "").replace(/\\/g, "/").replace(/\[([^\]]+)\]/g, ":$1");
284
+ if (!route.startsWith("/")) {
285
+ route = "/" + route;
286
+ }
287
+ const fullRoute = prefix.endsWith("/") ? prefix.slice(0, -1) + route : prefix + route;
288
+ return fullRoute;
289
+ }
290
+ __name(filePathToRoute, "filePathToRoute");
291
+ async function loadHttpHandlers(httpDir, prefix = "/api") {
292
+ const files = scanDirectoryRecursive(httpDir);
293
+ const handlers = [];
294
+ for (const { filePath, relativePath } of files) {
295
+ try {
296
+ const fileUrl = pathToFileURL(filePath).href;
297
+ const module = await import(fileUrl);
298
+ const definition = module.default;
299
+ if (definition && typeof definition.handler === "function") {
300
+ const route = filePathToRoute(relativePath, prefix);
301
+ const method = definition.method ?? "POST";
302
+ handlers.push({
303
+ route,
304
+ method,
305
+ path: filePath,
306
+ definition
307
+ });
308
+ }
309
+ } catch (err) {
310
+ console.warn(`[Server] Failed to load HTTP handler: ${filePath}`, err);
311
+ }
312
+ }
313
+ return handlers;
314
+ }
315
+ __name(loadHttpHandlers, "loadHttpHandlers");
316
+
317
+ // src/room/RoomManager.ts
318
+ var _RoomManager = class _RoomManager {
319
+ constructor(sendFn) {
320
+ __publicField(this, "_definitions", /* @__PURE__ */ new Map());
321
+ __publicField(this, "_rooms", /* @__PURE__ */ new Map());
322
+ __publicField(this, "_playerToRoom", /* @__PURE__ */ new Map());
323
+ __publicField(this, "_nextRoomId", 1);
324
+ __publicField(this, "_sendFn");
325
+ this._sendFn = sendFn;
326
+ }
327
+ /**
328
+ * @zh 注册房间类型
329
+ * @en Define room type
330
+ */
331
+ define(name, roomClass) {
332
+ this._definitions.set(name, {
333
+ roomClass
334
+ });
335
+ }
336
+ /**
337
+ * @zh 创建房间
338
+ * @en Create room
339
+ */
340
+ async create(name, options) {
341
+ const def = this._definitions.get(name);
342
+ if (!def) {
343
+ console.warn(`[RoomManager] Room type not found: ${name}`);
344
+ return null;
345
+ }
346
+ const roomId = this._generateRoomId();
347
+ const room = new def.roomClass();
348
+ room._init({
349
+ id: roomId,
350
+ sendFn: this._sendFn,
351
+ broadcastFn: /* @__PURE__ */ __name((type, data) => {
352
+ for (const player of room.players) {
353
+ player.send(type, data);
354
+ }
355
+ }, "broadcastFn"),
356
+ disposeFn: /* @__PURE__ */ __name(() => {
357
+ this._rooms.delete(roomId);
358
+ }, "disposeFn")
359
+ });
360
+ this._rooms.set(roomId, room);
361
+ await room._create(options);
362
+ console.log(`[Room] Created: ${name} (${roomId})`);
363
+ return room;
364
+ }
365
+ /**
366
+ * @zh 加入或创建房间
367
+ * @en Join or create room
368
+ */
369
+ async joinOrCreate(name, playerId, conn, options) {
370
+ let room = this._findAvailableRoom(name);
371
+ if (!room) {
372
+ room = await this.create(name, options);
373
+ if (!room) return null;
374
+ }
375
+ const player = await room._addPlayer(playerId, conn);
376
+ if (!player) return null;
377
+ this._playerToRoom.set(playerId, room.id);
378
+ console.log(`[Room] Player ${playerId} joined ${room.id}`);
379
+ return {
380
+ room,
381
+ player
382
+ };
383
+ }
384
+ /**
385
+ * @zh 加入指定房间
386
+ * @en Join specific room
387
+ */
388
+ async joinById(roomId, playerId, conn) {
389
+ const room = this._rooms.get(roomId);
390
+ if (!room) return null;
391
+ const player = await room._addPlayer(playerId, conn);
392
+ if (!player) return null;
393
+ this._playerToRoom.set(playerId, room.id);
394
+ console.log(`[Room] Player ${playerId} joined ${room.id}`);
395
+ return {
396
+ room,
397
+ player
398
+ };
399
+ }
400
+ /**
401
+ * @zh 玩家离开
402
+ * @en Player leave
403
+ */
404
+ async leave(playerId, reason) {
405
+ const roomId = this._playerToRoom.get(playerId);
406
+ if (!roomId) return;
407
+ const room = this._rooms.get(roomId);
408
+ if (room) {
409
+ await room._removePlayer(playerId, reason);
410
+ }
411
+ this._playerToRoom.delete(playerId);
412
+ console.log(`[Room] Player ${playerId} left ${roomId}`);
413
+ }
414
+ /**
415
+ * @zh 处理消息
416
+ * @en Handle message
417
+ */
418
+ handleMessage(playerId, type, data) {
419
+ const roomId = this._playerToRoom.get(playerId);
420
+ if (!roomId) return;
421
+ const room = this._rooms.get(roomId);
422
+ if (room) {
423
+ room._handleMessage(type, data, playerId);
424
+ }
425
+ }
426
+ /**
427
+ * @zh 获取房间
428
+ * @en Get room
429
+ */
430
+ getRoom(roomId) {
431
+ return this._rooms.get(roomId);
432
+ }
433
+ /**
434
+ * @zh 获取玩家所在房间
435
+ * @en Get player's room
436
+ */
437
+ getPlayerRoom(playerId) {
438
+ const roomId = this._playerToRoom.get(playerId);
439
+ return roomId ? this._rooms.get(roomId) : void 0;
440
+ }
441
+ /**
442
+ * @zh 获取所有房间
443
+ * @en Get all rooms
444
+ */
445
+ getRooms() {
446
+ return Array.from(this._rooms.values());
447
+ }
448
+ /**
449
+ * @zh 获取指定类型的所有房间
450
+ * @en Get all rooms of a type
451
+ */
452
+ getRoomsByType(name) {
453
+ const def = this._definitions.get(name);
454
+ if (!def) return [];
455
+ return Array.from(this._rooms.values()).filter((room) => room instanceof def.roomClass);
456
+ }
457
+ _findAvailableRoom(name) {
458
+ const def = this._definitions.get(name);
459
+ if (!def) return null;
460
+ for (const room of this._rooms.values()) {
461
+ if (room instanceof def.roomClass && !room.isFull && !room.isLocked && !room.isDisposed) {
462
+ return room;
463
+ }
464
+ }
465
+ return null;
466
+ }
467
+ _generateRoomId() {
468
+ return `room_${this._nextRoomId++}`;
469
+ }
470
+ };
471
+ __name(_RoomManager, "RoomManager");
472
+ var RoomManager = _RoomManager;
473
+
474
+ // src/core/server.ts
475
+ var DEFAULT_CONFIG = {
476
+ port: 3e3,
477
+ apiDir: "src/api",
478
+ msgDir: "src/msg",
479
+ httpDir: "src/http",
480
+ httpPrefix: "/api",
481
+ tickRate: 20
482
+ };
483
+ async function createServer(config = {}) {
484
+ const opts = {
485
+ ...DEFAULT_CONFIG,
486
+ ...config
487
+ };
488
+ const cwd = process.cwd();
489
+ const apiHandlers = await loadApiHandlers(path.resolve(cwd, opts.apiDir));
490
+ const msgHandlers = await loadMsgHandlers(path.resolve(cwd, opts.msgDir));
491
+ const httpDir = config.httpDir ?? opts.httpDir;
492
+ const httpPrefix = config.httpPrefix ?? opts.httpPrefix;
493
+ const httpHandlers = await loadHttpHandlers(path.resolve(cwd, httpDir), httpPrefix);
494
+ if (apiHandlers.length > 0) {
495
+ console.log(`[Server] Loaded ${apiHandlers.length} API handlers`);
496
+ }
497
+ if (msgHandlers.length > 0) {
498
+ console.log(`[Server] Loaded ${msgHandlers.length} message handlers`);
499
+ }
500
+ if (httpHandlers.length > 0) {
501
+ console.log(`[Server] Loaded ${httpHandlers.length} HTTP handlers`);
502
+ }
503
+ const mergedHttpRoutes = {};
504
+ for (const handler of httpHandlers) {
505
+ const existingRoute = mergedHttpRoutes[handler.route];
506
+ if (existingRoute && typeof existingRoute !== "function") {
507
+ existingRoute[handler.method] = handler.definition.handler;
508
+ } else {
509
+ mergedHttpRoutes[handler.route] = {
510
+ [handler.method]: handler.definition.handler
511
+ };
512
+ }
513
+ }
514
+ if (config.http) {
515
+ for (const [route, handlerOrMethods] of Object.entries(config.http)) {
516
+ if (typeof handlerOrMethods === "function") {
517
+ mergedHttpRoutes[route] = handlerOrMethods;
518
+ } else {
519
+ const existing = mergedHttpRoutes[route];
520
+ if (existing && typeof existing !== "function") {
521
+ Object.assign(existing, handlerOrMethods);
522
+ } else {
523
+ mergedHttpRoutes[route] = handlerOrMethods;
524
+ }
525
+ }
526
+ }
527
+ }
528
+ const hasHttpRoutes = Object.keys(mergedHttpRoutes).length > 0;
529
+ const apiDefs = {
530
+ // 内置 API
531
+ JoinRoom: rpc.api(),
532
+ LeaveRoom: rpc.api()
533
+ };
534
+ const msgDefs = {
535
+ // 内置消息(房间消息透传)
536
+ RoomMessage: rpc.msg()
537
+ };
538
+ for (const handler of apiHandlers) {
539
+ apiDefs[handler.name] = rpc.api();
540
+ }
541
+ for (const handler of msgHandlers) {
542
+ msgDefs[handler.name] = rpc.msg();
543
+ }
544
+ const protocol = rpc.define({
545
+ api: apiDefs,
546
+ msg: msgDefs
547
+ });
548
+ let currentTick = 0;
549
+ let tickInterval = null;
550
+ let rpcServer = null;
551
+ let httpServer = null;
552
+ const roomManager = new RoomManager((conn, type, data) => {
553
+ rpcServer?.send(conn, "RoomMessage", {
554
+ type,
555
+ data
556
+ });
557
+ });
558
+ const apiMap = {};
559
+ for (const handler of apiHandlers) {
560
+ apiMap[handler.name] = handler;
561
+ }
562
+ const msgMap = {};
563
+ for (const handler of msgHandlers) {
564
+ msgMap[handler.name] = handler;
565
+ }
566
+ const gameServer = {
567
+ get connections() {
568
+ return rpcServer?.connections ?? [];
569
+ },
570
+ get tick() {
571
+ return currentTick;
572
+ },
573
+ get rooms() {
574
+ return roomManager;
575
+ },
576
+ /**
577
+ * @zh 注册房间类型
578
+ * @en Define room type
579
+ */
580
+ define(name, roomClass) {
581
+ roomManager.define(name, roomClass);
582
+ },
583
+ async start() {
584
+ const apiHandlersObj = {};
585
+ apiHandlersObj["JoinRoom"] = async (input, conn) => {
586
+ const { roomType, roomId, options } = input;
587
+ if (roomId) {
588
+ const result = await roomManager.joinById(roomId, conn.id, conn);
589
+ if (!result) {
590
+ throw new Error("Failed to join room");
591
+ }
592
+ return {
593
+ roomId: result.room.id,
594
+ playerId: result.player.id
595
+ };
596
+ }
597
+ if (roomType) {
598
+ const result = await roomManager.joinOrCreate(roomType, conn.id, conn, options);
599
+ if (!result) {
600
+ throw new Error("Failed to join or create room");
601
+ }
602
+ return {
603
+ roomId: result.room.id,
604
+ playerId: result.player.id
605
+ };
606
+ }
607
+ throw new Error("roomType or roomId required");
608
+ };
609
+ apiHandlersObj["LeaveRoom"] = async (_input, conn) => {
610
+ await roomManager.leave(conn.id);
611
+ return {
612
+ success: true
613
+ };
614
+ };
615
+ for (const [name, handler] of Object.entries(apiMap)) {
616
+ apiHandlersObj[name] = async (input, conn) => {
617
+ const ctx = {
618
+ conn,
619
+ server: gameServer
620
+ };
621
+ return handler.definition.handler(input, ctx);
622
+ };
623
+ }
624
+ const msgHandlersObj = {};
625
+ msgHandlersObj["RoomMessage"] = async (data, conn) => {
626
+ const { type, data: payload } = data;
627
+ roomManager.handleMessage(conn.id, type, payload);
628
+ };
629
+ for (const [name, handler] of Object.entries(msgMap)) {
630
+ msgHandlersObj[name] = async (data, conn) => {
631
+ const ctx = {
632
+ conn,
633
+ server: gameServer
634
+ };
635
+ await handler.definition.handler(data, ctx);
636
+ };
637
+ }
638
+ if (hasHttpRoutes) {
639
+ const httpRouter = createHttpRouter(mergedHttpRoutes, config.cors ?? true);
640
+ httpServer = createServer$1(async (req, res) => {
641
+ const handled = await httpRouter(req, res);
642
+ if (!handled) {
643
+ res.statusCode = 404;
644
+ res.setHeader("Content-Type", "application/json");
645
+ res.end(JSON.stringify({
646
+ error: "Not Found"
647
+ }));
648
+ }
649
+ });
650
+ rpcServer = serve(protocol, {
651
+ server: httpServer,
652
+ createConnData: /* @__PURE__ */ __name(() => ({}), "createConnData"),
653
+ onStart: /* @__PURE__ */ __name(() => {
654
+ console.log(`[Server] Started on http://localhost:${opts.port}`);
655
+ opts.onStart?.(opts.port);
656
+ }, "onStart"),
657
+ onConnect: /* @__PURE__ */ __name(async (conn) => {
658
+ await config.onConnect?.(conn);
659
+ }, "onConnect"),
660
+ onDisconnect: /* @__PURE__ */ __name(async (conn) => {
661
+ await roomManager?.leave(conn.id, "disconnected");
662
+ await config.onDisconnect?.(conn);
663
+ }, "onDisconnect"),
664
+ api: apiHandlersObj,
665
+ msg: msgHandlersObj
666
+ });
667
+ await rpcServer.start();
668
+ await new Promise((resolve2) => {
669
+ httpServer.listen(opts.port, () => resolve2());
670
+ });
671
+ } else {
672
+ rpcServer = serve(protocol, {
673
+ port: opts.port,
674
+ createConnData: /* @__PURE__ */ __name(() => ({}), "createConnData"),
675
+ onStart: /* @__PURE__ */ __name((p) => {
676
+ console.log(`[Server] Started on ws://localhost:${p}`);
677
+ opts.onStart?.(p);
678
+ }, "onStart"),
679
+ onConnect: /* @__PURE__ */ __name(async (conn) => {
680
+ await config.onConnect?.(conn);
681
+ }, "onConnect"),
682
+ onDisconnect: /* @__PURE__ */ __name(async (conn) => {
683
+ await roomManager?.leave(conn.id, "disconnected");
684
+ await config.onDisconnect?.(conn);
685
+ }, "onDisconnect"),
686
+ api: apiHandlersObj,
687
+ msg: msgHandlersObj
688
+ });
689
+ await rpcServer.start();
690
+ }
691
+ if (opts.tickRate > 0) {
692
+ tickInterval = setInterval(() => {
693
+ currentTick++;
694
+ }, 1e3 / opts.tickRate);
695
+ }
696
+ },
697
+ async stop() {
698
+ if (tickInterval) {
699
+ clearInterval(tickInterval);
700
+ tickInterval = null;
701
+ }
702
+ if (rpcServer) {
703
+ await rpcServer.stop();
704
+ rpcServer = null;
705
+ }
706
+ if (httpServer) {
707
+ await new Promise((resolve2, reject) => {
708
+ httpServer.close((err) => {
709
+ if (err) reject(err);
710
+ else resolve2();
711
+ });
712
+ });
713
+ httpServer = null;
714
+ }
715
+ },
716
+ broadcast(name, data) {
717
+ rpcServer?.broadcast(name, data);
718
+ },
719
+ send(conn, name, data) {
720
+ rpcServer?.send(conn, name, data);
721
+ }
722
+ };
723
+ return gameServer;
724
+ }
725
+ __name(createServer, "createServer");
726
+
727
+ export { createHttpRouter, createServer };
728
+ //# sourceMappingURL=chunk-BIAOJF7P.js.map
729
+ //# sourceMappingURL=chunk-BIAOJF7P.js.map