@colyseus/core
Version:
Multiplayer Framework for Node.js.
768 lines (766 loc) • 27.8 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// packages/core/src/MatchMaker.ts
var MatchMaker_exports = {};
__export(MatchMaker_exports, {
MatchMakerState: () => MatchMakerState,
accept: () => accept,
addRoomType: () => addRoomType,
buildSeatReservation: () => buildSeatReservation,
controller: () => import_controller.controller,
create: () => create,
createRoom: () => createRoom,
defineRoomType: () => defineRoomType,
disconnectAll: () => disconnectAll,
driver: () => driver,
findOneRoomAvailable: () => findOneRoomAvailable,
getAllHandlers: () => getAllHandlers,
getHandler: () => getHandler,
getLocalRoomById: () => getLocalRoomById,
getRoomById: () => getRoomById,
getRoomClass: () => getRoomClass,
gracefullyShutdown: () => gracefullyShutdown,
handleCreateRoom: () => handleCreateRoom,
healthCheckAllProcesses: () => healthCheckAllProcesses,
healthCheckProcessId: () => healthCheckProcessId,
hotReload: () => hotReload,
join: () => join,
joinById: () => joinById,
joinOrCreate: () => joinOrCreate,
onReady: () => onReady,
presence: () => presence,
processId: () => processId,
publicAddress: () => publicAddress,
query: () => query,
reconnect: () => reconnect,
remoteRoomCall: () => remoteRoomCall,
removeRoomType: () => removeRoomType,
reserveMultipleSeatsFor: () => reserveMultipleSeatsFor,
reserveSeatFor: () => reserveSeatFor,
selectProcessIdToCreateRoom: () => selectProcessIdToCreateRoom,
setHealthChecksEnabled: () => setHealthChecksEnabled,
setup: () => setup,
state: () => state,
stats: () => stats
});
module.exports = __toCommonJS(MatchMaker_exports);
var import_events = require("events");
var import_IPC = require("./IPC.cjs");
var import_Utils = require("./utils/Utils.cjs");
var import_DevMode = require("./utils/DevMode.cjs");
var import_RegisteredHandler = require("./matchmaker/RegisteredHandler.cjs");
var import_Room = require("./Room.cjs");
var import_LocalPresence = require("./presence/LocalPresence.cjs");
var import_Presence = require("./presence/Presence.cjs");
var import_Debug = require("./Debug.cjs");
var import_SeatReservationError = require("./errors/SeatReservationError.cjs");
var import_ServerError = require("./errors/ServerError.cjs");
var import_LocalDriver = require("./matchmaker/LocalDriver/LocalDriver.cjs");
var import_controller = require("./matchmaker/controller.cjs");
var stats = __toESM(require("./Stats.cjs"), 1);
var import_Logger = require("./Logger.cjs");
var import_driver = require("./matchmaker/driver.cjs");
var import_shared_types = require("@colyseus/shared-types");
var import_Env = require("./utils/Env.cjs");
var handlers = {};
var rooms = {};
var events = new import_events.EventEmitter();
var publicAddress;
var processId;
var presence;
var driver;
var selectProcessIdToCreateRoom = async function() {
return (await stats.fetchAll()).sort((p1, p2) => p1.roomCount > p2.roomCount ? 1 : -1)[0]?.processId || processId;
};
var enableHealthChecks = true;
function setHealthChecksEnabled(value) {
enableHealthChecks = value;
}
var onReady = new import_Utils.Deferred();
var MatchMakerState = {
INITIALIZING: 0,
READY: 1,
SHUTTING_DOWN: 2
};
var state;
async function setup(_presence, _driver, _publicAddress, _selectProcessIdToCreateRoom) {
if (onReady === void 0) {
onReady = new import_Utils.Deferred();
}
state = MatchMakerState.INITIALIZING;
presence = _presence || await (0, import_Env.getDefaultPresence)();
driver = _driver || await (0, import_Env.getDefaultDriver)();
publicAddress = _publicAddress || (0, import_Env.getDefaultPublicAddress)();
stats.reset(false);
if (import_DevMode.isDevMode) {
processId = await (0, import_DevMode.getPreviousProcessId)();
}
if (!processId) {
processId = (0, import_Utils.generateId)();
}
if (_selectProcessIdToCreateRoom) {
selectProcessIdToCreateRoom = _selectProcessIdToCreateRoom;
}
if (driver.boot) {
await driver.boot();
}
onReady.resolve();
}
async function accept(isStandalone = false) {
await onReady;
if (isStandalone) {
state = MatchMakerState.READY;
return;
}
await (0, import_IPC.subscribeIPC)(presence, getProcessChannel(), (method, args) => {
if (method === "healthcheck") {
return true;
} else {
return handleCreateRoom.apply(void 0, args);
}
});
if (enableHealthChecks) {
await healthCheckAllProcesses();
stats.setAutoPersistInterval();
}
state = MatchMakerState.READY;
await stats.persist();
if (import_DevMode.isDevMode) {
await (0, import_DevMode.reloadFromCache)();
}
}
async function joinOrCreate(roomName, clientOptions = {}, authContext) {
return await (0, import_Utils.retry)(async () => {
const authData = await callOnAuth(roomName, clientOptions, authContext);
let room = await findOneRoomAvailable(roomName, clientOptions);
if (!room) {
const handler = getHandler(roomName);
const filterOptions = handler.getFilterOptions(clientOptions);
const concurrencyKey = (0, import_driver.getLockId)(filterOptions);
await concurrentJoinOrCreateRoomLock(handler, concurrencyKey, async (roomId) => {
if (roomId) {
room = await driver.findOne({ roomId });
}
if (!room || room.locked) {
room = await findOneRoomAvailable(roomName, clientOptions);
}
if (!room) {
room = await createRoom(roomName, clientOptions);
presence.publish(`concurrent:${handler.name}:${concurrencyKey}`, room.roomId);
}
return room;
});
}
return await reserveSeatFor(room, clientOptions, authData);
}, 5, [import_SeatReservationError.SeatReservationError]);
}
async function create(roomName, clientOptions = {}, authContext) {
const authData = await callOnAuth(roomName, clientOptions, authContext);
const room = await createRoom(roomName, clientOptions);
return reserveSeatFor(room, clientOptions, authData);
}
async function join(roomName, clientOptions = {}, authContext) {
return await (0, import_Utils.retry)(async () => {
const authData = await callOnAuth(roomName, clientOptions, authContext);
const room = await findOneRoomAvailable(roomName, clientOptions);
if (!room) {
throw new import_ServerError.ServerError(import_shared_types.ErrorCode.MATCHMAKE_INVALID_CRITERIA, `no rooms found with provided criteria`);
}
return reserveSeatFor(room, clientOptions, authData);
});
}
async function reconnect(roomId, clientOptions = {}) {
const room = await driver.findOne({ roomId });
if (!room) {
if (process.env.NODE_ENV !== "production") {
import_Logger.logger.info(`\u274C room "${roomId}" has been disposed. Did you miss .allowReconnection()?
\u{1F449} https://docs.colyseus.io/room#allow-reconnection`);
}
throw new import_ServerError.ServerError(import_shared_types.ErrorCode.MATCHMAKE_INVALID_ROOM_ID, `room "${roomId}" has been disposed.`);
}
const reconnectionToken = clientOptions.reconnectionToken;
if (!reconnectionToken) {
throw new import_ServerError.ServerError(import_shared_types.ErrorCode.MATCHMAKE_UNHANDLED, `'reconnectionToken' must be provided for reconnection.`);
}
const sessionId = await remoteRoomCall(room.roomId, "checkReconnectionToken", [reconnectionToken]);
if (sessionId) {
return buildSeatReservation(room, sessionId);
} else {
if (process.env.NODE_ENV !== "production") {
import_Logger.logger.info(`\u274C reconnection token invalid or expired. Did you miss .allowReconnection()?
\u{1F449} https://docs.colyseus.io/room#allow-reconnection`);
}
throw new import_ServerError.ServerError(import_shared_types.ErrorCode.MATCHMAKE_EXPIRED, `reconnection token invalid or expired.`);
}
}
async function joinById(roomId, clientOptions = {}, authContext) {
const room = await driver.findOne({ roomId });
if (!room) {
throw new import_ServerError.ServerError(import_shared_types.ErrorCode.MATCHMAKE_INVALID_ROOM_ID, `room "${roomId}" not found`);
} else if (room.locked) {
throw new import_ServerError.ServerError(import_shared_types.ErrorCode.MATCHMAKE_INVALID_ROOM_ID, `room "${roomId}" is locked`);
}
const authData = await callOnAuth(room.name, clientOptions, authContext);
return reserveSeatFor(room, clientOptions, authData);
}
async function query(conditions = {}, sortOptions) {
return await driver.query(conditions, sortOptions);
}
async function findOneRoomAvailable(roomName, filterOptions, additionalSortOptions) {
const handler = getHandler(roomName);
const sortOptions = Object.assign({}, handler.sortOptions ?? {});
if (additionalSortOptions) {
Object.assign(sortOptions, additionalSortOptions);
}
return await driver.findOne({
locked: false,
name: roomName,
private: false,
...handler.getFilterOptions(filterOptions)
}, sortOptions);
}
async function remoteRoomCall(roomId, method, args, rejectionTimeout = import_Utils.REMOTE_ROOM_SHORT_TIMEOUT) {
const room = rooms[roomId];
if (!room) {
try {
return await (0, import_IPC.requestFromIPC)(presence, getRoomChannel(roomId), method, args, rejectionTimeout);
} catch (e) {
if (method === "_reserveSeat" && e.message === "ipc_timeout") {
throw e;
}
const request = `${String(method)}${args && " with args " + JSON.stringify(args) || ""}`;
throw new import_ServerError.ServerError(
import_shared_types.ErrorCode.MATCHMAKE_UNHANDLED,
`remote room (${roomId}) timed out, requesting "${request}". (${rejectionTimeout}ms exceeded)`
);
}
} else {
return !args && typeof room[method] !== "function" ? room[method] : await room[method].apply(room, args && JSON.parse(JSON.stringify(args)));
}
}
function defineRoomType(roomName, klass, defaultOptions) {
const registeredHandler = new import_RegisteredHandler.RegisteredHandler(klass, defaultOptions);
registeredHandler.name = roomName;
handlers[roomName] = registeredHandler;
if (klass.prototype["onAuth"] !== import_Room.Room.prototype["onAuth"]) {
if (klass["onAuth"] !== import_Room.Room["onAuth"]) {
import_Logger.logger.info(`\u274C "${roomName}"'s onAuth() defined at the instance level will be ignored.`);
}
}
return registeredHandler;
}
function addRoomType(handler) {
handlers[handler.name] = handler;
}
function removeRoomType(roomName) {
delete handlers[roomName];
}
function getAllHandlers() {
return handlers;
}
function getHandler(roomName) {
const handler = handlers[roomName];
if (!handler) {
throw new import_ServerError.ServerError(import_shared_types.ErrorCode.MATCHMAKE_NO_HANDLER, `provided room name "${roomName}" not defined`);
}
return handler;
}
function getRoomClass(roomName) {
return handlers[roomName]?.klass;
}
async function createRoom(roomName, clientOptions) {
const selectedProcessId = state === MatchMakerState.READY ? await selectProcessIdToCreateRoom(roomName, clientOptions) : processId;
let room;
if (selectedProcessId === void 0) {
if (import_DevMode.isDevMode && processId === void 0) {
await onReady;
return createRoom(roomName, clientOptions);
} else {
throw new import_ServerError.ServerError(import_shared_types.ErrorCode.MATCHMAKE_UNHANDLED, `no processId available to create room ${roomName}`);
}
} else if (selectedProcessId === processId) {
room = await handleCreateRoom(roomName, clientOptions);
} else {
try {
room = await (0, import_IPC.requestFromIPC)(
presence,
getProcessChannel(selectedProcessId),
void 0,
[roomName, clientOptions],
import_Utils.REMOTE_ROOM_SHORT_TIMEOUT
);
} catch (e) {
if (e.message === "ipc_timeout") {
(0, import_Debug.debugAndPrintError)(`${e.message}: create room request timed out for ${roomName} on processId ${selectedProcessId}.`);
if (enableHealthChecks) {
await stats.excludeProcess(selectedProcessId);
}
room = await handleCreateRoom(roomName, clientOptions);
} else {
throw e;
}
}
}
if (import_DevMode.isDevMode) {
presence.hset((0, import_DevMode.getRoomRestoreListKey)(), room.roomId, JSON.stringify({
"clientOptions": clientOptions,
"roomName": roomName,
"processId": processId
}));
}
return room;
}
async function handleCreateRoom(roomName, clientOptions, restoringRoomId) {
const handler = getHandler(roomName);
const room = new handler.klass();
if (restoringRoomId && import_DevMode.isDevMode) {
room.roomId = restoringRoomId;
} else {
room.roomId = (0, import_Utils.generateId)();
}
room["__init"]();
room.roomName = roomName;
room.presence = (0, import_Presence.createScopedPresence)(room, presence);
room["_listing"] = (0, import_driver.initializeRoomCache)({
name: roomName,
processId,
...handler.getMetadataFromOptions(clientOptions)
});
if (publicAddress) {
room["_listing"].publicAddress = publicAddress;
}
if (room.onCreate) {
try {
await room.onCreate((0, import_Utils.merge)({}, clientOptions, handler.options));
} catch (e) {
(0, import_Debug.debugAndPrintError)(e);
throw new import_ServerError.ServerError(
e.code || import_shared_types.ErrorCode.MATCHMAKE_UNHANDLED,
e.message
);
}
}
room["_internalState"] = import_Room.RoomInternalState.CREATED;
room["_listing"].roomId = room.roomId;
room["_listing"].maxClients = room.maxClients;
(0, import_Debug.debugMatchMaking)("creating room '%s', roomId: '%s', processId: '%s'", roomName, room.roomId, processId);
stats.local.roomCount++;
stats.persist();
room["_events"].on("lock", lockRoom.bind(void 0, room));
room["_events"].on("unlock", unlockRoom.bind(void 0, room));
room["_events"].on("join", onClientJoinRoom.bind(void 0, room));
room["_events"].on("leave", onClientLeaveRoom.bind(void 0, room));
room["_events"].once("dispose", disposeRoom.bind(void 0, roomName, room));
if (handler.realtimeListingEnabled) {
room["_events"].on("visibility-change", onVisibilityChange.bind(void 0, room));
room["_events"].on("metadata-change", onMetadataChange.bind(void 0, room));
}
room["_events"].once("disconnect", () => {
room["_events"].removeAllListeners("lock");
room["_events"].removeAllListeners("unlock");
room["_events"].removeAllListeners("dispose");
if (handler.realtimeListingEnabled) {
room["_events"].removeAllListeners("visibility-change");
room["_events"].removeAllListeners("metadata-change");
}
if (stats.local.roomCount <= 0) {
events.emit("no-active-rooms");
}
});
await createRoomReferences(room, true);
if (state !== MatchMakerState.SHUTTING_DOWN) {
await driver.persist(room["_listing"], true);
}
handler.emit("create", room);
return room["_listing"];
}
function getRoomById(roomId) {
return driver.findOne({ roomId });
}
function getLocalRoomById(roomId) {
return rooms[roomId];
}
function disconnectAll(closeCode) {
const promises = [];
for (const roomId in rooms) {
if (!rooms.hasOwnProperty(roomId)) {
continue;
}
promises.push(rooms[roomId].disconnect(closeCode));
}
return promises;
}
async function lockAndDisposeAll() {
await stats.excludeProcess(processId);
if (enableHealthChecks) {
stats.clearAutoPersistInterval();
}
const noActiveRooms = new import_Utils.Deferred();
if (stats.local.roomCount <= 0) {
noActiveRooms.resolve();
} else {
events.once("no-active-rooms", () => noActiveRooms.resolve());
}
for (const roomId in rooms) {
if (!rooms.hasOwnProperty(roomId)) {
continue;
}
const room = rooms[roomId];
room.lock();
if (import_DevMode.isDevMode) {
import_Room.Room.prototype.onBeforeShutdown.call(room);
} else {
room.onBeforeShutdown();
}
}
await noActiveRooms;
}
async function gracefullyShutdown() {
if (state === MatchMakerState.SHUTTING_DOWN) {
return Promise.reject("already_shutting_down");
}
(0, import_Debug.debugMatchMaking)(`${processId} is shutting down!`);
state = MatchMakerState.SHUTTING_DOWN;
onReady = void 0;
if (import_DevMode.isDevMode) {
for (const roomId in rooms) {
if (!rooms.hasOwnProperty(roomId)) {
continue;
}
rooms[roomId]["_rejectPendingReconnections"]?.("devmode_restart");
}
await new Promise((resolve) => setTimeout(resolve, 50));
await (0, import_DevMode.cacheRoomHistory)(rooms);
}
await lockAndDisposeAll();
await removeRoomsByProcessId(processId);
presence.unsubscribe(getProcessChannel());
return Promise.all(disconnectAll(
import_DevMode.isDevMode ? import_shared_types.CloseCode.MAY_TRY_RECONNECT : import_shared_types.CloseCode.SERVER_SHUTDOWN
));
}
async function hotReload() {
state = MatchMakerState.SHUTTING_DOWN;
for (const roomId in rooms) {
if (!rooms.hasOwnProperty(roomId)) {
continue;
}
rooms[roomId]["_rejectPendingReconnections"]?.("devmode_restart");
}
await new Promise((resolve) => setTimeout(resolve, 50));
await (0, import_DevMode.cacheRoomHistory)(rooms);
const noActiveRooms = new import_Utils.Deferred();
if (stats.local.roomCount <= 0) {
noActiveRooms.resolve();
} else {
events.once("no-active-rooms", () => noActiveRooms.resolve());
}
for (const roomId in rooms) {
if (!rooms.hasOwnProperty(roomId)) {
continue;
}
const room = rooms[roomId];
room.lock();
import_Room.Room.prototype.onBeforeShutdown.call(room);
}
await noActiveRooms;
await Promise.all(disconnectAll(import_shared_types.CloseCode.MAY_TRY_RECONNECT));
await removeRoomsByProcessId(processId);
state = MatchMakerState.READY;
await (0, import_DevMode.reloadFromCache)();
await stats.persist();
}
async function reserveSeatFor(room, options, authData) {
const sessionId = authData?.sessionId || (0, import_Utils.generateId)();
let successfulSeatReservation;
try {
successfulSeatReservation = await remoteRoomCall(
room.roomId,
"_reserveSeat",
[sessionId, options, authData],
import_Utils.REMOTE_ROOM_SHORT_TIMEOUT
);
} catch (e) {
(0, import_Debug.debugMatchMaking)(e);
if (e.message === "ipc_timeout" && !(enableHealthChecks && await healthCheckProcessId(room.processId))) {
throw new import_SeatReservationError.SeatReservationError(`process ${room.processId} is not available.`);
} else {
successfulSeatReservation = false;
}
}
if (!successfulSeatReservation) {
throw new import_SeatReservationError.SeatReservationError(`${room.roomId} is already full.`);
}
return buildSeatReservation(room, sessionId);
}
async function reserveMultipleSeatsFor(room, clientsData) {
let sessionIds = [];
let options = [];
let authData = [];
for (const clientData of clientsData) {
sessionIds.push(clientData.sessionId);
options.push(clientData.options);
authData.push(clientData.auth);
}
(0, import_Debug.debugMatchMaking)(
"reserving multiple seats. sessionIds: '%s', roomId: '%s', processId: '%s'",
sessionIds.join(", "),
room.roomId,
processId
);
let successfulSeatReservations;
try {
successfulSeatReservations = await remoteRoomCall(
room.roomId,
"_reserveMultipleSeats",
[sessionIds, options, authData],
import_Utils.REMOTE_ROOM_SHORT_TIMEOUT
);
} catch (e) {
(0, import_Debug.debugMatchMaking)(e);
if (e.message === "ipc_timeout" && !(enableHealthChecks && await healthCheckProcessId(room.processId))) {
throw new import_SeatReservationError.SeatReservationError(`process ${room.processId} is not available.`);
} else {
throw new import_SeatReservationError.SeatReservationError(`${room.roomId} is already full.`);
}
}
return successfulSeatReservations;
}
function buildSeatReservation(room, sessionId) {
const seatReservation = {
name: room.name,
sessionId,
roomId: room.roomId,
processId: room.processId
};
if (import_DevMode.isDevMode) {
seatReservation.devMode = import_DevMode.isDevMode;
}
if (room.publicAddress) {
seatReservation.publicAddress = room.publicAddress;
}
return seatReservation;
}
async function callOnAuth(roomName, clientOptions, authContext) {
const roomClass = getRoomClass(roomName);
if (roomClass && roomClass["onAuth"] && roomClass["onAuth"] !== import_Room.Room["onAuth"]) {
const result = await roomClass["onAuth"](authContext.token, clientOptions, authContext);
if (!result) {
throw new import_ServerError.ServerError(import_shared_types.ErrorCode.AUTH_FAILED, "onAuth failed");
}
return result;
}
}
async function healthCheckAllProcesses() {
const allStats = await stats.fetchAll();
const activeProcessChannels = (await presence.channels("p:*")).map((c) => c.substring(2));
if (allStats.length > 0) {
await Promise.all(
allStats.filter((stat) => stat.processId !== processId && // skip current process
!activeProcessChannels.includes(stat.processId)).map((stat) => healthCheckProcessId(stat.processId))
);
}
}
var _healthCheckByProcessId = {};
function healthCheckProcessId(processId2) {
if (_healthCheckByProcessId[processId2] !== void 0) {
return _healthCheckByProcessId[processId2];
}
_healthCheckByProcessId[processId2] = new Promise(async (resolve, reject) => {
import_Logger.logger.debug(`> Performing health-check against processId: '${processId2}'...`);
try {
const requestTime = Date.now();
await (0, import_IPC.requestFromIPC)(
presence,
getProcessChannel(processId2),
"healthcheck",
[],
import_Utils.REMOTE_ROOM_SHORT_TIMEOUT
);
import_Logger.logger.debug(`\u2705 Process '${processId2}' successfully responded (${Date.now() - requestTime}ms)`);
resolve(true);
} catch (e) {
import_Logger.logger.debug(`\u274C Process '${processId2}' failed to respond. Cleaning it up.`);
await stats.excludeProcess(processId2);
if (!import_DevMode.isDevMode) {
await removeRoomsByProcessId(processId2);
}
resolve(false);
} finally {
delete _healthCheckByProcessId[processId2];
}
});
return _healthCheckByProcessId[processId2];
}
async function removeRoomsByProcessId(processId2) {
await driver.cleanup(processId2);
}
async function createRoomReferences(room, init = false) {
rooms[room.roomId] = room;
if (init) {
await (0, import_IPC.subscribeIPC)(
presence,
getRoomChannel(room.roomId),
(method, args) => {
return !args && typeof room[method] !== "function" ? room[method] : room[method].apply(room, args);
}
);
}
return true;
}
async function concurrentJoinOrCreateRoomLock(handler, concurrencyKey, callback) {
return new Promise(async (resolve, reject) => {
const hkey = getConcurrencyHashKey(handler.name);
const concurrency = await presence.hincrbyex(
hkey,
concurrencyKey,
1,
// increment by 1
import_Utils.MAX_CONCURRENT_CREATE_ROOM_WAIT_TIME * 2
// expire in 2x the time of MAX_CONCURRENT_CREATE_ROOM_WAIT_TIME
) - 1;
const fulfill = async (roomId) => {
try {
resolve(await callback(roomId));
} catch (e) {
reject(e);
} finally {
await presence.hincrbyex(hkey, concurrencyKey, -1, import_Utils.MAX_CONCURRENT_CREATE_ROOM_WAIT_TIME * 2);
}
};
if (concurrency > 0) {
(0, import_Debug.debugMatchMaking)(
"receiving %d concurrent joinOrCreate for '%s' (%s)",
concurrency,
handler.name,
concurrencyKey
);
try {
const roomId = await (0, import_IPC.subscribeWithTimeout)(
presence,
`concurrent:${handler.name}:${concurrencyKey}`,
(import_Utils.MAX_CONCURRENT_CREATE_ROOM_WAIT_TIME + Math.min(concurrency, 3) * 0.2) * 1e3
// convert to milliseconds
);
return await fulfill(roomId);
} catch (error) {
}
}
return await fulfill();
});
}
function onClientJoinRoom(room, client) {
stats.local.ccu++;
stats.persist();
handlers[room.roomName].emit("join", room, client);
}
function onClientLeaveRoom(room, client, willDispose) {
stats.local.ccu--;
stats.persist();
handlers[room.roomName].emit("leave", room, client, willDispose);
}
function lockRoom(room) {
handlers[room.roomName].emit("lock", room);
}
async function unlockRoom(room) {
if (await createRoomReferences(room)) {
handlers[room.roomName].emit("unlock", room);
}
}
function onVisibilityChange(room, isInvisible) {
handlers[room.roomName].emit("visibility-change", room, isInvisible);
}
function onMetadataChange(room) {
handlers[room.roomName].emit("metadata-change", room);
}
async function disposeRoom(roomName, room) {
(0, import_Debug.debugMatchMaking)("disposing '%s' (%s) on processId '%s' (graceful shutdown: %s)", roomName, room.roomId, processId, state === MatchMakerState.SHUTTING_DOWN);
driver.remove(room["_listing"].roomId);
stats.local.roomCount--;
if (state !== MatchMakerState.SHUTTING_DOWN) {
stats.persist();
if (import_DevMode.isDevMode) {
await presence.hdel((0, import_DevMode.getRoomRestoreListKey)(), room.roomId);
}
}
handlers[roomName].emit("dispose", room);
presence.unsubscribe(getRoomChannel(room.roomId));
delete rooms[room.roomId];
}
function getRoomChannel(roomId) {
return `$${roomId}`;
}
function getConcurrencyHashKey(roomName) {
return `ch:${roomName}`;
}
function getProcessChannel(id = processId) {
return `p:${id}`;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
MatchMakerState,
accept,
addRoomType,
buildSeatReservation,
controller,
create,
createRoom,
defineRoomType,
disconnectAll,
driver,
findOneRoomAvailable,
getAllHandlers,
getHandler,
getLocalRoomById,
getRoomById,
getRoomClass,
gracefullyShutdown,
handleCreateRoom,
healthCheckAllProcesses,
healthCheckProcessId,
hotReload,
join,
joinById,
joinOrCreate,
onReady,
presence,
processId,
publicAddress,
query,
reconnect,
remoteRoomCall,
removeRoomType,
reserveMultipleSeatsFor,
reserveSeatFor,
selectProcessIdToCreateRoom,
setHealthChecksEnabled,
setup,
state,
stats
});