From 16b8d3a062d9847efa23eee8d1579018cc3f4203 Mon Sep 17 00:00:00 2001 From: Edmand46 Date: Sat, 30 Jul 2022 14:07:48 +0400 Subject: [PATCH 1/3] feat: replication level --- Ragon.Common/Protocol/RagonOperation.cs | 1 + Ragon/Sources/Game/GameRoom.cs | 41 +++++++--------- Ragon/Sources/Game/GameThread.cs | 21 ++++---- Ragon/Sources/Lobby/Lobby.cs | 64 +++++++++++++++++-------- Ragon/Sources/Room/RoomManager.cs | 16 +++++++ 5 files changed, 86 insertions(+), 57 deletions(-) diff --git a/Ragon.Common/Protocol/RagonOperation.cs b/Ragon.Common/Protocol/RagonOperation.cs index d486d5c..adc1480 100644 --- a/Ragon.Common/Protocol/RagonOperation.cs +++ b/Ragon.Common/Protocol/RagonOperation.cs @@ -7,6 +7,7 @@ namespace Ragon.Common AUTHORIZED_FAILED, JOIN_OR_CREATE_ROOM, + CREATE_ROOM, JOIN_ROOM, LEAVE_ROOM, OWNERSHIP_CHANGED, diff --git a/Ragon/Sources/Game/GameRoom.cs b/Ragon/Sources/Game/GameRoom.cs index 9b9a50f..318bb2a 100755 --- a/Ragon/Sources/Game/GameRoom.cs +++ b/Ragon/Sources/Game/GameRoom.cs @@ -94,8 +94,6 @@ namespace Ragon.Core _allPlayers = _players.Select(p => p.Key).ToArray(); _readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray(); - var isOwnershipChange = player.PeerId == _owner; - { _plugin.OnPlayerLeaved(player); @@ -114,34 +112,12 @@ namespace Ragon.Core Broadcast(_readyPlayers, sendData); } - if (_allPlayers.Length > 0 && isOwnershipChange) - { - var newRoomOwnerId = _allPlayers[0]; - var newRoomOwner = _players[newRoomOwnerId]; - - _owner = newRoomOwnerId; - - { - _plugin.OnOwnershipChanged(newRoomOwner); - - _serializer.Clear(); - _serializer.WriteOperation(RagonOperation.OWNERSHIP_CHANGED); - _serializer.WriteString(newRoomOwner.Id); - - var sendData = _serializer.ToArray(); - Broadcast(_readyPlayers, sendData); - } - } - _entitiesAll = _entities.Values.ToArray(); } } - public void ProcessEvent(uint peerId, ReadOnlySpan rawData) + public void ProcessEvent(uint peerId, RagonOperation operation, ReadOnlySpan payloadRawData) { - var operation = (RagonOperation) rawData[0]; - var payloadRawData = rawData.Slice(1, rawData.Length - 1); - _serializer.Clear(); _serializer.FromSpan(ref payloadRawData); @@ -193,6 +169,21 @@ namespace Ragon.Core Broadcast(_readyPlayers, sendData, DeliveryType.Reliable); break; } + case RagonOperation.LOAD_SCENE: + { + var sceneName = _serializer.ReadString(); + _readyPlayers = Array.Empty(); + _entitiesAll = Array.Empty(); + _entities.Clear(); + + _serializer.Clear(); + _serializer.WriteOperation(RagonOperation.LOAD_SCENE); + _serializer.WriteString(sceneName); + + var sendData = _serializer.ToArray(); + Broadcast(_allPlayers, sendData, DeliveryType.Reliable); + break; + } case RagonOperation.REPLICATE_EVENT: { var evntId = _serializer.ReadUShort(); diff --git a/Ragon/Sources/Game/GameThread.cs b/Ragon/Sources/Game/GameThread.cs index 7ea3d6a..e718a6f 100755 --- a/Ragon/Sources/Game/GameThread.cs +++ b/Ragon/Sources/Game/GameThread.cs @@ -1,7 +1,7 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.Threading; +using Ragon.Common; using ENet; using NLog; @@ -29,10 +29,10 @@ namespace Ragon.Core var authorizationProvider = factory.CreateAuthorizationProvider(configuration); var dispatcher = new Dispatcher(); _dispatcherInternal = dispatcher; - + ThreadDispatcher = dispatcher; Server = new ENetServer(this); - + _deltaTime = 1000.0f / configuration.SendRate; _roomManager = new RoomManager(factory, this); @@ -69,7 +69,7 @@ namespace Ragon.Core while (true) { Server.Process(); - + _dispatcherInternal.Process(); var elapsedMilliseconds = _gameLoopTimer.ElapsedMilliseconds; @@ -109,14 +109,13 @@ namespace Ragon.Core evnt.Packet.CopyTo(dataRaw); var data = new ReadOnlySpan(dataRaw); + var operation = (RagonOperation) data[0]; + var payload = data.Slice(1, data.Length - 1); + if (_roomManager.RoomsBySocket.TryGetValue(peerId, out var room)) - { - room.ProcessEvent(peerId, data); - } - else - { - _lobby.ProcessEvent(peerId, data); - } + room.ProcessEvent(peerId, operation, payload); + + _lobby.ProcessEvent(peerId, operation, payload); } catch (Exception exception) { diff --git a/Ragon/Sources/Lobby/Lobby.cs b/Ragon/Sources/Lobby/Lobby.cs index 618c7f8..7f610fd 100644 --- a/Ragon/Sources/Lobby/Lobby.cs +++ b/Ragon/Sources/Lobby/Lobby.cs @@ -1,11 +1,13 @@ using System; using System.Collections.Generic; +using NLog; using Ragon.Common; namespace Ragon.Core; public class Lobby : ILobby { + private readonly ILogger _logger = LogManager.GetCurrentClassLogger(); private readonly RagonSerializer _serializer; private readonly RoomManager _roomManager; private readonly AuthorizationManager _authorizationManager; @@ -19,30 +21,50 @@ public class Lobby : ILobby _authorizationManager = new AuthorizationManager(provider, gameThread, this, _serializer); } - public void ProcessEvent(uint peerId, ReadOnlySpan data) + public void ProcessEvent(uint peerId, RagonOperation op, ReadOnlySpan payload) { - var op = (RagonOperation) data[0]; - var payload = data.Slice(1, data.Length - 1); - _serializer.Clear(); _serializer.FromSpan(ref payload); + if (op == RagonOperation.AUTHORIZE) + { + var key = _serializer.ReadString(); + var playerName = _serializer.ReadString(); + var protocol = _serializer.ReadByte(); + _authorizationManager.OnAuthorization(peerId, key, playerName, protocol); + return; + } + + var player = _authorizationManager.GetPlayer(peerId); + if (player == null) + { + _logger.Warn($"Peer not authorized {peerId} trying to {op}"); + return; + } + switch (op) { - case RagonOperation.AUTHORIZE: - { - var key = _serializer.ReadString(); - var playerName = _serializer.ReadString(); - var protocol = _serializer.ReadByte(); - _authorizationManager.OnAuthorization(peerId, key, playerName, protocol); - break; - } case RagonOperation.JOIN_ROOM: { var roomId = _serializer.ReadString(); - var player = _authorizationManager.GetPlayer(peerId); - if (player != null) - _roomManager.Join(player, roomId, Array.Empty()); + + if (_roomManager.RoomsBySocket.ContainsKey(peerId)) + _roomManager.Left(player, Array.Empty()); + + _roomManager.Join(player, roomId, Array.Empty()); + + break; + } + case RagonOperation.CREATE_ROOM: + { + var min = _serializer.ReadUShort(); + var max = _serializer.ReadUShort(); + var map = _serializer.ReadString(); + + if (_roomManager.RoomsBySocket.ContainsKey(peerId)) + _roomManager.Left(player, Array.Empty()); + + _roomManager.Create(player, map, min, max, Array.Empty()); break; } case RagonOperation.JOIN_OR_CREATE_ROOM: @@ -50,16 +72,16 @@ public class Lobby : ILobby var min = _serializer.ReadUShort(); var max = _serializer.ReadUShort(); var map = _serializer.ReadString(); - var player = _authorizationManager.GetPlayer(peerId); - if (player != null) - _roomManager.JoinOrCreate(player, map, min, max, Array.Empty()); + + if (_roomManager.RoomsBySocket.ContainsKey(peerId)) + _roomManager.Left(player, Array.Empty()); + + _roomManager.JoinOrCreate(player, map, min, max, Array.Empty()); break; } case RagonOperation.LEAVE_ROOM: { - var player = _authorizationManager.GetPlayer(peerId); - if (player != null) - _roomManager.Left(player, Array.Empty()); + _roomManager.Left(player, Array.Empty()); break; } } diff --git a/Ragon/Sources/Room/RoomManager.cs b/Ragon/Sources/Room/RoomManager.cs index c35144c..dd4e385 100644 --- a/Ragon/Sources/Room/RoomManager.cs +++ b/Ragon/Sources/Room/RoomManager.cs @@ -40,6 +40,20 @@ public class RoomManager } } + public void Create(Player player, string map, int min, int max, byte[] payload) + { + var plugin = _factory.CreatePlugin(map); + if (plugin == null) + throw new NullReferenceException($"Plugin for map {map} is null"); + + var room = new GameRoom(_gameThread, plugin, map, min, max); + room.Joined(player, payload); + room.Start(); + + _roomsBySocket.Add(player.PeerId, room); + _rooms.Add(room); + } + public void JoinOrCreate(Player player, string map, int min, int max, byte[] payload) { if (_rooms.Count > 0) @@ -77,6 +91,8 @@ public class RoomManager room.Stop(); _rooms.Remove(room); } + + _gameThread.Server.Send(player.PeerId, new byte[] { (byte) RagonOperation.LEAVE_ROOM }, DeliveryType.Reliable); } } From 85336f998e0b3f0d75849bc0d5954643e2bcba1c Mon Sep 17 00:00:00 2001 From: Edmand46 Date: Sat, 30 Jul 2022 16:02:53 +0400 Subject: [PATCH 2/3] refactor: allow set roomId from client, added base room parameters --- Ragon.Common/Protocol/RagonRoomParameters.cs | 25 ++++++++++++++ Ragon.Common/Protocol/RagonSerializer.cs | 18 ++++++++++ Ragon/Sources/Game/GameRoom.cs | 4 +-- Ragon/Sources/Lobby/Lobby.cs | 35 +++++++++++++------ Ragon/Sources/Room/RoomManager.cs | 36 +++++++++++++++----- 5 files changed, 97 insertions(+), 21 deletions(-) create mode 100644 Ragon.Common/Protocol/RagonRoomParameters.cs diff --git a/Ragon.Common/Protocol/RagonRoomParameters.cs b/Ragon.Common/Protocol/RagonRoomParameters.cs new file mode 100644 index 0000000..218fa91 --- /dev/null +++ b/Ragon.Common/Protocol/RagonRoomParameters.cs @@ -0,0 +1,25 @@ +using NetStack.Serialization; + +namespace Ragon.Common +{ + public class RagonRoomParameters: IRagonSerializable + { + public string Map { get; set; } + public int Min { get; set; } + public int Max { get; set; } + + public void Serialize(BitBuffer buffer) + { + buffer.AddString(Map); + buffer.AddInt(Min); + buffer.AddInt(Max); + } + + public void Deserialize(BitBuffer buffer) + { + Map = buffer.ReadString(); + Min = buffer.ReadInt(); + Max = buffer.ReadInt(); + } + } +} \ No newline at end of file diff --git a/Ragon.Common/Protocol/RagonSerializer.cs b/Ragon.Common/Protocol/RagonSerializer.cs index d06cba5..1b70bd8 100644 --- a/Ragon.Common/Protocol/RagonSerializer.cs +++ b/Ragon.Common/Protocol/RagonSerializer.cs @@ -38,6 +38,24 @@ namespace Ragon.Common return value; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteBool(bool value) + { + ResizeIfNeed(1); + + _data[_offset] = value ? (byte) 1 : (byte) 0; + _offset += 1; + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ReadBool() + { + var value = _data[_offset]; + _offset += 1; + return value == 1; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteInt(int value) { diff --git a/Ragon/Sources/Game/GameRoom.cs b/Ragon/Sources/Game/GameRoom.cs index 318bb2a..cc581c1 100755 --- a/Ragon/Sources/Game/GameRoom.cs +++ b/Ragon/Sources/Game/GameRoom.cs @@ -29,7 +29,7 @@ namespace Ragon.Core private uint[] _allPlayers = Array.Empty(); private Entity[] _entitiesAll = Array.Empty(); - public GameRoom(IGameThread gameThread, PluginBase pluginBase, string map, int min, int max) + public GameRoom(IGameThread gameThread, PluginBase pluginBase, string roomId, string map, int min, int max) { _gameThread = gameThread; _plugin = pluginBase; @@ -38,7 +38,7 @@ namespace Ragon.Core Map = map; PlayersMin = min; PlayersMax = max; - Id = Guid.NewGuid().ToString(); + Id = roomId; _plugin.Attach(this); } diff --git a/Ragon/Sources/Lobby/Lobby.cs b/Ragon/Sources/Lobby/Lobby.cs index 7f610fd..2113262 100644 --- a/Ragon/Sources/Lobby/Lobby.cs +++ b/Ragon/Sources/Lobby/Lobby.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using NetStack.Serialization; using NLog; using Ragon.Common; @@ -9,6 +10,7 @@ public class Lobby : ILobby { private readonly ILogger _logger = LogManager.GetCurrentClassLogger(); private readonly RagonSerializer _serializer; + private readonly BitBuffer _buffer; private readonly RoomManager _roomManager; private readonly AuthorizationManager _authorizationManager; @@ -17,6 +19,7 @@ public class Lobby : ILobby public Lobby(IAuthorizationProvider provider, RoomManager manager, IGameThread gameThread) { _roomManager = manager; + _buffer = new BitBuffer(); _serializer = new RagonSerializer(); _authorizationManager = new AuthorizationManager(provider, gameThread, this, _serializer); } @@ -52,31 +55,43 @@ public class Lobby : ILobby _roomManager.Left(player, Array.Empty()); _roomManager.Join(player, roomId, Array.Empty()); - break; } case RagonOperation.CREATE_ROOM: { - var min = _serializer.ReadUShort(); - var max = _serializer.ReadUShort(); - var map = _serializer.ReadString(); + var roomId = Guid.NewGuid().ToString(); + var custom = _serializer.ReadBool(); + if (custom) + roomId = _serializer.ReadString(); + + var propertiesPayload = _serializer.ReadData(_serializer.Size); + _buffer.Clear(); + _buffer.FromSpan(ref propertiesPayload, propertiesPayload.Length); + + var roomProperties = new RagonRoomParameters(); + roomProperties.Deserialize(_buffer); if (_roomManager.RoomsBySocket.ContainsKey(peerId)) _roomManager.Left(player, Array.Empty()); - - _roomManager.Create(player, map, min, max, Array.Empty()); + + _roomManager.Create(player, roomId, roomProperties, Array.Empty()); break; } case RagonOperation.JOIN_OR_CREATE_ROOM: { - var min = _serializer.ReadUShort(); - var max = _serializer.ReadUShort(); - var map = _serializer.ReadString(); + var roomId = Guid.NewGuid().ToString(); + var roomProperties = new RagonRoomParameters(); + var propertiesPayload = _serializer.ReadData(_serializer.Size); + + _buffer.Clear(); + _buffer.FromSpan(ref propertiesPayload, propertiesPayload.Length); + + roomProperties.Deserialize(_buffer); if (_roomManager.RoomsBySocket.ContainsKey(peerId)) _roomManager.Left(player, Array.Empty()); - _roomManager.JoinOrCreate(player, map, min, max, Array.Empty()); + _roomManager.JoinOrCreate(player, roomId, roomProperties, Array.Empty()); break; } case RagonOperation.LEAVE_ROOM: diff --git a/Ragon/Sources/Room/RoomManager.cs b/Ragon/Sources/Room/RoomManager.cs index dd4e385..3a2a508 100644 --- a/Ragon/Sources/Room/RoomManager.cs +++ b/Ragon/Sources/Room/RoomManager.cs @@ -26,6 +26,8 @@ public class RoomManager 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) @@ -34,34 +36,46 @@ public class RoomManager { existRoom.Joined(player, payload); _roomsBySocket.Add(player.PeerId, existRoom); - break; + return; } } } } - public void Create(Player player, string map, int min, int max, byte[] payload) + 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, map, min, max); - room.Joined(player, payload); + var room = new GameRoom(_gameThread, plugin, roomId, map, min, max); + room.Joined(creator, payload); room.Start(); - _roomsBySocket.Add(player.PeerId, room); + _roomsBySocket.Add(creator.PeerId, room); _rooms.Add(room); } - public void JoinOrCreate(Player player, string map, int min, int max, byte[] payload) + 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.Joined(player, payload); _roomsBySocket.Add(player.PeerId, existRoom); return; @@ -69,11 +83,13 @@ public class RoomManager } } + _logger.Trace($"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, map, min, max); + var room = new GameRoom(_gameThread, plugin, roomId, map, min, max); room.Joined(player, payload); room.Start(); @@ -85,14 +101,16 @@ public class RoomManager { if (_roomsBySocket.Remove(player.PeerId, out var room)) { + _logger.Trace($"Player ({player.PlayerName}|{player.Id}) left room with Id {room.Id}"); room.Leave(player.PeerId); if (room.PlayersCount < room.PlayersMin) { + _logger.Trace($"Room with Id {room.Id} destroyed"); room.Stop(); _rooms.Remove(room); } - - _gameThread.Server.Send(player.PeerId, new byte[] { (byte) RagonOperation.LEAVE_ROOM }, DeliveryType.Reliable); + + _gameThread.Server.Send(player.PeerId, new byte[] {(byte) RagonOperation.LEAVE_ROOM}, DeliveryType.Reliable); } } From e2032f381a60e47d4c099ecedaad56686a54a7dc Mon Sep 17 00:00:00 2001 From: Edmand46 Date: Sun, 31 Jul 2022 09:37:05 +0400 Subject: [PATCH 3/3] refactor: added error message on creation or joining to room --- Ragon/Sources/Lobby/Lobby.cs | 27 +++++++++++++++++++++++++++ Ragon/Sources/Room/RoomManager.cs | 6 +++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/Ragon/Sources/Lobby/Lobby.cs b/Ragon/Sources/Lobby/Lobby.cs index 2113262..8ae052b 100644 --- a/Ragon/Sources/Lobby/Lobby.cs +++ b/Ragon/Sources/Lobby/Lobby.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using NetStack.Serialization; using NLog; using Ragon.Common; @@ -13,12 +14,14 @@ public class Lobby : ILobby private readonly BitBuffer _buffer; private readonly RoomManager _roomManager; private readonly AuthorizationManager _authorizationManager; + private readonly IGameThread _gameThread; public AuthorizationManager AuthorizationManager => _authorizationManager; public Lobby(IAuthorizationProvider provider, RoomManager manager, IGameThread gameThread) { _roomManager = manager; + _gameThread = gameThread; _buffer = new BitBuffer(); _serializer = new RagonSerializer(); _authorizationManager = new AuthorizationManager(provider, gameThread, this, _serializer); @@ -50,6 +53,17 @@ public class Lobby : ILobby case RagonOperation.JOIN_ROOM: { var roomId = _serializer.ReadString(); + roomId = _serializer.ReadString(); + var exists = _roomManager.Rooms.Any(r => r.Id == roomId); + if (!exists) + { + _serializer.Clear(); + _serializer.WriteOperation(RagonOperation.JOIN_FAILED); + _serializer.WriteString($"Room with id {roomId} not exists"); + var sendData = _serializer.ToArray(); + _gameThread.Server.Send(peerId, sendData, DeliveryType.Reliable); + return; + } if (_roomManager.RoomsBySocket.ContainsKey(peerId)) _roomManager.Left(player, Array.Empty()); @@ -62,7 +76,20 @@ public class Lobby : ILobby var roomId = Guid.NewGuid().ToString(); var custom = _serializer.ReadBool(); if (custom) + { roomId = _serializer.ReadString(); + var exists = _roomManager.Rooms.Any(r => r.Id == roomId); + if (exists) + { + _serializer.Clear(); + _serializer.WriteOperation(RagonOperation.JOIN_FAILED); + _serializer.WriteString($"Room with id {roomId} already exists"); + + var sendData = _serializer.ToArray(); + _gameThread.Server.Send(peerId, sendData, DeliveryType.Reliable); + return; + } + } var propertiesPayload = _serializer.ReadData(_serializer.Size); _buffer.Clear(); diff --git a/Ragon/Sources/Room/RoomManager.cs b/Ragon/Sources/Room/RoomManager.cs index 3a2a508..5bcc303 100644 --- a/Ragon/Sources/Room/RoomManager.cs +++ b/Ragon/Sources/Room/RoomManager.cs @@ -11,12 +11,12 @@ public class RoomManager private readonly IGameThread _gameThread; private readonly PluginFactory _factory; private readonly Logger _logger = LogManager.GetCurrentClassLogger(); - private readonly List _rooms = new List(); + private readonly List _rooms = new(); private readonly Dictionary _roomsBySocket; public IReadOnlyDictionary RoomsBySocket => _roomsBySocket; public IReadOnlyList Rooms => _rooms; - + public RoomManager(PluginFactory factory, IGameThread gameThread) { _gameThread = gameThread; @@ -83,7 +83,7 @@ public class RoomManager } } - _logger.Trace($"Player ({player.PlayerName}|{player.Id}) create room with Id {roomId} and params ({map}|{min}|{max})"); + _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)