Files
SlipItIn/SlipItIn/Services/GameStateService.cs
Tim Krampitz 6f3c0eb716 Synchronisierungs- und Verbindungsstatus verbessert
Neuer Nachrichtentyp SyncStateChangedMessage zur Übermittlung des Sync-Status über das MVVM-Messaging-System eingeführt. GameStateService sendet nach relevanten Aktionen den aktuellen Sync-Status. Methoden zur Synchronisierung sind jetzt thread-sicher und melden Statusänderungen laufend. Fehler beim Senden von Queue-Aktionen werden einzeln behandelt und führen zu Fehlermeldungen im Messenger. ViewModels aktualisieren neue Properties (IsSyncing, SyncStatusText, PendingSyncActions) bei Statusänderungen. XAML-Seiten zeigen Verbindungs- und Sync-Status sowie Offline-Aktionen an. SignalRService meldet Verbindungsstatus konsistenter, auch bei Fehlern und Disconnects.
2026-08-08 21:23:27 +02:00

185 lines
5.9 KiB
C#

using System.Text.Json;
using CommunityToolkit.Mvvm.Messaging;
using SlipItIn.Messages;
using SlipItIn.Models;
using SlipItIn.Services.Interfaces;
using SlipItIn.Shared.DTOs;
namespace SlipItIn.Services;
public class GameStateService : IGameStateService
{
private readonly ISignalRService _signalR;
private readonly ILocalStorageService _localStorage;
private readonly IMessenger _messenger;
private readonly SemaphoreSlim _syncLock = new(1, 1);
private readonly List<QueuedGameAction> _queuedActions = [];
public GameStateDto? CurrentGameState { get; private set; }
public PlayerHandDto? CurrentPlayerHand { get; private set; }
public SlipChallengeDto? PendingChallenge { get; private set; }
public GameStateService(ISignalRService signalR, ILocalStorageService localStorage)
{
_signalR = signalR;
_localStorage = localStorage;
_messenger = WeakReferenceMessenger.Default;
_signalR.LobbyCreated += (_, value) => _messenger.Send(new LobbyCreatedMessage(value));
_signalR.GameStateUpdated += async (_, state) =>
{
CurrentGameState = state;
await _localStorage.SaveGameStateAsync(state);
_messenger.Send(new GameStateChangedMessage(state));
};
_signalR.GameStarted += (_, gameId) => _messenger.Send(new GameStartedMessage(gameId));
_signalR.PlayerHandUpdated += async (_, hand) =>
{
CurrentPlayerHand = hand;
await _localStorage.SavePlayerHandAsync(hand);
_messenger.Send(new PlayerHandChangedMessage(hand));
};
_signalR.ChallengeReceived += (_, challenge) =>
{
PendingChallenge = challenge;
_messenger.Send(new ChallengeReceivedMessage(challenge));
};
_signalR.ErrorReceived += (_, error) => _messenger.Send(new ErrorOccurredMessage(error));
_signalR.ConnectionStateChanged += async (_, connected) =>
{
_messenger.Send(new ConnectionStateChangedMessage(connected));
if (!connected)
return;
try
{
await ResyncAsync();
}
catch (Exception ex)
{
_messenger.Send(new ErrorOccurredMessage($"Resync fehlgeschlagen: {ex.Message}"));
}
};
}
public async Task InitializeAsync()
{
CurrentGameState = await _localStorage.GetGameStateAsync();
CurrentPlayerHand = await _localStorage.GetPlayerHandAsync();
var queued = await _localStorage.GetQueuedActionsAsync();
_queuedActions.Clear();
_queuedActions.AddRange(queued.OrderBy(a => a.CreatedAtUtc));
if (CurrentGameState is not null)
_messenger.Send(new GameStateChangedMessage(CurrentGameState));
if (CurrentPlayerHand is not null)
_messenger.Send(new PlayerHandChangedMessage(CurrentPlayerHand));
PublishSyncState(false, "Bereit", _queuedActions.Count);
}
public async Task ResyncAsync()
{
await _syncLock.WaitAsync();
try
{
PublishSyncState(true, "Syncing...", _queuedActions.Count);
if (CurrentGameState?.GameId > 0 && _signalR.IsConnected)
await _signalR.RequestGameStateAsync(CurrentGameState.GameId);
await FlushQueuedActionsCoreAsync();
PublishSyncState(false, "Synchronisiert", _queuedActions.Count);
}
catch
{
PublishSyncState(false, "Sync fehlgeschlagen", _queuedActions.Count);
throw;
}
finally
{
_syncLock.Release();
}
}
public async Task EnqueueActionAsync(string method, params object[] arguments)
{
var action = new QueuedGameAction
{
Method = method,
ArgumentsJson = JsonSerializer.Serialize(arguments),
CreatedAtUtc = DateTime.UtcNow
};
_queuedActions.Add(action);
await _localStorage.SaveQueuedActionsAsync(_queuedActions);
PublishSyncState(false, "Offline-Aktion gespeichert", _queuedActions.Count);
}
public async Task FlushQueuedActionsAsync()
{
await _syncLock.WaitAsync();
try
{
PublishSyncState(true, "Warteschlange wird gesendet...", _queuedActions.Count);
await FlushQueuedActionsCoreAsync();
PublishSyncState(false, "Warteschlange synchronisiert", _queuedActions.Count);
}
finally
{
_syncLock.Release();
}
}
public async Task ClearLocalGameDataAsync()
{
CurrentGameState = null;
CurrentPlayerHand = null;
PendingChallenge = null;
_queuedActions.Clear();
await _localStorage.ClearGameStateAsync();
await _localStorage.ClearPlayerHandAsync();
await _localStorage.ClearQueuedActionsAsync();
PublishSyncState(false, "Bereit", 0);
}
private async Task FlushQueuedActionsCoreAsync()
{
if (!_signalR.IsConnected || _queuedActions.Count == 0)
return;
var snapshot = _queuedActions.ToList();
foreach (var action in snapshot)
{
try
{
await _signalR.SendQueuedActionAsync(action);
_queuedActions.Remove(action);
PublishSyncState(true, "Warteschlange wird gesendet...", _queuedActions.Count);
}
catch (Exception ex)
{
_messenger.Send(new ErrorOccurredMessage($"Queue-Aktion fehlgeschlagen ({action.Method}): {ex.Message}"));
break;
}
}
await _localStorage.SaveQueuedActionsAsync(_queuedActions);
}
private void PublishSyncState(bool isSyncing, string statusText, int pendingActions)
{
_messenger.Send(new SyncStateChangedMessage(new SyncStateInfo(isSyncing, statusText, pendingActions)));
}
}