refactoring: heap -> span

This commit is contained in:
2022-04-30 08:20:17 +04:00
parent 9ab5d4aca5
commit 2dd81cd859
36 changed files with 494 additions and 489 deletions
@@ -19,4 +19,8 @@
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Source\Events" />
</ItemGroup>
</Project> </Project>
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
@@ -1,4 +1,4 @@
using System; using System;
using Game.Source; using Game.Source;
using NetStack.Serialization; using NetStack.Serialization;
using Ragon.Core; using Ragon.Core;
+11
View File
@@ -0,0 +1,11 @@
using Ragon.Core;
namespace Game.Source;
public class GameAuthorizer: AuthorizationManager
{
public override bool OnAuthorize(uint peerId, byte[] payload)
{
return true;
}
}
+11
View File
@@ -0,0 +1,11 @@
using Ragon.Core;
namespace Game.Source
{
public class GameFactory : PluginFactory
{
public string PluginName { get; set; } = "ExamplePlugin";
public PluginBase CreatePlugin(string map) => new ExamplePlugin();
public AuthorizationManager CreateManager() => new GameAuthorizer();
}
}
@@ -1,21 +1,14 @@
using Game.Source.Events; using NLog;
using NLog;
using Ragon.Core; using Ragon.Core;
namespace Game.Source namespace Game.Source
{ {
public class SpawnPlugin: PluginBase public class ExamplePlugin: PluginBase
{ {
private ILogger _logger = LogManager.GetCurrentClassLogger(); private ILogger _logger = LogManager.GetCurrentClassLogger();
public void SpawnEvent(SpawnEvent evnt)
{
}
public override void OnStart() public override void OnStart()
{ {
Subscribe<SpawnEvent>(SpawnEvent);
_logger.Info("Plugin started"); _logger.Info("Plugin started");
} }
@@ -1,4 +1,5 @@
{ {
"apiKey": "123",
"server": { "server": {
"port": 4444, "port": 4444,
"skipTimeout": 60 "skipTimeout": 60
-29
View File
@@ -1,29 +0,0 @@
using System;
using NetStack.Serialization;
namespace Ragon.Common
{
public static class BitBufferExtension
{
public static BitBuffer AddBytes(this BitBuffer buffer, byte[] data)
{
buffer.AddInt(data.Length);
foreach (var b in data)
buffer.AddByte(b);
return buffer;
}
public static byte[] ReadBytes(this BitBuffer buffer)
{
var size = buffer.ReadInt();
var data = new byte[size];
var i = 0;
while (i < size)
data[i] = buffer.ReadByte();
return data;
}
}
}
@@ -1,23 +0,0 @@
using NetStack.Serialization;
namespace Ragon.Core
{
public class AuthorationData : IData
{
public string Login { get; set; }
public string Password { get; set; }
public void Serialize(BitBuffer buffer)
{
buffer.AddString(Login);
buffer.AddString(Password);
}
public void Deserialize(BitBuffer buffer)
{
Login = buffer.ReadString();
Password = buffer.ReadString();
}
}
}
@@ -1,19 +0,0 @@
using NetStack.Serialization;
namespace Ragon.Core
{
public class FindAndJoinData: IData
{
public string Map;
public void Serialize(BitBuffer buffer)
{
buffer.AddString(Map);
}
public void Deserialize(BitBuffer buffer)
{
Map = buffer.ReadString();
}
}
}
@@ -1,19 +0,0 @@
using NetStack.Serialization;
namespace Ragon.Core
{
public class SceneLoadData: IData
{
public string Scene;
public void Serialize(BitBuffer buffer)
{
buffer.AddString(Scene);
}
public void Deserialize(BitBuffer buffer)
{
Scene = buffer.ReadString();
}
}
}
@@ -1,45 +0,0 @@
using NetStack.Serialization;
using Ragon.Common;
namespace Ragon.Core
{
public class EntityData: IData
{
public int EntityId;
public byte[] State;
public void Serialize(BitBuffer buffer)
{
buffer.AddInt(EntityId);
buffer.AddBytes(State);
}
public void Deserialize(BitBuffer buffer)
{
EntityId = buffer.ReadInt();
State = buffer.ReadBytes();
}
}
public class SnapshotData: IData
{
public EntityData[] Entities;
public void Serialize(BitBuffer buffer)
{
buffer.AddInt(Entities.Length);
foreach (var entityData in Entities)
entityData.Serialize(buffer);
}
public void Deserialize(BitBuffer buffer)
{
var entitiesSize = buffer.ReadInt();
var i = 0;
Entities = new EntityData[entitiesSize];
while (i < entitiesSize)
{
Entities[i] = new EntityData();
Entities[i].Deserialize(buffer);
}
}
}
}
@@ -2,7 +2,7 @@ using NetStack.Serialization;
namespace Ragon.Core namespace Ragon.Core
{ {
public interface IData public interface IPacket
{ {
public void Serialize(BitBuffer buffer); public void Serialize(BitBuffer buffer);
public void Deserialize(BitBuffer buffer); public void Deserialize(BitBuffer buffer);
+3 -2
View File
@@ -12,8 +12,8 @@ namespace Ragon.Common.Protocol
LOAD_SCENE, LOAD_SCENE,
SCENE_IS_LOADED, SCENE_IS_LOADED,
PLAYER_CONNECTED, PLAYER_JOINED,
PLAYER_DISCONNECTED, PLAYER_LEAVED,
CREATE_ENTITY, CREATE_ENTITY,
DESTROY_ENTITY, DESTROY_ENTITY,
@@ -24,6 +24,7 @@ namespace Ragon.Common.Protocol
REPLICATE_ENTITY_STATE, REPLICATE_ENTITY_STATE,
REPLICATE_ENTITY_PROPERTY, REPLICATE_ENTITY_PROPERTY,
REPLICATE_ENTITY_EVENT,
REPLICATE_EVENT, REPLICATE_EVENT,
} }
} }
+36
View File
@@ -0,0 +1,36 @@
using System;
using System.Runtime.CompilerServices;
using NetStack.Buffers;
namespace Ragon.Core
{
public static class ProtocolHeader
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteUShort(ushort id, ref Span<byte> data) {
data[0] = (byte)(id & 0x00FF);
data[1] = (byte)((id & 0xFF00) >> 8);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort ReadUShort(ref ReadOnlySpan<byte> data)
{
return (ushort)(data[0] + (data[1] << 8));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteInt(int id, ref Span<byte> data) {
data[0] = (byte)(id & 0x00FF);
data[1] = (byte)((id & 0xFF00) >> 8);
data[2] = (byte)((id & 0xFF00) >> 16);
data[3] = (byte)((id & 0xFF00) >> 24);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ReadInt(ref ReadOnlySpan<byte> data)
{
return (ushort)(data[0] + (data[1] << 8) + (data[2] << 16) + (data[3] << 24));
}
}
}
-55
View File
@@ -1,55 +0,0 @@
using System;
using System.Runtime.CompilerServices;
using NetStack.Buffers;
namespace Ragon.Common.Protocol
{
public static class ProtocolHeader
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteOperation(ushort id, byte[] data) {
data[0] = (byte)(id & 0x00FF);
data[1] = (byte)((id & 0xFF00) >> 8);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort ReadOperation(byte[] data, int offset = 0)
{
return (ushort)(data[offset] + (data[offset + 1] << 8));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteEntity(int id, byte[] data, int offset = 2) {
data[offset] = (byte)(id & 0x00FF);
data[offset + 1] = (byte)((id & 0xFF00) >> 8);
data[offset + 2] = (byte)((id & 0xFF00) >> 16);
data[offset + 3] = (byte)((id & 0xFF00) >> 24);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ReadEntity(byte[] data, int offset = 2)
{
return (ushort)(data[offset] + (data[offset + 1] << 8) + (data[offset + 2] << 16) + (data[offset + 3] << 24));
}
public static int ReadProperty(byte[] data, int offset = 2)
{
return ReadEntity(data, offset);
}
public static void WriteProperty(int id, byte[] data)
{
WriteEntity(id, data);
}
public static int ReadEvent(byte[] data, int offset = 2)
{
return ReadEntity(data, offset);
}
public static void WriteEvent(int id, byte[] data)
{
WriteEntity(id, data);
}
}
}
+2
View File
@@ -11,10 +11,12 @@
<OutputPath>/Users/edmand46/UnityProjects/ragon-client/Assets/RagonSDK/</OutputPath> <OutputPath>/Users/edmand46/UnityProjects/ragon-client/Assets/RagonSDK/</OutputPath>
<DebugSymbols>false</DebugSymbols> <DebugSymbols>false</DebugSymbols>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>TRACE;NETSTACK_SPAN</DefineConstants>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<OutputPath>/Users/edmand46/UnityProjects/ragon-client/Assets/RagonSDK/</OutputPath> <OutputPath>/Users/edmand46/UnityProjects/ragon-client/Assets/RagonSDK/</OutputPath>
<DefineConstants>TRACE;NETSTACK_SPAN</DefineConstants>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
+1 -1
View File
@@ -2,7 +2,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon", "Ragon\Ragon.csproj", "{BABA1AF0-CF91-43F2-9577-53800068ACCF}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon", "Ragon\Ragon.csproj", "{BABA1AF0-CF91-43F2-9577-53800068ACCF}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YohohoArenaPlugin", "YohohoArenaPlugin\YohohoArenaPlugin.csproj", "{C2B87ADE-A122-4366-8EB7-24DAE11EBAF0}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Game", "Game\Game.csproj", "{C2B87ADE-A122-4366-8EB7-24DAE11EBAF0}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Common", "Ragon.Common\Ragon.Common.csproj", "{F478B2A2-36F4-43B9-9BB7-382A57C449B2}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Common", "Ragon.Common\Ragon.Common.csproj", "{F478B2A2-36F4-43B9-9BB7-382A57C449B2}"
EndProject EndProject
+14
View File
@@ -6,6 +6,16 @@
<TargetFrameworks>net6.0;netstandard2.1</TargetFrameworks> <TargetFrameworks>net6.0;netstandard2.1</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>TRACE;NETSTACK_SPAN</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>TRACE;NETSTACK_SPAN</DefineConstants>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ENet-CSharp" Version="2.4.8" /> <PackageReference Include="ENet-CSharp" Version="2.4.8" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
@@ -16,4 +26,8 @@
<ProjectReference Include="..\Ragon.Common\Ragon.Common.csproj" /> <ProjectReference Include="..\Ragon.Common\Ragon.Common.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Sources\Protocol" />
</ItemGroup>
</Project> </Project>
-2
View File
@@ -1,2 +0,0 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=sources/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
+9
View File
@@ -0,0 +1,9 @@
namespace Ragon.Core;
public class AuthorizationManager
{
public virtual bool OnAuthorize(uint peerId, byte[] payload)
{
return true;
}
}
+3 -1
View File
@@ -2,6 +2,8 @@
{ {
public class Configuration public class Configuration
{ {
public string[] blacklist; public int UdpPort;
public int WsPort;
public string Transport;
} }
} }
+16 -16
View File
@@ -5,7 +5,6 @@ using System.Threading;
using DisruptorUnity3d; using DisruptorUnity3d;
using ENet; using ENet;
using NLog; using NLog;
using NLog.LayoutRenderers.Wrappers;
namespace Ragon.Core namespace Ragon.Core
{ {
@@ -19,15 +18,6 @@ namespace Ragon.Core
Connected Connected
} }
public enum DeliveryType
{
UnreliableUnsequenced,
UnreliableSequenced,
UnreliableFragmented,
ReliableSequenced
}
public class ENetServer : IDisposable public class ENetServer : IDisposable
{ {
public Status Status { get; private set; } public Status Status { get; private set; }
@@ -70,11 +60,25 @@ namespace Ragon.Core
while (true) while (true)
{ {
while (_sendBuffer.TryDequeue(out var data)) while (_sendBuffer.TryDequeue(out var data))
{
if (data.Type == EventType.DATA)
{ {
var newPacket = new Packet(); var newPacket = new Packet();
newPacket.Create(data.Data, data.Data.Length, PacketFlags.Reliable);
var packetFlags = PacketFlags.Instant;
if (data.Delivery == DeliveryType.Reliable)
packetFlags = PacketFlags.Reliable;
else if (data.Delivery == DeliveryType.Unreliable)
packetFlags = PacketFlags.Instant;
newPacket.Create(data.Data, data.Data.Length, packetFlags);
_peers[data.PeerId].Send(0, ref newPacket); _peers[data.PeerId].Send(0, ref newPacket);
} }
else if (data.Type == EventType.DISCONNECTED)
{
_peers[data.PeerId].DisconnectNow(0);
}
}
bool polled = false; bool polled = false;
while (!polled) while (!polled)
@@ -96,7 +100,6 @@ namespace Ragon.Core
case ENet.EventType.Connect: case ENet.EventType.Connect:
{ {
var @event = new Event {PeerId = _netEvent.Peer.ID, Type = EventType.CONNECTED}; var @event = new Event {PeerId = _netEvent.Peer.ID, Type = EventType.CONNECTED};
// Console.WriteLine("Client connected - ID: " + _netEvent.Peer.ID + ", IP: " + _netEvent.Peer.IP);
_peers[_netEvent.Peer.ID] = _netEvent.Peer; _peers[_netEvent.Peer.ID] = _netEvent.Peer;
_receiveBuffer.Enqueue(@event); _receiveBuffer.Enqueue(@event);
break; break;
@@ -104,22 +107,19 @@ namespace Ragon.Core
case ENet.EventType.Disconnect: case ENet.EventType.Disconnect:
{ {
var @event = new Event {PeerId = _netEvent.Peer.ID, Type = EventType.DISCONNECTED}; var @event = new Event {PeerId = _netEvent.Peer.ID, Type = EventType.DISCONNECTED};
// Console.WriteLine("Client disconnected - ID: " + _netEvent.Peer.ID + ", IP: " + _netEvent.Peer.IP);
_receiveBuffer.Enqueue(@event); _receiveBuffer.Enqueue(@event);
break; break;
} }
case ENet.EventType.Timeout: case ENet.EventType.Timeout:
{ {
// Console.WriteLine("Client timeout - ID: " + _netEvent.Peer.ID + ", IP: " + _netEvent.Peer.IP);
var @event = new Event {PeerId = _netEvent.Peer.ID, Type = EventType.TIMEOUT}; var @event = new Event {PeerId = _netEvent.Peer.ID, Type = EventType.TIMEOUT};
// Console.WriteLine("Client disconnected - ID: " + _netEvent.Peer.ID + ", IP: " + _netEvent.Peer.IP);
_receiveBuffer.Enqueue(@event); _receiveBuffer.Enqueue(@event);
break; break;
} }
case ENet.EventType.Receive: case ENet.EventType.Receive:
{ {
// Console.WriteLine("Packet received from - ID: " + _netEvent.Peer.ID + ", IP: " + _netEvent.Peer.IP + ", Channel ID: " + _netEvent.ChannelID + ", Data length: " + _netEvent.Packet.Length);
var data = new byte[_netEvent.Packet.Length]; var data = new byte[_netEvent.Packet.Length];
_netEvent.Packet.CopyTo(data); _netEvent.Packet.CopyTo(data);
_netEvent.Packet.Dispose(); _netEvent.Packet.Dispose();
+7
View File
@@ -0,0 +1,7 @@
namespace Ragon.Core;
public enum DeliveryType
{
Unreliable,
Reliable
}
+3
View File
@@ -1,8 +1,11 @@
using System;
namespace Ragon.Core namespace Ragon.Core
{ {
public struct Event public struct Event
{ {
public EventType Type; public EventType Type;
public DeliveryType Delivery;
public uint PeerId; public uint PeerId;
public byte[] Data; public byte[] Data;
} }
+39
View File
@@ -0,0 +1,39 @@
using System.Collections.Generic;
using System.Runtime.ExceptionServices;
using DisruptorUnity3d;
namespace Ragon.Core;
public class EventStream
{
private Stack<Event> _pool = new Stack<Event>(1024);
private RingBuffer<Event> _events = new RingBuffer<Event>(1024);
// public ref Event Reserve()
// {
// if (_pool.Count == 0)
// {
// var evnt = new Event();
// return ref evnt;
// }
// // var evnt = _pool.Pop();
// // ref Event evntRef = ref evnt;
// // return evntRef;
// }
// public void Retain(ref Event @event)
// {
//
// }
//
// public void WriteEvent(ref Event evnt)
// {
// // _pool.Push(evnt);
// _events.Enqueue(evnt);
// }
//
// public ref Event ReadEvent()
// {
//
// }
}
+106 -8
View File
@@ -1,25 +1,121 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using NetStack.Serialization;
using Ragon.Common.Protocol;
namespace Ragon.Core namespace Ragon.Core
{ {
public class PluginBase public class PluginBase
{ {
static class Storage<T> private delegate void SubscribeDelegate(Player player, ref ReadOnlySpan<byte> data);
private Dictionary<ushort, SubscribeDelegate> _subscribes = new();
private BitBuffer _buffer = new BitBuffer(1024);
protected Room Room { get; private set; }
public void Attach(Room room) => Room = room;
public void Detach() => _subscribes.Clear();
public void Subscribe<T>(ushort evntCode, Action<Player, T> action) where T: IPacket, new()
{ {
public static Dictionary<Room, Dictionary<ushort, Action<T>>> Subscribes = new(); var data = new T();
_subscribes.Add(evntCode, (Player player, ref ReadOnlySpan<byte> raw) =>
{
_buffer.Clear();
_buffer.FromSpan(ref raw, raw.Length);
data.Deserialize(_buffer);
action.Invoke(player, data);
});
}
// public void Subscribe<T>(RagonOperation operation, Action<Player, Span<byte> action) where T: IPacket, new()
// {
// var data = new T();
// _subscribes.Add(evntCode, (Player player, ref ReadOnlySpan<byte> raw) =>
// {
// _buffer.Clear();
// _buffer.FromSpan(ref raw, raw.Length);
// data.Deserialize(_buffer);
// action.Invoke(player, data);
// });
// }
public void UnsubscribeAll()
{
_subscribes.Clear();
} }
protected Room _room; public bool InternalHandle(uint peerId, ushort evntCode, ref ReadOnlySpan<byte> payload)
// protected Dictionary<ushort, > _subscribes = new Dictionary<ushort,???>();
public void Attach(Room room) => _room = room;
public void Subscribe<T>(ushort evntCode, Action<T> val)
{ {
Storage<T>.Subscribes.Add(_room, val); if (_subscribes.ContainsKey(evntCode))
{
var player = Room.GetPlayerByPeerId(peerId);
_subscribes[evntCode].Invoke(player, ref payload);
return true;
} }
return false;
}
public void Send(Player player, RagonOperation operation, IPacket payload)
{
Send(player.PeerId, operation, payload);
}
public void Broadcast(Player[] players, RagonOperation operation, IPacket payload)
{
var ids = new uint[players.Length];
for (int i = 0; i < players.Length; i++)
ids[i] = players[i].PeerId;
Broadcast(ids, operation, payload);
}
public void Send(uint peerId, RagonOperation operation, IPacket payload)
{
_buffer.Clear();
payload.Serialize(_buffer);
Span<byte> data = stackalloc byte[_buffer.Length + 2];
Span<byte> bufferSpan = data.Slice(2, data.Length - 2);
_buffer.ToSpan(ref bufferSpan);
ProtocolHeader.WriteUShort((ushort) operation, ref data);
Room.Send(peerId, data);
}
public void Broadcast(uint[] peersIds, RagonOperation operation, IPacket payload)
{
_buffer.Clear();
payload.Serialize(_buffer);
Span<byte> data = stackalloc byte[_buffer.Length + 2];
Span<byte> bufferSpan = data.Slice(2, data.Length - 2);
_buffer.ToSpan(ref bufferSpan);
ProtocolHeader.WriteUShort((ushort) operation, ref data);
Room.Broadcast(peersIds, data);
}
#region VIRTUAL
public virtual void OnRoomJoined()
{
}
public virtual void OnRoomLeaved()
{
}
public virtual void OnStart() public virtual void OnStart()
{ {
} }
@@ -32,5 +128,7 @@ namespace Ragon.Core
{ {
} }
#endregion
} }
} }
+4
View File
@@ -2,6 +2,10 @@ namespace Ragon.Core
{ {
public interface PluginFactory public interface PluginFactory
{ {
public string PluginName { get; set; }
public PluginBase CreatePlugin(string map); public PluginBase CreatePlugin(string map);
public AuthorizationManager CreateManager();
} }
} }
+130 -137
View File
@@ -1,9 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
using NetStack.Serialization; using NetStack.Serialization;
using NLog; using NLog;
using NLog.LayoutRenderers;
using Ragon.Common.Protocol; using Ragon.Common.Protocol;
namespace Ragon.Core namespace Ragon.Core
@@ -13,10 +15,7 @@ namespace Ragon.Core
private ILogger _logger = LogManager.GetCurrentClassLogger(); private ILogger _logger = LogManager.GetCurrentClassLogger();
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 BitBuffer _buffer = new BitBuffer(8192);
private byte[] _bytes = new byte[8192];
private readonly PluginBase _plugin; private readonly PluginBase _plugin;
private readonly RoomThread _roomThread; private readonly RoomThread _roomThread;
@@ -25,9 +24,8 @@ namespace Ragon.Core
// Cache // Cache
private uint[] _readyPlayers = Array.Empty<uint>(); private uint[] _readyPlayers = Array.Empty<uint>();
private uint[] _allPlayers = Array.Empty<uint>();
public int Players => _players.Count; private Entity[] _entitiesAll = Array.Empty<Entity>();
public int MaxPlayers { get; } = 0;
public Room(RoomThread roomThread, PluginBase pluginBase, string map) public Room(RoomThread roomThread, PluginBase pluginBase, string map)
{ {
@@ -35,6 +33,7 @@ namespace Ragon.Core
_plugin = pluginBase; _plugin = pluginBase;
_map = map; _map = map;
_logger.Info("Room created");
_plugin.Attach(this); _plugin.Attach(this);
} }
@@ -42,7 +41,7 @@ namespace Ragon.Core
{ {
if (_players.Count == 0) if (_players.Count == 0)
{ {
Owner = peerId; _owner = peerId;
} }
var player = new Player() var player = new Player()
@@ -55,88 +54,112 @@ namespace Ragon.Core
}; };
_players.Add(peerId, player); _players.Add(peerId, player);
_plugin.OnPlayerConnected(player);
var data = new byte[8]; _allPlayers = _players.Select(p => p.Key).ToArray();
ProtocolHeader.WriteEntity((int) peerId, data, 0);
ProtocolHeader.WriteEntity((int) Owner, data, 4);
Send(peerId, RagonOperation.JOIN_ROOM, data);
var sceneRawData = Encoding.UTF8.GetBytes(_map); Span<byte> data = stackalloc byte[10];
Send(peerId, RagonOperation.LOAD_SCENE, sceneRawData); Span<byte> operationData = data.Slice(0, 2);
Span<byte> peerData = data.Slice(2, 4);
Span<byte> ownerData = data.Slice(4, 4);
ProtocolHeader.WriteUShort((ushort) RagonOperation.JOIN_ROOM, ref operationData);
ProtocolHeader.WriteInt((int) peerId, ref peerData);
ProtocolHeader.WriteInt((int) _owner, ref ownerData);
Send(peerId, data);
// var sceneRawData = Encoding.UTF8.GetBytes(_map);
// Send(peerId, RagonOperation.LOAD_SCENE, sceneRawData);
} }
public void Leave(uint peerId) public void Leave(uint peerId)
{ {
if (_players.Remove(peerId, out var player)) if (_players.Remove(peerId, out var player))
{ {
_plugin.OnPlayerDisconnected(player); _allPlayers = _players.Select(p => p.Key).ToArray();
foreach (var entityId in player.EntitiesIds) foreach (var entityId in player.EntitiesIds)
{ {
var entityData = new byte[4]; Span<byte> entityData = stackalloc byte[6];
ProtocolHeader.WriteEntity(entityId, entityData, 0); var operationData = entityData.Slice(0, 2);
Broadcast(_readyPlayers, RagonOperation.DESTROY_ENTITY, entityData);
ProtocolHeader.WriteUShort((ushort) RagonOperation.DESTROY_ENTITY, ref operationData);
ProtocolHeader.WriteInt(entityId, ref entityData);
Broadcast(_allPlayers, entityData);
_entities.Remove(entityId); _entities.Remove(entityId);
} }
} }
} }
public void ProcessEvent(RagonOperation operation, uint peerId, byte[] rawData) public void ProcessEvent(RagonOperation operation, uint peerId, ReadOnlySpan<byte> rawData)
{ {
if (_plugin.InternalHandle(peerId, (ushort) operation, ref rawData)) return;
switch (operation) switch (operation)
{ {
case RagonOperation.REPLICATE_ENTITY_STATE: case RagonOperation.REPLICATE_ENTITY_STATE:
{ {
var entityId = ProtocolHeader.ReadEntity(rawData, 2); var entityData = rawData.Slice(2, 4);
var entityId = ProtocolHeader.ReadInt(ref entityData);
if (_entities.TryGetValue(entityId, out var ent)) if (_entities.TryGetValue(entityId, out var ent))
{ {
var data = new byte[rawData.Length - 6]; // opcode(ushort)(2) + entity(int)(4) ent.State = rawData.Slice(6, rawData.Length - 6).ToArray();
Array.Copy(rawData, 6, data, 0, rawData.Length - 6);
_entities[entityId].State = data;
Broadcast(_readyPlayers, rawData); Span<byte> data = stackalloc byte[rawData.Length];
rawData.CopyTo(data);
Broadcast(_readyPlayers, data);
} }
break; break;
} }
case RagonOperation.REPLICATE_ENTITY_PROPERTY: case RagonOperation.REPLICATE_ENTITY_PROPERTY:
{ {
var entityId = ProtocolHeader.ReadEntity(rawData, 2); var entityData = rawData.Slice(2, 4);
var entityId = ProtocolHeader.ReadInt(ref entityData);
if (_entities.TryGetValue(entityId, out var ent)) if (_entities.TryGetValue(entityId, out var ent))
{ {
var propertyId = ProtocolHeader.ReadProperty(rawData, 6); var propertyData = rawData.Slice(6, 4);
var data = new byte[rawData.Length - 10]; // opcode(ushort)(2) + entity(int)(4) + propertyId(int)(4) var propertyId = ProtocolHeader.ReadInt(ref propertyData);
Array.Copy(rawData, 10, data, 0, rawData.Length - 10); var payload = rawData.Slice(10, rawData.Length - 10).ToArray();
var props = _entities[entityId].Properties; var props = _entities[entityId].Properties;
if (props.ContainsKey(propertyId)) if (props.ContainsKey(propertyId))
{ {
props[propertyId] = data; props[propertyId] = payload;
} }
else else
{ {
props.Add(propertyId, data); props.Add(propertyId, payload);
} }
Broadcast(_readyPlayers, RagonOperation.REPLICATE_ENTITY_PROPERTY, rawData); // Span<byte> sendData = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(rawData), rawData.Length);
Span<byte> sendData = stackalloc byte[rawData.Length];
rawData.CopyTo(sendData);
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
} }
break; break;
} }
case RagonOperation.REPLICATE_EVENT: case RagonOperation.REPLICATE_EVENT:
case RagonOperation.REPLICATE_ENTITY_EVENT:
{ {
Span<byte> data = stackalloc byte[rawData.Length + 4];
Span<byte> peerData = data.Slice(2, 4);
Span<byte> rawDataSlice = data.Slice(4, rawData.Length);
Broadcast(_readyPlayers, rawData); rawData.CopyTo(rawDataSlice);
ProtocolHeader.WriteInt((int) peerId, ref peerData);
Broadcast(_readyPlayers, data, DeliveryType.Reliable);
break; break;
} }
case RagonOperation.CREATE_ENTITY: case RagonOperation.CREATE_ENTITY:
{ {
var entity = new Entity(peerId); var entity = new Entity(peerId);
var data = new byte[rawData.Length - 2]; // opcode(ushort)(2) var entityPayload = rawData.Slice(2, rawData.Length - 2);
Array.Copy(rawData, 2, data, 0, rawData.Length - 2); entity.State = entityPayload.ToArray();
entity.State = data;
entity.Properties = new Dictionary<int, byte[]>(); entity.Properties = new Dictionary<int, byte[]>();
var player = _players[peerId]; var player = _players[peerId];
@@ -144,21 +167,27 @@ namespace Ragon.Core
player.EntitiesIds.Add(entity.EntityId); player.EntitiesIds.Add(entity.EntityId);
_entities.Add(entity.EntityId, entity); _entities.Add(entity.EntityId, entity);
_entitiesAll = _entities.Values.ToArray();
var entityData = new byte[entity.State.Length + 8]; Span<byte> data = stackalloc byte[entityPayload.Length + 10];
ProtocolHeader.WriteEntity(entity.EntityId, entityData, 0); var operationData = data.Slice(0, 2);
ProtocolHeader.WriteEntity((int) peerId, entityData, 4); var entityData = data.Slice(2, 4);
var peerData = data.Slice(6, 4);
var payload = data.Slice(10, entityPayload.Length);
Array.Copy(entity.State, 0, entityData, 8, entity.State.Length); entityPayload.CopyTo(payload);
_logger.Trace("Create entity Owner:" + peerId + " Id: " + entity.EntityId); ProtocolHeader.WriteUShort((ushort) RagonOperation.CREATE_ENTITY, ref operationData);
ProtocolHeader.WriteInt(entity.EntityId, ref entityData);
ProtocolHeader.WriteInt((int) peerId, ref peerData);
Broadcast(_readyPlayers, RagonOperation.CREATE_ENTITY, entityData); Broadcast(_allPlayers, data, DeliveryType.Reliable);
break; break;
} }
case RagonOperation.DESTROY_ENTITY: case RagonOperation.DESTROY_ENTITY:
{ {
var entityId = ProtocolHeader.ReadEntity(rawData); var entityData = rawData.Slice(2, 4);
var entityId = ProtocolHeader.ReadInt(ref entityData);
if (_entities.TryGetValue(entityId, out var entity)) if (_entities.TryGetValue(entityId, out var entity))
{ {
if (entity.OwnerId == peerId) if (entity.OwnerId == peerId)
@@ -169,8 +198,11 @@ namespace Ragon.Core
player.EntitiesIds.Remove(entity.EntityId); player.EntitiesIds.Remove(entity.EntityId);
_entities.Remove(entityId); _entities.Remove(entityId);
_entitiesAll = _entities.Values.ToArray();
Broadcast(_readyPlayers, rawData); Span<byte> sendData = stackalloc byte[rawData.Length];
rawData.CopyTo(sendData);
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
} }
} }
@@ -178,17 +210,26 @@ namespace Ragon.Core
} }
case RagonOperation.SCENE_IS_LOADED: case RagonOperation.SCENE_IS_LOADED:
{ {
Send(peerId, RagonOperation.RESTORE_BEGIN, Array.Empty<byte>()); Send(peerId, RagonOperation.RESTORE_BEGIN);
foreach (var entity in _entities.Values) foreach (var entity in _entities.Values)
{ {
var entityData = new byte[entity.State.Length + 8]; var entityState = entity.State.AsSpan();
ProtocolHeader.WriteEntity(entity.EntityId, entityData, 0);
ProtocolHeader.WriteEntity((int) entity.OwnerId, entityData, 4);
Array.Copy(entity.State, 0, entityData, 8, entity.State.Length); Span<byte> sendData = stackalloc byte[entity.State.Length + 10];
Send(peerId, RagonOperation.CREATE_ENTITY, entityData); Span<byte> operationData = sendData.Slice(0, 2);
Span<byte> entityData = sendData.Slice(2, 4);
Span<byte> ownerData = sendData.Slice(6, 4);
Span<byte> entityStateData = sendData.Slice(10, entity.State.Length);
ProtocolHeader.WriteUShort((ushort) RagonOperation.CREATE_ENTITY, ref operationData);;
ProtocolHeader.WriteInt(entity.EntityId, ref entityData);
ProtocolHeader.WriteInt((int) entity.OwnerId, ref ownerData);
entityState.CopyTo(entityStateData);
Send(peerId, sendData, DeliveryType.Reliable);
} }
Send(peerId, RagonOperation.RESTORE_END, Array.Empty<byte>()); Send(peerId, RagonOperation.RESTORE_END);
break; break;
} }
case RagonOperation.RESTORED: case RagonOperation.RESTORED:
@@ -203,140 +244,92 @@ namespace Ragon.Core
public void Tick(float deltaTime) public void Tick(float deltaTime)
{ {
_ticks++; _ticks++;
_plugin.OnTick(_ticks, deltaTime); _plugin.OnTick(_ticks, deltaTime);
// for (var i = 0; i < _entitiesAll.Length; i++)
// {
// var entity = _entities[i];
// Span<byte> data = stackalloc byte[entity.State.Length];
// rawData.CopyTo(data);
// Broadcast(_readyPlayers, data);
// }
} }
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();
} }
public void Dispose() public void Dispose()
{ {
_logger.Info("Room destroyed");
_plugin.Detach();
} }
public void Send(uint peerId, RagonOperation operation, byte[] payload) public Player GetPlayerByPeerId(uint peerId) => _players[peerId];
{ public Player GetOwner() => _players[_owner];
if (payload.Length > 0) public int PlayersCount => _players.Count;
{
var data = new byte[payload.Length + 2];
Array.Copy(payload, 0, data, 2, payload.Length); public void Send(uint peerId, RagonOperation operation, DeliveryType deliveryType = DeliveryType.Unreliable)
ProtocolHeader.WriteOperation((ushort) operation, data); {
Span<byte> data = stackalloc byte[2];
ProtocolHeader.WriteUShort((ushort) operation, ref data);
var bytes = data.ToArray();
_roomThread.WriteOutEvent(new Event() _roomThread.WriteOutEvent(new Event()
{ {
PeerId = peerId, PeerId = peerId,
Data = data, Data = bytes,
Type = EventType.DATA, Type = EventType.DATA,
Delivery = deliveryType,
}); });
} }
else
public void Send(uint peerId, Span<byte> payload, DeliveryType deliveryType = DeliveryType.Unreliable)
{ {
var data = new byte[2]; var bytes = payload.ToArray();
ProtocolHeader.WriteOperation((ushort) operation, data);
_roomThread.WriteOutEvent(new Event() _roomThread.WriteOutEvent(new Event()
{ {
PeerId = peerId, PeerId = peerId,
Data = data, Data = bytes,
Type = EventType.DATA,
});
}
}
public void Send(uint peerId, RagonOperation operation, IData payload)
{
_buffer.Clear();
payload.Serialize(_buffer);
_buffer.ToArray(_bytes);
var data = new byte[_buffer.Length + 2];
Array.Copy(_bytes, 0, data, 2, _buffer.Length);
ProtocolHeader.WriteOperation((ushort) operation, data);
_roomThread.WriteOutEvent(new Event()
{
PeerId = peerId,
Data = data,
Type = EventType.DATA, Type = EventType.DATA,
Delivery = deliveryType,
}); });
} }
public void Broadcast(uint[] peersIds, Span<byte> rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
public void Broadcast(uint[] peersIds, RagonOperation operation, IData payload)
{ {
_buffer.Clear(); var bytes = rawData.ToArray();
payload.Serialize(_buffer);
_buffer.ToArray(_bytes);
var data = new byte[_buffer.Length + 2];
Array.Copy(_bytes, 0, data, 2, _buffer.Length);
ProtocolHeader.WriteOperation((ushort) operation, data);
foreach (var peer in peersIds) foreach (var peer in peersIds)
{ {
_roomThread.WriteOutEvent(new Event() _roomThread.WriteOutEvent(new Event()
{ {
PeerId = peer, PeerId = peer,
Data = data, Data = bytes,
Type = EventType.DATA, Type = EventType.DATA,
Delivery = deliveryType,
}); });
} }
} }
public void Broadcast(uint[] peersIds, RagonOperation operation, byte[] payload) public void Broadcast(Span<byte> rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
{
var data = new byte[payload.Length + 2];
Array.Copy(payload, 0, data, 2, payload.Length);
ProtocolHeader.WriteOperation((ushort) operation, data);
foreach (var peer in peersIds)
{
_roomThread.WriteOutEvent(new Event()
{
PeerId = peer,
Data = data,
Type = EventType.DATA,
});
}
}
public void Broadcast(uint[] peersIds, byte[] rawData)
{
foreach (var peer in peersIds)
{
_roomThread.WriteOutEvent(new Event()
{
PeerId = peer,
Data = rawData,
Type = EventType.DATA,
});
}
}
public void Broadcast(byte[] rawData)
{ {
var bytes = rawData.ToArray();
foreach (var player in _players.Values.ToArray()) foreach (var player in _players.Values.ToArray())
{ {
_roomThread.WriteOutEvent(new Event() _roomThread.WriteOutEvent(new Event()
{ {
PeerId = player.PeerId, PeerId = player.PeerId,
Data = rawData, Data = bytes,
Type = EventType.DATA, Type = EventType.DATA,
Delivery = deliveryType,
}); });
} }
} }
+51 -17
View File
@@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using NetStack.Serialization;
using NLog; using NLog;
using Ragon.Common.Protocol; using Ragon.Common.Protocol;
@@ -13,8 +12,8 @@ namespace Ragon.Core
private List<Room> _rooms; private List<Room> _rooms;
private Dictionary<uint, Room> _peersByRoom; private Dictionary<uint, Room> _peersByRoom;
private PluginFactory _factory; private PluginFactory _factory;
private AuthorizationManager _manager;
private RoomThread _roomThread; private RoomThread _roomThread;
private BitBuffer _bitBuffer;
public Action<(uint, Room)> OnJoined; public Action<(uint, Room)> OnJoined;
public Action<(uint, Room)> OnLeaved; public Action<(uint, Room)> OnLeaved;
@@ -23,12 +22,13 @@ namespace Ragon.Core
{ {
_roomThread = roomThread; _roomThread = roomThread;
_factory = factory; _factory = factory;
_manager = _factory.CreateManager();
_rooms = new List<Room>(); _rooms = new List<Room>();
_peersByRoom = new Dictionary<uint, Room>(); _peersByRoom = new Dictionary<uint, Room>();
_bitBuffer = new BitBuffer(1024);
} }
public void ProccessEvent(RagonOperation operation, uint peerId, byte[] payload) public void ProcessEvent(RagonOperation operation, uint peerId, byte[] payload)
{ {
switch (operation) switch (operation)
{ {
@@ -54,28 +54,47 @@ namespace Ragon.Core
public void OnAuthorize(uint peerId, byte[] payload) public void OnAuthorize(uint peerId, byte[] payload)
{ {
_bitBuffer.Clear(); if (_manager.OnAuthorize(peerId, payload))
// _bitBuffer.FromArray(payload, payload.Length); {
Span<byte> data = stackalloc byte[2];
ProtocolHeader.WriteUShort((ushort) RagonOperation.AUTHORIZED_SUCCESS, ref data);
// var authorizePacket = new AuthorationData(); var bytes = data.ToArray();
// authorizePacket.Deserialize(_bitBuffer); _roomThread.WriteOutEvent(new Event()
{
Delivery = DeliveryType.Reliable,
Type = EventType.DATA,
Data = bytes,
PeerId = peerId,
});
}
else
{
Span<byte> data = stackalloc byte[2];
ProtocolHeader.WriteUShort((ushort) RagonOperation.AUTHORIZED_FAILED, ref data);
var data = new byte[2]; var bytes = data.ToArray();
_roomThread.WriteOutEvent(new Event()
ProtocolHeader.WriteOperation((ushort) RagonOperation.AUTHORIZED_SUCCESS, data); {
Delivery = DeliveryType.Reliable,
Type = EventType.DATA,
Data = bytes,
PeerId = peerId,
});
_roomThread.WriteOutEvent(new Event() _roomThread.WriteOutEvent(new Event()
{ {
Type = EventType.DATA, Delivery = DeliveryType.Reliable,
Data = data, Type = EventType.DISCONNECTED,
Data = Array.Empty<byte>(),
PeerId = peerId, PeerId = peerId,
}); });
} }
}
public Room Join(uint peerId, byte[] payload) public Room Join(uint peerId, byte[] payload)
{ {
var map = Encoding.UTF8.GetString(payload); var map = Encoding.UTF8.GetString(payload);
if (_rooms.Count > 0) if (_rooms.Count > 0)
{ {
var existsRoom = _rooms[0]; var existsRoom = _rooms[0];
@@ -89,10 +108,10 @@ namespace Ragon.Core
if (plugin == null) if (plugin == null)
throw new NullReferenceException($"Plugin for map {map} is null"); throw new NullReferenceException($"Plugin for map {map} is null");
_logger.Info("Room created");
var room = new Room(_roomThread, plugin, map); var room = new Room(_roomThread, plugin, map);
room.Joined(peerId, payload); room.Joined(peerId, payload);
room.Start();
_peersByRoom.Add(peerId, room); _peersByRoom.Add(peerId, room);
_rooms.Add(room); _rooms.Add(room);
@@ -103,11 +122,26 @@ namespace Ragon.Core
public Room Left(uint peerId, byte[] payload) public Room Left(uint peerId, byte[] payload)
{ {
_peersByRoom.Remove(peerId, out var room); _peersByRoom.Remove(peerId, out var room);
room?.Leave(peerId);
return room; return room;
} }
public void Disconnected(uint peerId)
{
_peersByRoom.Remove(peerId, out var room);
if (room != null)
{
room.Leave(peerId);
if (room.PlayersCount <= 0)
{
_rooms.Remove(room);
room.Stop();
room.Dispose();
}
}
}
public void Tick(float deltaTime) public void Tick(float deltaTime)
{ {
foreach (Room room in _rooms) foreach (Room room in _rooms)
+4 -5
View File
@@ -62,14 +62,15 @@ namespace Ragon.Core
if (_socketByRooms.ContainsKey(evnt.PeerId)) if (_socketByRooms.ContainsKey(evnt.PeerId))
{ {
_roomManager.Left(evnt.PeerId, Array.Empty<byte>()); _roomManager.Disconnected(evnt.PeerId);
_socketByRooms.Remove(evnt.PeerId); _socketByRooms.Remove(evnt.PeerId);
} }
} }
if (evnt.Type == EventType.DATA) if (evnt.Type == EventType.DATA)
{ {
var operation = (RagonOperation) ProtocolHeader.ReadOperation(evnt.Data, 0); ReadOnlySpan<byte> data = evnt.Data.AsSpan();
var operation = (RagonOperation) ProtocolHeader.ReadUShort(ref data);
if (_socketByRooms.TryGetValue(evnt.PeerId, out var room)) if (_socketByRooms.TryGetValue(evnt.PeerId, out var room))
{ {
room.ProcessEvent(operation, evnt.PeerId, evnt.Data); room.ProcessEvent(operation, evnt.PeerId, evnt.Data);
@@ -77,10 +78,8 @@ namespace Ragon.Core
else else
{ {
var payload = new byte[evnt.Data.Length - 2]; var payload = new byte[evnt.Data.Length - 2];
Array.Copy(evnt.Data, 2, payload, 0, evnt.Data.Length - 2); Array.Copy(evnt.Data, 2, payload, 0, evnt.Data.Length - 2);
_roomManager.ProcessEvent(operation, evnt.PeerId, payload);
_roomManager.ProccessEvent(operation, evnt.PeerId, payload);
} }
} }
} }
-2
View File
@@ -1,2 +0,0 @@
- Send states only on scene loaded
- Add events global and by entity
-46
View File
@@ -1,46 +0,0 @@
using NLog;
using Ragon.Core;
namespace Game.Source
{
public class ArenaPlugin: PluginBase
{
private ILogger _logger = LogManager.GetCurrentClassLogger();
public override void OnStart()
{
// _logger.Info("Plugin started");
}
public override void OnStop()
{
// _logger.Info("Plugin stopped");
}
public long FindPrimeNumber(int n)
{
int count=0;
long a = 2;
while(count<n)
{
long b = 2;
int prime = 1;// to check if found a prime
while(b * b <= a)
{
if(a % b == 0)
{
prime = 0;
break;
}
b++;
}
if(prime > 0)
{
count++;
}
a++;
}
return (--a);
}
}
}
@@ -1,6 +0,0 @@
namespace Game.Source.Events;
public class SpawnEvent
{
}
-17
View File
@@ -1,17 +0,0 @@
using Ragon.Core;
namespace Game.Source
{
public class GameFactory : PluginFactory
{
public PluginBase CreatePlugin(string map)
{
// if (map == "spawn")
// return new SpawnPlugin();
//
// if (map == "arena")
// return new ArenaPlugin();
return new SpawnPlugin();
}
}
}
+8 -1
View File
@@ -2,5 +2,12 @@
<img src="Images/logo.png" width="200" > <img src="Images/logo.png" width="200" >
</p> </p>
Ragon - high perfomance game server with plugin based architecture. Ragon - high perfomance room based game server with plugin based architecture.
Features:
- Room base architecture
- Efficient memory management
- Flexible plugin system
- No CCU limitations
- Fully multithreaded