Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f3b2d7ed8 | |||
| 773adeefb2 | |||
| e674600308 | |||
| 4f587fa59c | |||
| 76caa840bd | |||
| 1bff47e56b | |||
| 1e41b9f2eb | |||
| 679608bc48 | |||
| e9c129e2f2 | |||
| 66181c612a | |||
| 189278e17c | |||
| 08c399a030 | |||
| 05c8904601 | |||
| 04b1b16b11 | |||
| 530c6109ea | |||
| 3efd73d8cb | |||
| a85ac99a3c | |||
| e295e9f7db | |||
| 06dd23ee8d | |||
| 62d3f7acdd | |||
| e2d07eb396 | |||
| 4f00c36cd9 | |||
| 8c1945e352 | |||
| 2ef1da5c90 | |||
| ec65b9f305 | |||
| 35ca016520 | |||
| 8481cb89ad | |||
| bcec99cff1 | |||
| bb7cccb61a |
@@ -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
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
@@ -1,275 +0,0 @@
|
|||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace DisruptorUnity3d
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Implementation of the Disruptor pattern
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">the type of item to be stored</typeparam>
|
|
||||||
public class RingBuffer<T>
|
|
||||||
{
|
|
||||||
private readonly T[] _entries;
|
|
||||||
private readonly int _modMask;
|
|
||||||
private Volatile.PaddedLong _consumerCursor = new Volatile.PaddedLong();
|
|
||||||
private Volatile.PaddedLong _producerCursor = new Volatile.PaddedLong();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new RingBuffer with the given capacity
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="capacity">The capacity of the buffer</param>
|
|
||||||
/// <remarks>Only a single thread may attempt to consume at any one time</remarks>
|
|
||||||
public RingBuffer(int capacity)
|
|
||||||
{
|
|
||||||
capacity = NextPowerOfTwo(capacity);
|
|
||||||
_modMask = capacity - 1;
|
|
||||||
_entries = new T[capacity];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The maximum number of items that can be stored
|
|
||||||
/// </summary>
|
|
||||||
public int Capacity
|
|
||||||
{
|
|
||||||
get { return _entries.Length; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public T this[long index]
|
|
||||||
{
|
|
||||||
get { unchecked { return _entries[index & _modMask]; } }
|
|
||||||
set { unchecked { _entries[index & _modMask] = value; } }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes an item from the buffer.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The next available item</returns>
|
|
||||||
public T Dequeue()
|
|
||||||
{
|
|
||||||
var next = _consumerCursor.ReadAcquireFence() + 1;
|
|
||||||
while (_producerCursor.ReadAcquireFence() < next) // makes sure we read the data from _entries after we have read the producer cursor
|
|
||||||
{
|
|
||||||
Thread.SpinWait(1);
|
|
||||||
}
|
|
||||||
var result = this[next];
|
|
||||||
_consumerCursor.WriteReleaseFence(next); // makes sure we read the data from _entries before we update the consumer cursor
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempts to remove an items from the queue
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="obj">the items</param>
|
|
||||||
/// <returns>True if successful</returns>
|
|
||||||
public bool TryDequeue(out T obj)
|
|
||||||
{
|
|
||||||
var next = _consumerCursor.ReadAcquireFence() + 1;
|
|
||||||
|
|
||||||
if (_producerCursor.ReadAcquireFence() < next)
|
|
||||||
{
|
|
||||||
obj = default(T);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
obj = Dequeue();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add an item to the buffer
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="item"></param>
|
|
||||||
public void Enqueue(T item)
|
|
||||||
{
|
|
||||||
var next = _producerCursor.ReadAcquireFence() + 1;
|
|
||||||
|
|
||||||
long wrapPoint = next - _entries.Length;
|
|
||||||
long min = _consumerCursor.ReadAcquireFence();
|
|
||||||
|
|
||||||
while (wrapPoint > min)
|
|
||||||
{
|
|
||||||
min = _consumerCursor.ReadAcquireFence();
|
|
||||||
Thread.SpinWait(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
this[next] = item;
|
|
||||||
_producerCursor.WriteReleaseFence(next); // makes sure we write the data in _entries before we update the producer cursor
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The number of items in the buffer
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>for indicative purposes only, may contain stale data</remarks>
|
|
||||||
public int Count { get { return (int)(_producerCursor.ReadFullFence() - _consumerCursor.ReadFullFence()); } }
|
|
||||||
|
|
||||||
private static int NextPowerOfTwo(int x)
|
|
||||||
{
|
|
||||||
var result = 2;
|
|
||||||
while (result < x)
|
|
||||||
{
|
|
||||||
result <<= 1;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
public static class Volatile
|
|
||||||
{
|
|
||||||
private const int CacheLineSize = 64;
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit, Size = CacheLineSize * 2)]
|
|
||||||
public struct PaddedLong
|
|
||||||
{
|
|
||||||
[FieldOffset(CacheLineSize)]
|
|
||||||
private long _value;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new <see cref="PaddedLong"/> with the given initial value.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value">Initial value</param>
|
|
||||||
public PaddedLong(long value)
|
|
||||||
{
|
|
||||||
_value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Read the value without applying any fence
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The current value</returns>
|
|
||||||
public long ReadUnfenced()
|
|
||||||
{
|
|
||||||
return _value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Read the value applying acquire fence semantic
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The current value</returns>
|
|
||||||
public long ReadAcquireFence()
|
|
||||||
{
|
|
||||||
var value = _value;
|
|
||||||
Thread.MemoryBarrier();
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Read the value applying full fence semantic
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The current value</returns>
|
|
||||||
public long ReadFullFence()
|
|
||||||
{
|
|
||||||
Thread.MemoryBarrier();
|
|
||||||
return _value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Read the value applying a compiler only fence, no CPU fence is applied
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The current value</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.NoOptimization)]
|
|
||||||
public long ReadCompilerOnlyFence()
|
|
||||||
{
|
|
||||||
return _value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Write the value applying release fence semantic
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="newValue">The new value</param>
|
|
||||||
public void WriteReleaseFence(long newValue)
|
|
||||||
{
|
|
||||||
Thread.MemoryBarrier();
|
|
||||||
_value = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Write the value applying full fence semantic
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="newValue">The new value</param>
|
|
||||||
public void WriteFullFence(long newValue)
|
|
||||||
{
|
|
||||||
Thread.MemoryBarrier();
|
|
||||||
_value = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Write the value applying a compiler fence only, no CPU fence is applied
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="newValue">The new value</param>
|
|
||||||
[MethodImpl(MethodImplOptions.NoOptimization)]
|
|
||||||
public void WriteCompilerOnlyFence(long newValue)
|
|
||||||
{
|
|
||||||
_value = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Write without applying any fence
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="newValue">The new value</param>
|
|
||||||
public void WriteUnfenced(long newValue)
|
|
||||||
{
|
|
||||||
_value = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Atomically set the value to the given updated value if the current value equals the comparand
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="newValue">The new value</param>
|
|
||||||
/// <param name="comparand">The comparand (expected value)</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public bool AtomicCompareExchange(long newValue, long comparand)
|
|
||||||
{
|
|
||||||
return Interlocked.CompareExchange(ref _value, newValue, comparand) == comparand;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Atomically set the value to the given updated value
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="newValue">The new value</param>
|
|
||||||
/// <returns>The original value</returns>
|
|
||||||
public long AtomicExchange(long newValue)
|
|
||||||
{
|
|
||||||
return Interlocked.Exchange(ref _value, newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Atomically add the given value to the current value and return the sum
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="delta">The value to be added</param>
|
|
||||||
/// <returns>The sum of the current value and the given value</returns>
|
|
||||||
public long AtomicAddAndGet(long delta)
|
|
||||||
{
|
|
||||||
return Interlocked.Add(ref _value, delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Atomically increment the current value and return the new value
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The incremented value.</returns>
|
|
||||||
public long AtomicIncrementAndGet()
|
|
||||||
{
|
|
||||||
return Interlocked.Increment(ref _value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Atomically increment the current value and return the new value
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The decremented value.</returns>
|
|
||||||
public long AtomicDecrementAndGet()
|
|
||||||
{
|
|
||||||
return Interlocked.Decrement(ref _value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the string representation of the current value.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>the string representation of the current value.</returns>
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
var value = ReadFullFence();
|
|
||||||
return value.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using NetStack.Buffers;
|
|
||||||
|
|
||||||
namespace Ragon.Common
|
|
||||||
{
|
|
||||||
public static class RagonHeader
|
|
||||||
{
|
|
||||||
[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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,17 @@
|
|||||||
namespace Ragon.Common
|
namespace Ragon.Common
|
||||||
{
|
{
|
||||||
public enum RagonOperation: ushort
|
public enum RagonOperation: byte
|
||||||
{
|
{
|
||||||
AUTHORIZE,
|
AUTHORIZE,
|
||||||
AUTHORIZED_SUCCESS,
|
AUTHORIZED_SUCCESS,
|
||||||
AUTHORIZED_FAILED,
|
AUTHORIZED_FAILED,
|
||||||
|
|
||||||
|
JOIN_OR_CREATE_ROOM,
|
||||||
JOIN_ROOM,
|
JOIN_ROOM,
|
||||||
LEAVE_ROOM,
|
LEAVE_ROOM,
|
||||||
|
OWNERSHIP_CHANGED,
|
||||||
|
JOIN_SUCCESS,
|
||||||
|
JOIN_FAILED,
|
||||||
|
|
||||||
LOAD_SCENE,
|
LOAD_SCENE,
|
||||||
SCENE_IS_LOADED,
|
SCENE_IS_LOADED,
|
||||||
@@ -16,11 +20,10 @@ namespace Ragon.Common
|
|||||||
PLAYER_LEAVED,
|
PLAYER_LEAVED,
|
||||||
|
|
||||||
CREATE_ENTITY,
|
CREATE_ENTITY,
|
||||||
|
CREATE_STATIC_ENTITY,
|
||||||
DESTROY_ENTITY,
|
DESTROY_ENTITY,
|
||||||
|
|
||||||
RESTORE_BEGIN,
|
SNAPSHOT,
|
||||||
RESTORE_END,
|
|
||||||
RESTORED,
|
|
||||||
|
|
||||||
REPLICATE_ENTITY_STATE,
|
REPLICATE_ENTITY_STATE,
|
||||||
REPLICATE_ENTITY_EVENT,
|
REPLICATE_ENTITY_EVENT,
|
||||||
|
|||||||
@@ -0,0 +1,200 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Ragon.Common
|
||||||
|
{
|
||||||
|
|
||||||
|
public class RagonSerializer
|
||||||
|
{
|
||||||
|
private byte[] _data;
|
||||||
|
private int _offset;
|
||||||
|
private int _size;
|
||||||
|
public int Lenght => _offset;
|
||||||
|
public int Size => _size - _offset;
|
||||||
|
|
||||||
|
public RagonSerializer(int capacity = 256)
|
||||||
|
{
|
||||||
|
_data = new byte[capacity];
|
||||||
|
_offset = 0;
|
||||||
|
_size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteByte(byte value)
|
||||||
|
{
|
||||||
|
ResizeIfNeed(1);
|
||||||
|
|
||||||
|
_data[_offset] = value;
|
||||||
|
_offset += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public byte ReadByte()
|
||||||
|
{
|
||||||
|
var value = _data[_offset];
|
||||||
|
_offset += 1;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteInt(int value)
|
||||||
|
{
|
||||||
|
ResizeIfNeed(4);
|
||||||
|
|
||||||
|
_data[_offset] = (byte) (value & 0x00FF);
|
||||||
|
_data[_offset + 1] = (byte) ((value & 0xFF00) >> 8);
|
||||||
|
_data[_offset + 2] = (byte) ((value & 0xFF00) >> 16);
|
||||||
|
_data[_offset + 3] = (byte) ((value & 0xFF00) >> 24);
|
||||||
|
_offset += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int ReadInt()
|
||||||
|
{
|
||||||
|
var value = _data[_offset] + (_data[_offset + 1] << 8) + (_data[_offset + 2] << 16) + (_data[_offset + 3] << 24);
|
||||||
|
_offset += 4;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteString(string value)
|
||||||
|
{
|
||||||
|
var stringRaw = Encoding.UTF8.GetBytes(value).AsSpan();
|
||||||
|
ResizeIfNeed(2 + stringRaw.Length);
|
||||||
|
|
||||||
|
WriteUShort((ushort) stringRaw.Length);
|
||||||
|
var data = _data.AsSpan().Slice(_offset, stringRaw.Length);
|
||||||
|
stringRaw.CopyTo(data);
|
||||||
|
_offset += stringRaw.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public string ReadString()
|
||||||
|
{
|
||||||
|
var lenght = ReadUShort();
|
||||||
|
var stringRaw = _data.AsSpan().Slice(_offset, lenght);
|
||||||
|
var str = Encoding.UTF8.GetString(stringRaw);
|
||||||
|
_offset += lenght;
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ReadOnlySpan<byte> ReadData(int lenght)
|
||||||
|
{
|
||||||
|
var data = _data.AsSpan();
|
||||||
|
var payloadData = data.Slice(_offset, lenght);
|
||||||
|
|
||||||
|
_offset += payloadData.Length;
|
||||||
|
return payloadData;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteData(ref ReadOnlySpan<byte> payload)
|
||||||
|
{
|
||||||
|
ResizeIfNeed(payload.Length);
|
||||||
|
|
||||||
|
var data = _data.AsSpan();
|
||||||
|
var payloadData = data.Slice(_offset, payload.Length);
|
||||||
|
|
||||||
|
payload.CopyTo(payloadData);
|
||||||
|
_offset += payload.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public Span<byte> GetWritableData(int lenght)
|
||||||
|
{
|
||||||
|
ResizeIfNeed(lenght);
|
||||||
|
|
||||||
|
var data = _data.AsSpan();
|
||||||
|
var payloadData = data.Slice(_offset, lenght);
|
||||||
|
|
||||||
|
_offset += lenght;
|
||||||
|
return payloadData;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteOperation(RagonOperation ragonOperation)
|
||||||
|
{
|
||||||
|
ResizeIfNeed(1);
|
||||||
|
|
||||||
|
_data[_offset] = (byte) ragonOperation;
|
||||||
|
_offset += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public RagonOperation ReadOperation()
|
||||||
|
{
|
||||||
|
var op = (RagonOperation) _data[_offset];
|
||||||
|
_offset += 1;
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteUShort(ushort value)
|
||||||
|
{
|
||||||
|
ResizeIfNeed(2);
|
||||||
|
|
||||||
|
_data[_offset] = (byte) (value & 0x00FF);
|
||||||
|
_data[_offset + 1] = (byte) ((value & 0xFF00) >> 8);
|
||||||
|
_offset += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ushort ReadUShort()
|
||||||
|
{
|
||||||
|
var value = (ushort) (_data[_offset] + (_data[_offset + 1] << 8));
|
||||||
|
_offset += 2;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_offset = 0;
|
||||||
|
_size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ToSpan(ref Span<byte> data)
|
||||||
|
{
|
||||||
|
var span = _data.AsSpan();
|
||||||
|
var dataSpan = span.Slice(0, _offset);
|
||||||
|
dataSpan.CopyTo(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FromSpan(ref ReadOnlySpan<byte> data)
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
ResizeIfNeed(data.Length);
|
||||||
|
var dataSpan = _data.AsSpan();
|
||||||
|
data.CopyTo(dataSpan);
|
||||||
|
_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()
|
||||||
|
{
|
||||||
|
var bytes = new byte[_offset];
|
||||||
|
Buffer.BlockCopy(_data, 0, bytes, 0, _offset);
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResizeIfNeed(int lenght)
|
||||||
|
{
|
||||||
|
if (_offset + lenght < _data.Length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var newData = new byte[_data.Length * 4 + lenght];
|
||||||
|
Buffer.BlockCopy(_data, 0, newData, 0, _data.Length);
|
||||||
|
_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
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})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-4
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Executable
+11
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"key": "defaultkey",
|
||||||
|
"statisticsInterval": 5,
|
||||||
|
"sendRate": 30,
|
||||||
|
"port": 4444,
|
||||||
|
"skipTimeout": 60,
|
||||||
|
"reconnectTimeout": 300,
|
||||||
|
"maxConnections": 4095,
|
||||||
|
"maxPlayersPerRoom": 20,
|
||||||
|
"maxRooms": 200
|
||||||
|
}
|
||||||
@@ -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("49.12.70.233", 4444, 50);
|
||||||
|
Thread.Sleep(300);
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.ReadKey();
|
||||||
|
Library.Deinitialize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
@@ -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
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -2,89 +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;
|
|
||||||
|
|
||||||
public Application(PluginFactory factory, Configuration configuration, int threadsCount)
|
public Application(PluginFactory factory, Configuration configuration)
|
||||||
{
|
{
|
||||||
_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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
_socketServer.Start(_configuration.Server.Port);
|
Library.Initialize();
|
||||||
|
_gameThread.Start();
|
||||||
|
_logger.Info("Started");
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var roomThread in _roomThreads)
|
public void Stop()
|
||||||
roomThread.Start();
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
{
|
||||||
foreach (var roomThread in _roomThreads)
|
_gameThread.Stop();
|
||||||
while (roomThread.ReadOutEvent(out var evnt))
|
Library.Deinitialize();
|
||||||
_socketServer.WriteEvent(evnt);
|
_logger.Info("Stopped");
|
||||||
|
|
||||||
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 Dispose()
|
|
||||||
{
|
|
||||||
foreach (var roomThread in _roomThreads)
|
|
||||||
roomThread.Dispose();
|
|
||||||
|
|
||||||
_roomThreads.Clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Ragon.Core;
|
|
||||||
|
|
||||||
public class AuthorizationManager
|
|
||||||
{
|
|
||||||
public virtual bool OnAuthorize(uint peerId, ref ReadOnlySpan<byte> payload)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
using NLog;
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using NLog;
|
||||||
|
|
||||||
namespace Ragon.Core
|
namespace Ragon.Core
|
||||||
{
|
{
|
||||||
@@ -6,12 +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 configuration = ConfigurationLoader.Load("config.json");
|
var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config.json");
|
||||||
var app = new Application(factory, configuration, 2);
|
var configuration = ConfigurationLoader.Load(filePath);
|
||||||
app.Start();
|
var app = new Application(factory, configuration);
|
||||||
|
return app;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,17 +2,17 @@
|
|||||||
|
|
||||||
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 ReconnectTimeout;
|
||||||
|
public int MaxConnections;
|
||||||
|
public int MaxPlayersPerRoom;
|
||||||
|
public int MaxRooms;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,7 @@ namespace Ragon.Core
|
|||||||
public static class ConfigurationLoader
|
public static class ConfigurationLoader
|
||||||
{
|
{
|
||||||
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||||
private static readonly string _serverVersion = "1.0.0-rc";
|
private static readonly string _serverVersion = "1.0.9-rc";
|
||||||
|
|
||||||
private static void CopyrightInfo()
|
private static void CopyrightInfo()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,17 +6,21 @@ public class Entity
|
|||||||
{
|
{
|
||||||
private static int _idGenerator = 0;
|
private static int _idGenerator = 0;
|
||||||
public int EntityId { get; private set; }
|
public int EntityId { get; private set; }
|
||||||
|
public int StaticId { get; private set; }
|
||||||
public uint OwnerId { get; private set; }
|
public uint OwnerId { get; private set; }
|
||||||
public ushort EntityType { get; private set; }
|
public ushort EntityType { get; private set; }
|
||||||
public RagonAuthority Authority { get; private set; }
|
public RagonAuthority Authority { get; private set; }
|
||||||
public EntityState State { get; private set; }
|
public EntityState State { get; private set; }
|
||||||
|
public EntityState Payload { get; private set; }
|
||||||
|
|
||||||
public Entity(uint ownerId, ushort entityType, RagonAuthority stateAuthority, RagonAuthority eventAuthority)
|
public Entity(uint ownerId, ushort entityType, int staticId, RagonAuthority stateAuthority, RagonAuthority eventAuthority)
|
||||||
{
|
{
|
||||||
OwnerId = ownerId;
|
OwnerId = ownerId;
|
||||||
|
StaticId = staticId;
|
||||||
EntityType = entityType;
|
EntityType = entityType;
|
||||||
EntityId = _idGenerator++;
|
EntityId = _idGenerator++;
|
||||||
State = new EntityState(stateAuthority);
|
State = new EntityState(stateAuthority);
|
||||||
|
Payload = new EntityState(stateAuthority);
|
||||||
Authority = eventAuthority;
|
Authority = eventAuthority;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,25 +8,31 @@ public class EntityState
|
|||||||
{
|
{
|
||||||
public bool isDirty { get; private set; }
|
public bool isDirty { get; private set; }
|
||||||
public RagonAuthority Authority { get; private set; }
|
public RagonAuthority Authority { get; private set; }
|
||||||
|
public int Size => _size;
|
||||||
|
|
||||||
public byte[] Data
|
private int _size = 0;
|
||||||
{
|
private byte[] _data = new byte[2048];
|
||||||
get => Data;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
Data = value;
|
|
||||||
isDirty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public EntityState(RagonAuthority ragonAuthority)
|
public EntityState(RagonAuthority ragonAuthority)
|
||||||
{
|
{
|
||||||
Authority = ragonAuthority;
|
Authority = ragonAuthority;
|
||||||
|
isDirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlySpan<byte> Read()
|
||||||
|
{
|
||||||
|
return _data.AsSpan().Slice(0, _size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(ref ReadOnlySpan<byte> src)
|
||||||
|
{
|
||||||
|
src.CopyTo(_data);
|
||||||
|
_size = src.Length;
|
||||||
isDirty = true;
|
isDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
isDirty = true;
|
isDirty = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Ragon.Core
|
|
||||||
{
|
|
||||||
public struct Event
|
|
||||||
{
|
|
||||||
public EventType Type;
|
|
||||||
public DeliveryType Delivery;
|
|
||||||
public byte[] Data;
|
|
||||||
public uint PeerId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()
|
|
||||||
// {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
namespace Ragon.Core
|
|
||||||
{
|
|
||||||
public enum EventType
|
|
||||||
{
|
|
||||||
CONNECTED,
|
|
||||||
DISCONNECTED,
|
|
||||||
TIMEOUT,
|
|
||||||
DATA,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Executable
+457
@@ -0,0 +1,457 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using Ragon.Common;
|
||||||
|
|
||||||
|
namespace Ragon.Core
|
||||||
|
{
|
||||||
|
public class GameRoom : IGameRoom
|
||||||
|
{
|
||||||
|
public int PlayersMin { get; private set; }
|
||||||
|
public int PlayersMax { get; private set; }
|
||||||
|
public int PlayersCount => _players.Count;
|
||||||
|
public string Id { get; private set; }
|
||||||
|
public string Map { get; private set; }
|
||||||
|
|
||||||
|
private ILogger _logger = LogManager.GetCurrentClassLogger();
|
||||||
|
private Dictionary<uint, Player> _players = new();
|
||||||
|
private Dictionary<int, Entity> _entities = new();
|
||||||
|
private uint _owner;
|
||||||
|
|
||||||
|
private readonly IScheduler _scheduler;
|
||||||
|
private readonly IGameThread _gameThread;
|
||||||
|
private readonly PluginBase _plugin;
|
||||||
|
private readonly RagonSerializer _serializer = new(512);
|
||||||
|
|
||||||
|
// Cache
|
||||||
|
private uint[] _readyPlayers = Array.Empty<uint>();
|
||||||
|
private uint[] _allPlayers = Array.Empty<uint>();
|
||||||
|
private Entity[] _entitiesAll = Array.Empty<Entity>();
|
||||||
|
|
||||||
|
public GameRoom(IGameThread gameThread, PluginBase pluginBase, string map, int min, int max)
|
||||||
|
{
|
||||||
|
_gameThread = gameThread;
|
||||||
|
_plugin = pluginBase;
|
||||||
|
_scheduler = new Scheduler();
|
||||||
|
|
||||||
|
Map = map;
|
||||||
|
PlayersMin = min;
|
||||||
|
PlayersMax = max;
|
||||||
|
Id = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
_plugin.Attach(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Joined(Player player, ReadOnlySpan<byte> payload)
|
||||||
|
{
|
||||||
|
if (_players.Count == 0)
|
||||||
|
{
|
||||||
|
_owner = player.PeerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
_serializer.Clear();
|
||||||
|
_serializer.WriteOperation(RagonOperation.PLAYER_JOINED);
|
||||||
|
_serializer.WriteUShort((ushort) player.PeerId);
|
||||||
|
_serializer.WriteString(player.Id);
|
||||||
|
_serializer.WriteString(player.PlayerName);
|
||||||
|
|
||||||
|
var sendData = _serializer.ToArray();
|
||||||
|
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
|
||||||
|
}
|
||||||
|
|
||||||
|
_players.Add(player.PeerId, player);
|
||||||
|
_allPlayers = _players.Select(p => p.Key).ToArray();
|
||||||
|
|
||||||
|
{
|
||||||
|
_serializer.Clear();
|
||||||
|
_serializer.WriteOperation(RagonOperation.JOIN_SUCCESS);
|
||||||
|
_serializer.WriteString(Id);
|
||||||
|
_serializer.WriteString(player.Id);
|
||||||
|
_serializer.WriteString(GetOwner().Id);
|
||||||
|
_serializer.WriteUShort((ushort) PlayersMin);
|
||||||
|
_serializer.WriteUShort((ushort) PlayersMax);
|
||||||
|
|
||||||
|
var sendData = _serializer.ToArray();
|
||||||
|
Send(player.PeerId, sendData, DeliveryType.Reliable);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
_serializer.Clear();
|
||||||
|
_serializer.WriteOperation(RagonOperation.LOAD_SCENE);
|
||||||
|
_serializer.WriteString(Map);
|
||||||
|
|
||||||
|
var sendData = _serializer.ToArray();
|
||||||
|
Send(player.PeerId, sendData, DeliveryType.Reliable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Leave(uint peerId)
|
||||||
|
{
|
||||||
|
if (_players.Remove(peerId, out var player))
|
||||||
|
{
|
||||||
|
_allPlayers = _players.Select(p => p.Key).ToArray();
|
||||||
|
_readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray();
|
||||||
|
|
||||||
|
var isOwnershipChange = player.PeerId == _owner;
|
||||||
|
|
||||||
|
{
|
||||||
|
_plugin.OnPlayerLeaved(player);
|
||||||
|
|
||||||
|
_serializer.Clear();
|
||||||
|
_serializer.WriteOperation(RagonOperation.PLAYER_LEAVED);
|
||||||
|
_serializer.WriteString(player.Id);
|
||||||
|
|
||||||
|
_serializer.WriteUShort((ushort) player.EntitiesIds.Count);
|
||||||
|
foreach (var entityId in player.EntitiesIds)
|
||||||
|
{
|
||||||
|
_serializer.WriteInt(entityId);
|
||||||
|
_entities.Remove(entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var sendData = _serializer.ToArray();
|
||||||
|
Broadcast(_readyPlayers, sendData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_allPlayers.Length > 0 && isOwnershipChange)
|
||||||
|
{
|
||||||
|
var newRoomOwnerId = _allPlayers[0];
|
||||||
|
var newRoomOwner = _players[newRoomOwnerId];
|
||||||
|
|
||||||
|
_owner = newRoomOwnerId;
|
||||||
|
|
||||||
|
{
|
||||||
|
_plugin.OnOwnershipChanged(newRoomOwner);
|
||||||
|
|
||||||
|
_serializer.Clear();
|
||||||
|
_serializer.WriteOperation(RagonOperation.OWNERSHIP_CHANGED);
|
||||||
|
_serializer.WriteString(newRoomOwner.Id);
|
||||||
|
|
||||||
|
var sendData = _serializer.ToArray();
|
||||||
|
Broadcast(_readyPlayers, sendData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_entitiesAll = _entities.Values.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ProcessEvent(uint peerId, ReadOnlySpan<byte> rawData)
|
||||||
|
{
|
||||||
|
var operation = (RagonOperation) rawData[0];
|
||||||
|
var payloadRawData = rawData.Slice(1, rawData.Length - 1);
|
||||||
|
|
||||||
|
_serializer.Clear();
|
||||||
|
_serializer.FromSpan(ref payloadRawData);
|
||||||
|
|
||||||
|
switch (operation)
|
||||||
|
{
|
||||||
|
case RagonOperation.REPLICATE_ENTITY_STATE:
|
||||||
|
{
|
||||||
|
var entityId = _serializer.ReadInt();
|
||||||
|
if (_entities.TryGetValue(entityId, out var ent))
|
||||||
|
{
|
||||||
|
if (ent.State.Authority == RagonAuthority.OWNER_ONLY && ent.OwnerId != peerId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var entityStateData = _serializer.ReadData(_serializer.Size);
|
||||||
|
ent.State.Write(ref entityStateData);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RagonOperation.REPLICATE_ENTITY_EVENT:
|
||||||
|
{
|
||||||
|
var evntId = _serializer.ReadUShort();
|
||||||
|
var evntMode = _serializer.ReadByte();
|
||||||
|
var entityId = _serializer.ReadInt();
|
||||||
|
|
||||||
|
if (!_entities.TryGetValue(entityId, out var ent))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ent.Authority == RagonAuthority.OWNER_ONLY && ent.OwnerId != peerId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Span<byte> payloadRaw = stackalloc byte[_serializer.Size];
|
||||||
|
var payloadData = _serializer.ReadData(_serializer.Size);
|
||||||
|
payloadData.CopyTo(payloadRaw);
|
||||||
|
|
||||||
|
ReadOnlySpan<byte> payload = payloadRaw;
|
||||||
|
if (_plugin.InternalHandle(peerId, entityId, evntId, ref payload))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_serializer.Clear();
|
||||||
|
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
|
||||||
|
_serializer.WriteUShort(evntId);
|
||||||
|
_serializer.WriteUShort((ushort) peerId);
|
||||||
|
_serializer.WriteByte(evntMode);
|
||||||
|
_serializer.WriteInt(entityId);
|
||||||
|
_serializer.WriteData(ref payload);
|
||||||
|
var sendData = _serializer.ToArray();
|
||||||
|
|
||||||
|
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RagonOperation.REPLICATE_EVENT:
|
||||||
|
{
|
||||||
|
var evntId = _serializer.ReadUShort();
|
||||||
|
var evntMode = _serializer.ReadByte();
|
||||||
|
Span<byte> payloadRaw = stackalloc byte[_serializer.Size];
|
||||||
|
var payloadData = _serializer.ReadData(_serializer.Size);
|
||||||
|
payloadData.CopyTo(payloadRaw);
|
||||||
|
|
||||||
|
ReadOnlySpan<byte> payload = payloadRaw;
|
||||||
|
if (_plugin.InternalHandle(peerId, evntId, ref payload))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_serializer.Clear();
|
||||||
|
_serializer.WriteOperation(RagonOperation.REPLICATE_EVENT);
|
||||||
|
_serializer.WriteUShort((ushort) peerId);
|
||||||
|
_serializer.WriteByte(evntMode);
|
||||||
|
_serializer.WriteUShort(evntId);
|
||||||
|
_serializer.WriteData(ref payload);
|
||||||
|
|
||||||
|
var sendData = _serializer.ToArray();
|
||||||
|
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RagonOperation.CREATE_STATIC_ENTITY:
|
||||||
|
{
|
||||||
|
var entityType = _serializer.ReadUShort();
|
||||||
|
var staticId = _serializer.ReadUShort();
|
||||||
|
var stateAuthority = (RagonAuthority) _serializer.ReadByte();
|
||||||
|
var eventAuthority = (RagonAuthority) _serializer.ReadByte();
|
||||||
|
var entity = new Entity(peerId, entityType, staticId, stateAuthority, eventAuthority);
|
||||||
|
|
||||||
|
{
|
||||||
|
var entityPayload = _serializer.ReadData(_serializer.Size);
|
||||||
|
entity.Payload.Write(ref entityPayload);
|
||||||
|
}
|
||||||
|
|
||||||
|
var player = _players[peerId];
|
||||||
|
player.Entities.Add(entity);
|
||||||
|
player.EntitiesIds.Add(entity.EntityId);
|
||||||
|
|
||||||
|
var ownerId = (ushort) peerId;
|
||||||
|
|
||||||
|
_entities.Add(entity.EntityId, entity);
|
||||||
|
_entitiesAll = _entities.Values.ToArray();
|
||||||
|
|
||||||
|
_plugin.OnEntityCreated(player, entity);
|
||||||
|
|
||||||
|
_serializer.Clear();
|
||||||
|
_serializer.WriteOperation(RagonOperation.CREATE_STATIC_ENTITY);
|
||||||
|
_serializer.WriteUShort(entityType);
|
||||||
|
_serializer.WriteUShort(staticId);
|
||||||
|
_serializer.WriteByte((byte) stateAuthority);
|
||||||
|
_serializer.WriteByte((byte) eventAuthority);
|
||||||
|
_serializer.WriteInt(entity.EntityId);
|
||||||
|
_serializer.WriteUShort(ownerId);
|
||||||
|
|
||||||
|
{
|
||||||
|
var entityPayload = entity.Payload.Read();
|
||||||
|
_serializer.WriteData(ref entityPayload);
|
||||||
|
}
|
||||||
|
|
||||||
|
var sendData = _serializer.ToArray();
|
||||||
|
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RagonOperation.CREATE_ENTITY:
|
||||||
|
{
|
||||||
|
var entityType = _serializer.ReadUShort();
|
||||||
|
var stateAuthority = (RagonAuthority) _serializer.ReadByte();
|
||||||
|
var eventAuthority = (RagonAuthority) _serializer.ReadByte();
|
||||||
|
var entity = new Entity(peerId, entityType, -1, stateAuthority, eventAuthority);
|
||||||
|
|
||||||
|
{
|
||||||
|
var entityPayload = _serializer.ReadData(_serializer.Size);
|
||||||
|
entity.Payload.Write(ref entityPayload);
|
||||||
|
}
|
||||||
|
|
||||||
|
var player = _players[peerId];
|
||||||
|
player.Entities.Add(entity);
|
||||||
|
player.EntitiesIds.Add(entity.EntityId);
|
||||||
|
|
||||||
|
var ownerId = (ushort) peerId;
|
||||||
|
|
||||||
|
_entities.Add(entity.EntityId, entity);
|
||||||
|
_entitiesAll = _entities.Values.ToArray();
|
||||||
|
|
||||||
|
_plugin.OnEntityCreated(player, entity);
|
||||||
|
|
||||||
|
_serializer.Clear();
|
||||||
|
_serializer.WriteOperation(RagonOperation.CREATE_ENTITY);
|
||||||
|
_serializer.WriteUShort(entityType);
|
||||||
|
_serializer.WriteByte((byte) stateAuthority);
|
||||||
|
_serializer.WriteByte((byte) eventAuthority);
|
||||||
|
_serializer.WriteInt(entity.EntityId);
|
||||||
|
_serializer.WriteUShort(ownerId);
|
||||||
|
|
||||||
|
{
|
||||||
|
var entityPayload = entity.Payload.Read();
|
||||||
|
_serializer.WriteData(ref entityPayload);
|
||||||
|
}
|
||||||
|
|
||||||
|
var sendData = _serializer.ToArray();
|
||||||
|
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RagonOperation.DESTROY_ENTITY:
|
||||||
|
{
|
||||||
|
var entityId = _serializer.ReadInt();
|
||||||
|
if (_entities.TryGetValue(entityId, out var entity))
|
||||||
|
{
|
||||||
|
if (entity.Authority == RagonAuthority.OWNER_ONLY && entity.OwnerId != peerId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var player = _players[peerId];
|
||||||
|
var destroyPayload = _serializer.ReadData(_serializer.Size);
|
||||||
|
|
||||||
|
player.Entities.Remove(entity);
|
||||||
|
player.EntitiesIds.Remove(entity.EntityId);
|
||||||
|
|
||||||
|
_entities.Remove(entityId);
|
||||||
|
_entitiesAll = _entities.Values.ToArray();
|
||||||
|
|
||||||
|
_plugin.OnEntityDestroyed(player, entity);
|
||||||
|
|
||||||
|
_serializer.Clear();
|
||||||
|
_serializer.WriteOperation(RagonOperation.DESTROY_ENTITY);
|
||||||
|
_serializer.WriteInt(entityId);
|
||||||
|
_serializer.WriteData(ref destroyPayload);
|
||||||
|
|
||||||
|
var sendData = _serializer.ToArray();
|
||||||
|
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RagonOperation.SCENE_IS_LOADED:
|
||||||
|
{
|
||||||
|
_serializer.Clear();
|
||||||
|
_serializer.WriteOperation(RagonOperation.SNAPSHOT);
|
||||||
|
|
||||||
|
_serializer.WriteInt(_allPlayers.Length);
|
||||||
|
foreach (var playerPeerId in _allPlayers)
|
||||||
|
{
|
||||||
|
_serializer.WriteString(_players[playerPeerId].Id);
|
||||||
|
_serializer.WriteUShort((ushort) playerPeerId);
|
||||||
|
_serializer.WriteString(_players[playerPeerId].PlayerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
var dynamicCount = _entitiesAll.Where(e => e.StaticId == -1).ToArray();
|
||||||
|
_serializer.WriteInt(dynamicCount.Length);
|
||||||
|
foreach (var entity in dynamicCount)
|
||||||
|
{
|
||||||
|
if (entity.StaticId != -1) continue;
|
||||||
|
|
||||||
|
var payload = entity.Payload.Read();
|
||||||
|
var state = entity.State.Read();
|
||||||
|
|
||||||
|
_serializer.WriteInt(entity.EntityId);
|
||||||
|
_serializer.WriteByte((byte) entity.State.Authority);
|
||||||
|
_serializer.WriteByte((byte) entity.Authority);
|
||||||
|
_serializer.WriteUShort(entity.EntityType);
|
||||||
|
_serializer.WriteUShort((ushort) entity.OwnerId);
|
||||||
|
_serializer.WriteUShort((ushort) payload.Length);
|
||||||
|
_serializer.WriteData(ref payload);
|
||||||
|
_serializer.WriteUShort((ushort) state.Length);
|
||||||
|
_serializer.WriteData(ref state);
|
||||||
|
}
|
||||||
|
|
||||||
|
var staticCount = _entitiesAll.Where(e => e.StaticId != -1).ToArray();
|
||||||
|
_serializer.WriteInt(staticCount.Length);
|
||||||
|
foreach (var entity in staticCount)
|
||||||
|
{
|
||||||
|
var payload = entity.Payload.Read();
|
||||||
|
var state = entity.State.Read();
|
||||||
|
|
||||||
|
_serializer.WriteInt(entity.EntityId);
|
||||||
|
_serializer.WriteUShort((ushort) entity.StaticId);
|
||||||
|
_serializer.WriteByte((byte) entity.State.Authority);
|
||||||
|
_serializer.WriteByte((byte) entity.Authority);
|
||||||
|
_serializer.WriteUShort(entity.EntityType);
|
||||||
|
_serializer.WriteUShort((ushort) entity.OwnerId);
|
||||||
|
_serializer.WriteUShort((ushort) payload.Length);
|
||||||
|
_serializer.WriteData(ref payload);
|
||||||
|
_serializer.WriteUShort((ushort) state.Length);
|
||||||
|
_serializer.WriteData(ref state);
|
||||||
|
}
|
||||||
|
|
||||||
|
var sendData = _serializer.ToArray();
|
||||||
|
Send(peerId, sendData, DeliveryType.Reliable);
|
||||||
|
|
||||||
|
_players[peerId].IsLoaded = true;
|
||||||
|
_readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray();
|
||||||
|
|
||||||
|
_plugin.OnPlayerJoined(_players[peerId]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Tick(float deltaTime)
|
||||||
|
{
|
||||||
|
_scheduler.Tick(deltaTime);
|
||||||
|
|
||||||
|
foreach (var entity in _entitiesAll)
|
||||||
|
{
|
||||||
|
if (entity.State.isDirty)
|
||||||
|
{
|
||||||
|
var state = entity.State.Read();
|
||||||
|
|
||||||
|
_serializer.Clear();
|
||||||
|
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_STATE);
|
||||||
|
_serializer.WriteInt(entity.EntityId);
|
||||||
|
_serializer.WriteData(ref state);
|
||||||
|
|
||||||
|
var sendData = _serializer.ToArray();
|
||||||
|
Broadcast(_readyPlayers, sendData, DeliveryType.Unreliable);
|
||||||
|
|
||||||
|
entity.State.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
_plugin.OnStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
foreach (var peerId in _allPlayers)
|
||||||
|
_gameThread.Server.Disconnect(peerId, 0);
|
||||||
|
|
||||||
|
_plugin.OnStop();
|
||||||
|
_plugin.Detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Player GetPlayerById(uint peerId) => _players[peerId];
|
||||||
|
|
||||||
|
public Entity GetEntityById(int entityId) => _entities[entityId];
|
||||||
|
|
||||||
|
public Player GetOwner() => _players[_owner];
|
||||||
|
|
||||||
|
public IDispatcher GetThreadDispatcher() => _gameThread.ThreadDispatcher;
|
||||||
|
|
||||||
|
public IScheduler GetScheduler() => _scheduler;
|
||||||
|
|
||||||
|
public void Send(uint peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
|
||||||
|
{
|
||||||
|
_gameThread.Server.Send(peerId, rawData, deliveryType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Broadcast(uint[] peersIds, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
|
||||||
|
{
|
||||||
|
_gameThread.Server.Broadcast(peersIds, rawData, deliveryType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Broadcast(byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
|
||||||
|
{
|
||||||
|
_gameThread.Server.Broadcast(_allPlayers, rawData, deliveryType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+128
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
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 IScheduler GetScheduler();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using Ragon.Common;
|
||||||
|
|
||||||
|
namespace Ragon.Core;
|
||||||
|
|
||||||
|
public interface IGameThread
|
||||||
|
{
|
||||||
|
public IDispatcher ThreadDispatcher { get; }
|
||||||
|
public ISocketServer Server { get; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Ragon.Core;
|
||||||
|
|
||||||
|
public interface ILobby
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Ragon.Common;
|
||||||
|
|
||||||
|
namespace Ragon.Core;
|
||||||
|
|
||||||
|
public class Lobby : ILobby
|
||||||
|
{
|
||||||
|
private readonly RagonSerializer _serializer;
|
||||||
|
private readonly RoomManager _roomManager;
|
||||||
|
private readonly AuthorizationManager _authorizationManager;
|
||||||
|
|
||||||
|
public AuthorizationManager AuthorizationManager => _authorizationManager;
|
||||||
|
|
||||||
|
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 min = _serializer.ReadUShort();
|
||||||
|
var max = _serializer.ReadUShort();
|
||||||
|
var map = _serializer.ReadString();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ namespace Ragon.Core
|
|||||||
{
|
{
|
||||||
public class Player
|
public class Player
|
||||||
{
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
public uint PeerId { get; set; }
|
public uint PeerId { get; set; }
|
||||||
public string PlayerName { get; set; }
|
public string PlayerName { get; set; }
|
||||||
public bool IsLoaded { get; set; }
|
public bool IsLoaded { get; set; }
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -7,28 +7,31 @@ 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);
|
||||||
|
|
||||||
private delegate void SubscribeEntityDelegate(Player player, Entity entity, ref ReadOnlySpan<byte> data);
|
private delegate void SubscribeEntityDelegate(Player player, Entity entity, ref ReadOnlySpan<byte> data);
|
||||||
|
|
||||||
private Dictionary<ushort, SubscribeDelegate> _globalEvents = new();
|
private Dictionary<ushort, SubscribeDelegate> _globalEvents = new();
|
||||||
private Dictionary<int, Dictionary<ushort, SubscribeEntityDelegate>> _entityEvents = new();
|
private Dictionary<int, Dictionary<ushort, SubscribeEntityDelegate>> _entityEvents = new();
|
||||||
private BitBuffer _buffer = new BitBuffer(8192);
|
private readonly BitBuffer _buffer = 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();
|
||||||
@@ -38,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,14 +65,11 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
_globalEvents.Add(evntCode, (Player player, ref ReadOnlySpan<byte> raw) =>
|
_globalEvents.Add(evntCode, (Player player, ref ReadOnlySpan<byte> raw) => { action.Invoke(player); });
|
||||||
{
|
|
||||||
action.Invoke(player);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Subscribe<T>(Entity entity, ushort evntCode, Action<Player, Entity, T> action) where T : IRagonSerializable, new()
|
public void Subscribe<T>(Entity entity, ushort evntCode, Action<Player, Entity, T> action) where T : IRagonSerializable, new()
|
||||||
@@ -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,9 +107,10 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
_buffer.Clear();
|
_buffer.Clear();
|
||||||
_buffer.FromSpan(ref raw, raw.Length);
|
_buffer.FromSpan(ref raw, raw.Length);
|
||||||
data.Deserialize(_buffer);
|
data.Deserialize(_buffer);
|
||||||
@@ -124,23 +125,17 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
_entityEvents[entity.EntityId].Add(evntCode, (Player player, Entity ent, ref ReadOnlySpan<byte> raw) =>
|
_entityEvents[entity.EntityId].Add(evntCode, (Player player, Entity ent, ref ReadOnlySpan<byte> raw) => { action.Invoke(player, ent); });
|
||||||
{
|
|
||||||
action.Invoke(player, ent);
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
_entityEvents.Add(entity.EntityId, new Dictionary<ushort, SubscribeEntityDelegate>());
|
_entityEvents.Add(entity.EntityId, new Dictionary<ushort, SubscribeEntityDelegate>());
|
||||||
_entityEvents[entity.EntityId].Add(evntCode, (Player player, Entity ent, ref ReadOnlySpan<byte> raw) =>
|
_entityEvents[entity.EntityId].Add(evntCode, (Player player, Entity ent, ref ReadOnlySpan<byte> raw) => { action.Invoke(player, ent); });
|
||||||
{
|
|
||||||
action.Invoke(player, ent);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,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;
|
||||||
@@ -169,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;
|
||||||
}
|
}
|
||||||
@@ -179,72 +174,64 @@ namespace Ragon.Core
|
|||||||
|
|
||||||
public void SendEvent(Player player, uint eventCode, IRagonSerializable payload)
|
public void SendEvent(Player player, uint eventCode, IRagonSerializable payload)
|
||||||
{
|
{
|
||||||
|
_serializer.Clear();
|
||||||
|
_serializer.WriteOperation(RagonOperation.REPLICATE_EVENT);
|
||||||
|
|
||||||
_buffer.Clear();
|
_buffer.Clear();
|
||||||
payload.Serialize(_buffer);
|
payload.Serialize(_buffer);
|
||||||
|
|
||||||
var sendData = new byte[_buffer.Length + 4];
|
var payloadData = _serializer.GetWritableData(_buffer.Length);
|
||||||
Span<byte> data = sendData.AsSpan();
|
|
||||||
Span<byte> operationData = data.Slice(0, 2);
|
|
||||||
Span<byte> eventCodeData = data.Slice(2, 2);
|
|
||||||
Span<byte> payloadData = data.Slice(4, data.Length - 4);
|
|
||||||
|
|
||||||
_buffer.ToSpan(ref payloadData);
|
_buffer.ToSpan(ref payloadData);
|
||||||
|
|
||||||
RagonHeader.WriteUShort((ushort) RagonOperation.REPLICATE_EVENT, ref operationData);
|
var sendData = _serializer.ToArray();
|
||||||
RagonHeader.WriteUShort((ushort) eventCode, ref eventCodeData);
|
GameRoom.Send(player.PeerId, sendData);
|
||||||
|
|
||||||
Room.Send(player.PeerId, sendData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendEvent(ushort eventCode, IRagonSerializable payload)
|
public void BroadcastEvent(ushort eventCode, IRagonSerializable payload)
|
||||||
{
|
{
|
||||||
|
_serializer.Clear();
|
||||||
|
_serializer.WriteOperation(RagonOperation.REPLICATE_EVENT);
|
||||||
|
|
||||||
_buffer.Clear();
|
_buffer.Clear();
|
||||||
payload.Serialize(_buffer);
|
payload.Serialize(_buffer);
|
||||||
|
|
||||||
var sendData = new byte[_buffer.Length + 4];
|
var payloadData = _serializer.GetWritableData(_buffer.Length);
|
||||||
Span<byte> data = sendData.AsSpan();
|
|
||||||
Span<byte> operationData = data.Slice(0, 2);
|
|
||||||
Span<byte> eventCodeData = data.Slice(2, 2);
|
|
||||||
Span<byte> payloadData = data.Slice(4, _buffer.Length);
|
|
||||||
|
|
||||||
RagonHeader.WriteUShort((ushort) RagonOperation.REPLICATE_EVENT,ref operationData);
|
|
||||||
RagonHeader.WriteUShort( eventCode, ref eventCodeData);
|
|
||||||
|
|
||||||
_buffer.ToSpan(ref payloadData);
|
_buffer.ToSpan(ref payloadData);
|
||||||
|
|
||||||
Room.Broadcast(sendData);
|
var sendData = _serializer.ToArray();
|
||||||
|
GameRoom.Broadcast(sendData, DeliveryType.Reliable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendEntityEvent(Player player, Entity entity, IRagonSerializable payload)
|
public void SendEntityEvent(Player player, Entity entity, IRagonSerializable payload)
|
||||||
{
|
{
|
||||||
|
_serializer.Clear();
|
||||||
|
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
|
||||||
|
_serializer.WriteInt(entity.EntityId);
|
||||||
|
|
||||||
_buffer.Clear();
|
_buffer.Clear();
|
||||||
payload.Serialize(_buffer);
|
payload.Serialize(_buffer);
|
||||||
|
|
||||||
var sendData = new byte[_buffer.Length + 6];
|
var payloadData = _serializer.GetWritableData(_buffer.Length);
|
||||||
Span<byte> data = sendData.AsSpan();
|
_buffer.ToSpan(ref payloadData);
|
||||||
Span<byte> operationData = data.Slice(0, 2);
|
|
||||||
Span<byte> entityData = data.Slice(2, 4);
|
|
||||||
|
|
||||||
RagonHeader.WriteUShort((ushort) RagonOperation.REPLICATE_EVENT, ref operationData);
|
var sendData = _serializer.ToArray();
|
||||||
RagonHeader.WriteInt(entity.EntityId, ref entityData);
|
GameRoom.Send(player.PeerId, sendData, DeliveryType.Reliable);
|
||||||
|
|
||||||
Room.Send(player.PeerId, sendData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendEntityEvent(Entity entity, IRagonSerializable payload)
|
public void BroadcastEntityEvent(Entity entity, IRagonSerializable payload)
|
||||||
{
|
{
|
||||||
|
_serializer.Clear();
|
||||||
|
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
|
||||||
|
_serializer.WriteInt(entity.EntityId);
|
||||||
|
|
||||||
_buffer.Clear();
|
_buffer.Clear();
|
||||||
payload.Serialize(_buffer);
|
payload.Serialize(_buffer);
|
||||||
|
|
||||||
var sendData = new byte[_buffer.Length + 6];
|
var payloadData = _serializer.GetWritableData(_buffer.Length);
|
||||||
Span<byte> data = sendData.AsSpan();
|
_buffer.ToSpan(ref payloadData);
|
||||||
Span<byte> operationData = data.Slice(0, 2);
|
|
||||||
Span<byte> entityData = data.Slice(2, 4);
|
|
||||||
|
|
||||||
RagonHeader.WriteUShort((ushort) RagonOperation.REPLICATE_EVENT, ref operationData);
|
var sendData = _serializer.ToArray();
|
||||||
RagonHeader.WriteInt(entity.EntityId, ref entityData);
|
GameRoom.Broadcast(sendData);
|
||||||
|
|
||||||
Room.Broadcast(sendData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -258,18 +245,16 @@ namespace Ragon.Core
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnOwnerChanged(Player player)
|
public virtual void OnOwnershipChanged(Player player)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnEntityCreated(Player creator, Entity entity)
|
public virtual void OnEntityCreated(Player creator, Entity entity)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnEntityDestroyed(Player destoyer, Entity entity)
|
public virtual void OnEntityDestroyed(Player destoyer, Entity entity)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnStart()
|
public virtual void OnStart()
|
||||||
@@ -280,10 +265,6 @@ namespace Ragon.Core
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnTick(ulong ticks, float deltaTime)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,393 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using NetStack.Serialization;
|
|
||||||
using NLog;
|
|
||||||
using Ragon.Common;
|
|
||||||
|
|
||||||
namespace Ragon.Core
|
|
||||||
{
|
|
||||||
public class Room : IDisposable
|
|
||||||
{
|
|
||||||
public int PlayersMin { get; private set; }
|
|
||||||
public int PlayersMax { get; private set; }
|
|
||||||
public int PlayersCount => _players.Count;
|
|
||||||
public string Id { get; private set; }
|
|
||||||
public string Map { get; private set; }
|
|
||||||
|
|
||||||
private ILogger _logger = LogManager.GetCurrentClassLogger();
|
|
||||||
private Dictionary<uint, Player> _players = new();
|
|
||||||
private Dictionary<int, Entity> _entities = new();
|
|
||||||
private uint _owner;
|
|
||||||
private uint _ticks;
|
|
||||||
|
|
||||||
private readonly PluginBase _plugin;
|
|
||||||
private readonly RoomThread _roomThread;
|
|
||||||
|
|
||||||
// Cache
|
|
||||||
private uint[] _readyPlayers = Array.Empty<uint>();
|
|
||||||
private uint[] _allPlayers = Array.Empty<uint>();
|
|
||||||
private Entity[] _entitiesAll = Array.Empty<Entity>();
|
|
||||||
|
|
||||||
public Room(RoomThread roomThread, PluginBase pluginBase, string map, int min, int max)
|
|
||||||
{
|
|
||||||
_roomThread = roomThread;
|
|
||||||
_plugin = pluginBase;
|
|
||||||
|
|
||||||
Map = map;
|
|
||||||
PlayersMin = min;
|
|
||||||
PlayersMax = max;
|
|
||||||
Id = Guid.NewGuid().ToString();
|
|
||||||
|
|
||||||
_logger.Info($"Room created with plugin: {_plugin.GetType().Name}");
|
|
||||||
_plugin.Attach(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Joined(uint peerId, ReadOnlySpan<byte> payload)
|
|
||||||
{
|
|
||||||
if (_players.Count == 0)
|
|
||||||
{
|
|
||||||
_owner = peerId;
|
|
||||||
}
|
|
||||||
|
|
||||||
var player = new Player()
|
|
||||||
{
|
|
||||||
PlayerName = "Player " + peerId,
|
|
||||||
PeerId = peerId,
|
|
||||||
IsLoaded = false,
|
|
||||||
Entities = new List<Entity>(),
|
|
||||||
EntitiesIds = new List<int>(),
|
|
||||||
};
|
|
||||||
|
|
||||||
_players.Add(peerId, player);
|
|
||||||
_allPlayers = _players.Select(p => p.Key).ToArray();
|
|
||||||
|
|
||||||
{
|
|
||||||
var idRaw = Encoding.UTF8.GetBytes(Id).AsSpan();
|
|
||||||
|
|
||||||
var sendData = new byte[idRaw.Length + 18];
|
|
||||||
var data = sendData.AsSpan();
|
|
||||||
|
|
||||||
Span<byte> operationData = data.Slice(0, 2);
|
|
||||||
Span<byte> peerData = data.Slice(2, 4);
|
|
||||||
Span<byte> ownerData = data.Slice(4, 4);
|
|
||||||
Span<byte> minData = data.Slice(10, 4);
|
|
||||||
Span<byte> maxData = data.Slice(14, 4);
|
|
||||||
Span<byte> idData = data.Slice(18, idRaw.Length);
|
|
||||||
|
|
||||||
RagonHeader.WriteUShort((ushort) RagonOperation.JOIN_ROOM, ref operationData);
|
|
||||||
RagonHeader.WriteInt((int) peerId, ref peerData);
|
|
||||||
RagonHeader.WriteInt((int) _owner, ref ownerData);
|
|
||||||
RagonHeader.WriteInt(PlayersMin, ref minData);
|
|
||||||
RagonHeader.WriteInt(PlayersMax, ref maxData);
|
|
||||||
|
|
||||||
idRaw.CopyTo(idData);
|
|
||||||
|
|
||||||
Send(peerId, sendData);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
var sceneRawData = Encoding.UTF8.GetBytes(Map).AsSpan();
|
|
||||||
var sendData = new byte[sceneRawData.Length + 2];
|
|
||||||
var data = sendData.AsSpan();
|
|
||||||
|
|
||||||
Span<byte> operationData = data.Slice(0, 2);
|
|
||||||
Span<byte> sceneData = data.Slice(2, sceneRawData.Length);
|
|
||||||
|
|
||||||
RagonHeader.WriteUShort((ushort) RagonOperation.LOAD_SCENE, ref operationData);
|
|
||||||
sceneRawData.CopyTo(sceneData);
|
|
||||||
|
|
||||||
Send(peerId, sendData, DeliveryType.Reliable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Leave(uint peerId)
|
|
||||||
{
|
|
||||||
if (_players.Remove(peerId, out var player))
|
|
||||||
{
|
|
||||||
_allPlayers = _players.Select(p => p.Key).ToArray();
|
|
||||||
|
|
||||||
_plugin.OnPlayerLeaved(player);
|
|
||||||
|
|
||||||
foreach (var entityId in player.EntitiesIds)
|
|
||||||
{
|
|
||||||
var sendData = new byte[6];
|
|
||||||
var entityData = sendData.AsSpan();
|
|
||||||
var operationData = entityData.Slice(0, 2);
|
|
||||||
|
|
||||||
RagonHeader.WriteUShort((ushort) RagonOperation.DESTROY_ENTITY, ref operationData);
|
|
||||||
RagonHeader.WriteInt(entityId, ref entityData);
|
|
||||||
|
|
||||||
Broadcast(_allPlayers, sendData);
|
|
||||||
|
|
||||||
_entities.Remove(entityId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ProcessEvent(RagonOperation operation, uint peerId, ReadOnlySpan<byte> rawData)
|
|
||||||
{
|
|
||||||
switch (operation)
|
|
||||||
{
|
|
||||||
case RagonOperation.REPLICATE_ENTITY_STATE:
|
|
||||||
{
|
|
||||||
var entityData = rawData.Slice(2, 4);
|
|
||||||
var entityId = RagonHeader.ReadInt(ref entityData);
|
|
||||||
if (_entities.TryGetValue(entityId, out var ent))
|
|
||||||
{
|
|
||||||
if (ent.State.Authority == RagonAuthority.OWNER_ONLY && ent.OwnerId != peerId)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ent.State.Data = rawData.Slice(6, rawData.Length - 6).ToArray();
|
|
||||||
|
|
||||||
var data = new byte[rawData.Length];
|
|
||||||
|
|
||||||
rawData.CopyTo(data);
|
|
||||||
|
|
||||||
Broadcast(_readyPlayers, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case RagonOperation.REPLICATE_ENTITY_EVENT:
|
|
||||||
{
|
|
||||||
var evntCodeData = rawData.Slice(2, 2);
|
|
||||||
var entityIdData = rawData.Slice(4, 4);
|
|
||||||
var evntId = RagonHeader.ReadUShort(ref evntCodeData);
|
|
||||||
var entityId = RagonHeader.ReadInt(ref entityIdData);
|
|
||||||
|
|
||||||
if (!_entities.TryGetValue(entityId, out var ent))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (ent.Authority == RagonAuthority.OWNER_ONLY && ent.OwnerId != peerId)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var payload = rawData.Slice(8, rawData.Length - 8);
|
|
||||||
if (_plugin.InternalHandle(peerId, entityId, evntId, ref payload))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var data = new byte[rawData.Length];
|
|
||||||
|
|
||||||
rawData.CopyTo(data);
|
|
||||||
|
|
||||||
Broadcast(_readyPlayers, data, DeliveryType.Reliable);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case RagonOperation.REPLICATE_EVENT:
|
|
||||||
{
|
|
||||||
var evntCodeData = rawData.Slice(2, 2);
|
|
||||||
var evntId = RagonHeader.ReadUShort(ref evntCodeData);
|
|
||||||
|
|
||||||
var payload = rawData.Slice(4, rawData.Length - 4);
|
|
||||||
if (_plugin.InternalHandle(peerId, evntId, ref payload))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var data = new byte[rawData.Length];
|
|
||||||
|
|
||||||
rawData.CopyTo(data);
|
|
||||||
|
|
||||||
Broadcast(_readyPlayers, data, DeliveryType.Reliable);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case RagonOperation.CREATE_ENTITY:
|
|
||||||
{
|
|
||||||
var typeData = rawData.Slice(2, 2);
|
|
||||||
var authorityData = rawData.Slice(2, 2);
|
|
||||||
var entityPayloadData = rawData.Slice(4, rawData.Length - 4);
|
|
||||||
|
|
||||||
var entityType = RagonHeader.ReadUShort(ref typeData);
|
|
||||||
var stateAuthority = (RagonAuthority) authorityData[0];
|
|
||||||
var eventAuthority = (RagonAuthority) authorityData[1];
|
|
||||||
var entity = new Entity(peerId, entityType, stateAuthority, eventAuthority);
|
|
||||||
entity.State.Data = entityPayloadData.ToArray();
|
|
||||||
|
|
||||||
var player = _players[peerId];
|
|
||||||
player.Entities.Add(entity);
|
|
||||||
player.EntitiesIds.Add(entity.EntityId);
|
|
||||||
|
|
||||||
_entities.Add(entity.EntityId, entity);
|
|
||||||
_entitiesAll = _entities.Values.ToArray();
|
|
||||||
|
|
||||||
_plugin.OnEntityCreated(player, entity);
|
|
||||||
|
|
||||||
var data = new byte[entityPayloadData.Length + 14];
|
|
||||||
var sendData = data.AsSpan();
|
|
||||||
var operationData = sendData.Slice(0, 2);
|
|
||||||
var entityTypeData = sendData.Slice(2, 2);
|
|
||||||
var authority = sendData.Slice(4, 2);
|
|
||||||
var entityIdData = sendData.Slice(6, 4);
|
|
||||||
var peerData = sendData.Slice(10, 4);
|
|
||||||
var payload = sendData.Slice(14, entityPayloadData.Length);
|
|
||||||
|
|
||||||
entityPayloadData.CopyTo(payload);
|
|
||||||
|
|
||||||
authority[0] = authorityData[0];
|
|
||||||
authority[1] = authorityData[1];
|
|
||||||
|
|
||||||
RagonHeader.WriteUShort((ushort) RagonOperation.CREATE_ENTITY, ref operationData);
|
|
||||||
RagonHeader.WriteUShort(entityType, ref entityTypeData);
|
|
||||||
RagonHeader.WriteInt(entity.EntityId, ref entityIdData);
|
|
||||||
RagonHeader.WriteInt((int) peerId, ref peerData);
|
|
||||||
|
|
||||||
Broadcast(_allPlayers, data, DeliveryType.Reliable);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case RagonOperation.DESTROY_ENTITY:
|
|
||||||
{
|
|
||||||
var entityData = rawData.Slice(2, 4);
|
|
||||||
var entityId = RagonHeader.ReadInt(ref entityData);
|
|
||||||
if (_entities.TryGetValue(entityId, out var entity))
|
|
||||||
{
|
|
||||||
if (entity.Authority == RagonAuthority.OWNER_ONLY && entity.OwnerId != peerId)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var player = _players[peerId];
|
|
||||||
|
|
||||||
player.Entities.Remove(entity);
|
|
||||||
player.EntitiesIds.Remove(entity.EntityId);
|
|
||||||
|
|
||||||
_entities.Remove(entityId);
|
|
||||||
_entitiesAll = _entities.Values.ToArray();
|
|
||||||
|
|
||||||
_plugin.OnEntityDestroyed(player, entity);
|
|
||||||
|
|
||||||
var data = new byte[rawData.Length];
|
|
||||||
Span<byte> sendData = data.AsSpan();
|
|
||||||
rawData.CopyTo(sendData);
|
|
||||||
|
|
||||||
Broadcast(_readyPlayers, data, DeliveryType.Reliable);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case RagonOperation.SCENE_IS_LOADED:
|
|
||||||
{
|
|
||||||
Send(peerId, RagonOperation.RESTORE_BEGIN, DeliveryType.Reliable);
|
|
||||||
foreach (var entity in _entities.Values)
|
|
||||||
{
|
|
||||||
var entityState = entity.State.Data.AsSpan();
|
|
||||||
var data = new byte[entity.State.Data.Length + 12];
|
|
||||||
|
|
||||||
Span<byte> sendData = data.AsSpan();
|
|
||||||
Span<byte> operationData = sendData.Slice(0, 2);
|
|
||||||
Span<byte> entityTypeData = sendData.Slice(2, 2);
|
|
||||||
Span<byte> authorityData = sendData.Slice(4, 2);
|
|
||||||
Span<byte> entityData = sendData.Slice(6, 4);
|
|
||||||
Span<byte> ownerData = sendData.Slice(10, 4);
|
|
||||||
Span<byte> entityStateData = sendData.Slice(14, entity.State.Data.Length);
|
|
||||||
|
|
||||||
RagonHeader.WriteUShort((ushort) RagonOperation.CREATE_ENTITY, ref operationData);
|
|
||||||
RagonHeader.WriteUShort(entity.EntityType, ref entityTypeData);
|
|
||||||
RagonHeader.WriteInt(entity.EntityId, ref entityData);
|
|
||||||
RagonHeader.WriteInt((int) entity.OwnerId, ref ownerData);
|
|
||||||
|
|
||||||
authorityData[0] = (byte) entity.State.Authority;
|
|
||||||
authorityData[1] = (byte) entity.Authority;
|
|
||||||
|
|
||||||
entityState.CopyTo(entityStateData);
|
|
||||||
|
|
||||||
Send(peerId, data, DeliveryType.Reliable);
|
|
||||||
}
|
|
||||||
|
|
||||||
Send(peerId, RagonOperation.RESTORE_END, DeliveryType.Reliable);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case RagonOperation.RESTORED:
|
|
||||||
{
|
|
||||||
_players[peerId].IsLoaded = true;
|
|
||||||
_readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray();
|
|
||||||
|
|
||||||
_plugin.OnPlayerJoined(_players[peerId]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Tick(float deltaTime)
|
|
||||||
{
|
|
||||||
_ticks++;
|
|
||||||
_plugin.OnTick(_ticks, deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Start()
|
|
||||||
{
|
|
||||||
_logger.Info("Room started");
|
|
||||||
_plugin.OnStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Stop()
|
|
||||||
{
|
|
||||||
_logger.Info("Room stopped");
|
|
||||||
_plugin.OnStop();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_logger.Info("Room destroyed");
|
|
||||||
_plugin.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Player GetPlayerById(uint peerId) => _players[peerId];
|
|
||||||
public Entity GetEntityById(int entityId) => _entities[entityId];
|
|
||||||
public Player GetOwner() => _players[_owner];
|
|
||||||
|
|
||||||
public void Send(uint peerId, RagonOperation operation, DeliveryType deliveryType = DeliveryType.Unreliable)
|
|
||||||
{
|
|
||||||
var rawData = new byte[2];
|
|
||||||
var rawDataSpan = new Span<byte>(rawData);
|
|
||||||
|
|
||||||
RagonHeader.WriteUShort((ushort) operation, ref rawDataSpan);
|
|
||||||
|
|
||||||
_roomThread.WriteOutEvent(new Event()
|
|
||||||
{
|
|
||||||
PeerId = peerId,
|
|
||||||
Data = rawData,
|
|
||||||
Type = EventType.DATA,
|
|
||||||
Delivery = deliveryType,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Send(uint peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
|
|
||||||
{
|
|
||||||
_roomThread.WriteOutEvent(new Event()
|
|
||||||
{
|
|
||||||
PeerId = peerId,
|
|
||||||
Data = rawData,
|
|
||||||
Type = EventType.DATA,
|
|
||||||
Delivery = deliveryType,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Broadcast(uint[] peersIds, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
|
|
||||||
{
|
|
||||||
foreach (var peer in peersIds)
|
|
||||||
{
|
|
||||||
_roomThread.WriteOutEvent(new Event()
|
|
||||||
{
|
|
||||||
PeerId = peer,
|
|
||||||
Data = rawData,
|
|
||||||
Type = EventType.DATA,
|
|
||||||
Delivery = deliveryType,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Broadcast(byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
|
|
||||||
{
|
|
||||||
foreach (var player in _players.Values.ToArray())
|
|
||||||
{
|
|
||||||
_roomThread.WriteOutEvent(new Event()
|
|
||||||
{
|
|
||||||
PeerId = player.PeerId,
|
|
||||||
Data = rawData,
|
|
||||||
Type = EventType.DATA,
|
|
||||||
Delivery = deliveryType,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,166 +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;
|
|
||||||
|
|
||||||
public Action<(uint, Room)> OnJoined;
|
|
||||||
public Action<(uint, Room)> OnLeaved;
|
|
||||||
|
|
||||||
public RoomManager(RoomThread roomThread, PluginFactory factory)
|
|
||||||
{
|
|
||||||
_roomThread = roomThread;
|
|
||||||
_factory = factory;
|
|
||||||
|
|
||||||
_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_ROOM:
|
|
||||||
{
|
|
||||||
var room = Join(peerId, payload);
|
|
||||||
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[2];
|
|
||||||
Span<byte> data = sendData.AsSpan();
|
|
||||||
|
|
||||||
RagonHeader.WriteUShort((ushort) RagonOperation.AUTHORIZED_SUCCESS, ref data);
|
|
||||||
|
|
||||||
_roomThread.WriteOutEvent(new Event()
|
|
||||||
{
|
|
||||||
Delivery = DeliveryType.Reliable,
|
|
||||||
Type = EventType.DATA,
|
|
||||||
Data = sendData,
|
|
||||||
PeerId = peerId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var sendData = new byte[2];
|
|
||||||
var data = sendData.AsSpan();
|
|
||||||
RagonHeader.WriteUShort((ushort) RagonOperation.AUTHORIZED_FAILED, ref data);
|
|
||||||
|
|
||||||
_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 minData = payload.Slice(0, 2);
|
|
||||||
var maxData = payload.Slice(2, 2);
|
|
||||||
var mapData = payload.Slice(4, payload.Length - 4);
|
|
||||||
|
|
||||||
var map = Encoding.UTF8.GetString(mapData);
|
|
||||||
var min = RagonHeader.ReadUShort(ref minData);
|
|
||||||
var max = RagonHeader.ReadUShort(ref maxData);
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
_rooms.Remove(room);
|
|
||||||
|
|
||||||
room.Stop();
|
|
||||||
room.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Tick(float deltaTime)
|
|
||||||
{
|
|
||||||
foreach (Room room in _rooms)
|
|
||||||
room.Tick(deltaTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 RingBuffer<Event> _receiveBuffer = new RingBuffer<Event>(8192 + 8192);
|
|
||||||
private RingBuffer<Event> _sendBuffer = new RingBuffer<Event>(8192 + 8192);
|
|
||||||
|
|
||||||
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 operationData = data.Slice(0, 2);
|
|
||||||
var operation = (RagonOperation) RagonHeader.ReadUShort(ref operationData);
|
|
||||||
if (_socketByRooms.TryGetValue(evnt.PeerId, out var room))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
room.ProcessEvent(operation, evnt.PeerId, data);
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
_logger.Error(exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var payload = data.Slice(2, data.Length - 2);
|
|
||||||
_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()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Executable
+144
@@ -0,0 +1,144 @@
|
|||||||
|
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 Broadcast(uint[] peersIds, byte[] data, DeliveryType type)
|
||||||
|
{
|
||||||
|
var newPacket = new Packet();
|
||||||
|
var packetFlags = PacketFlags.Instant;
|
||||||
|
byte channel = 1;
|
||||||
|
|
||||||
|
if (type == DeliveryType.Reliable)
|
||||||
|
{
|
||||||
|
packetFlags = PacketFlags.Reliable;
|
||||||
|
channel = 0;
|
||||||
|
}
|
||||||
|
else if (type == DeliveryType.Unreliable)
|
||||||
|
{
|
||||||
|
channel = 1;
|
||||||
|
packetFlags = PacketFlags.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
newPacket.Create(data, data.Length, packetFlags);
|
||||||
|
foreach (var peerId in peersIds)
|
||||||
|
_peers[peerId].Send(channel, ref newPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Send(uint peerId, byte[] data, DeliveryType type)
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using ENet;
|
||||||
|
|
||||||
|
namespace Ragon.Core;
|
||||||
|
|
||||||
|
public interface IHandler
|
||||||
|
{
|
||||||
|
public void OnEvent(Event evnt);
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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 Broadcast(uint[] peersIds, byte[] data, DeliveryType type);
|
||||||
|
public void Disconnect(uint peerId, uint errorCode);
|
||||||
|
}
|
||||||
@@ -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()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace Ragon.Core.Storage;
|
|
||||||
|
|
||||||
public struct EntityInfo
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace Ragon.Core.Storage;
|
|
||||||
|
|
||||||
public struct PlayerInfo
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace Ragon.Core.Storage;
|
|
||||||
|
|
||||||
public struct RoomInfo
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ragon.Core;
|
||||||
|
|
||||||
|
public interface IScheduler
|
||||||
|
{
|
||||||
|
public SchedulerTask Schedule(Action<SchedulerTask> action, float interval, int count = 1);
|
||||||
|
public SchedulerTask ScheduleForever(Action<SchedulerTask> action, float interval);
|
||||||
|
public void StopSchedule(SchedulerTask schedulerTask);
|
||||||
|
|
||||||
|
public void Tick(float deltaTime);
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace Ragon.Core
|
||||||
|
{
|
||||||
|
public class Scheduler: IScheduler
|
||||||
|
{
|
||||||
|
List<SchedulerTask> _scheduledTasks;
|
||||||
|
|
||||||
|
public Scheduler(int defaultCapacity = 100)
|
||||||
|
{
|
||||||
|
_scheduledTasks = new List<SchedulerTask>(defaultCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SchedulerTask Schedule(Action<SchedulerTask> action, float interval, int count = 1)
|
||||||
|
{
|
||||||
|
var newTask = new SchedulerTask(action, interval, count - 1);
|
||||||
|
_scheduledTasks.Add(newTask);
|
||||||
|
return newTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SchedulerTask ScheduleForever(Action<SchedulerTask> action, float interval)
|
||||||
|
{
|
||||||
|
var newTask = new SchedulerTask(action, interval, -1);
|
||||||
|
_scheduledTasks.Add(newTask);
|
||||||
|
return newTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopSchedule(SchedulerTask schedulerTask)
|
||||||
|
{
|
||||||
|
if (_scheduledTasks.Contains(schedulerTask))
|
||||||
|
_scheduledTasks.Remove(schedulerTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Tick(float deltaTime)
|
||||||
|
{
|
||||||
|
for (int i = _scheduledTasks.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var scheduledTask = _scheduledTasks[i];
|
||||||
|
scheduledTask.Tick(deltaTime);
|
||||||
|
|
||||||
|
if (!scheduledTask.IsActive)
|
||||||
|
_scheduledTasks.Remove(scheduledTask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_scheduledTasks.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SchedulerTask
|
||||||
|
{
|
||||||
|
private Action<SchedulerTask> _action;
|
||||||
|
private float _timer = 0;
|
||||||
|
private float _interval = 0;
|
||||||
|
private int _repeats = 0;
|
||||||
|
private bool _active;
|
||||||
|
|
||||||
|
public int Repeats => _repeats;
|
||||||
|
public bool IsActive => _active;
|
||||||
|
|
||||||
|
public SchedulerTask(Action<SchedulerTask> task, float interval, int repeatCount = 0)
|
||||||
|
{
|
||||||
|
_action = task;
|
||||||
|
_interval = interval;
|
||||||
|
_timer = 0;
|
||||||
|
_active = true;
|
||||||
|
_repeats = repeatCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Tick(float deltaTime)
|
||||||
|
{
|
||||||
|
_timer += deltaTime;
|
||||||
|
if (_timer >= _interval)
|
||||||
|
{
|
||||||
|
_action.Invoke(this);
|
||||||
|
if (_repeats == -1)
|
||||||
|
{
|
||||||
|
_timer = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_repeats > 0)
|
||||||
|
{
|
||||||
|
_timer = 0;
|
||||||
|
_repeats--;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_repeats == 0)
|
||||||
|
_active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"key": "defaultkey",
|
|
||||||
"server": {
|
|
||||||
"port": 4444,
|
|
||||||
"skipTimeout": 60,
|
|
||||||
"tickRate": 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -1,18 +1,17 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="Images/logo.png" width="200" >
|
<img src="Images/ragon-logo.png" width="200" >
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Ragon Server
|
## Ragon Server
|
||||||
|
|
||||||
Ragon is fully free high perfomance room based game server with plugin based architecture.
|
Ragon is fully free, small and high perfomance room based game server with plugin based architecture.
|
||||||
|
|
||||||
|
<a href="https://ragon-server.com/docs/category/basics">Documentation</a>
|
||||||
<a href="">Documentation</a>
|
|
||||||
<br>
|
<br>
|
||||||
<a href="">Get started</a>
|
<a href="https://ragon-server.com/docs/get-started">Get started</a>
|
||||||
|
|
||||||
|
|
||||||
### Features:
|
### Features:
|
||||||
|
- Effective
|
||||||
- Free
|
- Free
|
||||||
- Simple matchmaking
|
- Simple matchmaking
|
||||||
- Flexiable API
|
- Flexiable API
|
||||||
@@ -20,27 +19,26 @@ Ragon is fully free high perfomance room based game server with plugin based arc
|
|||||||
- Extendable room logic via plugin
|
- Extendable room logic via plugin
|
||||||
- Custom authorization
|
- Custom authorization
|
||||||
- No CCU limitations*
|
- No CCU limitations*
|
||||||
- Multi-threaded
|
|
||||||
- Engine agnostic
|
- Engine agnostic
|
||||||
- Support any client architecture (MonoBehaviors, ECS)
|
- Support any client architecture (MonoBehaviors, ECS)
|
||||||
- UDP
|
- RUDP
|
||||||
|
|
||||||
### Roadmap:
|
### Roadmap:
|
||||||
- Allow customize matchmaking
|
|
||||||
- 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
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
- OSX, Windows, Linux(Ubuntu, Debian)
|
- OSX, Windows, Linux(Ubuntu, Debian)
|
||||||
- .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
|
||||||
|
|||||||
Reference in New Issue
Block a user