UNPKG

@colyseus/core

Version:

Multiplayer Framework for Node.js.

768 lines (766 loc) 27.8 kB
"use strict"; 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 });