using AspShogiSockets.Extensions; using AspShogiSockets.Managers.Utility; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net.WebSockets; using System.Threading.Tasks; using Websockets.Managers.ClientActionHandlers; using Websockets.ServiceModels.Types; namespace Websockets.Managers { public interface ISocketCommunicationManager { Task CommunicateWith(WebSocket w, string s); Task BroadcastToAll(string msg); Task BroadcastToGame(string gameName, Func msgBuilder); Task BroadcastToGame(string gameName, string msg); void SubscribeToGame(WebSocket socket, string gameName, string playerName); void SubscribeToBroadcast(WebSocket socket, string playerName); void UnsubscribeFromBroadcastAndGames(string playerName); void UnsubscribeFromGame(string gameName, string playerName); } public class SocketCommunicationManager : ISocketCommunicationManager { private readonly ConcurrentDictionary connections; private readonly ConcurrentDictionary> gameSeats; private readonly ILogger logger; private readonly ActionHandlerResolver handlerResolver; public SocketCommunicationManager( ILogger logger, ActionHandlerResolver handlerResolver) { this.logger = logger; this.handlerResolver = handlerResolver; connections = new ConcurrentDictionary(); gameSeats = new ConcurrentDictionary>(); } public async Task CommunicateWith(WebSocket socket, string userName) { SubscribeToBroadcast(socket, userName); while (!socket.CloseStatus.HasValue) { try { var message = await socket.ReceiveTextAsync(); if (string.IsNullOrWhiteSpace(message)) continue; var request = JsonConvert.DeserializeObject(message); if (!Enum.IsDefined(typeof(ClientAction), request.Action)) { await socket.SendTextAsync("Error: Action not recognized."); } else { var handler = handlerResolver(request.Action); await handler.Handle(socket, message, userName); } } catch (OperationCanceledException ex) { logger.LogError(ex.Message); } } UnsubscribeFromBroadcastAndGames(userName); } public void SubscribeToBroadcast(WebSocket socket, string playerName) { logger.LogInformation("Subscribing [{0}] to broadcast", playerName); connections.TryAdd(playerName, socket); } public void UnsubscribeFromBroadcastAndGames(string playerName) { logger.LogInformation("Unsubscribing [{0}] from broadcast", playerName); connections.TryRemove(playerName, out _); foreach (var game in gameSeats) { game.Value.Remove(playerName); } } /// /// Unsubscribes the player from their current game, then subscribes to the new game. /// public void SubscribeToGame(WebSocket socket, string gameName, string playerName) { // Unsubscribe from any other games foreach (var kvp in gameSeats) { var gameNameKey = kvp.Key; UnsubscribeFromGame(gameNameKey, playerName); } // Subscribe logger.LogInformation("Subscribing player [{0}] to game [{1}]", playerName, gameName); var addSuccess = gameSeats.TryAdd(gameName, new List { playerName }); if (!addSuccess && !gameSeats[gameName].Contains(playerName)) { gameSeats[gameName].Add(playerName); } } public void UnsubscribeFromGame(string gameName, string playerName) { if (gameSeats.ContainsKey(gameName)) { logger.LogInformation("Unsubscribing player [{0}] from game [{1}]", playerName, gameName); gameSeats[gameName].Remove(playerName); if (gameSeats[gameName].Count == 0) gameSeats.TryRemove(gameName, out _); } } public async Task BroadcastToAll(string msg) { var tasks = connections.Select(kvp => { var player = kvp.Key; var socket = kvp.Value; logger.LogInformation("Broadcasting to player [{0}] \n{1}\n", new[] { player, msg }); return socket.SendTextAsync(msg); }); await Task.WhenAll(tasks); } public async Task BroadcastToGame(string gameName, string msg) { if (gameSeats.ContainsKey(gameName)) { var tasks = gameSeats[gameName] .Select(playerName => { logger.LogInformation("Broadcasting to game [{0}], player [{0}] \n{1}\n", gameName, playerName, msg); return connections[playerName]; }) .Where(stream => stream != null) .Select(socket => socket.SendTextAsync(msg)); await Task.WhenAll(tasks); } } public async Task BroadcastToGame(string gameName, Func msgBuilder) { if (gameSeats.ContainsKey(gameName)) { var tasks = gameSeats[gameName] .Select(playerName => { var socket = connections[playerName]; var msg = msgBuilder(playerName, socket); logger.LogInformation("Broadcasting to game [{0}], player [{0}] \n{1}\n", gameName, playerName, msg); return socket.SendTextAsync(msg); }); await Task.WhenAll(tasks); } } } }