From 05c89046014ca54645c720c93c89924307e3d893 Mon Sep 17 00:00:00 2001 From: Edmand46 Date: Thu, 23 Jun 2022 20:45:41 +0400 Subject: [PATCH] wip --- .github/workflows/main.yml | 6 +- Ragon.Common/Protocol/RagonSerializer.cs | 15 +- .../NLog.config | 0 .../Program.cs | 5 +- .../Ragon.SimpleServer.csproj | 4 - .../Source/AuthorizationProviderByKey.cs | 29 +++ .../Source/Plugins/SimplePlugin.cs | 11 +- .../Source/SimplePluginFactory.cs | 8 +- Ragon.SimpleServer/config.json | 8 + Ragon.Stress/Ragon.Stress.csproj | 19 ++ Ragon.sln | 8 +- Ragon/Ragon.csproj | 2 +- Ragon/Sources/Application.cs | 88 ++------ .../Authorization/AuthorizationManager.cs | 85 +++++++ .../Authorization/IAuthorizationManager.cs | 6 + .../Authorization/IAuthorizationProvider.cs | 9 + Ragon/Sources/AuthorizationManager.cs | 11 - Ragon/Sources/Bootstrap.cs | 6 +- Ragon/Sources/Configuration/Configuration.cs | 2 +- Ragon/Sources/Core/Communication/IReceiver.cs | 6 + Ragon/Sources/Core/Communication/ISender.cs | 6 + Ragon/Sources/Core/IO/Dispatcher.cs | 21 ++ Ragon/Sources/Core/IO/DispatcherTask.cs | 15 ++ Ragon/Sources/Core/IO/IDispatcher.cs | 9 + Ragon/Sources/Core/Utils/SynchronizedCache.cs | 130 +++++++++++ Ragon/Sources/Event/EventStream.cs | 39 ---- .../Event/{Event.cs => SocketEvent.cs} | 2 +- .../{EventType.cs => SocketEventType.cs} | 0 .../{Rooms/Room.cs => Game/GameRoom.cs} | 75 +++---- Ragon/Sources/Game/GameThread.cs | 138 ++++++++++++ Ragon/Sources/Game/IGameRoom.cs | 18 ++ Ragon/Sources/Game/IGameThread.cs | 11 + Ragon/Sources/Lobby/Lobby.cs | 66 ++++++ Ragon/Sources/Matchmaking/Matchmaking.cs | 77 +++++++ Ragon/Sources/Plugin/PluginBase.cs | 24 +- Ragon/Sources/Plugin/PluginFactory.cs | 2 +- Ragon/Sources/Rooms/RoomManager.cs | 211 ------------------ Ragon/Sources/Rooms/RoomThread.cs | 110 --------- Ragon/Sources/Server/ENetServer.cs | 60 ++--- Ragon/Sources/Server/WebsocketServer.cs | 60 ----- Ragon/Sources/Storage/EntityInfo.cs | 6 - Ragon/Sources/Storage/PlayerInfo.cs | 6 - Ragon/Sources/Storage/RoomInfo.cs | 6 - Ragon/Sources/Storage/Storage.cs | 29 --- SimpleServer/Source/AuthorizerByKey.cs | 20 -- SimpleServer/Source/Events/SimpleEvent.cs | 19 -- SimpleServer/config.json | 8 - 47 files changed, 781 insertions(+), 715 deletions(-) rename {SimpleServer => Ragon.SimpleServer}/NLog.config (100%) rename {SimpleServer => Ragon.SimpleServer}/Program.cs (66%) rename SimpleServer/SimpleServer.csproj => Ragon.SimpleServer/Ragon.SimpleServer.csproj (90%) create mode 100644 Ragon.SimpleServer/Source/AuthorizationProviderByKey.cs rename {SimpleServer => Ragon.SimpleServer}/Source/Plugins/SimplePlugin.cs (62%) rename {SimpleServer => Ragon.SimpleServer}/Source/SimplePluginFactory.cs (55%) create mode 100755 Ragon.SimpleServer/config.json create mode 100644 Ragon.Stress/Ragon.Stress.csproj create mode 100644 Ragon/Sources/Authorization/AuthorizationManager.cs create mode 100644 Ragon/Sources/Authorization/IAuthorizationManager.cs create mode 100644 Ragon/Sources/Authorization/IAuthorizationProvider.cs delete mode 100644 Ragon/Sources/AuthorizationManager.cs create mode 100644 Ragon/Sources/Core/Communication/IReceiver.cs create mode 100644 Ragon/Sources/Core/Communication/ISender.cs create mode 100644 Ragon/Sources/Core/IO/Dispatcher.cs create mode 100644 Ragon/Sources/Core/IO/DispatcherTask.cs create mode 100644 Ragon/Sources/Core/IO/IDispatcher.cs create mode 100644 Ragon/Sources/Core/Utils/SynchronizedCache.cs delete mode 100644 Ragon/Sources/Event/EventStream.cs rename Ragon/Sources/Event/{Event.cs => SocketEvent.cs} (84%) rename Ragon/Sources/Event/{EventType.cs => SocketEventType.cs} (100%) rename Ragon/Sources/{Rooms/Room.cs => Game/GameRoom.cs} (91%) create mode 100755 Ragon/Sources/Game/GameThread.cs create mode 100644 Ragon/Sources/Game/IGameRoom.cs create mode 100644 Ragon/Sources/Game/IGameThread.cs create mode 100644 Ragon/Sources/Lobby/Lobby.cs create mode 100644 Ragon/Sources/Matchmaking/Matchmaking.cs delete mode 100644 Ragon/Sources/Rooms/RoomManager.cs delete mode 100755 Ragon/Sources/Rooms/RoomThread.cs delete mode 100644 Ragon/Sources/Server/WebsocketServer.cs delete mode 100644 Ragon/Sources/Storage/EntityInfo.cs delete mode 100644 Ragon/Sources/Storage/PlayerInfo.cs delete mode 100644 Ragon/Sources/Storage/RoomInfo.cs delete mode 100644 Ragon/Sources/Storage/Storage.cs delete mode 100644 SimpleServer/Source/AuthorizerByKey.cs delete mode 100644 SimpleServer/Source/Events/SimpleEvent.cs delete mode 100755 SimpleServer/config.json diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6b7e85d..7e7eb95 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,7 +19,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ github.ref }} - release_name: SimpleServer-${{ github.ref }} + release_name: Ragon.SimpleServer-${{ github.ref }} prerelease: true build: @@ -53,10 +53,10 @@ jobs: - name: Build shell: bash run: | - release_name="SimpleServer-${{ env.TAG }}-${{ matrix.target }}" + release_name="Ragon.SimpleServer-${{ env.TAG }}-${{ matrix.target }}" # Build everything - dotnet publish SimpleServer/SimpleServer.csproj -c Release --runtime "${{ matrix.target }}" -p:PublishSingleFile=true -o "$release_name" + dotnet publish Ragon.SimpleServer/Ragon.SimpleServer.csproj -c Release --runtime "${{ matrix.target }}" -p:PublishSingleFile=true -o "$release_name" # Pack files 7z a -tzip "${release_name}.zip" "./${release_name}/*" diff --git a/Ragon.Common/Protocol/RagonSerializer.cs b/Ragon.Common/Protocol/RagonSerializer.cs index f62f4ad..d06cba5 100644 --- a/Ragon.Common/Protocol/RagonSerializer.cs +++ b/Ragon.Common/Protocol/RagonSerializer.cs @@ -14,7 +14,7 @@ namespace Ragon.Common public int Lenght => _offset; public int Size => _size - _offset; - public RagonSerializer(int capacity = 2048) + public RagonSerializer(int capacity = 256) { _data = new byte[capacity]; _offset = 0; @@ -31,7 +31,7 @@ namespace Ragon.Common } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ReadByte() + public byte ReadByte() { var value = _data[_offset]; _offset += 1; @@ -171,6 +171,15 @@ namespace Ragon.Common _size = data.Length; } + public void FromArray(byte[] data) + { + Clear(); + ResizeIfNeed(data.Length); + Buffer.BlockCopy(data, 0, _data, 0, _offset); + _size = data.Length; + } + + public byte[] ToArray() { var bytes = new byte[_offset]; @@ -183,7 +192,7 @@ namespace Ragon.Common if (_offset + lenght < _data.Length) return; - var newData = new byte[_data.Length * 2 + lenght]; + var newData = new byte[_data.Length * 4 + lenght]; Buffer.BlockCopy(_data, 0, newData, 0, _data.Length); _data = newData; } diff --git a/SimpleServer/NLog.config b/Ragon.SimpleServer/NLog.config similarity index 100% rename from SimpleServer/NLog.config rename to Ragon.SimpleServer/NLog.config diff --git a/SimpleServer/Program.cs b/Ragon.SimpleServer/Program.cs similarity index 66% rename from SimpleServer/Program.cs rename to Ragon.SimpleServer/Program.cs index aebd55e..36173e6 100755 --- a/SimpleServer/Program.cs +++ b/Ragon.SimpleServer/Program.cs @@ -9,9 +9,10 @@ namespace SimpleServer static void Main(string[] args) { var bootstrap = new Bootstrap(); - bootstrap.Configure(new SimplePluginFactory()); - + var app = bootstrap.Configure(new SimplePluginFactory()); + app.Start(); Console.Read(); + app.Stop(); } } } \ No newline at end of file diff --git a/SimpleServer/SimpleServer.csproj b/Ragon.SimpleServer/Ragon.SimpleServer.csproj similarity index 90% rename from SimpleServer/SimpleServer.csproj rename to Ragon.SimpleServer/Ragon.SimpleServer.csproj index cd51211..eefcee0 100755 --- a/SimpleServer/SimpleServer.csproj +++ b/Ragon.SimpleServer/Ragon.SimpleServer.csproj @@ -23,8 +23,4 @@ - - - - diff --git a/Ragon.SimpleServer/Source/AuthorizationProviderByKey.cs b/Ragon.SimpleServer/Source/AuthorizationProviderByKey.cs new file mode 100644 index 0000000..cb1bcf1 --- /dev/null +++ b/Ragon.SimpleServer/Source/AuthorizationProviderByKey.cs @@ -0,0 +1,29 @@ +using System; +using System.Threading.Tasks; +using Ragon.Core; + +namespace Game.Source; + +public class AuthorizationProviderByKey: IAuthorizationProvider +{ + private Configuration _configuration; + public AuthorizationProviderByKey(Configuration configuration) + { + _configuration = configuration; + } + + public async Task OnAuthorizationRequest(string key, string name, byte protocol, byte[] additionalData, Action accept, Action reject) + { + if (key == _configuration.Key) + { + var playerId = Guid.NewGuid().ToString(); + var playerName = name; + + accept(playerId, playerName); + } + else + { + reject(0); + } + } +} \ No newline at end of file diff --git a/SimpleServer/Source/Plugins/SimplePlugin.cs b/Ragon.SimpleServer/Source/Plugins/SimplePlugin.cs similarity index 62% rename from SimpleServer/Source/Plugins/SimplePlugin.cs rename to Ragon.SimpleServer/Source/Plugins/SimplePlugin.cs index caabe32..742b237 100755 --- a/SimpleServer/Source/Plugins/SimplePlugin.cs +++ b/Ragon.SimpleServer/Source/Plugins/SimplePlugin.cs @@ -1,8 +1,4 @@ -using System.Runtime.InteropServices; -using Game.Source.Events; -using NLog; -using Ragon.Common; -using Ragon.Core; +using Ragon.Core; namespace Game.Source { @@ -20,12 +16,13 @@ namespace Game.Source public override void OnPlayerJoined(Player player) { - _logger.Info("Player joined " + player.PlayerName); + + _logger.Info($"Player({player.PlayerName}) joined to Room({GameRoom.Id})"); } public override void OnPlayerLeaved(Player player) { - _logger.Info("Player leaved " + player.PlayerName); + _logger.Info($"Player({player.PlayerName}) left from Room({GameRoom.Id})"); } } } \ No newline at end of file diff --git a/SimpleServer/Source/SimplePluginFactory.cs b/Ragon.SimpleServer/Source/SimplePluginFactory.cs similarity index 55% rename from SimpleServer/Source/SimplePluginFactory.cs rename to Ragon.SimpleServer/Source/SimplePluginFactory.cs index ed1281f..a0131db 100644 --- a/SimpleServer/Source/SimplePluginFactory.cs +++ b/Ragon.SimpleServer/Source/SimplePluginFactory.cs @@ -5,16 +5,14 @@ namespace Game.Source { public class SimplePluginFactory : PluginFactory { - public string PluginName { get; set; } = "SimplePlugin"; public PluginBase CreatePlugin(string map) { - return new SimplePlugin(); } - - public AuthorizationManager CreateManager(Configuration configuration) + + public IAuthorizationProvider CreateAuthorizationProvider(Configuration configuration) { - return new AuthorizerByKey(configuration); + return new AuthorizationProviderByKey(configuration); } } } \ No newline at end of file diff --git a/Ragon.SimpleServer/config.json b/Ragon.SimpleServer/config.json new file mode 100755 index 0000000..ab66704 --- /dev/null +++ b/Ragon.SimpleServer/config.json @@ -0,0 +1,8 @@ +{ + "key": "defaultkey", + "tickRate": 30, + "skipTimeout": 60, + "server": { + "port": 4444 + } +} \ No newline at end of file diff --git a/Ragon.Stress/Ragon.Stress.csproj b/Ragon.Stress/Ragon.Stress.csproj new file mode 100644 index 0000000..b990a0f --- /dev/null +++ b/Ragon.Stress/Ragon.Stress.csproj @@ -0,0 +1,19 @@ + + + + Exe + net6.0 + enable + enable + StressTest + + + + + + + + + + + diff --git a/Ragon.sln b/Ragon.sln index 6f2e392..c5909d8 100755 --- a/Ragon.sln +++ b/Ragon.sln @@ -2,10 +2,12 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon", "Ragon\Ragon.csproj", "{BABA1AF0-CF91-43F2-9577-53800068ACCF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleServer", "SimpleServer\SimpleServer.csproj", "{C2B87ADE-A122-4366-8EB7-24DAE11EBAF0}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.SimpleServer", "Ragon.SimpleServer\Ragon.SimpleServer.csproj", "{C2B87ADE-A122-4366-8EB7-24DAE11EBAF0}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Common", "Ragon.Common\Ragon.Common.csproj", "{F478B2A2-36F4-43B9-9BB7-382A57C449B2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Stress", "Ragon.Stress\Ragon.Stress.csproj", "{45E4C6A4-6AB5-4BEA-82DD-1F75C1648EC4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -24,5 +26,9 @@ 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 EndGlobalSection EndGlobal diff --git a/Ragon/Ragon.csproj b/Ragon/Ragon.csproj index 79a3555..f9e263f 100755 --- a/Ragon/Ragon.csproj +++ b/Ragon/Ragon.csproj @@ -18,7 +18,7 @@ - + diff --git a/Ragon/Sources/Application.cs b/Ragon/Sources/Application.cs index 1a84066..0736cdd 100755 --- a/Ragon/Sources/Application.cs +++ b/Ragon/Sources/Application.cs @@ -2,97 +2,35 @@ using System.Collections.Generic; using System.Linq; using System.Threading; +using ENet; using NLog; namespace Ragon.Core { - public class Application : IDisposable + public class Application { private readonly Logger _logger = LogManager.GetCurrentClassLogger(); - private readonly List _roomThreads = new(); - private readonly Dictionary _socketByRoomThreads = new(); - private readonly Dictionary _roomThreadCounter = new(); - private readonly Configuration _configuration; - private readonly ENetServer _socketServer; - private int _roomThreadBalancer = 0; - - private Thread _thread; - - public Application(PluginFactory factory, Configuration configuration, int threadsCount) + private readonly GameThread _gameThread; + + public Application(PluginFactory factory, Configuration configuration) { - _socketServer = new ENetServer(); - _configuration = configuration; + ThreadPool.SetMinThreads(1, 1); - for (var i = 0; i < threadsCount; i++) - { - var roomThread = new RoomThread(factory, configuration); - _roomThreadCounter.Add(roomThread, 0); - _roomThreads.Add(roomThread); - } - } - - private void Loop() - { - while (true) - { - foreach (var roomThread in _roomThreads) - while (roomThread.ReadOutEvent(out var evnt)) - _socketServer.WriteEvent(evnt); - - while (_socketServer.ReadEvent(out var evnt)) - { - if (evnt.Type == EventType.CONNECTED) - { - if (_roomThreadBalancer >= _roomThreads.Count) - _roomThreadBalancer = 0; - - var roomThread = _roomThreads[_roomThreadBalancer]; - _roomThreadCounter[roomThread] += 1; - _socketByRoomThreads.Add(evnt.PeerId, roomThread); - - // TODO: Todo room manager matchmaking across all room threads - // TEMP_FIX: Remove this magical number - if (_roomThreadCounter[roomThread] > 20) - _roomThreadBalancer++; - } - - if (_socketByRoomThreads.TryGetValue(evnt.PeerId, out var existsRoomThread)) - existsRoomThread.WriteInEvent(evnt); - - if (evnt.Type == EventType.DISCONNECTED) - { - _socketByRoomThreads.Remove(evnt.PeerId, out var roomThread); - _roomThreadCounter[roomThread] =- 1; - } - - if (evnt.Type == EventType.TIMEOUT) - { - _socketByRoomThreads.Remove(evnt.PeerId, out var roomThread); - _roomThreadCounter[roomThread] =- 1; - } - } - - Thread.Sleep(1); - } + _gameThread = new GameThread(factory, configuration); } public void Start() { - _socketServer.Start(_configuration.Server.Port); + Library.Initialize(); - foreach (var roomThread in _roomThreads) - roomThread.Start(); - - _thread = new Thread(Loop); - _thread.Start(); + _gameThread.Start(); } - - public void Dispose() + + public void Stop() { - foreach (var roomThread in _roomThreads) - roomThread.Dispose(); + _gameThread.Stop(); - _roomThreads.Clear(); + Library.Deinitialize(); } } } \ No newline at end of file diff --git a/Ragon/Sources/Authorization/AuthorizationManager.cs b/Ragon/Sources/Authorization/AuthorizationManager.cs new file mode 100644 index 0000000..fbcd2d8 --- /dev/null +++ b/Ragon/Sources/Authorization/AuthorizationManager.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using Ragon.Common; + +namespace Ragon.Core; + +public class AuthorizationManager : IAuthorizationManager +{ + private IAuthorizationProvider _provider; + private IGameThread _gameThread; + private Lobby _lobby; + private RagonSerializer _serializer; + private readonly Dictionary _playersByPeers; + private readonly Dictionary _playersByIds; + + public AuthorizationManager(IAuthorizationProvider provider, IGameThread gameThread, Lobby lobby, RagonSerializer serializer) + { + _serializer = serializer; + _lobby = lobby; + _provider = provider; + _gameThread = gameThread; + _playersByIds = new Dictionary(); + _playersByPeers = new Dictionary(); + } + + public void OnAuthorization(uint peerId, string key, string name, byte protocol) + { + var dispatcher = _gameThread.GetDispatcher(); + _provider.OnAuthorizationRequest(key, name, protocol, Array.Empty(), + (playerId, playerName) => { dispatcher.Dispatch(() => Accepted(peerId, playerId, playerName)); }, + (errorCode) => { dispatcher.Dispatch(() => Rejected(peerId, errorCode)); }); + } + + public void Accepted(uint 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.SendSocketEvent(new SocketEvent() {Data = sendData, PeerId = peerId, Type = EventType.DATA, Delivery = DeliveryType.Reliable}); + } + + public void Rejected(uint peerId, uint code) + { + _serializer.Clear(); + _serializer.WriteOperation(RagonOperation.AUTHORIZED_FAILED); + _serializer.WriteInt((int) code); + + var sendData = _serializer.ToArray(); + _gameThread.SendSocketEvent(new SocketEvent() {Data = sendData, PeerId = peerId, Type = EventType.DATA, Delivery = DeliveryType.Reliable}); + var emtpyData = Array.Empty(); + _gameThread.SendSocketEvent(new SocketEvent() {Data = emtpyData, PeerId = peerId, Type = EventType.DISCONNECTED, Delivery = DeliveryType.Reliable}); + } + + public void Cleanup(uint peerId) + { + if (_playersByPeers.Remove(peerId, out var player)) + _playersByIds.Remove(player.Id); + } + + public Player GetPlayer(uint peerId) + { + return _playersByPeers[peerId]; + } + + public Player GetPlayer(string playerId) + { + return _playersByIds[playerId]; + } +} \ No newline at end of file diff --git a/Ragon/Sources/Authorization/IAuthorizationManager.cs b/Ragon/Sources/Authorization/IAuthorizationManager.cs new file mode 100644 index 0000000..f0371b1 --- /dev/null +++ b/Ragon/Sources/Authorization/IAuthorizationManager.cs @@ -0,0 +1,6 @@ +namespace Ragon.Core; + +public interface IAuthorizationManager +{ + +} \ No newline at end of file diff --git a/Ragon/Sources/Authorization/IAuthorizationProvider.cs b/Ragon/Sources/Authorization/IAuthorizationProvider.cs new file mode 100644 index 0000000..bd3d3d3 --- /dev/null +++ b/Ragon/Sources/Authorization/IAuthorizationProvider.cs @@ -0,0 +1,9 @@ +using System; +using System.Threading.Tasks; + +namespace Ragon.Core; + +public interface IAuthorizationProvider +{ + Task OnAuthorizationRequest(string key, string playerName, byte protocol, byte[] additionalData, Action Accept, Action Reject); +} \ No newline at end of file diff --git a/Ragon/Sources/AuthorizationManager.cs b/Ragon/Sources/AuthorizationManager.cs deleted file mode 100644 index 4cb5f6d..0000000 --- a/Ragon/Sources/AuthorizationManager.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace Ragon.Core; - -public class AuthorizationManager -{ - public virtual bool OnAuthorize(uint peerId, ref ReadOnlySpan payload) - { - return true; - } -} \ No newline at end of file diff --git a/Ragon/Sources/Bootstrap.cs b/Ragon/Sources/Bootstrap.cs index a060bbf..0e7cb2f 100755 --- a/Ragon/Sources/Bootstrap.cs +++ b/Ragon/Sources/Bootstrap.cs @@ -8,13 +8,13 @@ namespace Ragon.Core { private ILogger _logger = LogManager.GetCurrentClassLogger(); - public void Configure(PluginFactory factory) + public Application Configure(PluginFactory factory) { _logger.Info("Configure application..."); var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config.json"); var configuration = ConfigurationLoader.Load(filePath); - var app = new Application(factory, configuration, 2); - app.Start(); + var app = new Application(factory, configuration); + return app; } } } \ No newline at end of file diff --git a/Ragon/Sources/Configuration/Configuration.cs b/Ragon/Sources/Configuration/Configuration.cs index ee48ce6..03afbb1 100755 --- a/Ragon/Sources/Configuration/Configuration.cs +++ b/Ragon/Sources/Configuration/Configuration.cs @@ -6,13 +6,13 @@ namespace Ragon.Core public struct Server { public ushort Port; - public ushort TickRate; } [Serializable] public struct Configuration { public string Key; + public ushort TickRate; public Server Server; } } \ No newline at end of file diff --git a/Ragon/Sources/Core/Communication/IReceiver.cs b/Ragon/Sources/Core/Communication/IReceiver.cs new file mode 100644 index 0000000..8026716 --- /dev/null +++ b/Ragon/Sources/Core/Communication/IReceiver.cs @@ -0,0 +1,6 @@ +namespace Ragon.Core; + +public interface Receiver +{ + public bool Receive(out T data); +} \ No newline at end of file diff --git a/Ragon/Sources/Core/Communication/ISender.cs b/Ragon/Sources/Core/Communication/ISender.cs new file mode 100644 index 0000000..2232a8e --- /dev/null +++ b/Ragon/Sources/Core/Communication/ISender.cs @@ -0,0 +1,6 @@ +namespace Ragon.Core; + +public interface ISender +{ + public void Send(T data); +} \ No newline at end of file diff --git a/Ragon/Sources/Core/IO/Dispatcher.cs b/Ragon/Sources/Core/IO/Dispatcher.cs new file mode 100644 index 0000000..910cebf --- /dev/null +++ b/Ragon/Sources/Core/IO/Dispatcher.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace Ragon.Core; + +public class Dispatcher: IDispatcher +{ + public Queue _actions = new Queue(); + public void Dispatch(Action action) + { + lock (_actions) + _actions.Enqueue(new DispatcherTask() { Action = action }); + } + + public void Process() + { + lock(_actions) + while(_actions.TryDequeue(out var action)) + action.Execute(); + } +} \ No newline at end of file diff --git a/Ragon/Sources/Core/IO/DispatcherTask.cs b/Ragon/Sources/Core/IO/DispatcherTask.cs new file mode 100644 index 0000000..71d7f20 --- /dev/null +++ b/Ragon/Sources/Core/IO/DispatcherTask.cs @@ -0,0 +1,15 @@ +using System; +using System.Diagnostics; + +namespace Ragon.Core; + +public class DispatcherTask +{ + public Action Action; + public Action Callback; + + public void Execute() + { + Action?.Invoke(); + } +} \ No newline at end of file diff --git a/Ragon/Sources/Core/IO/IDispatcher.cs b/Ragon/Sources/Core/IO/IDispatcher.cs new file mode 100644 index 0000000..6fe63b9 --- /dev/null +++ b/Ragon/Sources/Core/IO/IDispatcher.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ragon.Core; + +public interface IDispatcher +{ + public void Dispatch(Action action); + public void Process(); +} \ No newline at end of file diff --git a/Ragon/Sources/Core/Utils/SynchronizedCache.cs b/Ragon/Sources/Core/Utils/SynchronizedCache.cs new file mode 100644 index 0000000..81c9943 --- /dev/null +++ b/Ragon/Sources/Core/Utils/SynchronizedCache.cs @@ -0,0 +1,130 @@ +using System.Collections.Generic; +using System.Threading; + +namespace Ragon.Core.Core.Utils; + +public class SynchronizedCache +{ + private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim(); + private Dictionary innerCache = new Dictionary(); + + public int Count + { get { return innerCache.Count; } } + + public string Read(int key) + { + cacheLock.EnterReadLock(); + try + { + return innerCache[key]; + } + finally + { + cacheLock.ExitReadLock(); + } + } + + public void Add(int key, string value) + { + cacheLock.EnterWriteLock(); + try + { + innerCache.Add(key, value); + } + finally + { + cacheLock.ExitWriteLock(); + } + } + + public bool AddWithTimeout(int key, string value, int timeout) + { + if (cacheLock.TryEnterWriteLock(timeout)) + { + try + { + innerCache.Add(key, value); + } + finally + { + cacheLock.ExitWriteLock(); + } + return true; + } + else + { + return false; + } + } + + public AddOrUpdateStatus AddOrUpdate(int key, string value) + { + cacheLock.EnterUpgradeableReadLock(); + try + { + string result = null; + if (innerCache.TryGetValue(key, out result)) + { + if (result == value) + { + return AddOrUpdateStatus.Unchanged; + } + else + { + cacheLock.EnterWriteLock(); + try + { + innerCache[key] = value; + } + finally + { + cacheLock.ExitWriteLock(); + } + return AddOrUpdateStatus.Updated; + } + } + else + { + cacheLock.EnterWriteLock(); + try + { + innerCache.Add(key, value); + } + finally + { + cacheLock.ExitWriteLock(); + } + return AddOrUpdateStatus.Added; + } + } + finally + { + cacheLock.ExitUpgradeableReadLock(); + } + } + + public void Delete(int key) + { + cacheLock.EnterWriteLock(); + try + { + innerCache.Remove(key); + } + finally + { + cacheLock.ExitWriteLock(); + } + } + + public enum AddOrUpdateStatus + { + Added, + Updated, + Unchanged + }; + + ~SynchronizedCache() + { + if (cacheLock != null) cacheLock.Dispose(); + } +} \ No newline at end of file diff --git a/Ragon/Sources/Event/EventStream.cs b/Ragon/Sources/Event/EventStream.cs deleted file mode 100644 index 9b376d5..0000000 --- a/Ragon/Sources/Event/EventStream.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Collections.Generic; -using System.Runtime.ExceptionServices; -using DisruptorUnity3d; - -namespace Ragon.Core; - -public class EventStream -{ - private Stack _pool = new Stack(1024); - private RingBuffer _events = new RingBuffer(1024); - - // public ref Event Reserve() - // { - // if (_pool.Count == 0) - // { - // var evnt = new Event(); - // return ref evnt; - // } - // // var evnt = _pool.Pop(); - // // ref Event evntRef = ref evnt; - // // return evntRef; - // } - - // public void Retain(ref Event @event) - // { - // - // } - // - // public void WriteEvent(ref Event evnt) - // { - // // _pool.Push(evnt); - // _events.Enqueue(evnt); - // } - // - // public ref Event ReadEvent() - // { - // - // } -} \ No newline at end of file diff --git a/Ragon/Sources/Event/Event.cs b/Ragon/Sources/Event/SocketEvent.cs similarity index 84% rename from Ragon/Sources/Event/Event.cs rename to Ragon/Sources/Event/SocketEvent.cs index d275c7e..040f6c9 100644 --- a/Ragon/Sources/Event/Event.cs +++ b/Ragon/Sources/Event/SocketEvent.cs @@ -2,7 +2,7 @@ using System; namespace Ragon.Core { - public struct Event + public struct SocketEvent { public EventType Type; public DeliveryType Delivery; diff --git a/Ragon/Sources/Event/EventType.cs b/Ragon/Sources/Event/SocketEventType.cs similarity index 100% rename from Ragon/Sources/Event/EventType.cs rename to Ragon/Sources/Event/SocketEventType.cs diff --git a/Ragon/Sources/Rooms/Room.cs b/Ragon/Sources/Game/GameRoom.cs similarity index 91% rename from Ragon/Sources/Rooms/Room.cs rename to Ragon/Sources/Game/GameRoom.cs index 70bb9df..7a63621 100755 --- a/Ragon/Sources/Rooms/Room.cs +++ b/Ragon/Sources/Game/GameRoom.cs @@ -1,13 +1,13 @@ using System; using System.Collections.Generic; -using System.Diagnostics; + using System.Linq; using NLog; using Ragon.Common; namespace Ragon.Core { - public class Room : IDisposable + public class GameRoom : IGameRoom { public int PlayersMin { get; private set; } public int PlayersMax { get; private set; } @@ -22,21 +22,17 @@ namespace Ragon.Core private uint _ticks; private readonly PluginBase _plugin; - private readonly RoomThread _roomThread; + private readonly IGameThread _sender; private readonly RagonSerializer _serializer = new(512); // Cache private uint[] _readyPlayers = Array.Empty(); private uint[] _allPlayers = Array.Empty(); private Entity[] _entitiesAll = Array.Empty(); - - public Player GetPlayerById(uint peerId) => _players[peerId]; - public Entity GetEntityById(int entityId) => _entities[entityId]; - public Player GetOwner() => _players[_owner]; - - public Room(RoomThread roomThread, PluginBase pluginBase, string map, int min, int max) + + public GameRoom(IGameThread sender, PluginBase pluginBase, string map, int min, int max) { - _roomThread = roomThread; + _sender = sender; _plugin = pluginBase; Map = map; @@ -48,22 +44,12 @@ namespace Ragon.Core _plugin.Attach(this); } - public void Joined(uint peerId, ReadOnlySpan payload) + public void Joined(Player player, ReadOnlySpan payload) { if (_players.Count == 0) { - _owner = peerId; + _owner = player.PeerId; } - - var player = new Player() - { - Id = Guid.NewGuid().ToString(), - PlayerName = "Player " + peerId, - PeerId = peerId, - IsLoaded = false, - Entities = new List(), - EntitiesIds = new List(), - }; { _serializer.Clear(); @@ -76,7 +62,7 @@ namespace Ragon.Core Broadcast(_readyPlayers, sendData, DeliveryType.Reliable); } - _players.Add(peerId, player); + _players.Add(player.PeerId, player); _allPlayers = _players.Select(p => p.Key).ToArray(); { @@ -89,7 +75,7 @@ namespace Ragon.Core _serializer.WriteUShort((ushort) PlayersMax); var sendData = _serializer.ToArray(); - Send(peerId, sendData, DeliveryType.Reliable); + Send(player.PeerId, sendData, DeliveryType.Reliable); } { @@ -98,7 +84,7 @@ namespace Ragon.Core _serializer.WriteString(Map); var sendData = _serializer.ToArray(); - Send(peerId, sendData, DeliveryType.Reliable); + Send(player.PeerId, sendData, DeliveryType.Reliable); } } @@ -145,14 +131,19 @@ namespace Ragon.Core Broadcast(_readyPlayers, sendData); } } + + _entitiesAll = _entities.Values.ToArray(); } } - public void ProcessEvent(RagonOperation operation, uint peerId, ReadOnlySpan rawData) + public void ProcessEvent(uint peerId, ReadOnlySpan rawData) { + var operation = (RagonOperation) rawData[0]; + var payloadRawData = rawData.Slice(1, rawData.Length - 1); + _serializer.Clear(); - _serializer.FromSpan(ref rawData); - + _serializer.FromSpan(ref payloadRawData); + switch (operation) { case RagonOperation.REPLICATE_ENTITY_STATE: @@ -300,19 +291,20 @@ namespace Ragon.Core _serializer.WriteUShort((ushort) playerPeerId); _serializer.WriteString(_players[playerPeerId].PlayerName); } - + _serializer.WriteInt(_entitiesAll.Length); foreach (var entity in _entitiesAll) { + var payload = entity.Payload.Read(); + var state = entity.State.Read(); + _serializer.WriteInt(entity.EntityId); _serializer.WriteByte((byte) entity.State.Authority); _serializer.WriteByte((byte) entity.Authority); _serializer.WriteUShort(entity.EntityType); _serializer.WriteUShort((ushort) entity.OwnerId); - var payload = entity.Payload.Read(); _serializer.WriteUShort((ushort) payload.Length); _serializer.WriteData(ref payload); - var state = entity.State.Read(); _serializer.WriteUShort((ushort) state.Length); _serializer.WriteData(ref state); } @@ -328,7 +320,7 @@ namespace Ragon.Core } } } - + public void Tick(float deltaTime) { _ticks++; @@ -363,18 +355,19 @@ namespace Ragon.Core { _logger.Info("Room stopped"); _plugin.OnStop(); + + _plugin.Detach(); } + public Player GetPlayerById(uint peerId) => _players[peerId]; - public void Dispose() - { - _logger.Info("Room destroyed"); - _plugin.Dispose(); - } - + public Entity GetEntityById(int entityId) => _entities[entityId]; + + public Player GetOwner() => _players[_owner]; + public void Send(uint peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable) { - _roomThread.WriteOutEvent(new Event() + _sender.SendSocketEvent(new SocketEvent() { PeerId = peerId, Data = rawData, @@ -387,7 +380,7 @@ namespace Ragon.Core { foreach (var peer in peersIds) { - _roomThread.WriteOutEvent(new Event() + _sender.SendSocketEvent(new SocketEvent() { PeerId = peer, Data = rawData, @@ -401,7 +394,7 @@ namespace Ragon.Core { foreach (var player in _players.Values.ToArray()) { - _roomThread.WriteOutEvent(new Event() + _sender.SendSocketEvent(new SocketEvent() { PeerId = player.PeerId, Data = rawData, diff --git a/Ragon/Sources/Game/GameThread.cs b/Ragon/Sources/Game/GameThread.cs new file mode 100755 index 0000000..096bf9b --- /dev/null +++ b/Ragon/Sources/Game/GameThread.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using NLog; + +namespace Ragon.Core +{ + public class GameThread : IGameThread + { + private readonly Dictionary _socketByRooms; + private readonly RoomManager _roomManager; + private readonly ENetServer _socketServer; + private readonly Thread _thread; + private readonly Server _serverConfiguration; + private readonly Stopwatch _gameLoopTimer; + private readonly Lobby _lobby; + private readonly IDispatcher _dispatcher; + private readonly ILogger _logger = LogManager.GetCurrentClassLogger(); + private readonly float _deltaTime = 0.0f; + + private int _packets = 0; + private readonly Stopwatch _packetsTimer; + public GameThread(PluginFactory factory, Configuration configuration) + { + var authorizationProvider = factory.CreateAuthorizationProvider(configuration); + + _serverConfiguration = configuration.Server; + _deltaTime = 1000.0f / configuration.TickRate; + + _dispatcher = new Dispatcher(); + _roomManager = new RoomManager(factory, this); + _lobby = new Lobby(authorizationProvider, _roomManager, this); + _socketServer = new ENetServer(); + _gameLoopTimer = new Stopwatch(); + _packetsTimer = new Stopwatch(); + _socketByRooms = new Dictionary(); + + _thread = new Thread(Execute); + _thread.Name = "Game Thread"; + _thread.IsBackground = true; + } + + public void Start() + { + _gameLoopTimer.Start(); + _packetsTimer.Start(); + + _socketServer.Start(_serverConfiguration.Port); + _thread.Start(); + } + + public void Stop() + { + _gameLoopTimer.Stop(); + _packetsTimer.Stop(); + _socketServer.Stop(); + _thread.Interrupt(); + } + + private void Execute() + { + while (true) + { + _dispatcher.Process(); + + while (_socketServer.ReceiveBuffer.TryDequeue(out var evnt)) + { + if (evnt.Type == EventType.DISCONNECTED || evnt.Type == EventType.TIMEOUT) + { + if (_socketByRooms.Remove(evnt.PeerId, out var room)) + room.Leave(evnt.PeerId); + + _lobby.OnDisconnected(evnt.PeerId); + } + + if (evnt.Type == EventType.DATA) + { + _packets += 1; + try + { + var peerId = evnt.PeerId; + var data = new ReadOnlySpan(evnt.Data); + if (_socketByRooms.TryGetValue(evnt.PeerId, out var room)) + { + room.ProcessEvent(peerId, data); + } + else + { + _lobby.ProcessEvent(peerId, data); + } + } + catch (Exception exception) + { + _logger.Error(exception); + } + } + } + + var elapsedMilliseconds = _gameLoopTimer.ElapsedMilliseconds; + if (elapsedMilliseconds > _deltaTime) + { + _roomManager.Tick(elapsedMilliseconds / 1000.0f); + _gameLoopTimer.Restart(); + continue; + } + + if (_packetsTimer.Elapsed.Seconds > 1) + { + _logger.Trace($"Clients: {_socketByRooms.Keys.Count} Packets: {_packets} per sec"); + _packetsTimer.Restart(); + _packets = 0; + } + Thread.Sleep(15); + } + } + + public void Attach(uint peerId, GameRoom room) + { + _socketByRooms.Add(peerId, room); + } + + public void Detach(uint peerId) + { + _socketByRooms.Remove(peerId); + } + + public void SendSocketEvent(SocketEvent socketEvent) + { + _socketServer.SendBuffer.Enqueue(socketEvent); + } + + public IDispatcher GetDispatcher() + { + return _dispatcher; + } + } +} \ No newline at end of file diff --git a/Ragon/Sources/Game/IGameRoom.cs b/Ragon/Sources/Game/IGameRoom.cs new file mode 100644 index 0000000..7e8ecab --- /dev/null +++ b/Ragon/Sources/Game/IGameRoom.cs @@ -0,0 +1,18 @@ +namespace Ragon.Core; + +public interface IGameRoom +{ + public string Id { get; } + public string Map { get; } + public int PlayersMin { get; } + public int PlayersMax { get; } + public int PlayersCount { get; } + + public Player GetPlayerById(uint peerId); + public Entity GetEntityById(int entityId); + public Player GetOwner(); + + public void Send(uint peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable); + public void Broadcast(uint[] peersIds, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable); + public void Broadcast(byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable); +} \ No newline at end of file diff --git a/Ragon/Sources/Game/IGameThread.cs b/Ragon/Sources/Game/IGameThread.cs new file mode 100644 index 0000000..5368a7f --- /dev/null +++ b/Ragon/Sources/Game/IGameThread.cs @@ -0,0 +1,11 @@ +using Ragon.Common; + +namespace Ragon.Core; + +public interface IGameThread +{ + public void Attach(uint peerId, GameRoom room); + public void Detach(uint peerId); + public void SendSocketEvent(SocketEvent socketEvent); + public IDispatcher GetDispatcher(); +} \ No newline at end of file diff --git a/Ragon/Sources/Lobby/Lobby.cs b/Ragon/Sources/Lobby/Lobby.cs new file mode 100644 index 0000000..703f92f --- /dev/null +++ b/Ragon/Sources/Lobby/Lobby.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using Ragon.Common; +namespace Ragon.Core; + +public class Lobby +{ + private readonly RagonSerializer _serializer; + private readonly AuthorizationManager _authorizationManager; + private readonly RoomManager _roomManager; + + public Lobby(IAuthorizationProvider provider, RoomManager manager, IGameThread gameThread) + { + _roomManager = manager; + _serializer = new RagonSerializer(); + _authorizationManager = new AuthorizationManager(provider, gameThread, this, _serializer); + } + + public void ProcessEvent(uint peerId, ReadOnlySpan data) + { + var op = (RagonOperation) data[0]; + var payload = data.Slice(1, data.Length - 1); + + _serializer.Clear(); + _serializer.FromSpan(ref payload); + + 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); + _roomManager.Join(player, roomId, Array.Empty()); + break; + } + case RagonOperation.JOIN_OR_CREATE_ROOM: + { + var map = _serializer.ReadString(); + var min = _serializer.ReadInt(); + var max = _serializer.ReadInt(); + var player = _authorizationManager.GetPlayer(peerId); + _roomManager.JoinOrCreate(player, map, min, max, Array.Empty()); + break; + } + case RagonOperation.LEAVE_ROOM: + { + var player = _authorizationManager.GetPlayer(peerId); + _roomManager.Left(player, Array.Empty()); + break; + } + } + } + + public void OnDisconnected(uint peerId) + { + _authorizationManager.Cleanup(peerId); + } +} \ No newline at end of file diff --git a/Ragon/Sources/Matchmaking/Matchmaking.cs b/Ragon/Sources/Matchmaking/Matchmaking.cs new file mode 100644 index 0000000..9f8f47d --- /dev/null +++ b/Ragon/Sources/Matchmaking/Matchmaking.cs @@ -0,0 +1,77 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NLog; +using Ragon.Common; + +namespace Ragon.Core; + +public class RoomManager +{ + private readonly IGameThread _gameThread; + private readonly PluginFactory _factory; + private readonly Logger _logger = LogManager.GetCurrentClassLogger(); + private List _rooms = new List(); + + public RoomManager(PluginFactory factory, IGameThread gameThread) + { + _gameThread = gameThread; + _factory = factory; + } + + public void Join(Player player, string roomId, byte[] payload) + { + if (_rooms.Count > 0) + { + foreach (var existRoom in _rooms) + { + if (existRoom.Id == roomId && existRoom.PlayersCount < existRoom.PlayersMax) + { + existRoom.Joined(player, payload); + _gameThread.Attach(player.PeerId, existRoom); + break; + } + } + } + } + + public void JoinOrCreate(Player player, string map, int min, int max, byte[] payload) + { + if (_rooms.Count > 0) + { + foreach (var existRoom in _rooms) + { + if (existRoom.Map == map && existRoom.PlayersCount < existRoom.PlayersMax) + { + existRoom.Joined(player, payload); + _gameThread.Attach(player.PeerId, existRoom); + + return; + } + } + } + + 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(); + + _gameThread.Attach(player.PeerId, room); + _rooms.Add(room); + } + + public void Left(Player player, byte[] payload) + { + + } + + public void Tick(float deltaTime) + { + foreach (var gameRoom in _rooms) + gameRoom.Tick(deltaTime); + } +} \ No newline at end of file diff --git a/Ragon/Sources/Plugin/PluginBase.cs b/Ragon/Sources/Plugin/PluginBase.cs index a1b2e39..e4d9033 100755 --- a/Ragon/Sources/Plugin/PluginBase.cs +++ b/Ragon/Sources/Plugin/PluginBase.cs @@ -7,7 +7,7 @@ using Ragon.Common; namespace Ragon.Core { - public class PluginBase : IDisposable + public class PluginBase { private delegate void SubscribeDelegate(Player player, ref ReadOnlySpan data); @@ -18,20 +18,20 @@ namespace Ragon.Core private readonly BitBuffer _buffer = new(); private readonly RagonSerializer _serializer = new(); - protected Room Room { get; private set; } + protected IGameRoom GameRoom { get; private set; } protected ILogger _logger; - public void Attach(Room room) + public void Attach(GameRoom gameRoom) { _logger = LogManager.GetLogger($"Plugin<{GetType().Name}>"); - Room = room; + GameRoom = gameRoom; _globalEvents.Clear(); _entityEvents.Clear(); } - public void Dispose() + public void Detach() { _globalEvents.Clear(); _entityEvents.Clear(); @@ -153,8 +153,8 @@ namespace Ragon.Core if (!_entityEvents[entityId].ContainsKey(evntCode)) return false; - var player = Room.GetPlayerById(peerId); - var entity = Room.GetEntityById(entityId); + var player = GameRoom.GetPlayerById(peerId); + var entity = GameRoom.GetEntityById(entityId); _entityEvents[entityId][evntCode].Invoke(player, entity, ref payload); return true; @@ -164,7 +164,7 @@ namespace Ragon.Core { if (_globalEvents.ContainsKey(evntCode)) { - var player = Room.GetPlayerById(peerId); + var player = GameRoom.GetPlayerById(peerId); _globalEvents[evntCode].Invoke(player, ref payload); return true; } @@ -184,7 +184,7 @@ namespace Ragon.Core _buffer.ToSpan(ref payloadData); var sendData = _serializer.ToArray(); - Room.Send(player.PeerId, sendData); + GameRoom.Send(player.PeerId, sendData); } public void BroadcastEvent(ushort eventCode, IRagonSerializable payload) @@ -199,7 +199,7 @@ namespace Ragon.Core _buffer.ToSpan(ref payloadData); var sendData = _serializer.ToArray(); - Room.Broadcast(sendData, DeliveryType.Reliable); + GameRoom.Broadcast(sendData, DeliveryType.Reliable); } public void SendEntityEvent(Player player, Entity entity, IRagonSerializable payload) @@ -215,7 +215,7 @@ namespace Ragon.Core _buffer.ToSpan(ref payloadData); var sendData = _serializer.ToArray(); - Room.Send(player.PeerId, sendData, DeliveryType.Reliable); + GameRoom.Send(player.PeerId, sendData, DeliveryType.Reliable); } public void BroadcastEntityEvent(Entity entity, IRagonSerializable payload) @@ -231,7 +231,7 @@ namespace Ragon.Core _buffer.ToSpan(ref payloadData); var sendData = _serializer.ToArray(); - Room.Broadcast(sendData); + GameRoom.Broadcast(sendData); } diff --git a/Ragon/Sources/Plugin/PluginFactory.cs b/Ragon/Sources/Plugin/PluginFactory.cs index faaa749..227569b 100644 --- a/Ragon/Sources/Plugin/PluginFactory.cs +++ b/Ragon/Sources/Plugin/PluginFactory.cs @@ -3,7 +3,7 @@ namespace Ragon.Core public interface PluginFactory { public PluginBase CreatePlugin(string map); - public AuthorizationManager CreateManager(Configuration configuration); + public IAuthorizationProvider CreateAuthorizationProvider(Configuration configuration); } diff --git a/Ragon/Sources/Rooms/RoomManager.cs b/Ragon/Sources/Rooms/RoomManager.cs deleted file mode 100644 index 77a7ef5..0000000 --- a/Ragon/Sources/Rooms/RoomManager.cs +++ /dev/null @@ -1,211 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using NLog; -using Ragon.Common; - -namespace Ragon.Core -{ - public class RoomManager - { - private readonly Logger _logger = LogManager.GetCurrentClassLogger(); - private List _rooms; - private Dictionary _peersByRoom; - private PluginFactory _factory; - private AuthorizationManager _manager; - private RoomThread _roomThread; - private RagonSerializer _serializer; - public Action<(uint, Room)> OnJoined; - public Action<(uint, Room)> OnLeaved; - - public RoomManager(RoomThread roomThread, PluginFactory factory) - { - _roomThread = roomThread; - _factory = factory; - - _serializer = new RagonSerializer(); - _manager = _factory.CreateManager(roomThread.Configuration); - _rooms = new List(); - _peersByRoom = new Dictionary(); - } - - public void ProcessEvent(RagonOperation operation, uint peerId, ReadOnlySpan payload) - { - switch (operation) - { - case RagonOperation.AUTHORIZE: - { - OnAuthorize(peerId, payload); - break; - } - case RagonOperation.JOIN_OR_CREATE_ROOM: - { - var room = JoinOrCreate(peerId, payload); - if (room == null) - { - var sendData = new[] {(byte) RagonOperation.JOIN_FAILED}; - _roomThread.WriteOutEvent(new Event() - { - Delivery = DeliveryType.Reliable, - Type = EventType.DATA, - Data = sendData, - PeerId = peerId, - }); - return; - } - OnJoined?.Invoke((peerId, room)); - break; - } - case RagonOperation.JOIN_ROOM: - { - var room = Join(peerId, payload); - if (room == null) - { - var sendData = new[] {(byte) RagonOperation.JOIN_FAILED}; - _roomThread.WriteOutEvent(new Event() - { - Delivery = DeliveryType.Reliable, - Type = EventType.DATA, - Data = sendData, - PeerId = peerId, - }); - - return; - } - OnJoined?.Invoke((peerId, room)); - break; - } - case RagonOperation.LEAVE_ROOM: - { - var room = Left(peerId, payload); - OnLeaved((peerId, room)); - break; - } - } - } - - public void OnAuthorize(uint peerId, ReadOnlySpan payload) - { - if (_manager.OnAuthorize(peerId, ref payload)) - { - var sendData = new[] {(byte) RagonOperation.AUTHORIZED_SUCCESS}; - _roomThread.WriteOutEvent(new Event() - { - Delivery = DeliveryType.Reliable, - Type = EventType.DATA, - Data = sendData, - PeerId = peerId, - }); - } - else - { - var sendData = new[] {(byte) RagonOperation.AUTHORIZED_FAILED}; - _roomThread.WriteOutEvent(new Event() - { - Delivery = DeliveryType.Reliable, - Type = EventType.DATA, - Data = sendData, - PeerId = peerId, - }); - _roomThread.WriteOutEvent(new Event() - { - Delivery = DeliveryType.Reliable, - Type = EventType.DISCONNECTED, - Data = Array.Empty(), - PeerId = peerId, - }); - } - } - - public Room? Join(uint peerId, ReadOnlySpan payload) - { - var roomId = Encoding.UTF8.GetString(payload); - - if (_rooms.Count > 0) - { - foreach (var existRoom in _rooms) - { - if (existRoom.Id == roomId && existRoom.PlayersCount < existRoom.PlayersMax) - { - existRoom.Joined(peerId, payload); - - _peersByRoom.Add(peerId, existRoom); - - return existRoom; - } - } - } - - return null; - } - - public Room? JoinOrCreate(uint peerId, ReadOnlySpan payload) - { - _serializer.Clear(); - _serializer.FromSpan(ref payload); - - var min = _serializer.ReadUShort(); - var max = _serializer.ReadUShort(); - var map = _serializer.ReadString(); - - Room room = null; - if (_rooms.Count > 0) - { - foreach (var existRoom in _rooms) - { - if (existRoom.Map == map && existRoom.PlayersCount < existRoom.PlayersMax) - { - room = existRoom; - room.Joined(peerId, payload); - - _peersByRoom.Add(peerId, room); - - return room; - } - } - } - - var plugin = _factory.CreatePlugin(map); - if (plugin == null) - throw new NullReferenceException($"Plugin for map {map} is null"); - - room = new Room(_roomThread, plugin, map, min, max); - room.Joined(peerId, payload); - room.Start(); - - _peersByRoom.Add(peerId, room); - _rooms.Add(room); - - return room; - } - - public Room Left(uint peerId, ReadOnlySpan payload) - { - _peersByRoom.Remove(peerId, out var room); - - return room; - } - - public void Disconnected(uint peerId) - { - _peersByRoom.Remove(peerId, out var room); - if (room != null) - { - room.Leave(peerId); - if (room.PlayersCount <= 0 && room.PlayersMin > 0) - { - _rooms.Remove(room); - - room.Stop(); - room.Dispose(); - } - } - } - - public void Tick(float deltaTime) - { - foreach (Room room in _rooms) - room.Tick(deltaTime); - } - } -} \ No newline at end of file diff --git a/Ragon/Sources/Rooms/RoomThread.cs b/Ragon/Sources/Rooms/RoomThread.cs deleted file mode 100755 index 089c3b5..0000000 --- a/Ragon/Sources/Rooms/RoomThread.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; -using DisruptorUnity3d; -using NLog; -using Ragon.Common; - -namespace Ragon.Core -{ - public class RoomThread : IDisposable - { - private readonly RoomManager _roomManager; - private readonly Dictionary _socketByRooms; - private readonly Thread _thread; - private readonly Stopwatch _timer; - private readonly ILogger _logger = LogManager.GetCurrentClassLogger(); - private readonly float _deltaTime = 0.0f; - private readonly RingBuffer _receiveBuffer = new(2048); - private readonly RingBuffer _sendBuffer = new(2048); - - public Configuration Configuration { get; private set; } - public bool ReadOutEvent(out Event evnt) => _sendBuffer.TryDequeue(out evnt); - public void WriteOutEvent(Event evnt) => _sendBuffer.Enqueue(evnt); - - public bool ReadIntEvent(out Event evnt) => _receiveBuffer.TryDequeue(out evnt); - public void WriteInEvent(Event evnt) => _receiveBuffer.Enqueue(evnt); - - public RoomThread(PluginFactory factory, Configuration configuration) - { - _thread = new Thread(Execute); - _thread.IsBackground = true; - _timer = new Stopwatch(); - _socketByRooms = new Dictionary(); - - Configuration = configuration; - _deltaTime = 1000.0f / Configuration.Server.TickRate; - - _roomManager = new RoomManager(this, factory); - _roomManager.OnJoined += (tuple) => _socketByRooms.Add(tuple.Item1, tuple.Item2); - _roomManager.OnLeaved += (tuple) => _socketByRooms.Remove(tuple.Item1); - } - - public void Start() - { - _timer.Start(); - _thread.Start(); - } - - public void Stop() - { - _thread.Interrupt(); - } - - private void Execute() - { - while (true) - { - while (_receiveBuffer.TryDequeue(out var evnt)) - { - if (evnt.Type == EventType.DISCONNECTED || evnt.Type == EventType.TIMEOUT) - { - if (_socketByRooms.ContainsKey(evnt.PeerId)) - { - _roomManager.Disconnected(evnt.PeerId); - _socketByRooms.Remove(evnt.PeerId); - } - } - - if (evnt.Type == EventType.DATA) - { - var data = new ReadOnlySpan(evnt.Data); - var operation = (RagonOperation) data[0]; - var payload = data.Slice(1, data.Length - 1); - - if (_socketByRooms.TryGetValue(evnt.PeerId, out var room)) - { - try - { - room.ProcessEvent(operation, evnt.PeerId, payload); - } - catch (Exception exception) - { - _logger.Error(exception); - } - } - else - { - _roomManager.ProcessEvent(operation, evnt.PeerId, payload); - } - } - } - - var elapsedMilliseconds = _timer.ElapsedMilliseconds; - if (elapsedMilliseconds > _deltaTime) - { - _roomManager.Tick(elapsedMilliseconds / 1000.0f); - _timer.Restart(); - continue; - } - - Thread.Sleep(15); - } - } - - public void Dispose() - { - } - } -} \ No newline at end of file diff --git a/Ragon/Sources/Server/ENetServer.cs b/Ragon/Sources/Server/ENetServer.cs index d195ba2..aa23d37 100755 --- a/Ragon/Sources/Server/ENetServer.cs +++ b/Ragon/Sources/Server/ENetServer.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.Threading; using DisruptorUnity3d; @@ -18,7 +17,7 @@ namespace Ragon.Core Connected } - public class ENetServer : IDisposable + public class ENetServer { public Status Status { get; private set; } @@ -28,17 +27,13 @@ namespace Ragon.Core private Address _address; private ENet.Event _netEvent; private Peer[] _peers; - - private RingBuffer _receiveBuffer; - private RingBuffer _sendBuffer; + private int _seconds = 0; + private Stopwatch _packetsTimer; + public RingBuffer SendBuffer; + public RingBuffer ReceiveBuffer; - public void WriteEvent(Event evnt) => _sendBuffer.Enqueue(evnt); - public bool ReadEvent(out Event evnt) => _receiveBuffer.TryDequeue(out evnt); - public void Start(ushort port) { - Library.Initialize(); - _address = default; _address.Port = port; @@ -46,22 +41,24 @@ namespace Ragon.Core _host.Create(_address, 4095, 2, 0, 0, 1024 * 1024); _peers = new Peer[4095]; - _sendBuffer = new RingBuffer(8192 + 8192); - _receiveBuffer = new RingBuffer(8192 + 8192); + + ReceiveBuffer = new RingBuffer(8192 + 8192); + SendBuffer = new RingBuffer(8192 + 8192); Status = Status.Listening; - + _packetsTimer = new Stopwatch(); _thread = new Thread(Execute); _thread.Name = "NetworkThread"; _thread.Start(); - _logger.Info($"ENet Server Started at port {port}"); + _logger.Info($"Network listening on {port}"); } private void Execute() { + _packetsTimer.Start(); while (true) { - while (_sendBuffer.TryDequeue(out var data)) + while (SendBuffer.TryDequeue(out var data)) { if (data.Type == EventType.DATA) { @@ -86,16 +83,16 @@ namespace Ragon.Core else if (data.Type == EventType.DISCONNECTED) { _peers[data.PeerId].DisconnectNow(0); - _receiveBuffer.Enqueue(data); + ReceiveBuffer.Enqueue(data); } } - + bool polled = false; while (!polled) { if (_host.CheckEvents(out _netEvent) <= 0) { - if (_host.Service(16, out _netEvent) <= 0) + if (_host.Service(15, out _netEvent) <= 0) break; polled = true; @@ -109,21 +106,21 @@ namespace Ragon.Core case ENet.EventType.Connect: { - var @event = new Event {PeerId = _netEvent.Peer.ID, Type = EventType.CONNECTED}; + var @event = new SocketEvent {PeerId = _netEvent.Peer.ID, Type = EventType.CONNECTED}; _peers[_netEvent.Peer.ID] = _netEvent.Peer; - _receiveBuffer.Enqueue(@event); + ReceiveBuffer.Enqueue(@event); break; } case ENet.EventType.Disconnect: { - var @event = new Event {PeerId = _netEvent.Peer.ID, Type = EventType.DISCONNECTED}; - _receiveBuffer.Enqueue(@event); + var @event = new SocketEvent {PeerId = _netEvent.Peer.ID, Type = EventType.DISCONNECTED}; + ReceiveBuffer.Enqueue(@event); break; } case ENet.EventType.Timeout: { - var @event = new Event {PeerId = _netEvent.Peer.ID, Type = EventType.TIMEOUT}; - _receiveBuffer.Enqueue(@event); + var @event = new SocketEvent {PeerId = _netEvent.Peer.ID, Type = EventType.TIMEOUT}; + ReceiveBuffer.Enqueue(@event); break; } case ENet.EventType.Receive: @@ -133,20 +130,23 @@ namespace Ragon.Core _netEvent.Packet.CopyTo(data); _netEvent.Packet.Dispose(); - var @event = new Event {PeerId = _netEvent.Peer.ID, Type = EventType.DATA, Data = data }; - - _receiveBuffer.Enqueue(@event); + var @event = new SocketEvent {PeerId = _netEvent.Peer.ID, Type = EventType.DATA, Data = data }; + ReceiveBuffer.Enqueue(@event); break; } } } + + if (_packetsTimer.Elapsed.Seconds > 5) + { + Console.WriteLine($"Connections: {_host.PeersCount}"); + _packetsTimer.Restart(); + } } } - public void Dispose() + public void Stop() { - Library.Deinitialize(); - _host?.Dispose(); } } diff --git a/Ragon/Sources/Server/WebsocketServer.cs b/Ragon/Sources/Server/WebsocketServer.cs deleted file mode 100644 index 99915a5..0000000 --- a/Ragon/Sources/Server/WebsocketServer.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Net; -using System.Net.WebSockets; -using System.Threading; -using DisruptorUnity3d; -using NLog; - -namespace Ragon.Core; - -public class WebsocketServer : IDisposable -{ - private HttpListener _httpListener; - private ILogger _logger = LogManager.GetCurrentClassLogger(); - private Thread _thread; - private ENet.Event _netEvent; - - private RingBuffer _receiveBuffer; - private RingBuffer _sendBuffer; - - public void WriteEvent(Event evnt) => _sendBuffer.Enqueue(evnt); - public bool ReadEvent(out Event evnt) => _receiveBuffer.TryDequeue(out evnt); - - public void Start(ushort port) - { - // _httpListener = new HttpListener(); - // _httpListener.Prefixes.Add("http://localhost/"); - // _httpListener.Start(); - // - // _thread = new Thread(Execute); - // _thread.Name = "NetworkThread"; - // _thread.Start(); - // _logger.Info($"Socket Server Started at port {port}"); - } - - public void Execute() - { - - } - - public async void ExecuteAsync() - { - // while (true) - // { - // HttpListenerContext context = await _httpListener.GetContextAsync(); - // if (context.Request.IsWebSocketRequest) - // { - // HttpListenerWebSocketContext webSocketContext = await context.AcceptWebSocketAsync(null); - // WebSocket webSocket = webSocketContext.WebSocket; - // while (webSocket.State == WebSocketState.Open) - // { - // await webSocket.SendAsync(... ); - // } - // } - // } - } - - public void Dispose() - { - } -} \ No newline at end of file diff --git a/Ragon/Sources/Storage/EntityInfo.cs b/Ragon/Sources/Storage/EntityInfo.cs deleted file mode 100644 index 54f3b74..0000000 --- a/Ragon/Sources/Storage/EntityInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Ragon.Core.Storage; - -public struct EntityInfo -{ - -} \ No newline at end of file diff --git a/Ragon/Sources/Storage/PlayerInfo.cs b/Ragon/Sources/Storage/PlayerInfo.cs deleted file mode 100644 index a1bf14b..0000000 --- a/Ragon/Sources/Storage/PlayerInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Ragon.Core.Storage; - -public struct PlayerInfo -{ - -} \ No newline at end of file diff --git a/Ragon/Sources/Storage/RoomInfo.cs b/Ragon/Sources/Storage/RoomInfo.cs deleted file mode 100644 index e7721c2..0000000 --- a/Ragon/Sources/Storage/RoomInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Ragon.Core.Storage; - -public struct RoomInfo -{ - -} \ No newline at end of file diff --git a/Ragon/Sources/Storage/Storage.cs b/Ragon/Sources/Storage/Storage.cs deleted file mode 100644 index 012c962..0000000 --- a/Ragon/Sources/Storage/Storage.cs +++ /dev/null @@ -1,29 +0,0 @@ - -namespace Ragon.Core.Storage; - -public class Storage -{ - // private ConnectionMultiplexer _connection; - - public Storage(Configuration _configuration) - { - // _connection = ConnectionMultiplexer.Connect(_configuration.Key); - } - - public void UpdateEntity(int entityId) - { - // var db = _connection.GetDatabase(); - - // db.set("entity_", ) - } - - public void UpdatePlayer() - { - - } - - public void UpdateRoom() - { - - } -} diff --git a/SimpleServer/Source/AuthorizerByKey.cs b/SimpleServer/Source/AuthorizerByKey.cs deleted file mode 100644 index eacb357..0000000 --- a/SimpleServer/Source/AuthorizerByKey.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Text; -using Ragon.Core; - -namespace Game.Source; - -public class AuthorizerByKey: AuthorizationManager -{ - private Configuration _configuration; - public AuthorizerByKey(Configuration configuration) - { - _configuration = configuration; - } - - public override bool OnAuthorize(uint peerId, ref ReadOnlySpan payload) - { - var key = Encoding.UTF8.GetString(payload); - return _configuration.Key == key; - } -} \ No newline at end of file diff --git a/SimpleServer/Source/Events/SimpleEvent.cs b/SimpleServer/Source/Events/SimpleEvent.cs deleted file mode 100644 index 32402a7..0000000 --- a/SimpleServer/Source/Events/SimpleEvent.cs +++ /dev/null @@ -1,19 +0,0 @@ -using NetStack.Serialization; -using Ragon.Common; - -namespace Game.Source.Events; - -public class SimpleEvent: IRagonSerializable -{ - public string Name; - - public void Serialize(BitBuffer buffer) - { - buffer.AddString(Name); - } - - public void Deserialize(BitBuffer buffer) - { - Name = buffer.ReadString(); - } -} \ No newline at end of file diff --git a/SimpleServer/config.json b/SimpleServer/config.json deleted file mode 100755 index 65d58c6..0000000 --- a/SimpleServer/config.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "key": "defaultkey", - "server": { - "port": 4444, - "skipTimeout": 60, - "tickRate": 30 - } -} \ No newline at end of file