From b7e8327ca8aa1a0af05b94a1050529430da4b445 Mon Sep 17 00:00:00 2001 From: Edmand46 Date: Sat, 22 Oct 2022 21:34:35 +0400 Subject: [PATCH] feat: websocket --- Ragon.SimpleServer/Program.cs | 1 + .../Source/AuthorizationProvider.cs | 19 ++- .../Source/SimplePluginFactory.cs | 4 +- Ragon/Sources/Application.cs | 78 ++++++------ ...thorizationManager.cs => Authorization.cs} | 6 +- .../{Configuration => }/Configuration.cs | 7 +- Ragon/Sources/{Utils => }/Dispatcher.cs | 2 +- Ragon/Sources/Entity/Entity.cs | 17 +++ Ragon/Sources/Extensions/StringExtensions.cs | 14 --- Ragon/Sources/Extensions/ValueExtensions.cs | 14 --- Ragon/Sources/GameRoom.cs | 62 ++++------ .../{IAuthorizationProvider.cs => Handler.cs} | 5 +- Ragon/Sources/IAuthorizationManager.cs | 6 - Ragon/Sources/IGameRoom.cs | 14 --- Ragon/Sources/Lobby.cs | 2 +- Ragon/Sources/Plugin/IPluginFactory.cs | 4 +- Ragon/Sources/Plugin/PluginBase.cs | 2 +- Ragon/Sources/{Utils => }/Scheduler.cs | 4 +- Ragon/Sources/Server/ENet/ENetServer.cs | 19 +-- Ragon/Sources/Server/Http/WebSocketPacket.cs | 7 ++ Ragon/Sources/Server/Http/WebSocketServer.cs | 115 ++++++++++++++++++ .../Server/Http/WebSocketTaskScheduler.cs | 46 +++++++ Ragon/Sources/Server/IEventHandler.cs | 11 ++ Ragon/Sources/Server/ISocketHandler.cs | 8 -- Ragon/Sources/Utils/IDispatcher.cs | 8 -- Ragon/Sources/Utils/IDispatcherInternal.cs | 6 - Ragon/Sources/Utils/IScheduler.cs | 12 -- 27 files changed, 312 insertions(+), 181 deletions(-) rename Ragon/Sources/{AuthorizationManager.cs => Authorization.cs} (91%) rename Ragon/Sources/{Configuration => }/Configuration.cs (88%) rename Ragon/Sources/{Utils => }/Dispatcher.cs (86%) delete mode 100755 Ragon/Sources/Extensions/StringExtensions.cs delete mode 100755 Ragon/Sources/Extensions/ValueExtensions.cs rename Ragon/Sources/{IAuthorizationProvider.cs => Handler.cs} (53%) delete mode 100644 Ragon/Sources/IAuthorizationManager.cs delete mode 100644 Ragon/Sources/IGameRoom.cs rename Ragon/Sources/{Utils => }/Scheduler.cs (97%) create mode 100644 Ragon/Sources/Server/Http/WebSocketPacket.cs create mode 100644 Ragon/Sources/Server/Http/WebSocketServer.cs create mode 100644 Ragon/Sources/Server/Http/WebSocketTaskScheduler.cs create mode 100644 Ragon/Sources/Server/IEventHandler.cs delete mode 100644 Ragon/Sources/Server/ISocketHandler.cs delete mode 100644 Ragon/Sources/Utils/IDispatcher.cs delete mode 100644 Ragon/Sources/Utils/IDispatcherInternal.cs delete mode 100644 Ragon/Sources/Utils/IScheduler.cs diff --git a/Ragon.SimpleServer/Program.cs b/Ragon.SimpleServer/Program.cs index 36173e6..5c5d2a6 100755 --- a/Ragon.SimpleServer/Program.cs +++ b/Ragon.SimpleServer/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using Game.Source; using Ragon.Core; diff --git a/Ragon.SimpleServer/Source/AuthorizationProvider.cs b/Ragon.SimpleServer/Source/AuthorizationProvider.cs index f263aef..6d6cfe6 100644 --- a/Ragon.SimpleServer/Source/AuthorizationProvider.cs +++ b/Ragon.SimpleServer/Source/AuthorizationProvider.cs @@ -4,10 +4,10 @@ using Ragon.Core; namespace Game.Source; -public class AuthorizationProviderByKey: IAuthorizationProvider +public class ApplicationHandlerByKey: IApplicationHandler { private Configuration _configuration; - public AuthorizationProviderByKey(Configuration configuration) + public ApplicationHandlerByKey(Configuration configuration) { _configuration = configuration; } @@ -26,4 +26,19 @@ public class AuthorizationProviderByKey: IAuthorizationProvider reject(0); } } + + public void OnCustomEvent(ushort peerId, ReadOnlySpan payload) + { + + } + + public void OnJoin(ushort peerId) + { + + } + + public void OnLeave(ushort peerId) + { + + } } \ No newline at end of file diff --git a/Ragon.SimpleServer/Source/SimplePluginFactory.cs b/Ragon.SimpleServer/Source/SimplePluginFactory.cs index a0131db..39bcbe3 100644 --- a/Ragon.SimpleServer/Source/SimplePluginFactory.cs +++ b/Ragon.SimpleServer/Source/SimplePluginFactory.cs @@ -10,9 +10,9 @@ namespace Game.Source return new SimplePlugin(); } - public IAuthorizationProvider CreateAuthorizationProvider(Configuration configuration) + public IApplicationHandler CreateAuthorizationProvider(Configuration configuration) { - return new AuthorizationProviderByKey(configuration); + return new ApplicationHandlerByKey(configuration); } } } \ No newline at end of file diff --git a/Ragon/Sources/Application.cs b/Ragon/Sources/Application.cs index 1cbb07f..86ab25e 100755 --- a/Ragon/Sources/Application.cs +++ b/Ragon/Sources/Application.cs @@ -6,21 +6,20 @@ using NLog; namespace Ragon.Core { - public class Application : IHandler + public class Application : IEventHandler { private readonly RoomManager _roomManager; private readonly Thread _thread; private readonly Lobby _lobby; private readonly ISocketServer _socketServer; - private readonly IDispatcherInternal _dispatcherInternal; - private readonly IDispatcher _dispatcher; + private readonly Dispatcher _dispatcher; private readonly ILogger _logger = LogManager.GetCurrentClassLogger(); private readonly float _deltaTime = 0.0f; private readonly Configuration _configuration; private readonly RagonSerializer _serializer; public ISocketServer SocketServer => _socketServer; - public IDispatcher Dispatcher => _dispatcher; + public Dispatcher Dispatcher => _dispatcher; public Application(PluginFactory factory, Configuration configuration) { @@ -30,10 +29,9 @@ namespace Ragon.Core _serializer = new RagonSerializer(); var dispatcher = new Dispatcher(); - _dispatcherInternal = dispatcher; _dispatcher = dispatcher; - _socketServer = new ENetServer(this); + _socketServer = new WebSocketServer(this); _deltaTime = 1000.0f / configuration.SendRate; _roomManager = new RoomManager(factory, this); @@ -81,45 +79,55 @@ namespace Ragon.Core while (true) { _socketServer.Process(); - _dispatcherInternal.Process(); + _dispatcher.Process(); _roomManager.Tick(_deltaTime); - + // Thread.Sleep((int) _deltaTime); } } - - - public void OnEvent(Event evnt) + + public void OnConnected(ushort peerId) { - if (evnt.Type == EventType.Timeout || evnt.Type == EventType.Disconnect) + _logger.Trace("Connected " + peerId); + } + + public void OnDisconnected(ushort peerId) + { + _logger.Trace("Disconnected " + peerId); + + var player = _lobby.AuthorizationManager.GetPlayer(peerId); + if (player != null) + _roomManager.Left(player, Array.Empty()); + + _lobby.OnDisconnected(peerId); + } + + public void OnData(ushort peerId, byte[] data) + { + try { - var player = _lobby.AuthorizationManager.GetPlayer((ushort) evnt.Peer.ID); - if (player != null) - _roomManager.Left(player, Array.Empty()); + _serializer.Clear(); + _serializer.FromArray(data); - _lobby.OnDisconnected((ushort) evnt.Peer.ID); + var operation = _serializer.ReadOperation(); + if (_roomManager.RoomsBySocket.TryGetValue(peerId, out var room)) + room.ProcessEvent(peerId, operation, _serializer); + + _lobby.ProcessEvent(peerId, operation, _serializer); } - - if (evnt.Type == EventType.Receive) + catch (Exception exception) { - try - { - var peerId = (ushort) evnt.Peer.ID; - var dataRaw = new byte[evnt.Packet.Length]; - evnt.Packet.CopyTo(dataRaw); - _serializer.FromArray(dataRaw); - - var operation = _serializer.ReadOperation(); - if (_roomManager.RoomsBySocket.TryGetValue(peerId, out var room)) - room.ProcessEvent(peerId, operation, _serializer); - - _lobby.ProcessEvent(peerId, operation, _serializer); - } - catch (Exception exception) - { - _logger.Error(exception); - } + _logger.Error(exception); } } + + public void OnTimeout(ushort peerId) + { + var player = _lobby.AuthorizationManager.GetPlayer(peerId); + if (player != null) + _roomManager.Left(player, Array.Empty()); + + _lobby.OnDisconnected(peerId); + } } } \ No newline at end of file diff --git a/Ragon/Sources/AuthorizationManager.cs b/Ragon/Sources/Authorization.cs similarity index 91% rename from Ragon/Sources/AuthorizationManager.cs rename to Ragon/Sources/Authorization.cs index 2d4a3cc..88c1d2f 100644 --- a/Ragon/Sources/AuthorizationManager.cs +++ b/Ragon/Sources/Authorization.cs @@ -5,17 +5,17 @@ using Ragon.Common; namespace Ragon.Core; -public class AuthorizationManager : IAuthorizationManager +public class AuthorizationManager { private Logger _logger = LogManager.GetCurrentClassLogger(); - private IAuthorizationProvider _provider; + private IApplicationHandler _provider; private Application _gameThread; private Lobby _lobby; private RagonSerializer _serializer; private readonly Dictionary _playersByPeers; private readonly Dictionary _playersByIds; - public AuthorizationManager(IAuthorizationProvider provider, Application gameThread, Lobby lobby, RagonSerializer serializer) + public AuthorizationManager(IApplicationHandler provider, Application gameThread, Lobby lobby, RagonSerializer serializer) { _serializer = serializer; _lobby = lobby; diff --git a/Ragon/Sources/Configuration/Configuration.cs b/Ragon/Sources/Configuration.cs similarity index 88% rename from Ragon/Sources/Configuration/Configuration.cs rename to Ragon/Sources/Configuration.cs index a3f4028..281879a 100755 --- a/Ragon/Sources/Configuration/Configuration.cs +++ b/Ragon/Sources/Configuration.cs @@ -29,11 +29,10 @@ namespace Ragon.Core _logger.Info($"OS: {Environment.OSVersion}"); _logger.Info($"Processors: {Environment.ProcessorCount}"); _logger.Info($"Runtime Version: {Environment.Version}"); - _logger.Info("=================================="); - _logger.Info("= ="); - _logger.Info($"={"Ragon".PadBoth(32)}="); - _logger.Info("= ="); + _logger.Info("| |"); + _logger.Info("| Ragon |"); + _logger.Info("| |"); _logger.Info("=================================="); } diff --git a/Ragon/Sources/Utils/Dispatcher.cs b/Ragon/Sources/Dispatcher.cs similarity index 86% rename from Ragon/Sources/Utils/Dispatcher.cs rename to Ragon/Sources/Dispatcher.cs index d8d8eed..33b1b71 100644 --- a/Ragon/Sources/Utils/Dispatcher.cs +++ b/Ragon/Sources/Dispatcher.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace Ragon.Core; -public class Dispatcher: IDispatcher, IDispatcherInternal +public class Dispatcher { public Queue _actions = new Queue(); diff --git a/Ragon/Sources/Entity/Entity.cs b/Ragon/Sources/Entity/Entity.cs index 635a0ee..fc04bb1 100644 --- a/Ragon/Sources/Entity/Entity.cs +++ b/Ragon/Sources/Entity/Entity.cs @@ -228,4 +228,21 @@ public class Entity } } } + + public void ProcessEvent(Player player, RagonSerializer reader) + { + var eventId = reader.ReadUShort(); + var eventMode = (RagonReplicationMode) reader.ReadByte(); + var targetMode = (RagonTarget) reader.ReadByte(); + var payloadData = reader.ReadData(reader.Size); + + Span payloadRaw = stackalloc byte[reader.Size]; + ReadOnlySpan payload = payloadRaw; + payloadData.CopyTo(payloadRaw); + + if (_room.Plugin.InternalHandle(player.PeerId, EntityId, eventId, ref payload)) + return; + + ReplicateEvent(player.PeerId, eventId, payload, eventMode, targetMode); + } } \ No newline at end of file diff --git a/Ragon/Sources/Extensions/StringExtensions.cs b/Ragon/Sources/Extensions/StringExtensions.cs deleted file mode 100755 index d8ec5ac..0000000 --- a/Ragon/Sources/Extensions/StringExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Ragon.Core -{ - public static class StringExtensions - { - public static string PadBoth(this string str, int length) - { - int spaces = length - str.Length; - int padLeft = spaces / 2 + str.Length; - return str.PadLeft(padLeft).PadRight(length); - } - } -} \ No newline at end of file diff --git a/Ragon/Sources/Extensions/ValueExtensions.cs b/Ragon/Sources/Extensions/ValueExtensions.cs deleted file mode 100755 index c51a904..0000000 --- a/Ragon/Sources/Extensions/ValueExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Ragon.Core -{ - public static class ValueExtensions - { - public static T Clamp(this T val, T min, T max) where T : IComparable - { - if (val.CompareTo(min) < 0) return min; - else if (val.CompareTo(max) > 0) return max; - else return val; - } - } -} \ No newline at end of file diff --git a/Ragon/Sources/GameRoom.cs b/Ragon/Sources/GameRoom.cs index 9313634..2de71bd 100755 --- a/Ragon/Sources/GameRoom.cs +++ b/Ragon/Sources/GameRoom.cs @@ -7,7 +7,7 @@ using Ragon.Common; namespace Ragon.Core { - public class GameRoom : IGameRoom + public class GameRoom { public int PlayersMin { get; private set; } public int PlayersMax { get; private set; } @@ -15,14 +15,14 @@ namespace Ragon.Core public int EntitiesCount => _entities.Count; public string Id { get; private set; } public string Map { get; private set; } - + public PluginBase Plugin => _plugin; private ILogger _logger = LogManager.GetCurrentClassLogger(); private Dictionary _players = new(); private Dictionary _entities = new(); private ushort _owner; - private readonly IScheduler _scheduler; private readonly ISocketServer _socketServer; + private readonly Scheduler _scheduler; private readonly Application _application; private readonly PluginBase _plugin; private readonly RagonSerializer _writer = new(512); @@ -131,7 +131,7 @@ namespace Ragon.Core } player.AttachEntity(entity); - AttachEntity(entity); + AttachEntity(player, entity); } _entitiesAll = _entities.Values.ToArray(); @@ -202,26 +202,15 @@ namespace Ragon.Core } case RagonOperation.REPLICATE_ENTITY_EVENT: { - var eventId = reader.ReadUShort(); - var eventMode = (RagonReplicationMode) reader.ReadByte(); - var targetMode = (RagonTarget) reader.ReadByte(); var entityId = reader.ReadUShort(); - var payloadData = reader.ReadData(reader.Size); - - Span payloadRaw = stackalloc byte[reader.Size]; - ReadOnlySpan payload = payloadRaw; - payloadData.CopyTo(payloadRaw); - if (!_entities.TryGetValue(entityId, out var ent)) { - _logger.Warn($"Entity not found for event with Id {eventId}"); + _logger.Warn($"Entity not found for event with Id {entityId}"); return; } - - if (_plugin.InternalHandle(peerId, entityId, eventId, ref payload)) - return; - - ent.ReplicateEvent(peerId, eventId, payload, eventMode, targetMode); + + var player = _players[peerId]; + ent.ProcessEvent(player, reader); break; } case RagonOperation.CREATE_ENTITY: @@ -233,7 +222,7 @@ namespace Ragon.Core _logger.Trace($"[{peerId}] Create Entity {entityType}"); var player = _players[peerId]; - var entity = new Entity(this, (ushort) player.PeerId, entityType, 0, eventAuthority); + var entity = new Entity(this, player.PeerId, entityType, 0, eventAuthority); for (var i = 0; i < propertiesCount; i++) { var propertyType = reader.ReadBool(); @@ -248,7 +237,7 @@ namespace Ragon.Core return; player.AttachEntity(entity); - AttachEntity(entity); + AttachEntity(player, entity); entity.Create(); break; @@ -258,21 +247,10 @@ namespace Ragon.Core var entityId = reader.ReadInt(); if (_entities.TryGetValue(entityId, out var entity)) { - if (entity.Authority == RagonAuthority.OwnerOnly && entity.OwnerId != peerId) - return; - var player = _players[peerId]; - var destroyPayload = reader.ReadData(reader.Size); - - player.DetachEntity(entity); - DetachEntity(entity); - - if (_plugin.OnEntityDestroyed(player, entity)) - return; - - entity.Destroy(destroyPayload); + var payload = reader.ReadData(reader.Size); + DetachEntity(player, entity, payload); } - break; } } @@ -293,19 +271,29 @@ namespace Ragon.Core { foreach (var peerId in _allPlayers) _application.SocketServer.Disconnect(peerId, 0); - + _plugin.OnStop(); _plugin.Detach(); + _scheduler.Dispose(); } - public void AttachEntity(Entity entity) + public void AttachEntity(Player player, Entity entity) { _entities.Add(entity.EntityId, entity); _entitiesAll = _entities.Values.ToArray(); } - public void DetachEntity(Entity entity) + public void DetachEntity(Player player, Entity entity, ReadOnlySpan payload) { + if (entity.Authority == RagonAuthority.OwnerOnly && entity.OwnerId != player.PeerId) + return; + + if (_plugin.OnEntityDestroyed(player, entity)) + return; + + player.DetachEntity(entity); + entity.Destroy(payload); + _entities.Remove(entity.EntityId); _entitiesAll = _entities.Values.ToArray(); } diff --git a/Ragon/Sources/IAuthorizationProvider.cs b/Ragon/Sources/Handler.cs similarity index 53% rename from Ragon/Sources/IAuthorizationProvider.cs rename to Ragon/Sources/Handler.cs index be8967a..dedf5f8 100644 --- a/Ragon/Sources/IAuthorizationProvider.cs +++ b/Ragon/Sources/Handler.cs @@ -3,7 +3,10 @@ using System.Threading.Tasks; namespace Ragon.Core; -public interface IAuthorizationProvider +public interface IApplicationHandler { Task OnAuthorizationRequest(string key, string playerName, byte[] additionalData, Action Accept, Action Reject); + public void OnCustomEvent(ushort peerId, ReadOnlySpan payload); + public void OnJoin(ushort peerId); + public void OnLeave(ushort peerId); } \ No newline at end of file diff --git a/Ragon/Sources/IAuthorizationManager.cs b/Ragon/Sources/IAuthorizationManager.cs deleted file mode 100644 index f0371b1..0000000 --- a/Ragon/Sources/IAuthorizationManager.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Ragon.Core; - -public interface IAuthorizationManager -{ - -} \ No newline at end of file diff --git a/Ragon/Sources/IGameRoom.cs b/Ragon/Sources/IGameRoom.cs deleted file mode 100644 index 0d7d3da..0000000 --- a/Ragon/Sources/IGameRoom.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Ragon.Core; - -public interface IGameRoom -{ - public string Id { get; } - public string Map { get; } - public int PlayersMin { get; } - public int PlayersMax { get; } - public int PlayersCount { get; } - - public Player GetPlayerById(string id); - public Player GetPlayerByPeer(ushort peerId); - public Entity GetEntityById(int entityId); -} \ No newline at end of file diff --git a/Ragon/Sources/Lobby.cs b/Ragon/Sources/Lobby.cs index bf3b4ea..fcb3c14 100644 --- a/Ragon/Sources/Lobby.cs +++ b/Ragon/Sources/Lobby.cs @@ -15,7 +15,7 @@ public class Lobby public AuthorizationManager AuthorizationManager => _authorizationManager; - public Lobby(IAuthorizationProvider provider, RoomManager manager, Application application) + public Lobby(IApplicationHandler provider, RoomManager manager, Application application) { _roomManager = manager; _application = application; diff --git a/Ragon/Sources/Plugin/IPluginFactory.cs b/Ragon/Sources/Plugin/IPluginFactory.cs index 227569b..2a79149 100644 --- a/Ragon/Sources/Plugin/IPluginFactory.cs +++ b/Ragon/Sources/Plugin/IPluginFactory.cs @@ -3,8 +3,6 @@ namespace Ragon.Core public interface PluginFactory { public PluginBase CreatePlugin(string map); - public IAuthorizationProvider CreateAuthorizationProvider(Configuration configuration); + public IApplicationHandler CreateAuthorizationProvider(Configuration configuration); } - - } \ No newline at end of file diff --git a/Ragon/Sources/Plugin/PluginBase.cs b/Ragon/Sources/Plugin/PluginBase.cs index e9d5dcb..98f98ba 100755 --- a/Ragon/Sources/Plugin/PluginBase.cs +++ b/Ragon/Sources/Plugin/PluginBase.cs @@ -14,7 +14,7 @@ namespace Ragon.Core private Dictionary> _entityEvents = new(); private readonly RagonSerializer _serializer = new(); - protected IGameRoom Room { get; private set; } = null!; + protected GameRoom Room { get; private set; } = null!; protected ILogger Logger = null!; public void Attach(GameRoom gameRoom) diff --git a/Ragon/Sources/Utils/Scheduler.cs b/Ragon/Sources/Scheduler.cs similarity index 97% rename from Ragon/Sources/Utils/Scheduler.cs rename to Ragon/Sources/Scheduler.cs index c0c75f8..9e22f8a 100644 --- a/Ragon/Sources/Utils/Scheduler.cs +++ b/Ragon/Sources/Scheduler.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; -using System.ComponentModel; + namespace Ragon.Core { - public class Scheduler: IScheduler + public class Scheduler: IDisposable { List _scheduledTasks; diff --git a/Ragon/Sources/Server/ENet/ENetServer.cs b/Ragon/Sources/Server/ENet/ENetServer.cs index c9e1f43..c9e9552 100755 --- a/Ragon/Sources/Server/ENet/ENetServer.cs +++ b/Ragon/Sources/Server/ENet/ENetServer.cs @@ -14,12 +14,12 @@ namespace Ragon.Core private Address _address; private Event _netEvent; private Peer[] _peers; - private IHandler _handler; + private IEventHandler _eventHandler; private Stopwatch _timer; - public ENetServer(IHandler handler) + public ENetServer(IEventHandler eventHandler) { - _handler = handler; + _eventHandler = eventHandler; _timer = Stopwatch.StartNew(); _peers = Array.Empty(); _host = new Host(); @@ -115,23 +115,28 @@ namespace Ragon.Core // break; // } _peers[_netEvent.Peer.ID] = _netEvent.Peer; - _handler.OnEvent(_netEvent); + _eventHandler.OnConnected((ushort)_netEvent.Peer.ID); break; } case EventType.Disconnect: { - _handler.OnEvent(_netEvent); + _eventHandler.OnDisconnected((ushort)_netEvent.Peer.ID); break; } case EventType.Timeout: { - _handler.OnEvent(_netEvent); + _eventHandler.OnTimeout((ushort)_netEvent.Peer.ID); break; } case EventType.Receive: { - _handler.OnEvent(_netEvent); + var peerId = (ushort) _netEvent.Peer.ID; + var dataRaw = new byte[_netEvent.Packet.Length]; + + _netEvent.Packet.CopyTo(dataRaw); _netEvent.Packet.Dispose(); + + _eventHandler.OnData(peerId, dataRaw); break; } } diff --git a/Ragon/Sources/Server/Http/WebSocketPacket.cs b/Ragon/Sources/Server/Http/WebSocketPacket.cs new file mode 100644 index 0000000..dda1364 --- /dev/null +++ b/Ragon/Sources/Server/Http/WebSocketPacket.cs @@ -0,0 +1,7 @@ +namespace Ragon.Core; + +public class WebSocketPacket +{ + public ushort PeerId; + public byte[] Data; +} \ No newline at end of file diff --git a/Ragon/Sources/Server/Http/WebSocketServer.cs b/Ragon/Sources/Server/Http/WebSocketServer.cs new file mode 100644 index 0000000..ebda88d --- /dev/null +++ b/Ragon/Sources/Server/Http/WebSocketServer.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; +using NLog; +using Ragon.Common; + +namespace Ragon.Core; + +public class WebSocketServer : ISocketServer +{ + private ushort _idSequencer = 0; + private ILogger _logger = LogManager.GetCurrentClassLogger(); + private Dictionary _webSockets = new Dictionary(); + private Queue _events; + private IEventHandler _eventHandler; + private WebSocketTaskScheduler _webSocketScheduler; + private TaskFactory _taskFactory; + private HttpListener _httpListener; + + public WebSocketServer(IEventHandler eventHandler) + { + _eventHandler = eventHandler; + _events = new Queue(1024); + _webSocketScheduler = new WebSocketTaskScheduler(); + _taskFactory = new TaskFactory(_webSocketScheduler); + } + + async void StartAccept() + { + while (true) + { + var context = await _httpListener.GetContextAsync(); + if (!context.Request.IsWebSocketRequest) continue; + + var webSocketContext = await context.AcceptWebSocketAsync(null); + var webSocket = webSocketContext.WebSocket; + + _idSequencer++; + _webSockets.Add(_idSequencer, webSocket); + + _ = _taskFactory.StartNew(() => StartListen(webSocket, _idSequencer)); + } + } + + async void StartListen(WebSocket webSocket, ushort peerId) + { + _eventHandler.OnConnected(peerId); + + var bytes = new byte[2048]; + var buffer = new Memory(bytes); + while (webSocket.State == WebSocketState.Open) + { + var result = await webSocket.ReceiveAsync(buffer, CancellationToken.None); + var dataRaw = buffer.Slice(0, result.Count); + _eventHandler.OnData(peerId, dataRaw.ToArray()); + } + + _eventHandler.OnDisconnected(peerId); + } + + async void ProcessQueue() + { + while (_events.TryDequeue(out var evnt)) + { + if (_webSockets.TryGetValue(evnt.PeerId, out var ws) && ws.State == WebSocketState.Open) + { + await ws.SendAsync(evnt.Data, WebSocketMessageType.Binary, WebSocketMessageFlags.EndOfMessage, CancellationToken.None); + } + } + } + + public void Start(ushort port, int connections, uint protocol) + { + _httpListener = new HttpListener(); + _httpListener.Prefixes.Add($"http://*:{port}/"); + _httpListener.Start(); + + _taskFactory.StartNew(StartAccept); + + var protocolDecoded = (protocol >> 16 & 0xFF) + "." + (protocol >> 8 & 0xFF) + "." + (protocol & 0xFF); + _logger.Info($"Network listening on http://*:{port}/"); + _logger.Info($"Protocol: {protocolDecoded}"); + } + + public void Process() + { + _webSocketScheduler.Process(); + + ProcessQueue(); + } + + public void Stop() + { + _httpListener.Stop(); + } + + public void Send(ushort peerId, byte[] data, DeliveryType type) + { + _events.Enqueue(new WebSocketPacket() {PeerId = peerId, Data = data}); + } + + public void Broadcast(ushort[] peersIds, byte[] data, DeliveryType type) + { + foreach (var peerId in peersIds) + _events.Enqueue(new WebSocketPacket() {PeerId = peerId, Data = data}); + } + + public void Disconnect(ushort peerId, uint errorCode) + { + _webSockets[peerId].CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None); + } +} \ No newline at end of file diff --git a/Ragon/Sources/Server/Http/WebSocketTaskScheduler.cs b/Ragon/Sources/Server/Http/WebSocketTaskScheduler.cs new file mode 100644 index 0000000..9a92db7 --- /dev/null +++ b/Ragon/Sources/Server/Http/WebSocketTaskScheduler.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Threading.Channels; +using System.Threading.Tasks; + +namespace Ragon.Core; + +public class WebSocketTaskScheduler: TaskScheduler +{ + private ChannelReader _reader; + private ChannelWriter _writer; + private Channel _channel; + + public WebSocketTaskScheduler() + { + _channel = Channel.CreateUnbounded(); + _reader = _channel.Reader; + _writer = _channel.Writer; + } + + protected override IEnumerable? GetScheduledTasks() + { + throw new NotSupportedException(); + } + + protected override void QueueTask(Task task) + { + _writer.TryWrite(task); + } + + protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) + { + return false; + } + + public void Process() + { + while (_reader.TryRead(out var task)) + { + TryExecuteTask(task); + + if (task.Status != TaskStatus.RanToCompletion) + _writer.TryWrite(task); + } + } +} \ No newline at end of file diff --git a/Ragon/Sources/Server/IEventHandler.cs b/Ragon/Sources/Server/IEventHandler.cs new file mode 100644 index 0000000..a1f75ef --- /dev/null +++ b/Ragon/Sources/Server/IEventHandler.cs @@ -0,0 +1,11 @@ +using ENet; + +namespace Ragon.Core; + +public interface IEventHandler +{ + public void OnConnected(ushort peerId); + public void OnDisconnected(ushort peerId); + public void OnTimeout(ushort peerId); + public void OnData(ushort peerId, byte[] data); +} \ No newline at end of file diff --git a/Ragon/Sources/Server/ISocketHandler.cs b/Ragon/Sources/Server/ISocketHandler.cs deleted file mode 100644 index eb8df6b..0000000 --- a/Ragon/Sources/Server/ISocketHandler.cs +++ /dev/null @@ -1,8 +0,0 @@ -using ENet; - -namespace Ragon.Core; - -public interface IHandler -{ - public void OnEvent(Event evnt); -} \ No newline at end of file diff --git a/Ragon/Sources/Utils/IDispatcher.cs b/Ragon/Sources/Utils/IDispatcher.cs deleted file mode 100644 index ace4de7..0000000 --- a/Ragon/Sources/Utils/IDispatcher.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace Ragon.Core; - -public interface IDispatcher -{ - public void Dispatch(Action action); -} \ No newline at end of file diff --git a/Ragon/Sources/Utils/IDispatcherInternal.cs b/Ragon/Sources/Utils/IDispatcherInternal.cs deleted file mode 100644 index b3ad25e..0000000 --- a/Ragon/Sources/Utils/IDispatcherInternal.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Ragon.Core; - -public interface IDispatcherInternal -{ - public void Process(); -} \ No newline at end of file diff --git a/Ragon/Sources/Utils/IScheduler.cs b/Ragon/Sources/Utils/IScheduler.cs deleted file mode 100644 index 9f5fa67..0000000 --- a/Ragon/Sources/Utils/IScheduler.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Ragon.Core; - -public interface IScheduler -{ - public SchedulerTask Schedule(Action action, float interval, int count = 1); - public SchedulerTask ScheduleForever(Action action, float interval); - public void StopSchedule(SchedulerTask schedulerTask); - - public void Tick(float deltaTime); -} \ No newline at end of file