perf(reversi): improve performance of reversi backend
This commit is contained in:
parent
3ff229af6f
commit
1a01a85182
|
@ -12,18 +12,14 @@ import { IsNull } from 'typeorm';
|
||||||
import type {
|
import type {
|
||||||
MiReversiGame,
|
MiReversiGame,
|
||||||
ReversiGamesRepository,
|
ReversiGamesRepository,
|
||||||
UsersRepository,
|
|
||||||
} from '@/models/_.js';
|
} from '@/models/_.js';
|
||||||
import type { MiUser } from '@/models/User.js';
|
import type { MiUser } from '@/models/User.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import type { Packed } from '@/misc/json-schema.js';
|
|
||||||
import { NotificationService } from '@/core/NotificationService.js';
|
import { NotificationService } from '@/core/NotificationService.js';
|
||||||
import { Serialized } from '@/types.js';
|
import { Serialized } from '@/types.js';
|
||||||
import { ReversiGameEntityService } from './entities/ReversiGameEntityService.js';
|
import { ReversiGameEntityService } from './entities/ReversiGameEntityService.js';
|
||||||
|
@ -58,7 +54,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async cacheGame(game: MiReversiGame) {
|
private async cacheGame(game: MiReversiGame) {
|
||||||
await this.redisClient.setex(`reversi:game:cache:${game.id}`, 60 * 3, JSON.stringify(game));
|
await this.redisClient.setex(`reversi:game:cache:${game.id}`, 60 * 60, JSON.stringify(game));
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -66,6 +62,33 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||||
await this.redisClient.del(`reversi:game:cache:${gameId}`);
|
await this.redisClient.del(`reversi:game:cache:${gameId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
private getBakeProps(game: MiReversiGame) {
|
||||||
|
return {
|
||||||
|
startedAt: game.startedAt,
|
||||||
|
endedAt: game.endedAt,
|
||||||
|
// ゲームの途中からユーザーが変わることは無いので
|
||||||
|
//user1Id: game.user1Id,
|
||||||
|
//user2Id: game.user2Id,
|
||||||
|
user1Ready: game.user1Ready,
|
||||||
|
user2Ready: game.user2Ready,
|
||||||
|
black: game.black,
|
||||||
|
isStarted: game.isStarted,
|
||||||
|
isEnded: game.isEnded,
|
||||||
|
winnerId: game.winnerId,
|
||||||
|
surrenderedUserId: game.surrenderedUserId,
|
||||||
|
timeoutUserId: game.timeoutUserId,
|
||||||
|
isLlotheo: game.isLlotheo,
|
||||||
|
canPutEverywhere: game.canPutEverywhere,
|
||||||
|
loopedBoard: game.loopedBoard,
|
||||||
|
timeLimitForEachTurn: game.timeLimitForEachTurn,
|
||||||
|
logs: game.logs,
|
||||||
|
map: game.map,
|
||||||
|
bw: game.bw,
|
||||||
|
crc32: game.crc32,
|
||||||
|
} satisfies Partial<MiReversiGame>;
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async matchSpecificUser(me: MiUser, targetUser: MiUser): Promise<MiReversiGame | null> {
|
public async matchSpecificUser(me: MiUser, targetUser: MiUser): Promise<MiReversiGame | null> {
|
||||||
if (targetUser.id === me.id) {
|
if (targetUser.id === me.id) {
|
||||||
|
@ -204,14 +227,10 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||||
let isBothReady = false;
|
let isBothReady = false;
|
||||||
|
|
||||||
if (game.user1Id === user.id) {
|
if (game.user1Id === user.id) {
|
||||||
const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update()
|
const updatedGame = {
|
||||||
.set({
|
...game,
|
||||||
user1Ready: ready,
|
user1Ready: ready,
|
||||||
})
|
};
|
||||||
.where('id = :id', { id: game.id })
|
|
||||||
.returning('*')
|
|
||||||
.execute()
|
|
||||||
.then((response) => response.raw[0]);
|
|
||||||
this.cacheGame(updatedGame);
|
this.cacheGame(updatedGame);
|
||||||
|
|
||||||
this.globalEventService.publishReversiGameStream(game.id, 'changeReadyStates', {
|
this.globalEventService.publishReversiGameStream(game.id, 'changeReadyStates', {
|
||||||
|
@ -221,14 +240,10 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||||
|
|
||||||
if (ready && updatedGame.user2Ready) isBothReady = true;
|
if (ready && updatedGame.user2Ready) isBothReady = true;
|
||||||
} else if (game.user2Id === user.id) {
|
} else if (game.user2Id === user.id) {
|
||||||
const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update()
|
const updatedGame = {
|
||||||
.set({
|
...game,
|
||||||
user2Ready: ready,
|
user2Ready: ready,
|
||||||
})
|
};
|
||||||
.where('id = :id', { id: game.id })
|
|
||||||
.returning('*')
|
|
||||||
.execute()
|
|
||||||
.then((response) => response.raw[0]);
|
|
||||||
this.cacheGame(updatedGame);
|
this.cacheGame(updatedGame);
|
||||||
|
|
||||||
this.globalEventService.publishReversiGameStream(game.id, 'changeReadyStates', {
|
this.globalEventService.publishReversiGameStream(game.id, 'changeReadyStates', {
|
||||||
|
@ -262,22 +277,15 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||||
bw = parseInt(game.bw, 10);
|
bw = parseInt(game.bw, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRandomMap() {
|
|
||||||
const mapCount = Object.entries(Reversi.maps).length;
|
|
||||||
const rnd = Math.floor(Math.random() * mapCount);
|
|
||||||
return Object.values(Reversi.maps)[rnd].data;
|
|
||||||
}
|
|
||||||
|
|
||||||
const map = game.map != null ? game.map : getRandomMap();
|
|
||||||
|
|
||||||
const crc32 = CRC32.str(JSON.stringify(game.logs)).toString();
|
const crc32 = CRC32.str(JSON.stringify(game.logs)).toString();
|
||||||
|
|
||||||
const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update()
|
const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update()
|
||||||
.set({
|
.set({
|
||||||
|
...this.getBakeProps(game),
|
||||||
startedAt: new Date(),
|
startedAt: new Date(),
|
||||||
isStarted: true,
|
isStarted: true,
|
||||||
black: bw,
|
black: bw,
|
||||||
map: map,
|
map: game.map,
|
||||||
crc32,
|
crc32,
|
||||||
})
|
})
|
||||||
.where('id = :id', { id: game.id })
|
.where('id = :id', { id: game.id })
|
||||||
|
@ -287,38 +295,23 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||||
this.cacheGame(updatedGame);
|
this.cacheGame(updatedGame);
|
||||||
|
|
||||||
//#region 盤面に最初から石がないなどして始まった瞬間に勝敗が決定する場合があるのでその処理
|
//#region 盤面に最初から石がないなどして始まった瞬間に勝敗が決定する場合があるのでその処理
|
||||||
const engine = new Reversi.Game(map, {
|
const engine = new Reversi.Game(updatedGame.map, {
|
||||||
isLlotheo: game.isLlotheo,
|
isLlotheo: updatedGame.isLlotheo,
|
||||||
canPutEverywhere: game.canPutEverywhere,
|
canPutEverywhere: updatedGame.canPutEverywhere,
|
||||||
loopedBoard: game.loopedBoard,
|
loopedBoard: updatedGame.loopedBoard,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (engine.isEnded) {
|
if (engine.isEnded) {
|
||||||
let winner;
|
let winnerId;
|
||||||
if (engine.winner === true) {
|
if (engine.winner === true) {
|
||||||
winner = bw === 1 ? game.user1Id : game.user2Id;
|
winnerId = bw === 1 ? updatedGame.user1Id : updatedGame.user2Id;
|
||||||
} else if (engine.winner === false) {
|
} else if (engine.winner === false) {
|
||||||
winner = bw === 1 ? game.user2Id : game.user1Id;
|
winnerId = bw === 1 ? updatedGame.user2Id : updatedGame.user1Id;
|
||||||
} else {
|
} else {
|
||||||
winner = null;
|
winnerId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update()
|
await this.endGame(updatedGame, winnerId, null);
|
||||||
.set({
|
|
||||||
isEnded: true,
|
|
||||||
endedAt: new Date(),
|
|
||||||
winnerId: winner,
|
|
||||||
})
|
|
||||||
.where('id = :id', { id: game.id })
|
|
||||||
.returning('*')
|
|
||||||
.execute()
|
|
||||||
.then((response) => response.raw[0]);
|
|
||||||
this.cacheGame(updatedGame);
|
|
||||||
|
|
||||||
this.globalEventService.publishReversiGameStream(game.id, 'ended', {
|
|
||||||
winnerId: winner,
|
|
||||||
game: await this.reversiGameEntityService.packDetail(game.id),
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -327,7 +320,30 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||||
this.redisClient.setex(`reversi:game:turnTimer:${game.id}:1`, updatedGame.timeLimitForEachTurn, '');
|
this.redisClient.setex(`reversi:game:turnTimer:${game.id}:1`, updatedGame.timeLimitForEachTurn, '');
|
||||||
|
|
||||||
this.globalEventService.publishReversiGameStream(game.id, 'started', {
|
this.globalEventService.publishReversiGameStream(game.id, 'started', {
|
||||||
game: await this.reversiGameEntityService.packDetail(game.id),
|
game: await this.reversiGameEntityService.packDetail(updatedGame),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
private async endGame(game: MiReversiGame, winnerId: MiUser['id'] | null, reason: 'surrender' | 'timeout' | null) {
|
||||||
|
const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update()
|
||||||
|
.set({
|
||||||
|
...this.getBakeProps(game),
|
||||||
|
isEnded: true,
|
||||||
|
endedAt: new Date(),
|
||||||
|
winnerId: winnerId,
|
||||||
|
surrenderedUserId: reason === 'surrender' ? (winnerId === game.user1Id ? game.user2Id : game.user1Id) : null,
|
||||||
|
timeoutUserId: reason === 'timeout' ? (winnerId === game.user1Id ? game.user2Id : game.user1Id) : null,
|
||||||
|
})
|
||||||
|
.where('id = :id', { id: game.id })
|
||||||
|
.returning('*')
|
||||||
|
.execute()
|
||||||
|
.then((response) => response.raw[0]);
|
||||||
|
this.cacheGame(updatedGame);
|
||||||
|
|
||||||
|
this.globalEventService.publishReversiGameStream(game.id, 'ended', {
|
||||||
|
winnerId: winnerId,
|
||||||
|
game: await this.reversiGameEntityService.packDetail(updatedGame),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,14 +370,10 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||||
|
|
||||||
// TODO: より厳格なバリデーション
|
// TODO: より厳格なバリデーション
|
||||||
|
|
||||||
const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update()
|
const updatedGame = {
|
||||||
.set({
|
...game,
|
||||||
[key]: value,
|
[key]: value,
|
||||||
})
|
};
|
||||||
.where('id = :id', { id: game.id })
|
|
||||||
.returning('*')
|
|
||||||
.execute()
|
|
||||||
.then((response) => response.raw[0]);
|
|
||||||
this.cacheGame(updatedGame);
|
this.cacheGame(updatedGame);
|
||||||
|
|
||||||
this.globalEventService.publishReversiGameStream(game.id, 'updateSettings', {
|
this.globalEventService.publishReversiGameStream(game.id, 'updateSettings', {
|
||||||
|
@ -397,17 +409,6 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||||
|
|
||||||
engine.putStone(pos);
|
engine.putStone(pos);
|
||||||
|
|
||||||
let winner;
|
|
||||||
if (engine.isEnded) {
|
|
||||||
if (engine.winner === true) {
|
|
||||||
winner = game.black === 1 ? game.user1Id : game.user2Id;
|
|
||||||
} else if (engine.winner === false) {
|
|
||||||
winner = game.black === 1 ? game.user2Id : game.user1Id;
|
|
||||||
} else {
|
|
||||||
winner = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const logs = Reversi.Serializer.deserializeLogs(game.logs);
|
const logs = Reversi.Serializer.deserializeLogs(game.logs);
|
||||||
|
|
||||||
const log = {
|
const log = {
|
||||||
|
@ -423,17 +424,11 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||||
|
|
||||||
const crc32 = CRC32.str(JSON.stringify(serializeLogs)).toString();
|
const crc32 = CRC32.str(JSON.stringify(serializeLogs)).toString();
|
||||||
|
|
||||||
const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update()
|
const updatedGame = {
|
||||||
.set({
|
...game,
|
||||||
crc32,
|
crc32,
|
||||||
isEnded: engine.isEnded,
|
|
||||||
winnerId: winner,
|
|
||||||
logs: serializeLogs,
|
logs: serializeLogs,
|
||||||
})
|
};
|
||||||
.where('id = :id', { id: game.id })
|
|
||||||
.returning('*')
|
|
||||||
.execute()
|
|
||||||
.then((response) => response.raw[0]);
|
|
||||||
this.cacheGame(updatedGame);
|
this.cacheGame(updatedGame);
|
||||||
|
|
||||||
this.globalEventService.publishReversiGameStream(game.id, 'log', {
|
this.globalEventService.publishReversiGameStream(game.id, 'log', {
|
||||||
|
@ -442,10 +437,16 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (engine.isEnded) {
|
if (engine.isEnded) {
|
||||||
this.globalEventService.publishReversiGameStream(game.id, 'ended', {
|
let winnerId;
|
||||||
winnerId: winner ?? null,
|
if (engine.winner === true) {
|
||||||
game: await this.reversiGameEntityService.packDetail(game.id),
|
winnerId = game.black === 1 ? game.user1Id : game.user2Id;
|
||||||
});
|
} else if (engine.winner === false) {
|
||||||
|
winnerId = game.black === 1 ? game.user2Id : game.user1Id;
|
||||||
|
} else {
|
||||||
|
winnerId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.endGame(updatedGame, winnerId, null);
|
||||||
} else {
|
} else {
|
||||||
this.redisClient.setex(`reversi:game:turnTimer:${game.id}:${engine.turn ? '1' : '0'}`, updatedGame.timeLimitForEachTurn, '');
|
this.redisClient.setex(`reversi:game:turnTimer:${game.id}:${engine.turn ? '1' : '0'}`, updatedGame.timeLimitForEachTurn, '');
|
||||||
}
|
}
|
||||||
|
@ -460,23 +461,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||||
|
|
||||||
const winnerId = game.user1Id === user.id ? game.user2Id : game.user1Id;
|
const winnerId = game.user1Id === user.id ? game.user2Id : game.user1Id;
|
||||||
|
|
||||||
const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update()
|
await this.endGame(game, winnerId, 'surrender');
|
||||||
.set({
|
|
||||||
isEnded: true,
|
|
||||||
endedAt: new Date(),
|
|
||||||
winnerId: winnerId,
|
|
||||||
surrenderedUserId: user.id,
|
|
||||||
})
|
|
||||||
.where('id = :id', { id: game.id })
|
|
||||||
.returning('*')
|
|
||||||
.execute()
|
|
||||||
.then((response) => response.raw[0]);
|
|
||||||
this.cacheGame(updatedGame);
|
|
||||||
|
|
||||||
this.globalEventService.publishReversiGameStream(game.id, 'ended', {
|
|
||||||
winnerId: winnerId,
|
|
||||||
game: await this.reversiGameEntityService.packDetail(game.id),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -500,23 +485,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||||
if (timer === 0) {
|
if (timer === 0) {
|
||||||
const winnerId = engine.turn ? (game.black === 1 ? game.user2Id : game.user1Id) : (game.black === 1 ? game.user1Id : game.user2Id);
|
const winnerId = engine.turn ? (game.black === 1 ? game.user2Id : game.user1Id) : (game.black === 1 ? game.user1Id : game.user2Id);
|
||||||
|
|
||||||
const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update()
|
await this.endGame(game, winnerId, 'timeout');
|
||||||
.set({
|
|
||||||
isEnded: true,
|
|
||||||
endedAt: new Date(),
|
|
||||||
winnerId: winnerId,
|
|
||||||
timeoutUserId: engine.turn ? (game.black === 1 ? game.user1Id : game.user2Id) : (game.black === 1 ? game.user2Id : game.user1Id),
|
|
||||||
})
|
|
||||||
.where('id = :id', { id: game.id })
|
|
||||||
.returning('*')
|
|
||||||
.execute()
|
|
||||||
.then((response) => response.raw[0]);
|
|
||||||
this.cacheGame(updatedGame);
|
|
||||||
|
|
||||||
this.globalEventService.publishReversiGameStream(game.id, 'ended', {
|
|
||||||
winnerId: winnerId,
|
|
||||||
game: await this.reversiGameEntityService.packDetail(game.id),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue