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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,18 +1,10 @@
using System.Net.WebSockets; using System.Net.WebSockets;
using pacMan.Game;
using pacMan.Game.Items;
using pacMan.Services;
namespace pacMan.Interfaces; namespace pacMan.Interfaces;
public interface IWebSocketService public interface IWebSocketService
{ {
SynchronizedCollection<GameGroup> Games { get; }
int CountConnected { get; }
event Func<ArraySegment<byte>, Task>? Connections;
Task Send(WebSocket webSocket, ArraySegment<byte> segment); Task Send(WebSocket webSocket, ArraySegment<byte> segment);
void SendToAll(ArraySegment<byte> segment);
Task<WebSocketReceiveResult> Receive(WebSocket webSocket, byte[] buffer); Task<WebSocketReceiveResult> Receive(WebSocket webSocket, byte[] buffer);
Task Close(WebSocket webSocket, WebSocketCloseStatus closeStatus, string? closeStatusDescription); 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. // Add services to the container.
builder.Services.AddControllersWithViews(); builder.Services.AddControllersWithViews();
builder.Services.AddSingleton<IWebSocketService, WebSocketService>() builder.Services
.AddSingleton<IWebSocketService, WebSocketService>()
.AddSingleton<GameService>()
.AddTransient<IActionService, ActionService>(); .AddTransient<IActionService, ActionService>();
var app = builder.Build(); var app = builder.Build();

View File

@ -2,7 +2,6 @@ using System.Text.Json;
using Microsoft.CSharp.RuntimeBinder; using Microsoft.CSharp.RuntimeBinder;
using pacMan.Game; using pacMan.Game;
using pacMan.Game.Items; using pacMan.Game.Items;
using pacMan.Interfaces;
namespace pacMan.Services; namespace pacMan.Services;
@ -21,14 +20,14 @@ public interface IActionService
public class ActionService : IActionService public class ActionService : IActionService
{ {
private readonly IDiceCup _diceCup; private readonly IDiceCup _diceCup;
private readonly GameService _gameService;
private readonly ILogger<ActionService> _logger; 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; _logger = logger;
_diceCup = new DiceCup(); _diceCup = new DiceCup();
_wsService = wsService; _gameService = gameService;
} }
public GameGroup? Group { get; set; } public GameGroup? Group { get; set; }
@ -63,7 +62,7 @@ public class ActionService : IActionService
PlayerInfoData data = JsonSerializer.Deserialize<PlayerInfoData>(message.Data); PlayerInfoData data = JsonSerializer.Deserialize<PlayerInfoData>(message.Data);
Player = data.Player; Player = data.Player;
Group = _wsService.AddPlayer(Player, data.Spawns); Group = _gameService.AddPlayer(Player, data.Spawns);
} }
catch (RuntimeBinderException e) catch (RuntimeBinderException e)
{ {
@ -112,6 +111,6 @@ public struct PlayerInfoData
public struct ReadyData public struct ReadyData
{ {
public required bool AllReady { get; set; } public required bool AllReady { get; init; }
public required IEnumerable<IPlayer> Players { get; set; } 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 System.Net.WebSockets;
using pacMan.Game;
using pacMan.Game.Items;
using pacMan.Interfaces; using pacMan.Interfaces;
using pacMan.Utils; using pacMan.Utils;
@ -8,20 +6,14 @@ namespace pacMan.Services;
public class WebSocketService : IWebSocketService public class WebSocketService : IWebSocketService
{ {
private readonly ILogger<WebSocketService> _logger; protected readonly ILogger<WebSocketService> Logger;
public WebSocketService(ILogger<WebSocketService> logger) public WebSocketService(ILogger<WebSocketService> logger)
{ {
_logger = logger; Logger = logger;
logger.Log(LogLevel.Debug, "WebSocket Service created"); 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) public async Task Send(WebSocket webSocket, ArraySegment<byte> segment)
{ {
await webSocket.SendAsync( await webSocket.SendAsync(
@ -30,18 +22,13 @@ public class WebSocketService : IWebSocketService
true, true,
CancellationToken.None); CancellationToken.None);
_logger.Log(LogLevel.Debug, "Message sent to WebSocket"); Logger.Log(LogLevel.Debug, "Message sent to WebSocket");
}
public void SendToAll(ArraySegment<byte> segment)
{
Connections?.Invoke(segment);
} }
public async Task<WebSocketReceiveResult> Receive(WebSocket webSocket, byte[] buffer) public async Task<WebSocketReceiveResult> Receive(WebSocket webSocket, byte[] buffer)
{ {
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None); var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
_logger.Log(LogLevel.Debug, Logger.Log(LogLevel.Debug,
"Message \"{}\" received from WebSocket", "Message \"{}\" received from WebSocket",
buffer.GetString(result.Count)); buffer.GetString(result.Count));
return result; return result;
@ -54,23 +41,6 @@ public class WebSocketService : IWebSocketService
closeStatusDescription, closeStatusDescription,
CancellationToken.None); CancellationToken.None);
_logger.Log(LogLevel.Information, "WebSocket connection closed"); 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];
} }
} }