subHour(); Player::query() ->where(function ($query) use ($cutoff): void { $query->where('last_seen_at', '<=', $cutoff) ->orWhere(function ($query) use ($cutoff): void { $query->whereNull('last_seen_at') ->where('created_at', '<=', $cutoff); }); }) ->select(['id', 'room_id']) ->chunkById(100, function ($players) use ($cutoff, $realtimeState): void { foreach ($players as $player) { $result = DB::transaction(function () use ($player, $cutoff): ?array { $room = Room::withTrashed() ->lockForUpdate() ->find($player->room_id); if ($room === null) { return null; } $currentPlayer = $room->players() ->lockForUpdate() ->find($player->id); if ($currentPlayer === null) { return null; } $lastActivity = $currentPlayer->last_seen_at ?? $currentPlayer->created_at; if ($lastActivity === null || $lastActivity->greaterThan($cutoff)) { return null; } $currentPlayer->delete(); $roomDeleted = false; if ($room->owner_id === null && ! $room->trashed()) { if (! $room->players()->exists()) { $room->delete(); $roomDeleted = true; } elseif (! $room->players()->where('is_moderator', true)->exists()) { $successor = $room->players() ->where(function ($query) use ($cutoff): void { $query->where('last_seen_at', '>', $cutoff) ->orWhere(function ($query) use ($cutoff): void { $query->whereNull('last_seen_at') ->where('created_at', '>', $cutoff); }); }) ->orderBy('created_at') ->orderBy('id') ->first(); $successor?->update(['is_moderator' => true]); } } $room->update([ 'state_version' => $room->state_version + 1, ]); return [$room, $roomDeleted]; }); if ($result === null) { continue; } [$room, $roomDeleted] = $result; if ($roomDeleted) { RoomStateChanged::dispatch( $room->id, $room->state_version, RoomStateChange::RoomDeleted, ); } elseif (! $room->trashed()) { RoomStateChanged::dispatch( $room->id, $room->state_version, RoomStateChange::PlayerKicked, $realtimeState->for($room), $player->id, ); } } }); } }