togethere.cloud/public_html/disciplines/ping-pong/1v1/node-server/src/matchmaking.js

43 lines
1.4 KiB
JavaScript

import { randomId } from './crypto.js';
import { key } from './redisClient.js';
const QUEUE_KEY = key('queue:zset');
const QUEUE_LOCK_KEY = key('queue:lock');
export async function enqueue(redis, userId) {
const score = Date.now();
await redis.zAdd(QUEUE_KEY, [{ score, value: String(userId) }]);
return redis.zCard(QUEUE_KEY);
}
export async function leaveQueue(redis, userId) {
return redis.zRem(QUEUE_KEY, String(userId));
}
export async function getQueueSize(redis) {
return redis.zCard(QUEUE_KEY);
}
export async function dequeuePair(redis) {
// Simple lock to reduce thundering herd when multi-instance
const lockVal = randomId('lock_');
const locked = await redis.set(QUEUE_LOCK_KEY, lockVal, { NX: true, PX: 200 });
if (!locked) return null;
try {
const ids = await redis.zRange(QUEUE_KEY, 0, -1);
if (!ids || ids.length < 2) return null;
const firstIndex = Math.floor(Math.random() * ids.length);
let secondIndex = Math.floor(Math.random() * (ids.length - 1));
if (secondIndex >= firstIndex) secondIndex += 1;
const picked = [ids[firstIndex], ids[secondIndex]];
await redis.zRem(QUEUE_KEY, picked[0], picked[1]);
return [Number(picked[0]), Number(picked[1])];
} finally {
// best-effort unlock
const cur = await redis.get(QUEUE_LOCK_KEY);
if (cur === lockVal) await redis.del(QUEUE_LOCK_KEY);
}
}