Separated WebSocketService to another service

This commit is contained in:
Martin Berg Alstad 2023-07-18 14:12:53 +02:00
parent ad0d8c7d0a
commit fab3e3d13f
11 changed files with 77 additions and 89 deletions

View File

@ -6,7 +6,6 @@ using Microsoft.Extensions.Logging;
using NSubstitute;
using pacMan.Controllers;
using pacMan.Game;
using pacMan.Interfaces;
using pacMan.Services;
using pacMan.Utils;
@ -16,16 +15,16 @@ public class GameControllerTests
{
private IActionService _actionService = null!;
private GameController _controller = null!;
private IWebSocketService _webSocketService = null!;
private GameService _gameService = null!;
[SetUp]
public void Setup()
{
_webSocketService = Substitute.For<WebSocketService>(Substitute.For<ILogger<WebSocketService>>());
_gameService = Substitute.For<GameService>(Substitute.For<ILogger<GameService>>());
_actionService = Substitute.For<ActionService>(
Substitute.For<ILogger<ActionService>>(), _webSocketService
Substitute.For<ILogger<ActionService>>(), _gameService
);
_controller = new GameController(Substitute.For<ILogger<GameController>>(), _webSocketService, _actionService);
_controller = new GameController(Substitute.For<ILogger<GameController>>(), _gameService, _actionService);
}
[Test]
@ -42,18 +41,14 @@ public class GameControllerTests
var result = runMethod!.Invoke(_controller, new object[] { wssResult, data });
if (result is ArraySegment<byte> resultSegment)
{
Assert.Multiple(() =>
{
Assert.That(resultSegment, Has.Count.EqualTo(data.Length));
Assert.That(Encoding.UTF8.GetString(resultSegment.ToArray()), Is.EqualTo(data.GetString(data.Length)));
});
// TODO not working, works like a normal method call
// _actionService.ReceivedWithAnyArgs().DoAction(default!);
}
// TODO not working, works like a normal method call
// _actionService.ReceivedWithAnyArgs().DoAction(default!);
else
{
Assert.Fail("Result is not an ArraySegment<byte>");
}
}
}

View File

@ -4,7 +4,6 @@ using Microsoft.Extensions.Logging;
using NSubstitute;
using pacMan.Game;
using pacMan.Game.Items;
using pacMan.Interfaces;
using pacMan.Services;
namespace BackendTests.Services;
@ -16,13 +15,13 @@ public class ActionServiceTests
private readonly Player _whitePlayer = (Player)Players.Create("white");
private ActionMessage _blackMessage = null!;
private GameService _gameService = null!;
private ActionMessage _redMessage = null!;
private IActionService _service = null!;
private Queue<DirectionalPosition> _spawns = null!;
private ActionMessage _whiteMessage = null!;
private IWebSocketService _wssSub = null!;
[SetUp]
public void Setup()
@ -43,8 +42,8 @@ public class ActionServiceTests
Action = GameAction.PlayerInfo,
Data = JsonSerializer.Serialize(new { Player = _redPlayer, Spawns = CreateQueue() })
};
_wssSub = Substitute.For<WebSocketService>(Substitute.For<ILogger<WebSocketService>>());
_service = new ActionService(Substitute.For<ILogger<ActionService>>(), _wssSub);
_gameService = Substitute.For<GameService>(Substitute.For<ILogger<GameService>>());
_service = new ActionService(Substitute.For<ILogger<ActionService>>(), _gameService);
}
private static Queue<DirectionalPosition> CreateQueue() =>

View File

