@colyseus/core 0.17.1 → 0.17.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/rooms/RankedQueueRoom.js.map +2 -2
- package/build/rooms/RankedQueueRoom.mjs.map +2 -2
- package/package.json +6 -4
- package/src/rooms/RankedQueueRoom.ts +1 -1
- package/build/matchmaker/driver/api.js +0 -48
- package/build/matchmaker/driver/api.js.map +0 -7
- package/build/matchmaker/driver/api.mjs +0 -24
- package/build/matchmaker/driver/api.mjs.map +0 -7
- package/build/matchmaker/driver/local/LocalDriver.js +0 -94
- package/build/matchmaker/driver/local/LocalDriver.js.map +0 -7
- package/build/matchmaker/driver/local/LocalDriver.mjs +0 -71
- package/build/matchmaker/driver/local/LocalDriver.mjs.map +0 -7
- package/build/matchmaker/driver/local/Query.js +0 -111
- package/build/matchmaker/driver/local/Query.js.map +0 -7
- package/build/matchmaker/driver/local/Query.mjs +0 -88
- package/build/matchmaker/driver/local/Query.mjs.map +0 -7
- package/build/matchmaker/routes.js +0 -79
- package/build/matchmaker/routes.js.map +0 -7
- package/build/matchmaker/routes.mjs +0 -45
- package/build/matchmaker/routes.mjs.map +0 -7
- package/build/rooms/createRoom.js +0 -51
- package/build/rooms/createRoom.js.map +0 -7
- package/build/rooms/createRoom.mjs +0 -28
- package/build/rooms/createRoom.mjs.map +0 -7
- package/build/src/matchmaker/driver/api.d.ts +0 -145
- package/build/src/matchmaker/driver/local/LocalDriver.d.ts +0 -17
- package/build/src/matchmaker/driver/local/Query.d.ts +0 -12
- package/build/src/matchmaker/routes.d.ts +0 -92
- package/build/src/rooms/createRoom.d.ts +0 -65
- /package/build/{src/Debug.d.ts → Debug.d.ts} +0 -0
- /package/build/{src/IPC.d.ts → IPC.d.ts} +0 -0
- /package/build/{src/Logger.d.ts → Logger.d.ts} +0 -0
- /package/build/{src/MatchMaker.d.ts → MatchMaker.d.ts} +0 -0
- /package/build/{src/Protocol.d.ts → Protocol.d.ts} +0 -0
- /package/build/{src/Room.d.ts → Room.d.ts} +0 -0
- /package/build/{src/Server.d.ts → Server.d.ts} +0 -0
- /package/build/{src/Stats.d.ts → Stats.d.ts} +0 -0
- /package/build/{src/Transport.d.ts → Transport.d.ts} +0 -0
- /package/build/{src/errors → errors}/RoomExceptions.d.ts +0 -0
- /package/build/{src/errors → errors}/SeatReservationError.d.ts +0 -0
- /package/build/{src/errors → errors}/ServerError.d.ts +0 -0
- /package/build/{src/index.d.ts → index.d.ts} +0 -0
- /package/build/{src/matchmaker → matchmaker}/Lobby.d.ts +0 -0
- /package/build/{src/matchmaker → matchmaker}/LocalDriver/LocalDriver.d.ts +0 -0
- /package/build/{src/matchmaker → matchmaker}/LocalDriver/Query.d.ts +0 -0
- /package/build/{src/matchmaker → matchmaker}/RegisteredHandler.d.ts +0 -0
- /package/build/{src/matchmaker → matchmaker}/controller.d.ts +0 -0
- /package/build/{src/matchmaker → matchmaker}/driver.d.ts +0 -0
- /package/build/{src/presence → presence}/LocalPresence.d.ts +0 -0
- /package/build/{src/presence → presence}/Presence.d.ts +0 -0
- /package/build/{src/rooms → rooms}/LobbyRoom.d.ts +0 -0
- /package/build/{src/rooms → rooms}/RankedQueueRoom.d.ts +0 -0
- /package/build/{src/rooms → rooms}/RelayRoom.d.ts +0 -0
- /package/build/{src/router → router}/default_routes.d.ts +0 -0
- /package/build/{src/router → router}/index.d.ts +0 -0
- /package/build/{src/serializer → serializer}/NoneSerializer.d.ts +0 -0
- /package/build/{src/serializer → serializer}/SchemaSerializer.d.ts +0 -0
- /package/build/{src/serializer → serializer}/SchemaSerializerDebug.d.ts +0 -0
- /package/build/{src/serializer → serializer}/Serializer.d.ts +0 -0
- /package/build/{src/utils → utils}/DevMode.d.ts +0 -0
- /package/build/{src/utils → utils}/StandardSchema.d.ts +0 -0
- /package/build/{src/utils → utils}/Utils.d.ts +0 -0
- /package/build/{src/utils → utils}/nanoevents.d.ts +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/rooms/RankedQueueRoom.ts"],
|
|
4
|
-
"sourcesContent": ["import { Room, type Client, matchMaker, type IRoomCache, debugMatchMaking, ServerError, ErrorCode, isDevMode, } from \"@colyseus/core\";\n\nexport interface RankedQueueOptions {\n /**\n * number of players on each match\n */\n maxPlayers?: number;\n\n /**\n * name of the room to create\n */\n matchRoomName: string;\n\n /**\n * after these cycles, create a match with a bot\n */\n maxWaitingCycles?: number;\n\n /**\n * after this time, try to fit this client with a not-so-compatible group\n */\n maxWaitingCyclesForPriority?: number;\n\n /**\n * If set, teams must have the same size to be matched together\n */\n maxTeamSize?: number;\n\n /**\n * If `allowIncompleteGroups` is true, players inside an unmatched group (that\n * did not reached `maxPlayers`, and `maxWaitingCycles` has been\n * reached) will be matched together. Your room should fill the remaining\n * spots with \"bots\" on this case.\n */\n allowIncompleteGroups?: boolean;\n\n /**\n * Comparison function for matching clients to groups\n * Returns true if the client is compatible with the group\n */\n compare?: (client: ClientQueueData, matchGroup: MatchGroup) => boolean;\n\n /**\n *\n * When onGroupReady is set, the \"roomNameToCreate\" option is ignored.\n */\n onGroupReady?: (this: RankedQueueRoom, group: MatchGroup) => Promise<IRoomCache>;\n}\n\nexport interface MatchGroup {\n averageRank: number;\n clients: Array<Client<{ userData: ClientQueueData }>>,\n ready?: boolean;\n confirmed?: number;\n}\n\nexport interface MatchTeam {\n averageRank: number;\n clients: Array<Client<{ userData: ClientQueueData }>>,\n teamId: string | symbol;\n}\n\nexport interface ClientQueueData {\n /**\n * Rank of the client\n */\n rank: number;\n\n /**\n * Timestamp of when the client entered the queue\n */\n currentCycle?: number;\n\n /**\n * Optional: if matching with a team, the team ID\n */\n teamId?: string;\n\n /**\n * Additional options passed by the client when joining the room\n */\n options?: any;\n\n /**\n * Match group the client is currently in\n */\n group?: MatchGroup;\n\n /**\n * Whether the client has confirmed the connection to the room\n */\n confirmed?: boolean;\n\n /**\n * Whether the client should be prioritized in the queue\n * (e.g. for players that are waiting for a long time)\n */\n highPriority?: boolean;\n\n /**\n * The last number of clients in the queue sent to the client\n */\n lastQueueClientCount?: number;\n}\n\nconst DEFAULT_TEAM = Symbol(\"$default_team\");\nconst DEFAULT_COMPARE = (client: ClientQueueData, matchGroup: MatchGroup) => {\n const diff = Math.abs(client.rank - matchGroup.averageRank);\n const diffRatio = (diff / matchGroup.averageRank);\n // If diff ratio is too high, create a new match group\n return (diff < 10 || diffRatio <= 2);\n}\n\nexport class RankedQueueRoom extends Room {\n maxPlayers = 4;\n maxTeamSize: number;\n allowIncompleteGroups: boolean = false;\n\n maxWaitingCycles = 15;\n maxWaitingCyclesForPriority?: number = 10;\n\n /**\n * Evaluate groups for each client at interval\n */\n cycleTickInterval = 1000;\n\n /**\n * Groups of players per iteration\n */\n groups: MatchGroup[] = [];\n highPriorityGroups: MatchGroup[] = [];\n\n matchRoomName: string;\n\n protected compare = DEFAULT_COMPARE;\n protected onGroupReady = (group: MatchGroup) => matchMaker.createRoom(this.matchRoomName, {});\n\n messages = {\n confirm: (client: Client, _: unknown) => {\n const queueData = client.userData;\n\n if (queueData && queueData.group && typeof (queueData.group.confirmed) === \"number\") {\n queueData.confirmed = true;\n queueData.group.confirmed++;\n client.leave();\n }\n },\n }\n\n onCreate(options: RankedQueueOptions) {\n if (typeof(options.maxWaitingCycles) === \"number\") {\n this.maxWaitingCycles = options.maxWaitingCycles;\n }\n\n if (typeof(options.maxPlayers) === \"number\") {\n this.maxPlayers = options.maxPlayers;\n }\n\n if (typeof(options.maxTeamSize) === \"number\") {\n this.maxTeamSize = options.maxTeamSize;\n }\n\n if (typeof(options.allowIncompleteGroups) !== \"undefined\") {\n this.allowIncompleteGroups = options.allowIncompleteGroups;\n }\n\n if (typeof(options.compare) === \"function\") {\n this.compare = options.compare;\n }\n\n if (typeof(options.onGroupReady) === \"function\") {\n this.onGroupReady = options.onGroupReady;\n }\n\n if (options.matchRoomName) {\n this.matchRoomName = options.matchRoomName;\n\n } else {\n throw new ServerError(ErrorCode.APPLICATION_ERROR, \"RankedQueueRoom: 'matchRoomName' option is required.\");\n }\n\n debugMatchMaking(\"RankedQueueRoom#onCreate() maxPlayers: %d, maxWaitingCycles: %d, maxTeamSize: %d, allowIncompleteGroups: %d, roomNameToCreate: %s\", this.maxPlayers, this.maxWaitingCycles, this.maxTeamSize, this.allowIncompleteGroups, this.matchRoomName);\n\n /**\n * Redistribute clients into groups at every interval\n */\n this.setSimulationInterval(() => this.reassignMatchGroups(), this.cycleTickInterval);\n }\n\n onJoin(client: Client, options: any, auth?: unknown) {\n this.addToQueue(client, {\n rank: options.rank,\n teamId: options.teamId,\n options,\n });\n }\n\n addToQueue(client: Client, queueData: ClientQueueData) {\n if (queueData.currentCycle === undefined) {\n queueData.currentCycle = 0;\n }\n client.userData = queueData;\n\n // FIXME: reassign groups upon joining [?] (without incrementing cycle count)\n client.send(\"clients\", 1);\n }\n\n createMatchGroup() {\n const group: MatchGroup = { clients: [], averageRank: 0 };\n this.groups.push(group);\n return group;\n }\n\n reassignMatchGroups() {\n // Re-set all groups\n this.groups.length = 0;\n this.highPriorityGroups.length = 0;\n\n const sortedClients = (this.clients)\n .filter((client) => {\n // Filter out:\n // - clients that are not in the queue\n // - clients that are already in a \"ready\" group\n return (\n client.userData &&\n client.userData.group?.ready !== true\n );\n })\n .sort((a, b) => {\n //\n // Sort by rank ascending\n //\n return a.userData.rank - b.userData.rank;\n });\n\n //\n // The room either distribute by teams or by clients\n //\n if (typeof(this.maxTeamSize) === \"number\") {\n this.redistributeTeams(sortedClients);\n\n } else {\n this.redistributeClients(sortedClients);\n }\n\n this.evaluateHighPriorityGroups();\n this.processGroupsReady();\n }\n\n redistributeTeams(sortedClients: Client<{ userData: ClientQueueData }>[]) {\n const teamsByID: { [teamId: string | symbol]: MatchTeam } = {};\n\n sortedClients.forEach((client) => {\n const teamId = client.userData.teamId || DEFAULT_TEAM;\n\n // Create a new team if it doesn't exist\n if (!teamsByID[teamId]) {\n teamsByID[teamId] = { teamId: teamId, clients: [], averageRank: 0, };\n }\n\n teamsByID[teamId].averageRank += client.userData.rank;\n teamsByID[teamId].clients.push(client);\n });\n\n // Calculate average rank for each team\n let teams = Object.values(teamsByID).map((team) => {\n team.averageRank /= team.clients.length;\n return team;\n }).sort((a, b) => {\n // Sort by average rank ascending\n return a.averageRank - b.averageRank;\n });\n\n // Iterate over teams multiple times until all clients are assigned to a group\n do {\n let currentGroup: MatchGroup = this.createMatchGroup();\n teams = teams.filter((team) => {\n // Remove clients from the team and add them to the current group\n const totalRank = team.averageRank * team.clients.length;\n\n // currentGroup.averageRank = (currentGroup.averageRank === undefined)\n // ? team.averageRank\n // : (currentGroup.averageRank + team.averageRank) / ;\n currentGroup = this.redistributeClients(team.clients.splice(0, this.maxTeamSize), currentGroup, totalRank);\n\n if (team.clients.length >= this.maxTeamSize) {\n // team still has enough clients to form a group\n return true;\n }\n\n // increment cycle count for all clients in the team\n team.clients.forEach((client) => client.userData.currentCycle++);\n\n return false;\n });\n } while (teams.length >= 2);\n }\n\n redistributeClients(\n sortedClients: Client<{ userData: ClientQueueData }>[],\n currentGroup: MatchGroup = this.createMatchGroup(),\n totalRank: number = 0,\n ) {\n for (let i = 0, l = sortedClients.length; i < l; i++) {\n const client = sortedClients[i];\n const userData = client.userData;\n const currentCycle = userData.currentCycle++;\n\n if (currentGroup.averageRank > 0) {\n if (\n !this.compare(userData, currentGroup) &&\n !userData.highPriority\n ) {\n currentGroup = this.createMatchGroup();\n totalRank = 0;\n }\n }\n\n userData.group = currentGroup;\n currentGroup.clients.push(client);\n\n totalRank += userData.rank;\n currentGroup.averageRank = totalRank / currentGroup.clients.length;\n\n // Enough players in the group, mark it as ready!\n if (currentGroup.clients.length === this.maxPlayers) {\n currentGroup.ready = true;\n currentGroup = this.createMatchGroup();\n totalRank = 0;\n continue;\n }\n\n if (currentCycle >= this.maxWaitingCycles && this.allowIncompleteGroups) {\n /**\n * Match long-waiting clients with bots\n */\n if (this.highPriorityGroups.indexOf(currentGroup) === -1) {\n this.highPriorityGroups.push(currentGroup);\n }\n\n } else if (\n this.maxWaitingCyclesForPriority !== undefined &&\n currentCycle >= this.maxWaitingCyclesForPriority\n ) {\n /**\n * Force this client to join a group, even if rank is incompatible\n */\n userData.highPriority = true;\n }\n }\n\n return currentGroup;\n }\n\n evaluateHighPriorityGroups() {\n /**\n * Evaluate groups with high priority clients\n */\n this.highPriorityGroups.forEach((group) => {\n group.ready = group.clients.every((c) => {\n // Give new clients another chance to join a group that is not \"high priority\"\n return c.userData?.currentCycle > 1;\n // return c.userData?.currentCycle >= this.maxWaitingCycles;\n });\n });\n }\n\n processGroupsReady() {\n this.groups.forEach(async (group) => {\n if (group.ready) {\n group.confirmed = 0;\n\n try {\n /**\n * Create room instance in the server.\n */\n const room = await this.onGroupReady.call(this, group);\n\n /**\n * Reserve a seat for each client in the group.\n * (If one fails, force all clients to leave, re-queueing is up to the client-side logic)\n */\n await matchMaker.reserveMultipleSeatsFor(\n room,\n group.clients.map((client) => ({\n sessionId: client.sessionId,\n options: client.userData.options,\n auth: client.auth,\n })),\n );\n\n /**\n * Send room data for new WebSocket connection!\n */\n group.clients.forEach((client, i) => {\n client.send(\"seat\", matchMaker.buildSeatReservation(room, client.sessionId));\n });\n\n } catch (e: any) {\n //\n // If creating a room, or reserving a seat failed - fail all clients\n // Whether the clients retry or not is up to the client-side logic\n //\n group.clients.forEach(client => client.leave(1011, e.message));\n }\n\n } else {\n /**\n * Notify clients within the group on how many players are in the queue\n */\n group.clients.forEach((client) => {\n //\n // avoid sending the same number of clients to the client if it hasn't changed\n //\n const queueClientCount = group.clients.length;\n if (client.userData.lastQueueClientCount !== queueClientCount) {\n client.userData.lastQueueClientCount = queueClientCount;\n client.send(\"clients\", queueClientCount);\n }\n });\n }\n });\n }\n\n}"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,
|
|
4
|
+
"sourcesContent": ["import { Room, type Client, matchMaker, type IRoomCache, debugMatchMaking, ServerError, ErrorCode } from \"@colyseus/core\";\n\nexport interface RankedQueueOptions {\n /**\n * number of players on each match\n */\n maxPlayers?: number;\n\n /**\n * name of the room to create\n */\n matchRoomName: string;\n\n /**\n * after these cycles, create a match with a bot\n */\n maxWaitingCycles?: number;\n\n /**\n * after this time, try to fit this client with a not-so-compatible group\n */\n maxWaitingCyclesForPriority?: number;\n\n /**\n * If set, teams must have the same size to be matched together\n */\n maxTeamSize?: number;\n\n /**\n * If `allowIncompleteGroups` is true, players inside an unmatched group (that\n * did not reached `maxPlayers`, and `maxWaitingCycles` has been\n * reached) will be matched together. Your room should fill the remaining\n * spots with \"bots\" on this case.\n */\n allowIncompleteGroups?: boolean;\n\n /**\n * Comparison function for matching clients to groups\n * Returns true if the client is compatible with the group\n */\n compare?: (client: ClientQueueData, matchGroup: MatchGroup) => boolean;\n\n /**\n *\n * When onGroupReady is set, the \"roomNameToCreate\" option is ignored.\n */\n onGroupReady?: (this: RankedQueueRoom, group: MatchGroup) => Promise<IRoomCache>;\n}\n\nexport interface MatchGroup {\n averageRank: number;\n clients: Array<Client<{ userData: ClientQueueData }>>,\n ready?: boolean;\n confirmed?: number;\n}\n\nexport interface MatchTeam {\n averageRank: number;\n clients: Array<Client<{ userData: ClientQueueData }>>,\n teamId: string | symbol;\n}\n\nexport interface ClientQueueData {\n /**\n * Rank of the client\n */\n rank: number;\n\n /**\n * Timestamp of when the client entered the queue\n */\n currentCycle?: number;\n\n /**\n * Optional: if matching with a team, the team ID\n */\n teamId?: string;\n\n /**\n * Additional options passed by the client when joining the room\n */\n options?: any;\n\n /**\n * Match group the client is currently in\n */\n group?: MatchGroup;\n\n /**\n * Whether the client has confirmed the connection to the room\n */\n confirmed?: boolean;\n\n /**\n * Whether the client should be prioritized in the queue\n * (e.g. for players that are waiting for a long time)\n */\n highPriority?: boolean;\n\n /**\n * The last number of clients in the queue sent to the client\n */\n lastQueueClientCount?: number;\n}\n\nconst DEFAULT_TEAM = Symbol(\"$default_team\");\nconst DEFAULT_COMPARE = (client: ClientQueueData, matchGroup: MatchGroup) => {\n const diff = Math.abs(client.rank - matchGroup.averageRank);\n const diffRatio = (diff / matchGroup.averageRank);\n // If diff ratio is too high, create a new match group\n return (diff < 10 || diffRatio <= 2);\n}\n\nexport class RankedQueueRoom extends Room {\n maxPlayers = 4;\n maxTeamSize: number;\n allowIncompleteGroups: boolean = false;\n\n maxWaitingCycles = 15;\n maxWaitingCyclesForPriority?: number = 10;\n\n /**\n * Evaluate groups for each client at interval\n */\n cycleTickInterval = 1000;\n\n /**\n * Groups of players per iteration\n */\n groups: MatchGroup[] = [];\n highPriorityGroups: MatchGroup[] = [];\n\n matchRoomName: string;\n\n protected compare = DEFAULT_COMPARE;\n protected onGroupReady = (group: MatchGroup) => matchMaker.createRoom(this.matchRoomName, {});\n\n messages = {\n confirm: (client: Client, _: unknown) => {\n const queueData = client.userData;\n\n if (queueData && queueData.group && typeof (queueData.group.confirmed) === \"number\") {\n queueData.confirmed = true;\n queueData.group.confirmed++;\n client.leave();\n }\n },\n }\n\n onCreate(options: RankedQueueOptions) {\n if (typeof(options.maxWaitingCycles) === \"number\") {\n this.maxWaitingCycles = options.maxWaitingCycles;\n }\n\n if (typeof(options.maxPlayers) === \"number\") {\n this.maxPlayers = options.maxPlayers;\n }\n\n if (typeof(options.maxTeamSize) === \"number\") {\n this.maxTeamSize = options.maxTeamSize;\n }\n\n if (typeof(options.allowIncompleteGroups) !== \"undefined\") {\n this.allowIncompleteGroups = options.allowIncompleteGroups;\n }\n\n if (typeof(options.compare) === \"function\") {\n this.compare = options.compare;\n }\n\n if (typeof(options.onGroupReady) === \"function\") {\n this.onGroupReady = options.onGroupReady;\n }\n\n if (options.matchRoomName) {\n this.matchRoomName = options.matchRoomName;\n\n } else {\n throw new ServerError(ErrorCode.APPLICATION_ERROR, \"RankedQueueRoom: 'matchRoomName' option is required.\");\n }\n\n debugMatchMaking(\"RankedQueueRoom#onCreate() maxPlayers: %d, maxWaitingCycles: %d, maxTeamSize: %d, allowIncompleteGroups: %d, roomNameToCreate: %s\", this.maxPlayers, this.maxWaitingCycles, this.maxTeamSize, this.allowIncompleteGroups, this.matchRoomName);\n\n /**\n * Redistribute clients into groups at every interval\n */\n this.setSimulationInterval(() => this.reassignMatchGroups(), this.cycleTickInterval);\n }\n\n onJoin(client: Client, options: any, auth?: unknown) {\n this.addToQueue(client, {\n rank: options.rank,\n teamId: options.teamId,\n options,\n });\n }\n\n addToQueue(client: Client, queueData: ClientQueueData) {\n if (queueData.currentCycle === undefined) {\n queueData.currentCycle = 0;\n }\n client.userData = queueData;\n\n // FIXME: reassign groups upon joining [?] (without incrementing cycle count)\n client.send(\"clients\", 1);\n }\n\n createMatchGroup() {\n const group: MatchGroup = { clients: [], averageRank: 0 };\n this.groups.push(group);\n return group;\n }\n\n reassignMatchGroups() {\n // Re-set all groups\n this.groups.length = 0;\n this.highPriorityGroups.length = 0;\n\n const sortedClients = (this.clients)\n .filter((client) => {\n // Filter out:\n // - clients that are not in the queue\n // - clients that are already in a \"ready\" group\n return (\n client.userData &&\n client.userData.group?.ready !== true\n );\n })\n .sort((a, b) => {\n //\n // Sort by rank ascending\n //\n return a.userData.rank - b.userData.rank;\n });\n\n //\n // The room either distribute by teams or by clients\n //\n if (typeof(this.maxTeamSize) === \"number\") {\n this.redistributeTeams(sortedClients);\n\n } else {\n this.redistributeClients(sortedClients);\n }\n\n this.evaluateHighPriorityGroups();\n this.processGroupsReady();\n }\n\n redistributeTeams(sortedClients: Client<{ userData: ClientQueueData }>[]) {\n const teamsByID: { [teamId: string | symbol]: MatchTeam } = {};\n\n sortedClients.forEach((client) => {\n const teamId = client.userData.teamId || DEFAULT_TEAM;\n\n // Create a new team if it doesn't exist\n if (!teamsByID[teamId]) {\n teamsByID[teamId] = { teamId: teamId, clients: [], averageRank: 0, };\n }\n\n teamsByID[teamId].averageRank += client.userData.rank;\n teamsByID[teamId].clients.push(client);\n });\n\n // Calculate average rank for each team\n let teams = Object.values(teamsByID).map((team) => {\n team.averageRank /= team.clients.length;\n return team;\n }).sort((a, b) => {\n // Sort by average rank ascending\n return a.averageRank - b.averageRank;\n });\n\n // Iterate over teams multiple times until all clients are assigned to a group\n do {\n let currentGroup: MatchGroup = this.createMatchGroup();\n teams = teams.filter((team) => {\n // Remove clients from the team and add them to the current group\n const totalRank = team.averageRank * team.clients.length;\n\n // currentGroup.averageRank = (currentGroup.averageRank === undefined)\n // ? team.averageRank\n // : (currentGroup.averageRank + team.averageRank) / ;\n currentGroup = this.redistributeClients(team.clients.splice(0, this.maxTeamSize), currentGroup, totalRank);\n\n if (team.clients.length >= this.maxTeamSize) {\n // team still has enough clients to form a group\n return true;\n }\n\n // increment cycle count for all clients in the team\n team.clients.forEach((client) => client.userData.currentCycle++);\n\n return false;\n });\n } while (teams.length >= 2);\n }\n\n redistributeClients(\n sortedClients: Client<{ userData: ClientQueueData }>[],\n currentGroup: MatchGroup = this.createMatchGroup(),\n totalRank: number = 0,\n ) {\n for (let i = 0, l = sortedClients.length; i < l; i++) {\n const client = sortedClients[i];\n const userData = client.userData;\n const currentCycle = userData.currentCycle++;\n\n if (currentGroup.averageRank > 0) {\n if (\n !this.compare(userData, currentGroup) &&\n !userData.highPriority\n ) {\n currentGroup = this.createMatchGroup();\n totalRank = 0;\n }\n }\n\n userData.group = currentGroup;\n currentGroup.clients.push(client);\n\n totalRank += userData.rank;\n currentGroup.averageRank = totalRank / currentGroup.clients.length;\n\n // Enough players in the group, mark it as ready!\n if (currentGroup.clients.length === this.maxPlayers) {\n currentGroup.ready = true;\n currentGroup = this.createMatchGroup();\n totalRank = 0;\n continue;\n }\n\n if (currentCycle >= this.maxWaitingCycles && this.allowIncompleteGroups) {\n /**\n * Match long-waiting clients with bots\n */\n if (this.highPriorityGroups.indexOf(currentGroup) === -1) {\n this.highPriorityGroups.push(currentGroup);\n }\n\n } else if (\n this.maxWaitingCyclesForPriority !== undefined &&\n currentCycle >= this.maxWaitingCyclesForPriority\n ) {\n /**\n * Force this client to join a group, even if rank is incompatible\n */\n userData.highPriority = true;\n }\n }\n\n return currentGroup;\n }\n\n evaluateHighPriorityGroups() {\n /**\n * Evaluate groups with high priority clients\n */\n this.highPriorityGroups.forEach((group) => {\n group.ready = group.clients.every((c) => {\n // Give new clients another chance to join a group that is not \"high priority\"\n return c.userData?.currentCycle > 1;\n // return c.userData?.currentCycle >= this.maxWaitingCycles;\n });\n });\n }\n\n processGroupsReady() {\n this.groups.forEach(async (group) => {\n if (group.ready) {\n group.confirmed = 0;\n\n try {\n /**\n * Create room instance in the server.\n */\n const room = await this.onGroupReady.call(this, group);\n\n /**\n * Reserve a seat for each client in the group.\n * (If one fails, force all clients to leave, re-queueing is up to the client-side logic)\n */\n await matchMaker.reserveMultipleSeatsFor(\n room,\n group.clients.map((client) => ({\n sessionId: client.sessionId,\n options: client.userData.options,\n auth: client.auth,\n })),\n );\n\n /**\n * Send room data for new WebSocket connection!\n */\n group.clients.forEach((client, i) => {\n client.send(\"seat\", matchMaker.buildSeatReservation(room, client.sessionId));\n });\n\n } catch (e: any) {\n //\n // If creating a room, or reserving a seat failed - fail all clients\n // Whether the clients retry or not is up to the client-side logic\n //\n group.clients.forEach(client => client.leave(1011, e.message));\n }\n\n } else {\n /**\n * Notify clients within the group on how many players are in the queue\n */\n group.clients.forEach((client) => {\n //\n // avoid sending the same number of clients to the client if it hasn't changed\n //\n const queueClientCount = group.clients.length;\n if (client.userData.lastQueueClientCount !== queueClientCount) {\n client.userData.lastQueueClientCount = queueClientCount;\n client.send(\"clients\", queueClientCount);\n }\n });\n }\n });\n }\n\n}"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/rooms/RankedQueueRoom.ts"],
|
|
4
|
-
"sourcesContent": ["import { Room, type Client, matchMaker, type IRoomCache, debugMatchMaking, ServerError, ErrorCode, isDevMode, } from \"@colyseus/core\";\n\nexport interface RankedQueueOptions {\n /**\n * number of players on each match\n */\n maxPlayers?: number;\n\n /**\n * name of the room to create\n */\n matchRoomName: string;\n\n /**\n * after these cycles, create a match with a bot\n */\n maxWaitingCycles?: number;\n\n /**\n * after this time, try to fit this client with a not-so-compatible group\n */\n maxWaitingCyclesForPriority?: number;\n\n /**\n * If set, teams must have the same size to be matched together\n */\n maxTeamSize?: number;\n\n /**\n * If `allowIncompleteGroups` is true, players inside an unmatched group (that\n * did not reached `maxPlayers`, and `maxWaitingCycles` has been\n * reached) will be matched together. Your room should fill the remaining\n * spots with \"bots\" on this case.\n */\n allowIncompleteGroups?: boolean;\n\n /**\n * Comparison function for matching clients to groups\n * Returns true if the client is compatible with the group\n */\n compare?: (client: ClientQueueData, matchGroup: MatchGroup) => boolean;\n\n /**\n *\n * When onGroupReady is set, the \"roomNameToCreate\" option is ignored.\n */\n onGroupReady?: (this: RankedQueueRoom, group: MatchGroup) => Promise<IRoomCache>;\n}\n\nexport interface MatchGroup {\n averageRank: number;\n clients: Array<Client<{ userData: ClientQueueData }>>,\n ready?: boolean;\n confirmed?: number;\n}\n\nexport interface MatchTeam {\n averageRank: number;\n clients: Array<Client<{ userData: ClientQueueData }>>,\n teamId: string | symbol;\n}\n\nexport interface ClientQueueData {\n /**\n * Rank of the client\n */\n rank: number;\n\n /**\n * Timestamp of when the client entered the queue\n */\n currentCycle?: number;\n\n /**\n * Optional: if matching with a team, the team ID\n */\n teamId?: string;\n\n /**\n * Additional options passed by the client when joining the room\n */\n options?: any;\n\n /**\n * Match group the client is currently in\n */\n group?: MatchGroup;\n\n /**\n * Whether the client has confirmed the connection to the room\n */\n confirmed?: boolean;\n\n /**\n * Whether the client should be prioritized in the queue\n * (e.g. for players that are waiting for a long time)\n */\n highPriority?: boolean;\n\n /**\n * The last number of clients in the queue sent to the client\n */\n lastQueueClientCount?: number;\n}\n\nconst DEFAULT_TEAM = Symbol(\"$default_team\");\nconst DEFAULT_COMPARE = (client: ClientQueueData, matchGroup: MatchGroup) => {\n const diff = Math.abs(client.rank - matchGroup.averageRank);\n const diffRatio = (diff / matchGroup.averageRank);\n // If diff ratio is too high, create a new match group\n return (diff < 10 || diffRatio <= 2);\n}\n\nexport class RankedQueueRoom extends Room {\n maxPlayers = 4;\n maxTeamSize: number;\n allowIncompleteGroups: boolean = false;\n\n maxWaitingCycles = 15;\n maxWaitingCyclesForPriority?: number = 10;\n\n /**\n * Evaluate groups for each client at interval\n */\n cycleTickInterval = 1000;\n\n /**\n * Groups of players per iteration\n */\n groups: MatchGroup[] = [];\n highPriorityGroups: MatchGroup[] = [];\n\n matchRoomName: string;\n\n protected compare = DEFAULT_COMPARE;\n protected onGroupReady = (group: MatchGroup) => matchMaker.createRoom(this.matchRoomName, {});\n\n messages = {\n confirm: (client: Client, _: unknown) => {\n const queueData = client.userData;\n\n if (queueData && queueData.group && typeof (queueData.group.confirmed) === \"number\") {\n queueData.confirmed = true;\n queueData.group.confirmed++;\n client.leave();\n }\n },\n }\n\n onCreate(options: RankedQueueOptions) {\n if (typeof(options.maxWaitingCycles) === \"number\") {\n this.maxWaitingCycles = options.maxWaitingCycles;\n }\n\n if (typeof(options.maxPlayers) === \"number\") {\n this.maxPlayers = options.maxPlayers;\n }\n\n if (typeof(options.maxTeamSize) === \"number\") {\n this.maxTeamSize = options.maxTeamSize;\n }\n\n if (typeof(options.allowIncompleteGroups) !== \"undefined\") {\n this.allowIncompleteGroups = options.allowIncompleteGroups;\n }\n\n if (typeof(options.compare) === \"function\") {\n this.compare = options.compare;\n }\n\n if (typeof(options.onGroupReady) === \"function\") {\n this.onGroupReady = options.onGroupReady;\n }\n\n if (options.matchRoomName) {\n this.matchRoomName = options.matchRoomName;\n\n } else {\n throw new ServerError(ErrorCode.APPLICATION_ERROR, \"RankedQueueRoom: 'matchRoomName' option is required.\");\n }\n\n debugMatchMaking(\"RankedQueueRoom#onCreate() maxPlayers: %d, maxWaitingCycles: %d, maxTeamSize: %d, allowIncompleteGroups: %d, roomNameToCreate: %s\", this.maxPlayers, this.maxWaitingCycles, this.maxTeamSize, this.allowIncompleteGroups, this.matchRoomName);\n\n /**\n * Redistribute clients into groups at every interval\n */\n this.setSimulationInterval(() => this.reassignMatchGroups(), this.cycleTickInterval);\n }\n\n onJoin(client: Client, options: any, auth?: unknown) {\n this.addToQueue(client, {\n rank: options.rank,\n teamId: options.teamId,\n options,\n });\n }\n\n addToQueue(client: Client, queueData: ClientQueueData) {\n if (queueData.currentCycle === undefined) {\n queueData.currentCycle = 0;\n }\n client.userData = queueData;\n\n // FIXME: reassign groups upon joining [?] (without incrementing cycle count)\n client.send(\"clients\", 1);\n }\n\n createMatchGroup() {\n const group: MatchGroup = { clients: [], averageRank: 0 };\n this.groups.push(group);\n return group;\n }\n\n reassignMatchGroups() {\n // Re-set all groups\n this.groups.length = 0;\n this.highPriorityGroups.length = 0;\n\n const sortedClients = (this.clients)\n .filter((client) => {\n // Filter out:\n // - clients that are not in the queue\n // - clients that are already in a \"ready\" group\n return (\n client.userData &&\n client.userData.group?.ready !== true\n );\n })\n .sort((a, b) => {\n //\n // Sort by rank ascending\n //\n return a.userData.rank - b.userData.rank;\n });\n\n //\n // The room either distribute by teams or by clients\n //\n if (typeof(this.maxTeamSize) === \"number\") {\n this.redistributeTeams(sortedClients);\n\n } else {\n this.redistributeClients(sortedClients);\n }\n\n this.evaluateHighPriorityGroups();\n this.processGroupsReady();\n }\n\n redistributeTeams(sortedClients: Client<{ userData: ClientQueueData }>[]) {\n const teamsByID: { [teamId: string | symbol]: MatchTeam } = {};\n\n sortedClients.forEach((client) => {\n const teamId = client.userData.teamId || DEFAULT_TEAM;\n\n // Create a new team if it doesn't exist\n if (!teamsByID[teamId]) {\n teamsByID[teamId] = { teamId: teamId, clients: [], averageRank: 0, };\n }\n\n teamsByID[teamId].averageRank += client.userData.rank;\n teamsByID[teamId].clients.push(client);\n });\n\n // Calculate average rank for each team\n let teams = Object.values(teamsByID).map((team) => {\n team.averageRank /= team.clients.length;\n return team;\n }).sort((a, b) => {\n // Sort by average rank ascending\n return a.averageRank - b.averageRank;\n });\n\n // Iterate over teams multiple times until all clients are assigned to a group\n do {\n let currentGroup: MatchGroup = this.createMatchGroup();\n teams = teams.filter((team) => {\n // Remove clients from the team and add them to the current group\n const totalRank = team.averageRank * team.clients.length;\n\n // currentGroup.averageRank = (currentGroup.averageRank === undefined)\n // ? team.averageRank\n // : (currentGroup.averageRank + team.averageRank) / ;\n currentGroup = this.redistributeClients(team.clients.splice(0, this.maxTeamSize), currentGroup, totalRank);\n\n if (team.clients.length >= this.maxTeamSize) {\n // team still has enough clients to form a group\n return true;\n }\n\n // increment cycle count for all clients in the team\n team.clients.forEach((client) => client.userData.currentCycle++);\n\n return false;\n });\n } while (teams.length >= 2);\n }\n\n redistributeClients(\n sortedClients: Client<{ userData: ClientQueueData }>[],\n currentGroup: MatchGroup = this.createMatchGroup(),\n totalRank: number = 0,\n ) {\n for (let i = 0, l = sortedClients.length; i < l; i++) {\n const client = sortedClients[i];\n const userData = client.userData;\n const currentCycle = userData.currentCycle++;\n\n if (currentGroup.averageRank > 0) {\n if (\n !this.compare(userData, currentGroup) &&\n !userData.highPriority\n ) {\n currentGroup = this.createMatchGroup();\n totalRank = 0;\n }\n }\n\n userData.group = currentGroup;\n currentGroup.clients.push(client);\n\n totalRank += userData.rank;\n currentGroup.averageRank = totalRank / currentGroup.clients.length;\n\n // Enough players in the group, mark it as ready!\n if (currentGroup.clients.length === this.maxPlayers) {\n currentGroup.ready = true;\n currentGroup = this.createMatchGroup();\n totalRank = 0;\n continue;\n }\n\n if (currentCycle >= this.maxWaitingCycles && this.allowIncompleteGroups) {\n /**\n * Match long-waiting clients with bots\n */\n if (this.highPriorityGroups.indexOf(currentGroup) === -1) {\n this.highPriorityGroups.push(currentGroup);\n }\n\n } else if (\n this.maxWaitingCyclesForPriority !== undefined &&\n currentCycle >= this.maxWaitingCyclesForPriority\n ) {\n /**\n * Force this client to join a group, even if rank is incompatible\n */\n userData.highPriority = true;\n }\n }\n\n return currentGroup;\n }\n\n evaluateHighPriorityGroups() {\n /**\n * Evaluate groups with high priority clients\n */\n this.highPriorityGroups.forEach((group) => {\n group.ready = group.clients.every((c) => {\n // Give new clients another chance to join a group that is not \"high priority\"\n return c.userData?.currentCycle > 1;\n // return c.userData?.currentCycle >= this.maxWaitingCycles;\n });\n });\n }\n\n processGroupsReady() {\n this.groups.forEach(async (group) => {\n if (group.ready) {\n group.confirmed = 0;\n\n try {\n /**\n * Create room instance in the server.\n */\n const room = await this.onGroupReady.call(this, group);\n\n /**\n * Reserve a seat for each client in the group.\n * (If one fails, force all clients to leave, re-queueing is up to the client-side logic)\n */\n await matchMaker.reserveMultipleSeatsFor(\n room,\n group.clients.map((client) => ({\n sessionId: client.sessionId,\n options: client.userData.options,\n auth: client.auth,\n })),\n );\n\n /**\n * Send room data for new WebSocket connection!\n */\n group.clients.forEach((client, i) => {\n client.send(\"seat\", matchMaker.buildSeatReservation(room, client.sessionId));\n });\n\n } catch (e: any) {\n //\n // If creating a room, or reserving a seat failed - fail all clients\n // Whether the clients retry or not is up to the client-side logic\n //\n group.clients.forEach(client => client.leave(1011, e.message));\n }\n\n } else {\n /**\n * Notify clients within the group on how many players are in the queue\n */\n group.clients.forEach((client) => {\n //\n // avoid sending the same number of clients to the client if it hasn't changed\n //\n const queueClientCount = group.clients.length;\n if (client.userData.lastQueueClientCount !== queueClientCount) {\n client.userData.lastQueueClientCount = queueClientCount;\n client.send(\"clients\", queueClientCount);\n }\n });\n }\n });\n }\n\n}"],
|
|
5
|
-
"mappings": ";AAAA,SAAS,MAAmB,YAA6B,kBAAkB,aAAa,
|
|
4
|
+
"sourcesContent": ["import { Room, type Client, matchMaker, type IRoomCache, debugMatchMaking, ServerError, ErrorCode } from \"@colyseus/core\";\n\nexport interface RankedQueueOptions {\n /**\n * number of players on each match\n */\n maxPlayers?: number;\n\n /**\n * name of the room to create\n */\n matchRoomName: string;\n\n /**\n * after these cycles, create a match with a bot\n */\n maxWaitingCycles?: number;\n\n /**\n * after this time, try to fit this client with a not-so-compatible group\n */\n maxWaitingCyclesForPriority?: number;\n\n /**\n * If set, teams must have the same size to be matched together\n */\n maxTeamSize?: number;\n\n /**\n * If `allowIncompleteGroups` is true, players inside an unmatched group (that\n * did not reached `maxPlayers`, and `maxWaitingCycles` has been\n * reached) will be matched together. Your room should fill the remaining\n * spots with \"bots\" on this case.\n */\n allowIncompleteGroups?: boolean;\n\n /**\n * Comparison function for matching clients to groups\n * Returns true if the client is compatible with the group\n */\n compare?: (client: ClientQueueData, matchGroup: MatchGroup) => boolean;\n\n /**\n *\n * When onGroupReady is set, the \"roomNameToCreate\" option is ignored.\n */\n onGroupReady?: (this: RankedQueueRoom, group: MatchGroup) => Promise<IRoomCache>;\n}\n\nexport interface MatchGroup {\n averageRank: number;\n clients: Array<Client<{ userData: ClientQueueData }>>,\n ready?: boolean;\n confirmed?: number;\n}\n\nexport interface MatchTeam {\n averageRank: number;\n clients: Array<Client<{ userData: ClientQueueData }>>,\n teamId: string | symbol;\n}\n\nexport interface ClientQueueData {\n /**\n * Rank of the client\n */\n rank: number;\n\n /**\n * Timestamp of when the client entered the queue\n */\n currentCycle?: number;\n\n /**\n * Optional: if matching with a team, the team ID\n */\n teamId?: string;\n\n /**\n * Additional options passed by the client when joining the room\n */\n options?: any;\n\n /**\n * Match group the client is currently in\n */\n group?: MatchGroup;\n\n /**\n * Whether the client has confirmed the connection to the room\n */\n confirmed?: boolean;\n\n /**\n * Whether the client should be prioritized in the queue\n * (e.g. for players that are waiting for a long time)\n */\n highPriority?: boolean;\n\n /**\n * The last number of clients in the queue sent to the client\n */\n lastQueueClientCount?: number;\n}\n\nconst DEFAULT_TEAM = Symbol(\"$default_team\");\nconst DEFAULT_COMPARE = (client: ClientQueueData, matchGroup: MatchGroup) => {\n const diff = Math.abs(client.rank - matchGroup.averageRank);\n const diffRatio = (diff / matchGroup.averageRank);\n // If diff ratio is too high, create a new match group\n return (diff < 10 || diffRatio <= 2);\n}\n\nexport class RankedQueueRoom extends Room {\n maxPlayers = 4;\n maxTeamSize: number;\n allowIncompleteGroups: boolean = false;\n\n maxWaitingCycles = 15;\n maxWaitingCyclesForPriority?: number = 10;\n\n /**\n * Evaluate groups for each client at interval\n */\n cycleTickInterval = 1000;\n\n /**\n * Groups of players per iteration\n */\n groups: MatchGroup[] = [];\n highPriorityGroups: MatchGroup[] = [];\n\n matchRoomName: string;\n\n protected compare = DEFAULT_COMPARE;\n protected onGroupReady = (group: MatchGroup) => matchMaker.createRoom(this.matchRoomName, {});\n\n messages = {\n confirm: (client: Client, _: unknown) => {\n const queueData = client.userData;\n\n if (queueData && queueData.group && typeof (queueData.group.confirmed) === \"number\") {\n queueData.confirmed = true;\n queueData.group.confirmed++;\n client.leave();\n }\n },\n }\n\n onCreate(options: RankedQueueOptions) {\n if (typeof(options.maxWaitingCycles) === \"number\") {\n this.maxWaitingCycles = options.maxWaitingCycles;\n }\n\n if (typeof(options.maxPlayers) === \"number\") {\n this.maxPlayers = options.maxPlayers;\n }\n\n if (typeof(options.maxTeamSize) === \"number\") {\n this.maxTeamSize = options.maxTeamSize;\n }\n\n if (typeof(options.allowIncompleteGroups) !== \"undefined\") {\n this.allowIncompleteGroups = options.allowIncompleteGroups;\n }\n\n if (typeof(options.compare) === \"function\") {\n this.compare = options.compare;\n }\n\n if (typeof(options.onGroupReady) === \"function\") {\n this.onGroupReady = options.onGroupReady;\n }\n\n if (options.matchRoomName) {\n this.matchRoomName = options.matchRoomName;\n\n } else {\n throw new ServerError(ErrorCode.APPLICATION_ERROR, \"RankedQueueRoom: 'matchRoomName' option is required.\");\n }\n\n debugMatchMaking(\"RankedQueueRoom#onCreate() maxPlayers: %d, maxWaitingCycles: %d, maxTeamSize: %d, allowIncompleteGroups: %d, roomNameToCreate: %s\", this.maxPlayers, this.maxWaitingCycles, this.maxTeamSize, this.allowIncompleteGroups, this.matchRoomName);\n\n /**\n * Redistribute clients into groups at every interval\n */\n this.setSimulationInterval(() => this.reassignMatchGroups(), this.cycleTickInterval);\n }\n\n onJoin(client: Client, options: any, auth?: unknown) {\n this.addToQueue(client, {\n rank: options.rank,\n teamId: options.teamId,\n options,\n });\n }\n\n addToQueue(client: Client, queueData: ClientQueueData) {\n if (queueData.currentCycle === undefined) {\n queueData.currentCycle = 0;\n }\n client.userData = queueData;\n\n // FIXME: reassign groups upon joining [?] (without incrementing cycle count)\n client.send(\"clients\", 1);\n }\n\n createMatchGroup() {\n const group: MatchGroup = { clients: [], averageRank: 0 };\n this.groups.push(group);\n return group;\n }\n\n reassignMatchGroups() {\n // Re-set all groups\n this.groups.length = 0;\n this.highPriorityGroups.length = 0;\n\n const sortedClients = (this.clients)\n .filter((client) => {\n // Filter out:\n // - clients that are not in the queue\n // - clients that are already in a \"ready\" group\n return (\n client.userData &&\n client.userData.group?.ready !== true\n );\n })\n .sort((a, b) => {\n //\n // Sort by rank ascending\n //\n return a.userData.rank - b.userData.rank;\n });\n\n //\n // The room either distribute by teams or by clients\n //\n if (typeof(this.maxTeamSize) === \"number\") {\n this.redistributeTeams(sortedClients);\n\n } else {\n this.redistributeClients(sortedClients);\n }\n\n this.evaluateHighPriorityGroups();\n this.processGroupsReady();\n }\n\n redistributeTeams(sortedClients: Client<{ userData: ClientQueueData }>[]) {\n const teamsByID: { [teamId: string | symbol]: MatchTeam } = {};\n\n sortedClients.forEach((client) => {\n const teamId = client.userData.teamId || DEFAULT_TEAM;\n\n // Create a new team if it doesn't exist\n if (!teamsByID[teamId]) {\n teamsByID[teamId] = { teamId: teamId, clients: [], averageRank: 0, };\n }\n\n teamsByID[teamId].averageRank += client.userData.rank;\n teamsByID[teamId].clients.push(client);\n });\n\n // Calculate average rank for each team\n let teams = Object.values(teamsByID).map((team) => {\n team.averageRank /= team.clients.length;\n return team;\n }).sort((a, b) => {\n // Sort by average rank ascending\n return a.averageRank - b.averageRank;\n });\n\n // Iterate over teams multiple times until all clients are assigned to a group\n do {\n let currentGroup: MatchGroup = this.createMatchGroup();\n teams = teams.filter((team) => {\n // Remove clients from the team and add them to the current group\n const totalRank = team.averageRank * team.clients.length;\n\n // currentGroup.averageRank = (currentGroup.averageRank === undefined)\n // ? team.averageRank\n // : (currentGroup.averageRank + team.averageRank) / ;\n currentGroup = this.redistributeClients(team.clients.splice(0, this.maxTeamSize), currentGroup, totalRank);\n\n if (team.clients.length >= this.maxTeamSize) {\n // team still has enough clients to form a group\n return true;\n }\n\n // increment cycle count for all clients in the team\n team.clients.forEach((client) => client.userData.currentCycle++);\n\n return false;\n });\n } while (teams.length >= 2);\n }\n\n redistributeClients(\n sortedClients: Client<{ userData: ClientQueueData }>[],\n currentGroup: MatchGroup = this.createMatchGroup(),\n totalRank: number = 0,\n ) {\n for (let i = 0, l = sortedClients.length; i < l; i++) {\n const client = sortedClients[i];\n const userData = client.userData;\n const currentCycle = userData.currentCycle++;\n\n if (currentGroup.averageRank > 0) {\n if (\n !this.compare(userData, currentGroup) &&\n !userData.highPriority\n ) {\n currentGroup = this.createMatchGroup();\n totalRank = 0;\n }\n }\n\n userData.group = currentGroup;\n currentGroup.clients.push(client);\n\n totalRank += userData.rank;\n currentGroup.averageRank = totalRank / currentGroup.clients.length;\n\n // Enough players in the group, mark it as ready!\n if (currentGroup.clients.length === this.maxPlayers) {\n currentGroup.ready = true;\n currentGroup = this.createMatchGroup();\n totalRank = 0;\n continue;\n }\n\n if (currentCycle >= this.maxWaitingCycles && this.allowIncompleteGroups) {\n /**\n * Match long-waiting clients with bots\n */\n if (this.highPriorityGroups.indexOf(currentGroup) === -1) {\n this.highPriorityGroups.push(currentGroup);\n }\n\n } else if (\n this.maxWaitingCyclesForPriority !== undefined &&\n currentCycle >= this.maxWaitingCyclesForPriority\n ) {\n /**\n * Force this client to join a group, even if rank is incompatible\n */\n userData.highPriority = true;\n }\n }\n\n return currentGroup;\n }\n\n evaluateHighPriorityGroups() {\n /**\n * Evaluate groups with high priority clients\n */\n this.highPriorityGroups.forEach((group) => {\n group.ready = group.clients.every((c) => {\n // Give new clients another chance to join a group that is not \"high priority\"\n return c.userData?.currentCycle > 1;\n // return c.userData?.currentCycle >= this.maxWaitingCycles;\n });\n });\n }\n\n processGroupsReady() {\n this.groups.forEach(async (group) => {\n if (group.ready) {\n group.confirmed = 0;\n\n try {\n /**\n * Create room instance in the server.\n */\n const room = await this.onGroupReady.call(this, group);\n\n /**\n * Reserve a seat for each client in the group.\n * (If one fails, force all clients to leave, re-queueing is up to the client-side logic)\n */\n await matchMaker.reserveMultipleSeatsFor(\n room,\n group.clients.map((client) => ({\n sessionId: client.sessionId,\n options: client.userData.options,\n auth: client.auth,\n })),\n );\n\n /**\n * Send room data for new WebSocket connection!\n */\n group.clients.forEach((client, i) => {\n client.send(\"seat\", matchMaker.buildSeatReservation(room, client.sessionId));\n });\n\n } catch (e: any) {\n //\n // If creating a room, or reserving a seat failed - fail all clients\n // Whether the clients retry or not is up to the client-side logic\n //\n group.clients.forEach(client => client.leave(1011, e.message));\n }\n\n } else {\n /**\n * Notify clients within the group on how many players are in the queue\n */\n group.clients.forEach((client) => {\n //\n // avoid sending the same number of clients to the client if it hasn't changed\n //\n const queueClientCount = group.clients.length;\n if (client.userData.lastQueueClientCount !== queueClientCount) {\n client.userData.lastQueueClientCount = queueClientCount;\n client.send(\"clients\", queueClientCount);\n }\n });\n }\n });\n }\n\n}"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colyseus/core",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.3",
|
|
4
4
|
"description": "Multiplayer Framework for Node.js.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"input": "./src/index.ts",
|
|
@@ -9,13 +9,15 @@
|
|
|
9
9
|
"typings": "./build/index.d.ts",
|
|
10
10
|
"exports": {
|
|
11
11
|
".": {
|
|
12
|
+
"@source": "./src/index.ts",
|
|
12
13
|
"types": "./build/index.d.ts",
|
|
13
|
-
"import": "./
|
|
14
|
+
"import": "./build/index.mjs",
|
|
14
15
|
"require": "./build/index.js"
|
|
15
16
|
},
|
|
16
17
|
"./*": {
|
|
18
|
+
"@source": "./src/*.ts",
|
|
17
19
|
"types": "./build/*.d.ts",
|
|
18
|
-
"import": "./
|
|
20
|
+
"import": "./build/*.mjs",
|
|
19
21
|
"require": "./build/*.js"
|
|
20
22
|
}
|
|
21
23
|
},
|
|
@@ -49,7 +51,7 @@
|
|
|
49
51
|
"debug": "^4.3.4",
|
|
50
52
|
"nanoid": "^3.3.11",
|
|
51
53
|
"@colyseus/better-call": "^1.0.26",
|
|
52
|
-
"@colyseus/greeting-banner": "^3.0.
|
|
54
|
+
"@colyseus/greeting-banner": "^3.0.3"
|
|
53
55
|
},
|
|
54
56
|
"devDependencies": {
|
|
55
57
|
"@colyseus/schema": "^4.0.1"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Room, type Client, matchMaker, type IRoomCache, debugMatchMaking, ServerError, ErrorCode
|
|
1
|
+
import { Room, type Client, matchMaker, type IRoomCache, debugMatchMaking, ServerError, ErrorCode } from "@colyseus/core";
|
|
2
2
|
|
|
3
3
|
export interface RankedQueueOptions {
|
|
4
4
|
/**
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
var api_exports = {};
|
|
20
|
-
__export(api_exports, {
|
|
21
|
-
getLockId: () => getLockId,
|
|
22
|
-
initializeRoomCache: () => initializeRoomCache
|
|
23
|
-
});
|
|
24
|
-
module.exports = __toCommonJS(api_exports);
|
|
25
|
-
function getLockId(filterOptions) {
|
|
26
|
-
return Object.keys(filterOptions).map((key) => `${key}:${filterOptions[key]}`).join("-");
|
|
27
|
-
}
|
|
28
|
-
function initializeRoomCache(initialValues = {}) {
|
|
29
|
-
return {
|
|
30
|
-
clients: 0,
|
|
31
|
-
maxClients: Infinity,
|
|
32
|
-
locked: false,
|
|
33
|
-
private: false,
|
|
34
|
-
metadata: void 0,
|
|
35
|
-
// name: '',
|
|
36
|
-
// publicAddress: '',
|
|
37
|
-
// processId: '',
|
|
38
|
-
// roomId: '',
|
|
39
|
-
createdAt: initialValues && initialValues.createdAt ? new Date(initialValues.createdAt) : /* @__PURE__ */ new Date(),
|
|
40
|
-
unlisted: false,
|
|
41
|
-
...initialValues
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
45
|
-
0 && (module.exports = {
|
|
46
|
-
getLockId,
|
|
47
|
-
initializeRoomCache
|
|
48
|
-
});
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/matchmaker/driver/api.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Room } from \"@colyseus/core\";\nimport type { Type } from \"../../utils/Utils.ts\";\n\n/**\n * Sort options for room queries.\n */\nexport interface SortOptions {\n [fieldName: string]: 1 | -1 | 'asc' | 'desc' | 'ascending' | 'descending';\n}\n\n/**\n * Built-in room cache fields that can be used for sorting.\n */\nexport type IRoomCacheSortByKeys = 'clients' | 'maxClients' | 'createdAt';\n\n/**\n * Built-in room cache fields that can be used for filtering.\n */\nexport type IRoomCacheFilterByKeys = 'clients' | 'maxClients' | 'processId';\n\n/**\n * Extract metadata type from Room type\n */\nexport type ExtractMetadata<RoomType extends Room> =\n RoomType extends Room<infer M> ? M : any;\n\n/**\n * Generates a unique lock ID based on filter options.\n */\nexport function getLockId(filterOptions: any) {\n return Object.keys(filterOptions).map((key) => `${key}:${filterOptions[key]}`).join(\"-\");\n}\n\n/**\n * Initialize a room cache which contains CRUD operations for room listings.\n *\n * @internal\n * @param initialValues - Predefined room properties.\n * @returns RoomData - New room cache.\n */\nexport function initializeRoomCache(initialValues: Partial<IRoomCache> = {}): IRoomCache {\n return {\n clients: 0,\n maxClients: Infinity,\n locked: false,\n private: false,\n metadata: undefined,\n // name: '',\n // publicAddress: '',\n // processId: '',\n // roomId: '',\n createdAt: (initialValues && initialValues.createdAt) ? new Date(initialValues.createdAt) : new Date(),\n unlisted: false,\n ...initialValues,\n } as IRoomCache;\n}\n\nexport interface IRoomCache<Metadata = any> {\n /**\n * Room name.\n */\n name: string;\n\n /**\n * Unique identifier for the room.\n */\n roomId: string;\n\n /**\n * Process id where the room is running.\n */\n processId: string;\n\n /**\n * Number of clients connected to this room.\n */\n clients: number;\n\n /**\n * Maximum number of clients allowed to join the room.\n */\n maxClients: number;\n\n /**\n * Indicates if the room is locked (i.e. join requests are rejected).\n */\n locked?: boolean;\n\n /**\n * Indicates if the room is private\n * Private rooms can't be joined via `join()` or `joinOrCreate()`.\n */\n private?: boolean;\n\n /**\n * Public address of the server.\n */\n publicAddress?: string;\n\n /**\n * Do not show this room in lobby listing.\n */\n unlisted?: boolean;\n\n /**\n * Metadata associated with the room.\n */\n metadata?: Metadata;\n\n /**\n * When the room was created.\n */\n createdAt?: Date;\n}\n\nexport interface MatchMakerDriver {\n /**\n * Check if a room exists in room cache.\n *\n * @param roomId - The room id.\n *\n * @returns Promise<boolean> | boolean - A promise or a boolean value indicating if the room exists.\n */\n has(roomId: string): Promise<boolean> | boolean;\n\n /**\n * Query rooms in room cache for given conditions.\n *\n * @param conditions - Filtering conditions.\n *\n * @returns Promise<IRoomCache[]> | IRoomCache[] - A promise or an object contaning room metadata list.\n */\n query<T extends Room = any>(\n conditions: Partial<IRoomCache & ExtractMetadata<T>>,\n sortOptions?: SortOptions\n ): Promise<Array<IRoomCache<ExtractMetadata<T>>>> | Array<IRoomCache<ExtractMetadata<T>>>;\n\n /**\n * Clean up rooms in room cache by process id.\n * @param processId - The process id.\n */\n cleanup?(processId: string): Promise<void>;\n\n /**\n * Query for a room in room cache for given conditions.\n *\n * @param conditions - Filtering conditions.\n *\n * @returns `IRoomCache` - An object contaning filtered room metadata.\n */\n findOne<T extends Room = any>(\n conditions: Partial<IRoomCache & ExtractMetadata<T>>,\n sortOptions?: SortOptions\n ): Promise<IRoomCache<ExtractMetadata<T>>>;\n\n /**\n * Remove a room from room cache.\n *\n * @param roomId - The room id.\n */\n remove(roomId: string): Promise<boolean> | boolean;\n\n /**\n * Update a room in room cache.\n *\n * @param IRoomCache - The room to update.\n * @param operations - The operations to update the room.\n */\n update(\n room: IRoomCache,\n operations: Partial<{ $set: Partial<IRoomCache>, $inc: Partial<IRoomCache> }>\n ): Promise<boolean> | boolean;\n\n /**\n * Persist a room in room cache.\n *\n * @param room - The room to persist.\n * @param create - If true, create a new record. If false (default), update existing record.\n */\n persist(room: IRoomCache, create?: boolean): Promise<boolean> | boolean;\n\n /**\n * Empty the room cache. Used for testing purposes only.\n * @internal Do not call this method yourself.\n */\n clear(): void;\n\n /**\n * Boot the room cache medium (if available).\n */\n boot?(): Promise<void>;\n\n /**\n * Dispose the connection of the room cache medium.\n */\n shutdown(): void;\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6BO,SAAS,UAAU,eAAoB;AAC5C,SAAO,OAAO,KAAK,aAAa,EAAE,IAAI,CAAC,QAAQ,GAAG,GAAG,IAAI,cAAc,GAAG,CAAC,EAAE,EAAE,KAAK,GAAG;AACzF;AASO,SAAS,oBAAoB,gBAAqC,CAAC,GAAe;AACvF,SAAO;AAAA,IACL,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA,IAKV,WAAY,iBAAiB,cAAc,YAAa,IAAI,KAAK,cAAc,SAAS,IAAI,oBAAI,KAAK;AAAA,IACrG,UAAU;AAAA,IACV,GAAG;AAAA,EACL;AACF;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
// packages/core/src/matchmaker/driver/api.ts
|
|
2
|
-
function getLockId(filterOptions) {
|
|
3
|
-
return Object.keys(filterOptions).map((key) => `${key}:${filterOptions[key]}`).join("-");
|
|
4
|
-
}
|
|
5
|
-
function initializeRoomCache(initialValues = {}) {
|
|
6
|
-
return {
|
|
7
|
-
clients: 0,
|
|
8
|
-
maxClients: Infinity,
|
|
9
|
-
locked: false,
|
|
10
|
-
private: false,
|
|
11
|
-
metadata: void 0,
|
|
12
|
-
// name: '',
|
|
13
|
-
// publicAddress: '',
|
|
14
|
-
// processId: '',
|
|
15
|
-
// roomId: '',
|
|
16
|
-
createdAt: initialValues && initialValues.createdAt ? new Date(initialValues.createdAt) : /* @__PURE__ */ new Date(),
|
|
17
|
-
unlisted: false,
|
|
18
|
-
...initialValues
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
export {
|
|
22
|
-
getLockId,
|
|
23
|
-
initializeRoomCache
|
|
24
|
-
};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/matchmaker/driver/api.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Room } from \"@colyseus/core\";\nimport type { Type } from \"../../utils/Utils.ts\";\n\n/**\n * Sort options for room queries.\n */\nexport interface SortOptions {\n [fieldName: string]: 1 | -1 | 'asc' | 'desc' | 'ascending' | 'descending';\n}\n\n/**\n * Built-in room cache fields that can be used for sorting.\n */\nexport type IRoomCacheSortByKeys = 'clients' | 'maxClients' | 'createdAt';\n\n/**\n * Built-in room cache fields that can be used for filtering.\n */\nexport type IRoomCacheFilterByKeys = 'clients' | 'maxClients' | 'processId';\n\n/**\n * Extract metadata type from Room type\n */\nexport type ExtractMetadata<RoomType extends Room> =\n RoomType extends Room<infer M> ? M : any;\n\n/**\n * Generates a unique lock ID based on filter options.\n */\nexport function getLockId(filterOptions: any) {\n return Object.keys(filterOptions).map((key) => `${key}:${filterOptions[key]}`).join(\"-\");\n}\n\n/**\n * Initialize a room cache which contains CRUD operations for room listings.\n *\n * @internal\n * @param initialValues - Predefined room properties.\n * @returns RoomData - New room cache.\n */\nexport function initializeRoomCache(initialValues: Partial<IRoomCache> = {}): IRoomCache {\n return {\n clients: 0,\n maxClients: Infinity,\n locked: false,\n private: false,\n metadata: undefined,\n // name: '',\n // publicAddress: '',\n // processId: '',\n // roomId: '',\n createdAt: (initialValues && initialValues.createdAt) ? new Date(initialValues.createdAt) : new Date(),\n unlisted: false,\n ...initialValues,\n } as IRoomCache;\n}\n\nexport interface IRoomCache<Metadata = any> {\n /**\n * Room name.\n */\n name: string;\n\n /**\n * Unique identifier for the room.\n */\n roomId: string;\n\n /**\n * Process id where the room is running.\n */\n processId: string;\n\n /**\n * Number of clients connected to this room.\n */\n clients: number;\n\n /**\n * Maximum number of clients allowed to join the room.\n */\n maxClients: number;\n\n /**\n * Indicates if the room is locked (i.e. join requests are rejected).\n */\n locked?: boolean;\n\n /**\n * Indicates if the room is private\n * Private rooms can't be joined via `join()` or `joinOrCreate()`.\n */\n private?: boolean;\n\n /**\n * Public address of the server.\n */\n publicAddress?: string;\n\n /**\n * Do not show this room in lobby listing.\n */\n unlisted?: boolean;\n\n /**\n * Metadata associated with the room.\n */\n metadata?: Metadata;\n\n /**\n * When the room was created.\n */\n createdAt?: Date;\n}\n\nexport interface MatchMakerDriver {\n /**\n * Check if a room exists in room cache.\n *\n * @param roomId - The room id.\n *\n * @returns Promise<boolean> | boolean - A promise or a boolean value indicating if the room exists.\n */\n has(roomId: string): Promise<boolean> | boolean;\n\n /**\n * Query rooms in room cache for given conditions.\n *\n * @param conditions - Filtering conditions.\n *\n * @returns Promise<IRoomCache[]> | IRoomCache[] - A promise or an object contaning room metadata list.\n */\n query<T extends Room = any>(\n conditions: Partial<IRoomCache & ExtractMetadata<T>>,\n sortOptions?: SortOptions\n ): Promise<Array<IRoomCache<ExtractMetadata<T>>>> | Array<IRoomCache<ExtractMetadata<T>>>;\n\n /**\n * Clean up rooms in room cache by process id.\n * @param processId - The process id.\n */\n cleanup?(processId: string): Promise<void>;\n\n /**\n * Query for a room in room cache for given conditions.\n *\n * @param conditions - Filtering conditions.\n *\n * @returns `IRoomCache` - An object contaning filtered room metadata.\n */\n findOne<T extends Room = any>(\n conditions: Partial<IRoomCache & ExtractMetadata<T>>,\n sortOptions?: SortOptions\n ): Promise<IRoomCache<ExtractMetadata<T>>>;\n\n /**\n * Remove a room from room cache.\n *\n * @param roomId - The room id.\n */\n remove(roomId: string): Promise<boolean> | boolean;\n\n /**\n * Update a room in room cache.\n *\n * @param IRoomCache - The room to update.\n * @param operations - The operations to update the room.\n */\n update(\n room: IRoomCache,\n operations: Partial<{ $set: Partial<IRoomCache>, $inc: Partial<IRoomCache> }>\n ): Promise<boolean> | boolean;\n\n /**\n * Persist a room in room cache.\n *\n * @param room - The room to persist.\n * @param create - If true, create a new record. If false (default), update existing record.\n */\n persist(room: IRoomCache, create?: boolean): Promise<boolean> | boolean;\n\n /**\n * Empty the room cache. Used for testing purposes only.\n * @internal Do not call this method yourself.\n */\n clear(): void;\n\n /**\n * Boot the room cache medium (if available).\n */\n boot?(): Promise<void>;\n\n /**\n * Dispose the connection of the room cache medium.\n */\n shutdown(): void;\n}\n"],
|
|
5
|
-
"mappings": ";AA6BO,SAAS,UAAU,eAAoB;AAC5C,SAAO,OAAO,KAAK,aAAa,EAAE,IAAI,CAAC,QAAQ,GAAG,GAAG,IAAI,cAAc,GAAG,CAAC,EAAE,EAAE,KAAK,GAAG;AACzF;AASO,SAAS,oBAAoB,gBAAqC,CAAC,GAAe;AACvF,SAAO;AAAA,IACL,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA,IAKV,WAAY,iBAAiB,cAAc,YAAa,IAAI,KAAK,cAAc,SAAS,IAAI,oBAAI,KAAK;AAAA,IACrG,UAAU;AAAA,IACV,GAAG;AAAA,EACL;AACF;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
var LocalDriver_exports = {};
|
|
20
|
-
__export(LocalDriver_exports, {
|
|
21
|
-
LocalDriver: () => LocalDriver
|
|
22
|
-
});
|
|
23
|
-
module.exports = __toCommonJS(LocalDriver_exports);
|
|
24
|
-
var import_Debug = require("../../../Debug.ts");
|
|
25
|
-
var import_Query = require("./Query.ts");
|
|
26
|
-
class LocalDriver {
|
|
27
|
-
constructor() {
|
|
28
|
-
this.rooms = [];
|
|
29
|
-
}
|
|
30
|
-
has(roomId) {
|
|
31
|
-
return this.rooms.some((room) => room.roomId === roomId);
|
|
32
|
-
}
|
|
33
|
-
query(conditions, sortOptions) {
|
|
34
|
-
const query = new import_Query.Query(this.rooms, conditions);
|
|
35
|
-
if (sortOptions) {
|
|
36
|
-
query.sort(sortOptions);
|
|
37
|
-
}
|
|
38
|
-
return query.filter(conditions);
|
|
39
|
-
}
|
|
40
|
-
cleanup(processId) {
|
|
41
|
-
const cachedRooms = this.query({ processId });
|
|
42
|
-
(0, import_Debug.debugMatchMaking)("removing stale rooms by processId %s (%s rooms found)", processId, cachedRooms.length);
|
|
43
|
-
cachedRooms.forEach((room) => this.remove(room.roomId));
|
|
44
|
-
return Promise.resolve();
|
|
45
|
-
}
|
|
46
|
-
findOne(conditions, sortOptions) {
|
|
47
|
-
const query = new import_Query.Query(this.rooms, conditions);
|
|
48
|
-
if (sortOptions) {
|
|
49
|
-
query.sort(sortOptions);
|
|
50
|
-
}
|
|
51
|
-
return query;
|
|
52
|
-
}
|
|
53
|
-
update(room, operations) {
|
|
54
|
-
if (operations.$set) {
|
|
55
|
-
for (const field in operations.$set) {
|
|
56
|
-
if (operations.$set.hasOwnProperty(field)) {
|
|
57
|
-
room[field] = operations.$set[field];
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
if (operations.$inc) {
|
|
62
|
-
for (const field in operations.$inc) {
|
|
63
|
-
if (operations.$inc.hasOwnProperty(field)) {
|
|
64
|
-
room[field] += operations.$inc[field];
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
persist(room, create = false) {
|
|
71
|
-
if (!create) {
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
this.rooms.push(room);
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
remove(roomId) {
|
|
78
|
-
const roomIndex = this.rooms.findIndex((room) => room.roomId === roomId);
|
|
79
|
-
if (roomIndex !== -1) {
|
|
80
|
-
this.rooms.splice(roomIndex, 1);
|
|
81
|
-
return true;
|
|
82
|
-
}
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
clear() {
|
|
86
|
-
this.rooms = [];
|
|
87
|
-
}
|
|
88
|
-
shutdown() {
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
92
|
-
0 && (module.exports = {
|
|
93
|
-
LocalDriver
|
|
94
|
-
});
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../../src/matchmaker/driver/local/LocalDriver.ts"],
|
|
4
|
-
"sourcesContent": ["import { debugMatchMaking } from '../../../Debug.ts';\nimport type { IRoomCache, SortOptions, MatchMakerDriver } from '../api.ts';\nimport { Query } from './Query.ts';\n\n// re-export\nexport type { IRoomCache, SortOptions, MatchMakerDriver };\n\nexport class LocalDriver implements MatchMakerDriver {\n public rooms: IRoomCache[] = [];\n\n public has(roomId: string) {\n return this.rooms.some((room) => room.roomId === roomId);\n }\n\n public query(conditions: Partial<IRoomCache>, sortOptions?: SortOptions) {\n const query = new Query<IRoomCache>(this.rooms, conditions);\n\n if (sortOptions) {\n query.sort(sortOptions);\n }\n\n return query.filter(conditions);\n }\n\n public cleanup(processId: string) {\n const cachedRooms = this.query({ processId });\n debugMatchMaking(\"removing stale rooms by processId %s (%s rooms found)\", processId, cachedRooms.length);\n\n cachedRooms.forEach((room) => this.remove(room.roomId));\n return Promise.resolve();\n }\n\n public findOne(conditions: Partial<IRoomCache>, sortOptions?: SortOptions) {\n const query = new Query<IRoomCache>(this.rooms, conditions);\n\n if (sortOptions) {\n query.sort(sortOptions);\n }\n\n return query as unknown as Promise<IRoomCache>;\n }\n\n public update(room: IRoomCache, operations: Partial<{ $set: Partial<IRoomCache>, $inc: Partial<IRoomCache> }>) {\n if (operations.$set) {\n for (const field in operations.$set) {\n if (operations.$set.hasOwnProperty(field)) {\n room[field] = operations.$set[field];\n }\n }\n }\n\n if (operations.$inc) {\n for (const field in operations.$inc) {\n if (operations.$inc.hasOwnProperty(field)) {\n room[field] += operations.$inc[field];\n }\n }\n }\n\n return true;\n }\n\n public persist(room: IRoomCache, create: boolean = false) {\n // if (this.rooms.indexOf(room) !== -1) {\n // // already in the list\n // return true;\n // }\n\n if (!create) { return false; }\n\n // add to the list\n this.rooms.push(room);\n\n return true;\n }\n\n public remove(roomId: string) {\n const roomIndex = this.rooms.findIndex((room) => room.roomId === roomId);\n if (roomIndex !== -1) {\n this.rooms.splice(roomIndex, 1);\n return true;\n }\n return false;\n }\n\n public clear() {\n this.rooms = [];\n }\n\n public shutdown() {\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAiC;AAEjC,mBAAsB;AAKf,MAAM,YAAwC;AAAA,EAA9C;AACL,SAAO,QAAsB,CAAC;AAAA;AAAA,EAEvB,IAAI,QAAgB;AACzB,WAAO,KAAK,MAAM,KAAK,CAAC,SAAS,KAAK,WAAW,MAAM;AAAA,EACzD;AAAA,EAEO,MAAM,YAAiC,aAA2B;AACvE,UAAM,QAAQ,IAAI,mBAAkB,KAAK,OAAO,UAAU;AAE1D,QAAI,aAAa;AACf,YAAM,KAAK,WAAW;AAAA,IACxB;AAEA,WAAO,MAAM,OAAO,UAAU;AAAA,EAChC;AAAA,EAEO,QAAQ,WAAmB;AAChC,UAAM,cAAc,KAAK,MAAM,EAAE,UAAU,CAAC;AAC5C,uCAAiB,yDAAyD,WAAW,YAAY,MAAM;AAEvG,gBAAY,QAAQ,CAAC,SAAS,KAAK,OAAO,KAAK,MAAM,CAAC;AACtD,WAAO,QAAQ,QAAQ;AAAA,EACzB;AAAA,EAEO,QAAQ,YAAiC,aAA2B;AACzE,UAAM,QAAQ,IAAI,mBAAkB,KAAK,OAAO,UAAU;AAE1D,QAAI,aAAa;AACf,YAAM,KAAK,WAAW;AAAA,IACxB;AAEA,WAAO;AAAA,EACT;AAAA,EAEO,OAAO,MAAkB,YAA+E;AAC7G,QAAI,WAAW,MAAM;AACnB,iBAAW,SAAS,WAAW,MAAM;AACnC,YAAI,WAAW,KAAK,eAAe,KAAK,GAAG;AACzC,eAAK,KAAK,IAAI,WAAW,KAAK,KAAK;AAAA,QACrC;AAAA,MACF;AAAA,IACF;AAEA,QAAI,WAAW,MAAM;AACnB,iBAAW,SAAS,WAAW,MAAM;AACnC,YAAI,WAAW,KAAK,eAAe,KAAK,GAAG;AACzC,eAAK,KAAK,KAAK,WAAW,KAAK,KAAK;AAAA,QACtC;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEO,QAAQ,MAAkB,SAAkB,OAAO;AAMxD,QAAI,CAAC,QAAQ;AAAE,aAAO;AAAA,IAAO;AAG7B,SAAK,MAAM,KAAK,IAAI;AAEpB,WAAO;AAAA,EACT;AAAA,EAEO,OAAO,QAAgB;AAC5B,UAAM,YAAY,KAAK,MAAM,UAAU,CAAC,SAAS,KAAK,WAAW,MAAM;AACvE,QAAI,cAAc,IAAI;AACpB,WAAK,MAAM,OAAO,WAAW,CAAC;AAC9B,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA,EAEO,QAAQ;AACb,SAAK,QAAQ,CAAC;AAAA,EAChB;AAAA,EAEO,WAAW;AAAA,EAClB;AACF;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
// packages/core/src/matchmaker/driver/local/LocalDriver.ts
|
|
2
|
-
import { debugMatchMaking } from "../../../Debug.mjs";
|
|
3
|
-
import { Query } from "./Query.mjs";
|
|
4
|
-
var LocalDriver = class {
|
|
5
|
-
constructor() {
|
|
6
|
-
this.rooms = [];
|
|
7
|
-
}
|
|
8
|
-
has(roomId) {
|
|
9
|
-
return this.rooms.some((room) => room.roomId === roomId);
|
|
10
|
-
}
|
|
11
|
-
query(conditions, sortOptions) {
|
|
12
|
-
const query = new Query(this.rooms, conditions);
|
|
13
|
-
if (sortOptions) {
|
|
14
|
-
query.sort(sortOptions);
|
|
15
|
-
}
|
|
16
|
-
return query.filter(conditions);
|
|
17
|
-
}
|
|
18
|
-
cleanup(processId) {
|
|
19
|
-
const cachedRooms = this.query({ processId });
|
|
20
|
-
debugMatchMaking("removing stale rooms by processId %s (%s rooms found)", processId, cachedRooms.length);
|
|
21
|
-
cachedRooms.forEach((room) => this.remove(room.roomId));
|
|
22
|
-
return Promise.resolve();
|
|
23
|
-
}
|
|
24
|
-
findOne(conditions, sortOptions) {
|
|
25
|
-
const query = new Query(this.rooms, conditions);
|
|
26
|
-
if (sortOptions) {
|
|
27
|
-
query.sort(sortOptions);
|
|
28
|
-
}
|
|
29
|
-
return query;
|
|
30
|
-
}
|
|
31
|
-
update(room, operations) {
|
|
32
|
-
if (operations.$set) {
|
|
33
|
-
for (const field in operations.$set) {
|
|
34
|
-
if (operations.$set.hasOwnProperty(field)) {
|
|
35
|
-
room[field] = operations.$set[field];
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
if (operations.$inc) {
|
|
40
|
-
for (const field in operations.$inc) {
|
|
41
|
-
if (operations.$inc.hasOwnProperty(field)) {
|
|
42
|
-
room[field] += operations.$inc[field];
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
persist(room, create = false) {
|
|
49
|
-
if (!create) {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
this.rooms.push(room);
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
remove(roomId) {
|
|
56
|
-
const roomIndex = this.rooms.findIndex((room) => room.roomId === roomId);
|
|
57
|
-
if (roomIndex !== -1) {
|
|
58
|
-
this.rooms.splice(roomIndex, 1);
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
clear() {
|
|
64
|
-
this.rooms = [];
|
|
65
|
-
}
|
|
66
|
-
shutdown() {
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
export {
|
|
70
|
-
LocalDriver
|
|
71
|
-
};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../../src/matchmaker/driver/local/LocalDriver.ts"],
|
|
4
|
-
"sourcesContent": ["import { debugMatchMaking } from '../../../Debug.ts';\nimport type { IRoomCache, SortOptions, MatchMakerDriver } from '../api.ts';\nimport { Query } from './Query.ts';\n\n// re-export\nexport type { IRoomCache, SortOptions, MatchMakerDriver };\n\nexport class LocalDriver implements MatchMakerDriver {\n public rooms: IRoomCache[] = [];\n\n public has(roomId: string) {\n return this.rooms.some((room) => room.roomId === roomId);\n }\n\n public query(conditions: Partial<IRoomCache>, sortOptions?: SortOptions) {\n const query = new Query<IRoomCache>(this.rooms, conditions);\n\n if (sortOptions) {\n query.sort(sortOptions);\n }\n\n return query.filter(conditions);\n }\n\n public cleanup(processId: string) {\n const cachedRooms = this.query({ processId });\n debugMatchMaking(\"removing stale rooms by processId %s (%s rooms found)\", processId, cachedRooms.length);\n\n cachedRooms.forEach((room) => this.remove(room.roomId));\n return Promise.resolve();\n }\n\n public findOne(conditions: Partial<IRoomCache>, sortOptions?: SortOptions) {\n const query = new Query<IRoomCache>(this.rooms, conditions);\n\n if (sortOptions) {\n query.sort(sortOptions);\n }\n\n return query as unknown as Promise<IRoomCache>;\n }\n\n public update(room: IRoomCache, operations: Partial<{ $set: Partial<IRoomCache>, $inc: Partial<IRoomCache> }>) {\n if (operations.$set) {\n for (const field in operations.$set) {\n if (operations.$set.hasOwnProperty(field)) {\n room[field] = operations.$set[field];\n }\n }\n }\n\n if (operations.$inc) {\n for (const field in operations.$inc) {\n if (operations.$inc.hasOwnProperty(field)) {\n room[field] += operations.$inc[field];\n }\n }\n }\n\n return true;\n }\n\n public persist(room: IRoomCache, create: boolean = false) {\n // if (this.rooms.indexOf(room) !== -1) {\n // // already in the list\n // return true;\n // }\n\n if (!create) { return false; }\n\n // add to the list\n this.rooms.push(room);\n\n return true;\n }\n\n public remove(roomId: string) {\n const roomIndex = this.rooms.findIndex((room) => room.roomId === roomId);\n if (roomIndex !== -1) {\n this.rooms.splice(roomIndex, 1);\n return true;\n }\n return false;\n }\n\n public clear() {\n this.rooms = [];\n }\n\n public shutdown() {\n }\n}\n"],
|
|
5
|
-
"mappings": ";AAAA,SAAS,wBAAwB;AAEjC,SAAS,aAAa;AAKf,IAAM,cAAN,MAA8C;AAAA,EAA9C;AACL,SAAO,QAAsB,CAAC;AAAA;AAAA,EAEvB,IAAI,QAAgB;AACzB,WAAO,KAAK,MAAM,KAAK,CAAC,SAAS,KAAK,WAAW,MAAM;AAAA,EACzD;AAAA,EAEO,MAAM,YAAiC,aAA2B;AACvE,UAAM,QAAQ,IAAI,MAAkB,KAAK,OAAO,UAAU;AAE1D,QAAI,aAAa;AACf,YAAM,KAAK,WAAW;AAAA,IACxB;AAEA,WAAO,MAAM,OAAO,UAAU;AAAA,EAChC;AAAA,EAEO,QAAQ,WAAmB;AAChC,UAAM,cAAc,KAAK,MAAM,EAAE,UAAU,CAAC;AAC5C,qBAAiB,yDAAyD,WAAW,YAAY,MAAM;AAEvG,gBAAY,QAAQ,CAAC,SAAS,KAAK,OAAO,KAAK,MAAM,CAAC;AACtD,WAAO,QAAQ,QAAQ;AAAA,EACzB;AAAA,EAEO,QAAQ,YAAiC,aAA2B;AACzE,UAAM,QAAQ,IAAI,MAAkB,KAAK,OAAO,UAAU;AAE1D,QAAI,aAAa;AACf,YAAM,KAAK,WAAW;AAAA,IACxB;AAEA,WAAO;AAAA,EACT;AAAA,EAEO,OAAO,MAAkB,YAA+E;AAC7G,QAAI,WAAW,MAAM;AACnB,iBAAW,SAAS,WAAW,MAAM;AACnC,YAAI,WAAW,KAAK,eAAe,KAAK,GAAG;AACzC,eAAK,KAAK,IAAI,WAAW,KAAK,KAAK;AAAA,QACrC;AAAA,MACF;AAAA,IACF;AAEA,QAAI,WAAW,MAAM;AACnB,iBAAW,SAAS,WAAW,MAAM;AACnC,YAAI,WAAW,KAAK,eAAe,KAAK,GAAG;AACzC,eAAK,KAAK,KAAK,WAAW,KAAK,KAAK;AAAA,QACtC;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEO,QAAQ,MAAkB,SAAkB,OAAO;AAMxD,QAAI,CAAC,QAAQ;AAAE,aAAO;AAAA,IAAO;AAG7B,SAAK,MAAM,KAAK,IAAI;AAEpB,WAAO;AAAA,EACT;AAAA,EAEO,OAAO,QAAgB;AAC5B,UAAM,YAAY,KAAK,MAAM,UAAU,CAAC,SAAS,KAAK,WAAW,MAAM;AACvE,QAAI,cAAc,IAAI;AACpB,WAAK,MAAM,OAAO,WAAW,CAAC;AAC9B,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA,EAEO,QAAQ;AACb,SAAK,QAAQ,CAAC;AAAA,EAChB;AAAA,EAEO,WAAW;AAAA,EAClB;AACF;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|