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) { $room = DB::transaction(function () use ($player, $cutoff): ?Room { $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(); $room->update([ 'state_version' => $room->state_version + 1, ]); return $room; }); if ($room !== null && ! $room->trashed()) { RoomStateChanged::dispatch( $room->id, $room->state_version, RoomStateChange::PlayerKicked, $realtimeState->for($room), $player->id, ); } } }); } }