@ -3,7 +3,6 @@ using BackendTests.TestUtils;
using Microsoft.Extensions.Logging;
using NSubstitute;
using pacMan.Game;
using pacMan.Interfaces;
using pacMan.Services;
using pacMan.Utils;
@ -14,7 +13,7 @@ public class WebSocketServiceTests
private readonly DirectionalPosition _spawn3By3Up = new()
{ At = new Position { X = 3, Y = 3 }, Direction = Direction.Up };
private IWebSocketService _service = null!;
private GameService _service = null!;
private Queue<DirectionalPosition> _spawns = null!;
@ -22,7 +21,7 @@ public class WebSocketServiceTests
[SetUp]
public void SetUp()
{
_service = new WebSocketService(Substitute.For<ILogger<WebSocketService>>());
_service = new GameService(Substitute.For<ILogger<GameService>>());
_spawns = new Queue<DirectionalPosition>(new[]
{
_spawn3By3Up,
@ -40,7 +39,7 @@ public class WebSocketServiceTests
var segment = "test".ToArraySegment();
using var webSocket = Substitute.For<WebSocket>();
_service.Send(webSocket, segment);
_service.Send(webSocket, segment).Wait();
webSocket.ReceivedWithAnyArgs().SendAsync(default, default, default, default);
}

View File

@ -1,7 +1,6 @@
using System.Net.WebSockets;
using Microsoft.AspNetCore.Mvc;
using pacMan.Game;
using pacMan.Interfaces;
using pacMan.Services;
using pacMan.Utils;
@ -13,8 +12,8 @@ public class GameController : GenericController // TODO reconnect using player i
{
private readonly IActionService _actionService;
public GameController(ILogger<GameController> logger, IWebSocketService wsService, IActionService actionService) :
base(logger, wsService) =>
public GameController(ILogger<GameController> logger, GameService gameService, IActionService actionService) :
base(logger, gameService) =>
_actionService = actionService;
[HttpGet("connect")]
@ -24,7 +23,7 @@ public class GameController : GenericController // TODO reconnect using player i
public IEnumerable<GameGroup> GetAllGames()
{
Logger.Log(LogLevel.Information, "Returning all games");
return WsService.Games;
return GameService.Games;
}

View File

@ -1,20 +1,20 @@
using System.Net.WebSockets;
using Microsoft.AspNetCore.Mvc;
using pacMan.Interfaces;
using pacMan.Services;
namespace pacMan.Controllers;
public abstract class GenericController : ControllerBase
public abstract class GenericController : ControllerBase // TODO only use WebSocketService in this class
{
private const int BufferSize = 1024 * 4;
protected readonly GameService GameService;
protected readonly ILogger<GenericController> Logger;
protected readonly IWebSocketService WsService;
private WebSocket? _webSocket;
protected GenericController(ILogger<GenericController> logger, IWebSocketService wsService)
protected GenericController(ILogger<GenericController> logger, GameService gameService)
{
Logger = logger;
WsService = wsService;
GameService = gameService;
Logger.Log(LogLevel.Debug, "WebSocket Controller created");
}
@ -25,7 +25,7 @@ public abstract class GenericController : ControllerBase
using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
Logger.Log(LogLevel.Information, "WebSocket connection established to {}", HttpContext.Connection.Id);
_webSocket = webSocket;
WsService.Connections += WsServiceOnFire;
GameService.Connections += WsServiceOnFire;
await Echo();
}
else
@ -37,7 +37,7 @@ public abstract class GenericController : ControllerBase
private async Task WsServiceOnFire(ArraySegment<byte> segment)
{
if (_webSocket == null) return;
await WsService.Send(_webSocket, segment);
await GameService.Send(_webSocket, segment);
}
@ -50,16 +50,16 @@ public abstract class GenericController : ControllerBase
do
{
var buffer = new byte[BufferSize];
result = await WsService.Receive(_webSocket, buffer);
result = await GameService.Receive(_webSocket, buffer);
if (result.CloseStatus.HasValue) break;
var segment = Run(result, buffer);
WsService.SendToAll(segment);
GameService.SendToAll(segment);
} while (true);
await WsService.Close(_webSocket, result.CloseStatus.Value, result.CloseStatusDescription);
await GameService.Close(_webSocket, result.CloseStatus.Value, result.CloseStatusDescription);
}
catch (WebSocketException e)
{
@ -71,5 +71,5 @@ public abstract class GenericController : ControllerBase
protected abstract ArraySegment<byte> Run(WebSocketReceiveResult result, byte[] data);
protected virtual void Disconnect() => WsService.Connections -= WsServiceOnFire;
protected virtual void Disconnect() => GameService.Connections -= WsServiceOnFire;
}

View File

@ -1,6 +1,6 @@
using System.Net.WebSockets;
using Microsoft.AspNetCore.Mvc;
using pacMan.Interfaces;
using pacMan.Services;
namespace pacMan.Controllers;
@ -8,9 +8,7 @@ namespace pacMan.Controllers;
[Route("api/[controller]")]
public class WsController : GenericController
{
public WsController(ILogger<WsController> logger, IWebSocketService wsService) : base(logger, wsService)
{
}
public WsController(ILogger<WsController> logger, GameService gameService) : base(logger, gameService) { }
[HttpGet]
public override async Task Accept() => await base.Accept();
@ -20,4 +18,4 @@ public class WsController : GenericController
var segment = new ArraySegment<byte>(data, 0, result.Count);
return segment;
}
}
}

View File

@ -1,18 +1,10 @@
using System.Net.WebSockets;
using pacMan.Game;
using pacMan.Game.Items;
using pacMan.Services;
namespace pacMan.Interfaces;
public interface IWebSocketService
{
SynchronizedCollection<GameGroup> Games { get; }
int CountConnected { get; }
event Func<ArraySegment<byte>, Task>? Connections;
Task Send(WebSocket webSocket, ArraySegment<byte> segment);
void SendToAll(ArraySegment<byte> segment);
Task<WebSocketReceiveResult> Receive(WebSocket webSocket, byte[] buffer);
Task Close(WebSocket webSocket, WebSocketCloseStatus closeStatus, string? closeStatusDescription);
GameGroup AddPlayer(IPlayer player, Queue<DirectionalPosition> spawns);
}

View File

@ -6,7 +6,9 @@ var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddSingleton<IWebSocketService, WebSocketService>()
builder.Services
.AddSingleton<IWebSocketService, WebSocketService>()
.AddSingleton<GameService>()
.AddTransient<IActionService, ActionService>();
var app = builder.Build();

View File

@ -2,7 +2,6 @@ using System.Text.Json;
using Microsoft.CSharp.RuntimeBinder;
using pacMan.Game;
using pacMan.Game.Items;
using pacMan.Interfaces;
namespace pacMan.Services;
@ -21,14 +20,14 @@ public interface IActionService
public class ActionService : IActionService
{
private readonly IDiceCup _diceCup;
private readonly GameService _gameService;
private readonly ILogger<ActionService> _logger;
private readonly IWebSocketService _wsService;
public ActionService(ILogger<ActionService> logger, IWebSocketService wsService)
public ActionService(ILogger<ActionService> logger, GameService gameService)
{
_logger = logger;
_diceCup = new DiceCup();
_wsService = wsService;
_gameService = gameService;
}
public GameGroup? Group { get; set; }
@ -63,7 +62,7 @@ public class ActionService : IActionService
PlayerInfoData data = JsonSerializer.Deserialize<PlayerInfoData>(message.Data);
Player = data.Player;
Group = _wsService.AddPlayer(Player, data.Spawns);
Group = _gameService.AddPlayer(Player, data.Spawns);
}
catch (RuntimeBinderException e)
{
@ -112,6 +111,6 @@ public struct PlayerInfoData
public struct ReadyData
{
public required bool AllReady { get; set; }
public required bool AllReady { get; init; }
public required IEnumerable<IPlayer> Players { get; set; }
}

View File

@ -0,0 +1,35 @@
using pacMan.Game;
using pacMan.Game.Items;
namespace pacMan.Services;
public class GameService : WebSocketService
{
public GameService(ILogger<GameService> logger) : base(logger) { }
public SynchronizedCollection<GameGroup> Games { get; } = new();
public event Func<ArraySegment<byte>, Task>? Connections; // TODO remove and use GameGroup
public void SendToAll(ArraySegment<byte> segment)
{
Connections?.Invoke(segment);
}
public GameGroup AddPlayer(IPlayer player, Queue<DirectionalPosition> spawns)
{
var index = 0;
try
{
while (!Games[index].AddPlayer(player)) index++;
}
catch (ArgumentOutOfRangeException)
{
var game = new GameGroup(spawns);
game.AddPlayer(player);
Games.Add(game);
}
return Games[index];
}
}

View File

@ -1,6 +1,4 @@
using System.Net.WebSockets;
using pacMan.Game;
using pacMan.Game.Items;
using pacMan.Interfaces;
using pacMan.Utils;
@ -8,20 +6,14 @@ namespace pacMan.Services;
public class WebSocketService : IWebSocketService
{
private readonly ILogger<WebSocketService> _logger;
protected readonly ILogger<WebSocketService> Logger;
public WebSocketService(ILogger<WebSocketService> logger)
{
_logger = logger;
Logger = logger;
logger.Log(LogLevel.Debug, "WebSocket Service created");
}
public SynchronizedCollection<GameGroup> Games { get; } = new();
public int CountConnected => Connections?.GetInvocationList().Length ?? 0;
public event Func<ArraySegment<byte>, Task>? Connections; // TODO remove and use GameGroup
public async Task Send(WebSocket webSocket, ArraySegment<byte> segment)
{
await webSocket.SendAsync(
@ -30,18 +22,13 @@ public class WebSocketService : IWebSocketService
true,
CancellationToken.None);
_logger.Log(LogLevel.Debug, "Message sent to WebSocket");
}
public void SendToAll(ArraySegment<byte> segment)
{
Connections?.Invoke(segment);
Logger.Log(LogLevel.Debug, "Message sent to WebSocket");
}
public async Task<WebSocketReceiveResult> Receive(WebSocket webSocket, byte[] buffer)
{
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
_logger.Log(LogLevel.Debug,
Logger.Log(LogLevel.Debug,
"Message \"{}\" received from WebSocket",
buffer.GetString(result.Count));
return result;
@ -54,23 +41,6 @@ public class WebSocketService : IWebSocketService
closeStatusDescription,
CancellationToken.None);
_logger.Log(LogLevel.Information, "WebSocket connection closed");
}
public GameGroup AddPlayer(IPlayer player, Queue<DirectionalPosition> spawns)
{
var index = 0;
try
{
while (!Games[index].AddPlayer(player)) index++;
}
catch (ArgumentOutOfRangeException)
{
var game = new GameGroup(spawns);
game.AddPlayer(player);
Games.Add(game);
}
return Games[index];
Logger.Log(LogLevel.Information, "WebSocket connection closed");
}
}