From 4d8ed1105a9ca0321d85e50210bcaee9942f3608 Mon Sep 17 00:00:00 2001 From: Edmand46 Date: Fri, 16 Dec 2022 00:05:46 +0400 Subject: [PATCH] wip --- Ragon.Core/Application.cs | 102 ++++ Ragon.Core/Configuration.cs | 45 ++ .../Executor.cs | 14 +- Ragon.Core/Game/Entity.cs | 195 +++++++ .../Entity => Ragon.Core/Game}/EntityEvent.cs | 2 +- Ragon.Core/Game/EntityList.cs | 35 ++ Ragon.Core/Game/EntityState.cs | 100 ++++ .../Game/EntityStateProperty.cs | 6 +- Ragon.Core/Game/Room.cs | 171 ++++++ Ragon.Core/Game/RoomInformation.cs | 13 + Ragon.Core/Game/RoomPlayer.cs | 36 ++ Ragon.Core/HandlerRegistry.cs | 109 ++++ Ragon.Core/Handlers/Abstract/IHandler.cs | 8 + Ragon.Core/Handlers/AuthHandler.cs | 39 ++ Ragon.Core/Handlers/EntityCreateHandler.cs | 32 ++ Ragon.Core/Handlers/EntityDestroyHandler.cs | 21 + Ragon.Core/Handlers/EntityEventHandler.cs | 47 ++ Ragon.Core/Handlers/EntityStateHandler.cs | 28 + Ragon.Core/Handlers/RoomCreateHandler.cs | 82 +++ Ragon.Core/Handlers/RoomJoinHandler.cs | 62 +++ .../Handlers/RoomJoinOrCreateHandler.cs | 79 +++ Ragon.Core/Handlers/RoomLeaveHandler.cs | 19 + Ragon.Core/Handlers/SceneLoadedHandler.cs | 123 +++++ Ragon.Core/Lobby/Abstract/ILobby.cs | 11 + Ragon.Core/Lobby/LobbyInMemory.cs | 56 ++ Ragon.Core/Lobby/LobbyPlayer.cs | 27 + Ragon.Core/PlayerContext.cs | 28 + Ragon.Core/Ragon.Core.csproj | 21 + Ragon.Core/Time/IScheduleTask.cs | 6 + Ragon.Core/Time/Scheduler.cs | 27 + .../Ragon.Protocol.csproj | 1 + .../Sources/RagonAuthority.cs | 0 .../Sources/RagonOperation.cs | 0 .../Sources/RagonReplicationMode.cs | 0 .../Sources/RagonRoomParameters.cs | 0 .../Sources/RagonSerializable.cs | 0 .../Sources/RagonSerializer.cs | 0 .../Sources/RagonTarget.cs | 2 + Ragon.Protocol/Sources/RagonVersion.cs | 28 + .../NLog.config | 0 Ragon.Relay/Program.cs | 30 ++ .../Ragon.Relay.csproj | 2 +- .../config.json | 9 +- Ragon.Server.ENet/ENetConnection.cs | 17 + Ragon.Server.ENet/ENetReliableChannel.cs | 23 + Ragon.Server.ENet/ENetServer.cs | 115 ++++ Ragon.Server.ENet/ENetUnreliableChannel.cs | 23 + .../Ragon.Server.ENet.csproj | 8 +- .../Ragon.Server.WebSockets.csproj | 10 + Ragon.Server/INetworkChannel.cs | 6 + Ragon.Server/INetworkConnection.cs | 8 + Ragon.Server/INetworkListener.cs | 9 + Ragon.Server/INetworkServer.cs | 8 + Ragon.Server/NetworkConfiguration.cs | 9 + Ragon.Server/Ragon.Server.csproj | 13 + Ragon.SimpleServer/Program.cs | 19 - .../Source/AuthorizationProvider.cs | 44 -- .../Source/Plugins/EmptyPlugin.cs | 41 -- .../Source/SimplePluginFactory.cs | 18 - Ragon.Stress/Program.cs | 206 ------- Ragon.sln | 36 +- Ragon/Ragon.csproj | 29 - Ragon/Sources/Application.cs | 144 ----- Ragon/Sources/Authorization.cs | 96 ---- Ragon/Sources/Bootstrap.cs | 22 - Ragon/Sources/Configuration.cs | 49 -- Ragon/Sources/Dispatcher.cs | 22 - Ragon/Sources/Entity/Entity.cs | 249 --------- Ragon/Sources/GameRoom.cs | 503 ------------------ Ragon/Sources/Handler.cs | 12 - Ragon/Sources/Lobby.cs | 125 ----- Ragon/Sources/Player.cs | 28 - Ragon/Sources/Plugin/IPluginFactory.cs | 8 - Ragon/Sources/Plugin/PluginBase.cs | 206 ------- Ragon/Sources/RoomManager.cs | 122 ----- Ragon/Sources/Scheduler.cs | 98 ---- Ragon/Sources/Server/DeliveryType.cs | 7 - Ragon/Sources/Server/ENet/ENetServer.cs | 156 ------ Ragon/Sources/Server/Http/WebSocketPacket.cs | 7 - Ragon/Sources/Server/Http/WebSocketServer.cs | 123 ----- Ragon/Sources/Server/IEventHandler.cs | 11 - Ragon/Sources/Server/ISocketServer.cs | 11 - global.json | 2 +- 83 files changed, 1872 insertions(+), 2387 deletions(-) create mode 100644 Ragon.Core/Application.cs create mode 100644 Ragon.Core/Configuration.cs rename Ragon/Sources/Server/Http/WebSocketTaskScheduler.cs => Ragon.Core/Executor.cs (87%) create mode 100644 Ragon.Core/Game/Entity.cs rename {Ragon/Sources/Entity => Ragon.Core/Game}/EntityEvent.cs (88%) create mode 100644 Ragon.Core/Game/EntityList.cs create mode 100644 Ragon.Core/Game/EntityState.cs rename Ragon/Sources/Entity/EntityProperty.cs => Ragon.Core/Game/EntityStateProperty.cs (85%) create mode 100644 Ragon.Core/Game/Room.cs create mode 100644 Ragon.Core/Game/RoomInformation.cs create mode 100644 Ragon.Core/Game/RoomPlayer.cs create mode 100644 Ragon.Core/HandlerRegistry.cs create mode 100644 Ragon.Core/Handlers/Abstract/IHandler.cs create mode 100644 Ragon.Core/Handlers/AuthHandler.cs create mode 100644 Ragon.Core/Handlers/EntityCreateHandler.cs create mode 100644 Ragon.Core/Handlers/EntityDestroyHandler.cs create mode 100644 Ragon.Core/Handlers/EntityEventHandler.cs create mode 100644 Ragon.Core/Handlers/EntityStateHandler.cs create mode 100644 Ragon.Core/Handlers/RoomCreateHandler.cs create mode 100644 Ragon.Core/Handlers/RoomJoinHandler.cs create mode 100644 Ragon.Core/Handlers/RoomJoinOrCreateHandler.cs create mode 100644 Ragon.Core/Handlers/RoomLeaveHandler.cs create mode 100644 Ragon.Core/Handlers/SceneLoadedHandler.cs create mode 100644 Ragon.Core/Lobby/Abstract/ILobby.cs create mode 100644 Ragon.Core/Lobby/LobbyInMemory.cs create mode 100644 Ragon.Core/Lobby/LobbyPlayer.cs create mode 100644 Ragon.Core/PlayerContext.cs create mode 100644 Ragon.Core/Ragon.Core.csproj create mode 100644 Ragon.Core/Time/IScheduleTask.cs create mode 100644 Ragon.Core/Time/Scheduler.cs rename Ragon.Common/Ragon.Common.csproj => Ragon.Protocol/Ragon.Protocol.csproj (93%) rename {Ragon.Common => Ragon.Protocol}/Sources/RagonAuthority.cs (100%) rename {Ragon.Common => Ragon.Protocol}/Sources/RagonOperation.cs (100%) rename {Ragon.Common => Ragon.Protocol}/Sources/RagonReplicationMode.cs (100%) rename {Ragon.Common => Ragon.Protocol}/Sources/RagonRoomParameters.cs (100%) rename {Ragon.Common => Ragon.Protocol}/Sources/RagonSerializable.cs (100%) rename {Ragon.Common => Ragon.Protocol}/Sources/RagonSerializer.cs (100%) rename {Ragon.Common => Ragon.Protocol}/Sources/RagonTarget.cs (76%) create mode 100644 Ragon.Protocol/Sources/RagonVersion.cs rename {Ragon.SimpleServer => Ragon.Relay}/NLog.config (100%) create mode 100755 Ragon.Relay/Program.cs rename Ragon.SimpleServer/Ragon.SimpleServer.csproj => Ragon.Relay/Ragon.Relay.csproj (90%) rename {Ragon.SimpleServer => Ragon.Relay}/config.json (52%) create mode 100644 Ragon.Server.ENet/ENetConnection.cs create mode 100644 Ragon.Server.ENet/ENetReliableChannel.cs create mode 100755 Ragon.Server.ENet/ENetServer.cs create mode 100644 Ragon.Server.ENet/ENetUnreliableChannel.cs rename Ragon.Stress/Ragon.Stress.csproj => Ragon.Server.ENet/Ragon.Server.ENet.csproj (67%) create mode 100644 Ragon.Server.WebSockets/Ragon.Server.WebSockets.csproj create mode 100644 Ragon.Server/INetworkChannel.cs create mode 100644 Ragon.Server/INetworkConnection.cs create mode 100644 Ragon.Server/INetworkListener.cs create mode 100644 Ragon.Server/INetworkServer.cs create mode 100644 Ragon.Server/NetworkConfiguration.cs create mode 100644 Ragon.Server/Ragon.Server.csproj delete mode 100755 Ragon.SimpleServer/Program.cs delete mode 100644 Ragon.SimpleServer/Source/AuthorizationProvider.cs delete mode 100755 Ragon.SimpleServer/Source/Plugins/EmptyPlugin.cs delete mode 100644 Ragon.SimpleServer/Source/SimplePluginFactory.cs delete mode 100644 Ragon.Stress/Program.cs delete mode 100755 Ragon/Ragon.csproj delete mode 100755 Ragon/Sources/Application.cs delete mode 100644 Ragon/Sources/Authorization.cs delete mode 100755 Ragon/Sources/Bootstrap.cs delete mode 100755 Ragon/Sources/Configuration.cs delete mode 100644 Ragon/Sources/Dispatcher.cs delete mode 100644 Ragon/Sources/Entity/Entity.cs delete mode 100755 Ragon/Sources/GameRoom.cs delete mode 100644 Ragon/Sources/Handler.cs delete mode 100644 Ragon/Sources/Lobby.cs delete mode 100755 Ragon/Sources/Player.cs delete mode 100644 Ragon/Sources/Plugin/IPluginFactory.cs delete mode 100755 Ragon/Sources/Plugin/PluginBase.cs delete mode 100644 Ragon/Sources/RoomManager.cs delete mode 100644 Ragon/Sources/Scheduler.cs delete mode 100644 Ragon/Sources/Server/DeliveryType.cs delete mode 100755 Ragon/Sources/Server/ENet/ENetServer.cs delete mode 100644 Ragon/Sources/Server/Http/WebSocketPacket.cs delete mode 100644 Ragon/Sources/Server/Http/WebSocketServer.cs delete mode 100644 Ragon/Sources/Server/IEventHandler.cs delete mode 100644 Ragon/Sources/Server/ISocketServer.cs diff --git a/Ragon.Core/Application.cs b/Ragon.Core/Application.cs new file mode 100644 index 0000000..58405d7 --- /dev/null +++ b/Ragon.Core/Application.cs @@ -0,0 +1,102 @@ +using System.Diagnostics; +using NLog; +using Ragon.Common; +using Ragon.Core.Lobby; +using Ragon.Core.Time; +using Ragon.Server; +using Ragon.Server.ENet; + +namespace Ragon.Core; + +public class Application : INetworkListener +{ + private Logger _logger = LogManager.GetCurrentClassLogger(); + + private INetworkServer _server; + private Thread _dedicatedThread; + private Configuration _configuration; + private HandlerRegistry _handlerRegistry; + private ILobby _lobby; + private Scheduler _scheduler; + private Dictionary _contexts = new(); + + public Application(Configuration configuration) + { + _configuration = configuration; + _dedicatedThread = new Thread(Execute); + _dedicatedThread.IsBackground = true; + + _handlerRegistry = new HandlerRegistry(); + _lobby = new LobbyInMemory(); + _scheduler = new Scheduler(); + + if (configuration.Socket == "enet") + _server = new ENetServer(); + + Debug.Assert(_server != null, $"Socket type not supported: {configuration.Socket}. Supported: [enet, websocket]"); + } + + public void Execute() + { + while (true) + { + _scheduler.Tick(); + _server.Poll(); + + Thread.Sleep((int) 1000.0f / _configuration.SendRate); + } + } + + public void Start() + { + var networkConfiguration = new NetworkConfiguration() + { + LimitConnections = _configuration.LimitConnections, + Protocol = RagonVersion.Parse(_configuration.Protocol), + Address = "0.0.0.0", + Port = _configuration.Port, + }; + + _server.Start(this, networkConfiguration); + _dedicatedThread.Start(); + } + + public void Stop() + { + _server.Stop(); + _dedicatedThread.Interrupt(); + } + + public void OnConnected(INetworkConnection connection) + { + var context = new PlayerContext(connection, new LobbyPlayer(connection)); + context.Lobby = _lobby; + context.Scheduler = _scheduler; + + _logger.Trace($"Connected {connection.Id}"); + _contexts.Add(connection.Id, context); + } + + public void OnDisconnected(INetworkConnection connection) + { + _logger.Trace($"Disconnected {connection.Id}"); + + if (_contexts.Remove(connection.Id, out var context)) + { + context.Room?.RemovePlayer(context.RoomPlayer); + context.Dispose(); + } + } + + public void OnTimeout(INetworkConnection connection) + { + if (_contexts.Remove(connection.Id, out var context)) + context.Dispose(); + } + + public void OnData(INetworkConnection connection, byte[] data) + { + if (_contexts.TryGetValue(connection.Id, out var context)) + _handlerRegistry.Handle(context, data); + } +} \ No newline at end of file diff --git a/Ragon.Core/Configuration.cs b/Ragon.Core/Configuration.cs new file mode 100644 index 0000000..2b341a3 --- /dev/null +++ b/Ragon.Core/Configuration.cs @@ -0,0 +1,45 @@ +using Newtonsoft.Json; +using NLog; + +namespace Ragon.Core; + +[Serializable] +public struct Configuration +{ + public string Key; + public string Protocol; + public string Socket; + public ushort SendRate; + public ushort Port; + public int SkipTimeout; + public int ReconnectTimeout; + public int LimitConnections; + public int LimitPlayersPerRoom; + public int LimitRooms; + + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private static readonly string ServerVersion = "1.1.0-rc"; + + private static void CopyrightInfo() + { + Logger.Info($"Server Version: {ServerVersion}"); + Logger.Info($"Machine Name: {Environment.MachineName}"); + Logger.Info($"OS: {Environment.OSVersion}"); + Logger.Info($"Processors: {Environment.ProcessorCount}"); + Logger.Info($"Runtime Version: {Environment.Version}"); + Logger.Info("=================================="); + Logger.Info("| |"); + Logger.Info("| Ragon |"); + Logger.Info("| |"); + Logger.Info("=================================="); + } + + public static Configuration Load(string filePath) + { + CopyrightInfo(); + + var data = File.ReadAllText(filePath); + var configuration = JsonConvert.DeserializeObject(data); + return configuration; + } +} \ No newline at end of file diff --git a/Ragon/Sources/Server/Http/WebSocketTaskScheduler.cs b/Ragon.Core/Executor.cs similarity index 87% rename from Ragon/Sources/Server/Http/WebSocketTaskScheduler.cs rename to Ragon.Core/Executor.cs index 1f8429d..1023959 100644 --- a/Ragon/Sources/Server/Http/WebSocketTaskScheduler.cs +++ b/Ragon.Core/Executor.cs @@ -6,18 +6,24 @@ using NLog.LayoutRenderers.Wrappers; namespace Ragon.Core; -public class WebSocketTaskScheduler: TaskScheduler +public class Executor: TaskScheduler { private ChannelReader _reader; private ChannelWriter _writer; private Queue _pendingTasks; - public WebSocketTaskScheduler() + public void Run(Task task) + { + task.Start(this); + } + + public Executor() { var channel = Channel.CreateUnbounded(); - _pendingTasks = new Queue(); _reader = channel.Reader; _writer = channel.Writer; + + _pendingTasks = new Queue(); } protected override IEnumerable? GetScheduledTasks() @@ -35,7 +41,7 @@ public class WebSocketTaskScheduler: TaskScheduler return false; } - public void Process() + public void Execute() { while (_reader.TryRead(out var task)) { diff --git a/Ragon.Core/Game/Entity.cs b/Ragon.Core/Game/Entity.cs new file mode 100644 index 0000000..9252a47 --- /dev/null +++ b/Ragon.Core/Game/Entity.cs @@ -0,0 +1,195 @@ +using Ragon.Common; + +namespace Ragon.Core.Game; + +public class Entity +{ + private static ushort _idGenerator = 0; + + public ushort Id { get; private set; } + public RoomPlayer Owner { get; private set; } + public RagonAuthority Authority { get; private set; } + public EntityState State { get; private set; } + public byte[] Payload { get; private set; } + public ushort StaticId { get; private set; } + public ushort Type { get; private set; } + + private List _bufferedEvents; + + public Entity(RoomPlayer owner, ushort type, ushort staticId, RagonAuthority eventAuthority) + { + Owner = owner; + StaticId = staticId; + Type = type; + Id = _idGenerator++; + Payload = Array.Empty(); + Authority = eventAuthority; + State = new EntityState(this); + + _bufferedEvents = new List(); + } + + public void SetPayload(byte[] payload) + { + Payload = payload; + } + + public void SetOwner(RoomPlayer owner) + { + Owner = owner; + } + + public void RestoreBufferedEvents(RoomPlayer roomPlayer, RagonSerializer writer) + { + foreach (var bufferedEvent in _bufferedEvents) + { + writer.Clear(); + writer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT); + writer.WriteUShort(bufferedEvent.EventId); + writer.WriteUShort(bufferedEvent.PeerId); + writer.WriteByte((byte) RagonReplicationMode.Server); + writer.WriteUShort(Id); + + ReadOnlySpan data = bufferedEvent.EventData.AsSpan(); + writer.WriteData(ref data); + + var sendData = writer.ToArray(); + roomPlayer.Connection.ReliableChannel.Send(sendData); + } + } + + public void Create() + { + var room = Owner.Room; + var serializer = room.Writer; + + serializer.Clear(); + serializer.WriteOperation(RagonOperation.CREATE_ENTITY); + serializer.WriteUShort(Type); + serializer.WriteUShort(Id); + serializer.WriteUShort(Owner.Connection.Id); + + ReadOnlySpan entityPayload = Payload.AsSpan(); + serializer.WriteUShort((ushort) entityPayload.Length); + serializer.WriteData(ref entityPayload); + + var sendData = serializer.ToArray(); + foreach (var player in room.ReadyPlayersList) + player.Connection.ReliableChannel.Send(sendData); + } + + public void Destroy() + { + var room = Owner.Room; + var serializer = room.Writer; + + serializer.Clear(); + serializer.WriteOperation(RagonOperation.DESTROY_ENTITY); + serializer.WriteInt(Id); + serializer.WriteUShort(0); + // serializer.WriteData(ref Payload); + + var sendData = serializer.ToArray(); + foreach (var player in room.ReadyPlayersList) + player.Connection.ReliableChannel.Send(sendData); + } + + public void ReplicateEvent( + RoomPlayer caller, + ushort eventId, + ReadOnlySpan payload, + RagonReplicationMode eventMode, + RoomPlayer targetPlayer + ) + { + var room = Owner.Room; + var serializer = room.Writer; + + serializer.Clear(); + serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT); + serializer.WriteUShort(eventId); + serializer.WriteUShort(caller.Connection.Id); + serializer.WriteByte((byte) eventMode); + serializer.WriteUShort(Id); + serializer.WriteData(ref payload); + + var sendData = serializer.ToArray(); + targetPlayer.Connection.ReliableChannel.Send(sendData); + } + + public void ReplicateEvent( + RoomPlayer caller, + ushort eventId, + ReadOnlySpan payload, + RagonReplicationMode eventMode, + RagonTarget targetMode + ) + { + if (Authority == RagonAuthority.OwnerOnly && + Owner.Connection.Id != caller.Connection.Id) + { + Console.WriteLine($"Player have not enought authority for event with Id {eventId}"); + return; + } + + if (eventMode == RagonReplicationMode.Buffered && targetMode != RagonTarget.Owner) + { + var bufferedEvent = new EntityEvent() + { + EventData = payload.ToArray(), + Target = targetMode, + EventId = eventId, + PeerId = caller.Connection.Id, + }; + _bufferedEvents.Add(bufferedEvent); + } + + var room = Owner.Room; + var serializer = room.Writer; + + serializer.Clear(); + serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT); + serializer.WriteUShort(eventId); + serializer.WriteUShort(caller.Connection.Id); + serializer.WriteByte((byte) eventMode); + serializer.WriteUShort(Id); + serializer.WriteData(ref payload); + + var sendData = serializer.ToArray(); + + switch (targetMode) + { + case RagonTarget.Owner: + { + Owner.Connection.ReliableChannel.Send(sendData); + break; + } + case RagonTarget.ExceptOwner: + { + foreach (var roomPlayer in room.ReadyPlayersList) + { + if (roomPlayer.Connection.Id != Owner.Connection.Id) + roomPlayer.Connection.ReliableChannel.Send(sendData); + } + + break; + } + case RagonTarget.ExceptInvoker: + { + foreach (var roomPlayer in room.ReadyPlayersList) + { + if (roomPlayer.Connection.Id != caller.Connection.Id) + roomPlayer.Connection.ReliableChannel.Send(sendData); + } + + break; + } + case RagonTarget.All: + { + foreach (var roomPlayer in room.ReadyPlayersList) + roomPlayer.Connection.ReliableChannel.Send(sendData); + break; + } + } + } +} \ No newline at end of file diff --git a/Ragon/Sources/Entity/EntityEvent.cs b/Ragon.Core/Game/EntityEvent.cs similarity index 88% rename from Ragon/Sources/Entity/EntityEvent.cs rename to Ragon.Core/Game/EntityEvent.cs index 1bf79b4..808eb3e 100644 --- a/Ragon/Sources/Entity/EntityEvent.cs +++ b/Ragon.Core/Game/EntityEvent.cs @@ -1,6 +1,6 @@ using Ragon.Common; -namespace Ragon.Core; +namespace Ragon.Core.Game; public class EntityEvent { diff --git a/Ragon.Core/Game/EntityList.cs b/Ragon.Core/Game/EntityList.cs new file mode 100644 index 0000000..459208f --- /dev/null +++ b/Ragon.Core/Game/EntityList.cs @@ -0,0 +1,35 @@ +namespace Ragon.Core.Game; + +public class EntityList +{ + private List _dynamicEntitiesList = new List(); + private List _staticEntitesList = new List(); + private Dictionary _entitiesMap = new Dictionary(); + + public IReadOnlyList StaticList => _staticEntitesList; + public IReadOnlyList DynamicList => _dynamicEntitiesList; + public IReadOnlyDictionary Map => _entitiesMap; + + public void Add(Entity entity) + { + if (entity.StaticId != 0) + _staticEntitesList.Add(entity); + else + _dynamicEntitiesList.Add(entity); + + _entitiesMap.Add(entity.Id, entity); + } + + public Entity Remove(Entity entity) + { + if (_entitiesMap.Remove(entity.Id, out var existEntity)) + { + _staticEntitesList.Remove(entity); + _dynamicEntitiesList.Remove(entity); + + return existEntity; + } + + return null; + } +} \ No newline at end of file diff --git a/Ragon.Core/Game/EntityState.cs b/Ragon.Core/Game/EntityState.cs new file mode 100644 index 0000000..844f917 --- /dev/null +++ b/Ragon.Core/Game/EntityState.cs @@ -0,0 +1,100 @@ +using NLog; +using Ragon.Common; + +namespace Ragon.Core.Game; + +public class EntityState +{ + private Logger _logger = LogManager.GetCurrentClassLogger(); + private List _properties; + private Entity _entity; + + public EntityState(Entity entity, int capacity = 10) + { + _entity = entity; + _properties = new List(10); + } + + public void AddProperty(EntityStateProperty property) + { + _properties.Add(property); + } + + public void Write(RagonSerializer serializer) + { + serializer.WriteUShort(_entity.Id); + + for (int propertyIndex = 0; propertyIndex < _properties.Count; propertyIndex++) + { + var property = _properties[propertyIndex]; + if (property.IsDirty) + { + serializer.WriteBool(true); + var span = serializer.GetWritableData(property.Size); + var data = property.Read(); + data.CopyTo(span); + property.Clear(); + } + else + { + serializer.WriteBool(false); + } + } + } + + public void Read(RagonSerializer serializer) + { + for (var i = 0; i < _properties.Count; i++) + { + if (serializer.ReadBool()) + { + var property = _properties[i]; + var size = property.Size; + if (!property.IsFixed) + size = serializer.ReadUShort(); + + if (size > property.Capacity) + { + Console.WriteLine($"Property {i} payload too large, size: {size}"); + continue; + } + + var propertyPayload = serializer.ReadData(size); + property.Write(ref propertyPayload); + property.Size = size; + } + } + } + + public void Snapshot(RagonSerializer serializer) + { + ReadOnlySpan payload = _entity.Payload.AsSpan(); + + serializer.WriteUShort(_entity.Type); + serializer.WriteUShort(_entity.Id); + + if (_entity.StaticId != 0) + serializer.WriteUShort(_entity.StaticId); + + serializer.WriteUShort(_entity.Owner.Connection.Id); + serializer.WriteUShort((ushort) payload.Length); + serializer.WriteData(ref payload); + + for (int propertyIndex = 0; propertyIndex < _properties.Count; propertyIndex++) + { + var property = _properties[propertyIndex]; + var hasPayload = property.IsFixed || property.Size > 0 && !property.IsFixed; + if (hasPayload) + { + serializer.WriteBool(true); + var span = serializer.GetWritableData(property.Size); + var data = property.Read(); + data.CopyTo(span); + } + else + { + serializer.WriteBool(false); + } + } + } +} \ No newline at end of file diff --git a/Ragon/Sources/Entity/EntityProperty.cs b/Ragon.Core/Game/EntityStateProperty.cs similarity index 85% rename from Ragon/Sources/Entity/EntityProperty.cs rename to Ragon.Core/Game/EntityStateProperty.cs index 46e037c..242e0bc 100644 --- a/Ragon/Sources/Entity/EntityProperty.cs +++ b/Ragon.Core/Game/EntityStateProperty.cs @@ -1,9 +1,9 @@ using System; using Ragon.Common; -namespace Ragon.Core; +namespace Ragon.Core.Game; -public class EntityProperty +public class EntityStateProperty { public int Size { get; set; } public int Capacity { get; set; } @@ -11,7 +11,7 @@ public class EntityProperty public bool IsFixed { get; private set; } private byte[] _data; - public EntityProperty(int size, bool isFixed) + public EntityStateProperty(int size, bool isFixed) { Capacity = 512; Size = size; diff --git a/Ragon.Core/Game/Room.cs b/Ragon.Core/Game/Room.cs new file mode 100644 index 0000000..b3270a3 --- /dev/null +++ b/Ragon.Core/Game/Room.cs @@ -0,0 +1,171 @@ +using Ragon.Common; + +namespace Ragon.Core.Game; + +public class Room +{ + public string Id { get; } + public RoomInformation Info { get; } + public RoomPlayer Owner { get; set; } + + public Dictionary Players { get; } + public List WaitPlayersList { get; private set; } + public List ReadyPlayersList { get; private set; } + public List PlayerList { get; private set; } + + public Dictionary Entities { get; private set; } + public List DynamicEntitiesList { get; private set; } + public List StaticEntitiesList { get; private set; } + public List EntityList { get; private set; } + + private HashSet _entitiesDirtySet; + private RagonSerializer _writer; + + public RagonSerializer Writer => _writer; + public PluginBase Plugin { get; set; } + + public Room(string roomId, RoomInformation info, PluginBase plugin) + { + Id = roomId; + Info = info; + + Plugin = plugin; + + Players = new Dictionary(info.Max); + WaitPlayersList = new List(info.Max); + ReadyPlayersList = new List(info.Max); + PlayerList = new List(info.Max); + + Entities = new Dictionary(); + DynamicEntitiesList = new List(); + StaticEntitiesList = new List(); + EntityList = new List(); + + _entitiesDirtySet = new HashSet(); + _writer = new RagonSerializer(512); + } + + public void AttachEntity(RoomPlayer newOwner, Entity entity) + { + Entities.Add(entity.Id, entity); + EntityList.Add(entity); + + if (entity.StaticId == 0) + DynamicEntitiesList.Add(entity); + else + StaticEntitiesList.Add(entity); + + entity.Create(); + + newOwner.Entities.Add(entity); + } + + public void DetachEntity(RoomPlayer currentOwner, Entity entity, byte[] payload) + { + Entities.Remove(entity.Id); + EntityList.Remove(entity); + StaticEntitiesList.Remove(entity); + DynamicEntitiesList.Remove(entity); + _entitiesDirtySet.Remove(entity); + + entity.Destroy(); + currentOwner.Entities.Remove(entity); + } + + public void Tick() + { + var entities = (ushort) _entitiesDirtySet.Count; + if (entities > 0) + { + _writer.Clear(); + _writer.WriteOperation(RagonOperation.REPLICATE_ENTITY_STATE); + _writer.WriteUShort(entities); + + foreach (var entity in _entitiesDirtySet) + entity.State.Write(_writer); + + _entitiesDirtySet.Clear(); + + var sendData = _writer.ToArray(); + foreach (var roomPlayer in ReadyPlayersList) + roomPlayer.Connection.UnreliableChannel.Send(sendData); + } + } + + public void AddPlayer(RoomPlayer player) + { + if (Players.Count == 0) + Owner = player; + + player.Attach(this); + + PlayerList.Add(player); + Players.Add(player.Connection.Id, player); + } + + public void RemovePlayer(RoomPlayer roomPlayer) + { + if (Players.Remove(roomPlayer.Connection.Id, out var player)) + { + PlayerList.Remove(player); + + { + _writer.Clear(); + _writer.WriteOperation(RagonOperation.PLAYER_LEAVED); + _writer.WriteString(player.Id); + + var entitiesToDelete = player.Entities.DynamicList; + _writer.WriteUShort((ushort) entitiesToDelete.Count); + foreach (var entity in entitiesToDelete) + { + _writer.WriteUShort(entity.Id); + EntityList.Remove(entity); + } + + var sendData = _writer.ToArray(); + Broadcast(sendData); + } + + if (roomPlayer.Connection.Id == Owner.Connection.Id && PlayerList.Count > 0) + { + var nextOwner = PlayerList[0]; + + Owner = nextOwner; + + var entitiesToUpdate = roomPlayer.Entities.StaticList; + + _writer.Clear(); + _writer.WriteOperation(RagonOperation.OWNERSHIP_CHANGED); + _writer.WriteString(Owner.Id); + _writer.WriteUShort((ushort) entitiesToUpdate.Count); + + foreach (var entity in entitiesToUpdate) + { + _writer.WriteUShort(entity.Id); + + entity.SetOwner(nextOwner); + nextOwner.Entities.Add(entity); + } + + var sendData = _writer.ToArray(); + Broadcast(sendData); + } + } + } + + public void UpdateReadyPlayerList() + { + ReadyPlayersList = PlayerList.Where(p => p.IsLoaded).ToList(); + } + + public void Track(Entity entity) + { + _entitiesDirtySet.Add(entity); + } + + public void Broadcast(byte[] data) + { + foreach (var readyPlayer in ReadyPlayersList) + readyPlayer.Connection.ReliableChannel.Send(data); + } +} \ No newline at end of file diff --git a/Ragon.Core/Game/RoomInformation.cs b/Ragon.Core/Game/RoomInformation.cs new file mode 100644 index 0000000..cd608e7 --- /dev/null +++ b/Ragon.Core/Game/RoomInformation.cs @@ -0,0 +1,13 @@ +namespace Ragon.Core.Game; + +public class RoomInformation +{ + public string Map { get; set; } + public int Min { get; set; } + public int Max { get; set; } + + public override string ToString() + { + return $"Map: {Map} Count: {Min}/{Max}"; + } +} \ No newline at end of file diff --git a/Ragon.Core/Game/RoomPlayer.cs b/Ragon.Core/Game/RoomPlayer.cs new file mode 100644 index 0000000..c6224b0 --- /dev/null +++ b/Ragon.Core/Game/RoomPlayer.cs @@ -0,0 +1,36 @@ +using Ragon.Server; + +namespace Ragon.Core.Game; + +public class RoomPlayer +{ + public INetworkConnection Connection { get; } + public string Id { get; } + public string Name { get; } + public bool IsLoaded { get; private set; } + public Room Room { get; private set; } + public EntityList Entities { get; private set; } + + public RoomPlayer(INetworkConnection connection, string id, string name) + { + Id = id; + Name = name; + Connection = connection; + Entities = new EntityList(); + } + + public void Attach(Room room) + { + Room = room; + } + + public void Detach() + { + Room = null!; + } + + public void SetReady() + { + IsLoaded = true; + } +} \ No newline at end of file diff --git a/Ragon.Core/HandlerRegistry.cs b/Ragon.Core/HandlerRegistry.cs new file mode 100644 index 0000000..3d37f36 --- /dev/null +++ b/Ragon.Core/HandlerRegistry.cs @@ -0,0 +1,109 @@ +using NLog; +using Ragon.Common; +using Ragon.Core.Handlers; + +namespace Ragon.Core; + +public sealed class HandlerRegistry +{ + private IHandler _entityEventHandler; + private IHandler _entityCreateHandler; + private IHandler _entityDestroyHandler; + private IHandler _entityStateHandler; + private IHandler _sceneLoadedHandler; + + private IHandler _authorizationHandler; + private IHandler _joinOrCreateHandler; + private IHandler _createHandler; + private IHandler _joinHandler; + private IHandler _leaveHandler; + + private Logger _logger = LogManager.GetCurrentClassLogger(); + private RagonSerializer _reader; + private RagonSerializer _writer; + + public HandlerRegistry() + { + _reader = new RagonSerializer(2048); + _writer = new RagonSerializer(2048); + + _authorizationHandler = new AuthHandler(); + _joinOrCreateHandler = new JoinOrCreateHandler(); + _sceneLoadedHandler = new SceneLoadedHandler(); + _createHandler = new CreateHandler(); + _joinHandler = new JoinHandler(); + _leaveHandler = new LeaveHandler(); + + _entityEventHandler = new EntityEventHandler(); + _entityCreateHandler = new EntityCreateHandler(); + _entityDestroyHandler = new EntityDestroyHandler(); + _entityStateHandler = new EntityStateHandler(); + } + + public void Handle(PlayerContext context, byte[] data) + { + _writer.Clear(); + _reader.Clear(); + _reader.FromArray(data); + + var operation = _reader.ReadOperation(); + switch (operation) + { + case RagonOperation.REPLICATE_ENTITY_EVENT: + { + if (context.RoomPlayer != null) + _entityEventHandler.Handle(context, _reader, _writer); + break; + } + case RagonOperation.REPLICATE_ENTITY_STATE: + { + if (context.RoomPlayer != null) + _entityStateHandler.Handle(context, _reader, _writer); + break; + } + case RagonOperation.CREATE_ENTITY: + { + if (context.RoomPlayer != null) + _entityCreateHandler.Handle(context, _reader, _writer); + break; + } + case RagonOperation.DESTROY_ENTITY: + { + if (context.RoomPlayer != null) + _entityDestroyHandler.Handle(context, _reader, _writer); + break; + } + case RagonOperation.SCENE_LOADED: + { + if (context.RoomPlayer != null) + _sceneLoadedHandler.Handle(context, _reader, _writer); + break; + } + case RagonOperation.JOIN_OR_CREATE_ROOM: + { + _joinOrCreateHandler.Handle(context, _reader, _writer); + break; + } + case RagonOperation.CREATE_ROOM: + { + _createHandler.Handle(context, _reader, _writer); + break; + } + case RagonOperation.JOIN_ROOM: + { + _joinHandler.Handle(context, _reader, _writer); + break; + } + case RagonOperation.LEAVE_ROOM: + { + _leaveHandler.Handle(context, _reader, _writer); + break; + } + case RagonOperation.AUTHORIZE: + { + _authorizationHandler.Handle(context, _reader, _writer); + break; + } + } + } +} \ No newline at end of file diff --git a/Ragon.Core/Handlers/Abstract/IHandler.cs b/Ragon.Core/Handlers/Abstract/IHandler.cs new file mode 100644 index 0000000..1962506 --- /dev/null +++ b/Ragon.Core/Handlers/Abstract/IHandler.cs @@ -0,0 +1,8 @@ +using Ragon.Common; + +namespace Ragon.Core; + +public interface IHandler +{ + public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer); +} \ No newline at end of file diff --git a/Ragon.Core/Handlers/AuthHandler.cs b/Ragon.Core/Handlers/AuthHandler.cs new file mode 100644 index 0000000..d11e520 --- /dev/null +++ b/Ragon.Core/Handlers/AuthHandler.cs @@ -0,0 +1,39 @@ +using NLog; +using Ragon.Common; +using Ragon.Core.Lobby; + +namespace Ragon.Core.Handlers; + +public sealed class AuthHandler: IHandler +{ + private Logger _logger = LogManager.GetCurrentClassLogger(); + + public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer) + { + if (context.LobbyPlayer.Status == LobbyPlayerStatus.Authorized) + { + _logger.Warn("Player already authorized"); + return; + } + + var key = reader.ReadString(); + var playerName = reader.ReadString(); + var additionalData = reader.ReadData(reader.Size); + + context.LobbyPlayer.Name = playerName; + context.LobbyPlayer.AdditionalData = additionalData.ToArray(); + context.LobbyPlayer.Status = LobbyPlayerStatus.Authorized; + + var playerId = context.LobbyPlayer.Id; + + writer.Clear(); + writer.WriteOperation(RagonOperation.AUTHORIZED_SUCCESS); + writer.WriteString(playerId); + writer.WriteString(playerName); + + var sendData = writer.ToArray(); + context.Connection.ReliableChannel.Send(sendData); + + _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} authorized"); + } +} \ No newline at end of file diff --git a/Ragon.Core/Handlers/EntityCreateHandler.cs b/Ragon.Core/Handlers/EntityCreateHandler.cs new file mode 100644 index 0000000..9d8a192 --- /dev/null +++ b/Ragon.Core/Handlers/EntityCreateHandler.cs @@ -0,0 +1,32 @@ +using NLog; +using Ragon.Common; +using Ragon.Core.Game; + + +namespace Ragon.Core.Handlers; + +public sealed class EntityCreateHandler: IHandler +{ + private Logger _logger = LogManager.GetCurrentClassLogger(); + public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer) + { + var entityType = reader.ReadUShort(); + var eventAuthority = (RagonAuthority) reader.ReadByte(); + var propertiesCount = reader.ReadUShort(); + + var entity = new Entity(context.RoomPlayer, entityType, 0, eventAuthority); + for (var i = 0; i < propertiesCount; i++) + { + var propertyType = reader.ReadBool(); + var propertySize = reader.ReadUShort(); + entity.State.AddProperty(new EntityStateProperty(propertySize, propertyType)); + } + + var entityPayload = reader.ReadData(reader.Size); + entity.SetPayload(entityPayload.ToArray()); + + context.Room.AttachEntity(context.RoomPlayer, entity); + + _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} created entity {entity.Id}:{entity.Type}"); + } +} \ No newline at end of file diff --git a/Ragon.Core/Handlers/EntityDestroyHandler.cs b/Ragon.Core/Handlers/EntityDestroyHandler.cs new file mode 100644 index 0000000..6a5df1a --- /dev/null +++ b/Ragon.Core/Handlers/EntityDestroyHandler.cs @@ -0,0 +1,21 @@ +using NLog; +using Ragon.Common; + +namespace Ragon.Core.Handlers; + +public sealed class EntityDestroyHandler: IHandler +{ + private Logger _logger = LogManager.GetCurrentClassLogger(); + public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer) + { + var entityId = reader.ReadUShort(); + if (context.Room.Entities.TryGetValue(entityId, out var entity)) + { + var player = context.RoomPlayer; + var payload = reader.ReadData(reader.Size); + + context.Room.DetachEntity(player, entity, Array.Empty()); + _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} destoyed entity {entity.Id}"); + } + } +} \ No newline at end of file diff --git a/Ragon.Core/Handlers/EntityEventHandler.cs b/Ragon.Core/Handlers/EntityEventHandler.cs new file mode 100644 index 0000000..be1b3b4 --- /dev/null +++ b/Ragon.Core/Handlers/EntityEventHandler.cs @@ -0,0 +1,47 @@ +using NLog; +using Ragon.Common; + +namespace Ragon.Core.Handlers; + +public sealed class EntityEventHandler: IHandler +{ + private Logger _logger = LogManager.GetCurrentClassLogger(); + + public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer) + { + var player = context.RoomPlayer; + var room = context.Room; + var entityId = reader.ReadUShort(); + + if (!room.Entities.TryGetValue(entityId, out var ent)) + { + _logger.Warn($"Entity not found for event with Id {entityId}"); + return; + } + + var eventId = reader.ReadUShort(); + var eventMode = (RagonReplicationMode) reader.ReadByte(); + var targetMode = (RagonTarget) reader.ReadByte(); + var payloadData = reader.ReadData(reader.Size); + var targetPlayerPeerId = reader.ReadUShort(); + + if (targetMode == RagonTarget.Player && context.Room.Players.TryGetValue(targetPlayerPeerId, out var targetPlayer)) + { + Span payloadRaw = stackalloc byte[payloadData.Length]; + ReadOnlySpan payload = payloadRaw; + payloadData.CopyTo(payloadRaw); + + _logger.Trace($"Event {eventId} Payload: {payloadData.Length} to {targetMode}"); + ent.ReplicateEvent(player, eventId, payload, eventMode, targetPlayer); + } + else + { + Span payloadRaw = stackalloc byte[payloadData.Length]; + ReadOnlySpan payload = payloadRaw; + payloadData.CopyTo(payloadRaw); + + _logger.Trace($"Event {eventId} Payload: {payloadData.Length} to {targetMode}"); + ent.ReplicateEvent(player, eventId, payload, eventMode, targetMode); + } + } +} \ No newline at end of file diff --git a/Ragon.Core/Handlers/EntityStateHandler.cs b/Ragon.Core/Handlers/EntityStateHandler.cs new file mode 100644 index 0000000..1a981bb --- /dev/null +++ b/Ragon.Core/Handlers/EntityStateHandler.cs @@ -0,0 +1,28 @@ +using NLog; +using Ragon.Common; + +namespace Ragon.Core.Handlers; + +public sealed class EntityStateHandler: IHandler +{ + private ILogger _logger = LogManager.GetCurrentClassLogger(); + + public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer) + { + var room = context.Room; + var entitiesCount = reader.ReadUShort(); + for (var entityIndex = 0; entityIndex < entitiesCount; entityIndex++) + { + var entityId = reader.ReadUShort(); + if (room.Entities.TryGetValue(entityId, out var entity)) + { + entity.State.Read(reader); + room.Track(entity); + } + else + { + _logger.Error($"Entity with Id {entityId} not found, replication interrupted"); + } + } + } +} \ No newline at end of file diff --git a/Ragon.Core/Handlers/RoomCreateHandler.cs b/Ragon.Core/Handlers/RoomCreateHandler.cs new file mode 100644 index 0000000..54459cc --- /dev/null +++ b/Ragon.Core/Handlers/RoomCreateHandler.cs @@ -0,0 +1,82 @@ +using NLog; +using Ragon.Common; +using Ragon.Core.Game; +using Ragon.Core.Lobby; + +namespace Ragon.Core.Handlers; + +public sealed class CreateHandler: IHandler +{ + private RagonRoomParameters _roomParameters = new(); + private Logger _logger = LogManager.GetCurrentClassLogger(); + + public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer) + { + if (context.LobbyPlayer.Status == LobbyPlayerStatus.Unauthorized) + { + _logger.Warn($"Player {context.Connection.Id} not authorized for this request"); + return; + } + + var custom = reader.ReadBool(); + var roomId = Guid.NewGuid().ToString(); + + if (custom) + { + roomId = reader.ReadString(); + if (context.Lobby.FindRoomById(roomId, out _)) + { + writer.Clear(); + writer.WriteOperation(RagonOperation.JOIN_FAILED); + writer.WriteString($"Room with id {roomId} already exists"); + + var sendData = writer.ToArray(); + context.Connection.ReliableChannel.Send(sendData); + + _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} join failed to room {roomId}, room already exist"); + return; + } + } + + _roomParameters.Deserialize(reader); + + var information = new RoomInformation() + { + Map = _roomParameters.Map, + Max = _roomParameters.Max, + Min = _roomParameters.Min, + }; + + var lobbyPlayer = context.LobbyPlayer; + var roomPlayer = new RoomPlayer(lobbyPlayer.Connection, lobbyPlayer.Id, lobbyPlayer.Name); + + var room = new Room(roomId, information, new PluginBase()); + room.AddPlayer(roomPlayer); + + context.Room?.RemovePlayer(context.RoomPlayer); + context.Room = room; + context.RoomPlayer = roomPlayer; + context.Lobby.Persist(room); + + _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} create room {room.Id} {information}"); + + JoinSuccess(roomPlayer, room, writer); + + _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} joined to room {room.Id}"); + } + + private void JoinSuccess(RoomPlayer player, Room room, RagonSerializer writer) + { + writer.Clear(); + writer.WriteOperation(RagonOperation.JOIN_SUCCESS); + writer.WriteString(room.Id); + writer.WriteString(player.Id); + writer.WriteString(room.Owner.Id); + writer.WriteUShort((ushort) room.Info.Min); + writer.WriteUShort((ushort) room.Info.Max); + writer.WriteString(room.Info.Map); + + var sendData = writer.ToArray(); + player.Connection.ReliableChannel.Send(sendData); + } +} \ No newline at end of file diff --git a/Ragon.Core/Handlers/RoomJoinHandler.cs b/Ragon.Core/Handlers/RoomJoinHandler.cs new file mode 100644 index 0000000..43784d7 --- /dev/null +++ b/Ragon.Core/Handlers/RoomJoinHandler.cs @@ -0,0 +1,62 @@ +using NLog; +using Ragon.Common; +using Ragon.Core.Game; +using Ragon.Core.Lobby; + +namespace Ragon.Core.Handlers; + +public sealed class JoinHandler : IHandler +{ + private Logger _logger = LogManager.GetCurrentClassLogger(); + + public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer) + { + var roomId = reader.ReadString(); + var lobbyPlayer = context.LobbyPlayer; + + if (!context.Lobby.FindRoomById(roomId, out var existsRoom)) + { + JoinFailed(lobbyPlayer, writer); + + _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} failed to join room {roomId}"); + return; + } + + var roomPlayer = new RoomPlayer(lobbyPlayer.Connection, lobbyPlayer.Id, lobbyPlayer.Name); + + context.Room?.RemovePlayer(context.RoomPlayer); + context.Room = existsRoom; + context.RoomPlayer = roomPlayer; + + existsRoom.AddPlayer(roomPlayer); + + JoinSuccess(roomPlayer, existsRoom, writer); + + _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} joined to {existsRoom.Id}"); + } + + private void JoinSuccess(RoomPlayer player, Room room, RagonSerializer writer) + { + writer.Clear(); + writer.WriteOperation(RagonOperation.JOIN_SUCCESS); + writer.WriteString(room.Id); + writer.WriteString(player.Id); + writer.WriteString(room.Owner.Id); + writer.WriteUShort((ushort) room.Info.Min); + writer.WriteUShort((ushort) room.Info.Max); + writer.WriteString(room.Info.Map); + + var sendData = writer.ToArray(); + player.Connection.ReliableChannel.Send(sendData); + } + + private void JoinFailed(LobbyPlayer player, RagonSerializer writer) + { + writer.Clear(); + writer.WriteOperation(RagonOperation.JOIN_FAILED); + writer.WriteString($"Room not exists"); + + var sendData = writer.ToArray(); + player.Connection.ReliableChannel.Send(sendData); + } +} \ No newline at end of file diff --git a/Ragon.Core/Handlers/RoomJoinOrCreateHandler.cs b/Ragon.Core/Handlers/RoomJoinOrCreateHandler.cs new file mode 100644 index 0000000..1661777 --- /dev/null +++ b/Ragon.Core/Handlers/RoomJoinOrCreateHandler.cs @@ -0,0 +1,79 @@ +using NLog; +using Ragon.Common; +using Ragon.Core.Game; +using Ragon.Core.Lobby; + +namespace Ragon.Core.Handlers; + +public sealed class JoinOrCreateHandler : IHandler +{ + private RagonRoomParameters _roomParameters = new(); + private Logger _logger = LogManager.GetCurrentClassLogger(); + + public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer) + { + if (context.LobbyPlayer.Status == LobbyPlayerStatus.Unauthorized) + { + _logger.Warn("Player not authorized for this request"); + return; + } + + var roomId = Guid.NewGuid().ToString(); + var lobbyPlayer = context.LobbyPlayer; + + _roomParameters.Deserialize(reader); + + if (context.Lobby.FindRoomByMap(_roomParameters.Map, out var existsRoom)) + { + var roomPlayer = new RoomPlayer(lobbyPlayer.Connection, lobbyPlayer.Id, lobbyPlayer.Name); + + context.Room?.RemovePlayer(context.RoomPlayer); + context.Room = existsRoom; + context.RoomPlayer = roomPlayer; + + existsRoom.AddPlayer(roomPlayer); + + JoinSuccess(roomPlayer, existsRoom, writer); + } + else + { + var information = new RoomInformation() + { + Map = _roomParameters.Map, + Max = _roomParameters.Max, + Min = _roomParameters.Min, + }; + + var room = new Room(roomId, information, new PluginBase()); + context.Lobby.Persist(room); + + var roomPlayer = new RoomPlayer(lobbyPlayer.Connection, lobbyPlayer.Id, lobbyPlayer.Name); + room.AddPlayer(roomPlayer); + + context.Room?.RemovePlayer(context.RoomPlayer); + context.Room = room; + context.RoomPlayer = roomPlayer; + + _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} create room {room.Id} {information}"); + + JoinSuccess(roomPlayer, room, writer); + } + } + + private void JoinSuccess(RoomPlayer player, Room room, RagonSerializer writer) + { + writer.Clear(); + writer.WriteOperation(RagonOperation.JOIN_SUCCESS); + writer.WriteString(room.Id); + writer.WriteString(player.Id); + writer.WriteString(room.Owner.Id); + writer.WriteUShort((ushort) room.Info.Min); + writer.WriteUShort((ushort) room.Info.Max); + writer.WriteString(room.Info.Map); + + var sendData = writer.ToArray(); + player.Connection.ReliableChannel.Send(sendData); + + _logger.Trace($"Joined to room {room.Id}"); + } +} \ No newline at end of file diff --git a/Ragon.Core/Handlers/RoomLeaveHandler.cs b/Ragon.Core/Handlers/RoomLeaveHandler.cs new file mode 100644 index 0000000..a541fbb --- /dev/null +++ b/Ragon.Core/Handlers/RoomLeaveHandler.cs @@ -0,0 +1,19 @@ +using NLog; +using Ragon.Common; + +namespace Ragon.Core.Handlers; + +public sealed class LeaveHandler: IHandler +{ + private Logger _logger = LogManager.GetCurrentClassLogger(); + public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer) + { + var room = context.Room; + var roomPlayer = context.RoomPlayer; + if (room != null) + { + context.Room?.RemovePlayer(roomPlayer); + _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} leaved from {room.Id}"); + } + } +} \ No newline at end of file diff --git a/Ragon.Core/Handlers/SceneLoadedHandler.cs b/Ragon.Core/Handlers/SceneLoadedHandler.cs new file mode 100644 index 0000000..bba8e10 --- /dev/null +++ b/Ragon.Core/Handlers/SceneLoadedHandler.cs @@ -0,0 +1,123 @@ +using NLog; +using Ragon.Common; +using Ragon.Core.Game; +using Ragon.Core.Lobby; + +namespace Ragon.Core.Handlers; + +public sealed class SceneLoadedHandler : IHandler +{ + private Logger _logger = LogManager.GetCurrentClassLogger(); + + public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer) + { + if (context.LobbyPlayer.Status == LobbyPlayerStatus.Unauthorized) + return; + + var owner = context.Room.Owner; + var player = context.RoomPlayer; + var room = context.Room; + + if (player == owner) + { + var statics = reader.ReadUShort(); + for (var staticIndex = 0; staticIndex < statics; staticIndex++) + { + var entityType = reader.ReadUShort(); + var eventAuthority = (RagonAuthority) reader.ReadByte(); + var staticId = reader.ReadUShort(); + var propertiesCount = reader.ReadUShort(); + + var entity = new Entity(player, entityType, staticId, eventAuthority); + for (var propertyIndex = 0; propertyIndex < propertiesCount; propertyIndex++) + { + var propertyType = reader.ReadBool(); + var propertySize = reader.ReadUShort(); + entity.State.AddProperty(new EntityStateProperty(propertySize, propertyType)); + } + + room.AttachEntity(player, entity); + } + + _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} loaded with {statics} scene entities"); + + room.WaitPlayersList.Add(player); + + foreach (var roomPlayer in room.WaitPlayersList) + { + DispatchPlayerJoinExcludePlayer(room, roomPlayer, writer); + + roomPlayer.SetReady(); + } + + room.UpdateReadyPlayerList(); + + DispatchSnapshot(room, room.WaitPlayersList, writer); + + room.WaitPlayersList.Clear(); + } + else if (owner.IsLoaded) + { + player.SetReady(); + + DispatchPlayerJoinExcludePlayer(room, player, writer); + + room.UpdateReadyPlayerList(); + + DispatchSnapshot(room, new List() { player }, writer); + + foreach (var entity in room.EntityList) + entity.RestoreBufferedEvents(player, writer); + } + else + { + _logger.Trace($"Player {player.Connection.Id}|{context.LobbyPlayer.Name} waiting owner of room"); + room.WaitPlayersList.Add(player); + } + } + + private void DispatchPlayerJoinExcludePlayer(Room room, RoomPlayer roomPlayer, RagonSerializer writer) + { + writer.Clear(); + writer.WriteOperation(RagonOperation.PLAYER_JOINED); + writer.WriteUShort(roomPlayer.Connection.Id); + writer.WriteString(roomPlayer.Id); + writer.WriteString(roomPlayer.Name); + + var sendData = writer.ToArray(); + foreach (var awaiter in room.ReadyPlayersList) + { + if (awaiter != roomPlayer) + awaiter.Connection.ReliableChannel.Send(sendData); + } + } + + private void DispatchSnapshot(Room room, List receviersList, RagonSerializer writer) + { + writer.Clear(); + writer.WriteOperation(RagonOperation.SNAPSHOT); + writer.WriteUShort((ushort) room.ReadyPlayersList.Count); + foreach (var roomPlayer in room.ReadyPlayersList) + { + writer.WriteUShort(roomPlayer.Connection.Id); + writer.WriteString(roomPlayer.Id); + writer.WriteString(roomPlayer.Name); + } + + var dynamicEntities = room.DynamicEntitiesList; + var dynamicEntitiesCount = (ushort) dynamicEntities.Count; + writer.WriteUShort(dynamicEntitiesCount); + foreach (var entity in dynamicEntities) + entity.State.Snapshot(writer); + + var staticEntities = room.StaticEntitiesList; + var staticEntitiesCount = (ushort) staticEntities.Count; + writer.WriteUShort(staticEntitiesCount); + foreach (var entity in staticEntities) + entity.State.Snapshot(writer); + + var sendData = writer.ToArray(); + foreach (var player in receviersList) + player.Connection.ReliableChannel.Send(sendData); + } +} \ No newline at end of file diff --git a/Ragon.Core/Lobby/Abstract/ILobby.cs b/Ragon.Core/Lobby/Abstract/ILobby.cs new file mode 100644 index 0000000..112b499 --- /dev/null +++ b/Ragon.Core/Lobby/Abstract/ILobby.cs @@ -0,0 +1,11 @@ +using Ragon.Core.Game; + +namespace Ragon.Core.Lobby; + +public interface ILobby +{ + public bool FindRoomById(string roomId, out Room room); + public bool FindRoomByMap(string map, out Room room); + public void Persist(Room room); + public void Remove(Room room); +} \ No newline at end of file diff --git a/Ragon.Core/Lobby/LobbyInMemory.cs b/Ragon.Core/Lobby/LobbyInMemory.cs new file mode 100644 index 0000000..676eabe --- /dev/null +++ b/Ragon.Core/Lobby/LobbyInMemory.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using NLog; +using Ragon.Core.Game; + +namespace Ragon.Core.Lobby; + +public class LobbyInMemory: ILobby +{ + private readonly List _rooms = new(); + private readonly Logger _logger = LogManager.GetCurrentClassLogger(); + + public bool FindRoomById(string roomId, out Room room) + { + foreach (var existRoom in _rooms) + { + var info = existRoom.Info; + if (existRoom.Id == roomId && info.Min < info.Max) + { + room = existRoom; + return true; + } + } + + room = null; + return false; + } + + public bool FindRoomByMap(string map, out Room room) + { + foreach (var existRoom in _rooms) + { + var info = existRoom.Info; + if (info.Map == map && existRoom.Players.Count < info.Max) + { + room = existRoom; + return true; + } + } + + room = null; + return false; + } + + public void Persist(Room room) + { + _rooms.Add(room); + + foreach (var r in _rooms) + _logger.Trace($"{r.Id} {r.Info}"); + } + + public void Remove(Room room) + { + _rooms.Remove(room); + } +} \ No newline at end of file diff --git a/Ragon.Core/Lobby/LobbyPlayer.cs b/Ragon.Core/Lobby/LobbyPlayer.cs new file mode 100644 index 0000000..8307f88 --- /dev/null +++ b/Ragon.Core/Lobby/LobbyPlayer.cs @@ -0,0 +1,27 @@ +using Ragon.Server; + +namespace Ragon.Core.Lobby; + +public enum LobbyPlayerStatus +{ + Unauthorized, + Authorized, +} + +public class LobbyPlayer +{ + public string Id { get; private set; } + public string Name { get; set; } + public byte[] AdditionalData { get; set; } + public LobbyPlayerStatus Status { get; set; } + public INetworkConnection Connection { get; private set; } + + public LobbyPlayer(INetworkConnection connection) + { + Id = Guid.NewGuid().ToString(); + Connection = connection; + Status = LobbyPlayerStatus.Unauthorized; + Name = "None"; + AdditionalData = Array.Empty(); + } +} \ No newline at end of file diff --git a/Ragon.Core/PlayerContext.cs b/Ragon.Core/PlayerContext.cs new file mode 100644 index 0000000..12393e4 --- /dev/null +++ b/Ragon.Core/PlayerContext.cs @@ -0,0 +1,28 @@ +using NLog; +using Ragon.Core.Game; +using Ragon.Core.Lobby; +using Ragon.Core.Time; +using Ragon.Server; + +namespace Ragon.Core; + +public class PlayerContext: IDisposable +{ + public INetworkConnection Connection { get; } + public Scheduler Scheduler; + public ILobby Lobby { get; set; } + public LobbyPlayer LobbyPlayer { private set; get; } + public Room? Room { get; set; } + public RoomPlayer? RoomPlayer { get; set; } + + public PlayerContext(INetworkConnection conn, LobbyPlayer player) + { + Connection = conn; + LobbyPlayer = player; + } + + public void Dispose() + { + RoomPlayer?.Room.RemovePlayer(RoomPlayer); + } +} \ No newline at end of file diff --git a/Ragon.Core/Ragon.Core.csproj b/Ragon.Core/Ragon.Core.csproj new file mode 100644 index 0000000..95b0d6c --- /dev/null +++ b/Ragon.Core/Ragon.Core.csproj @@ -0,0 +1,21 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + + + diff --git a/Ragon.Core/Time/IScheduleTask.cs b/Ragon.Core/Time/IScheduleTask.cs new file mode 100644 index 0000000..94e7e92 --- /dev/null +++ b/Ragon.Core/Time/IScheduleTask.cs @@ -0,0 +1,6 @@ +namespace Ragon.Core.Time; + +public interface IScheduleTask +{ + public void Tick(); +} \ No newline at end of file diff --git a/Ragon.Core/Time/Scheduler.cs b/Ragon.Core/Time/Scheduler.cs new file mode 100644 index 0000000..b787ea9 --- /dev/null +++ b/Ragon.Core/Time/Scheduler.cs @@ -0,0 +1,27 @@ +namespace Ragon.Core.Time; + +public class Scheduler +{ + private List _tasks; + + public Scheduler() + { + _tasks = new List(35); + } + + public void Add(IScheduleTask task) + { + _tasks.Add(task); + } + + public void Remove(IScheduleTask task) + { + _tasks.Remove(task); + } + + public void Tick() + { + foreach (var task in _tasks) + task.Tick(); + } +} \ No newline at end of file diff --git a/Ragon.Common/Ragon.Common.csproj b/Ragon.Protocol/Ragon.Protocol.csproj similarity index 93% rename from Ragon.Common/Ragon.Common.csproj rename to Ragon.Protocol/Ragon.Protocol.csproj index ee2974e..68be873 100644 --- a/Ragon.Common/Ragon.Common.csproj +++ b/Ragon.Protocol/Ragon.Protocol.csproj @@ -5,6 +5,7 @@ disable 8 netstandard2.1 + Ragon.Common diff --git a/Ragon.Common/Sources/RagonAuthority.cs b/Ragon.Protocol/Sources/RagonAuthority.cs similarity index 100% rename from Ragon.Common/Sources/RagonAuthority.cs rename to Ragon.Protocol/Sources/RagonAuthority.cs diff --git a/Ragon.Common/Sources/RagonOperation.cs b/Ragon.Protocol/Sources/RagonOperation.cs similarity index 100% rename from Ragon.Common/Sources/RagonOperation.cs rename to Ragon.Protocol/Sources/RagonOperation.cs diff --git a/Ragon.Common/Sources/RagonReplicationMode.cs b/Ragon.Protocol/Sources/RagonReplicationMode.cs similarity index 100% rename from Ragon.Common/Sources/RagonReplicationMode.cs rename to Ragon.Protocol/Sources/RagonReplicationMode.cs diff --git a/Ragon.Common/Sources/RagonRoomParameters.cs b/Ragon.Protocol/Sources/RagonRoomParameters.cs similarity index 100% rename from Ragon.Common/Sources/RagonRoomParameters.cs rename to Ragon.Protocol/Sources/RagonRoomParameters.cs diff --git a/Ragon.Common/Sources/RagonSerializable.cs b/Ragon.Protocol/Sources/RagonSerializable.cs similarity index 100% rename from Ragon.Common/Sources/RagonSerializable.cs rename to Ragon.Protocol/Sources/RagonSerializable.cs diff --git a/Ragon.Common/Sources/RagonSerializer.cs b/Ragon.Protocol/Sources/RagonSerializer.cs similarity index 100% rename from Ragon.Common/Sources/RagonSerializer.cs rename to Ragon.Protocol/Sources/RagonSerializer.cs diff --git a/Ragon.Common/Sources/RagonTarget.cs b/Ragon.Protocol/Sources/RagonTarget.cs similarity index 76% rename from Ragon.Common/Sources/RagonTarget.cs rename to Ragon.Protocol/Sources/RagonTarget.cs index ced8277..25f6678 100644 --- a/Ragon.Common/Sources/RagonTarget.cs +++ b/Ragon.Protocol/Sources/RagonTarget.cs @@ -4,6 +4,8 @@ namespace Ragon.Common { Owner, ExceptOwner, + ExceptInvoker, All, + Player, } } \ No newline at end of file diff --git a/Ragon.Protocol/Sources/RagonVersion.cs b/Ragon.Protocol/Sources/RagonVersion.cs new file mode 100644 index 0000000..563294e --- /dev/null +++ b/Ragon.Protocol/Sources/RagonVersion.cs @@ -0,0 +1,28 @@ +namespace Ragon.Common +{ + public static class RagonVersion + { + public static uint Parse(string version) + { + var strings = version.Split("."); + if (strings.Length < 3) + return 0; + + var parts = new uint[] {0, 0, 0}; + for (int i = 0; i < parts.Length; i++) + { + if (!uint.TryParse(strings[i], out var v)) + return 0; + + parts[i] = v; + } + + return (parts[0] << 16) | (parts[1] << 8) | parts[2]; + } + + public static string Parse(uint version) + { + return (version >> 16 & 0xFF) + "." + (version >> 8 & 0xFF) + "." + (version & 0xFF); + } + } +} \ No newline at end of file diff --git a/Ragon.SimpleServer/NLog.config b/Ragon.Relay/NLog.config similarity index 100% rename from Ragon.SimpleServer/NLog.config rename to Ragon.Relay/NLog.config diff --git a/Ragon.Relay/Program.cs b/Ragon.Relay/Program.cs new file mode 100755 index 0000000..b8cde5c --- /dev/null +++ b/Ragon.Relay/Program.cs @@ -0,0 +1,30 @@ +using System; +using System.Runtime.InteropServices; +using NLog; +using Ragon.Core; + +namespace Ragon.Relay +{ + [StructLayout(LayoutKind.Sequential)] + struct Serializer + { + [FieldOffset(0)] public Guid Uuid; + [FieldOffset(0)] public long Long0; + [FieldOffset(0)] public long Long1; + } + class Program + { + static void Main(string[] args) + { + var logger = LogManager.GetLogger("Ragon.Relay"); + logger.Info("Relay Application"); + var configuration = Configuration.Load("config.json"); + var relay = new Application(configuration); + relay.Start(); + logger.Info("Started"); + Console.ReadKey(); + relay.Stop(); + logger.Info("Stopped"); + } + } +} \ No newline at end of file diff --git a/Ragon.SimpleServer/Ragon.SimpleServer.csproj b/Ragon.Relay/Ragon.Relay.csproj similarity index 90% rename from Ragon.SimpleServer/Ragon.SimpleServer.csproj rename to Ragon.Relay/Ragon.Relay.csproj index eefcee0..3813ce0 100755 --- a/Ragon.SimpleServer/Ragon.SimpleServer.csproj +++ b/Ragon.Relay/Ragon.Relay.csproj @@ -20,7 +20,7 @@ - + diff --git a/Ragon.SimpleServer/config.json b/Ragon.Relay/config.json similarity index 52% rename from Ragon.SimpleServer/config.json rename to Ragon.Relay/config.json index 9bf1456..b348e42 100755 --- a/Ragon.SimpleServer/config.json +++ b/Ragon.Relay/config.json @@ -1,13 +1,12 @@ { "key": "defaultkey", - "socket": "udp", + "socket": "enet", "protocol": "1.0.0", - "statisticsInterval": 5, "sendRate": 50, "port": 4444, "skipTimeout": 60, "reconnectTimeout": 300, - "maxConnections": 4095, - "maxPlayersPerRoom": 20, - "maxRooms": 200 + "limitConnections": 4095, + "limitPlayersPerRoom": 20, + "limitRooms": 200 } \ No newline at end of file diff --git a/Ragon.Server.ENet/ENetConnection.cs b/Ragon.Server.ENet/ENetConnection.cs new file mode 100644 index 0000000..127394e --- /dev/null +++ b/Ragon.Server.ENet/ENetConnection.cs @@ -0,0 +1,17 @@ +using ENet; + +namespace Ragon.Server.ENet; + +public sealed class ENetConnection: INetworkConnection +{ + public ushort Id { get; } + public INetworkChannel ReliableChannel { get; private set; } + public INetworkChannel UnreliableChannel { get; private set; } + + public ENetConnection(Peer peer) + { + Id = (ushort) peer.ID; + ReliableChannel = new ENetReliableChannel(peer, 0); + UnreliableChannel = new ENetUnreliableChannel(peer, 1); + } +} \ No newline at end of file diff --git a/Ragon.Server.ENet/ENetReliableChannel.cs b/Ragon.Server.ENet/ENetReliableChannel.cs new file mode 100644 index 0000000..6cb6571 --- /dev/null +++ b/Ragon.Server.ENet/ENetReliableChannel.cs @@ -0,0 +1,23 @@ +using ENet; + +namespace Ragon.Server.ENet; + +public sealed class ENetReliableChannel: INetworkChannel +{ + private Peer _peer; + private byte _channelId; + + public ENetReliableChannel(Peer peer, int channelId) + { + _peer = peer; + _channelId = (byte) channelId; + } + + public void Send(byte[] data) + { + var newPacket = new Packet(); + newPacket.Create(data, data.Length, PacketFlags.Reliable); + + _peer.Send(_channelId, ref newPacket); + } +} \ No newline at end of file diff --git a/Ragon.Server.ENet/ENetServer.cs b/Ragon.Server.ENet/ENetServer.cs new file mode 100755 index 0000000..9df5144 --- /dev/null +++ b/Ragon.Server.ENet/ENetServer.cs @@ -0,0 +1,115 @@ +using System.Diagnostics; +using ENet; +using NLog; +using Ragon.Common; + + +namespace Ragon.Server.ENet +{ + public sealed class ENetServer: INetworkServer + { + public ENetConnection[] Connections; + private ILogger _logger = LogManager.GetCurrentClassLogger(); + private INetworkListener _listener; + private uint _protocol; + private Host _host; + private Event _event; + private NetworkConfiguration _configuration; + + public ENetServer() + { + _host = new Host(); + } + + public void Start(INetworkListener listener, NetworkConfiguration configuration) + { + Library.Initialize(); + + _listener = listener; + _protocol = configuration.Protocol; + + Connections = new ENetConnection[configuration.LimitConnections]; + + var address = new Address { Port = (ushort) configuration.Port }; + _host.Create(address, Connections.Length, 2, 0, 0, 1024 * 1024); + + var protocolDecoded = RagonVersion.Parse(_protocol); + _logger.Info($"Network listening on {configuration.Port}"); + _logger.Info($"Protocol: {protocolDecoded}"); + } + + public void Poll() + { + bool polled = false; + while (!polled) + { + if (_host.CheckEvents(out _event) <= 0) + { + if (_host.Service(0, out _event) <= 0) + break; + + polled = true; + } + + switch (_event.Type) + { + case EventType.None: + { + _logger.Trace("None event"); + break; + } + case EventType.Connect: + { + if (IsValidProtocol(_event.Data)) + { + _logger.Warn("Mismatched protocol, close connection"); + break; + } + + var connection = new ENetConnection(_event.Peer); + Connections[_event.Peer.ID] = connection; + + _listener.OnConnected(connection); + break; + } + case EventType.Disconnect: + { + var connection = Connections[_event.Peer.ID]; + _listener.OnDisconnected(connection); + break; + } + case EventType.Timeout: + { + var connection = Connections[_event.Peer.ID]; + _listener.OnTimeout(connection); + break; + } + case EventType.Receive: + { + var peerId = (ushort) _event.Peer.ID; + var connection = Connections[peerId]; + var dataRaw = new byte[_event.Packet.Length]; + + _event.Packet.CopyTo(dataRaw); + _event.Packet.Dispose(); + + _listener.OnData(connection, dataRaw); + break; + } + } + } + } + + public void Stop() + { + _host?.Dispose(); + + Library.Deinitialize(); + } + + private bool IsValidProtocol(uint protocol) + { + return protocol == _configuration.Protocol; + } + } +} \ No newline at end of file diff --git a/Ragon.Server.ENet/ENetUnreliableChannel.cs b/Ragon.Server.ENet/ENetUnreliableChannel.cs new file mode 100644 index 0000000..a147bee --- /dev/null +++ b/Ragon.Server.ENet/ENetUnreliableChannel.cs @@ -0,0 +1,23 @@ +using ENet; + +namespace Ragon.Server.ENet; + +public sealed class ENetUnreliableChannel: INetworkChannel +{ + private Peer _peer; + private byte _channelId; + + public ENetUnreliableChannel(Peer peer, int channelId) + { + _peer = peer; + _channelId = (byte) channelId; + } + + public void Send(byte[] data) + { + var newPacket = new Packet(); + newPacket.Create(data, data.Length, PacketFlags.None); + + _peer.Send(_channelId, ref newPacket); + } +} \ No newline at end of file diff --git a/Ragon.Stress/Ragon.Stress.csproj b/Ragon.Server.ENet/Ragon.Server.ENet.csproj similarity index 67% rename from Ragon.Stress/Ragon.Stress.csproj rename to Ragon.Server.ENet/Ragon.Server.ENet.csproj index b990a0f..fdbb1e7 100644 --- a/Ragon.Stress/Ragon.Stress.csproj +++ b/Ragon.Server.ENet/Ragon.Server.ENet.csproj @@ -1,19 +1,19 @@ - Exe net6.0 enable enable - StressTest + Ragon.ENet - + + - + diff --git a/Ragon.Server.WebSockets/Ragon.Server.WebSockets.csproj b/Ragon.Server.WebSockets/Ragon.Server.WebSockets.csproj new file mode 100644 index 0000000..13c6238 --- /dev/null +++ b/Ragon.Server.WebSockets/Ragon.Server.WebSockets.csproj @@ -0,0 +1,10 @@ + + + + net6.0 + enable + enable + Ragon.WebSockets + + + diff --git a/Ragon.Server/INetworkChannel.cs b/Ragon.Server/INetworkChannel.cs new file mode 100644 index 0000000..803ce66 --- /dev/null +++ b/Ragon.Server/INetworkChannel.cs @@ -0,0 +1,6 @@ +namespace Ragon.Server; + +public interface INetworkChannel +{ + void Send(byte[] data); +} \ No newline at end of file diff --git a/Ragon.Server/INetworkConnection.cs b/Ragon.Server/INetworkConnection.cs new file mode 100644 index 0000000..bb1ecf0 --- /dev/null +++ b/Ragon.Server/INetworkConnection.cs @@ -0,0 +1,8 @@ +namespace Ragon.Server; + +public interface INetworkConnection +{ + public ushort Id { get; } + public INetworkChannel ReliableChannel { get; } + public INetworkChannel UnreliableChannel { get; } +} \ No newline at end of file diff --git a/Ragon.Server/INetworkListener.cs b/Ragon.Server/INetworkListener.cs new file mode 100644 index 0000000..f3102f8 --- /dev/null +++ b/Ragon.Server/INetworkListener.cs @@ -0,0 +1,9 @@ +namespace Ragon.Server; + +public interface INetworkListener +{ + void OnConnected(INetworkConnection connection); + void OnDisconnected(INetworkConnection connection); + void OnTimeout(INetworkConnection connection); + void OnData(INetworkConnection connection, byte[] data); +} \ No newline at end of file diff --git a/Ragon.Server/INetworkServer.cs b/Ragon.Server/INetworkServer.cs new file mode 100644 index 0000000..ca6a061 --- /dev/null +++ b/Ragon.Server/INetworkServer.cs @@ -0,0 +1,8 @@ +namespace Ragon.Server; + +public interface INetworkServer +{ + public void Stop(); + public void Poll(); + public void Start(INetworkListener listener, NetworkConfiguration configuration); +} \ No newline at end of file diff --git a/Ragon.Server/NetworkConfiguration.cs b/Ragon.Server/NetworkConfiguration.cs new file mode 100644 index 0000000..15adb9c --- /dev/null +++ b/Ragon.Server/NetworkConfiguration.cs @@ -0,0 +1,9 @@ +namespace Ragon.Server; + +public struct NetworkConfiguration +{ + public int LimitConnections { get; set; } + public int Port { get; set; } + public uint Protocol { get; set; } + public string Address { get; set; } +} \ No newline at end of file diff --git a/Ragon.Server/Ragon.Server.csproj b/Ragon.Server/Ragon.Server.csproj new file mode 100644 index 0000000..95c97c8 --- /dev/null +++ b/Ragon.Server/Ragon.Server.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/Ragon.SimpleServer/Program.cs b/Ragon.SimpleServer/Program.cs deleted file mode 100755 index 5c5d2a6..0000000 --- a/Ragon.SimpleServer/Program.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Threading; -using Game.Source; -using Ragon.Core; - -namespace SimpleServer -{ - class Program - { - static void Main(string[] args) - { - var bootstrap = new Bootstrap(); - var app = bootstrap.Configure(new SimplePluginFactory()); - app.Start(); - Console.Read(); - app.Stop(); - } - } -} \ No newline at end of file diff --git a/Ragon.SimpleServer/Source/AuthorizationProvider.cs b/Ragon.SimpleServer/Source/AuthorizationProvider.cs deleted file mode 100644 index 6d6cfe6..0000000 --- a/Ragon.SimpleServer/Source/AuthorizationProvider.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Threading.Tasks; -using Ragon.Core; - -namespace Game.Source; - -public class ApplicationHandlerByKey: IApplicationHandler -{ - private Configuration _configuration; - public ApplicationHandlerByKey(Configuration configuration) - { - _configuration = configuration; - } - - public async Task OnAuthorizationRequest(string key, string name, byte[] additionalData, Action accept, Action reject) - { - if (key == _configuration.Key) - { - var playerId = Guid.NewGuid().ToString(); - var playerName = name; - - accept(playerId, playerName); - } - else - { - 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/Plugins/EmptyPlugin.cs b/Ragon.SimpleServer/Source/Plugins/EmptyPlugin.cs deleted file mode 100755 index 681d602..0000000 --- a/Ragon.SimpleServer/Source/Plugins/EmptyPlugin.cs +++ /dev/null @@ -1,41 +0,0 @@ -using NLog.Fluent; -using Ragon.Core; - -namespace Game.Source -{ - public class SimplePlugin: PluginBase - { - - public override void OnStart() - { - // _logger.Info("Plugin started"); - } - - public override void OnStop() - { - // _logger.Info("Plugin stopped"); - } - - public override void OnPlayerJoined(Player player) - { - // Logger.Info($"Player({player.PlayerName}) joined to Room({Room.Id})"); - } - - public override void OnPlayerLeaved(Player player) - { - // Logger.Info($"Player({player.PlayerName}) left from Room({Room.Id})"); - } - - public override bool OnEntityCreated(Player player, Entity entity) - { - return false; - // Logger.Info($"Player({player.PlayerName}) create entity {entity.EntityId}:{entity.EntityType}"); - } - - public override bool OnEntityDestroyed(Player player, Entity entity) - { - return false; - // Logger.Info($"Player({player.PlayerName}) destroy entity {entity.EntityId}:{entity.EntityType}"); - } - } -} \ No newline at end of file diff --git a/Ragon.SimpleServer/Source/SimplePluginFactory.cs b/Ragon.SimpleServer/Source/SimplePluginFactory.cs deleted file mode 100644 index 39bcbe3..0000000 --- a/Ragon.SimpleServer/Source/SimplePluginFactory.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Runtime.InteropServices; -using Ragon.Core; - -namespace Game.Source -{ - public class SimplePluginFactory : PluginFactory - { - public PluginBase CreatePlugin(string map) - { - return new SimplePlugin(); - } - - public IApplicationHandler CreateAuthorizationProvider(Configuration configuration) - { - return new ApplicationHandlerByKey(configuration); - } - } -} \ No newline at end of file diff --git a/Ragon.Stress/Program.cs b/Ragon.Stress/Program.cs deleted file mode 100644 index 9bd7f61..0000000 --- a/Ragon.Stress/Program.cs +++ /dev/null @@ -1,206 +0,0 @@ -using System; -using ENet; -using Ragon.Common; - -namespace Stress -{ - class SimulationClient - { - public Host Host; - public Peer Peer; - public bool InRoom; - public List Entities = new List(); - } - - class SimulationThread - { - private List _clients = new List(); - - public void Start(string url, ushort port, int numClients) - { - for (var i = 0; i < numClients; i++) - { - var client = CreateClient(url, port); - _clients.Add(client); - } - - var thread = new Thread(Execute); - thread.IsBackground = true; - thread.Start(); - } - - public void Execute() - { - var ragonSerializer = new RagonSerializer(); - - while (true) - { - foreach (SimulationClient simulationClient in _clients) - { - bool polled = false; - Event netEvent; - - while (!polled) - { - if (simulationClient.Host.CheckEvents(out netEvent) <= 0) - { - if (simulationClient.Host.Service(0, out netEvent) <= 0) - break; - - polled = true; - } - - switch (netEvent.Type) - { - case EventType.None: - break; - case EventType.Connect: - { - ragonSerializer.Clear(); - ragonSerializer.WriteOperation(RagonOperation.AUTHORIZE); - ragonSerializer.WriteString("defaultkey"); - ragonSerializer.WriteString("Player " + DateTime.Now.Ticks); - ragonSerializer.WriteByte(0); - - var sendData = ragonSerializer.ToArray(); - var packet = new Packet(); - packet.Create(sendData, PacketFlags.Reliable); - simulationClient.Peer.Send(0, ref packet); - Console.WriteLine("Client connected to server"); - break; - } - case EventType.Disconnect: - Console.WriteLine("Client disconnected from server"); - break; - case EventType.Timeout: - Console.WriteLine("Client connection timeout"); - break; - case EventType.Receive: - var data = new byte[netEvent.Packet.Length]; - netEvent.Packet.CopyTo(data); - - var op = (RagonOperation) data[0]; - switch (op) - { - case RagonOperation.AUTHORIZED_SUCCESS: - { - ragonSerializer.Clear(); - ragonSerializer.WriteOperation(RagonOperation.JOIN_OR_CREATE_ROOM); - ragonSerializer.WriteString("map"); - ragonSerializer.WriteInt(1); - ragonSerializer.WriteInt(5); - - var sendData = ragonSerializer.ToArray(); - var packet = new Packet(); - packet.Create(sendData, PacketFlags.Reliable); - simulationClient.Peer.Send(0, ref packet); - break; - } - case RagonOperation.JOIN_SUCCESS: - { - simulationClient.InRoom = true; - - ragonSerializer.Clear(); - ragonSerializer.WriteOperation(RagonOperation.SCENE_LOADED); - - var sendData = ragonSerializer.ToArray(); - var packet = new Packet(); - packet.Create(sendData, PacketFlags.Reliable); - simulationClient.Peer.Send(0, ref packet); - - break; - } - case RagonOperation.SNAPSHOT: - { - ragonSerializer.Clear(); - ragonSerializer.WriteOperation(RagonOperation.CREATE_ENTITY); - ragonSerializer.WriteUShort(0); - ragonSerializer.WriteUShort(0); - ragonSerializer.WriteUShort(0); - - var sendData = ragonSerializer.ToArray(); - var packet = new Packet(); - packet.Create(sendData, PacketFlags.Reliable); - simulationClient.Peer.Send(0, ref packet); - break; - } - case RagonOperation.CREATE_ENTITY: - { - ReadOnlySpan payload = data.AsSpan().Slice(1, data.Length - 1); - ragonSerializer.Clear(); - ragonSerializer.FromSpan(ref payload); - - var entityType = ragonSerializer.ReadUShort(); - var state = ragonSerializer.ReadByte(); - var ennt = ragonSerializer.ReadByte(); - var entityId = ragonSerializer.ReadInt(); - - simulationClient.Entities.Add(entityId); - break; - } - } - // Console.WriteLine(op); - // Console.WriteLine("Packet received from server - Channel ID: " + netEvent.ChannelID + ", Data length: " + netEvent.Packet.Length); - netEvent.Packet.Dispose(); - break; - } - } - - if (simulationClient.InRoom) - { - foreach (var entity in simulationClient.Entities) - { - ragonSerializer.Clear(); - ragonSerializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_STATE); - ragonSerializer.WriteInt(entity); - ragonSerializer.WriteInt(100); - ragonSerializer.WriteInt(200); - ragonSerializer.WriteInt(300); - - var sendData = ragonSerializer.ToArray(); - var packet = new Packet(); - packet.Create(sendData, PacketFlags.Instant); - simulationClient.Peer.Send(1, ref packet); - } - } - } - - Thread.Sleep(33); - } - } - - SimulationClient CreateClient(string url, ushort port) - { - Host client = new Host(); - Address address = new Address(); - - address.SetHost(url); - address.Port = port; - - client.Create(); - Console.WriteLine("Created client"); - - Peer peer = client.Connect(address); - - return new SimulationClient() {Host = client, Peer = peer}; - } - } - - class Program - { - static void Main(string[] args) - { - Library.Initialize(); - - for (var i = 0; i < 80; i ++) - { - var thread = new SimulationThread(); - thread.Start("49.12.70.233", 4444, 50); - Thread.Sleep(300); - } - - Console.ReadKey(); - Library.Deinitialize(); - } - } -} \ No newline at end of file diff --git a/Ragon.sln b/Ragon.sln index c5909d8..429c64c 100755 --- a/Ragon.sln +++ b/Ragon.sln @@ -1,12 +1,16 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon", "Ragon\Ragon.csproj", "{BABA1AF0-CF91-43F2-9577-53800068ACCF}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Relay", "Ragon.Relay\Ragon.Relay.csproj", "{C2B87ADE-A122-4366-8EB7-24DAE11EBAF0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.SimpleServer", "Ragon.SimpleServer\Ragon.SimpleServer.csproj", "{C2B87ADE-A122-4366-8EB7-24DAE11EBAF0}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Protocol", "Ragon.Protocol\Ragon.Protocol.csproj", "{F478B2A2-36F4-43B9-9BB7-382A57C449B2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Common", "Ragon.Common\Ragon.Common.csproj", "{F478B2A2-36F4-43B9-9BB7-382A57C449B2}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Server", "Ragon.Server\Ragon.Server.csproj", "{45D3B686-8960-4656-91B2-6F8BFCCAA225}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Stress", "Ragon.Stress\Ragon.Stress.csproj", "{45E4C6A4-6AB5-4BEA-82DD-1F75C1648EC4}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Core", "Ragon.Core\Ragon.Core.csproj", "{F4AA86B9-2486-4B53-BA77-43D958A2FDC3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Server.WebSockets", "Ragon.Server.WebSockets\Ragon.Server.WebSockets.csproj", "{81050343-A9B8-487B-86C8-7A5B7DD9C39B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Server.ENet", "Ragon.Server.ENet\Ragon.Server.ENet.csproj", "{DD79AC4F-9E5C-4938-850E-805D537E68D0}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -14,10 +18,6 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {BABA1AF0-CF91-43F2-9577-53800068ACCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BABA1AF0-CF91-43F2-9577-53800068ACCF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BABA1AF0-CF91-43F2-9577-53800068ACCF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BABA1AF0-CF91-43F2-9577-53800068ACCF}.Release|Any CPU.Build.0 = Release|Any CPU {C2B87ADE-A122-4366-8EB7-24DAE11EBAF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C2B87ADE-A122-4366-8EB7-24DAE11EBAF0}.Debug|Any CPU.Build.0 = Debug|Any CPU {C2B87ADE-A122-4366-8EB7-24DAE11EBAF0}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -26,9 +26,21 @@ Global {F478B2A2-36F4-43B9-9BB7-382A57C449B2}.Debug|Any CPU.Build.0 = Debug|Any CPU {F478B2A2-36F4-43B9-9BB7-382A57C449B2}.Release|Any CPU.ActiveCfg = Release|Any CPU {F478B2A2-36F4-43B9-9BB7-382A57C449B2}.Release|Any CPU.Build.0 = Release|Any CPU - {45E4C6A4-6AB5-4BEA-82DD-1F75C1648EC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {45E4C6A4-6AB5-4BEA-82DD-1F75C1648EC4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {45E4C6A4-6AB5-4BEA-82DD-1F75C1648EC4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {45E4C6A4-6AB5-4BEA-82DD-1F75C1648EC4}.Release|Any CPU.Build.0 = Release|Any CPU + {45D3B686-8960-4656-91B2-6F8BFCCAA225}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {45D3B686-8960-4656-91B2-6F8BFCCAA225}.Debug|Any CPU.Build.0 = Debug|Any CPU + {45D3B686-8960-4656-91B2-6F8BFCCAA225}.Release|Any CPU.ActiveCfg = Release|Any CPU + {45D3B686-8960-4656-91B2-6F8BFCCAA225}.Release|Any CPU.Build.0 = Release|Any CPU + {F4AA86B9-2486-4B53-BA77-43D958A2FDC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F4AA86B9-2486-4B53-BA77-43D958A2FDC3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F4AA86B9-2486-4B53-BA77-43D958A2FDC3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F4AA86B9-2486-4B53-BA77-43D958A2FDC3}.Release|Any CPU.Build.0 = Release|Any CPU + {81050343-A9B8-487B-86C8-7A5B7DD9C39B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {81050343-A9B8-487B-86C8-7A5B7DD9C39B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81050343-A9B8-487B-86C8-7A5B7DD9C39B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {81050343-A9B8-487B-86C8-7A5B7DD9C39B}.Release|Any CPU.Build.0 = Release|Any CPU + {DD79AC4F-9E5C-4938-850E-805D537E68D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DD79AC4F-9E5C-4938-850E-805D537E68D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DD79AC4F-9E5C-4938-850E-805D537E68D0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DD79AC4F-9E5C-4938-850E-805D537E68D0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Ragon/Ragon.csproj b/Ragon/Ragon.csproj deleted file mode 100755 index af4de57..0000000 --- a/Ragon/Ragon.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - 10 - enable - net6.0 - - - - true - TRACE;NETSTACK_SPAN - - - - true - TRACE;NETSTACK_SPAN - - - - - - - - - - - - - diff --git a/Ragon/Sources/Application.cs b/Ragon/Sources/Application.cs deleted file mode 100755 index 68233c8..0000000 --- a/Ragon/Sources/Application.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System; -using System.Threading; -using Ragon.Common; -using NLog; - -namespace Ragon.Core -{ - public class Application : IEventHandler - { - private readonly RoomManager _roomManager; - private readonly Thread _thread; - private readonly Lobby _lobby; - private readonly ISocketServer _socketServer; - 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 Dispatcher Dispatcher => _dispatcher; - - public Application(PluginFactory factory, Configuration configuration) - { - var authorizationProvider = factory.CreateAuthorizationProvider(configuration); - - _configuration = configuration; - _serializer = new RagonSerializer(); - - var dispatcher = new Dispatcher(); - _dispatcher = dispatcher; - - if (configuration.Socket == "udp") - { - _socketServer = new ENetServer(this); - } - else if (configuration.Socket == "websocket") - { - _socketServer = new WebSocketServer(this); - } - else - { - _logger.Error($"Unknown socket type {configuration.Socket}"); - } - - _deltaTime = 1000.0f / configuration.SendRate; - - _roomManager = new RoomManager(factory, this); - _lobby = new Lobby(authorizationProvider, _roomManager, this); - - _thread = new Thread(Execute); - _thread.Name = "Game Thread"; - _thread.IsBackground = true; - } - - public void Start() - { - var strings = _configuration.Protocol.Split("."); - if (strings.Length < 3) - { - _logger.Error("Wrong protocol passed to connect method"); - return; - } - - var parts = new uint[] {0, 0, 0}; - for (int i = 0; i < parts.Length; i++) - { - if (!uint.TryParse(strings[i], out var v)) - { - _logger.Error("Wrong protocol"); - return; - } - - parts[i] = v; - } - - uint encoded = (parts[0] << 16) | (parts[1] << 8) | parts[2]; - _socketServer.Start(_configuration.Port, _configuration.MaxConnections, encoded); - _thread.Start(); - } - - public void Stop() - { - _socketServer.Stop(); - _thread.Interrupt(); - } - - private void Execute() - { - while (true) - { - _socketServer.Process(); - _dispatcher.Process(); - _roomManager.Tick(_deltaTime); - // - Thread.Sleep((int) _deltaTime); - } - } - - public void OnConnected(ushort peerId) - { - _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 - { - _serializer.Clear(); - _serializer.FromArray(data); - - 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); - } - } - - 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/Authorization.cs b/Ragon/Sources/Authorization.cs deleted file mode 100644 index 88c1d2f..0000000 --- a/Ragon/Sources/Authorization.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using System.Collections.Generic; -using NLog; -using Ragon.Common; - -namespace Ragon.Core; - -public class AuthorizationManager -{ - private Logger _logger = LogManager.GetCurrentClassLogger(); - private IApplicationHandler _provider; - private Application _gameThread; - private Lobby _lobby; - private RagonSerializer _serializer; - private readonly Dictionary _playersByPeers; - private readonly Dictionary _playersByIds; - - public AuthorizationManager(IApplicationHandler provider, Application gameThread, Lobby lobby, RagonSerializer serializer) - { - _serializer = serializer; - _lobby = lobby; - _provider = provider; - _gameThread = gameThread; - _playersByIds = new Dictionary(); - _playersByPeers = new Dictionary(); - } - - public void OnAuthorization(ushort peerId, string key, string name, ReadOnlySpan additionalData) - { - if (_playersByPeers.ContainsKey(peerId)) - { - _logger.Warn($"Connection already authorized {peerId}"); - return; - } - - var dispatcher = _gameThread.Dispatcher; - - _provider.OnAuthorizationRequest(key, name, additionalData.ToArray(), - (playerId, playerName) => { dispatcher.Dispatch(() => Accepted(peerId, playerId, playerName)); }, - (errorCode) => { dispatcher.Dispatch(() => Rejected(peerId, errorCode)); }); - } - - public void Accepted(ushort peerId, string playerId, string playerName) - { - _serializer.Clear(); - _serializer.WriteOperation(RagonOperation.AUTHORIZED_SUCCESS); - _serializer.WriteString(playerId); - _serializer.WriteString(playerName); - - var player = new Player() - { - Id = playerId, - PlayerName = playerName, - PeerId = peerId, - IsLoaded = false, - Entities = new List(), - EntitiesIds = new List(), - }; - - _playersByIds.Add(playerId, player); - _playersByPeers.Add(peerId, player); - - var sendData = _serializer.ToArray(); - _gameThread.SocketServer.Send(peerId, sendData, DeliveryType.Reliable); - } - - public void Rejected(ushort peerId, uint code) - { - _serializer.Clear(); - _serializer.WriteOperation(RagonOperation.AUTHORIZED_FAILED); - _serializer.WriteInt((int) code); - - var sendData = _serializer.ToArray(); - _gameThread.SocketServer.Send(peerId, sendData, DeliveryType.Reliable); - _gameThread.SocketServer.Disconnect(peerId, 0); - } - - public void Cleanup(ushort peerId) - { - if (_playersByPeers.Remove(peerId, out var player)) - _playersByIds.Remove(player.Id); - } - - public Player? GetPlayer(ushort peerId) - { - if (_playersByPeers.TryGetValue(peerId, out var player)) - return player; - - return null; - } - - public Player GetPlayer(string playerId) - { - return _playersByIds[playerId]; - } -} \ No newline at end of file diff --git a/Ragon/Sources/Bootstrap.cs b/Ragon/Sources/Bootstrap.cs deleted file mode 100755 index 9c69cb2..0000000 --- a/Ragon/Sources/Bootstrap.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using NLog; - -namespace Ragon.Core -{ - public class Bootstrap - { - private ILogger _logger = LogManager.GetCurrentClassLogger(); - - public Application Configure(PluginFactory factory) - { - _logger.Info("Configure application..."); - var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config.json"); - var configuration = Configuration.Load(filePath); - var app = new Application(factory, configuration); - return app; - } - - } -} \ No newline at end of file diff --git a/Ragon/Sources/Configuration.cs b/Ragon/Sources/Configuration.cs deleted file mode 100755 index a14b85d..0000000 --- a/Ragon/Sources/Configuration.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.IO; -using Newtonsoft.Json; -using NLog; - -namespace Ragon.Core -{ - [Serializable] - public struct Configuration - { - public string Key; - public string Protocol; - public string Socket; - public int StatisticsInterval; - public ushort SendRate; - public ushort Port; - public int SkipTimeout; - public int ReconnectTimeout; - public int MaxConnections; - public int MaxPlayersPerRoom; - public int MaxRooms; - - private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); - private static readonly string _serverVersion = "1.0.23-rc"; - - private static void CopyrightInfo() - { - _logger.Info($"Server Version: {_serverVersion}"); - _logger.Info($"Machine Name: {Environment.MachineName}"); - _logger.Info($"OS: {Environment.OSVersion}"); - _logger.Info($"Processors: {Environment.ProcessorCount}"); - _logger.Info($"Runtime Version: {Environment.Version}"); - _logger.Info("=================================="); - _logger.Info("| |"); - _logger.Info("| Ragon |"); - _logger.Info("| |"); - _logger.Info("=================================="); - } - - public static Configuration Load(string filePath) - { - CopyrightInfo(); - - var data = File.ReadAllText(filePath); - var configuration = JsonConvert.DeserializeObject(data); - return configuration; - } - } -} \ No newline at end of file diff --git a/Ragon/Sources/Dispatcher.cs b/Ragon/Sources/Dispatcher.cs deleted file mode 100644 index 33b1b71..0000000 --- a/Ragon/Sources/Dispatcher.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Ragon.Core; - -public class Dispatcher -{ - public Queue _actions = new Queue(); - - public void Dispatch(Action action) - { - lock (_actions) - _actions.Enqueue(action); - } - - public void Process() - { - lock(_actions) - while(_actions.TryDequeue(out var action)) - action?.Invoke(); - } -} \ No newline at end of file diff --git a/Ragon/Sources/Entity/Entity.cs b/Ragon/Sources/Entity/Entity.cs deleted file mode 100644 index dfaf777..0000000 --- a/Ragon/Sources/Entity/Entity.cs +++ /dev/null @@ -1,249 +0,0 @@ -using System; -using System.Collections.Generic; -using NLog; -using Ragon.Common; - -namespace Ragon.Core; - -public class Entity -{ - private ILogger _logger = LogManager.GetCurrentClassLogger(); - private GameRoom _room; - - private static ushort _idGenerator = 0; - public ushort EntityId { get; private set; } - public ushort StaticId { get; private set; } - public ushort EntityType { get; private set; } - public ushort OwnerId { get; private set; } - public byte[] Payload { get; private set; } - public RagonAuthority Authority { get; private set; } - - private List _properties; - private List _bufferedEvents; - - public Entity(GameRoom room, ushort ownerId, ushort entityType, ushort staticId, RagonAuthority eventAuthority) - { - OwnerId = ownerId; - StaticId = staticId; - EntityType = entityType; - EntityId = _idGenerator++; - Payload = Array.Empty(); - Authority = eventAuthority; - - _room = room; - _properties = new List(); - _bufferedEvents = new List(); - } - - public void SetPayload(byte[] payload) - { - Payload = payload; - } - - public void SetOwner(ushort ownerId) - { - OwnerId = ownerId; - } - - public void AddProperty(EntityProperty property) - { - _properties.Add(property); - } - - public void ReplicateEvent(ushort peerId, ushort eventId, ReadOnlySpan payload, RagonReplicationMode eventMode, RagonTarget targetMode) - { - if (Authority == RagonAuthority.OwnerOnly && OwnerId != peerId) - { - _logger.Warn($"Player have not enought authority for event with Id {eventId}"); - return; - } - - if (eventMode == RagonReplicationMode.Buffered && targetMode != RagonTarget.Owner) - { - var bufferedEvent = new EntityEvent() - { - EventData = payload.ToArray(), - Target = targetMode, - EventId = eventId, - PeerId = peerId, - }; - _bufferedEvents.Add(bufferedEvent); - } - - var serializer = _room.GetSharedSerializer(); - - serializer.Clear(); - serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT); - serializer.WriteUShort(eventId); - serializer.WriteUShort(peerId); - serializer.WriteByte((byte) eventMode); - serializer.WriteUShort(EntityId); - serializer.WriteData(ref payload); - - var sendData = serializer.ToArray(); - Send(targetMode, sendData); - } - - public void ReadState(uint peerId, RagonSerializer serializer) - { - if (OwnerId != peerId) - { - _logger.Warn($"Not owner can't change properties of object {EntityId}"); - return; - } - - for (var i = 0; i < _properties.Count; i++) - { - if (serializer.ReadBool()) - { - var property = _properties[i]; - var size = property.Size; - if (!property.IsFixed) - size = serializer.ReadUShort(); - - if (size > property.Capacity) - { - _logger.Warn($"Property {i} payload too large, size: {size}"); - continue; - } - - var propertyPayload = serializer.ReadData(size); - property.Write(ref propertyPayload); - property.Size = size; - } - } - } - - public void WriteProperties(RagonSerializer serializer) - { - serializer.WriteUShort(EntityId); - - for (int propertyIndex = 0; propertyIndex < _properties.Count; propertyIndex++) - { - var property = _properties[propertyIndex]; - if (property.IsDirty) - { - serializer.WriteBool(true); - var span = serializer.GetWritableData(property.Size); - var data = property.Read(); - data.CopyTo(span); - property.Clear(); - } - else - { - serializer.WriteBool(false); - } - } - } - - public void WriteSnapshot(RagonSerializer serializer) - { - for (int propertyIndex = 0; propertyIndex < _properties.Count; propertyIndex++) - { - var property = _properties[propertyIndex]; - var hasPayload = property.IsFixed || property.Size > 0 && !property.IsFixed; - if (hasPayload) - { - serializer.WriteBool(true); - var span = serializer.GetWritableData(property.Size); - var data = property.Read(); - data.CopyTo(span); - } - else - { - serializer.WriteBool(false); - } - } - } - - public void RestoreBufferedEvents(ushort peerId) - { - var serializer = _room.GetSharedSerializer(); - foreach (var bufferedEvent in _bufferedEvents) - { - serializer.Clear(); - serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT); - serializer.WriteUShort(bufferedEvent.EventId); - serializer.WriteUShort(bufferedEvent.PeerId); - serializer.WriteByte((byte) RagonReplicationMode.Server); - serializer.WriteUShort(EntityId); - - ReadOnlySpan data = bufferedEvent.EventData.AsSpan(); - serializer.WriteData(ref data); - - var sendData = serializer.ToArray(); - _room.Send(peerId, sendData, DeliveryType.Reliable); - } - } - - public void Create() - { - var serializer = _room.GetSharedSerializer(); - - serializer.Clear(); - serializer.WriteOperation(RagonOperation.CREATE_ENTITY); - serializer.WriteUShort(EntityType); - serializer.WriteUShort(EntityId); - serializer.WriteUShort(OwnerId); - - ReadOnlySpan entityPayload = Payload.AsSpan(); - serializer.WriteUShort((ushort) entityPayload.Length); - serializer.WriteData(ref entityPayload); - - var sendData = serializer.ToArray(); - _room.BroadcastToReady(sendData, DeliveryType.Reliable); - } - - public void Destroy(ReadOnlySpan payload) - { - var serializer = _room.GetSharedSerializer(); - - serializer.Clear(); - serializer.WriteOperation(RagonOperation.DESTROY_ENTITY); - serializer.WriteInt(EntityId); - serializer.WriteUShort((ushort) payload.Length); - serializer.WriteData(ref payload); - - var sendData = serializer.ToArray(); - _room.BroadcastToReady(sendData, DeliveryType.Reliable); - } - - void Send(RagonTarget targetMode, byte[] sendData) - { - switch (targetMode) - { - case RagonTarget.Owner: - { - _room.Send(OwnerId, sendData, DeliveryType.Reliable); - break; - } - case RagonTarget.ExceptOwner: - { - _room.BroadcastToReady(sendData, new [] { OwnerId }, DeliveryType.Reliable); - break; - } - case RagonTarget.All: - { - _room.BroadcastToReady(sendData, DeliveryType.Reliable); - break; - } - } - } - - 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[payloadData.Length]; - 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/GameRoom.cs b/Ragon/Sources/GameRoom.cs deleted file mode 100755 index 1691d92..0000000 --- a/Ragon/Sources/GameRoom.cs +++ /dev/null @@ -1,503 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using NLog; -using Ragon.Common; - -namespace Ragon.Core -{ - public class GameRoom - { - public int PlayersMin { get; private set; } - public int PlayersMax { get; private set; } - public int PlayersCount => _players.Count; - 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 ISocketServer _socketServer; - private readonly Scheduler _scheduler; - private readonly Application _application; - private readonly PluginBase _plugin; - private readonly RagonSerializer _writer = new(512); - - // Cache - private ushort[] _readyPlayers = Array.Empty(); - private ushort[] _allPlayers = Array.Empty(); - private Entity[] _entitiesAll = Array.Empty(); - private HashSet _entitiesDirtySet = new HashSet(); - private List _entitiesDirty = new List(); - private List _peersCache = new List(); - private List _awaitingPeers = new List(); - - public Player GetPlayerByPeer(ushort peerId) => _players[peerId]; - - public Player GetPlayerById(string id) => _players.Values.FirstOrDefault(p => p.Id == id)!; - - public Entity GetEntityById(int entityId) => _entities[entityId]; - - public Player GetOwner() => _players[_owner]; - - public RagonSerializer GetSharedSerializer() => _writer; - - public GameRoom(Application application, PluginBase pluginBase, string roomId, string map, int min, int max) - { - _application = application; - _socketServer = application.SocketServer; - _plugin = pluginBase; - _scheduler = new Scheduler(); - - Map = map; - PlayersMin = min; - PlayersMax = max; - Id = roomId; - - _plugin.Attach(this); - } - - public void AddPlayer(Player player, ReadOnlySpan payload) - { - if (_players.Count == 0) - { - _owner = player.PeerId; - } - - _players.Add(player.PeerId, player); - _allPlayers = _players.Select(p => p.Key).ToArray(); - - AcceptPlayer(player); - } - - public void RemovePlayer(ushort peerId) - { - if (_players.Remove(peerId, out var player)) - { - _allPlayers = _players.Select(p => p.Key).ToArray(); - _readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray(); - - _plugin.OnPlayerLeaved(player); - - BroadcastLeaved(player); - - if (_allPlayers.Length > 0 && player.PeerId == _owner) - { - var nextOwnerId = _allPlayers[0]; - var nextOwner = _players[nextOwnerId]; - - _owner = nextOwnerId; - - BroadcastNewOwner(player, nextOwner); - } - - _entitiesAll = _entities.Values.ToArray(); - } - } - - public void ProcessEvent(ushort peerId, RagonOperation operation, RagonSerializer reader) - { - switch (operation) - { - case RagonOperation.LOAD_SCENE: - { - var sceneName = reader.ReadString(); - BroadcastNewScene(sceneName); - break; - } - case RagonOperation.SCENE_LOADED: - { - var player = _players[peerId]; - if (peerId == _owner) - { - var statics = reader.ReadUShort(); - for (var staticIndex = 0; staticIndex < statics; staticIndex++) - { - var entityType = reader.ReadUShort(); - var entityAuthority = (RagonAuthority) reader.ReadByte(); - var staticId = reader.ReadUShort(); - var propertiesCount = reader.ReadUShort(); - - var entity = new Entity(this, player.PeerId, entityType, staticId, entityAuthority); - for (var propertyIndex = 0; propertyIndex < propertiesCount; propertyIndex++) - { - var propertyType = reader.ReadBool(); - var propertySize = reader.ReadUShort(); - entity.AddProperty(new EntityProperty(propertySize, propertyType)); - } - - player.AttachEntity(entity); - AttachEntity(player, entity); - } - - _entitiesAll = _entities.Values.ToArray(); - _logger.Trace($"Scene entities: {statics}"); - - _awaitingPeers.Add(peerId); - - foreach (var peer in _awaitingPeers) - { - var joinedPlayer = _players[peer]; - joinedPlayer.IsLoaded = true; - - _plugin.OnPlayerJoined(joinedPlayer); - _logger.Trace($"[{_owner}][{peer}] Player {joinedPlayer.Id} restored"); - - BroadcastJoined(player); - } - - _readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray(); - - BroadcastSnapshot(_awaitingPeers.ToArray()); - - _awaitingPeers.Clear(); - } - else if (GetOwner().IsLoaded) - { - _logger.Trace($"[{_owner}][{peerId}] Player {player.Id} restored instantly"); - player.IsLoaded = true; - - BroadcastJoined(player); - - _readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray(); - _plugin.OnPlayerJoined(player); - - BroadcastSnapshot(new[] {peerId}); - - foreach (var (key, value) in _entities) - value.RestoreBufferedEvents(peerId); - } - else - { - _logger.Trace($"[{_owner}][{peerId}] Player {player.Id} waiting"); - _awaitingPeers.Add(peerId); - } - - break; - } - case RagonOperation.REPLICATE_ENTITY_STATE: - { - var entitiesCount = reader.ReadUShort(); - for (var entityIndex = 0; entityIndex < entitiesCount; entityIndex++) - { - var entityId = reader.ReadUShort(); - if (_entities.TryGetValue(entityId, out var entity)) - { - entity.ReadState(peerId, reader); - - if (_entitiesDirtySet.Add(entity)) - _entitiesDirty.Add(entity); - } - else - { - _logger.Error($"Entity with Id {entityId} not found, replication interrupted"); - break; - } - } - break; - } - case RagonOperation.REPLICATE_ENTITY_EVENT: - { - var entityId = reader.ReadUShort(); - if (!_entities.TryGetValue(entityId, out var ent)) - { - _logger.Warn($"Entity not found for event with Id {entityId}"); - return; - } - - var player = _players[peerId]; - ent.ProcessEvent(player, reader); - break; - } - case RagonOperation.CREATE_ENTITY: - { - var entityType = reader.ReadUShort(); - var eventAuthority = (RagonAuthority) reader.ReadByte(); - var propertiesCount = reader.ReadUShort(); - - _logger.Trace($"[{peerId}] Create Entity {entityType}"); - - var player = _players[peerId]; - var entity = new Entity(this, player.PeerId, entityType, 0, eventAuthority); - for (var i = 0; i < propertiesCount; i++) - { - var propertyType = reader.ReadBool(); - var propertySize = reader.ReadUShort(); - entity.AddProperty(new EntityProperty(propertySize, propertyType)); - } - - var entityPayload = reader.ReadData(reader.Size); - entity.SetPayload(entityPayload.ToArray()); - - if (_plugin.OnEntityCreated(player, entity)) - return; - - player.AttachEntity(entity); - AttachEntity(player, entity); - - entity.Create(); - break; - } - case RagonOperation.DESTROY_ENTITY: - { - var entityId = reader.ReadInt(); - if (_entities.TryGetValue(entityId, out var entity)) - { - var player = _players[peerId]; - var payload = reader.ReadData(reader.Size); - DetachEntity(player, entity, payload); - } - break; - } - } - } - - public void Tick(float deltaTime) - { - _scheduler.Tick(deltaTime); - BroadcastState(); - } - - public void OnStart() - { - _plugin.OnStart(); - } - - public void OnStop() - { - foreach (var peerId in _allPlayers) - _application.SocketServer.Disconnect(peerId, 0); - - _plugin.OnStop(); - _plugin.Detach(); - _scheduler.Dispose(); - } - - public void AttachEntity(Player player, Entity entity) - { - _entities.Add(entity.EntityId, entity); - _entitiesAll = _entities.Values.ToArray(); - } - - 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(); - } - - void BroadcastNewOwner(Player prev, Player next) - { - var entitiesToUpdate = prev.Entities.Where(e => e.StaticId > 0).ToArray(); - - _writer.Clear(); - _writer.WriteOperation(RagonOperation.OWNERSHIP_CHANGED); - _writer.WriteString(next.Id); - _writer.WriteUShort((ushort) entitiesToUpdate.Length); - foreach (var entity in entitiesToUpdate) - { - _writer.WriteUShort(entity.EntityId); - entity.SetOwner((ushort) next.PeerId); - } - - BroadcastToReady(_writer, DeliveryType.Reliable); - } - - void BroadcastJoined(Player player) - { - _writer.Clear(); - _writer.WriteOperation(RagonOperation.PLAYER_JOINED); - _writer.WriteUShort(player.PeerId); - _writer.WriteString(player.Id); - _writer.WriteString(player.PlayerName); - - BroadcastToReady(_writer, new [] { player.PeerId }, DeliveryType.Reliable); - } - - void BroadcastLeaved(Player player) - { - _writer.Clear(); - _writer.WriteOperation(RagonOperation.PLAYER_LEAVED); - _writer.WriteString(player.Id); - - var entitiesToDelete = player.Entities.Where(e => e.StaticId == 0).ToArray(); - _writer.WriteUShort((ushort) entitiesToDelete.Length); - foreach (var entity in entitiesToDelete) - { - _writer.WriteUShort(entity.EntityId); - _entities.Remove(entity.EntityId); - } - - BroadcastToReady(_writer, DeliveryType.Reliable); - } - - void BroadcastSnapshot(ushort[] peersIds) - { - _writer.Clear(); - _writer.WriteOperation(RagonOperation.SNAPSHOT); - _writer.WriteUShort((ushort) _readyPlayers.Length); - foreach (var playerPeerId in _readyPlayers) - { - _writer.WriteUShort(playerPeerId); - _writer.WriteString(_players[playerPeerId].Id); - _writer.WriteString(_players[playerPeerId].PlayerName); - } - - var dynamicEntities = _entitiesAll.Where(e => e.StaticId == 0).ToArray(); - var dynamicEntitiesCount = (ushort) dynamicEntities.Length; - _writer.WriteUShort(dynamicEntitiesCount); - foreach (var entity in dynamicEntities) - { - ReadOnlySpan payload = entity.Payload.AsSpan(); - - _writer.WriteUShort(entity.EntityType); - _writer.WriteUShort(entity.EntityId); - _writer.WriteUShort(entity.OwnerId); - _writer.WriteUShort((ushort) payload.Length); - _writer.WriteData(ref payload); - - entity.WriteSnapshot(_writer); - } - - var staticEntities = _entitiesAll.Where(e => e.StaticId != 0).ToArray(); - var staticEntitiesCount = (ushort) staticEntities.Length; - _writer.WriteUShort(staticEntitiesCount); - foreach (var entity in staticEntities) - { - ReadOnlySpan payload = entity.Payload.AsSpan(); - - _writer.WriteUShort(entity.EntityType); - _writer.WriteUShort(entity.EntityId); - _writer.WriteUShort(entity.StaticId); - _writer.WriteUShort(entity.OwnerId); - _writer.WriteUShort((ushort) payload.Length); - _writer.WriteData(ref payload); - - entity.WriteSnapshot(_writer); - } - - var sendData = _writer.ToArray(); - Broadcast(peersIds, sendData, DeliveryType.Reliable); - } - - void BroadcastState() - { - var entities = (ushort) _entitiesDirty.Count; - if (entities > 0) - { - _writer.Clear(); - _writer.WriteOperation(RagonOperation.REPLICATE_ENTITY_STATE); - _writer.WriteUShort(entities); - - foreach (var entity in _entitiesDirty) - entity.WriteProperties(_writer); - - _entitiesDirty.Clear(); - _entitiesDirtySet.Clear(); - - BroadcastToReady(_writer, DeliveryType.Reliable); - } - } - - void AcceptPlayer(Player player) - { - _writer.Clear(); - _writer.WriteOperation(RagonOperation.JOIN_SUCCESS); - _writer.WriteString(Id); - _writer.WriteString(player.Id); - _writer.WriteString(GetOwner().Id); - _writer.WriteUShort((ushort) PlayersMin); - _writer.WriteUShort((ushort) PlayersMax); - _writer.WriteString(Map); - - Send(player.PeerId, _writer, DeliveryType.Reliable); - } - - void BroadcastNewScene(string sceneName) - { - _readyPlayers = Array.Empty(); - _entitiesAll = Array.Empty(); - _entities.Clear(); - - _writer.Clear(); - _writer.WriteOperation(RagonOperation.LOAD_SCENE); - _writer.WriteString(sceneName); - - BroadcastToAll(_writer, DeliveryType.Reliable); - } - - public void Send(ushort peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable) - { - _socketServer.Send(peerId, rawData, deliveryType); - } - - public void Send(ushort peerId, RagonSerializer writer, DeliveryType deliveryType = DeliveryType.Unreliable) - { - var sendData = writer.ToArray(); - _socketServer.Send(peerId, sendData, deliveryType); - } - - public void Broadcast(ushort[] peersIds, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable) - { - _socketServer.Broadcast(peersIds, rawData, deliveryType); - } - - public void BroadcastToAll(byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable) - { - _socketServer.Broadcast(_allPlayers, rawData, deliveryType); - } - - public void BroadcastToAll(RagonSerializer writer, DeliveryType deliveryType = DeliveryType.Unreliable) - { - var sendData = writer.ToArray(); - _socketServer.Broadcast(_allPlayers, sendData, deliveryType); - } - - public void BroadcastToReady(byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable) - { - _socketServer.Broadcast(_readyPlayers, rawData, deliveryType); - } - - public void BroadcastToReady(RagonSerializer writer, DeliveryType deliveryType = DeliveryType.Unreliable) - { - var sendData = writer.ToArray(); - _socketServer.Broadcast(_readyPlayers, sendData, deliveryType); - } - - public void BroadcastToReady(byte[] rawData, ushort[] excludePeersIds, DeliveryType deliveryType = DeliveryType.Unreliable) - { - _peersCache.Clear(); - foreach (var playerPeerId in _readyPlayers) - { - foreach (var excludePeersId in excludePeersIds) - { - if (playerPeerId != excludePeersId) - { - _peersCache.Add(playerPeerId); - } - } - } - - var peersIds = _peersCache.ToArray(); - _socketServer.Broadcast(peersIds, rawData, deliveryType); - } - - public void BroadcastToReady(RagonSerializer writer, ushort[] excludePeersIds, DeliveryType deliveryType = DeliveryType.Unreliable) - { - var sendData = writer.ToArray(); - BroadcastToReady(sendData, excludePeersIds, deliveryType); - } - } -} \ No newline at end of file diff --git a/Ragon/Sources/Handler.cs b/Ragon/Sources/Handler.cs deleted file mode 100644 index dedf5f8..0000000 --- a/Ragon/Sources/Handler.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace Ragon.Core; - -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/Lobby.cs b/Ragon/Sources/Lobby.cs deleted file mode 100644 index fcb3c14..0000000 --- a/Ragon/Sources/Lobby.cs +++ /dev/null @@ -1,125 +0,0 @@ -using System; -using System.Linq; -using NLog; -using Ragon.Common; - -namespace Ragon.Core; - -public class Lobby -{ - private readonly ILogger _logger = LogManager.GetCurrentClassLogger(); - private readonly Application _application; - private readonly RagonSerializer _writer; - private readonly RoomManager _roomManager; - private readonly AuthorizationManager _authorizationManager; - - public AuthorizationManager AuthorizationManager => _authorizationManager; - - public Lobby(IApplicationHandler provider, RoomManager manager, Application application) - { - _roomManager = manager; - _application = application; - _writer = new RagonSerializer(); - _authorizationManager = new AuthorizationManager(provider, application, this, _writer); - } - - public void ProcessEvent(ushort peerId, RagonOperation op, RagonSerializer reader) - { - var player = _authorizationManager.GetPlayer(peerId); - if (op == RagonOperation.AUTHORIZE) - { - if (player != null) - { - _logger.Warn("Player already authorized"); - return; - } - - var key = reader.ReadString(); - var playerName = reader.ReadString(); - var additionalData = reader.ReadData(reader.Size); - _authorizationManager.OnAuthorization(peerId, key, playerName, additionalData); - return; - } - - if (player == null) - { - _logger.Warn($"Peer not authorized {peerId} trying to {op}"); - return; - } - - switch (op) - { - case RagonOperation.JOIN_ROOM: - { - var roomId = reader.ReadString(); - var exists = _roomManager.Rooms.Any(r => r.Id == roomId); - if (!exists) - { - _writer.Clear(); - _writer.WriteOperation(RagonOperation.JOIN_FAILED); - _writer.WriteString($"Room with id {roomId} not exists"); - var sendData = _writer.ToArray(); - _application.SocketServer.Send(peerId, sendData, DeliveryType.Reliable); - return; - } - - if (_roomManager.RoomsBySocket.ContainsKey(peerId)) - _roomManager.Left(player, Array.Empty()); - - _roomManager.Join(player, roomId, Array.Empty()); - break; - } - case RagonOperation.CREATE_ROOM: - { - var roomId = Guid.NewGuid().ToString(); - var custom = reader.ReadBool(); - if (custom) - { - roomId = reader.ReadString(); - var exists = _roomManager.Rooms.Any(r => r.Id == roomId); - if (exists) - { - _writer.Clear(); - _writer.WriteOperation(RagonOperation.JOIN_FAILED); - _writer.WriteString($"Room with id {roomId} already exists"); - - var sendData = _writer.ToArray(); - _application.SocketServer.Send(peerId, sendData, DeliveryType.Reliable); - return; - } - } - - var roomProperties = new RagonRoomParameters(); - roomProperties.Deserialize(reader); - - if (_roomManager.RoomsBySocket.ContainsKey(peerId)) - _roomManager.Left(player, Array.Empty()); - - _roomManager.Create(player, roomId, roomProperties, Array.Empty()); - break; - } - case RagonOperation.JOIN_OR_CREATE_ROOM: - { - var roomId = Guid.NewGuid().ToString(); - var roomProperties = new RagonRoomParameters(); - roomProperties.Deserialize(reader); - - if (_roomManager.RoomsBySocket.ContainsKey(peerId)) - _roomManager.Left(player, Array.Empty()); - - _roomManager.JoinOrCreate(player, roomId, roomProperties, Array.Empty()); - break; - } - case RagonOperation.LEAVE_ROOM: - { - _roomManager.Left(player, Array.Empty()); - break; - } - } - } - - public void OnDisconnected(ushort peerId) - { - _authorizationManager.Cleanup(peerId); - } -} \ No newline at end of file diff --git a/Ragon/Sources/Player.cs b/Ragon/Sources/Player.cs deleted file mode 100755 index bf4a574..0000000 --- a/Ragon/Sources/Player.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Ragon.Core -{ - public class Player - { - public string Id { get; set; } - public string PlayerName { get; set; } - public ushort PeerId { get; set; } - public bool IsLoaded { get; set; } - - public List Entities; - public List EntitiesIds; - - public void AttachEntity(Entity entity) - { - Entities.Add(entity); - EntitiesIds.Add((entity.EntityId)); - } - - public void DetachEntity(Entity entity) - { - Entities.Remove(entity); - EntitiesIds.Remove(entity.EntityId); - } - } -} \ No newline at end of file diff --git a/Ragon/Sources/Plugin/IPluginFactory.cs b/Ragon/Sources/Plugin/IPluginFactory.cs deleted file mode 100644 index 2a79149..0000000 --- a/Ragon/Sources/Plugin/IPluginFactory.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ragon.Core -{ - public interface PluginFactory - { - public PluginBase CreatePlugin(string map); - 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 deleted file mode 100755 index 98f98ba..0000000 --- a/Ragon/Sources/Plugin/PluginBase.cs +++ /dev/null @@ -1,206 +0,0 @@ -using System; -using System.Collections.Generic; -using NLog; -using Ragon.Common; - -namespace Ragon.Core -{ - public class PluginBase - { - private delegate void SubscribeDelegate(Player player, ref ReadOnlySpan data); - private delegate void SubscribeEntityDelegate(Player player, Entity entity, ref ReadOnlySpan data); - - private Dictionary _globalEvents = new(); - private Dictionary> _entityEvents = new(); - private readonly RagonSerializer _serializer = new(); - - protected GameRoom Room { get; private set; } = null!; - protected ILogger Logger = null!; - - public void Attach(GameRoom gameRoom) - { - Logger = LogManager.GetLogger($"Plugin<{GetType().Name}>"); - - Room = gameRoom; - - _globalEvents.Clear(); - _entityEvents.Clear(); - } - - public void Detach() - { - _globalEvents.Clear(); - _entityEvents.Clear(); - } - - public void OnEvent(ushort evntCode, Action action) where T : IRagonSerializable, new() - { - if (_globalEvents.ContainsKey(evntCode)) - { - Logger.Warn($"Event subscriber already added {evntCode}"); - return; - } - - var data = new T(); - _globalEvents.Add(evntCode, (Player player, ref ReadOnlySpan raw) => - { - if (raw.Length == 0) - { - Logger.Warn($"Payload is empty for event {evntCode}"); - return; - } - - _serializer.Clear(); - _serializer.FromSpan(ref raw); - data.Deserialize(_serializer); - action.Invoke(player, data); - }); - } - - public void OnEvent(ushort evntCode, Action action) - { - if (_globalEvents.ContainsKey(evntCode)) - { - Logger.Warn($"Event subscriber already added {evntCode}"); - return; - } - - _globalEvents.Add(evntCode, (Player player, ref ReadOnlySpan raw) => { action.Invoke(player); }); - } - - public void OnEvent(Entity entity, ushort evntCode, Action action) where T : IRagonSerializable, new() - { - if (_entityEvents.ContainsKey(entity.EntityId)) - { - if (_entityEvents[entity.EntityId].ContainsKey(evntCode)) - { - Logger.Warn($"Event subscriber already added {evntCode} for {entity.EntityId}"); - return; - } - - var data = new T(); - _entityEvents[entity.EntityId].Add(evntCode, (Player player, Entity ent, ref ReadOnlySpan raw) => - { - if (raw.Length == 0) - { - Logger.Warn($"Payload is empty for entity {ent.EntityId} event {evntCode}"); - return; - } - - _serializer.Clear(); - _serializer.FromSpan(ref raw); - data.Deserialize(_serializer); - action.Invoke(player, ent, data); - }); - - return; - } - - { - var data = new T(); - _entityEvents.Add(entity.EntityId, new Dictionary()); - _entityEvents[entity.EntityId].Add(evntCode, (Player player, Entity ent, ref ReadOnlySpan raw) => - { - if (raw.Length == 0) - { - Logger.Warn($"Payload is empty for entity {ent.EntityId} event {evntCode}"); - return; - } - - _serializer.Clear(); - _serializer.FromSpan(ref raw); - data.Deserialize(_serializer); - action.Invoke(player, ent, data); - }); - } - } - - public void OnEvent(Entity entity, ushort evntCode, Action action) - { - if (_entityEvents.ContainsKey(entity.EntityId)) - { - if (_entityEvents[entity.EntityId].ContainsKey(evntCode)) - { - Logger.Warn($"Event subscriber already added {evntCode} for {entity.EntityId}"); - return; - } - - _entityEvents[entity.EntityId].Add(evntCode, (Player player, Entity ent, ref ReadOnlySpan raw) => { action.Invoke(player, ent); }); - return; - } - - { - _entityEvents.Add(entity.EntityId, new Dictionary()); - _entityEvents[entity.EntityId].Add(evntCode, (Player player, Entity ent, ref ReadOnlySpan raw) => { action.Invoke(player, ent); }); - } - } - - public void UnsubscribeAll() - { - _globalEvents.Clear(); - _entityEvents.Clear(); - } - - public bool InternalHandle(uint peerId, int entityId, ushort evntCode, ref ReadOnlySpan payload) - { - if (!_entityEvents.ContainsKey(entityId)) - return false; - - if (!_entityEvents[entityId].ContainsKey(evntCode)) - return false; - - // var player = Room.GetPlayerById(peerId); - var entity = Room.GetEntityById(entityId); - - // _entityEvents[entityId][evntCode].Invoke(player, entity, ref payload); - - return true; - } - - public bool InternalHandle(uint peerId, ushort evntCode, ref ReadOnlySpan payload) - { - if (_globalEvents.ContainsKey(evntCode)) - { - // var player = Room.GetPlayerById(peerId); - // _globalEvents[evntCode].Invoke(player, ref payload); - return true; - } - - return false; - } - - #region VIRTUAL - - public virtual void OnPlayerJoined(Player player) - { - } - - public virtual void OnPlayerLeaved(Player player) - { - } - - public virtual void OnOwnershipChanged(Player player) - { - } - - public virtual bool OnEntityCreated(Player player, Entity entity) - { - return false; - } - - public virtual bool OnEntityDestroyed(Player player, Entity entity) - { - return false; - } - - public virtual void OnStart() - { - } - - public virtual void OnStop() - { - } - - #endregion - } -} \ No newline at end of file diff --git a/Ragon/Sources/RoomManager.cs b/Ragon/Sources/RoomManager.cs deleted file mode 100644 index f181162..0000000 --- a/Ragon/Sources/RoomManager.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using NLog; -using Ragon.Common; - -namespace Ragon.Core; - -public class RoomManager -{ - private readonly Application _gameThread; - private readonly PluginFactory _factory; - private readonly Logger _logger = LogManager.GetCurrentClassLogger(); - private readonly List _rooms = new(); - private readonly Dictionary _roomsBySocket; - - public IReadOnlyDictionary RoomsBySocket => _roomsBySocket; - public IReadOnlyList Rooms => _rooms; - - public RoomManager(PluginFactory factory, Application gameThread) - { - _gameThread = gameThread; - _factory = factory; - _roomsBySocket = new Dictionary(); - } - - public void Join(Player player, string roomId, byte[] payload) - { - _logger.Trace($"Player ({player.PlayerName}|{player.Id}) joined to room with Id {roomId}"); - - if (_rooms.Count > 0) - { - foreach (var existRoom in _rooms) - { - if (existRoom.Id == roomId && existRoom.PlayersCount < existRoom.PlayersMax) - { - existRoom.AddPlayer(player, payload); - _roomsBySocket.Add(player.PeerId, existRoom); - return; - } - } - } - } - - public void Create(Player creator, string roomId, RagonRoomParameters parameters, byte[] payload) - { - var map = parameters.Map; - var min = parameters.Min; - var max = parameters.Max; - - _logger.Trace($"Player ({creator.PlayerName}|{creator.Id}) create room with Id {roomId} and params ({map}|{min}|{max})"); - - var plugin = _factory.CreatePlugin(map); - if (plugin == null) - throw new NullReferenceException($"Plugin for map {map} is null"); - - var room = new GameRoom(_gameThread, plugin, roomId, map, min, max); - room.AddPlayer(creator, payload); - room.OnStart(); - - _roomsBySocket.Add(creator.PeerId, room); - _rooms.Add(room); - } - - public void JoinOrCreate(Player player, string roomId, RagonRoomParameters parameters, byte[] payload) - { - var map = parameters.Map; - var min = parameters.Min; - var max = parameters.Max; - - if (_rooms.Count > 0) - { - foreach (var existRoom in _rooms) - { - if (existRoom.Map == map && existRoom.PlayersCount < existRoom.PlayersMax) - { - _logger.Trace($"Player ({player.PlayerName}|{player.Id}) joined to room with Id {roomId}"); - - existRoom.AddPlayer(player, payload); - _roomsBySocket.Add(player.PeerId, existRoom); - return; - } - } - } - - _logger.Trace($"Room not found for Player ({player.PlayerName}|{player.Id}), create room with Id {roomId} and params ({map}|{min}|{max})"); - - var plugin = _factory.CreatePlugin(map); - if (plugin == null) - throw new NullReferenceException($"Plugin for map {map} is null"); - - var room = new GameRoom(_gameThread, plugin, roomId, map, min, max); - room.AddPlayer(player, payload); - room.OnStart(); - - _roomsBySocket.Add(player.PeerId, room); - _rooms.Add(room); - } - - public void Left(Player player, byte[] payload) - { - if (_roomsBySocket.Remove(player.PeerId, out var room)) - { - _logger.Trace($"Player ({player.PlayerName}|{player.Id}) left room with Id {room.Id}"); - room.RemovePlayer(player.PeerId); - if (room.PlayersCount < room.PlayersMin) - { - _logger.Trace($"Room with Id {room.Id} destroyed"); - room.OnStop(); - _rooms.Remove(room); - } - - _gameThread.SocketServer.Send(player.PeerId, new byte[] {(byte) RagonOperation.LEAVE_ROOM}, DeliveryType.Reliable); - } - } - - public void Tick(float deltaTime) - { - foreach (var gameRoom in _rooms) - gameRoom.Tick(deltaTime); - } -} \ No newline at end of file diff --git a/Ragon/Sources/Scheduler.cs b/Ragon/Sources/Scheduler.cs deleted file mode 100644 index 9e22f8a..0000000 --- a/Ragon/Sources/Scheduler.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Collections.Generic; - - -namespace Ragon.Core -{ - public class Scheduler: IDisposable - { - List _scheduledTasks; - - public Scheduler(int defaultCapacity = 100) - { - _scheduledTasks = new List(defaultCapacity); - } - - public SchedulerTask Schedule(Action action, float interval, int count = 1) - { - var newTask = new SchedulerTask(action, interval, count - 1); - _scheduledTasks.Add(newTask); - return newTask; - } - - public SchedulerTask ScheduleForever(Action action, float interval) - { - var newTask = new SchedulerTask(action, interval, -1); - _scheduledTasks.Add(newTask); - return newTask; - } - - public void StopSchedule(SchedulerTask schedulerTask) - { - if (_scheduledTasks.Contains(schedulerTask)) - _scheduledTasks.Remove(schedulerTask); - } - - public void Tick(float deltaTime) - { - for (int i = _scheduledTasks.Count - 1; i >= 0; i--) - { - var scheduledTask = _scheduledTasks[i]; - scheduledTask.Tick(deltaTime); - - if (!scheduledTask.IsActive) - _scheduledTasks.Remove(scheduledTask); - } - } - - public void Dispose() - { - _scheduledTasks.Clear(); - } - } - - public class SchedulerTask - { - private Action _action; - private float _timer = 0; - private float _interval = 0; - private int _repeats = 0; - private bool _active; - - public int Repeats => _repeats; - public bool IsActive => _active; - - public SchedulerTask(Action task, float interval, int repeatCount = 0) - { - _action = task; - _interval = interval; - _timer = 0; - _active = true; - _repeats = repeatCount; - } - - public void Tick(float deltaTime) - { - _timer += deltaTime; - if (_timer >= _interval) - { - _action.Invoke(this); - if (_repeats == -1) - { - _timer = 0; - return; - } - - if (_repeats > 0) - { - _timer = 0; - _repeats--; - return; - } - - if (_repeats == 0) - _active = false; - } - } - } -} \ No newline at end of file diff --git a/Ragon/Sources/Server/DeliveryType.cs b/Ragon/Sources/Server/DeliveryType.cs deleted file mode 100644 index 0ecf70f..0000000 --- a/Ragon/Sources/Server/DeliveryType.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Ragon.Core; - -public enum DeliveryType -{ - Unreliable, - Reliable -} \ No newline at end of file diff --git a/Ragon/Sources/Server/ENet/ENetServer.cs b/Ragon/Sources/Server/ENet/ENetServer.cs deleted file mode 100755 index c9e9552..0000000 --- a/Ragon/Sources/Server/ENet/ENetServer.cs +++ /dev/null @@ -1,156 +0,0 @@ -using System; -using System.Diagnostics; -using System.Timers; -using ENet; -using NLog; - -namespace Ragon.Core -{ - public class ENetServer : ISocketServer - { - private ILogger _logger = LogManager.GetCurrentClassLogger(); - private Host _host; - private uint _protocol; - private Address _address; - private Event _netEvent; - private Peer[] _peers; - private IEventHandler _eventHandler; - private Stopwatch _timer; - - public ENetServer(IEventHandler eventHandler) - { - _eventHandler = eventHandler; - _timer = Stopwatch.StartNew(); - _peers = Array.Empty(); - _host = new Host(); - } - - public void Start(ushort port, int connections, uint protocol) - { - _address = default; - _address.Port = port; - _peers = new Peer[connections]; - _protocol = protocol; - _host.Create(_address, connections, 2, 0, 0, 1024 * 1024); - - - var protocolDecoded = (protocol >> 16 & 0xFF) + "." + (protocol >> 8 & 0xFF) + "." + (protocol & 0xFF); - _logger.Info($"Network listening on {port}"); - _logger.Info($"Protocol: {protocolDecoded}"); - } - - public void Broadcast(ushort[] peersIds, byte[] data, DeliveryType type) - { - var newPacket = new Packet(); - var packetFlags = PacketFlags.Instant; - byte channel = 1; - - if (type == DeliveryType.Reliable) - { - packetFlags = PacketFlags.Reliable; - channel = 0; - } - else if (type == DeliveryType.Unreliable) - { - channel = 1; - packetFlags = PacketFlags.UnreliableFragmented; - } - - newPacket.Create(data, data.Length, packetFlags); - foreach (var peerId in peersIds) - _peers[peerId].Send(channel, ref newPacket); - } - - public void Send(ushort peerId, byte[] data, DeliveryType type) - { - var newPacket = new Packet(); - var packetFlags = PacketFlags.Instant; - byte channel = 1; - - if (type == DeliveryType.Reliable) - { - packetFlags = PacketFlags.Reliable; - channel = 0; - } - else if (type == DeliveryType.Unreliable) - { - channel = 1; - packetFlags = PacketFlags.None; - } - - newPacket.Create(data, data.Length, packetFlags); - _peers[peerId].Send(channel, ref newPacket); - } - - public void Disconnect(ushort peerId, uint errorCode) - { - _peers[peerId].Reset(); - } - - public void Process() - { - bool polled = false; - while (!polled) - { - if (_host.CheckEvents(out _netEvent) <= 0) - { - if (_host.Service(0, out _netEvent) <= 0) - break; - - polled = true; - } - - switch (_netEvent.Type) - { - case EventType.None: - { - _logger.Trace("None event"); - break; - } - case EventType.Connect: - { - // if (IsValidProtocol(_netEvent.Data)) - // { - // _logger.Warn("Mismatched protocol, close connection"); - // break; - // } - _peers[_netEvent.Peer.ID] = _netEvent.Peer; - _eventHandler.OnConnected((ushort)_netEvent.Peer.ID); - break; - } - case EventType.Disconnect: - { - _eventHandler.OnDisconnected((ushort)_netEvent.Peer.ID); - break; - } - case EventType.Timeout: - { - _eventHandler.OnTimeout((ushort)_netEvent.Peer.ID); - break; - } - case EventType.Receive: - { - 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; - } - } - } - } - - public void Stop() - { - _host?.Dispose(); - } - - private bool IsValidProtocol(uint protocol) - { - return protocol == _protocol; - } - } -} \ No newline at end of file diff --git a/Ragon/Sources/Server/Http/WebSocketPacket.cs b/Ragon/Sources/Server/Http/WebSocketPacket.cs deleted file mode 100644 index dda1364..0000000 --- a/Ragon/Sources/Server/Http/WebSocketPacket.cs +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index 33ca38e..0000000 --- a/Ragon/Sources/Server/Http/WebSocketServer.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -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) - { - try - { - var result = await webSocket.ReceiveAsync(buffer, CancellationToken.None); - var dataRaw = buffer.Slice(0, result.Count); - _eventHandler.OnData(peerId, dataRaw.ToArray()); - } - catch (Exception ex) - { - break; - } - } - - _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/IEventHandler.cs b/Ragon/Sources/Server/IEventHandler.cs deleted file mode 100644 index a1f75ef..0000000 --- a/Ragon/Sources/Server/IEventHandler.cs +++ /dev/null @@ -1,11 +0,0 @@ -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/ISocketServer.cs b/Ragon/Sources/Server/ISocketServer.cs deleted file mode 100644 index 90dc2f8..0000000 --- a/Ragon/Sources/Server/ISocketServer.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Ragon.Core; - -public interface ISocketServer -{ - public void Start(ushort port, int connections, uint protocol); - public void Process(); - public void Stop(); - public void Send(ushort peerId, byte[] data, DeliveryType type); - public void Broadcast(ushort[] peersIds, byte[] data, DeliveryType type); - public void Disconnect(ushort peerId, uint errorCode); -} \ No newline at end of file diff --git a/global.json b/global.json index cbde930..9e5e1fd 100755 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "5.0.0", + "version": "6.0.0", "rollForward": "latestMajor", "allowPrerelease": true }