Compare commits

...

12 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
edmand46 679608bc48 fixed: ci/cd 2022-06-23 22:02:20 +04:00
edmand46 e9c129e2f2 version 2022-06-23 21:57:45 +04:00
edmand46 66181c612a Merge remote-tracking branch 'origin/main' 2022-06-23 21:57:31 +04:00
edmand46 189278e17c added: stress testing 2022-06-23 21:57:24 +04:00
edmand46 08c399a030 added: stress testing 2022-06-23 20:45:57 +04:00
edmand46 05c8904601 wip 2022-06-23 20:45:41 +04:00
edmand46 04b1b16b11 chore: updated ci/cd 2022-05-29 16:56:17 +04:00
58 changed files with 1026 additions and 1218 deletions
+6 -5
View File
@@ -19,7 +19,8 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
tag_name: ${{ github.ref }} tag_name: ${{ github.ref }}
release_name: SimpleServer-${{ github.ref }} release_name: Ragon.SimpleServer-${{ github.ref }}
prerelease: true
build: build:
name: Release name: Release
@@ -52,10 +53,10 @@ jobs:
- name: Build - name: Build
shell: bash shell: bash
run: | run: |
release_name="SimpleServer-${{ env.TAG }}-${{ matrix.target }}" release_name="Ragon.SimpleServer-${{ env.TAG }}-${{ matrix.target }}"
# Build everything # Build everything
dotnet publish SimpleServer/SimpleServer.csproj -c Release --runtime "${{ matrix.target }}" -p:PublishSingleFile=true -o "$release_name" dotnet publish Ragon.SimpleServer/Ragon.SimpleServer.csproj -c Release --runtime "${{ matrix.target }}" -p:PublishSingleFile=true -o "$release_name"
# Pack files # Pack files
7z a -tzip "${release_name}.zip" "./${release_name}/*" 7z a -tzip "${release_name}.zip" "./${release_name}/*"
@@ -68,6 +69,6 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
upload_url: ${{ needs.release.outputs.upload_url }} upload_url: ${{ needs.release.outputs.upload_url }}
asset_path: SimpleServer-${{ env.TAG }}-${{ matrix.target }}.zip asset_path: Ragon.SimpleServer-${{ env.TAG }}-${{ matrix.target }}.zip
asset_name: SimpleServer-${{ env.TAG }}-${{ matrix.target }}.zip asset_name: Ragon.SimpleServer-${{ env.TAG }}-${{ matrix.target }}.zip
asset_content_type: application/zip asset_content_type: application/zip
-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();
}
}
}
}
+12 -3
View File
@@ -14,7 +14,7 @@ namespace Ragon.Common
public int Lenght => _offset; public int Lenght => _offset;
public int Size => _size - _offset; public int Size => _size - _offset;
public RagonSerializer(int capacity = 2048) public RagonSerializer(int capacity = 256)
{ {
_data = new byte[capacity]; _data = new byte[capacity];
_offset = 0; _offset = 0;
@@ -31,7 +31,7 @@ namespace Ragon.Common
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadByte() public byte ReadByte()
{ {
var value = _data[_offset]; var value = _data[_offset];
_offset += 1; _offset += 1;
@@ -171,6 +171,15 @@ namespace Ragon.Common
_size = data.Length; _size = data.Length;
} }
public void FromArray(byte[] data)
{
Clear();
ResizeIfNeed(data.Length);
Buffer.BlockCopy(data, 0, _data, 0, _offset);
_size = data.Length;
}
public byte[] ToArray() public byte[] ToArray()
{ {
var bytes = new byte[_offset]; var bytes = new byte[_offset];
@@ -183,7 +192,7 @@ namespace Ragon.Common
if (_offset + lenght < _data.Length) if (_offset + lenght < _data.Length)
return; return;
var newData = new byte[_data.Length * 2 + lenght]; var newData = new byte[_data.Length * 4 + lenght];
Buffer.BlockCopy(_data, 0, newData, 0, _data.Length); Buffer.BlockCopy(_data, 0, newData, 0, _data.Length);
_data = newData; _data = newData;
} }
@@ -9,9 +9,10 @@ namespace SimpleServer
static void Main(string[] args) static void Main(string[] args)
{ {
var bootstrap = new Bootstrap(); var bootstrap = new Bootstrap();
bootstrap.Configure(new SimplePluginFactory()); var app = bootstrap.Configure(new SimplePluginFactory());
app.Start();
Console.Read(); Console.Read();
app.Stop();
} }
} }
} }
@@ -23,8 +23,4 @@
<ProjectReference Include="..\Ragon\Ragon.csproj" /> <ProjectReference Include="..\Ragon\Ragon.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Source\Managers" />
</ItemGroup>
</Project> </Project>
@@ -0,0 +1,29 @@
using System;
using System.Threading.Tasks;
using Ragon.Core;
namespace Game.Source;
public class AuthorizationProviderByKey: IAuthorizationProvider
{
private Configuration _configuration;
public AuthorizationProviderByKey(Configuration configuration)
{
_configuration = configuration;
}
public async Task OnAuthorizationRequest(string key, string name, byte protocol, byte[] additionalData, Action<string, string> accept, Action<uint> reject)
{
if (key == _configuration.Key)
{
var playerId = Guid.NewGuid().ToString();
var playerName = name;
accept(playerId, playerName);
}
else
{
reject(0);
}
}
}
+27
View File
@@ -0,0 +1,27 @@
using Ragon.Core;
namespace Game.Source
{
public class SimplePlugin: PluginBase
{
public override void OnStart()
{
// _logger.Info("Plugin started");
}
public override void OnStop()
{
// _logger.Info("Plugin stopped");
}
public override void OnPlayerJoined(Player player)
{
// _logger.Info($"Player({player.PlayerName}) joined to Room({GameRoom.Id})");
}
public override void OnPlayerLeaved(Player player)
{
// _logger.Info($"Player({player.PlayerName}) left from Room({GameRoom.Id})");
}
}
}
@@ -5,16 +5,14 @@ namespace Game.Source
{ {
public class SimplePluginFactory : PluginFactory public class SimplePluginFactory : PluginFactory
{ {
public string PluginName { get; set; } = "SimplePlugin";
public PluginBase CreatePlugin(string map) public PluginBase CreatePlugin(string map)
{ {
return new SimplePlugin(); return new SimplePlugin();
} }
public AuthorizationManager CreateManager(Configuration configuration) public IAuthorizationProvider CreateAuthorizationProvider(Configuration configuration)
{ {
return new AuthorizerByKey(configuration); return new AuthorizationProviderByKey(configuration);
} }
} }
} }
+10
View File
@@ -0,0 +1,10 @@
{
"key": "defaultkey",
"statisticsInterval": 5,
"sendRate": 30,
"port": 4444,
"skipTimeout": 60,
"maxConnections": 4095,
"maxPlayersPerRoom": 20,
"maxRooms": 200
}
+206
View File
@@ -0,0 +1,206 @@
using System;
using ENet;
using Ragon.Common;
namespace Stress
{
class SimulationClient
{
public Host Host;
public Peer Peer;
public bool InRoom;
public List<int> Entities = new List<int>();
}
class SimulationThread
{
private List<SimulationClient> _clients = new List<SimulationClient>();
public void Start(string url, ushort port, int numClients)
{
for (var i = 0; i < numClients; i++)
{
var client = CreateClient(url, port);
_clients.Add(client);
}
var thread = new Thread(Execute);
thread.IsBackground = true;
thread.Start();
}
public void Execute()
{
var ragonSerializer = new RagonSerializer();
while (true)
{
foreach (SimulationClient simulationClient in _clients)
{
bool polled = false;
Event netEvent;
while (!polled)
{
if (simulationClient.Host.CheckEvents(out netEvent) <= 0)
{
if (simulationClient.Host.Service(0, out netEvent) <= 0)
break;
polled = true;
}
switch (netEvent.Type)
{
case EventType.None:
break;
case EventType.Connect:
{
ragonSerializer.Clear();
ragonSerializer.WriteOperation(RagonOperation.AUTHORIZE);
ragonSerializer.WriteString("defaultkey");
ragonSerializer.WriteString("Player " + DateTime.Now.Ticks);
ragonSerializer.WriteByte(0);
var sendData = ragonSerializer.ToArray();
var packet = new Packet();
packet.Create(sendData, PacketFlags.Reliable);
simulationClient.Peer.Send(0, ref packet);
Console.WriteLine("Client connected to server");
break;
}
case EventType.Disconnect:
Console.WriteLine("Client disconnected from server");
break;
case EventType.Timeout:
Console.WriteLine("Client connection timeout");
break;
case EventType.Receive:
var data = new byte[netEvent.Packet.Length];
netEvent.Packet.CopyTo(data);
var op = (RagonOperation) data[0];
switch (op)
{
case RagonOperation.AUTHORIZED_SUCCESS:
{
ragonSerializer.Clear();
ragonSerializer.WriteOperation(RagonOperation.JOIN_OR_CREATE_ROOM);
ragonSerializer.WriteString("map");
ragonSerializer.WriteInt(1);
ragonSerializer.WriteInt(5);
var sendData = ragonSerializer.ToArray();
var packet = new Packet();
packet.Create(sendData, PacketFlags.Reliable);
simulationClient.Peer.Send(0, ref packet);
break;
}
case RagonOperation.JOIN_SUCCESS:
{
simulationClient.InRoom = true;
ragonSerializer.Clear();
ragonSerializer.WriteOperation(RagonOperation.SCENE_IS_LOADED);
var sendData = ragonSerializer.ToArray();
var packet = new Packet();
packet.Create(sendData, PacketFlags.Reliable);
simulationClient.Peer.Send(0, ref packet);
break;
}
case RagonOperation.SNAPSHOT:
{
ragonSerializer.Clear();
ragonSerializer.WriteOperation(RagonOperation.CREATE_ENTITY);
ragonSerializer.WriteUShort(0);
ragonSerializer.WriteUShort(0);
ragonSerializer.WriteUShort(0);
var sendData = ragonSerializer.ToArray();
var packet = new Packet();
packet.Create(sendData, PacketFlags.Reliable);
simulationClient.Peer.Send(0, ref packet);
break;
}
case RagonOperation.CREATE_ENTITY:
{
ReadOnlySpan<byte> payload = data.AsSpan().Slice(1, data.Length - 1);
ragonSerializer.Clear();
ragonSerializer.FromSpan(ref payload);
var entityType = ragonSerializer.ReadUShort();
var state = ragonSerializer.ReadByte();
var ennt = ragonSerializer.ReadByte();
var entityId = ragonSerializer.ReadInt();
simulationClient.Entities.Add(entityId);
break;
}
}
// Console.WriteLine(op);
// Console.WriteLine("Packet received from server - Channel ID: " + netEvent.ChannelID + ", Data length: " + netEvent.Packet.Length);
netEvent.Packet.Dispose();
break;
}
}
if (simulationClient.InRoom)
{
foreach (var entity in simulationClient.Entities)
{
ragonSerializer.Clear();
ragonSerializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_STATE);
ragonSerializer.WriteInt(entity);
ragonSerializer.WriteInt(100);
ragonSerializer.WriteInt(200);
ragonSerializer.WriteInt(300);
var sendData = ragonSerializer.ToArray();
var packet = new Packet();
packet.Create(sendData, PacketFlags.Instant);
simulationClient.Peer.Send(1, ref packet);
}
}
}
Thread.Sleep(33);
}
}
SimulationClient CreateClient(string url, ushort port)
{
Host client = new Host();
Address address = new Address();
address.SetHost(url);
address.Port = port;
client.Create();
Console.WriteLine("Created client");
Peer peer = client.Connect(address);
return new SimulationClient() {Host = client, Peer = peer};
}
}
class Program
{
static void Main(string[] args)
{
Library.Initialize();
for (var i = 0; i < 80; i ++)
{
var thread = new SimulationThread();
thread.Start("127.0.0.1", 4444, 50);
Thread.Sleep(300);
}
Console.ReadKey();
Library.Deinitialize();
}
}
}
+19
View File
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>StressTest</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ragon.Common\Ragon.Common.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ENet-CSharp" Version="2.4.8" />
</ItemGroup>
</Project>
+7 -1
View File
@@ -2,10 +2,12 @@
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}") = "SimpleServer", "SimpleServer\SimpleServer.csproj", "{C2B87ADE-A122-4366-8EB7-24DAE11EBAF0}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.SimpleServer", "Ragon.SimpleServer\Ragon.SimpleServer.csproj", "{C2B87ADE-A122-4366-8EB7-24DAE11EBAF0}"
EndProject 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
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Stress", "Ragon.Stress\Ragon.Stress.csproj", "{45E4C6A4-6AB5-4BEA-82DD-1F75C1648EC4}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -24,5 +26,9 @@ Global
{F478B2A2-36F4-43B9-9BB7-382A57C449B2}.Debug|Any CPU.Build.0 = Debug|Any CPU {F478B2A2-36F4-43B9-9BB7-382A57C449B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F478B2A2-36F4-43B9-9BB7-382A57C449B2}.Release|Any CPU.ActiveCfg = Release|Any CPU {F478B2A2-36F4-43B9-9BB7-382A57C449B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F478B2A2-36F4-43B9-9BB7-382A57C449B2}.Release|Any CPU.Build.0 = Release|Any CPU {F478B2A2-36F4-43B9-9BB7-382A57C449B2}.Release|Any CPU.Build.0 = Release|Any CPU
{45E4C6A4-6AB5-4BEA-82DD-1F75C1648EC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{45E4C6A4-6AB5-4BEA-82DD-1F75C1648EC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{45E4C6A4-6AB5-4BEA-82DD-1F75C1648EC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{45E4C6A4-6AB5-4BEA-82DD-1F75C1648EC4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal
+2 -2
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' ">
@@ -18,7 +18,7 @@
<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.2-beta1" />
<PackageReference Include="NLog" Version="5.0.0-rc2" /> <PackageReference Include="NLog" Version="5.0.0-rc2" />
</ItemGroup> </ItemGroup>
+13 -76
View File
@@ -2,97 +2,34 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using ENet;
using NLog; using NLog;
namespace Ragon.Core namespace Ragon.Core
{ {
public class Application : IDisposable public class Application
{ {
private readonly Logger _logger = LogManager.GetCurrentClassLogger(); private readonly Logger _logger = LogManager.GetCurrentClassLogger();
private readonly List<RoomThread> _roomThreads = new(); private readonly GameThread _gameThread;
private readonly Dictionary<uint, RoomThread> _socketByRoomThreads = new();
private readonly Dictionary<RoomThread, int> _roomThreadCounter = new();
private readonly Configuration _configuration;
private readonly ENetServer _socketServer;
private int _roomThreadBalancer = 0;
private Thread _thread; public Application(PluginFactory factory, Configuration configuration)
public Application(PluginFactory factory, Configuration configuration, int threadsCount)
{ {
_socketServer = new ENetServer(); _gameThread = new GameThread(factory, configuration);
_configuration = configuration;
for (var i = 0; i < threadsCount; i++)
{
var roomThread = new RoomThread(factory, configuration);
_roomThreadCounter.Add(roomThread, 0);
_roomThreads.Add(roomThread);
}
}
private void Loop()
{
while (true)
{
foreach (var roomThread in _roomThreads)
while (roomThread.ReadOutEvent(out var evnt))
_socketServer.WriteEvent(evnt);
while (_socketServer.ReadEvent(out var evnt))
{
if (evnt.Type == EventType.CONNECTED)
{
if (_roomThreadBalancer >= _roomThreads.Count)
_roomThreadBalancer = 0;
var roomThread = _roomThreads[_roomThreadBalancer];
_roomThreadCounter[roomThread] += 1;
_socketByRoomThreads.Add(evnt.PeerId, roomThread);
// TODO: Todo room manager matchmaking across all room threads
// TEMP_FIX: Remove this magical number
if (_roomThreadCounter[roomThread] > 20)
_roomThreadBalancer++;
}
if (_socketByRoomThreads.TryGetValue(evnt.PeerId, out var existsRoomThread))
existsRoomThread.WriteInEvent(evnt);
if (evnt.Type == EventType.DISCONNECTED)
{
_socketByRoomThreads.Remove(evnt.PeerId, out var roomThread);
_roomThreadCounter[roomThread] =- 1;
}
if (evnt.Type == EventType.TIMEOUT)
{
_socketByRoomThreads.Remove(evnt.PeerId, out var roomThread);
_roomThreadCounter[roomThread] =- 1;
}
}
Thread.Sleep(1);
}
} }
public void Start() public void Start()
{ {
_socketServer.Start(_configuration.Server.Port); Library.Initialize();
_gameThread.Start();
foreach (var roomThread in _roomThreads) _logger.Info("Started");
roomThread.Start();
_thread = new Thread(Loop);
_thread.Start();
} }
public void Dispose() public void Stop()
{ {
foreach (var roomThread in _roomThreads) _gameThread.Stop();
roomThread.Dispose(); Library.Deinitialize();
_logger.Info("Stopped");
_roomThreads.Clear();
} }
} }
} }
@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using NLog.Targets;
using Ragon.Common;
namespace Ragon.Core;
public class AuthorizationManager : IAuthorizationManager
{
private IAuthorizationProvider _provider;
private IGameThread _gameThread;
private Lobby _lobby;
private RagonSerializer _serializer;
private readonly Dictionary<uint, Player> _playersByPeers;
private readonly Dictionary<string, Player> _playersByIds;
public AuthorizationManager(IAuthorizationProvider provider, IGameThread gameThread, Lobby lobby, RagonSerializer serializer)
{
_serializer = serializer;
_lobby = lobby;
_provider = provider;
_gameThread = gameThread;
_playersByIds = new Dictionary<string, Player>();
_playersByPeers = new Dictionary<uint, Player>();
}
public void OnAuthorization(uint peerId, string key, string name, byte protocol)
{
var dispatcher = _gameThread.ThreadDispatcher;
_provider.OnAuthorizationRequest(key, name, protocol, Array.Empty<byte>(),
(playerId, playerName) =>
{
dispatcher.Dispatch(() => Accepted(peerId, playerId, playerName));
},
(errorCode) =>
{
dispatcher.Dispatch(() => Rejected(peerId, errorCode));
});
}
public void Accepted(uint peerId, string playerId, string playerName)
{
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.AUTHORIZED_SUCCESS);
_serializer.WriteString(playerId);
_serializer.WriteString(playerName);
var player = new Player()
{
Id = playerId,
PlayerName = playerName,
PeerId = peerId,
IsLoaded = false,
Entities = new List<Entity>(),
EntitiesIds = new List<int>(),
};
_playersByIds.Add(playerId, player);
_playersByPeers.Add(peerId, player);
var sendData = _serializer.ToArray();
_gameThread.Server.Send(peerId, sendData, DeliveryType.Reliable);
}
public void Rejected(uint peerId, uint code)
{
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.AUTHORIZED_FAILED);
_serializer.WriteInt((int) code);
var sendData = _serializer.ToArray();
_gameThread.Server.Send(peerId, sendData, DeliveryType.Reliable);
_gameThread.Server.Disconnect(peerId, 0);
}
public void Cleanup(uint peerId)
{
if (_playersByPeers.Remove(peerId, out var player))
_playersByIds.Remove(player.Id);
}
public Player? GetPlayer(uint peerId)
{
if (_playersByPeers.TryGetValue(peerId, out var player))
return player;
return null;
}
public Player GetPlayer(string playerId)
{
return _playersByIds[playerId];
}
}
@@ -0,0 +1,6 @@
namespace Ragon.Core;
public interface IAuthorizationManager
{
}
@@ -0,0 +1,9 @@
using System;
using System.Threading.Tasks;
namespace Ragon.Core;
public interface IAuthorizationProvider
{
Task OnAuthorizationRequest(string key, string playerName, byte protocol, byte[] additionalData, Action<string, string> Accept, Action<uint> Reject);
}
-11
View File
@@ -1,11 +0,0 @@
using System;
namespace Ragon.Core;
public class AuthorizationManager
{
public virtual bool OnAuthorize(uint peerId, ref ReadOnlySpan<byte> payload)
{
return true;
}
}
+3 -3
View File
@@ -8,13 +8,13 @@ namespace Ragon.Core
{ {
private ILogger _logger = LogManager.GetCurrentClassLogger(); private ILogger _logger = LogManager.GetCurrentClassLogger();
public void Configure(PluginFactory factory) public Application Configure(PluginFactory factory)
{ {
_logger.Info("Configure application..."); _logger.Info("Configure application...");
var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config.json"); var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config.json");
var configuration = ConfigurationLoader.Load(filePath); var configuration = ConfigurationLoader.Load(filePath);
var app = new Application(factory, configuration, 2); var app = new Application(factory, configuration);
app.Start(); return app;
} }
} }
} }
+7 -8
View File
@@ -2,17 +2,16 @@
namespace Ragon.Core namespace Ragon.Core
{ {
[Serializable]
public struct Server
{
public ushort Port;
public ushort TickRate;
}
[Serializable] [Serializable]
public struct Configuration public struct Configuration
{ {
public string Key; public string Key;
public Server Server; public int StatisticsInterval;
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.4-rc"; private static readonly string _serverVersion = "1.0.6-rc";
private static void CopyrightInfo() private static void CopyrightInfo()
{ {
-12
View File
@@ -1,12 +0,0 @@
using System;
namespace Ragon.Core
{
public struct Event
{
public EventType Type;
public DeliveryType Delivery;
public byte[] Data;
public uint PeerId;
}
}
-39
View File
@@ -1,39 +0,0 @@
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()
// {
//
// }
}
-10
View File
@@ -1,10 +0,0 @@
namespace Ragon.Core
{
public enum EventType
{
CONNECTED,
DISCONNECTED,
TIMEOUT,
DATA,
}
}
@@ -1,13 +1,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using NLog; using NLog;
using Ragon.Common; using Ragon.Common;
namespace Ragon.Core namespace Ragon.Core
{ {
public class Room : IDisposable public class GameRoom : IGameRoom
{ {
public int PlayersMin { get; private set; } public int PlayersMin { get; private set; }
public int PlayersMax { get; private set; } public int PlayersMax { get; private set; }
@@ -19,24 +19,19 @@ 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 RoomThread _roomThread; private readonly IGameThread _gameThread;
private readonly RagonSerializer _serializer = new(512); private readonly RagonSerializer _serializer = new(512);
// Cache // Cache
private uint[] _readyPlayers = Array.Empty<uint>(); private uint[] _readyPlayers = Array.Empty<uint>();
private uint[] _allPlayers = Array.Empty<uint>(); private uint[] _allPlayers = Array.Empty<uint>();
private Entity[] _entitiesAll = Array.Empty<Entity>(); private Entity[] _entitiesAll = Array.Empty<Entity>();
public Player GetPlayerById(uint peerId) => _players[peerId]; public GameRoom(IGameThread gameThread, PluginBase pluginBase, string map, int min, int max)
public Entity GetEntityById(int entityId) => _entities[entityId];
public Player GetOwner() => _players[_owner];
public Room(RoomThread roomThread, PluginBase pluginBase, string map, int min, int max)
{ {
_roomThread = roomThread; _gameThread = gameThread;
_plugin = pluginBase; _plugin = pluginBase;
Map = map; Map = map;
@@ -44,26 +39,15 @@ 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);
} }
public void Joined(uint peerId, ReadOnlySpan<byte> payload) public void Joined(Player player, ReadOnlySpan<byte> payload)
{ {
if (_players.Count == 0) if (_players.Count == 0)
{ {
_owner = peerId; _owner = player.PeerId;
} }
var player = new Player()
{
Id = Guid.NewGuid().ToString(),
PlayerName = "Player " + peerId,
PeerId = peerId,
IsLoaded = false,
Entities = new List<Entity>(),
EntitiesIds = new List<int>(),
};
{ {
_serializer.Clear(); _serializer.Clear();
@@ -76,7 +60,7 @@ namespace Ragon.Core
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable); Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
} }
_players.Add(peerId, player); _players.Add(player.PeerId, player);
_allPlayers = _players.Select(p => p.Key).ToArray(); _allPlayers = _players.Select(p => p.Key).ToArray();
{ {
@@ -89,7 +73,7 @@ namespace Ragon.Core
_serializer.WriteUShort((ushort) PlayersMax); _serializer.WriteUShort((ushort) PlayersMax);
var sendData = _serializer.ToArray(); var sendData = _serializer.ToArray();
Send(peerId, sendData, DeliveryType.Reliable); Send(player.PeerId, sendData, DeliveryType.Reliable);
} }
{ {
@@ -98,7 +82,7 @@ namespace Ragon.Core
_serializer.WriteString(Map); _serializer.WriteString(Map);
var sendData = _serializer.ToArray(); var sendData = _serializer.ToArray();
Send(peerId, sendData, DeliveryType.Reliable); Send(player.PeerId, sendData, DeliveryType.Reliable);
} }
} }
@@ -133,7 +117,9 @@ 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);
@@ -145,14 +131,19 @@ namespace Ragon.Core
Broadcast(_readyPlayers, sendData); Broadcast(_readyPlayers, sendData);
} }
} }
_entitiesAll = _entities.Values.ToArray();
} }
} }
public void ProcessEvent(RagonOperation operation, uint peerId, ReadOnlySpan<byte> rawData) public void ProcessEvent(uint peerId, ReadOnlySpan<byte> rawData)
{ {
var operation = (RagonOperation) rawData[0];
var payloadRawData = rawData.Slice(1, rawData.Length - 1);
_serializer.Clear(); _serializer.Clear();
_serializer.FromSpan(ref rawData); _serializer.FromSpan(ref payloadRawData);
switch (operation) switch (operation)
{ {
case RagonOperation.REPLICATE_ENTITY_STATE: case RagonOperation.REPLICATE_ENTITY_STATE:
@@ -300,19 +291,20 @@ namespace Ragon.Core
_serializer.WriteUShort((ushort) playerPeerId); _serializer.WriteUShort((ushort) playerPeerId);
_serializer.WriteString(_players[playerPeerId].PlayerName); _serializer.WriteString(_players[playerPeerId].PlayerName);
} }
_serializer.WriteInt(_entitiesAll.Length); _serializer.WriteInt(_entitiesAll.Length);
foreach (var entity in _entitiesAll) foreach (var entity in _entitiesAll)
{ {
var payload = entity.Payload.Read();
var state = entity.State.Read();
_serializer.WriteInt(entity.EntityId); _serializer.WriteInt(entity.EntityId);
_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);
_serializer.WriteUShort((ushort) entity.OwnerId); _serializer.WriteUShort((ushort) entity.OwnerId);
var payload = entity.Payload.Read();
_serializer.WriteUShort((ushort) payload.Length); _serializer.WriteUShort((ushort) payload.Length);
_serializer.WriteData(ref payload); _serializer.WriteData(ref payload);
var state = entity.State.Read();
_serializer.WriteUShort((ushort) state.Length); _serializer.WriteUShort((ushort) state.Length);
_serializer.WriteData(ref state); _serializer.WriteData(ref state);
} }
@@ -328,11 +320,10 @@ 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)
{ {
@@ -355,59 +346,41 @@ 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();
} }
public Player GetPlayerById(uint peerId) => _players[peerId];
public void Dispose() public Entity GetEntityById(int entityId) => _entities[entityId];
{
_logger.Info("Room destroyed"); public Player GetOwner() => _players[_owner];
_plugin.Dispose();
} 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)
{ {
_roomThread.WriteOutEvent(new Event() _gameThread.Server.Send(peerId, rawData, deliveryType);
{
PeerId = peerId,
Data = rawData,
Type = EventType.DATA,
Delivery = deliveryType,
});
} }
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) foreach (var peer in peersIds)
{ {
_roomThread.WriteOutEvent(new Event() _gameThread.Server.Send(peer, rawData, deliveryType);
{
PeerId = peer,
Data = rawData,
Type = EventType.DATA,
Delivery = deliveryType,
});
} }
} }
public void Broadcast(byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable) public void Broadcast(byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
{ {
foreach (var player in _players.Values.ToArray()) foreach (var peer in _allPlayers)
{ {
_roomThread.WriteOutEvent(new Event() _gameThread.Server.Send(peer, rawData, deliveryType);
{
PeerId = player.PeerId,
Data = rawData,
Type = EventType.DATA,
Delivery = deliveryType,
});
} }
} }
} }
+128
View File
@@ -0,0 +1,128 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using ENet;
using NLog;
namespace Ragon.Core
{
public class GameThread : IGameThread, IHandler
{
private readonly RoomManager _roomManager;
private readonly Thread _thread;
private readonly Stopwatch _gameLoopTimer;
private readonly Lobby _lobby;
private readonly ILogger _logger = LogManager.GetCurrentClassLogger();
private readonly float _deltaTime = 0.0f;
private readonly Stopwatch _statisticsTimer;
private readonly Configuration _configuration;
private readonly IDispatcherInternal _dispatcherInternal;
public IDispatcher ThreadDispatcher { get; private set; }
public ISocketServer Server { get; private set; }
public GameThread(PluginFactory factory, Configuration configuration)
{
_configuration = configuration;
var authorizationProvider = factory.CreateAuthorizationProvider(configuration);
var dispatcher = new Dispatcher();
_dispatcherInternal = dispatcher;
ThreadDispatcher = dispatcher;
Server = new ENetServer(this);
_deltaTime = 1000.0f / configuration.SendRate;
_roomManager = new RoomManager(factory, this);
_lobby = new Lobby(authorizationProvider, _roomManager, this);
_gameLoopTimer = new Stopwatch();
_statisticsTimer = new Stopwatch();
_thread = new Thread(Execute);
_thread.Name = "Game Thread";
_thread.IsBackground = true;
}
public void Start()
{
Server.Start(_configuration.Port, _configuration.MaxConnections);
_gameLoopTimer.Start();
_statisticsTimer.Start();
_thread.Start();
}
public void Stop()
{
Server.Stop();
_gameLoopTimer.Stop();
_statisticsTimer.Stop();
_thread.Interrupt();
}
private void Execute()
{
while (true)
{
Server.Process();
_dispatcherInternal.Process();
var elapsedMilliseconds = _gameLoopTimer.ElapsedMilliseconds;
if (elapsedMilliseconds > _deltaTime)
{
_roomManager.Tick(elapsedMilliseconds / 1000.0f);
_gameLoopTimer.Restart();
continue;
}
if (_statisticsTimer.Elapsed.Seconds > _configuration.StatisticsInterval && _roomManager.RoomsBySocket.Count > 0)
{
_logger.Trace($"Rooms: {_roomManager.Rooms.Count} Clients: {_roomManager.RoomsBySocket.Count}");
_statisticsTimer.Restart();
}
}
}
public void OnEvent(Event evnt)
{
if (evnt.Type == EventType.Timeout || evnt.Type == EventType.Disconnect)
{
var player = _lobby.AuthorizationManager.GetPlayer(evnt.Peer.ID);
if (player != null)
_roomManager.Left(player, Array.Empty<byte>());
_lobby.OnDisconnected(evnt.Peer.ID);
}
if (evnt.Type == EventType.Receive)
{
try
{
var peerId = evnt.Peer.ID;
var dataRaw = new byte[evnt.Packet.Length];
evnt.Packet.CopyTo(dataRaw);
var data = new ReadOnlySpan<byte>(dataRaw);
if (_roomManager.RoomsBySocket.TryGetValue(peerId, out var room))
{
room.ProcessEvent(peerId, data);
}
else
{
_lobby.ProcessEvent(peerId, data);
}
}
catch (Exception exception)
{
_logger.Error(exception);
}
}
}
}
}
+19
View File
@@ -0,0 +1,19 @@
namespace Ragon.Core;
public interface IGameRoom
{
public string Id { get; }
public string Map { get; }
public int PlayersMin { get; }
public int PlayersMax { get; }
public int PlayersCount { get; }
public Player GetPlayerById(uint peerId);
public Entity GetEntityById(int entityId);
public Player GetOwner();
public IDispatcher GetThreadDispatcher();
public void Send(uint peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable);
public void Broadcast(uint[] peersIds, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable);
public void Broadcast(byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable);
}
+9
View File
@@ -0,0 +1,9 @@
using Ragon.Common;
namespace Ragon.Core;
public interface IGameThread
{
public IDispatcher ThreadDispatcher { get; }
public ISocketServer Server { get; }
}
+6
View File
@@ -0,0 +1,6 @@
namespace Ragon.Core;
public interface ILobby
{
}
+71
View File
@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using Ragon.Common;
namespace Ragon.Core;
public class Lobby : ILobby
{
private readonly RagonSerializer _serializer;
private readonly RoomManager _roomManager;
public AuthorizationManager AuthorizationManager { get; private set; }
public Lobby(IAuthorizationProvider provider, RoomManager manager, IGameThread gameThread)
{
_roomManager = manager;
_serializer = new RagonSerializer();
AuthorizationManager = new AuthorizationManager(provider, gameThread, this, _serializer);
}
public void ProcessEvent(uint peerId, ReadOnlySpan<byte> data)
{
var op = (RagonOperation) data[0];
var payload = data.Slice(1, data.Length - 1);
_serializer.Clear();
_serializer.FromSpan(ref payload);
switch (op)
{
case RagonOperation.AUTHORIZE:
{
var key = _serializer.ReadString();
var playerName = _serializer.ReadString();
var protocol = _serializer.ReadByte();
AuthorizationManager.OnAuthorization(peerId, key, playerName, protocol);
break;
}
case RagonOperation.JOIN_ROOM:
{
var roomId = _serializer.ReadString();
var player = AuthorizationManager.GetPlayer(peerId);
if (player != null)
_roomManager.Join(player, roomId, Array.Empty<byte>());
break;
}
case RagonOperation.JOIN_OR_CREATE_ROOM:
{
var map = _serializer.ReadString();
var min = _serializer.ReadInt();
var max = _serializer.ReadInt();
var player = AuthorizationManager.GetPlayer(peerId);
if (player != null)
_roomManager.JoinOrCreate(player, map, min, max, Array.Empty<byte>());
break;
}
case RagonOperation.LEAVE_ROOM:
{
var player = AuthorizationManager.GetPlayer(peerId);
if (player != null)
_roomManager.Left(player, Array.Empty<byte>());
break;
}
}
}
public void OnDisconnected(uint peerId)
{
AuthorizationManager.Cleanup(peerId);
}
}
@@ -3,7 +3,7 @@ namespace Ragon.Core
public interface PluginFactory public interface PluginFactory
{ {
public PluginBase CreatePlugin(string map); public PluginBase CreatePlugin(string map);
public AuthorizationManager CreateManager(Configuration configuration); public IAuthorizationProvider CreateAuthorizationProvider(Configuration configuration);
} }
+22 -22
View File
@@ -7,7 +7,7 @@ using Ragon.Common;
namespace Ragon.Core namespace Ragon.Core
{ {
public class PluginBase : IDisposable public class PluginBase
{ {
private delegate void SubscribeDelegate(Player player, ref ReadOnlySpan<byte> data); private delegate void SubscribeDelegate(Player player, ref ReadOnlySpan<byte> data);
@@ -18,20 +18,20 @@ namespace Ragon.Core
private readonly BitBuffer _buffer = new(); private readonly BitBuffer _buffer = new();
private readonly RagonSerializer _serializer = new(); private readonly RagonSerializer _serializer = new();
protected Room Room { get; private set; } protected IGameRoom GameRoom { get; private set; } = null!;
protected ILogger _logger; protected ILogger Logger = null!;
public void Attach(Room room) public void Attach(GameRoom gameRoom)
{ {
_logger = LogManager.GetLogger($"Plugin<{GetType().Name}>"); Logger = LogManager.GetLogger($"Plugin<{GetType().Name}>");
Room = room; GameRoom = gameRoom;
_globalEvents.Clear(); _globalEvents.Clear();
_entityEvents.Clear(); _entityEvents.Clear();
} }
public void Dispose() public void Detach()
{ {
_globalEvents.Clear(); _globalEvents.Clear();
_entityEvents.Clear(); _entityEvents.Clear();
@@ -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;
} }
@@ -153,8 +153,8 @@ namespace Ragon.Core
if (!_entityEvents[entityId].ContainsKey(evntCode)) if (!_entityEvents[entityId].ContainsKey(evntCode))
return false; return false;
var player = Room.GetPlayerById(peerId); var player = GameRoom.GetPlayerById(peerId);
var entity = Room.GetEntityById(entityId); var entity = GameRoom.GetEntityById(entityId);
_entityEvents[entityId][evntCode].Invoke(player, entity, ref payload); _entityEvents[entityId][evntCode].Invoke(player, entity, ref payload);
return true; return true;
@@ -164,7 +164,7 @@ namespace Ragon.Core
{ {
if (_globalEvents.ContainsKey(evntCode)) if (_globalEvents.ContainsKey(evntCode))
{ {
var player = Room.GetPlayerById(peerId); var player = GameRoom.GetPlayerById(peerId);
_globalEvents[evntCode].Invoke(player, ref payload); _globalEvents[evntCode].Invoke(player, ref payload);
return true; return true;
} }
@@ -184,7 +184,7 @@ namespace Ragon.Core
_buffer.ToSpan(ref payloadData); _buffer.ToSpan(ref payloadData);
var sendData = _serializer.ToArray(); var sendData = _serializer.ToArray();
Room.Send(player.PeerId, sendData); GameRoom.Send(player.PeerId, sendData);
} }
public void BroadcastEvent(ushort eventCode, IRagonSerializable payload) public void BroadcastEvent(ushort eventCode, IRagonSerializable payload)
@@ -199,7 +199,7 @@ namespace Ragon.Core
_buffer.ToSpan(ref payloadData); _buffer.ToSpan(ref payloadData);
var sendData = _serializer.ToArray(); var sendData = _serializer.ToArray();
Room.Broadcast(sendData, DeliveryType.Reliable); GameRoom.Broadcast(sendData, DeliveryType.Reliable);
} }
public void SendEntityEvent(Player player, Entity entity, IRagonSerializable payload) public void SendEntityEvent(Player player, Entity entity, IRagonSerializable payload)
@@ -215,7 +215,7 @@ namespace Ragon.Core
_buffer.ToSpan(ref payloadData); _buffer.ToSpan(ref payloadData);
var sendData = _serializer.ToArray(); var sendData = _serializer.ToArray();
Room.Send(player.PeerId, sendData, DeliveryType.Reliable); GameRoom.Send(player.PeerId, sendData, DeliveryType.Reliable);
} }
public void BroadcastEntityEvent(Entity entity, IRagonSerializable payload) public void BroadcastEntityEvent(Entity entity, IRagonSerializable payload)
@@ -231,7 +231,7 @@ namespace Ragon.Core
_buffer.ToSpan(ref payloadData); _buffer.ToSpan(ref payloadData);
var sendData = _serializer.ToArray(); var sendData = _serializer.ToArray();
Room.Broadcast(sendData); GameRoom.Broadcast(sendData);
} }
@@ -265,7 +265,7 @@ namespace Ragon.Core
{ {
} }
public virtual void OnTick(ulong ticks, float deltaTime) public virtual void OnTick(float deltaTime)
{ {
} }
+88
View File
@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Text;
using NLog;
using Ragon.Common;
namespace Ragon.Core;
public class RoomManager
{
private readonly IGameThread _gameThread;
private readonly PluginFactory _factory;
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
private 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)
{
_gameThread = gameThread;
_factory = factory;
_roomsBySocket = new Dictionary<uint, GameRoom>();
}
public void Join(Player player, string roomId, byte[] payload)
{
if (_rooms.Count > 0)
{
foreach (var existRoom in _rooms)
{
if (existRoom.Id == roomId && existRoom.PlayersCount < existRoom.PlayersMax)
{
existRoom.Joined(player, payload);
_roomsBySocket.Add(player.PeerId, existRoom);
break;
}
}
}
}
public void JoinOrCreate(Player player, string map, int min, int max, byte[] payload)
{
if (_rooms.Count > 0)
{
foreach (var existRoom in _rooms)
{
if (existRoom.Map == map && existRoom.PlayersCount < existRoom.PlayersMax)
{
existRoom.Joined(player, payload);
_roomsBySocket.Add(player.PeerId, existRoom);
return;
}
}
}
var plugin = _factory.CreatePlugin(map);
if (plugin == null)
throw new NullReferenceException($"Plugin for map {map} is null");
var room = new GameRoom(_gameThread, plugin, map, min, max);
room.Joined(player, payload);
room.Start();
_roomsBySocket.Add(player.PeerId, room);
_rooms.Add(room);
}
public void Left(Player player, byte[] payload)
{
if (_roomsBySocket.Remove(player.PeerId, out var room))
{
room.Leave(player.PeerId);
if (room.PlayersCount < room.PlayersMin)
{
room.Stop();
_rooms.Remove(room);
}
}
}
public void Tick(float deltaTime)
{
foreach (var gameRoom in _rooms)
gameRoom.Tick(deltaTime);
}
}
-211
View File
@@ -1,211 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using NLog;
using Ragon.Common;
namespace Ragon.Core
{
public class RoomManager
{
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
private List<Room> _rooms;
private Dictionary<uint, Room> _peersByRoom;
private PluginFactory _factory;
private AuthorizationManager _manager;
private RoomThread _roomThread;
private RagonSerializer _serializer;
public Action<(uint, Room)> OnJoined;
public Action<(uint, Room)> OnLeaved;
public RoomManager(RoomThread roomThread, PluginFactory factory)
{
_roomThread = roomThread;
_factory = factory;
_serializer = new RagonSerializer();
_manager = _factory.CreateManager(roomThread.Configuration);
_rooms = new List<Room>();
_peersByRoom = new Dictionary<uint, Room>();
}
public void ProcessEvent(RagonOperation operation, uint peerId, ReadOnlySpan<byte> payload)
{
switch (operation)
{
case RagonOperation.AUTHORIZE:
{
OnAuthorize(peerId, payload);
break;
}
case RagonOperation.JOIN_OR_CREATE_ROOM:
{
var room = JoinOrCreate(peerId, payload);
if (room == null)
{
var sendData = new[] {(byte) RagonOperation.JOIN_FAILED};
_roomThread.WriteOutEvent(new Event()
{
Delivery = DeliveryType.Reliable,
Type = EventType.DATA,
Data = sendData,
PeerId = peerId,
});
return;
}
OnJoined?.Invoke((peerId, room));
break;
}
case RagonOperation.JOIN_ROOM:
{
var room = Join(peerId, payload);
if (room == null)
{
var sendData = new[] {(byte) RagonOperation.JOIN_FAILED};
_roomThread.WriteOutEvent(new Event()
{
Delivery = DeliveryType.Reliable,
Type = EventType.DATA,
Data = sendData,
PeerId = peerId,
});
return;
}
OnJoined?.Invoke((peerId, room));
break;
}
case RagonOperation.LEAVE_ROOM:
{
var room = Left(peerId, payload);
OnLeaved((peerId, room));
break;
}
}
}
public void OnAuthorize(uint peerId, ReadOnlySpan<byte> payload)
{
if (_manager.OnAuthorize(peerId, ref payload))
{
var sendData = new[] {(byte) RagonOperation.AUTHORIZED_SUCCESS};
_roomThread.WriteOutEvent(new Event()
{
Delivery = DeliveryType.Reliable,
Type = EventType.DATA,
Data = sendData,
PeerId = peerId,
});
}
else
{
var sendData = new[] {(byte) RagonOperation.AUTHORIZED_FAILED};
_roomThread.WriteOutEvent(new Event()
{
Delivery = DeliveryType.Reliable,
Type = EventType.DATA,
Data = sendData,
PeerId = peerId,
});
_roomThread.WriteOutEvent(new Event()
{
Delivery = DeliveryType.Reliable,
Type = EventType.DISCONNECTED,
Data = Array.Empty<byte>(),
PeerId = peerId,
});
}
}
public Room? Join(uint peerId, ReadOnlySpan<byte> payload)
{
var roomId = Encoding.UTF8.GetString(payload);
if (_rooms.Count > 0)
{
foreach (var existRoom in _rooms)
{
if (existRoom.Id == roomId && existRoom.PlayersCount < existRoom.PlayersMax)
{
existRoom.Joined(peerId, payload);
_peersByRoom.Add(peerId, existRoom);
return existRoom;
}
}
}
return null;
}
public Room? JoinOrCreate(uint peerId, ReadOnlySpan<byte> payload)
{
_serializer.Clear();
_serializer.FromSpan(ref payload);
var min = _serializer.ReadUShort();
var max = _serializer.ReadUShort();
var map = _serializer.ReadString();
Room room = null;
if (_rooms.Count > 0)
{
foreach (var existRoom in _rooms)
{
if (existRoom.Map == map && existRoom.PlayersCount < existRoom.PlayersMax)
{
room = existRoom;
room.Joined(peerId, payload);
_peersByRoom.Add(peerId, room);
return room;
}
}
}
var plugin = _factory.CreatePlugin(map);
if (plugin == null)
throw new NullReferenceException($"Plugin for map {map} is null");
room = new Room(_roomThread, plugin, map, min, max);
room.Joined(peerId, payload);
room.Start();
_peersByRoom.Add(peerId, room);
_rooms.Add(room);
return room;
}
public Room Left(uint peerId, ReadOnlySpan<byte> payload)
{
_peersByRoom.Remove(peerId, out var room);
return room;
}
public void Disconnected(uint peerId)
{
_peersByRoom.Remove(peerId, out var room);
if (room != null)
{
room.Leave(peerId);
if (room.PlayersCount <= 0 && room.PlayersMin > 0)
{
_rooms.Remove(room);
room.Stop();
room.Dispose();
}
}
}
public void Tick(float deltaTime)
{
foreach (Room room in _rooms)
room.Tick(deltaTime);
}
}
}
-110
View File
@@ -1,110 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using DisruptorUnity3d;
using NLog;
using Ragon.Common;
namespace Ragon.Core
{
public class RoomThread : IDisposable
{
private readonly RoomManager _roomManager;
private readonly Dictionary<uint, Room> _socketByRooms;
private readonly Thread _thread;
private readonly Stopwatch _timer;
private readonly ILogger _logger = LogManager.GetCurrentClassLogger();
private readonly float _deltaTime = 0.0f;
private readonly RingBuffer<Event> _receiveBuffer = new(2048);
private readonly RingBuffer<Event> _sendBuffer = new(2048);
public Configuration Configuration { get; private set; }
public bool ReadOutEvent(out Event evnt) => _sendBuffer.TryDequeue(out evnt);
public void WriteOutEvent(Event evnt) => _sendBuffer.Enqueue(evnt);
public bool ReadIntEvent(out Event evnt) => _receiveBuffer.TryDequeue(out evnt);
public void WriteInEvent(Event evnt) => _receiveBuffer.Enqueue(evnt);
public RoomThread(PluginFactory factory, Configuration configuration)
{
_thread = new Thread(Execute);
_thread.IsBackground = true;
_timer = new Stopwatch();
_socketByRooms = new Dictionary<uint, Room>();
Configuration = configuration;
_deltaTime = 1000.0f / Configuration.Server.TickRate;
_roomManager = new RoomManager(this, factory);
_roomManager.OnJoined += (tuple) => _socketByRooms.Add(tuple.Item1, tuple.Item2);
_roomManager.OnLeaved += (tuple) => _socketByRooms.Remove(tuple.Item1);
}
public void Start()
{
_timer.Start();
_thread.Start();
}
public void Stop()
{
_thread.Interrupt();
}
private void Execute()
{
while (true)
{
while (_receiveBuffer.TryDequeue(out var evnt))
{
if (evnt.Type == EventType.DISCONNECTED || evnt.Type == EventType.TIMEOUT)
{
if (_socketByRooms.ContainsKey(evnt.PeerId))
{
_roomManager.Disconnected(evnt.PeerId);
_socketByRooms.Remove(evnt.PeerId);
}
}
if (evnt.Type == EventType.DATA)
{
var data = new ReadOnlySpan<byte>(evnt.Data);
var operation = (RagonOperation) data[0];
var payload = data.Slice(1, data.Length - 1);
if (_socketByRooms.TryGetValue(evnt.PeerId, out var room))
{
try
{
room.ProcessEvent(operation, evnt.PeerId, payload);
}
catch (Exception exception)
{
_logger.Error(exception);
}
}
else
{
_roomManager.ProcessEvent(operation, evnt.PeerId, payload);
}
}
}
var elapsedMilliseconds = _timer.ElapsedMilliseconds;
if (elapsedMilliseconds > _deltaTime)
{
_roomManager.Tick(elapsedMilliseconds / 1000.0f);
_timer.Restart();
continue;
}
Thread.Sleep(15);
}
}
public void Dispose()
{
}
}
}
+122
View File
@@ -0,0 +1,122 @@
using System;
using ENet;
using NLog;
namespace Ragon.Core
{
public enum Status
{
Stopped,
Listening,
Disconnecting,
Connecting,
Assigning,
Connected
}
public class ENetServer : ISocketServer
{
public Status Status { get; private set; }
private ILogger _logger = LogManager.GetCurrentClassLogger();
private Host _host;
private Address _address;
private Event _netEvent;
private Peer[] _peers;
private IHandler _handler;
public ENetServer(IHandler handler)
{
_handler = handler;
}
public void Start(ushort port, int connections)
{
_address = default;
_address.Port = port;
_peers = new Peer[connections];
_host = new Host();
_host.Create(_address, connections, 2, 0, 0, 1024 * 1024);
Status = Status.Listening;
_logger.Info($"Network listening on {port}");
}
public void Send(uint peerId, 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);
_peers[peerId].Send(channel, ref newPacket);
}
public void Disconnect(uint peerId, uint errorCode)
{
_peers[peerId].Reset();
}
public void Process()
{
bool polled = false;
while (!polled)
{
if (_host.CheckEvents(out _netEvent) <= 0)
{
if (_host.Service(15, out _netEvent) <= 0)
break;
polled = true;
}
switch (_netEvent.Type)
{
case EventType.None:
Console.WriteLine("None event");
break;
case EventType.Connect:
{
_peers[_netEvent.Peer.ID] = _netEvent.Peer;
_handler.OnEvent(_netEvent);
break;
}
case EventType.Disconnect:
{
_handler.OnEvent(_netEvent);
break;
}
case EventType.Timeout:
{
_handler.OnEvent(_netEvent);
break;
}
case EventType.Receive:
{
_handler.OnEvent(_netEvent);
_netEvent.Packet.Dispose();
break;
}
}
}
}
public void Stop()
{
_host?.Dispose();
}
}
}
-153
View File
@@ -1,153 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using DisruptorUnity3d;
using ENet;
using NLog;
namespace Ragon.Core
{
public enum Status
{
Stopped,
Listening,
Disconnecting,
Connecting,
Assigning,
Connected
}
public class ENetServer : IDisposable
{
public Status Status { get; private set; }
private ILogger _logger = LogManager.GetCurrentClassLogger();
private Thread _thread;
private Host _host;
private Address _address;
private ENet.Event _netEvent;
private Peer[] _peers;
private RingBuffer<Event> _receiveBuffer;
private RingBuffer<Event> _sendBuffer;
public void WriteEvent(Event evnt) => _sendBuffer.Enqueue(evnt);
public bool ReadEvent(out Event evnt) => _receiveBuffer.TryDequeue(out evnt);
public void Start(ushort port)
{
Library.Initialize();
_address = default;
_address.Port = port;
_host = new Host();
_host.Create(_address, 4095, 2, 0, 0, 1024 * 1024);
_peers = new Peer[4095];
_sendBuffer = new RingBuffer<Event>(8192 + 8192);
_receiveBuffer = new RingBuffer<Event>(8192 + 8192);
Status = Status.Listening;
_thread = new Thread(Execute);
_thread.Name = "NetworkThread";
_thread.Start();
_logger.Info($"ENet Server Started at port {port}");
}
private void Execute()
{
while (true)
{
while (_sendBuffer.TryDequeue(out var data))
{
if (data.Type == EventType.DATA)
{
var newPacket = new Packet();
var packetFlags = PacketFlags.Instant;
byte channel = 1;
if (data.Delivery == DeliveryType.Reliable)
{
packetFlags = PacketFlags.Reliable;
channel = 0;
}
else if (data.Delivery == DeliveryType.Unreliable)
{
channel = 1;
packetFlags = PacketFlags.Instant;
}
newPacket.Create(data.Data, data.Data.Length, packetFlags);
_peers[data.PeerId].Send(channel, ref newPacket);
}
else if (data.Type == EventType.DISCONNECTED)
{
_peers[data.PeerId].DisconnectNow(0);
_receiveBuffer.Enqueue(data);
}
}
bool polled = false;
while (!polled)
{
if (_host.CheckEvents(out _netEvent) <= 0)
{
if (_host.Service(16, out _netEvent) <= 0)
break;
polled = true;
}
switch (_netEvent.Type)
{
case ENet.EventType.None:
Console.WriteLine("None event");
break;
case ENet.EventType.Connect:
{
var @event = new Event {PeerId = _netEvent.Peer.ID, Type = EventType.CONNECTED};
_peers[_netEvent.Peer.ID] = _netEvent.Peer;
_receiveBuffer.Enqueue(@event);
break;
}
case ENet.EventType.Disconnect:
{
var @event = new Event {PeerId = _netEvent.Peer.ID, Type = EventType.DISCONNECTED};
_receiveBuffer.Enqueue(@event);
break;
}
case ENet.EventType.Timeout:
{
var @event = new Event {PeerId = _netEvent.Peer.ID, Type = EventType.TIMEOUT};
_receiveBuffer.Enqueue(@event);
break;
}
case ENet.EventType.Receive:
{
var data = new byte[_netEvent.Packet.Length];
_netEvent.Packet.CopyTo(data);
_netEvent.Packet.Dispose();
var @event = new Event {PeerId = _netEvent.Peer.ID, Type = EventType.DATA, Data = data };
_receiveBuffer.Enqueue(@event);
break;
}
}
}
}
}
public void Dispose()
{
Library.Deinitialize();
_host?.Dispose();
}
}
}
+8
View File
@@ -0,0 +1,8 @@
using ENet;
namespace Ragon.Core;
public interface IHandler
{
public void OnEvent(Event evnt);
}
+10
View File
@@ -0,0 +1,10 @@
namespace Ragon.Core;
public interface ISocketServer
{
public void Start(ushort port, int connections);
public void Process();
public void Stop();
public void Send(uint peerId, byte[] data, DeliveryType type);
public void Disconnect(uint peerId, uint errorCode);
}
-60
View File
@@ -1,60 +0,0 @@
using System;
using System.Net;
using System.Net.WebSockets;
using System.Threading;
using DisruptorUnity3d;
using NLog;
namespace Ragon.Core;
public class WebsocketServer : IDisposable
{
private HttpListener _httpListener;
private ILogger _logger = LogManager.GetCurrentClassLogger();
private Thread _thread;
private ENet.Event _netEvent;
private RingBuffer<Event> _receiveBuffer;
private RingBuffer<Event> _sendBuffer;
public void WriteEvent(Event evnt) => _sendBuffer.Enqueue(evnt);
public bool ReadEvent(out Event evnt) => _receiveBuffer.TryDequeue(out evnt);
public void Start(ushort port)
{
// _httpListener = new HttpListener();
// _httpListener.Prefixes.Add("http://localhost/");
// _httpListener.Start();
//
// _thread = new Thread(Execute);
// _thread.Name = "NetworkThread";
// _thread.Start();
// _logger.Info($"Socket Server Started at port {port}");
}
public void Execute()
{
}
public async void ExecuteAsync()
{
// while (true)
// {
// HttpListenerContext context = await _httpListener.GetContextAsync();
// if (context.Request.IsWebSocketRequest)
// {
// HttpListenerWebSocketContext webSocketContext = await context.AcceptWebSocketAsync(null);
// WebSocket webSocket = webSocketContext.WebSocket;
// while (webSocket.State == WebSocketState.Open)
// {
// await webSocket.SendAsync(... );
// }
// }
// }
}
public void Dispose()
{
}
}
-6
View File
@@ -1,6 +0,0 @@
namespace Ragon.Core.Storage;
public struct EntityInfo
{
}
-6
View File
@@ -1,6 +0,0 @@
namespace Ragon.Core.Storage;
public struct PlayerInfo
{
}
-6
View File
@@ -1,6 +0,0 @@
namespace Ragon.Core.Storage;
public struct RoomInfo
{
}
-29
View File
@@ -1,29 +0,0 @@
namespace Ragon.Core.Storage;
public class Storage
{
// private ConnectionMultiplexer _connection;
public Storage(Configuration _configuration)
{
// _connection = ConnectionMultiplexer.Connect(_configuration.Key);
}
public void UpdateEntity(int entityId)
{
// var db = _connection.GetDatabase();
// db.set("entity_", )
}
public void UpdatePlayer()
{
}
public void UpdateRoom()
{
}
}
+22
View File
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
namespace Ragon.Core;
public class Dispatcher: IDispatcher, IDispatcherInternal
{
public Queue<Action> _actions = new Queue<Action>();
public void Dispatch(Action action)
{
lock (_actions)
_actions.Enqueue(action);
}
public void Process()
{
lock(_actions)
while(_actions.TryDequeue(out var action))
action?.Invoke();
}
}
+8
View File
@@ -0,0 +1,8 @@
using System;
namespace Ragon.Core;
public interface IDispatcher
{
public void Dispatch(Action action);
}
@@ -0,0 +1,6 @@
namespace Ragon.Core;
public interface IDispatcherInternal
{
public void Process();
}
-20
View File
@@ -1,20 +0,0 @@
using System;
using System.Text;
using Ragon.Core;
namespace Game.Source;
public class AuthorizerByKey: AuthorizationManager
{
private Configuration _configuration;
public AuthorizerByKey(Configuration configuration)
{
_configuration = configuration;
}
public override bool OnAuthorize(uint peerId, ref ReadOnlySpan<byte> payload)
{
var key = Encoding.UTF8.GetString(payload);
return _configuration.Key == key;
}
}
-19
View File
@@ -1,19 +0,0 @@
using NetStack.Serialization;
using Ragon.Common;
namespace Game.Source.Events;
public class SimpleEvent: IRagonSerializable
{
public string Name;
public void Serialize(BitBuffer buffer)
{
buffer.AddString(Name);
}
public void Deserialize(BitBuffer buffer)
{
Name = buffer.ReadString();
}
}
@@ -1,31 +0,0 @@
using System.Runtime.InteropServices;
using Game.Source.Events;
using NLog;
using Ragon.Common;
using Ragon.Core;
namespace Game.Source
{
public class SimplePlugin: PluginBase
{
public override void OnStart()
{
_logger.Info("Plugin started");
}
public override void OnStop()
{
_logger.Info("Plugin stopped");
}
public override void OnPlayerJoined(Player player)
{
_logger.Info("Player joined " + player.PlayerName);
}
public override void OnPlayerLeaved(Player player)
{
_logger.Info("Player leaved " + player.PlayerName);
}
}
}
-8
View File
@@ -1,8 +0,0 @@
{
"key": "defaultkey",
"server": {
"port": 4444,
"skipTimeout": 60,
"tickRate": 30
}
}
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
+9 -13
View File
@@ -4,34 +4,31 @@
## 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
- Room based architecture - Room based architecture
- 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