diff --git a/Ragon.Common/External/DisruptorUnity3d/RingBuffer.cs b/Ragon.Common/External/DisruptorUnity3d/RingBuffer.cs deleted file mode 100755 index fe0e70d..0000000 --- a/Ragon.Common/External/DisruptorUnity3d/RingBuffer.cs +++ /dev/null @@ -1,275 +0,0 @@ -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Threading; - -namespace DisruptorUnity3d -{ - /// - /// Implementation of the Disruptor pattern - /// - /// the type of item to be stored - public class RingBuffer - { - private readonly T[] _entries; - private readonly int _modMask; - private Volatile.PaddedLong _consumerCursor = new Volatile.PaddedLong(); - private Volatile.PaddedLong _producerCursor = new Volatile.PaddedLong(); - - /// - /// Creates a new RingBuffer with the given capacity - /// - /// The capacity of the buffer - /// Only a single thread may attempt to consume at any one time - public RingBuffer(int capacity) - { - capacity = NextPowerOfTwo(capacity); - _modMask = capacity - 1; - _entries = new T[capacity]; - } - - /// - /// The maximum number of items that can be stored - /// - public int Capacity - { - get { return _entries.Length; } - } - - public T this[long index] - { - get { unchecked { return _entries[index & _modMask]; } } - set { unchecked { _entries[index & _modMask] = value; } } - } - - /// - /// Removes an item from the buffer. - /// - /// The next available item - public T Dequeue() - { - var next = _consumerCursor.ReadAcquireFence() + 1; - while (_producerCursor.ReadAcquireFence() < next) // makes sure we read the data from _entries after we have read the producer cursor - { - Thread.SpinWait(1); - } - var result = this[next]; - _consumerCursor.WriteReleaseFence(next); // makes sure we read the data from _entries before we update the consumer cursor - return result; - } - - /// - /// Attempts to remove an items from the queue - /// - /// the items - /// True if successful - public bool TryDequeue(out T obj) - { - var next = _consumerCursor.ReadAcquireFence() + 1; - - if (_producerCursor.ReadAcquireFence() < next) - { - obj = default(T); - return false; - } - obj = Dequeue(); - return true; - } - - /// - /// Add an item to the buffer - /// - /// - public void Enqueue(T item) - { - var next = _producerCursor.ReadAcquireFence() + 1; - - long wrapPoint = next - _entries.Length; - long min = _consumerCursor.ReadAcquireFence(); - - while (wrapPoint > min) - { - min = _consumerCursor.ReadAcquireFence(); - Thread.SpinWait(1); - } - - this[next] = item; - _producerCursor.WriteReleaseFence(next); // makes sure we write the data in _entries before we update the producer cursor - } - - /// - /// The number of items in the buffer - /// - /// for indicative purposes only, may contain stale data - public int Count { get { return (int)(_producerCursor.ReadFullFence() - _consumerCursor.ReadFullFence()); } } - - private static int NextPowerOfTwo(int x) - { - var result = 2; - while (result < x) - { - result <<= 1; - } - return result; - } - - - } - public static class Volatile - { - private const int CacheLineSize = 64; - - [StructLayout(LayoutKind.Explicit, Size = CacheLineSize * 2)] - public struct PaddedLong - { - [FieldOffset(CacheLineSize)] - private long _value; - - /// - /// Create a new with the given initial value. - /// - /// Initial value - public PaddedLong(long value) - { - _value = value; - } - - /// - /// Read the value without applying any fence - /// - /// The current value - public long ReadUnfenced() - { - return _value; - } - - /// - /// Read the value applying acquire fence semantic - /// - /// The current value - public long ReadAcquireFence() - { - var value = _value; - Thread.MemoryBarrier(); - return value; - } - - /// - /// Read the value applying full fence semantic - /// - /// The current value - public long ReadFullFence() - { - Thread.MemoryBarrier(); - return _value; - } - - /// - /// Read the value applying a compiler only fence, no CPU fence is applied - /// - /// The current value - [MethodImpl(MethodImplOptions.NoOptimization)] - public long ReadCompilerOnlyFence() - { - return _value; - } - - /// - /// Write the value applying release fence semantic - /// - /// The new value - public void WriteReleaseFence(long newValue) - { - Thread.MemoryBarrier(); - _value = newValue; - } - - /// - /// Write the value applying full fence semantic - /// - /// The new value - public void WriteFullFence(long newValue) - { - Thread.MemoryBarrier(); - _value = newValue; - } - - /// - /// Write the value applying a compiler fence only, no CPU fence is applied - /// - /// The new value - [MethodImpl(MethodImplOptions.NoOptimization)] - public void WriteCompilerOnlyFence(long newValue) - { - _value = newValue; - } - - /// - /// Write without applying any fence - /// - /// The new value - public void WriteUnfenced(long newValue) - { - _value = newValue; - } - - /// - /// Atomically set the value to the given updated value if the current value equals the comparand - /// - /// The new value - /// The comparand (expected value) - /// - public bool AtomicCompareExchange(long newValue, long comparand) - { - return Interlocked.CompareExchange(ref _value, newValue, comparand) == comparand; - } - - /// - /// Atomically set the value to the given updated value - /// - /// The new value - /// The original value - public long AtomicExchange(long newValue) - { - return Interlocked.Exchange(ref _value, newValue); - } - - /// - /// Atomically add the given value to the current value and return the sum - /// - /// The value to be added - /// The sum of the current value and the given value - public long AtomicAddAndGet(long delta) - { - return Interlocked.Add(ref _value, delta); - } - - /// - /// Atomically increment the current value and return the new value - /// - /// The incremented value. - public long AtomicIncrementAndGet() - { - return Interlocked.Increment(ref _value); - } - - /// - /// Atomically increment the current value and return the new value - /// - /// The decremented value. - public long AtomicDecrementAndGet() - { - return Interlocked.Decrement(ref _value); - } - - /// - /// Returns the string representation of the current value. - /// - /// the string representation of the current value. - public override string ToString() - { - var value = ReadFullFence(); - return value.ToString(); - } - } - } -} \ No newline at end of file diff --git a/Ragon.SimpleServer/Source/AuthorizationProviderByKey.cs b/Ragon.SimpleServer/Source/AuthorizationProvider.cs similarity index 100% rename from Ragon.SimpleServer/Source/AuthorizationProviderByKey.cs rename to Ragon.SimpleServer/Source/AuthorizationProvider.cs diff --git a/Ragon.SimpleServer/Source/Plugins/SimplePlugin.cs b/Ragon.SimpleServer/Source/Plugins/EmptyPlugin.cs similarity index 85% rename from Ragon.SimpleServer/Source/Plugins/SimplePlugin.cs rename to Ragon.SimpleServer/Source/Plugins/EmptyPlugin.cs index 483057c..a4873ac 100755 --- a/Ragon.SimpleServer/Source/Plugins/SimplePlugin.cs +++ b/Ragon.SimpleServer/Source/Plugins/EmptyPlugin.cs @@ -6,17 +6,16 @@ namespace Game.Source { public override void OnStart() { - _logger.Info("Plugin started"); + // _logger.Info("Plugin started"); } public override void OnStop() { - _logger.Info("Plugin stopped"); + // _logger.Info("Plugin stopped"); } public override void OnPlayerJoined(Player player) { - // _logger.Info($"Player({player.PlayerName}) joined to Room({GameRoom.Id})"); } diff --git a/Ragon.SimpleServer/config.json b/Ragon.SimpleServer/config.json index ab66704..1284e69 100755 --- a/Ragon.SimpleServer/config.json +++ b/Ragon.SimpleServer/config.json @@ -1,8 +1,10 @@ { "key": "defaultkey", - "tickRate": 30, + "statisticsInterval": 5, + "sendRate": 30, + "port": 4444, "skipTimeout": 60, - "server": { - "port": 4444 - } + "maxConnections": 4095, + "maxPlayersPerRoom": 20, + "maxRooms": 200 } \ No newline at end of file diff --git a/Ragon.Stress/Program.cs b/Ragon.Stress/Program.cs index 842438d..1eecdea 100644 --- a/Ragon.Stress/Program.cs +++ b/Ragon.Stress/Program.cs @@ -86,9 +86,9 @@ namespace Stress { ragonSerializer.Clear(); ragonSerializer.WriteOperation(RagonOperation.JOIN_OR_CREATE_ROOM); - ragonSerializer.WriteInt(2); - ragonSerializer.WriteInt(20); ragonSerializer.WriteString("map"); + ragonSerializer.WriteInt(1); + ragonSerializer.WriteInt(5); var sendData = ragonSerializer.ToArray(); var packet = new Packet(); @@ -139,7 +139,7 @@ namespace Stress break; } } - Console.WriteLine(op); + // Console.WriteLine(op); // Console.WriteLine("Packet received from server - Channel ID: " + netEvent.ChannelID + ", Data length: " + netEvent.Packet.Length); netEvent.Packet.Dispose(); break; @@ -165,7 +165,7 @@ namespace Stress } } - Thread.Sleep(16); + Thread.Sleep(33); } } @@ -192,65 +192,13 @@ namespace Stress { Library.Initialize(); - + for (var i = 0; i < 80; i ++) { var thread = new SimulationThread(); - thread.Start("127.0.0.1", 4444, 250); + thread.Start("127.0.0.1", 4444, 50); + Thread.Sleep(1000); } - Thread.Sleep(3000); - - { - var thread = new SimulationThread(); - thread.Start("127.0.0.1", 4444, 250); - } - - Thread.Sleep(3000); - - - { - var thread = new SimulationThread(); - thread.Start("127.0.0.1", 4444, 250); - } - - Thread.Sleep(3000); - - { - var thread = new SimulationThread(); - thread.Start("127.0.0.1", 4444, 250); - } - - Thread.Sleep(3000); - - { - var thread = new SimulationThread(); - thread.Start("127.0.0.1", 4444, 250); - } - - Thread.Sleep(3000); - - { - var thread = new SimulationThread(); - thread.Start("127.0.0.1", 4444, 250); - } - - Thread.Sleep(3000); - - { - var thread = new SimulationThread(); - thread.Start("127.0.0.1", 4444, 250); - } - - Thread.Sleep(3000); - - { - var thread = new SimulationThread(); - thread.Start("127.0.0.1", 4444, 250); - } - - Thread.Sleep(3000); - - Console.ReadKey(); Library.Deinitialize(); } diff --git a/Ragon/Ragon.csproj b/Ragon/Ragon.csproj index f9e263f..af4de57 100755 --- a/Ragon/Ragon.csproj +++ b/Ragon/Ragon.csproj @@ -3,7 +3,7 @@ 10 enable - net6.0;netstandard2.1 + net6.0 diff --git a/Ragon/Sources/Application.cs b/Ragon/Sources/Application.cs index 330db28..8edd063 100755 --- a/Ragon/Sources/Application.cs +++ b/Ragon/Sources/Application.cs @@ -11,7 +11,7 @@ namespace Ragon.Core { private readonly Logger _logger = LogManager.GetCurrentClassLogger(); private readonly GameThread _gameThread; - private readonly ENetServer _netServer; + public Application(PluginFactory factory, Configuration configuration) { _gameThread = new GameThread(factory, configuration); diff --git a/Ragon/Sources/Authorization/AuthorizationManager.cs b/Ragon/Sources/Authorization/AuthorizationManager.cs index 2f04592..03da1de 100644 --- a/Ragon/Sources/Authorization/AuthorizationManager.cs +++ b/Ragon/Sources/Authorization/AuthorizationManager.cs @@ -27,7 +27,7 @@ public class AuthorizationManager : IAuthorizationManager public void OnAuthorization(uint peerId, string key, string name, byte protocol) { - var dispatcher = _gameThread.Dispatcher; + var dispatcher = _gameThread.ThreadDispatcher; _provider.OnAuthorizationRequest(key, name, protocol, Array.Empty(), (playerId, playerName) => { @@ -77,9 +77,12 @@ public class AuthorizationManager : IAuthorizationManager _playersByIds.Remove(player.Id); } - public Player GetPlayer(uint peerId) + public Player? GetPlayer(uint peerId) { - return _playersByPeers[peerId]; + if (_playersByPeers.TryGetValue(peerId, out var player)) + return player; + + return null; } public Player GetPlayer(string playerId) diff --git a/Ragon/Sources/Configuration/Configuration.cs b/Ragon/Sources/Configuration/Configuration.cs index 03afbb1..66a1079 100755 --- a/Ragon/Sources/Configuration/Configuration.cs +++ b/Ragon/Sources/Configuration/Configuration.cs @@ -2,17 +2,16 @@ namespace Ragon.Core { - [Serializable] - public struct Server - { - public ushort Port; - } - [Serializable] public struct Configuration { public string Key; - public ushort TickRate; - public Server Server; + public int StatisticsInterval; + public ushort SendRate; + public ushort Port; + public int SkipTimeout; + public int MaxConnections; + public int MaxPlayersPerRoom; + public int MaxRooms; } } \ No newline at end of file diff --git a/Ragon/Sources/Core/Communication/IReceiver.cs b/Ragon/Sources/Core/Communication/IReceiver.cs deleted file mode 100644 index 8026716..0000000 --- a/Ragon/Sources/Core/Communication/IReceiver.cs +++ /dev/null @@ -1,6 +0,0 @@ -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 deleted file mode 100644 index 2232a8e..0000000 --- a/Ragon/Sources/Core/Communication/ISender.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Ragon.Core; - -public interface ISender -{ - public void Send(T data); -} \ No newline at end of file diff --git a/Ragon/Sources/Core/IO/DispatcherTask.cs b/Ragon/Sources/Core/IO/DispatcherTask.cs deleted file mode 100644 index 71d7f20..0000000 --- a/Ragon/Sources/Core/IO/DispatcherTask.cs +++ /dev/null @@ -1,15 +0,0 @@ -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/Utils/SynchronizedCache.cs b/Ragon/Sources/Core/Utils/SynchronizedCache.cs deleted file mode 100644 index 81c9943..0000000 --- a/Ragon/Sources/Core/Utils/SynchronizedCache.cs +++ /dev/null @@ -1,130 +0,0 @@ -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/SocketEvent.cs b/Ragon/Sources/Event/SocketEvent.cs deleted file mode 100644 index 040f6c9..0000000 --- a/Ragon/Sources/Event/SocketEvent.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Ragon.Core -{ - public struct SocketEvent - { - public EventType Type; - public DeliveryType Delivery; - public byte[] Data; - public uint PeerId; - } -} \ No newline at end of file diff --git a/Ragon/Sources/Event/SocketEventType.cs b/Ragon/Sources/Event/SocketEventType.cs deleted file mode 100644 index 8f147cd..0000000 --- a/Ragon/Sources/Event/SocketEventType.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Ragon.Core -{ - public enum EventType - { - CONNECTED, - DISCONNECTED, - TIMEOUT, - DATA, - } -} \ No newline at end of file diff --git a/Ragon/Sources/Game/GameRoom.cs b/Ragon/Sources/Game/GameRoom.cs index 57ca38e..7f5927a 100755 --- a/Ragon/Sources/Game/GameRoom.cs +++ b/Ragon/Sources/Game/GameRoom.cs @@ -19,7 +19,6 @@ namespace Ragon.Core private Dictionary _players = new(); private Dictionary _entities = new(); private uint _owner; - private uint _ticks; private readonly PluginBase _plugin; private readonly IGameThread _gameThread; @@ -40,7 +39,6 @@ namespace Ragon.Core PlayersMax = max; Id = Guid.NewGuid().ToString(); - _logger.Info($"Room created with plugin: {_plugin.GetType().Name}"); _plugin.Attach(this); } @@ -119,7 +117,9 @@ namespace Ragon.Core { var newRoomOwnerId = _allPlayers[0]; var newRoomOwner = _players[newRoomOwnerId]; - + + _owner = newRoomOwnerId; + { _plugin.OnOwnershipChanged(newRoomOwner); @@ -323,8 +323,7 @@ namespace Ragon.Core public void Tick(float deltaTime) { - _ticks++; - _plugin.OnTick(_ticks, deltaTime); + _plugin.OnTick(deltaTime); foreach (var entity in _entitiesAll) { @@ -347,15 +346,12 @@ namespace Ragon.Core public void Start() { - _logger.Info("Room started"); _plugin.OnStart(); } public void Stop() { - _logger.Info("Room stopped"); _plugin.OnStop(); - _plugin.Detach(); } @@ -365,6 +361,8 @@ namespace Ragon.Core public Player GetOwner() => _players[_owner]; + public IDispatcher GetThreadDispatcher() => _gameThread.ThreadDispatcher; + public void Send(uint peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable) { _gameThread.Server.Send(peerId, rawData, deliveryType); diff --git a/Ragon/Sources/Game/GameThread.cs b/Ragon/Sources/Game/GameThread.cs index fff1196..ea81589 100755 --- a/Ragon/Sources/Game/GameThread.cs +++ b/Ragon/Sources/Game/GameThread.cs @@ -9,37 +9,37 @@ namespace Ragon.Core { public class GameThread : IGameThread, IHandler { - private readonly Dictionary _socketByRooms; private readonly RoomManager _roomManager; - private readonly ISocketServer _server; private readonly Thread _thread; - private readonly Server _serverConfiguration; private readonly Stopwatch _gameLoopTimer; private readonly Lobby _lobby; private readonly ILogger _logger = LogManager.GetCurrentClassLogger(); private readonly float _deltaTime = 0.0f; - private readonly Stopwatch _packetsTimer; - private int _packets = 0; - - public IDispatcher Dispatcher { get; private set; } + private readonly Stopwatch _statisticsTimer; + private readonly Configuration _configuration; + private readonly IDispatcherInternal _dispatcherInternal; + + public IDispatcher ThreadDispatcher { get; private set; } public ISocketServer Server { get; private set; } - + public GameThread(PluginFactory factory, Configuration configuration) { + _configuration = configuration; + var authorizationProvider = factory.CreateAuthorizationProvider(configuration); + var dispatcher = new Dispatcher(); + _dispatcherInternal = dispatcher; - Dispatcher = new Dispatcher(); + ThreadDispatcher = dispatcher; Server = new ENetServer(this); - _serverConfiguration = configuration.Server; - _deltaTime = 1000.0f / configuration.TickRate; - + _deltaTime = 1000.0f / configuration.SendRate; + _roomManager = new RoomManager(factory, this); _lobby = new Lobby(authorizationProvider, _roomManager, this); - + _gameLoopTimer = new Stopwatch(); - _packetsTimer = new Stopwatch(); - _socketByRooms = new Dictionary(); + _statisticsTimer = new Stopwatch(); _thread = new Thread(Execute); _thread.Name = "Game Thread"; @@ -48,19 +48,19 @@ namespace Ragon.Core public void Start() { - Server.Start(_serverConfiguration.Port); - + Server.Start(_configuration.Port, _configuration.MaxConnections); + _gameLoopTimer.Start(); - _packetsTimer.Start(); + _statisticsTimer.Start(); _thread.Start(); } public void Stop() { Server.Stop(); - + _gameLoopTimer.Stop(); - _packetsTimer.Stop(); + _statisticsTimer.Stop(); _thread.Interrupt(); } @@ -69,8 +69,9 @@ namespace Ragon.Core while (true) { Server.Process(); - Dispatcher.Process(); + _dispatcherInternal.Process(); + var elapsedMilliseconds = _gameLoopTimer.ElapsedMilliseconds; if (elapsedMilliseconds > _deltaTime) { @@ -79,47 +80,38 @@ namespace Ragon.Core continue; } - if (_packetsTimer.Elapsed.Seconds > 1) + if (_statisticsTimer.Elapsed.Seconds > _configuration.StatisticsInterval && _roomManager.RoomsBySocket.Count > 0) { - _logger.Trace($"Clients: {_socketByRooms.Keys.Count} Packets: {_packets} per sec"); - _packetsTimer.Restart(); - _packets = 0; + _logger.Trace($"Rooms: {_roomManager.Rooms.Count} Clients: {_roomManager.RoomsBySocket.Count}"); + _statisticsTimer.Restart(); } + Thread.Sleep(15); } } - public void Attach(uint peerId, GameRoom room) - { - _socketByRooms.Add(peerId, room); - } - - public void Detach(uint peerId) - { - _socketByRooms.Remove(peerId); - } public void OnEvent(Event evnt) { - if (evnt.Type == ENet.EventType.Timeout || evnt.Type == ENet.EventType.Disconnect) + if (evnt.Type == EventType.Timeout || evnt.Type == EventType.Disconnect) { - if (_socketByRooms.Remove(evnt.Peer.ID, out var room)) - room.Leave(evnt.Peer.ID); + var player = _lobby.AuthorizationManager.GetPlayer(evnt.Peer.ID); + if (player != null) + _roomManager.Left(player, Array.Empty()); _lobby.OnDisconnected(evnt.Peer.ID); } - if (evnt.Type == ENet.EventType.Receive) + if (evnt.Type == EventType.Receive) { - _packets += 1; try { var peerId = evnt.Peer.ID; var dataRaw = new byte[evnt.Packet.Length]; evnt.Packet.CopyTo(dataRaw); - + var data = new ReadOnlySpan(dataRaw); - if (_socketByRooms.TryGetValue(peerId, out var room)) + if (_roomManager.RoomsBySocket.TryGetValue(peerId, out var room)) { room.ProcessEvent(peerId, data); } diff --git a/Ragon/Sources/Game/IGameRoom.cs b/Ragon/Sources/Game/IGameRoom.cs index 7e8ecab..fad769b 100644 --- a/Ragon/Sources/Game/IGameRoom.cs +++ b/Ragon/Sources/Game/IGameRoom.cs @@ -11,6 +11,7 @@ public interface IGameRoom public Player GetPlayerById(uint peerId); public Entity GetEntityById(int entityId); public Player GetOwner(); + public IDispatcher GetThreadDispatcher(); public void Send(uint peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable); public void Broadcast(uint[] peersIds, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable); diff --git a/Ragon/Sources/Game/IGameThread.cs b/Ragon/Sources/Game/IGameThread.cs index 8094084..a788fac 100644 --- a/Ragon/Sources/Game/IGameThread.cs +++ b/Ragon/Sources/Game/IGameThread.cs @@ -4,8 +4,6 @@ namespace Ragon.Core; public interface IGameThread { - public void Attach(uint peerId, GameRoom room); - public void Detach(uint peerId); - public IDispatcher Dispatcher { get; } + public IDispatcher ThreadDispatcher { get; } public ISocketServer Server { get; } } \ No newline at end of file diff --git a/Ragon/Sources/Lobby/ILobby.cs b/Ragon/Sources/Lobby/ILobby.cs new file mode 100644 index 0000000..ddeda53 --- /dev/null +++ b/Ragon/Sources/Lobby/ILobby.cs @@ -0,0 +1,6 @@ +namespace Ragon.Core; + +public interface ILobby +{ + +} \ No newline at end of file diff --git a/Ragon/Sources/Lobby/Lobby.cs b/Ragon/Sources/Lobby/Lobby.cs index 703f92f..280e2fd 100644 --- a/Ragon/Sources/Lobby/Lobby.cs +++ b/Ragon/Sources/Lobby/Lobby.cs @@ -1,19 +1,21 @@ using System; using System.Collections.Generic; using Ragon.Common; + namespace Ragon.Core; -public class Lobby +public class Lobby : ILobby { private readonly RagonSerializer _serializer; - private readonly AuthorizationManager _authorizationManager; private readonly RoomManager _roomManager; + public AuthorizationManager AuthorizationManager { get; private set; } + public Lobby(IAuthorizationProvider provider, RoomManager manager, IGameThread gameThread) { _roomManager = manager; _serializer = new RagonSerializer(); - _authorizationManager = new AuthorizationManager(provider, gameThread, this, _serializer); + AuthorizationManager = new AuthorizationManager(provider, gameThread, this, _serializer); } public void ProcessEvent(uint peerId, ReadOnlySpan data) @@ -31,14 +33,15 @@ public class Lobby var key = _serializer.ReadString(); var playerName = _serializer.ReadString(); var protocol = _serializer.ReadByte(); - _authorizationManager.OnAuthorization(peerId, key, playerName, protocol); + 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()); + var player = AuthorizationManager.GetPlayer(peerId); + if (player != null) + _roomManager.Join(player, roomId, Array.Empty()); break; } case RagonOperation.JOIN_OR_CREATE_ROOM: @@ -46,14 +49,16 @@ public class Lobby 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()); + var player = AuthorizationManager.GetPlayer(peerId); + if (player != null) + _roomManager.JoinOrCreate(player, map, min, max, Array.Empty()); break; } case RagonOperation.LEAVE_ROOM: { - var player = _authorizationManager.GetPlayer(peerId); - _roomManager.Left(player, Array.Empty()); + var player = AuthorizationManager.GetPlayer(peerId); + if (player != null) + _roomManager.Left(player, Array.Empty()); break; } } @@ -61,6 +66,6 @@ public class Lobby public void OnDisconnected(uint peerId) { - _authorizationManager.Cleanup(peerId); + AuthorizationManager.Cleanup(peerId); } } \ No newline at end of file diff --git a/Ragon/Sources/Plugin/PluginFactory.cs b/Ragon/Sources/Plugin/IPluginFactory.cs similarity index 100% rename from Ragon/Sources/Plugin/PluginFactory.cs rename to Ragon/Sources/Plugin/IPluginFactory.cs diff --git a/Ragon/Sources/Plugin/PluginBase.cs b/Ragon/Sources/Plugin/PluginBase.cs index e4d9033..45f32a6 100755 --- a/Ragon/Sources/Plugin/PluginBase.cs +++ b/Ragon/Sources/Plugin/PluginBase.cs @@ -18,12 +18,12 @@ namespace Ragon.Core private readonly BitBuffer _buffer = new(); private readonly RagonSerializer _serializer = new(); - protected IGameRoom GameRoom { get; private set; } - protected ILogger _logger; + protected IGameRoom GameRoom { get; private set; } = null!; + protected ILogger Logger = null!; public void Attach(GameRoom gameRoom) { - _logger = LogManager.GetLogger($"Plugin<{GetType().Name}>"); + Logger = LogManager.GetLogger($"Plugin<{GetType().Name}>"); GameRoom = gameRoom; @@ -41,7 +41,7 @@ namespace Ragon.Core { if (_globalEvents.ContainsKey(evntCode)) { - _logger.Warn($"Event subscriber already added {evntCode}"); + Logger.Warn($"Event subscriber already added {evntCode}"); return; } @@ -50,7 +50,7 @@ namespace Ragon.Core { if (raw.Length == 0) { - _logger.Warn($"Payload is empty for event {evntCode}"); + Logger.Warn($"Payload is empty for event {evntCode}"); return; } @@ -65,7 +65,7 @@ namespace Ragon.Core { if (_globalEvents.ContainsKey(evntCode)) { - _logger.Warn($"Event subscriber already added {evntCode}"); + Logger.Warn($"Event subscriber already added {evntCode}"); return; } @@ -78,7 +78,7 @@ namespace Ragon.Core { if (_entityEvents[entity.EntityId].ContainsKey(evntCode)) { - _logger.Warn($"Event subscriber already added {evntCode} for {entity.EntityId}"); + Logger.Warn($"Event subscriber already added {evntCode} for {entity.EntityId}"); return; } @@ -87,7 +87,7 @@ namespace Ragon.Core { if (raw.Length == 0) { - _logger.Warn($"Payload is empty for entity {ent.EntityId} event {evntCode}"); + Logger.Warn($"Payload is empty for entity {ent.EntityId} event {evntCode}"); return; } @@ -107,7 +107,7 @@ namespace Ragon.Core { if (raw.Length == 0) { - _logger.Warn($"Payload is empty for entity {ent.EntityId} event {evntCode}"); + Logger.Warn($"Payload is empty for entity {ent.EntityId} event {evntCode}"); return; } @@ -125,7 +125,7 @@ namespace Ragon.Core { if (_entityEvents[entity.EntityId].ContainsKey(evntCode)) { - _logger.Warn($"Event subscriber already added {evntCode} for {entity.EntityId}"); + Logger.Warn($"Event subscriber already added {evntCode} for {entity.EntityId}"); return; } @@ -265,7 +265,7 @@ namespace Ragon.Core { } - public virtual void OnTick(ulong ticks, float deltaTime) + public virtual void OnTick(float deltaTime) { } diff --git a/Ragon/Sources/Room/RoomManager.cs b/Ragon/Sources/Room/RoomManager.cs index 9f8f47d..c35144c 100644 --- a/Ragon/Sources/Room/RoomManager.cs +++ b/Ragon/Sources/Room/RoomManager.cs @@ -1,4 +1,3 @@ - using System; using System.Collections.Generic; using System.Text; @@ -12,14 +11,19 @@ public class RoomManager private readonly IGameThread _gameThread; private readonly PluginFactory _factory; private readonly Logger _logger = LogManager.GetCurrentClassLogger(); - private List _rooms = new List(); + private readonly List _rooms = new List(); + private readonly Dictionary _roomsBySocket; + + public IReadOnlyDictionary RoomsBySocket => _roomsBySocket; + public IReadOnlyList Rooms => _rooms; public RoomManager(PluginFactory factory, IGameThread gameThread) { _gameThread = gameThread; _factory = factory; - } - + _roomsBySocket = new Dictionary(); + } + public void Join(Player player, string roomId, byte[] payload) { if (_rooms.Count > 0) @@ -29,7 +33,7 @@ public class RoomManager if (existRoom.Id == roomId && existRoom.PlayersCount < existRoom.PlayersMax) { existRoom.Joined(player, payload); - _gameThread.Attach(player.PeerId, existRoom); + _roomsBySocket.Add(player.PeerId, existRoom); break; } } @@ -45,8 +49,7 @@ public class RoomManager if (existRoom.Map == map && existRoom.PlayersCount < existRoom.PlayersMax) { existRoom.Joined(player, payload); - _gameThread.Attach(player.PeerId, existRoom); - + _roomsBySocket.Add(player.PeerId, existRoom); return; } } @@ -56,17 +59,25 @@ public class RoomManager 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, map, min, max); room.Joined(player, payload); room.Start(); - _gameThread.Attach(player.PeerId, room); + _roomsBySocket.Add(player.PeerId, room); _rooms.Add(room); } public void Left(Player player, byte[] payload) { - + if (_roomsBySocket.Remove(player.PeerId, out var room)) + { + room.Leave(player.PeerId); + if (room.PlayersCount < room.PlayersMin) + { + room.Stop(); + _rooms.Remove(room); + } + } } public void Tick(float deltaTime) diff --git a/Ragon/Sources/Event/DeliveryType.cs b/Ragon/Sources/Server/DeliveryType.cs similarity index 100% rename from Ragon/Sources/Event/DeliveryType.cs rename to Ragon/Sources/Server/DeliveryType.cs diff --git a/Ragon/Sources/Server/ENetServer.cs b/Ragon/Sources/Server/ENet/ENetServer.cs similarity index 85% rename from Ragon/Sources/Server/ENetServer.cs rename to Ragon/Sources/Server/ENet/ENetServer.cs index 6851591..e0fcd32 100755 --- a/Ragon/Sources/Server/ENetServer.cs +++ b/Ragon/Sources/Server/ENet/ENetServer.cs @@ -1,7 +1,4 @@ using System; -using System.Diagnostics; -using System.Threading; -using DisruptorUnity3d; using ENet; using NLog; @@ -33,14 +30,14 @@ namespace Ragon.Core _handler = handler; } - public void Start(ushort port) + public void Start(ushort port, int connections) { _address = default; _address.Port = port; - _peers = new Peer[2048]; + _peers = new Peer[connections]; _host = new Host(); - _host.Create(_address, 2048, 2, 0, 0, 1024 * 1024); + _host.Create(_address, connections, 2, 0, 0, 1024 * 1024); Status = Status.Listening; _logger.Info($"Network listening on {port}"); @@ -87,27 +84,27 @@ namespace Ragon.Core switch (_netEvent.Type) { - case ENet.EventType.None: + case EventType.None: Console.WriteLine("None event"); break; - case ENet.EventType.Connect: + case EventType.Connect: { _peers[_netEvent.Peer.ID] = _netEvent.Peer; _handler.OnEvent(_netEvent); break; } - case ENet.EventType.Disconnect: + case EventType.Disconnect: { _handler.OnEvent(_netEvent); break; } - case ENet.EventType.Timeout: + case EventType.Timeout: { _handler.OnEvent(_netEvent); break; } - case ENet.EventType.Receive: + case EventType.Receive: { _handler.OnEvent(_netEvent); _netEvent.Packet.Dispose(); diff --git a/Ragon/Sources/Server/ISocketServer.cs b/Ragon/Sources/Server/ISocketServer.cs index d797f86..b3bd8b6 100644 --- a/Ragon/Sources/Server/ISocketServer.cs +++ b/Ragon/Sources/Server/ISocketServer.cs @@ -2,7 +2,7 @@ namespace Ragon.Core; public interface ISocketServer { - public void Start(ushort port); + public void Start(ushort port, int connections); public void Process(); public void Stop(); public void Send(uint peerId, byte[] data, DeliveryType type); diff --git a/Ragon/Sources/Core/IO/Dispatcher.cs b/Ragon/Sources/Utils/Dispatcher.cs similarity index 54% rename from Ragon/Sources/Core/IO/Dispatcher.cs rename to Ragon/Sources/Utils/Dispatcher.cs index 910cebf..d8d8eed 100644 --- a/Ragon/Sources/Core/IO/Dispatcher.cs +++ b/Ragon/Sources/Utils/Dispatcher.cs @@ -3,19 +3,20 @@ using System.Collections.Generic; namespace Ragon.Core; -public class Dispatcher: IDispatcher +public class Dispatcher: IDispatcher, IDispatcherInternal { - public Queue _actions = new Queue(); + public Queue _actions = new Queue(); + public void Dispatch(Action action) { lock (_actions) - _actions.Enqueue(new DispatcherTask() { Action = action }); + _actions.Enqueue(action); } public void Process() { lock(_actions) while(_actions.TryDequeue(out var action)) - action.Execute(); + action?.Invoke(); } } \ No newline at end of file diff --git a/Ragon/Sources/Core/IO/IDispatcher.cs b/Ragon/Sources/Utils/IDispatcher.cs similarity index 81% rename from Ragon/Sources/Core/IO/IDispatcher.cs rename to Ragon/Sources/Utils/IDispatcher.cs index 6fe63b9..ace4de7 100644 --- a/Ragon/Sources/Core/IO/IDispatcher.cs +++ b/Ragon/Sources/Utils/IDispatcher.cs @@ -5,5 +5,4 @@ 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/Utils/IDispatcherInternal.cs b/Ragon/Sources/Utils/IDispatcherInternal.cs new file mode 100644 index 0000000..b3ad25e --- /dev/null +++ b/Ragon/Sources/Utils/IDispatcherInternal.cs @@ -0,0 +1,6 @@ +namespace Ragon.Core; + +public interface IDispatcherInternal +{ + public void Process(); +} \ No newline at end of file diff --git a/readme.md b/readme.md index 96add55..86c9af2 100755 --- a/readme.md +++ b/readme.md @@ -4,34 +4,31 @@ ## Ragon Server -Ragon is fully free high perfomance room based game server with plugin based architecture. - +Ragon is fully free, small and high perfomance room based game server with plugin based architecture. Documentation
Get started - ### Features: +- Effective - Free - Simple matchmaking - Flexiable API - Room based architecture - Extendable room logic via plugin - Custom authorization -- No CCU limitations* -- Multi-threaded +- No CCU limitations* - Engine agnostic - Support any client architecture (MonoBehaviors, ECS) -- UDP +- RUDP ### Roadmap: -- Allow customize matchmaking -- Refactoring some moments(a lot duplications of code, etc...) - Use native memory - Reduce allocations - Dashboard for monitoring entities and players in realtime - Statistics for monitoring state of server, cpu, memory +- Horizontal Scaling - Docker support - Add additional API to plugin system @@ -40,9 +37,8 @@ Ragon is fully free high perfomance room based game server with plugin based arc - .NET 6.0 ### Dependencies -* ENet-Sharp v2.4.8 -* NetStack latest -* RingBuffer-Unity3D latest +* ENet-Sharp [v2.4.8] +* NetStack [latest] ### License SSPL-1.0