Compare commits

...

5 Commits

Author SHA1 Message Date
edmand46 e674600308 fixed: removed outdated code 2022-06-25 23:00:00 +04:00
edmand46 4f587fa59c chore: update readme 2022-06-25 13:32:05 +04:00
edmand46 76caa840bd update version 2022-06-25 13:26:39 +04:00
edmand46 1bff47e56b chore: removed prerelease flag 2022-06-25 11:09:36 +04:00
edmand46 1e41b9f2eb refactor 2022-06-25 11:08:50 +04:00
32 changed files with 159 additions and 651 deletions
-275
View File
@@ -1,275 +0,0 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
namespace DisruptorUnity3d
{
/// <summary>
/// Implementation of the Disruptor pattern
/// </summary>
/// <typeparam name="T">the type of item to be stored</typeparam>
public class RingBuffer<T>
{
private readonly T[] _entries;
private readonly int _modMask;
private Volatile.PaddedLong _consumerCursor = new Volatile.PaddedLong();
private Volatile.PaddedLong _producerCursor = new Volatile.PaddedLong();
/// <summary>
/// Creates a new RingBuffer with the given capacity
/// </summary>
/// <param name="capacity">The capacity of the buffer</param>
/// <remarks>Only a single thread may attempt to consume at any one time</remarks>
public RingBuffer(int capacity)
{
capacity = NextPowerOfTwo(capacity);
_modMask = capacity - 1;
_entries = new T[capacity];
}
/// <summary>
/// The maximum number of items that can be stored
/// </summary>
public int Capacity
{
get { return _entries.Length; }
}
public T this[long index]
{
get { unchecked { return _entries[index & _modMask]; } }
set { unchecked { _entries[index & _modMask] = value; } }
}
/// <summary>
/// Removes an item from the buffer.
/// </summary>
/// <returns>The next available item</returns>
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;
}
/// <summary>
/// Attempts to remove an items from the queue
/// </summary>
/// <param name="obj">the items</param>
/// <returns>True if successful</returns>
public bool TryDequeue(out T obj)
{
var next = _consumerCursor.ReadAcquireFence() + 1;
if (_producerCursor.ReadAcquireFence() < next)
{
obj = default(T);
return false;
}
obj = Dequeue();
return true;
}
/// <summary>
/// Add an item to the buffer
/// </summary>
/// <param name="item"></param>
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
}
/// <summary>
/// The number of items in the buffer
/// </summary>
/// <remarks>for indicative purposes only, may contain stale data</remarks>
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;
/// <summary>
/// Create a new <see cref="PaddedLong"/> with the given initial value.
/// </summary>
/// <param name="value">Initial value</param>
public PaddedLong(long value)
{
_value = value;
}
/// <summary>
/// Read the value without applying any fence
/// </summary>
/// <returns>The current value</returns>
public long ReadUnfenced()
{
return _value;
}
/// <summary>
/// Read the value applying acquire fence semantic
/// </summary>
/// <returns>The current value</returns>
public long ReadAcquireFence()
{
var value = _value;
Thread.MemoryBarrier();
return value;
}
/// <summary>
/// Read the value applying full fence semantic
/// </summary>
/// <returns>The current value</returns>
public long ReadFullFence()
{
Thread.MemoryBarrier();
return _value;
}
/// <summary>
/// Read the value applying a compiler only fence, no CPU fence is applied
/// </summary>
/// <returns>The current value</returns>
[MethodImpl(MethodImplOptions.NoOptimization)]
public long ReadCompilerOnlyFence()
{
return _value;
}
/// <summary>
/// Write the value applying release fence semantic
/// </summary>
/// <param name="newValue">The new value</param>
public void WriteReleaseFence(long newValue)
{
Thread.MemoryBarrier();
_value = newValue;
}
/// <summary>
/// Write the value applying full fence semantic
/// </summary>
/// <param name="newValue">The new value</param>
public void WriteFullFence(long newValue)
{
Thread.MemoryBarrier();
_value = newValue;
}
/// <summary>
/// Write the value applying a compiler fence only, no CPU fence is applied
/// </summary>
/// <param name="newValue">The new value</param>
[MethodImpl(MethodImplOptions.NoOptimization)]
public void WriteCompilerOnlyFence(long newValue)
{
_value = newValue;
}
/// <summary>
/// Write without applying any fence
/// </summary>
/// <param name="newValue">The new value</param>
public void WriteUnfenced(long newValue)
{
_value = newValue;
}
/// <summary>
/// Atomically set the value to the given updated value if the current value equals the comparand
/// </summary>
/// <param name="newValue">The new value</param>
/// <param name="comparand">The comparand (expected value)</param>
/// <returns></returns>
public bool AtomicCompareExchange(long newValue, long comparand)
{
return Interlocked.CompareExchange(ref _value, newValue, comparand) == comparand;
}
/// <summary>
/// Atomically set the value to the given updated value
/// </summary>
/// <param name="newValue">The new value</param>
/// <returns>The original value</returns>
public long AtomicExchange(long newValue)
{
return Interlocked.Exchange(ref _value, newValue);
}
/// <summary>
/// Atomically add the given value to the current value and return the sum
/// </summary>
/// <param name="delta">The value to be added</param>
/// <returns>The sum of the current value and the given value</returns>
public long AtomicAddAndGet(long delta)
{
return Interlocked.Add(ref _value, delta);
}
/// <summary>
/// Atomically increment the current value and return the new value
/// </summary>
/// <returns>The incremented value.</returns>
public long AtomicIncrementAndGet()
{
return Interlocked.Increment(ref _value);
}
/// <summary>
/// Atomically increment the current value and return the new value
/// </summary>
/// <returns>The decremented value.</returns>
public long AtomicDecrementAndGet()
{
return Interlocked.Decrement(ref _value);
}
/// <summary>
/// Returns the string representation of the current value.
/// </summary>
/// <returns>the string representation of the current value.</returns>
public override string ToString()
{
var value = ReadFullFence();
return value.ToString();
}
}
}
}
@@ -6,17 +6,16 @@ namespace Game.Source
{ {
public override void OnStart() public override void OnStart()
{ {
_logger.Info("Plugin started"); // _logger.Info("Plugin started");
} }
public override void OnStop() public override void OnStop()
{ {
_logger.Info("Plugin stopped"); // _logger.Info("Plugin stopped");
} }
public override void OnPlayerJoined(Player player) public override void OnPlayerJoined(Player player)
{ {
// _logger.Info($"Player({player.PlayerName}) joined to Room({GameRoom.Id})"); // _logger.Info($"Player({player.PlayerName}) joined to Room({GameRoom.Id})");
} }
+6 -4
View File
@@ -1,8 +1,10 @@
{ {
"key": "defaultkey", "key": "defaultkey",
"tickRate": 30, "statisticsInterval": 5,
"sendRate": 30,
"port": 4444,
"skipTimeout": 60, "skipTimeout": 60,
"server": { "maxConnections": 4095,
"port": 4444 "maxPlayersPerRoom": 20,
} "maxRooms": 200
} }
+7 -59
View File
@@ -86,9 +86,9 @@ namespace Stress
{ {
ragonSerializer.Clear(); ragonSerializer.Clear();
ragonSerializer.WriteOperation(RagonOperation.JOIN_OR_CREATE_ROOM); ragonSerializer.WriteOperation(RagonOperation.JOIN_OR_CREATE_ROOM);
ragonSerializer.WriteInt(2);
ragonSerializer.WriteInt(20);
ragonSerializer.WriteString("map"); ragonSerializer.WriteString("map");
ragonSerializer.WriteInt(1);
ragonSerializer.WriteInt(5);
var sendData = ragonSerializer.ToArray(); var sendData = ragonSerializer.ToArray();
var packet = new Packet(); var packet = new Packet();
@@ -139,7 +139,7 @@ namespace Stress
break; break;
} }
} }
Console.WriteLine(op); // Console.WriteLine(op);
// Console.WriteLine("Packet received from server - Channel ID: " + netEvent.ChannelID + ", Data length: " + netEvent.Packet.Length); // Console.WriteLine("Packet received from server - Channel ID: " + netEvent.ChannelID + ", Data length: " + netEvent.Packet.Length);
netEvent.Packet.Dispose(); netEvent.Packet.Dispose();
break; break;
@@ -165,7 +165,7 @@ namespace Stress
} }
} }
Thread.Sleep(16); Thread.Sleep(33);
} }
} }
@@ -192,65 +192,13 @@ namespace Stress
{ {
Library.Initialize(); Library.Initialize();
for (var i = 0; i < 80; i ++)
{ {
var thread = new SimulationThread(); var thread = new SimulationThread();
thread.Start("127.0.0.1", 4444, 250); thread.Start("127.0.0.1", 4444, 50);
Thread.Sleep(300);
} }
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(); Console.ReadKey();
Library.Deinitialize(); Library.Deinitialize();
} }
+1 -1
View File
@@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<LangVersion>10</LangVersion> <LangVersion>10</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<TargetFrameworks>net6.0;netstandard2.1</TargetFrameworks> <TargetFramework>net6.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+1 -1
View File
@@ -11,7 +11,7 @@ namespace Ragon.Core
{ {
private readonly Logger _logger = LogManager.GetCurrentClassLogger(); private readonly Logger _logger = LogManager.GetCurrentClassLogger();
private readonly GameThread _gameThread; private readonly GameThread _gameThread;
private readonly ENetServer _netServer;
public Application(PluginFactory factory, Configuration configuration) public Application(PluginFactory factory, Configuration configuration)
{ {
_gameThread = new GameThread(factory, configuration); _gameThread = new GameThread(factory, configuration);
@@ -27,13 +27,16 @@ public class AuthorizationManager : IAuthorizationManager
public void OnAuthorization(uint peerId, string key, string name, byte protocol) 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<byte>(), _provider.OnAuthorizationRequest(key, name, protocol, Array.Empty<byte>(),
(playerId, playerName) => (playerId, playerName) =>
{ {
dispatcher.Dispatch(() => Accepted(peerId, playerId, playerName)); dispatcher.Dispatch(() => Accepted(peerId, playerId, playerName));
}, },
(errorCode) => { dispatcher.Dispatch(() => Rejected(peerId, errorCode)); }); (errorCode) =>
{
dispatcher.Dispatch(() => Rejected(peerId, errorCode));
});
} }
public void Accepted(uint peerId, string playerId, string playerName) public void Accepted(uint peerId, string playerId, string playerName)
@@ -77,9 +80,12 @@ public class AuthorizationManager : IAuthorizationManager
_playersByIds.Remove(player.Id); _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) public Player GetPlayer(string playerId)
+7 -8
View File
@@ -2,17 +2,16 @@
namespace Ragon.Core namespace Ragon.Core
{ {
[Serializable]
public struct Server
{
public ushort Port;
}
[Serializable] [Serializable]
public struct Configuration public struct Configuration
{ {
public string Key; public string Key;
public ushort TickRate; public int StatisticsInterval;
public Server Server; public ushort SendRate;
public ushort Port;
public int SkipTimeout;
public int MaxConnections;
public int MaxPlayersPerRoom;
public int MaxRooms;
} }
} }
@@ -9,7 +9,7 @@ namespace Ragon.Core
public static class ConfigurationLoader public static class ConfigurationLoader
{ {
private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
private static readonly string _serverVersion = "1.0.5-rc"; private static readonly string _serverVersion = "1.0.6-rc";
private static void CopyrightInfo() private static void CopyrightInfo()
{ {
@@ -1,6 +0,0 @@
namespace Ragon.Core;
public interface Receiver<T>
{
public bool Receive(out T data);
}
@@ -1,6 +0,0 @@
namespace Ragon.Core;
public interface ISender<T>
{
public void Send(T data);
}
-15
View File
@@ -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();
}
}
@@ -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<int, string> innerCache = new Dictionary<int, string>();
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();
}
}
-12
View File
@@ -1,12 +0,0 @@
using System;
namespace Ragon.Core
{
public struct SocketEvent
{
public EventType Type;
public DeliveryType Delivery;
public byte[] Data;
public uint PeerId;
}
}
-10
View File
@@ -1,10 +0,0 @@
namespace Ragon.Core
{
public enum EventType
{
CONNECTED,
DISCONNECTED,
TIMEOUT,
DATA,
}
}
+5 -7
View File
@@ -19,7 +19,6 @@ namespace Ragon.Core
private Dictionary<uint, Player> _players = new(); private Dictionary<uint, Player> _players = new();
private Dictionary<int, Entity> _entities = new(); private Dictionary<int, Entity> _entities = new();
private uint _owner; private uint _owner;
private uint _ticks;
private readonly PluginBase _plugin; private readonly PluginBase _plugin;
private readonly IGameThread _gameThread; private readonly IGameThread _gameThread;
@@ -40,7 +39,6 @@ namespace Ragon.Core
PlayersMax = max; PlayersMax = max;
Id = Guid.NewGuid().ToString(); Id = Guid.NewGuid().ToString();
_logger.Info($"Room created with plugin: {_plugin.GetType().Name}");
_plugin.Attach(this); _plugin.Attach(this);
} }
@@ -120,6 +118,8 @@ namespace Ragon.Core
var newRoomOwnerId = _allPlayers[0]; var newRoomOwnerId = _allPlayers[0];
var newRoomOwner = _players[newRoomOwnerId]; var newRoomOwner = _players[newRoomOwnerId];
_owner = newRoomOwnerId;
{ {
_plugin.OnOwnershipChanged(newRoomOwner); _plugin.OnOwnershipChanged(newRoomOwner);
@@ -323,8 +323,7 @@ namespace Ragon.Core
public void Tick(float deltaTime) public void Tick(float deltaTime)
{ {
_ticks++; _plugin.OnTick(deltaTime);
_plugin.OnTick(_ticks, deltaTime);
foreach (var entity in _entitiesAll) foreach (var entity in _entitiesAll)
{ {
@@ -347,15 +346,12 @@ namespace Ragon.Core
public void Start() public void Start()
{ {
_logger.Info("Room started");
_plugin.OnStart(); _plugin.OnStart();
} }
public void Stop() public void Stop()
{ {
_logger.Info("Room stopped");
_plugin.OnStop(); _plugin.OnStop();
_plugin.Detach(); _plugin.Detach();
} }
@@ -365,6 +361,8 @@ namespace Ragon.Core
public Player GetOwner() => _players[_owner]; public Player GetOwner() => _players[_owner];
public IDispatcher GetThreadDispatcher() => _gameThread.ThreadDispatcher;
public void Send(uint peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable) public void Send(uint peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
{ {
_gameThread.Server.Send(peerId, rawData, deliveryType); _gameThread.Server.Send(peerId, rawData, deliveryType);
+26 -36
View File
@@ -9,37 +9,37 @@ namespace Ragon.Core
{ {
public class GameThread : IGameThread, IHandler public class GameThread : IGameThread, IHandler
{ {
private readonly Dictionary<uint, GameRoom> _socketByRooms;
private readonly RoomManager _roomManager; private readonly RoomManager _roomManager;
private readonly ISocketServer _server;
private readonly Thread _thread; private readonly Thread _thread;
private readonly Server _serverConfiguration;
private readonly Stopwatch _gameLoopTimer; private readonly Stopwatch _gameLoopTimer;
private readonly Lobby _lobby; private readonly Lobby _lobby;
private readonly ILogger _logger = LogManager.GetCurrentClassLogger(); private readonly ILogger _logger = LogManager.GetCurrentClassLogger();
private readonly float _deltaTime = 0.0f; private readonly float _deltaTime = 0.0f;
private readonly Stopwatch _packetsTimer; private readonly Stopwatch _statisticsTimer;
private int _packets = 0; private readonly Configuration _configuration;
private readonly IDispatcherInternal _dispatcherInternal;
public IDispatcher Dispatcher { get; private set; } public IDispatcher ThreadDispatcher { get; private set; }
public ISocketServer Server { get; private set; } public ISocketServer Server { get; private set; }
public GameThread(PluginFactory factory, Configuration configuration) public GameThread(PluginFactory factory, Configuration configuration)
{ {
var authorizationProvider = factory.CreateAuthorizationProvider(configuration); _configuration = configuration;
Dispatcher = new Dispatcher(); var authorizationProvider = factory.CreateAuthorizationProvider(configuration);
var dispatcher = new Dispatcher();
_dispatcherInternal = dispatcher;
ThreadDispatcher = dispatcher;
Server = new ENetServer(this); Server = new ENetServer(this);
_serverConfiguration = configuration.Server; _deltaTime = 1000.0f / configuration.SendRate;
_deltaTime = 1000.0f / configuration.TickRate;
_roomManager = new RoomManager(factory, this); _roomManager = new RoomManager(factory, this);
_lobby = new Lobby(authorizationProvider, _roomManager, this); _lobby = new Lobby(authorizationProvider, _roomManager, this);
_gameLoopTimer = new Stopwatch(); _gameLoopTimer = new Stopwatch();
_packetsTimer = new Stopwatch(); _statisticsTimer = new Stopwatch();
_socketByRooms = new Dictionary<uint, GameRoom>();
_thread = new Thread(Execute); _thread = new Thread(Execute);
_thread.Name = "Game Thread"; _thread.Name = "Game Thread";
@@ -48,10 +48,10 @@ namespace Ragon.Core
public void Start() public void Start()
{ {
Server.Start(_serverConfiguration.Port); Server.Start(_configuration.Port, _configuration.MaxConnections);
_gameLoopTimer.Start(); _gameLoopTimer.Start();
_packetsTimer.Start(); _statisticsTimer.Start();
_thread.Start(); _thread.Start();
} }
@@ -60,7 +60,7 @@ namespace Ragon.Core
Server.Stop(); Server.Stop();
_gameLoopTimer.Stop(); _gameLoopTimer.Stop();
_packetsTimer.Stop(); _statisticsTimer.Stop();
_thread.Interrupt(); _thread.Interrupt();
} }
@@ -69,7 +69,8 @@ namespace Ragon.Core
while (true) while (true)
{ {
Server.Process(); Server.Process();
Dispatcher.Process();
_dispatcherInternal.Process();
var elapsedMilliseconds = _gameLoopTimer.ElapsedMilliseconds; var elapsedMilliseconds = _gameLoopTimer.ElapsedMilliseconds;
if (elapsedMilliseconds > _deltaTime) if (elapsedMilliseconds > _deltaTime)
@@ -79,39 +80,28 @@ namespace Ragon.Core
continue; 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"); _logger.Trace($"Rooms: {_roomManager.Rooms.Count} Clients: {_roomManager.RoomsBySocket.Count}");
_packetsTimer.Restart(); _statisticsTimer.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 OnEvent(Event evnt) 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)) var player = _lobby.AuthorizationManager.GetPlayer(evnt.Peer.ID);
room.Leave(evnt.Peer.ID); if (player != null)
_roomManager.Left(player, Array.Empty<byte>());
_lobby.OnDisconnected(evnt.Peer.ID); _lobby.OnDisconnected(evnt.Peer.ID);
} }
if (evnt.Type == ENet.EventType.Receive) if (evnt.Type == EventType.Receive)
{ {
_packets += 1;
try try
{ {
var peerId = evnt.Peer.ID; var peerId = evnt.Peer.ID;
@@ -119,7 +109,7 @@ namespace Ragon.Core
evnt.Packet.CopyTo(dataRaw); evnt.Packet.CopyTo(dataRaw);
var data = new ReadOnlySpan<byte>(dataRaw); var data = new ReadOnlySpan<byte>(dataRaw);
if (_socketByRooms.TryGetValue(peerId, out var room)) if (_roomManager.RoomsBySocket.TryGetValue(peerId, out var room))
{ {
room.ProcessEvent(peerId, data); room.ProcessEvent(peerId, data);
} }
+1
View File
@@ -11,6 +11,7 @@ public interface IGameRoom
public Player GetPlayerById(uint peerId); public Player GetPlayerById(uint peerId);
public Entity GetEntityById(int entityId); public Entity GetEntityById(int entityId);
public Player GetOwner(); public Player GetOwner();
public IDispatcher GetThreadDispatcher();
public void Send(uint peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable); 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(uint[] peersIds, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable);
+1 -3
View File
@@ -4,8 +4,6 @@ namespace Ragon.Core;
public interface IGameThread public interface IGameThread
{ {
public void Attach(uint peerId, GameRoom room); public IDispatcher ThreadDispatcher { get; }
public void Detach(uint peerId);
public IDispatcher Dispatcher { get; }
public ISocketServer Server { get; } public ISocketServer Server { get; }
} }
+6
View File
@@ -0,0 +1,6 @@
namespace Ragon.Core;
public interface ILobby
{
}
+16 -11
View File
@@ -1,19 +1,21 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Ragon.Common; using Ragon.Common;
namespace Ragon.Core; namespace Ragon.Core;
public class Lobby public class Lobby : ILobby
{ {
private readonly RagonSerializer _serializer; private readonly RagonSerializer _serializer;
private readonly AuthorizationManager _authorizationManager;
private readonly RoomManager _roomManager; private readonly RoomManager _roomManager;
public AuthorizationManager AuthorizationManager { get; private set; }
public Lobby(IAuthorizationProvider provider, RoomManager manager, IGameThread gameThread) public Lobby(IAuthorizationProvider provider, RoomManager manager, IGameThread gameThread)
{ {
_roomManager = manager; _roomManager = manager;
_serializer = new RagonSerializer(); _serializer = new RagonSerializer();
_authorizationManager = new AuthorizationManager(provider, gameThread, this, _serializer); AuthorizationManager = new AuthorizationManager(provider, gameThread, this, _serializer);
} }
public void ProcessEvent(uint peerId, ReadOnlySpan<byte> data) public void ProcessEvent(uint peerId, ReadOnlySpan<byte> data)
@@ -31,14 +33,15 @@ public class Lobby
var key = _serializer.ReadString(); var key = _serializer.ReadString();
var playerName = _serializer.ReadString(); var playerName = _serializer.ReadString();
var protocol = _serializer.ReadByte(); var protocol = _serializer.ReadByte();
_authorizationManager.OnAuthorization(peerId, key, playerName, protocol); AuthorizationManager.OnAuthorization(peerId, key, playerName, protocol);
break; break;
} }
case RagonOperation.JOIN_ROOM: case RagonOperation.JOIN_ROOM:
{ {
var roomId = _serializer.ReadString(); var roomId = _serializer.ReadString();
var player = _authorizationManager.GetPlayer(peerId); var player = AuthorizationManager.GetPlayer(peerId);
_roomManager.Join(player, roomId, Array.Empty<byte>()); if (player != null)
_roomManager.Join(player, roomId, Array.Empty<byte>());
break; break;
} }
case RagonOperation.JOIN_OR_CREATE_ROOM: case RagonOperation.JOIN_OR_CREATE_ROOM:
@@ -46,14 +49,16 @@ public class Lobby
var map = _serializer.ReadString(); var map = _serializer.ReadString();
var min = _serializer.ReadInt(); var min = _serializer.ReadInt();
var max = _serializer.ReadInt(); var max = _serializer.ReadInt();
var player = _authorizationManager.GetPlayer(peerId); var player = AuthorizationManager.GetPlayer(peerId);
_roomManager.JoinOrCreate(player, map, min, max, Array.Empty<byte>()); if (player != null)
_roomManager.JoinOrCreate(player, map, min, max, Array.Empty<byte>());
break; break;
} }
case RagonOperation.LEAVE_ROOM: case RagonOperation.LEAVE_ROOM:
{ {
var player = _authorizationManager.GetPlayer(peerId); var player = AuthorizationManager.GetPlayer(peerId);
_roomManager.Left(player, Array.Empty<byte>()); if (player != null)
_roomManager.Left(player, Array.Empty<byte>());
break; break;
} }
} }
@@ -61,6 +66,6 @@ public class Lobby
public void OnDisconnected(uint peerId) public void OnDisconnected(uint peerId)
{ {
_authorizationManager.Cleanup(peerId); AuthorizationManager.Cleanup(peerId);
} }
} }
+11 -11
View File
@@ -18,12 +18,12 @@ namespace Ragon.Core
private readonly BitBuffer _buffer = new(); private readonly BitBuffer _buffer = new();
private readonly RagonSerializer _serializer = new(); private readonly RagonSerializer _serializer = new();
protected IGameRoom GameRoom { get; private set; } protected IGameRoom GameRoom { get; private set; } = null!;
protected ILogger _logger; protected ILogger Logger = null!;
public void Attach(GameRoom gameRoom) public void Attach(GameRoom gameRoom)
{ {
_logger = LogManager.GetLogger($"Plugin<{GetType().Name}>"); Logger = LogManager.GetLogger($"Plugin<{GetType().Name}>");
GameRoom = gameRoom; GameRoom = gameRoom;
@@ -41,7 +41,7 @@ namespace Ragon.Core
{ {
if (_globalEvents.ContainsKey(evntCode)) if (_globalEvents.ContainsKey(evntCode))
{ {
_logger.Warn($"Event subscriber already added {evntCode}"); Logger.Warn($"Event subscriber already added {evntCode}");
return; return;
} }
@@ -50,7 +50,7 @@ namespace Ragon.Core
{ {
if (raw.Length == 0) if (raw.Length == 0)
{ {
_logger.Warn($"Payload is empty for event {evntCode}"); Logger.Warn($"Payload is empty for event {evntCode}");
return; return;
} }
@@ -65,7 +65,7 @@ namespace Ragon.Core
{ {
if (_globalEvents.ContainsKey(evntCode)) if (_globalEvents.ContainsKey(evntCode))
{ {
_logger.Warn($"Event subscriber already added {evntCode}"); Logger.Warn($"Event subscriber already added {evntCode}");
return; return;
} }
@@ -78,7 +78,7 @@ namespace Ragon.Core
{ {
if (_entityEvents[entity.EntityId].ContainsKey(evntCode)) 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; return;
} }
@@ -87,7 +87,7 @@ namespace Ragon.Core
{ {
if (raw.Length == 0) 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; return;
} }
@@ -107,7 +107,7 @@ namespace Ragon.Core
{ {
if (raw.Length == 0) 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; return;
} }
@@ -125,7 +125,7 @@ namespace Ragon.Core
{ {
if (_entityEvents[entity.EntityId].ContainsKey(evntCode)) 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; return;
} }
@@ -265,7 +265,7 @@ namespace Ragon.Core
{ {
} }
public virtual void OnTick(ulong ticks, float deltaTime) public virtual void OnTick(float deltaTime)
{ {
} }
+18 -7
View File
@@ -1,4 +1,3 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
@@ -12,12 +11,17 @@ public class RoomManager
private readonly IGameThread _gameThread; private readonly IGameThread _gameThread;
private readonly PluginFactory _factory; private readonly PluginFactory _factory;
private readonly Logger _logger = LogManager.GetCurrentClassLogger(); private readonly Logger _logger = LogManager.GetCurrentClassLogger();
private List<GameRoom> _rooms = new List<GameRoom>(); private readonly List<GameRoom> _rooms = new List<GameRoom>();
private readonly Dictionary<uint, GameRoom> _roomsBySocket;
public IReadOnlyDictionary<uint, GameRoom> RoomsBySocket => _roomsBySocket;
public IReadOnlyList<GameRoom> Rooms => _rooms;
public RoomManager(PluginFactory factory, IGameThread gameThread) public RoomManager(PluginFactory factory, IGameThread gameThread)
{ {
_gameThread = gameThread; _gameThread = gameThread;
_factory = factory; _factory = factory;
_roomsBySocket = new Dictionary<uint, GameRoom>();
} }
public void Join(Player player, string roomId, byte[] payload) public void Join(Player player, string roomId, byte[] payload)
@@ -29,7 +33,7 @@ public class RoomManager
if (existRoom.Id == roomId && existRoom.PlayersCount < existRoom.PlayersMax) if (existRoom.Id == roomId && existRoom.PlayersCount < existRoom.PlayersMax)
{ {
existRoom.Joined(player, payload); existRoom.Joined(player, payload);
_gameThread.Attach(player.PeerId, existRoom); _roomsBySocket.Add(player.PeerId, existRoom);
break; break;
} }
} }
@@ -45,8 +49,7 @@ public class RoomManager
if (existRoom.Map == map && existRoom.PlayersCount < existRoom.PlayersMax) if (existRoom.Map == map && existRoom.PlayersCount < existRoom.PlayersMax)
{ {
existRoom.Joined(player, payload); existRoom.Joined(player, payload);
_gameThread.Attach(player.PeerId, existRoom); _roomsBySocket.Add(player.PeerId, existRoom);
return; return;
} }
} }
@@ -60,13 +63,21 @@ public class RoomManager
room.Joined(player, payload); room.Joined(player, payload);
room.Start(); room.Start();
_gameThread.Attach(player.PeerId, room); _roomsBySocket.Add(player.PeerId, room);
_rooms.Add(room); _rooms.Add(room);
} }
public void Left(Player player, byte[] payload) 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) public void Tick(float deltaTime)
@@ -1,7 +1,4 @@
using System; using System;
using System.Diagnostics;
using System.Threading;
using DisruptorUnity3d;
using ENet; using ENet;
using NLog; using NLog;
@@ -33,14 +30,14 @@ namespace Ragon.Core
_handler = handler; _handler = handler;
} }
public void Start(ushort port) public void Start(ushort port, int connections)
{ {
_address = default; _address = default;
_address.Port = port; _address.Port = port;
_peers = new Peer[2048]; _peers = new Peer[connections];
_host = new Host(); _host = new Host();
_host.Create(_address, 2048, 2, 0, 0, 1024 * 1024); _host.Create(_address, connections, 2, 0, 0, 1024 * 1024);
Status = Status.Listening; Status = Status.Listening;
_logger.Info($"Network listening on {port}"); _logger.Info($"Network listening on {port}");
@@ -79,7 +76,7 @@ namespace Ragon.Core
{ {
if (_host.CheckEvents(out _netEvent) <= 0) if (_host.CheckEvents(out _netEvent) <= 0)
{ {
if (_host.Service(0, out _netEvent) <= 0) if (_host.Service(15, out _netEvent) <= 0)
break; break;
polled = true; polled = true;
@@ -87,27 +84,27 @@ namespace Ragon.Core
switch (_netEvent.Type) switch (_netEvent.Type)
{ {
case ENet.EventType.None: case EventType.None:
Console.WriteLine("None event"); Console.WriteLine("None event");
break; break;
case ENet.EventType.Connect: case EventType.Connect:
{ {
_peers[_netEvent.Peer.ID] = _netEvent.Peer; _peers[_netEvent.Peer.ID] = _netEvent.Peer;
_handler.OnEvent(_netEvent); _handler.OnEvent(_netEvent);
break; break;
} }
case ENet.EventType.Disconnect: case EventType.Disconnect:
{ {
_handler.OnEvent(_netEvent); _handler.OnEvent(_netEvent);
break; break;
} }
case ENet.EventType.Timeout: case EventType.Timeout:
{ {
_handler.OnEvent(_netEvent); _handler.OnEvent(_netEvent);
break; break;
} }
case ENet.EventType.Receive: case EventType.Receive:
{ {
_handler.OnEvent(_netEvent); _handler.OnEvent(_netEvent);
_netEvent.Packet.Dispose(); _netEvent.Packet.Dispose();
+1 -1
View File
@@ -2,7 +2,7 @@ namespace Ragon.Core;
public interface ISocketServer public interface ISocketServer
{ {
public void Start(ushort port); public void Start(ushort port, int connections);
public void Process(); public void Process();
public void Stop(); public void Stop();
public void Send(uint peerId, byte[] data, DeliveryType type); public void Send(uint peerId, byte[] data, DeliveryType type);
@@ -3,19 +3,20 @@ using System.Collections.Generic;
namespace Ragon.Core; namespace Ragon.Core;
public class Dispatcher: IDispatcher public class Dispatcher: IDispatcher, IDispatcherInternal
{ {
public Queue<DispatcherTask> _actions = new Queue<DispatcherTask>(); public Queue<Action> _actions = new Queue<Action>();
public void Dispatch(Action action) public void Dispatch(Action action)
{ {
lock (_actions) lock (_actions)
_actions.Enqueue(new DispatcherTask() { Action = action }); _actions.Enqueue(action);
} }
public void Process() public void Process()
{ {
lock(_actions) lock(_actions)
while(_actions.TryDequeue(out var action)) while(_actions.TryDequeue(out var action))
action.Execute(); action?.Invoke();
} }
} }
@@ -5,5 +5,4 @@ namespace Ragon.Core;
public interface IDispatcher public interface IDispatcher
{ {
public void Dispatch(Action action); public void Dispatch(Action action);
public void Process();
} }
@@ -0,0 +1,6 @@
namespace Ragon.Core;
public interface IDispatcherInternal
{
public void Process();
}
+8 -12
View File
@@ -4,15 +4,14 @@
## Ragon Server ## 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.
<a href="https://ragon-server.com/docs/category/basics">Documentation</a>
<a href="">Documentation</a>
<br> <br>
<a href="">Get started</a> <a href="https://ragon-server.com/docs/get-started">Get started</a>
### Features: ### Features:
- Effective
- Free - Free
- Simple matchmaking - Simple matchmaking
- Flexiable API - Flexiable API
@@ -20,18 +19,16 @@ Ragon is fully free high perfomance room based game server with plugin based arc
- Extendable room logic via plugin - Extendable room logic via plugin
- Custom authorization - Custom authorization
- No CCU limitations* - No CCU limitations*
- Multi-threaded
- Engine agnostic - Engine agnostic
- Support any client architecture (MonoBehaviors, ECS) - Support any client architecture (MonoBehaviors, ECS)
- UDP - RUDP
### Roadmap: ### Roadmap:
- Allow customize matchmaking
- Refactoring some moments(a lot duplications of code, etc...)
- Use native memory - Use native memory
- Reduce allocations - Reduce allocations
- Dashboard for monitoring entities and players in realtime - Dashboard for monitoring entities and players in realtime
- Statistics for monitoring state of server, cpu, memory - Statistics for monitoring state of server, cpu, memory
- Horizontal Scaling
- Docker support - Docker support
- Add additional API to plugin system - 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 - .NET 6.0
### Dependencies ### Dependencies
* ENet-Sharp v2.4.8 * ENet-Sharp [v2.4.8]
* NetStack latest * NetStack [latest]
* RingBuffer-Unity3D latest
### License ### License
SSPL-1.0 SSPL-1.0