Compare commits

...

7 Commits

Author SHA1 Message Date
edmand46 5f3b2d7ed8 - Added support entities on scene
- Improved performance
2022-07-13 07:56:44 +04:00
edmand46 773adeefb2 feat: added scheduler, minor fixes 2022-07-02 11:02:09 +04:00
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
36 changed files with 402 additions and 692 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();
}
}
}
}
+1
View File
@@ -20,6 +20,7 @@ namespace Ragon.Common
PLAYER_LEAVED, PLAYER_LEAVED,
CREATE_ENTITY, CREATE_ENTITY,
CREATE_STATIC_ENTITY,
DESTROY_ENTITY, DESTROY_ENTITY,
SNAPSHOT, SNAPSHOT,
@@ -4,19 +4,19 @@ namespace Game.Source
{ {
public class SimplePlugin: PluginBase public class SimplePlugin: PluginBase
{ {
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})");
} }
+7 -4
View File
@@ -1,8 +1,11 @@
{ {
"key": "defaultkey", "key": "defaultkey",
"tickRate": 30, "statisticsInterval": 5,
"sendRate": 30,
"port": 4444,
"skipTimeout": 60, "skipTimeout": 60,
"server": { "reconnectTimeout": 300,
"port": 4444 "maxConnections": 4095,
} "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("49.12.70.233", 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,12 +27,10 @@ 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)); });
} }
@@ -77,9 +75,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)
+8 -8
View File
@@ -2,17 +2,17 @@
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 ReconnectTimeout;
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.9-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();
}
}
+3 -1
View File
@@ -6,15 +6,17 @@ public class Entity
{ {
private static int _idGenerator = 0; private static int _idGenerator = 0;
public int EntityId { get; private set; } public int EntityId { get; private set; }
public int StaticId { get; private set; }
public uint OwnerId { get; private set; } public uint OwnerId { get; private set; }
public ushort EntityType { get; private set; } public ushort EntityType { get; private set; }
public RagonAuthority Authority { get; private set; } public RagonAuthority Authority { get; private set; }
public EntityState State { get; private set; } public EntityState State { get; private set; }
public EntityState Payload { get; private set; } public EntityState Payload { get; private set; }
public Entity(uint ownerId, ushort entityType, RagonAuthority stateAuthority, RagonAuthority eventAuthority) public Entity(uint ownerId, ushort entityType, int staticId, RagonAuthority stateAuthority, RagonAuthority eventAuthority)
{ {
OwnerId = ownerId; OwnerId = ownerId;
StaticId = staticId;
EntityType = entityType; EntityType = entityType;
EntityId = _idGenerator++; EntityId = _idGenerator++;
State = new EntityState(stateAuthority); State = new EntityState(stateAuthority);
-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,
}
}
+89 -21
View File
@@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NLog; using NLog;
using Ragon.Common; using Ragon.Common;
@@ -19,10 +18,10 @@ 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 IScheduler _scheduler;
private readonly IGameThread _gameThread; private readonly IGameThread _gameThread;
private readonly PluginBase _plugin;
private readonly RagonSerializer _serializer = new(512); private readonly RagonSerializer _serializer = new(512);
// Cache // Cache
@@ -34,13 +33,13 @@ namespace Ragon.Core
{ {
_gameThread = gameThread; _gameThread = gameThread;
_plugin = pluginBase; _plugin = pluginBase;
_scheduler = new Scheduler();
Map = map; Map = map;
PlayersMin = min; PlayersMin = min;
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 +119,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);
@@ -157,11 +158,13 @@ namespace Ragon.Core
var entityStateData = _serializer.ReadData(_serializer.Size); var entityStateData = _serializer.ReadData(_serializer.Size);
ent.State.Write(ref entityStateData); ent.State.Write(ref entityStateData);
} }
break; break;
} }
case RagonOperation.REPLICATE_ENTITY_EVENT: case RagonOperation.REPLICATE_ENTITY_EVENT:
{ {
var evntId = _serializer.ReadUShort(); var evntId = _serializer.ReadUShort();
var evntMode = _serializer.ReadByte();
var entityId = _serializer.ReadInt(); var entityId = _serializer.ReadInt();
if (!_entities.TryGetValue(entityId, out var ent)) if (!_entities.TryGetValue(entityId, out var ent))
@@ -181,6 +184,8 @@ namespace Ragon.Core
_serializer.Clear(); _serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT); _serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
_serializer.WriteUShort(evntId); _serializer.WriteUShort(evntId);
_serializer.WriteUShort((ushort) peerId);
_serializer.WriteByte(evntMode);
_serializer.WriteInt(entityId); _serializer.WriteInt(entityId);
_serializer.WriteData(ref payload); _serializer.WriteData(ref payload);
var sendData = _serializer.ToArray(); var sendData = _serializer.ToArray();
@@ -191,7 +196,7 @@ namespace Ragon.Core
case RagonOperation.REPLICATE_EVENT: case RagonOperation.REPLICATE_EVENT:
{ {
var evntId = _serializer.ReadUShort(); var evntId = _serializer.ReadUShort();
var evntMode = _serializer.ReadByte();
Span<byte> payloadRaw = stackalloc byte[_serializer.Size]; Span<byte> payloadRaw = stackalloc byte[_serializer.Size];
var payloadData = _serializer.ReadData(_serializer.Size); var payloadData = _serializer.ReadData(_serializer.Size);
payloadData.CopyTo(payloadRaw); payloadData.CopyTo(payloadRaw);
@@ -202,6 +207,8 @@ namespace Ragon.Core
_serializer.Clear(); _serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_EVENT); _serializer.WriteOperation(RagonOperation.REPLICATE_EVENT);
_serializer.WriteUShort((ushort) peerId);
_serializer.WriteByte(evntMode);
_serializer.WriteUShort(evntId); _serializer.WriteUShort(evntId);
_serializer.WriteData(ref payload); _serializer.WriteData(ref payload);
@@ -209,12 +216,54 @@ namespace Ragon.Core
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable); Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
break; break;
} }
case RagonOperation.CREATE_STATIC_ENTITY:
{
var entityType = _serializer.ReadUShort();
var staticId = _serializer.ReadUShort();
var stateAuthority = (RagonAuthority) _serializer.ReadByte();
var eventAuthority = (RagonAuthority) _serializer.ReadByte();
var entity = new Entity(peerId, entityType, staticId, stateAuthority, eventAuthority);
{
var entityPayload = _serializer.ReadData(_serializer.Size);
entity.Payload.Write(ref entityPayload);
}
var player = _players[peerId];
player.Entities.Add(entity);
player.EntitiesIds.Add(entity.EntityId);
var ownerId = (ushort) peerId;
_entities.Add(entity.EntityId, entity);
_entitiesAll = _entities.Values.ToArray();
_plugin.OnEntityCreated(player, entity);
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.CREATE_STATIC_ENTITY);
_serializer.WriteUShort(entityType);
_serializer.WriteUShort(staticId);
_serializer.WriteByte((byte) stateAuthority);
_serializer.WriteByte((byte) eventAuthority);
_serializer.WriteInt(entity.EntityId);
_serializer.WriteUShort(ownerId);
{
var entityPayload = entity.Payload.Read();
_serializer.WriteData(ref entityPayload);
}
var sendData = _serializer.ToArray();
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
break;
}
case RagonOperation.CREATE_ENTITY: case RagonOperation.CREATE_ENTITY:
{ {
var entityType = _serializer.ReadUShort(); var entityType = _serializer.ReadUShort();
var stateAuthority = (RagonAuthority) _serializer.ReadByte(); var stateAuthority = (RagonAuthority) _serializer.ReadByte();
var eventAuthority = (RagonAuthority) _serializer.ReadByte(); var eventAuthority = (RagonAuthority) _serializer.ReadByte();
var entity = new Entity(peerId, entityType, stateAuthority, eventAuthority); var entity = new Entity(peerId, entityType, -1, stateAuthority, eventAuthority);
{ {
var entityPayload = _serializer.ReadData(_serializer.Size); var entityPayload = _serializer.ReadData(_serializer.Size);
@@ -292,13 +341,35 @@ namespace Ragon.Core
_serializer.WriteString(_players[playerPeerId].PlayerName); _serializer.WriteString(_players[playerPeerId].PlayerName);
} }
_serializer.WriteInt(_entitiesAll.Length); var dynamicCount = _entitiesAll.Where(e => e.StaticId == -1).ToArray();
foreach (var entity in _entitiesAll) _serializer.WriteInt(dynamicCount.Length);
foreach (var entity in dynamicCount)
{
if (entity.StaticId != -1) continue;
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);
_serializer.WriteUShort((ushort) payload.Length);
_serializer.WriteData(ref payload);
_serializer.WriteUShort((ushort) state.Length);
_serializer.WriteData(ref state);
}
var staticCount = _entitiesAll.Where(e => e.StaticId != -1).ToArray();
_serializer.WriteInt(staticCount.Length);
foreach (var entity in staticCount)
{ {
var payload = entity.Payload.Read(); var payload = entity.Payload.Read();
var state = entity.State.Read(); var state = entity.State.Read();
_serializer.WriteInt(entity.EntityId); _serializer.WriteInt(entity.EntityId);
_serializer.WriteUShort((ushort) entity.StaticId);
_serializer.WriteByte((byte) entity.State.Authority); _serializer.WriteByte((byte) entity.State.Authority);
_serializer.WriteByte((byte) entity.Authority); _serializer.WriteByte((byte) entity.Authority);
_serializer.WriteUShort(entity.EntityType); _serializer.WriteUShort(entity.EntityType);
@@ -323,8 +394,7 @@ namespace Ragon.Core
public void Tick(float deltaTime) public void Tick(float deltaTime)
{ {
_ticks++; _scheduler.Tick(deltaTime);
_plugin.OnTick(_ticks, deltaTime);
foreach (var entity in _entitiesAll) foreach (var entity in _entitiesAll)
{ {
@@ -347,15 +417,15 @@ 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"); foreach (var peerId in _allPlayers)
_plugin.OnStop(); _gameThread.Server.Disconnect(peerId, 0);
_plugin.OnStop();
_plugin.Detach(); _plugin.Detach();
} }
@@ -365,6 +435,10 @@ namespace Ragon.Core
public Player GetOwner() => _players[_owner]; public Player GetOwner() => _players[_owner];
public IDispatcher GetThreadDispatcher() => _gameThread.ThreadDispatcher;
public IScheduler GetScheduler() => _scheduler;
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);
@@ -372,18 +446,12 @@ namespace Ragon.Core
public void Broadcast(uint[] peersIds, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable) public void Broadcast(uint[] peersIds, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
{ {
foreach (var peer in peersIds) _gameThread.Server.Broadcast(peersIds, rawData, deliveryType);
{
_gameThread.Server.Send(peer, rawData, deliveryType);
}
} }
public void Broadcast(byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable) public void Broadcast(byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
{ {
foreach (var peer in _allPlayers) _gameThread.Server.Broadcast(_allPlayers, rawData, deliveryType);
{
_gameThread.Server.Send(peer, 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);
} }
+2
View File
@@ -11,6 +11,8 @@ 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 IScheduler GetScheduler();
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
{
}
+10 -4
View File
@@ -1,13 +1,16 @@
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;
private readonly AuthorizationManager _authorizationManager;
public AuthorizationManager AuthorizationManager => _authorizationManager;
public Lobby(IAuthorizationProvider provider, RoomManager manager, IGameThread gameThread) public Lobby(IAuthorizationProvider provider, RoomManager manager, IGameThread gameThread)
{ {
@@ -38,21 +41,24 @@ public class Lobby
{ {
var roomId = _serializer.ReadString(); var roomId = _serializer.ReadString();
var player = _authorizationManager.GetPlayer(peerId); var player = _authorizationManager.GetPlayer(peerId);
if (player != null)
_roomManager.Join(player, roomId, Array.Empty<byte>()); _roomManager.Join(player, roomId, Array.Empty<byte>());
break; break;
} }
case RagonOperation.JOIN_OR_CREATE_ROOM: case RagonOperation.JOIN_OR_CREATE_ROOM:
{ {
var min = _serializer.ReadUShort();
var max = _serializer.ReadUShort();
var map = _serializer.ReadString(); var map = _serializer.ReadString();
var min = _serializer.ReadInt();
var max = _serializer.ReadInt();
var player = _authorizationManager.GetPlayer(peerId); var player = _authorizationManager.GetPlayer(peerId);
if (player != null)
_roomManager.JoinOrCreate(player, map, min, max, Array.Empty<byte>()); _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);
if (player != null)
_roomManager.Left(player, Array.Empty<byte>()); _roomManager.Left(player, Array.Empty<byte>());
break; break;
} }
+10 -14
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,10 +265,6 @@ namespace Ragon.Core
{ {
} }
public virtual void OnTick(ulong ticks, float deltaTime)
{
}
#endregion #endregion
} }
} }
+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,19 +30,41 @@ 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}");
} }
public void Broadcast(uint[] peersIds, byte[] data, DeliveryType type)
{
var newPacket = new Packet();
var packetFlags = PacketFlags.Instant;
byte channel = 1;
if (type == DeliveryType.Reliable)
{
packetFlags = PacketFlags.Reliable;
channel = 0;
}
else if (type == DeliveryType.Unreliable)
{
channel = 1;
packetFlags = PacketFlags.None;
}
newPacket.Create(data, data.Length, packetFlags);
foreach (var peerId in peersIds)
_peers[peerId].Send(channel, ref newPacket);
}
public void Send(uint peerId, byte[] data, DeliveryType type) public void Send(uint peerId, byte[] data, DeliveryType type)
{ {
var newPacket = new Packet(); var newPacket = new Packet();
@@ -79,7 +98,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 +106,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();
+2 -1
View File
@@ -2,9 +2,10 @@ 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);
public void Broadcast(uint[] peersIds, byte[] data, DeliveryType type);
public void Disconnect(uint peerId, uint errorCode); public void Disconnect(uint peerId, uint errorCode);
} }
@@ -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();
}
+12
View File
@@ -0,0 +1,12 @@
using System;
namespace Ragon.Core;
public interface IScheduler
{
public SchedulerTask Schedule(Action<SchedulerTask> action, float interval, int count = 1);
public SchedulerTask ScheduleForever(Action<SchedulerTask> action, float interval);
public void StopSchedule(SchedulerTask schedulerTask);
public void Tick(float deltaTime);
}
+98
View File
@@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace Ragon.Core
{
public class Scheduler: IScheduler
{
List<SchedulerTask> _scheduledTasks;
public Scheduler(int defaultCapacity = 100)
{
_scheduledTasks = new List<SchedulerTask>(defaultCapacity);
}
public SchedulerTask Schedule(Action<SchedulerTask> action, float interval, int count = 1)
{
var newTask = new SchedulerTask(action, interval, count - 1);
_scheduledTasks.Add(newTask);
return newTask;
}
public SchedulerTask ScheduleForever(Action<SchedulerTask> action, float interval)
{
var newTask = new SchedulerTask(action, interval, -1);
_scheduledTasks.Add(newTask);
return newTask;
}
public void StopSchedule(SchedulerTask schedulerTask)
{
if (_scheduledTasks.Contains(schedulerTask))
_scheduledTasks.Remove(schedulerTask);
}
public void Tick(float deltaTime)
{
for (int i = _scheduledTasks.Count - 1; i >= 0; i--)
{
var scheduledTask = _scheduledTasks[i];
scheduledTask.Tick(deltaTime);
if (!scheduledTask.IsActive)
_scheduledTasks.Remove(scheduledTask);
}
}
public void Dispose()
{
_scheduledTasks.Clear();
}
}
public class SchedulerTask
{
private Action<SchedulerTask> _action;
private float _timer = 0;
private float _interval = 0;
private int _repeats = 0;
private bool _active;
public int Repeats => _repeats;
public bool IsActive => _active;
public SchedulerTask(Action<SchedulerTask> task, float interval, int repeatCount = 0)
{
_action = task;
_interval = interval;
_timer = 0;
_active = true;
_repeats = repeatCount;
}
public void Tick(float deltaTime)
{
_timer += deltaTime;
if (_timer >= _interval)
{
_action.Invoke(this);
if (_repeats == -1)
{
_timer = 0;
return;
}
if (_repeats > 0)
{
_timer = 0;
_repeats--;
return;
}
if (_repeats == 0)
_active = false;
}
}
}
}
+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