@colyseus/core 0.16.0-preview.9 → 0.16.1

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.
Files changed (146) hide show
  1. package/README.md +5 -5
  2. package/build/Debug.js +6 -2
  3. package/build/Debug.js.map +2 -2
  4. package/build/Debug.mjs +11 -10
  5. package/build/Debug.mjs.map +2 -2
  6. package/build/IPC.d.ts +1 -1
  7. package/build/IPC.js +3 -3
  8. package/build/IPC.js.map +2 -2
  9. package/build/IPC.mjs +4 -3
  10. package/build/IPC.mjs.map +2 -2
  11. package/build/Logger.mjs +4 -3
  12. package/build/Logger.mjs.map +1 -1
  13. package/build/MatchMaker.d.ts +35 -30
  14. package/build/MatchMaker.js +150 -100
  15. package/build/MatchMaker.js.map +2 -2
  16. package/build/MatchMaker.mjs +154 -107
  17. package/build/MatchMaker.mjs.map +2 -2
  18. package/build/Protocol.d.ts +3 -4
  19. package/build/Protocol.js +33 -19
  20. package/build/Protocol.js.map +2 -2
  21. package/build/Protocol.mjs +36 -21
  22. package/build/Protocol.mjs.map +2 -2
  23. package/build/Room.d.ts +64 -40
  24. package/build/Room.js +408 -151
  25. package/build/Room.js.map +2 -2
  26. package/build/Room.mjs +412 -158
  27. package/build/Room.mjs.map +2 -2
  28. package/build/Server.d.ts +8 -7
  29. package/build/Server.js +51 -18
  30. package/build/Server.js.map +2 -2
  31. package/build/Server.mjs +51 -21
  32. package/build/Server.mjs.map +3 -3
  33. package/build/Stats.d.ts +2 -0
  34. package/build/Stats.js +38 -3
  35. package/build/Stats.js.map +2 -2
  36. package/build/Stats.mjs +30 -6
  37. package/build/Stats.mjs.map +2 -2
  38. package/build/Transport.d.ts +8 -7
  39. package/build/Transport.js +1 -1
  40. package/build/Transport.js.map +2 -2
  41. package/build/Transport.mjs +6 -5
  42. package/build/Transport.mjs.map +2 -2
  43. package/build/discovery/index.d.ts +1 -1
  44. package/build/discovery/index.js.map +2 -2
  45. package/build/discovery/index.mjs +3 -2
  46. package/build/discovery/index.mjs.map +2 -2
  47. package/build/errors/RoomExceptions.d.ts +39 -0
  48. package/build/errors/RoomExceptions.js +100 -0
  49. package/build/errors/RoomExceptions.js.map +7 -0
  50. package/build/errors/RoomExceptions.mjs +71 -0
  51. package/build/errors/RoomExceptions.mjs.map +7 -0
  52. package/build/errors/SeatReservationError.mjs +3 -2
  53. package/build/errors/SeatReservationError.mjs.map +1 -1
  54. package/build/errors/ServerError.js +1 -1
  55. package/build/errors/ServerError.js.map +1 -1
  56. package/build/errors/ServerError.mjs +5 -4
  57. package/build/errors/ServerError.mjs.map +2 -2
  58. package/build/index.d.ts +21 -19
  59. package/build/index.js +45 -20
  60. package/build/index.js.map +2 -2
  61. package/build/index.mjs +39 -19
  62. package/build/index.mjs.map +2 -2
  63. package/build/matchmaker/Lobby.d.ts +3 -3
  64. package/build/matchmaker/Lobby.js +6 -3
  65. package/build/matchmaker/Lobby.js.map +2 -2
  66. package/build/matchmaker/Lobby.mjs +4 -4
  67. package/build/matchmaker/Lobby.mjs.map +2 -2
  68. package/build/matchmaker/RegisteredHandler.d.ts +6 -7
  69. package/build/matchmaker/RegisteredHandler.js +7 -10
  70. package/build/matchmaker/RegisteredHandler.js.map +2 -2
  71. package/build/matchmaker/RegisteredHandler.mjs +11 -13
  72. package/build/matchmaker/RegisteredHandler.mjs.map +2 -2
  73. package/build/matchmaker/controller.d.ts +4 -5
  74. package/build/matchmaker/controller.js +22 -15
  75. package/build/matchmaker/controller.js.map +2 -2
  76. package/build/matchmaker/controller.mjs +19 -13
  77. package/build/matchmaker/controller.mjs.map +2 -2
  78. package/build/matchmaker/driver/api.d.ts +104 -0
  79. package/build/matchmaker/driver/api.js +29 -0
  80. package/build/matchmaker/driver/api.js.map +7 -0
  81. package/build/matchmaker/driver/api.mjs +7 -0
  82. package/build/matchmaker/driver/api.mjs.map +7 -0
  83. package/build/matchmaker/driver/index.d.ts +2 -2
  84. package/build/matchmaker/driver/index.js +2 -2
  85. package/build/matchmaker/driver/index.js.map +2 -2
  86. package/build/matchmaker/driver/index.mjs +5 -4
  87. package/build/matchmaker/driver/index.mjs.map +2 -2
  88. package/build/matchmaker/driver/local/LocalDriver.d.ts +13 -0
  89. package/build/matchmaker/driver/local/LocalDriver.js +65 -0
  90. package/build/matchmaker/driver/local/LocalDriver.js.map +7 -0
  91. package/build/matchmaker/driver/local/LocalDriver.mjs +43 -0
  92. package/build/matchmaker/driver/local/LocalDriver.mjs.map +7 -0
  93. package/build/matchmaker/driver/local/Query.d.ts +9 -0
  94. package/build/matchmaker/driver/local/Query.js +78 -0
  95. package/build/matchmaker/driver/local/Query.js.map +7 -0
  96. package/build/matchmaker/driver/local/Query.mjs +56 -0
  97. package/build/matchmaker/driver/local/Query.mjs.map +7 -0
  98. package/build/matchmaker/driver/local/RoomData.d.ts +19 -0
  99. package/build/matchmaker/driver/local/RoomData.js +79 -0
  100. package/build/matchmaker/driver/local/RoomData.js.map +7 -0
  101. package/build/matchmaker/driver/local/RoomData.mjs +57 -0
  102. package/build/matchmaker/driver/local/RoomData.mjs.map +7 -0
  103. package/build/presence/LocalPresence.d.ts +10 -6
  104. package/build/presence/LocalPresence.js +85 -24
  105. package/build/presence/LocalPresence.js.map +3 -3
  106. package/build/presence/LocalPresence.mjs +85 -27
  107. package/build/presence/LocalPresence.mjs.map +3 -3
  108. package/build/presence/Presence.d.ts +38 -2
  109. package/build/presence/Presence.js.map +1 -1
  110. package/build/rooms/LobbyRoom.d.ts +6 -6
  111. package/build/rooms/LobbyRoom.js +8 -3
  112. package/build/rooms/LobbyRoom.js.map +2 -2
  113. package/build/rooms/LobbyRoom.mjs +7 -5
  114. package/build/rooms/LobbyRoom.mjs.map +2 -2
  115. package/build/rooms/RelayRoom.d.ts +3 -3
  116. package/build/rooms/RelayRoom.js +3 -1
  117. package/build/rooms/RelayRoom.js.map +2 -2
  118. package/build/rooms/RelayRoom.mjs +10 -7
  119. package/build/rooms/RelayRoom.mjs.map +2 -2
  120. package/build/serializer/NoneSerializer.d.ts +2 -2
  121. package/build/serializer/NoneSerializer.js.map +1 -1
  122. package/build/serializer/NoneSerializer.mjs +3 -2
  123. package/build/serializer/NoneSerializer.mjs.map +2 -2
  124. package/build/serializer/SchemaSerializer.d.ts +16 -15
  125. package/build/serializer/SchemaSerializer.js +12 -10
  126. package/build/serializer/SchemaSerializer.js.map +2 -2
  127. package/build/serializer/SchemaSerializer.mjs +16 -13
  128. package/build/serializer/SchemaSerializer.mjs.map +2 -2
  129. package/build/serializer/SchemaSerializerDebug.d.ts +7 -0
  130. package/build/serializer/SchemaSerializerDebug.js +0 -0
  131. package/build/serializer/SchemaSerializerDebug.js.map +7 -0
  132. package/build/serializer/SchemaSerializerDebug.mjs +0 -0
  133. package/build/serializer/SchemaSerializerDebug.mjs.map +7 -0
  134. package/build/serializer/Serializer.d.ts +1 -2
  135. package/build/serializer/Serializer.js.map +1 -1
  136. package/build/utils/DevMode.d.ts +2 -2
  137. package/build/utils/DevMode.js +8 -4
  138. package/build/utils/DevMode.js.map +2 -2
  139. package/build/utils/DevMode.mjs +7 -6
  140. package/build/utils/DevMode.mjs.map +2 -2
  141. package/build/utils/Utils.d.ts +8 -3
  142. package/build/utils/Utils.js +41 -17
  143. package/build/utils/Utils.js.map +2 -2
  144. package/build/utils/Utils.mjs +40 -21
  145. package/build/utils/Utils.mjs.map +2 -2
  146. package/package.json +17 -6
