diff --git a/packages/backend/src/core/ReversiService.ts b/packages/backend/src/core/ReversiService.ts index 66296f1ed4..1c8635212d 100644 --- a/packages/backend/src/core/ReversiService.ts +++ b/packages/backend/src/core/ReversiService.ts @@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; import { ModuleRef } from '@nestjs/core'; import * as Reversi from 'misskey-reversi'; -import { IsNull } from 'typeorm'; +import { IsNull, LessThan } from 'typeorm'; import type { MiReversiGame, ReversiGamesRepository, @@ -24,7 +24,7 @@ import { Serialized } from '@/types.js'; import { ReversiGameEntityService } from './entities/ReversiGameEntityService.js'; import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common'; -const MATCHING_TIMEOUT_MS = 1000 * 15; // 15sec +const MATCHING_TIMEOUT_MS = 1000 * 10; // 10sec @Injectable() export class ReversiService implements OnApplicationShutdown, OnModuleInit { @@ -89,11 +89,27 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { } @bindThis - public async matchSpecificUser(me: MiUser, targetUser: MiUser): Promise { + public async matchSpecificUser(me: MiUser, targetUser: MiUser, multiple = false): Promise { if (targetUser.id === me.id) { throw new Error('You cannot match yourself.'); } + if (!multiple) { + // 既にマッチしている対局が無いか探す(3分以内) + const games = await this.reversiGamesRepository.find({ + where: [ + { id: LessThan(this.idService.gen(Date.now() - 1000 * 60 * 3)), user1Id: me.id, user2Id: targetUser.id, isStarted: false }, + { id: LessThan(this.idService.gen(Date.now() - 1000 * 60 * 3)), user1Id: targetUser.id, user2Id: me.id, isStarted: false }, + ], + relations: ['user1', 'user2'], + order: { id: 'DESC' }, + }); + if (games.length > 0) { + return games[0]; + } + } + + //#region 相手から既に招待されてないか確認 const invitations = await this.redisClient.zrange( `reversi:matchSpecific:${me.id}`, Date.now() - MATCHING_TIMEOUT_MS, @@ -106,19 +122,35 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { const game = await this.matched(targetUser.id, me.id); return game; - } else { - this.redisClient.zadd(`reversi:matchSpecific:${targetUser.id}`, Date.now(), me.id); - - this.globalEventService.publishReversiStream(targetUser.id, 'invited', { - user: await this.userEntityService.pack(me, targetUser), - }); - - return null; } + //#endregion + + this.redisClient.zadd(`reversi:matchSpecific:${targetUser.id}`, Date.now(), me.id); + + this.globalEventService.publishReversiStream(targetUser.id, 'invited', { + user: await this.userEntityService.pack(me, targetUser), + }); + + return null; } @bindThis - public async matchAnyUser(me: MiUser): Promise { + public async matchAnyUser(me: MiUser, multiple = false): Promise { + if (!multiple) { + // 既にマッチしている対局が無いか探す(3分以内) + const games = await this.reversiGamesRepository.find({ + where: [ + { id: LessThan(this.idService.gen(Date.now() - 1000 * 60 * 3)), user1Id: me.id, isStarted: false }, + { id: LessThan(this.idService.gen(Date.now() - 1000 * 60 * 3)), user2Id: me.id, isStarted: false }, + ], + relations: ['user1', 'user2'], + order: { id: 'DESC' }, + }); + if (games.length > 0) { + return games[0]; + } + } + //#region まず自分宛ての招待を探す const invitations = await this.redisClient.zrange( `reversi:matchSpecific:${me.id}`, @@ -169,6 +201,14 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { await this.redisClient.zrem('reversi:matchAny', user.id); } + @bindThis + public async cleanOutdatedGames() { + await this.reversiGamesRepository.delete({ + id: LessThan(this.idService.gen(Date.now() - 1000 * 60 * 10)), + isStarted: false, + }); + } + @bindThis public async gameReady(gameId: MiReversiGame['id'], user: MiUser, ready: boolean) { const game = await this.get(gameId); diff --git a/packages/backend/src/queue/processors/CleanProcessorService.ts b/packages/backend/src/queue/processors/CleanProcessorService.ts index e252c5d8a1..17b6c8ba0c 100644 --- a/packages/backend/src/queue/processors/CleanProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanProcessorService.ts @@ -11,6 +11,7 @@ import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; import type { Config } from '@/config.js'; +import { ReversiService } from '@/core/ReversiService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -32,6 +33,7 @@ export class CleanProcessorService { private roleAssignmentsRepository: RoleAssignmentsRepository, private queueLoggerService: QueueLoggerService, + private reversiService: ReversiService, private idService: IdService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('clean'); @@ -65,6 +67,8 @@ export class CleanProcessorService { }); } + this.reversiService.cleanOutdatedGames(); + this.logger.succ('Cleaned.'); } } diff --git a/packages/backend/src/server/api/endpoints/reversi/match.ts b/packages/backend/src/server/api/endpoints/reversi/match.ts index 1065ce5a89..62682cfb50 100644 --- a/packages/backend/src/server/api/endpoints/reversi/match.ts +++ b/packages/backend/src/server/api/endpoints/reversi/match.ts @@ -37,6 +37,7 @@ export const paramDef = { type: 'object', properties: { userId: { type: 'string', format: 'misskey:id', nullable: true }, + multiple: { type: 'boolean', default: false }, }, required: [], } as const; @@ -56,7 +57,7 @@ export default class extends Endpoint { // eslint- throw err; }) : null; - const game = target ? await this.reversiService.matchSpecificUser(me, target) : await this.reversiService.matchAnyUser(me); + const game = target ? await this.reversiService.matchSpecificUser(me, target, ps.multiple) : await this.reversiService.matchAnyUser(me, ps.multiple); if (game == null) return; diff --git a/packages/frontend/src/pages/reversi/index.vue b/packages/frontend/src/pages/reversi/index.vue index 4c6c99ae51..09f742b83e 100644 --- a/packages/frontend/src/pages/reversi/index.vue +++ b/packages/frontend/src/pages/reversi/index.vue @@ -139,7 +139,9 @@ if ($i) { const connection = useStream().useChannel('reversi'); connection.on('matched', x => { - startGame(x.game); + if (matchingUser.value != null || matchingAny.value) { + startGame(x.game); + } }); connection.on('invited', invitation => { @@ -222,7 +224,7 @@ async function accept(user) { } } -useInterval(matchHeatbeat, 1000 * 10, { immediate: false, afterMounted: true }); +useInterval(matchHeatbeat, 1000 * 5, { immediate: false, afterMounted: true }); onMounted(() => { misskeyApi('reversi/invitations').then(_invitations => { diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index fe04658deb..4fa42bb919 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -1,6 +1,6 @@ /* - * version: 2024.2.0-beta.3 - * generatedAt: 2024-01-23T01:22:13.177Z + * version: 2024.2.0-beta.4 + * generatedAt: 2024-01-24T01:14:40.901Z */ import type { SwitchCaseResponseType } from '../api.js'; diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts index 0060003031..6b330c7c11 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -1,6 +1,6 @@ /* - * version: 2024.2.0-beta.3 - * generatedAt: 2024-01-23T01:22:13.175Z + * version: 2024.2.0-beta.4 + * generatedAt: 2024-01-24T01:14:40.899Z */ import type { diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index 55afcdeb31..8eb284ae9e 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -1,6 +1,6 @@ /* - * version: 2024.2.0-beta.3 - * generatedAt: 2024-01-23T01:22:13.173Z + * version: 2024.2.0-beta.4 + * generatedAt: 2024-01-24T01:14:40.897Z */ import { operations } from './types.js'; diff --git a/packages/misskey-js/src/autogen/models.ts b/packages/misskey-js/src/autogen/models.ts index c94dfaa25e..0a6f534aff 100644 --- a/packages/misskey-js/src/autogen/models.ts +++ b/packages/misskey-js/src/autogen/models.ts @@ -1,6 +1,6 @@ /* - * version: 2024.2.0-beta.3 - * generatedAt: 2024-01-23T01:22:13.172Z + * version: 2024.2.0-beta.4 + * generatedAt: 2024-01-24T01:14:40.896Z */ import { components } from './types.js'; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 761304ef28..44421240d0 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -2,8 +2,8 @@ /* eslint @typescript-eslint/no-explicit-any: 0 */ /* - * version: 2024.2.0-beta.3 - * generatedAt: 2024-01-23T01:22:13.093Z + * version: 2024.2.0-beta.4 + * generatedAt: 2024-01-24T01:14:40.815Z */ /** @@ -25799,6 +25799,8 @@ export type operations = { 'application/json': { /** Format: misskey:id */ userId?: string | null; + /** @default false */ + multiple?: boolean; }; }; };