mirror of
https://github.com/djdevin/recflare.git
synced 2026-10-27 16:08:58 -07:00
[match] improve presence delete on expiry/logout
This commit is contained in:
+28
-12
@@ -16,6 +16,7 @@ import {
|
||||
GAME_VERSION,
|
||||
getAccount,
|
||||
getClubSummary,
|
||||
getExpiredPresenceAccountIds,
|
||||
getExpiredPresenceInstanceIds,
|
||||
getFriendIds,
|
||||
getJoinableInstance,
|
||||
@@ -565,19 +566,22 @@ function presenceUpdateMessage(
|
||||
|
||||
/**
|
||||
* Push a SubscriptionUpdatePresence to every online friend of `playerId` after their
|
||||
* presence changes (they entered a room). Mirrors the reference's PlayerPresenceChanged:
|
||||
* only currently-connected friends receive it (an offline friend gets nothing, not a
|
||||
* queued stale frame), so it's an ephemeral batch send. The room instance the friends
|
||||
* see is read from the player's stored presence — the authoritative record just written,
|
||||
* the same one the heartbeat replays. Best-effort: a hub or lookup failure is logged and
|
||||
* swallowed, so it never fails the matchmake that triggered it.
|
||||
* presence changes (they entered a room, logged out, or lapsed). Mirrors the reference's
|
||||
* PlayerPresenceChanged: only currently-connected friends receive it (an offline friend
|
||||
* gets nothing, not a queued stale frame), so it's an ephemeral batch send. The room
|
||||
* instance the friends see is read from the player's stored presence — the authoritative
|
||||
* record just written, the same one the heartbeat replays; with the row gone (logout, or
|
||||
* the cron sweep purging a lapsed row) the read is null and the frame is the offline
|
||||
* snapshot (roomInstance null, isOnline false). Takes `Env` rather than a request context
|
||||
* so the sweep, which has no request, sends the same frame. Best-effort: a hub or lookup
|
||||
* failure is logged and swallowed, so it never fails the matchmake that triggered it.
|
||||
*/
|
||||
async function notifyFriendsPresence(c: Context<App>, playerId: number): Promise<void> {
|
||||
async function notifyFriendsPresence(env: Env, playerId: number): Promise<void> {
|
||||
try {
|
||||
const friendIds = await getFriendIds(c.env.DB, playerId)
|
||||
const friendIds = await getFriendIds(env.DB, playerId)
|
||||
if (friendIds.length === 0) return
|
||||
const presence = await getPresence<RoomInstance>(c.env.DB, playerId)
|
||||
await c.env.RECFLARE_NOTIFICATIONS_HUB.getByName(HUB_INSTANCE).notifyPlayersEphemeral(
|
||||
const presence = await getPresence<RoomInstance>(env.DB, playerId)
|
||||
await env.RECFLARE_NOTIFICATIONS_HUB.getByName(HUB_INSTANCE).notifyPlayersEphemeral(
|
||||
friendIds,
|
||||
NotificationType.SubscriptionUpdatePresence,
|
||||
presenceUpdateMessage(playerId, presence?.roomInstance ?? null, presence?.appVersion)
|
||||
@@ -649,7 +653,7 @@ async function enterRoom(c: Context<App>, id: number, roomInstance: RoomInstance
|
||||
// The player's presence changed — tell their online friends where they went, reading
|
||||
// the instance back from the presence we just stored. Best-effort; never blocks or
|
||||
// fails the matchmake.
|
||||
await notifyFriendsPresence(c, id)
|
||||
await notifyFriendsPresence(c.env, id)
|
||||
}
|
||||
|
||||
/** Returned when a room isn't in the DB — and for every other opaque refusal. */
|
||||
@@ -1859,7 +1863,7 @@ const app = new Hono<App>()
|
||||
// Their presence changed — tell online friends they went offline. Presence
|
||||
// is already cleared, so notifyFriendsPresence reads null and sends the
|
||||
// offline snapshot (roomInstance null, isOnline false).
|
||||
await notifyFriendsPresence(c, id)
|
||||
await notifyFriendsPresence(c.env, id)
|
||||
}
|
||||
}
|
||||
return c.body(null, 200)
|
||||
@@ -3506,14 +3510,26 @@ const app = new Hono<App>()
|
||||
* running after the purge above: this order is what makes a lapsed row count as a
|
||||
* departure. Fullness is recomputed last, so it works from the final head-count and
|
||||
* skips (returns null for) the instances just deleted.
|
||||
*
|
||||
* A lapsed row is also the player going offline, exactly as a logout is — the client just
|
||||
* never got to post one (it crashed, or lost its connection). So the sweep tells each
|
||||
* lapsed player's online friends what `POST /player/logout` would have: the offline
|
||||
* SubscriptionUpdatePresence. Without it a crashed player's friends kept seeing them
|
||||
* online until their own client re-read the friends list. The account ids are read
|
||||
* before the purge (the rows are what remembers who lapsed) and the frames sent after
|
||||
* it, so `notifyFriendsPresence` reads no presence and sends the offline snapshot.
|
||||
*/
|
||||
async function sweepExpiredPresence(env: Env): Promise<void> {
|
||||
const staleInstanceIds = await getExpiredPresenceInstanceIds(env.DB)
|
||||
const lapsedPlayerIds = await getExpiredPresenceAccountIds(env.DB)
|
||||
const removed = await deleteExpiredPresence(env.DB)
|
||||
const emptyInstanceIds = await deleteEmptyRoomInstances(env.DB)
|
||||
for (const instanceId of staleInstanceIds) {
|
||||
await refreshInstanceFullness(env.DB, instanceId)
|
||||
}
|
||||
for (const playerId of lapsedPlayerIds) {
|
||||
await notifyFriendsPresence(env, playerId)
|
||||
}
|
||||
// Sample the player count into `stat` — taken after the purge, so it's the live
|
||||
// rows and not the ones that just lapsed. One row per cron run: the `online` series.
|
||||
const online = await countOnlinePlayers(env.DB)
|
||||
|
||||
Reference in New Issue
Block a user