UI Refactoring and Fix of image issue when adding an image to a quiz
This commit is contained in:
@@ -4,8 +4,9 @@ import asyncio
|
||||
from ..models import QuizGame, QuizGameParticipant
|
||||
from channels.db import database_sync_to_async
|
||||
|
||||
# Dictionary to track inactive check tasks per game
|
||||
# Dictionary to track inactive check tasks and active connections per game
|
||||
game_check_tasks = {}
|
||||
game_connections = {}
|
||||
|
||||
class LobbyConsumer(AsyncWebsocketConsumer):
|
||||
@classmethod
|
||||
@@ -16,39 +17,20 @@ class LobbyConsumer(AsyncWebsocketConsumer):
|
||||
room_group_name = f'lobby_{join_code}'
|
||||
while True:
|
||||
try:
|
||||
await asyncio.sleep(30) # Check every 30 seconds
|
||||
await asyncio.sleep(10) # Check every 10 seconds to match frontend heartbeat
|
||||
# Get participants list and broadcast update
|
||||
try:
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
game = await database_sync_to_async(QuizGame.objects.get)(join_code=join_code)
|
||||
cutoff_time = timezone.now() - timedelta(minutes=1)
|
||||
# Get previous active participants
|
||||
prev_active = await database_sync_to_async(lambda: set(
|
||||
game.participants.values_list('display_name', flat=True)
|
||||
))()
|
||||
|
||||
# Get current active participants
|
||||
cutoff_time = timezone.now() - timedelta(seconds=20) # Consider inactive after 20 seconds (2 missed heartbeats)
|
||||
# Get current active participants with full info
|
||||
active_participants = await database_sync_to_async(lambda: list(
|
||||
game.participants.filter(last_heartbeat__gte=cutoff_time)
|
||||
.values_list('display_name', flat=True)
|
||||
.values('participant_id', 'display_name')
|
||||
))()
|
||||
|
||||
# Find removed participants
|
||||
removed = set(prev_active) - set(active_participants)
|
||||
|
||||
# Send notifications for removed participants
|
||||
for name in removed:
|
||||
await channel_layer.group_send(
|
||||
room_group_name,
|
||||
{
|
||||
'type': 'player_left',
|
||||
'player_name': name,
|
||||
'was_kicked': True
|
||||
}
|
||||
)
|
||||
|
||||
# Broadcast update to all clients
|
||||
# Send update to all clients
|
||||
await channel_layer.group_send(
|
||||
room_group_name,
|
||||
{
|
||||
@@ -76,6 +58,14 @@ class LobbyConsumer(AsyncWebsocketConsumer):
|
||||
# Get participant ID from session
|
||||
self.participant_id = self.scope['session'].get('participant_id')
|
||||
|
||||
# Track connection
|
||||
if self.join_code not in game_connections:
|
||||
game_connections[self.join_code] = set()
|
||||
game_connections[self.join_code].add(self.channel_name)
|
||||
|
||||
# Start inactive check task if not already running
|
||||
await self.start_inactive_check(self.join_code, self.channel_layer)
|
||||
|
||||
# Join room group
|
||||
await self.channel_layer.group_add(
|
||||
self.room_group_name,
|
||||
@@ -87,9 +77,6 @@ class LobbyConsumer(AsyncWebsocketConsumer):
|
||||
# Send initial participants list
|
||||
await self.update_participants()
|
||||
|
||||
# Ensure inactive check is running for this game
|
||||
await self.start_inactive_check(self.join_code, self.channel_layer)
|
||||
|
||||
async def disconnect(self, close_code):
|
||||
# Leave room group
|
||||
await self.channel_layer.group_discard(
|
||||
@@ -97,6 +84,17 @@ class LobbyConsumer(AsyncWebsocketConsumer):
|
||||
self.channel_name
|
||||
)
|
||||
|
||||
# Remove from connection tracking
|
||||
if self.join_code in game_connections:
|
||||
game_connections[self.join_code].discard(self.channel_name)
|
||||
if not game_connections[self.join_code]:
|
||||
# Last connection closed
|
||||
del game_connections[self.join_code]
|
||||
if self.join_code in game_check_tasks:
|
||||
# Cancel the inactive check task
|
||||
game_check_tasks[self.join_code].cancel()
|
||||
del game_check_tasks[self.join_code]
|
||||
|
||||
async def receive(self, text_data):
|
||||
data = json.loads(text_data)
|
||||
message_type = data.get('type')
|
||||
@@ -113,8 +111,7 @@ class LobbyConsumer(AsyncWebsocketConsumer):
|
||||
participant_id = data.get('participant_id')
|
||||
if participant_id:
|
||||
await self.update_participant_heartbeat(participant_id)
|
||||
# Update participants list after heartbeat
|
||||
await self.update_participants()
|
||||
# Don't update participants list after heartbeat - let the background task handle it
|
||||
elif message_type in ['leave_game', 'kick_player']:
|
||||
participant_id = data.get('participant_id')
|
||||
if participant_id:
|
||||
@@ -200,7 +197,7 @@ class LobbyConsumer(AsyncWebsocketConsumer):
|
||||
from datetime import timedelta
|
||||
game = await database_sync_to_async(QuizGame.objects.get)(join_code=self.join_code)
|
||||
# Only return participants that have been seen in the last minute
|
||||
cutoff_time = timezone.now() - timedelta(minutes=1)
|
||||
cutoff_time = timezone.now() - timedelta(seconds=20) # Consider inactive after 20 seconds (2 missed heartbeats)
|
||||
participants = await database_sync_to_async(lambda: list(
|
||||
game.participants.filter(last_heartbeat__gte=cutoff_time)
|
||||
.values('participant_id', 'display_name')
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.7 on 2025-04-06 17:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('play', '0009_quizanswer'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='quizgameparticipant',
|
||||
name='last_heartbeat',
|
||||
field=models.DateTimeField(auto_now_add=True),
|
||||
),
|
||||
]
|
||||
@@ -43,7 +43,7 @@ class QuizGameParticipant(models.Model):
|
||||
quiz_game = models.ForeignKey(QuizGame, on_delete=models.CASCADE, related_name="participants")
|
||||
score = models.IntegerField(verbose_name="Punkte", default=0)
|
||||
avatar = models.CharField(max_length=200, blank=True, default="")
|
||||
last_heartbeat = models.DateTimeField(auto_now=True)
|
||||
last_heartbeat = models.DateTimeField(auto_now_add=True)
|
||||
last_answer = models.IntegerField(null=True, blank=True)
|
||||
last_answer_correct = models.BooleanField(null=True, blank=True)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user