Skip to content

Commit 6061e53

Browse files
authored
Merge pull request #589 from TaloDev/develop
Release 0.85.0
2 parents d2c2bb9 + d5c7879 commit 6061e53

25 files changed

+235
-172
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "game-services",
3-
"version": "0.84.1",
3+
"version": "0.85.0",
44
"description": "",
55
"main": "src/index.ts",
66
"scripts": {

src/config/mikro-orm.config.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import 'dotenv/config'
22
import entities from '../entities'
3-
import subscribers from '../entities/subscribers'
43
import migrationsList from '../migrations'
54
import { TsMorphMetadataProvider } from '@mikro-orm/reflection'
65
import { Migrator } from '@mikro-orm/migrations'
@@ -19,7 +18,6 @@ const ormConfig = defineConfig({
1918
migrationsList,
2019
path: 'src/migrations' // for generating migrations via the cli
2120
},
22-
subscribers,
2321
metadataProvider: TsMorphMetadataProvider,
2422
extensions: [Migrator],
2523
pool: {

src/entities/player-group.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ export default class PlayerGroup {
122122
async checkMembership(em: EntityManager) {
123123
const players = await this.getQuery(em).getResultList()
124124
this.members.set(players)
125+
await em.flush()
125126
}
126127

127128
async isPlayerEligible(em: EntityManager, player: Player): Promise<boolean> {

src/entities/player.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import createClickHouseClient from '../lib/clickhouse/createClient'
1414
import { captureException } from '@sentry/node'
1515
import { ClickHouseClient } from '@clickhouse/client'
1616
import GameChannel, { GameChannelLeavingReason } from './game-channel'
17+
import checkGroupMemberships from '../lib/groups/checkGroupMemberships'
1718

1819
@Entity()
1920
export default class Player {
@@ -71,7 +72,9 @@ export default class Player {
7172
}
7273

7374
setProps(props: { key: string, value: string }[]) {
74-
this.props.set(props.map(({ key, value }) => new PlayerProp(this, key, value)))
75+
this.props.set(
76+
props.map(({ key, value }) => new PlayerProp(this, key, value))
77+
)
7578
}
7679

7780
async insertSession(clickhouse: ClickHouseClient, session: PlayerSession) {
@@ -149,9 +152,11 @@ export default class Player {
149152
await em.flush()
150153

151154
const conns = await socket.findConnectionsAsync(async (conn) => {
152-
return conn.hasScope(APIKeyScope.READ_PLAYERS) &&
155+
return (
156+
conn.hasScope(APIKeyScope.READ_PLAYERS) &&
153157
!!conn.playerAliasId &&
154158
this.game.id === (await conn.getPlayerAlias())?.player.game.id
159+
)
155160
})
156161
await sendMessages(conns, 'v1.players.presence.updated', {
157162
presence: this.presence,
@@ -194,8 +199,14 @@ export default class Player {
194199
this.addProp('META_DEV_BUILD', '1')
195200
}
196201

202+
async checkGroupMemberships(em: EntityManager) {
203+
await checkGroupMemberships(em, this)
204+
}
205+
197206
toJSON() {
198-
const presence = this.presence ? { ...this.presence.toJSON(), playerAlias: undefined } : null
207+
const presence = this.presence
208+
? { ...this.presence.toJSON(), playerAlias: undefined }
209+
: null
199210

200211
return {
201212
id: this.id,
@@ -204,7 +215,9 @@ export default class Player {
204215
devBuild: this.devBuild,
205216
createdAt: this.createdAt,
206217
lastSeenAt: this.lastSeenAt,
207-
groups: this.groups.getItems().map((group) => ({ id: group.id, name: group.name })),
218+
groups: this.groups
219+
.getItems()
220+
.map((group) => ({ id: group.id, name: group.name })),
208221
auth: this.auth ?? undefined,
209222
presence
210223
}

src/entities/subscribers/index.ts

Lines changed: 0 additions & 6 deletions
This file was deleted.

src/entities/subscribers/player-group.subscriber.ts

Lines changed: 0 additions & 70 deletions
This file was deleted.

src/lib/groups/checkGroupMemberships.ts

Lines changed: 56 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,54 +2,38 @@ import { EntityManager } from '@mikro-orm/mysql'
22
import Player from '../../entities/player'
33
import PlayerGroup from '../../entities/player-group'
44
import { getResultCacheOptions } from '../perf/getResultCacheOptions'
5+
import { captureException } from '@sentry/node'
6+
import { createRedisConnection } from '../../config/redis.config'
57

6-
const enableLogging = process.env.NODE_ENV !== 'test'
7-
8-
class PlayerGroupMember {
9-
player: Player
10-
group: PlayerGroup
11-
12-
constructor(player: Player, group: PlayerGroup) {
13-
this.player = player
14-
this.group = group
15-
}
16-
17-
toJSON() {
18-
return {
19-
player_id: this.player.id,
20-
player_group_id: this.group.id
21-
}
22-
}
23-
}
24-
25-
export default async function checkGroupMemberships(em: EntityManager, player: Player): Promise<boolean> {
26-
const groups = await em.repo(PlayerGroup).find({
27-
game: player.game
28-
}, getResultCacheOptions(PlayerGroup.getCacheKey(player.game)))
8+
let redis: ReturnType<typeof createRedisConnection>
299

30-
if (groups.length === 0) {
31-
return false
32-
}
10+
const enableLogging = process.env.NODE_ENV !== 'test'
3311

12+
async function runMembershipChecksForGroups(em: EntityManager, player: Player, groups: PlayerGroup[]) {
3413
const label = `Checking group memberships for ${player.id}`
3514

3615
/* v8 ignore next 3 */
3716
if (enableLogging) {
3817
console.time(label)
3918
}
4019

20+
let shouldFlush = false
21+
4122
for (const group of groups) {
4223
const eligible = await group.isPlayerEligible(em, player)
43-
const groupMember = new PlayerGroupMember(player, group).toJSON()
44-
45-
if (eligible) {
46-
await em.qb('player_group_members')
47-
.insert(groupMember)
48-
.onConflict()
49-
.ignore()
50-
.execute()
51-
} else {
52-
await em.nativeDelete('player_group_members', groupMember)
24+
const isInGroup = player.groups.contains(group)
25+
26+
const eligibleButNotInGroup = eligible && !isInGroup
27+
const inGroupButNotEligible = !eligible && isInGroup
28+
29+
if (eligibleButNotInGroup) {
30+
player.groups.add(group)
31+
} else if (inGroupButNotEligible) {
32+
player.groups.remove(group)
33+
}
34+
35+
if (eligibleButNotInGroup || inGroupButNotEligible) {
36+
shouldFlush = true
5337
}
5438
}
5539

@@ -58,5 +42,40 @@ export default async function checkGroupMemberships(em: EntityManager, player: P
5842
console.timeEnd(label)
5943
}
6044

61-
return true
45+
return shouldFlush
46+
}
47+
48+
export default async function checkGroupMemberships(em: EntityManager, player: Player) {
49+
if (!redis) {
50+
redis = createRedisConnection()
51+
}
52+
53+
const groups = await em.repo(PlayerGroup).find({
54+
game: player.game
55+
}, getResultCacheOptions(PlayerGroup.getCacheKey(player.game)))
56+
57+
if (groups.length === 0) {
58+
return
59+
}
60+
61+
const redisKey = `checkMembership:${player.id}`
62+
let lockCreated: 'OK' | null = null
63+
let shouldFlush = false
64+
65+
try {
66+
lockCreated = await redis.set(redisKey, '1', 'EX', 30, 'NX')
67+
if (lockCreated) {
68+
shouldFlush = await runMembershipChecksForGroups(em, player, groups)
69+
}
70+
} catch (err) {
71+
console.error(`Failed checking memberships: ${(err as Error).message}`)
72+
captureException(err)
73+
} finally {
74+
if (lockCreated) {
75+
if (shouldFlush) {
76+
await em.flush()
77+
}
78+
await redis.del(redisKey)
79+
}
80+
}
6281
}

src/lib/queues/game-metrics/flush-events-queue-handler.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Event from '../../../entities/event'
2-
import { FlushMetricsQueueHandler } from './flush-metrics-queue-handler'
2+
import Player from '../../../entities/player'
3+
import { FlushMetricsQueueHandler, postFlushCheckMemberships } from './flush-metrics-queue-handler'
34

45
export class FlushEventsQueueHandler extends FlushMetricsQueueHandler<Event> {
56
constructor() {
@@ -14,6 +15,26 @@ export class FlushEventsQueueHandler extends FlushMetricsQueueHandler<Event> {
1415
values: values.flatMap((event) => event.getInsertableProps()),
1516
format: 'JSONEachRow'
1617
})
18+
}, {
19+
postFlush: async (values) => {
20+
const playerSet = this.buildPlayerSet(values)
21+
22+
if (playerSet.size > 0) {
23+
/* v8 ignore next 3 */
24+
if (process.env.NODE_ENV !== 'test') {
25+
console.info(`FlushEventsQueueHandler checking groups for ${playerSet.size} players`)
26+
}
27+
await postFlushCheckMemberships(Array.from(playerSet))
28+
}
29+
}
1730
})
1831
}
32+
33+
buildPlayerSet(values: Event[]) {
34+
const playerMap = new Map<string, Player>()
35+
for (const event of values) {
36+
playerMap.set(event.playerAlias.player.id, event.playerAlias.player)
37+
}
38+
return new Set(playerMap.values())
39+
}
1940
}

0 commit comments

Comments
 (0)