@@ -1,39 +1,45 @@
1
- import { ErrorCode, Protocol } from "./Protocol";
2
- import { requestFromIPC, subscribeIPC } from "./IPC";
3
- import { Deferred, generateId, merge, REMOTE_ROOM_SHORT_TIMEOUT, retry } from "./utils/Utils";
4
- import { isDevMode, cacheRoomHistory, getPreviousProcessId, getRoomRestoreListKey, reloadFromCache } from "./utils/DevMode";
5
- import { RegisteredHandler } from "./matchmaker/RegisteredHandler";
6
- import { Room, RoomInternalState } from "./Room";
7
- import { LocalPresence } from "./presence/LocalPresence";
8
- import { debugAndPrintError, debugMatchMaking } from "./Debug";
9
- import { SeatReservationError } from "./errors/SeatReservationError";
10
- import { ServerError } from "./errors/ServerError";
11
- import { LocalDriver } from "./matchmaker/driver";
12
- import controller from "./matchmaker/controller";
13
- import * as stats from "./Stats";
14
- import { logger } from "./Logger";
15
- import { getHostname } from "./discovery";
16
- const handlers = {};
17
- const rooms = {};
18
- let publicAddress;
19
- let processId;
20
- let presence;
21
- let driver;
22
- let selectProcessIdToCreateRoom;
23
- let isGracefullyShuttingDown;
24
- let onReady = new Deferred();
1
+ // packages/core/src/MatchMaker.ts
2
+ import { EventEmitter } from "events";
3
+ import { ErrorCode, Protocol } from "./Protocol.mjs";
4
+ import { requestFromIPC, subscribeIPC } from "./IPC.mjs";
5
+ import { Deferred, generateId, merge, retry, MAX_CONCURRENT_CREATE_ROOM_WAIT_TIME, REMOTE_ROOM_SHORT_TIMEOUT } from "./utils/Utils.mjs";
6
+ import { isDevMode, cacheRoomHistory, getPreviousProcessId, getRoomRestoreListKey, reloadFromCache } from "./utils/DevMode.mjs";
7
+ import { RegisteredHandler } from "./matchmaker/RegisteredHandler.mjs";
8
+ import { Room, RoomInternalState } from "./Room.mjs";
9
+ import { LocalPresence } from "./presence/LocalPresence.mjs";
10
+ import { debugAndPrintError, debugMatchMaking } from "./Debug.mjs";
11
+ import { SeatReservationError } from "./errors/SeatReservationError.mjs";
12
+ import { ServerError } from "./errors/ServerError.mjs";
13
+ import { LocalDriver } from "./matchmaker/driver/local/LocalDriver.mjs";
14
+ import controller from "./matchmaker/controller.mjs";
15
+ import * as stats from "./Stats.mjs";
16
+ import { logger } from "./Logger.mjs";
17
+ import { getHostname } from "./discovery/index.mjs";
18
+ import { getLockId } from "./matchmaker/driver/api.mjs";
19
+ var handlers = {};
20
+ var rooms = {};
21
+ var events = new EventEmitter();
22
+ var publicAddress;
23
+ var processId;
24
+ var presence;
25
+ var driver;
26
+ var selectProcessIdToCreateRoom;
27
+ var enableHealthChecks = true;
28
+ function setHealthChecksEnabled(value) {
29
+ enableHealthChecks = value;
30
+ }
31
+ var onReady = new Deferred();
25
32
  var MatchMakerState = /* @__PURE__ */ ((MatchMakerState2) => {
26
33
  MatchMakerState2[MatchMakerState2["INITIALIZING"] = 0] = "INITIALIZING";
27
34
  MatchMakerState2[MatchMakerState2["READY"] = 1] = "READY";
28
35
  MatchMakerState2[MatchMakerState2["SHUTTING_DOWN"] = 2] = "SHUTTING_DOWN";
29
36
  return MatchMakerState2;
30
37
  })(MatchMakerState || {});
31
- let state;
38
+ var state;
32
39
  async function setup(_presence, _driver, _publicAddress, _selectProcessIdToCreateRoom) {
33
40
  if (onReady === void 0) {
34
41
  onReady = new Deferred();
35
42
  }
36
- isGracefullyShuttingDown = false;
37
43
  state = 0 /* INITIALIZING */;
38
44
  presence = _presence || new LocalPresence();
39
45
  driver = _driver || new LocalDriver();
@@ -59,25 +65,44 @@ async function accept() {
59
65
  return handleCreateRoom.apply(void 0, args);
60
66
  }
61
67
  });
62
- await healthCheckAllProcesses();
68
+ if (enableHealthChecks) {
69
+ await healthCheckAllProcesses();
70
+ stats.setAutoPersistInterval();
71
+ }
63
72
  state = 1 /* READY */;
64
73
  await stats.persist();
65
74
  if (isDevMode) {
66
75
  await reloadFromCache();
67
76
  }
68
77
  }
69
- async function joinOrCreate(roomName, clientOptions = {}, authOptions) {
78
+ async function joinOrCreate(roomName, clientOptions = {}, authContext) {
70
79
  return await retry(async () => {
71
- const authData = await callOnAuth(roomName, authOptions);
80
+ const authData = await callOnAuth(roomName, clientOptions, authContext);
72
81
  let room = await findOneRoomAvailable(roomName, clientOptions);
73
82
  if (!room) {
74
- room = await createRoom(roomName, clientOptions);
83
+ const handler = getHandler(roomName);
84
+ const filterOptions = handler.getFilterOptions(clientOptions);
85
+ const concurrencyKey = getLockId(filterOptions);
86
+ await concurrentJoinOrCreateRoomLock(handler, concurrencyKey, async (roomId) => {
87
+ if (roomId) {
88
+ room = await driver.findOne({ roomId });
89
+ }
90
+ if (!room) {
91
+ room = await findOneRoomAvailable(roomName, clientOptions);
92
+ }
93
+ if (!room) {
94
+ room = await createRoom(roomName, clientOptions);
95
+ presence.lpush(`l:${handler.name}:${concurrencyKey}`, room.roomId);
96
+ presence.expire(`l:${handler.name}:${concurrencyKey}`, MAX_CONCURRENT_CREATE_ROOM_WAIT_TIME * 2);
97
+ }
98
+ return room;
99
+ });
75
100
  }
76
101
  return await reserveSeatFor(room, clientOptions, authData);
77
102
  }, 5, [SeatReservationError]);
78
103
  }
79
- async function create(roomName, clientOptions = {}, authOptions) {
80
- const authData = await callOnAuth(roomName, authOptions);
104
+ async function create(roomName, clientOptions = {}, authContext) {
105
+ const authData = await callOnAuth(roomName, clientOptions, authContext);
81
106
  const room = await createRoom(roomName, clientOptions);
82
107
  return reserveSeatFor(room, clientOptions, authData);
83
108
  }
@@ -125,23 +150,21 @@ async function joinById(roomId, clientOptions = {}, authOptions) {
125
150
  const authData = await callOnAuth(room.name, authOptions);
126
151
  return reserveSeatFor(room, clientOptions, authData);
127
152
  }
128
- async function query(conditions = {}) {
129
- return await driver.find(conditions);
130
- }
131
- async function findOneRoomAvailable(roomName, clientOptions) {
132
- return await awaitRoomAvailable(roomName, async () => {
133
- const handler = getHandler(roomName);
134
- const roomQuery = driver.findOne({
135
- locked: false,
136
- name: roomName,
137
- private: false,
138
- ...handler.getFilterOptions(clientOptions)
139
- });
140
- if (handler.sortOptions) {
141
- roomQuery.sort(handler.sortOptions);
142
- }
143
- return await roomQuery;
144
- });
153
+ async function query(conditions = {}, sortOptions) {
154
+ return await driver.query(conditions, sortOptions);
155
+ }
156
+ async function findOneRoomAvailable(roomName, filterOptions, additionalSortOptions) {
157
+ const handler = getHandler(roomName);
158
+ const sortOptions = Object.assign({}, handler.sortOptions ?? {});
159
+ if (additionalSortOptions) {
160
+ Object.assign(sortOptions, additionalSortOptions);
161
+ }
162
+ return await driver.findOne({
163
+ locked: false,
164
+ name: roomName,
165
+ private: false,
166
+ ...handler.getFilterOptions(filterOptions)
167
+ }, sortOptions);
145
168
  }
146
169
  async function remoteRoomCall(roomId, method, args, rejectionTimeout = REMOTE_ROOM_SHORT_TIMEOUT) {
147
170
  const room = rooms[roomId];
@@ -163,23 +186,17 @@ async function remoteRoomCall(roomId, method, args, rejectionTimeout = REMOTE_RO
163
186
  }
164
187
  }
165
188
  function defineRoomType(roomName, klass, defaultOptions) {
166
- const registeredHandler = new RegisteredHandler(klass, defaultOptions);
189
+ const registeredHandler = new RegisteredHandler(roomName, klass, defaultOptions);
167
190
  handlers[roomName] = registeredHandler;
168
191
  if (klass.prototype["onAuth"] !== Room.prototype["onAuth"]) {
169
192
  if (klass["onAuth"] !== Room["onAuth"]) {
170
193
  logger.info(`\u274C "${roomName}"'s onAuth() defined at the instance level will be ignored.`);
171
194
  }
172
195
  }
173
- if (!isDevMode) {
174
- cleanupStaleRooms(roomName);
175
- }
176
196
  return registeredHandler;
177
197
  }
178
198
  function removeRoomType(roomName) {
179
199
  delete handlers[roomName];
180
- if (!isDevMode) {
181
- cleanupStaleRooms(roomName);
182
- }
183
200
  }
184
201
  function hasHandler(roomName) {
185
202
  logger.warn("hasHandler() is deprecated. Use getHandler() instead.");
@@ -214,7 +231,9 @@ async function createRoom(roomName, clientOptions) {
214
231
  } catch (e) {
215
232
  if (e.message === "ipc_timeout") {
216
233
  debugAndPrintError(`${e.message}: create room request timed out for ${roomName} on processId ${selectedProcessId}.`);
217
- await stats.excludeProcess(selectedProcessId);
234
+ if (enableHealthChecks) {
235
+ await stats.excludeProcess(selectedProcessId);
236
+ }
218
237
  room = await handleCreateRoom(roomName, clientOptions);
219
238
  } else {
220
239
  throw e;
@@ -238,9 +257,7 @@ async function handleCreateRoom(roomName, clientOptions, restoringRoomId) {
238
257
  } else {
239
258
  room.roomId = generateId();
240
259
  }
241
- if (room.state) {
242
- room.setState(room.state);
243
- }
260
+ room["__init"]();
244
261
  room.roomName = roomName;
245
262
  room.presence = presence;
246
263
  const additionalListingData = handler.getFilterOptions(clientOptions);
@@ -280,13 +297,21 @@ async function handleCreateRoom(roomName, clientOptions, restoringRoomId) {
280
297
  room._events.removeAllListeners("unlock");
281
298
  room._events.removeAllListeners("visibility-change");
282
299
  room._events.removeAllListeners("dispose");
300
+ if (stats.local.roomCount <= 0) {
301
+ events.emit("no-active-rooms");
302
+ }
283
303
  });
284
304
  await createRoomReferences(room, true);
285
- await room.listing.save();
305
+ if (state !== 2 /* SHUTTING_DOWN */) {
306
+ await room.listing.save();
307
+ }
286
308
  handler.emit("create", room);
287
309
  return room.listing;
288
310
  }
289
311
  function getRoomById(roomId) {
312
+ return driver.findOne({ roomId });
313
+ }
314
+ function getLocalRoomById(roomId) {
290
315
  return rooms[roomId];
291
316
  }
292
317
  function disconnectAll(closeCode) {
@@ -295,24 +320,42 @@ function disconnectAll(closeCode) {
295
320
  if (!rooms.hasOwnProperty(roomId)) {
296
321
  continue;
297
322
  }
298
- const room = rooms[roomId];
299
- room._events.removeAllListeners("leave");
300
- promises.push(room.disconnect(closeCode));
323
+ promises.push(rooms[roomId].disconnect(closeCode));
301
324
  }
302
325
  return promises;
303
326
  }
327
+ async function lockAndDisposeAll() {
328
+ await stats.excludeProcess(processId);
329
+ if (enableHealthChecks) {
330
+ stats.clearAutoPersistInterval();
331
+ }
332
+ const noActiveRooms = new Deferred();
333
+ if (stats.local.roomCount <= 0) {
334
+ noActiveRooms.resolve();
335
+ } else {
336
+ events.once("no-active-rooms", () => noActiveRooms.resolve());
337
+ }
338
+ for (const roomId in rooms) {
339
+ if (!rooms.hasOwnProperty(roomId)) {
340
+ continue;
341
+ }
342
+ const room = rooms[roomId];
343
+ room.lock();
344
+ room.onBeforeShutdown();
345
+ }
346
+ await noActiveRooms;
347
+ }
304
348
  async function gracefullyShutdown() {
305
- if (isGracefullyShuttingDown) {
349
+ if (state === 2 /* SHUTTING_DOWN */) {
306
350
  return Promise.reject("already_shutting_down");
307
351
  }
308
- isGracefullyShuttingDown = true;
352
+ debugMatchMaking(`${processId} is shutting down!`);
309
353
  state = 2 /* SHUTTING_DOWN */;
310
354
  onReady = void 0;
311
- debugMatchMaking(`${processId} is shutting down!`);
355
+ await lockAndDisposeAll();
312
356
  if (isDevMode) {
313
357
  await cacheRoomHistory(rooms);
314
358
  }
315
- await stats.excludeProcess(processId);
316
359
  await removeRoomsByProcessId(processId);
317
360
  presence.unsubscribe(getProcessChannel());
318
361
  return Promise.all(disconnectAll(
@@ -337,7 +380,7 @@ async function reserveSeatFor(room, options, authData) {
337
380
  );
338
381
  } catch (e) {
339
382
  debugMatchMaking(e);
340
- if (e.message === "ipc_timeout" && !await healthCheckProcessId(room.processId)) {
383
+ if (e.message === "ipc_timeout" && !(enableHealthChecks && await healthCheckProcessId(room.processId))) {
341
384
  throw new SeatReservationError(`process ${room.processId} is not available.`);
342
385
  } else {
343
386
  successfulSeatReservation = false;
@@ -352,12 +395,9 @@ async function reserveSeatFor(room, options, authData) {
352
395
  }
353
396
  return response;
354
397
  }
355
- function callOnAuth(roomName, authOptions) {
398
+ function callOnAuth(roomName, clientOptions, authContext) {
356
399
  const roomClass = getRoomClass(roomName);
357
- return roomClass && roomClass["onAuth"] && roomClass["onAuth"] !== Room["onAuth"] ? roomClass["onAuth"](authOptions.token, authOptions.request) : void 0;
358
- }
359
- async function cleanupStaleRooms(roomName) {
360
- await presence.del(getHandlerConcurrencyKey(roomName));
400
+ return roomClass && roomClass["onAuth"] && roomClass["onAuth"] !== Room["onAuth"] ? roomClass["onAuth"](authContext.token, clientOptions, authContext) : void 0;
361
401
  }
362
402
  async function healthCheckAllProcesses() {
363
403
  const allStats = await stats.fetchAll();
@@ -367,7 +407,7 @@ async function healthCheckAllProcesses() {
367
407
  );
368
408
  }
369
409
  }
370
- const _healthCheckByProcessId = {};
410
+ var _healthCheckByProcessId = {};
371
411
  function healthCheckProcessId(processId2) {
372
412
  if (_healthCheckByProcessId[processId2] !== void 0) {
373
413
  return _healthCheckByProcessId[processId2];
@@ -399,13 +439,7 @@ function healthCheckProcessId(processId2) {
399
439
  return _healthCheckByProcessId[processId2];
400
440
  }
401
441
  async function removeRoomsByProcessId(processId2) {
402
- if (typeof driver.cleanup === "function") {
403
- await driver.cleanup(processId2);
404
- } else {
405
- const cachedRooms = await driver.find({ processId: processId2 }, { _id: 1 });
406
- logger.debug("> Removing stale rooms by processId:", processId2, `(${cachedRooms.length} rooms found)`);
407
- cachedRooms.forEach((room) => room.remove());
408
- }
442
+ await driver.cleanup(processId2);
409
443
  }
410
444
  async function createRoomReferences(room, init = false) {
411
445
  rooms[room.roomId] = room;
@@ -421,29 +455,42 @@ async function createRoomReferences(room, init = false) {
421
455
  }
422
456
  return true;
423
457
  }
424
- async function awaitRoomAvailable(roomToJoin, callback) {
458
+ async function concurrentJoinOrCreateRoomLock(handler, concurrencyKey, callback) {
425
459
  return new Promise(async (resolve, reject) => {
426
- const concurrencyKey = getHandlerConcurrencyKey(roomToJoin);
427
- const concurrency = await presence.incr(concurrencyKey) - 1;
428
- const concurrencyTimeout = Math.min(concurrency * 100, 500);
429
- if (concurrency > 0) {
430
- debugMatchMaking(
431
- "receiving %d concurrent requests for joining '%s' (waiting %d ms)",
432
- concurrency,
433
- roomToJoin,
434
- concurrencyTimeout
435
- );
436
- }
437
- setTimeout(async () => {
460
+ const hkey = getConcurrencyHashKey(handler.name);
461
+ const concurrency = await presence.hincrbyex(
462
+ hkey,
463
+ concurrencyKey,
464
+ 1,
465
+ // increment by 1
466
+ MAX_CONCURRENT_CREATE_ROOM_WAIT_TIME * 2
467
+ // expire in 2x the time of MAX_CONCURRENT_CREATE_ROOM_WAIT_TIME
468
+ ) - 1;
469
+ const fulfill = async (roomId) => {
438
470
  try {
439
- const result = await callback();
440
- resolve(result);
471
+ resolve(await callback(roomId));
441
472
  } catch (e) {
442
473
  reject(e);
443
474
  } finally {
444
- await presence.decr(concurrencyKey);
475
+ await presence.hincrby(hkey, concurrencyKey, -1);
445
476
  }
446
- }, concurrencyTimeout);
477
+ };
478
+ if (concurrency > 0) {
479
+ debugMatchMaking(
480
+ "receiving %d concurrent joinOrCreate for '%s' (%s)",
481
+ concurrency,
482
+ handler.name,
483
+ concurrencyKey
484
+ );
485
+ const result = await presence.brpop(
486
+ `l:${handler.name}:${concurrencyKey}`,
487
+ MAX_CONCURRENT_CREATE_ROOM_WAIT_TIME + Math.min(concurrency, 3) * 0.2
488
+ // add extra milliseconds for each concurrent request
489
+ );
490
+ return await fulfill(result && result[1]);
491
+ } else {
492
+ return await fulfill();
493
+ }
447
494
  });
448
495
  }
449
496
  function onClientJoinRoom(room, client) {
@@ -468,24 +515,24 @@ function onVisibilityChange(room, isInvisible) {
468
515
  handlers[room.roomName].emit("visibility-change", room, isInvisible);
469
516
  }
470
517
  async function disposeRoom(roomName, room) {
471
- debugMatchMaking("disposing '%s' (%s) on processId '%s' (graceful shutdown: %s)", roomName, room.roomId, processId, isGracefullyShuttingDown);
472
- if (!isGracefullyShuttingDown) {
473
- stats.local.roomCount--;
518
+ debugMatchMaking("disposing '%s' (%s) on processId '%s' (graceful shutdown: %s)", roomName, room.roomId, processId, state === 2 /* SHUTTING_DOWN */);
519
+ room.listing.remove();
520
+ stats.local.roomCount--;
521
+ if (state !== 2 /* SHUTTING_DOWN */) {
474
522
  stats.persist();
475
523
  if (isDevMode) {
476
524
  await presence.hdel(getRoomRestoreListKey(), room.roomId);
477
525
  }
478
526
  }
479
527
  handlers[roomName].emit("dispose", room);
480
- presence.del(getHandlerConcurrencyKey(roomName));
481
528
  presence.unsubscribe(getRoomChannel(room.roomId));
482
529
  delete rooms[room.roomId];
483
530
  }
484
531
  function getRoomChannel(roomId) {
485
532
  return `$${roomId}`;
486
533
  }
487
- function getHandlerConcurrencyKey(name) {
488
- return `c:${name}`;
534
+ function getConcurrencyHashKey(roomName) {
535
+ return `ch:${roomName}`;
489
536
  }
490
537
  function getProcessChannel(id = processId) {
491
538
  return `p:${id}`;
@@ -493,7 +540,6 @@ function getProcessChannel(id = processId) {
493
540
  export {
494
541
  MatchMakerState,
495
542
  accept,
496
- cleanupStaleRooms,
497
543
  controller,
498
544
  create,
499
545
  createRoom,
@@ -502,6 +548,7 @@ export {
502
548
  driver,
503
549
  findOneRoomAvailable,
504
550
  getHandler,
551
+ getLocalRoomById,
505
552
  getRoomById,
506
553
  getRoomClass,
507
554
  gracefullyShutdown,
@@ -509,7 +556,6 @@ export {
509
556
  hasHandler,
510
557
  healthCheckAllProcesses,
511
558
  healthCheckProcessId,
512
- isGracefullyShuttingDown,
513
559
  join,
514
560
  joinById,
515
561
  joinOrCreate,
@@ -523,6 +569,7 @@ export {
523
569
  removeRoomType,
524
570
  reserveSeatFor,
525
571
  selectProcessIdToCreateRoom,
572
+ setHealthChecksEnabled,
526
573
  setup,
527
574
  state,
528
575
  stats