Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ddd52bf9d | |||
| ecf3631163 | |||
| 8a149eb3b4 | |||
| 14ae5e8189 | |||
| b7e8327ca8 | |||
| b9e79af9d8 | |||
| 545ec02ecc | |||
| 73feb77169 | |||
| cbcf1773aa | |||
| 5519ae7679 | |||
| 1558b5eefb | |||
| aaa0e4a317 | |||
| ff712dc094 | |||
| c2c75cb513 | |||
| 9a22566f79 | |||
| 783d2ce922 | |||
| 5771ec738b | |||
| 85081e1da7 | |||
| dbaa5d9d92 | |||
| 54786c065d | |||
| 0b3a0dd1ac | |||
| 72ff37e94a | |||
| 75dab9d16f | |||
| 25b0e54d13 | |||
| e4f664b557 | |||
| 174a4c4422 | |||
| a51d7c73cd | |||
| 957622a170 | |||
| 4a07424293 | |||
| 568a3282c3 | |||
| 6a71fe5fe0 | |||
| 082e183989 | |||
| 51c0482a40 | |||
| 9e16c53cad | |||
| 6ec29e5dcd | |||
| 65c1d9c6d4 | |||
| f2934bc8ee | |||
| 81879d4fe2 | |||
| 2d89d267f5 | |||
| 34107cbd5f | |||
| ba2ce25aec | |||
| ca17e7de83 | |||
| dff6dbcf97 | |||
| ae485f96d4 | |||
| 74904de16b | |||
| 655cf3503d | |||
| 8fd8558323 | |||
| 944cb65733 | |||
| 877ebdcde2 | |||
| e2032f381a | |||
| 85336f998e | |||
| 16b8d3a062 | |||
| f72b4c0904 | |||
| 19272609ef | |||
| 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,69 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018 Stanislav Denisov
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System.Threading;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace NetStack.Buffers {
|
|
||||||
public abstract class ArrayPool<T> {
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
private static ArrayPool<T> s_sharedInstance = null;
|
|
||||||
#else
|
|
||||||
private static volatile ArrayPool<T> s_sharedInstance = null;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public static ArrayPool<T> Shared {
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
[MethodImpl(256)]
|
|
||||||
get {
|
|
||||||
return Volatile.Read(ref s_sharedInstance) ?? EnsureSharedCreated();
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
[MethodImpl(256)]
|
|
||||||
get {
|
|
||||||
return s_sharedInstance ?? EnsureSharedCreated();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma warning disable 420
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
||||||
private static ArrayPool<T> EnsureSharedCreated() {
|
|
||||||
Interlocked.CompareExchange(ref s_sharedInstance, Create(), null);
|
|
||||||
|
|
||||||
return s_sharedInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArrayPool<T> Create() {
|
|
||||||
return new DefaultArrayPool<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArrayPool<T> Create(int maxArrayLength, int maxArraysPerBucket) {
|
|
||||||
return new DefaultArrayPool<T>(maxArrayLength, maxArraysPerBucket);
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract T[] Rent(int minimumLength);
|
|
||||||
|
|
||||||
public abstract void Return(T[] array, bool clearArray = false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018 Stanislav Denisov
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
using UnityEngine;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace NetStack.Buffers {
|
|
||||||
internal sealed class ArrayPoolEventSource {
|
|
||||||
#if NETSTACK_BUFFERS_LOG
|
|
||||||
internal static readonly ArrayPoolEventSource EventLog = new ArrayPoolEventSource();
|
|
||||||
|
|
||||||
internal enum BufferAllocatedReason : int {
|
|
||||||
Pooled,
|
|
||||||
OverMaximumSize,
|
|
||||||
PoolExhausted
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void BufferAllocated(int bufferId, int bufferSize, int poolId, int bucketId, BufferAllocatedReason reason) {
|
|
||||||
var message = "Buffer allocated (Buffer ID: " + bufferId + ", Buffer size: " + bufferSize + ", Pool ID: " + poolId + ", Bucket ID: " + bucketId + ", Reason: " + reason + ")";
|
|
||||||
|
|
||||||
if (reason == BufferAllocatedReason.Pooled)
|
|
||||||
Log.Info("Buffers", message);
|
|
||||||
else
|
|
||||||
Log.Warning("Buffers", message);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void BufferRented(int bufferId, int bufferSize, int poolId, int bucketId) {
|
|
||||||
Log.Info("Buffers", "Buffer rented (Buffer ID: " + bufferId + ", Buffer size: " + bufferSize + ", Pool ID: " + poolId + ", Bucket ID: " + bucketId + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void BufferReturned(int bufferId, int bufferSize, int poolId) {
|
|
||||||
Log.Info("Buffers", "Buffer returned (Buffer ID: " + bufferId + ", Buffer size: " + bufferSize + ", Pool ID: " + poolId + ")");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static class Log {
|
|
||||||
private static string Output(string module, string message) {
|
|
||||||
return DateTime.Now.ToString("[HH:mm:ss]") + " [NetStack." + module + "] " + message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Info(string module, string message) {
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
Debug.Log(Output(module, message));
|
|
||||||
#else
|
|
||||||
Console.WriteLine(Output(module, message));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Warning(string module, string message) {
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
Debug.LogWarning(Output(module, message));
|
|
||||||
#else
|
|
||||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
|
||||||
Console.WriteLine(Output(module, message));
|
|
||||||
Console.ResetColor();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018 Stanislav Denisov
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace NetStack.Buffers {
|
|
||||||
internal sealed partial class DefaultArrayPool<T> : ArrayPool<T> {
|
|
||||||
private const int DefaultMaxArrayLength = 1024 * 1024;
|
|
||||||
private const int DefaultMaxNumberOfArraysPerBucket = 50;
|
|
||||||
private static T[] s_emptyArray;
|
|
||||||
private readonly Bucket[] _buckets;
|
|
||||||
|
|
||||||
internal DefaultArrayPool() : this(DefaultMaxArrayLength, DefaultMaxNumberOfArraysPerBucket) { }
|
|
||||||
|
|
||||||
internal DefaultArrayPool(int maxArrayLength, int maxArraysPerBucket) {
|
|
||||||
if (maxArrayLength <= 0)
|
|
||||||
throw new ArgumentOutOfRangeException("maxArrayLength");
|
|
||||||
|
|
||||||
if (maxArraysPerBucket <= 0)
|
|
||||||
throw new ArgumentOutOfRangeException("maxArraysPerBucket");
|
|
||||||
|
|
||||||
const int MinimumArrayLength = 0x10, MaximumArrayLength = 0x40000000;
|
|
||||||
|
|
||||||
if (maxArrayLength > MaximumArrayLength)
|
|
||||||
maxArrayLength = MaximumArrayLength;
|
|
||||||
else if (maxArrayLength < MinimumArrayLength)
|
|
||||||
maxArrayLength = MinimumArrayLength;
|
|
||||||
|
|
||||||
int poolId = Id;
|
|
||||||
int maxBuckets = Utilities.SelectBucketIndex(maxArrayLength);
|
|
||||||
var buckets = new Bucket[maxBuckets + 1];
|
|
||||||
|
|
||||||
for (int i = 0; i < buckets.Length; i++) {
|
|
||||||
buckets[i] = new Bucket(Utilities.GetMaxSizeForBucket(i), maxArraysPerBucket, poolId);
|
|
||||||
}
|
|
||||||
|
|
||||||
_buckets = buckets;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int Id {
|
|
||||||
get {
|
|
||||||
return GetHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override T[] Rent(int minimumLength) {
|
|
||||||
if (minimumLength < 0)
|
|
||||||
throw new ArgumentOutOfRangeException("minimumLength");
|
|
||||||
else if (minimumLength == 0)
|
|
||||||
return s_emptyArray ?? (s_emptyArray = new T[0]);
|
|
||||||
|
|
||||||
#if NETSTACK_BUFFERS_LOG
|
|
||||||
var log = ArrayPoolEventSource.EventLog;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
T[] buffer = null;
|
|
||||||
int index = Utilities.SelectBucketIndex(minimumLength);
|
|
||||||
|
|
||||||
if (index < _buckets.Length) {
|
|
||||||
const int MaxBucketsToTry = 2;
|
|
||||||
|
|
||||||
int i = index;
|
|
||||||
|
|
||||||
do {
|
|
||||||
buffer = _buckets[i].Rent();
|
|
||||||
|
|
||||||
if (buffer != null) {
|
|
||||||
#if NETSTACK_BUFFERS_LOG
|
|
||||||
log.BufferRented(buffer.GetHashCode(), buffer.Length, Id, _buckets[i].Id);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (++i < _buckets.Length && i != index + MaxBucketsToTry);
|
|
||||||
|
|
||||||
buffer = new T[_buckets[index]._bufferLength];
|
|
||||||
} else {
|
|
||||||
buffer = new T[minimumLength];
|
|
||||||
}
|
|
||||||
|
|
||||||
#if NETSTACK_BUFFERS_LOG
|
|
||||||
int bufferId = buffer.GetHashCode(), bucketId = -1;
|
|
||||||
|
|
||||||
log.BufferRented(bufferId, buffer.Length, Id, bucketId);
|
|
||||||
log.BufferAllocated(bufferId, buffer.Length, Id, bucketId, index >= _buckets.Length ? ArrayPoolEventSource.BufferAllocatedReason.OverMaximumSize : ArrayPoolEventSource.BufferAllocatedReason.PoolExhausted);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Return(T[] array, bool clearArray = false) {
|
|
||||||
if (array == null)
|
|
||||||
throw new ArgumentNullException("array");
|
|
||||||
else if (array.Length == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
int bucket = Utilities.SelectBucketIndex(array.Length);
|
|
||||||
|
|
||||||
if (bucket < _buckets.Length) {
|
|
||||||
if (clearArray)
|
|
||||||
Array.Clear(array, 0, array.Length);
|
|
||||||
|
|
||||||
_buckets[bucket].Return(array);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if NETSTACK_BUFFERS_LOG
|
|
||||||
var log = ArrayPoolEventSource.EventLog;
|
|
||||||
|
|
||||||
log.BufferReturned(array.GetHashCode(), array.Length, Id);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018 Stanislav Denisov
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace NetStack.Buffers {
|
|
||||||
internal sealed partial class DefaultArrayPool<T> : ArrayPool<T> {
|
|
||||||
private sealed class Bucket {
|
|
||||||
internal readonly int _bufferLength;
|
|
||||||
private readonly T[][] _buffers;
|
|
||||||
#if NETSTACK_BUFFERS_LOG
|
|
||||||
private readonly int _poolId;
|
|
||||||
#endif
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
private SpinLock _lock;
|
|
||||||
#else
|
|
||||||
private object _lock;
|
|
||||||
#endif
|
|
||||||
private int _index;
|
|
||||||
|
|
||||||
internal Bucket(int bufferLength, int numberOfBuffers, int poolId) {
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
_lock = new SpinLock();
|
|
||||||
#else
|
|
||||||
_lock = new Object();
|
|
||||||
#endif
|
|
||||||
_buffers = new T[numberOfBuffers][];
|
|
||||||
_bufferLength = bufferLength;
|
|
||||||
|
|
||||||
#if NETSTACK_BUFFERS_LOG
|
|
||||||
_poolId = poolId;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
internal int Id => GetHashCode();
|
|
||||||
#else
|
|
||||||
internal int Id {
|
|
||||||
get {
|
|
||||||
return GetHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
internal T[] Rent() {
|
|
||||||
T[][] buffers = _buffers;
|
|
||||||
T[] buffer = null;
|
|
||||||
bool allocateBuffer = false;
|
|
||||||
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
bool lockTaken = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
_lock.Enter(ref lockTaken);
|
|
||||||
|
|
||||||
if (_index < buffers.Length) {
|
|
||||||
buffer = buffers[_index];
|
|
||||||
buffers[_index++] = null;
|
|
||||||
allocateBuffer = buffer == null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
finally {
|
|
||||||
if (lockTaken)
|
|
||||||
_lock.Exit(false);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
try {
|
|
||||||
Monitor.Enter(_lock);
|
|
||||||
|
|
||||||
if (_index < buffers.Length) {
|
|
||||||
buffer = buffers[_index];
|
|
||||||
buffers[_index++] = null;
|
|
||||||
allocateBuffer = buffer == null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
finally {
|
|
||||||
Monitor.Exit(_lock);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (allocateBuffer) {
|
|
||||||
buffer = new T[_bufferLength];
|
|
||||||
|
|
||||||
#if NETSTACK_BUFFERS_LOG
|
|
||||||
var log = ArrayPoolEventSource.EventLog;
|
|
||||||
|
|
||||||
log.BufferAllocated(buffer.GetHashCode(), _bufferLength, _poolId, Id, ArrayPoolEventSource.BufferAllocatedReason.Pooled);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void Return(T[] array) {
|
|
||||||
if (array.Length != _bufferLength)
|
|
||||||
throw new ArgumentException("BufferNotFromPool", "array");
|
|
||||||
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
bool lockTaken = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
_lock.Enter(ref lockTaken);
|
|
||||||
|
|
||||||
if (_index != 0)
|
|
||||||
_buffers[--_index] = array;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
finally {
|
|
||||||
if (lockTaken)
|
|
||||||
_lock.Exit(false);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
try {
|
|
||||||
Monitor.Enter(_lock);
|
|
||||||
|
|
||||||
if (_index != 0)
|
|
||||||
_buffers[--_index] = array;
|
|
||||||
}
|
|
||||||
|
|
||||||
finally {
|
|
||||||
Monitor.Exit(_lock);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018 Stanislav Denisov
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
using UnityEngine.Assertions;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace NetStack.Buffers {
|
|
||||||
internal static class Utilities {
|
|
||||||
[MethodImpl(256)]
|
|
||||||
internal static int SelectBucketIndex(int bufferSize) {
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
Assert.IsTrue(bufferSize > 0);
|
|
||||||
#else
|
|
||||||
Debug.Assert(bufferSize > 0);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
uint bitsRemaining = ((uint)bufferSize - 1) >> 4;
|
|
||||||
int poolIndex = 0;
|
|
||||||
|
|
||||||
if (bitsRemaining > 0xFFFF) {
|
|
||||||
bitsRemaining >>= 16;
|
|
||||||
poolIndex = 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bitsRemaining > 0xFF) {
|
|
||||||
bitsRemaining >>= 8;
|
|
||||||
poolIndex += 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bitsRemaining > 0xF) {
|
|
||||||
bitsRemaining >>= 4;
|
|
||||||
poolIndex += 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bitsRemaining > 0x3) {
|
|
||||||
bitsRemaining >>= 2;
|
|
||||||
poolIndex += 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bitsRemaining > 0x1) {
|
|
||||||
bitsRemaining >>= 1;
|
|
||||||
poolIndex += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return poolIndex + (int)bitsRemaining;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
internal static int GetMaxSizeForBucket(int binIndex) {
|
|
||||||
int maxSize = 16 << binIndex;
|
|
||||||
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
Assert.IsTrue(maxSize >= 0);
|
|
||||||
#else
|
|
||||||
Debug.Assert(maxSize >= 0);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return maxSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,184 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018 Stanislav Denisov
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
#if !(ENABLE_MONO || ENABLE_IL2CPP)
|
|
||||||
using System.Numerics;
|
|
||||||
#else
|
|
||||||
using UnityEngine;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace NetStack.Quantization {
|
|
||||||
public struct QuantizedVector2 {
|
|
||||||
public uint x;
|
|
||||||
public uint y;
|
|
||||||
|
|
||||||
public QuantizedVector2(uint x, uint y) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct QuantizedVector3 {
|
|
||||||
public uint x;
|
|
||||||
public uint y;
|
|
||||||
public uint z;
|
|
||||||
|
|
||||||
public QuantizedVector3(uint x, uint y, uint z) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.z = z;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct QuantizedVector4 {
|
|
||||||
public uint x;
|
|
||||||
public uint y;
|
|
||||||
public uint z;
|
|
||||||
public uint w;
|
|
||||||
|
|
||||||
public QuantizedVector4(uint x, uint y, uint z, uint w) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.z = z;
|
|
||||||
this.w = w;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class DeBruijn {
|
|
||||||
public static readonly int[] Lookup = new int[32] {
|
|
||||||
0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30,
|
|
||||||
8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BoundedRange {
|
|
||||||
private readonly float minValue;
|
|
||||||
private readonly float maxValue;
|
|
||||||
private readonly float precision;
|
|
||||||
private readonly int requiredBits;
|
|
||||||
private readonly uint mask;
|
|
||||||
|
|
||||||
public BoundedRange(float minValue, float maxValue, float precision) {
|
|
||||||
this.minValue = minValue;
|
|
||||||
this.maxValue = maxValue;
|
|
||||||
this.precision = precision;
|
|
||||||
|
|
||||||
requiredBits = Log2((uint)((maxValue - minValue) * (1.0f / precision) + 0.5f)) + 1;
|
|
||||||
mask = (uint)((1L << requiredBits) - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int Log2(uint value) {
|
|
||||||
value |= value >> 1;
|
|
||||||
value |= value >> 2;
|
|
||||||
value |= value >> 4;
|
|
||||||
value |= value >> 8;
|
|
||||||
value |= value >> 16;
|
|
||||||
|
|
||||||
return DeBruijn.Lookup[(value * 0x07C4ACDDU) >> 27];
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public uint Quantize(float value) {
|
|
||||||
if (value < minValue)
|
|
||||||
value = minValue;
|
|
||||||
else if (value > maxValue)
|
|
||||||
value = maxValue;
|
|
||||||
|
|
||||||
return (uint)((float)((value - minValue) * (1f / precision)) + 0.5f) & mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public float Dequantize(uint data) {
|
|
||||||
float adjusted = ((float)data * precision) + minValue;
|
|
||||||
|
|
||||||
if (adjusted < minValue)
|
|
||||||
adjusted = minValue;
|
|
||||||
else if (adjusted > maxValue)
|
|
||||||
adjusted = maxValue;
|
|
||||||
|
|
||||||
return adjusted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static QuantizedVector2 Quantize(Vector2 vector2, BoundedRange[] boundedRange) {
|
|
||||||
QuantizedVector2 data = default(QuantizedVector2);
|
|
||||||
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
data.x = boundedRange[0].Quantize(vector2.x);
|
|
||||||
data.y = boundedRange[1].Quantize(vector2.y);
|
|
||||||
#else
|
|
||||||
data.x = boundedRange[0].Quantize(vector2.X);
|
|
||||||
data.y = boundedRange[1].Quantize(vector2.Y);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static QuantizedVector3 Quantize(Vector3 vector3, BoundedRange[] boundedRange) {
|
|
||||||
QuantizedVector3 data = default(QuantizedVector3);
|
|
||||||
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
data.x = boundedRange[0].Quantize(vector3.x);
|
|
||||||
data.y = boundedRange[1].Quantize(vector3.y);
|
|
||||||
data.z = boundedRange[2].Quantize(vector3.z);
|
|
||||||
#else
|
|
||||||
data.x = boundedRange[0].Quantize(vector3.X);
|
|
||||||
data.y = boundedRange[1].Quantize(vector3.Y);
|
|
||||||
data.z = boundedRange[2].Quantize(vector3.Z);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static QuantizedVector4 Quantize(Vector4 vector4, BoundedRange[] boundedRange) {
|
|
||||||
QuantizedVector4 data = default(QuantizedVector4);
|
|
||||||
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
data.x = boundedRange[0].Quantize(vector4.x);
|
|
||||||
data.y = boundedRange[1].Quantize(vector4.y);
|
|
||||||
data.z = boundedRange[2].Quantize(vector4.z);
|
|
||||||
data.w = boundedRange[3].Quantize(vector4.w);
|
|
||||||
#else
|
|
||||||
data.x = boundedRange[0].Quantize(vector4.X);
|
|
||||||
data.y = boundedRange[1].Quantize(vector4.Y);
|
|
||||||
data.z = boundedRange[2].Quantize(vector4.Z);
|
|
||||||
data.w = boundedRange[3].Quantize(vector4.W);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Vector2 Dequantize(QuantizedVector2 data, BoundedRange[] boundedRange) {
|
|
||||||
return new Vector2(boundedRange[0].Dequantize(data.x), boundedRange[1].Dequantize(data.y));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Vector3 Dequantize(QuantizedVector3 data, BoundedRange[] boundedRange) {
|
|
||||||
return new Vector3(boundedRange[0].Dequantize(data.x), boundedRange[1].Dequantize(data.y), boundedRange[2].Dequantize(data.z));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Vector4 Dequantize(QuantizedVector4 data, BoundedRange[] boundedRange) {
|
|
||||||
return new Vector4(boundedRange[0].Dequantize(data.x), boundedRange[1].Dequantize(data.y), boundedRange[2].Dequantize(data.z), boundedRange[3].Dequantize(data.w));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018 Stanislav Denisov
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace NetStack.Quantization {
|
|
||||||
public static class HalfPrecision {
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
private struct Values {
|
|
||||||
[FieldOffset(0)]
|
|
||||||
public float f;
|
|
||||||
[FieldOffset(0)]
|
|
||||||
public int i;
|
|
||||||
[FieldOffset(0)]
|
|
||||||
public uint u;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public static ushort Quantize(float value) {
|
|
||||||
var values = new Values {
|
|
||||||
f = value
|
|
||||||
};
|
|
||||||
|
|
||||||
return Quantize(values.i);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ushort Quantize(int value) {
|
|
||||||
int s = (value >> 16) & 0x00008000;
|
|
||||||
int e = ((value >> 23) & 0X000000FF) - (127 - 15);
|
|
||||||
int m = value & 0X007FFFFF;
|
|
||||||
|
|
||||||
if (e <= 0) {
|
|
||||||
if (e < -10)
|
|
||||||
return (ushort)s;
|
|
||||||
|
|
||||||
m = m | 0x00800000;
|
|
||||||
|
|
||||||
int t = 14 - e;
|
|
||||||
int a = (1 << (t - 1)) - 1;
|
|
||||||
int b = (m >> t) & 1;
|
|
||||||
|
|
||||||
m = (m + a + b) >> t;
|
|
||||||
|
|
||||||
return (ushort)(s | m);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e == 0XFF - (127 - 15)) {
|
|
||||||
if (m == 0)
|
|
||||||
return (ushort)(s | 0X7C00);
|
|
||||||
|
|
||||||
m >>= 13;
|
|
||||||
|
|
||||||
return (ushort)(s | 0X7C00 | m | ((m == 0) ? 1 : 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
m = m + 0X00000FFF + ((m >> 13) & 1);
|
|
||||||
|
|
||||||
if ((m & 0x00800000) != 0) {
|
|
||||||
m = 0;
|
|
||||||
e++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e > 30)
|
|
||||||
return (ushort)(s | 0X7C00);
|
|
||||||
|
|
||||||
return (ushort)(s | (e << 10) | (m >> 13));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float Dequantize(ushort value) {
|
|
||||||
uint result;
|
|
||||||
uint mantissa = (uint)(value & 1023);
|
|
||||||
uint exponent = 0XFFFFFFF2;
|
|
||||||
|
|
||||||
if ((value & -33792) == 0) {
|
|
||||||
if (mantissa != 0) {
|
|
||||||
while ((mantissa & 1024) == 0) {
|
|
||||||
exponent--;
|
|
||||||
mantissa = mantissa << 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
mantissa &= 0XFFFFFBFF;
|
|
||||||
result = ((uint)((((uint)value & 0x8000) << 16) | ((exponent + 127) << 23))) | (mantissa << 13);
|
|
||||||
} else {
|
|
||||||
result = (uint)((value & 0x8000) << 16);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result = ((((uint)value & 0x8000) << 16) | ((((((uint)value >> 10) & 0X1F) - 15) + 127) << 23)) | (mantissa << 13);
|
|
||||||
}
|
|
||||||
|
|
||||||
var values = new Values {
|
|
||||||
u = result
|
|
||||||
};
|
|
||||||
|
|
||||||
return values.f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,223 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2020 Stanislav Denisov, Maxim Munning, Davin Carten
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
|
|
||||||
#if !(ENABLE_MONO || ENABLE_IL2CPP)
|
|
||||||
using System.Numerics;
|
|
||||||
#else
|
|
||||||
using UnityEngine;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace NetStack.Quantization {
|
|
||||||
public struct QuantizedQuaternion {
|
|
||||||
public uint m;
|
|
||||||
public uint a;
|
|
||||||
public uint b;
|
|
||||||
public uint c;
|
|
||||||
|
|
||||||
public QuantizedQuaternion(uint m, uint a, uint b, uint c) {
|
|
||||||
this.m = m;
|
|
||||||
this.a = a;
|
|
||||||
this.b = b;
|
|
||||||
this.c = c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class SmallestThree {
|
|
||||||
private const float smallestThreeUnpack = 0.70710678118654752440084436210485f + 0.0000001f;
|
|
||||||
private const float smallestThreePack = 1.0f / smallestThreeUnpack;
|
|
||||||
|
|
||||||
public static QuantizedQuaternion Quantize(Quaternion quaternion, int bitsPerElement = 12) {
|
|
||||||
float halfRange = (1 << bitsPerElement - 1);
|
|
||||||
float packer = smallestThreePack * halfRange;
|
|
||||||
float maxValue = float.MinValue;
|
|
||||||
bool signMinus = false;
|
|
||||||
uint m = 0;
|
|
||||||
uint a = 0;
|
|
||||||
uint b = 0;
|
|
||||||
uint c = 0;
|
|
||||||
|
|
||||||
for (uint i = 0; i <= 3; i++) {
|
|
||||||
float element = 0.0f;
|
|
||||||
float abs = 0.0f;
|
|
||||||
|
|
||||||
switch (i) {
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
case 0:
|
|
||||||
element = quaternion.x;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
element = quaternion.y;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
element = quaternion.z;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
element = quaternion.w;
|
|
||||||
|
|
||||||
break;
|
|
||||||
#else
|
|
||||||
case 0:
|
|
||||||
element = quaternion.X;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
element = quaternion.Y;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
element = quaternion.Z;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
element = quaternion.W;
|
|
||||||
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
abs = Math.Abs(element);
|
|
||||||
|
|
||||||
if (abs > maxValue) {
|
|
||||||
signMinus = (element < 0.0f);
|
|
||||||
m = i;
|
|
||||||
maxValue = abs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float af = 0.0f;
|
|
||||||
float bf = 0.0f;
|
|
||||||
float cf = 0.0f;
|
|
||||||
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
switch (m) {
|
|
||||||
case 0:
|
|
||||||
af = quaternion.y;
|
|
||||||
bf = quaternion.z;
|
|
||||||
cf = quaternion.w;
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
af = quaternion.x;
|
|
||||||
bf = quaternion.z;
|
|
||||||
cf = quaternion.w;
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
af = quaternion.x;
|
|
||||||
bf = quaternion.y;
|
|
||||||
cf = quaternion.w;
|
|
||||||
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
af = quaternion.x;
|
|
||||||
bf = quaternion.y;
|
|
||||||
cf = quaternion.z;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
switch (m) {
|
|
||||||
case 0:
|
|
||||||
af = quaternion.Y;
|
|
||||||
bf = quaternion.Z;
|
|
||||||
cf = quaternion.W;
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
af = quaternion.X;
|
|
||||||
bf = quaternion.Z;
|
|
||||||
cf = quaternion.W;
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
af = quaternion.X;
|
|
||||||
bf = quaternion.Y;
|
|
||||||
cf = quaternion.W;
|
|
||||||
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
af = quaternion.X;
|
|
||||||
bf = quaternion.Y;
|
|
||||||
cf = quaternion.Z;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (signMinus) {
|
|
||||||
a = (uint)((-af * packer) + halfRange);
|
|
||||||
b = (uint)((-bf * packer) + halfRange);
|
|
||||||
c = (uint)((-cf * packer) + halfRange);
|
|
||||||
} else {
|
|
||||||
a = (uint)((af * packer) + halfRange);
|
|
||||||
b = (uint)((bf * packer) + halfRange);
|
|
||||||
c = (uint)((cf * packer) + halfRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new QuantizedQuaternion(m, a, b, c);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Quaternion Dequantize(QuantizedQuaternion data, int bitsPerElement = 12) {
|
|
||||||
int halfRange = (1 << bitsPerElement - 1);
|
|
||||||
float unpacker = smallestThreeUnpack * (1.0f / halfRange);
|
|
||||||
uint m = data.m;
|
|
||||||
int ai = (int)data.a;
|
|
||||||
int bi = (int)data.b;
|
|
||||||
int ci = (int)data.c;
|
|
||||||
|
|
||||||
ai -= halfRange;
|
|
||||||
bi -= halfRange;
|
|
||||||
ci -= halfRange;
|
|
||||||
|
|
||||||
float a = ai * unpacker;
|
|
||||||
float b = bi * unpacker;
|
|
||||||
float c = ci * unpacker;
|
|
||||||
|
|
||||||
float d = (float)Math.Sqrt(1.0f - ((a * a) + (b * b) + (c * c)));
|
|
||||||
|
|
||||||
switch (m) {
|
|
||||||
case 0:
|
|
||||||
return new Quaternion(d, a, b, c);
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
return new Quaternion(a, d, b, c);
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
return new Quaternion(a, b, d, c);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return new Quaternion(a, b, c, d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,546 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018 Stanislav Denisov, Maxim Munnig
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copyright (c) 2018 Alexander Shoulson
|
|
||||||
*
|
|
||||||
* This software is provided 'as-is', without any express or implied
|
|
||||||
* warranty. In no event will the authors be held liable for any damages
|
|
||||||
* arising from the use of this software.
|
|
||||||
* Permission is granted to anyone to use this software for any purpose,
|
|
||||||
* including commercial applications, and to alter it and redistribute it
|
|
||||||
* freely, subject to the following restrictions:
|
|
||||||
*
|
|
||||||
* 1. The origin of this software must not be misrepresented; you must not
|
|
||||||
* claim that you wrote the original software. If you use this software
|
|
||||||
* in a product, an acknowledgment in the product documentation would be
|
|
||||||
* appreciated but is not required.
|
|
||||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
|
||||||
* misrepresented as being the original software.
|
|
||||||
* 3. This notice may not be removed or altered from any source distribution.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
using UnityEngine.Assertions;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace NetStack.Serialization {
|
|
||||||
public class BitBuffer {
|
|
||||||
private const int defaultCapacity = 375; // 375 * 4 = 1500 bytes
|
|
||||||
private const int stringLengthBits = 8;
|
|
||||||
private const int stringLengthMax = (1 << stringLengthBits) - 1; // 255
|
|
||||||
private const int bitsASCII = 7;
|
|
||||||
private const int growFactor = 2;
|
|
||||||
private const int minGrow = 1;
|
|
||||||
private int readPosition;
|
|
||||||
private int nextPosition;
|
|
||||||
private uint[] chunks;
|
|
||||||
|
|
||||||
public BitBuffer(int capacity = defaultCapacity) {
|
|
||||||
readPosition = 0;
|
|
||||||
nextPosition = 0;
|
|
||||||
chunks = new uint[capacity];
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Length {
|
|
||||||
get {
|
|
||||||
return ((nextPosition - 1) >> 3) + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsFinished {
|
|
||||||
get {
|
|
||||||
return nextPosition == readPosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public void Clear() {
|
|
||||||
readPosition = 0;
|
|
||||||
nextPosition = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public BitBuffer Add(int numBits, uint value) {
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
Assert.IsFalse(numBits < 0); // Pushing negative bits
|
|
||||||
Assert.IsFalse(numBits > 32); // Pushing too many bits
|
|
||||||
#else
|
|
||||||
Debug.Assert(!(numBits < 0));
|
|
||||||
Debug.Assert(!(numBits > 32));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int index = nextPosition >> 5;
|
|
||||||
int used = nextPosition & 0x0000001F;
|
|
||||||
|
|
||||||
if ((index + 1) >= chunks.Length)
|
|
||||||
ExpandArray();
|
|
||||||
|
|
||||||
ulong chunkMask = ((1UL << used) - 1);
|
|
||||||
ulong scratch = chunks[index] & chunkMask;
|
|
||||||
ulong result = scratch | ((ulong)value << used);
|
|
||||||
|
|
||||||
chunks[index] = (uint)result;
|
|
||||||
chunks[index + 1] = (uint)(result >> 32);
|
|
||||||
nextPosition += numBits;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public uint Read(int numBits) {
|
|
||||||
uint result = Peek(numBits);
|
|
||||||
|
|
||||||
readPosition += numBits;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public uint Peek(int numBits) {
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
Assert.IsFalse(numBits < 0); // Pushing negative bits
|
|
||||||
Assert.IsFalse(numBits > 32); // Pushing too many bits
|
|
||||||
#else
|
|
||||||
Debug.Assert(!(numBits < 0));
|
|
||||||
Debug.Assert(!(numBits > 32));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int index = readPosition >> 5;
|
|
||||||
int used = readPosition & 0x0000001F;
|
|
||||||
|
|
||||||
ulong chunkMask = ((1UL << numBits) - 1) << used;
|
|
||||||
ulong scratch = (ulong)chunks[index];
|
|
||||||
|
|
||||||
if ((index + 1) < chunks.Length)
|
|
||||||
scratch |= (ulong)chunks[index + 1] << 32;
|
|
||||||
|
|
||||||
ulong result = (scratch & chunkMask) >> used;
|
|
||||||
|
|
||||||
return (uint)result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int ToArray(byte[] data) {
|
|
||||||
Add(1, 1);
|
|
||||||
|
|
||||||
int numChunks = (nextPosition >> 5) + 1;
|
|
||||||
int length = data.Length;
|
|
||||||
|
|
||||||
for (int i = 0; i < numChunks; i++) {
|
|
||||||
int dataIdx = i * 4;
|
|
||||||
uint chunk = chunks[i];
|
|
||||||
|
|
||||||
if (dataIdx < length)
|
|
||||||
data[dataIdx] = (byte)(chunk);
|
|
||||||
|
|
||||||
if (dataIdx + 1 < length)
|
|
||||||
data[dataIdx + 1] = (byte)(chunk >> 8);
|
|
||||||
|
|
||||||
if (dataIdx + 2 < length)
|
|
||||||
data[dataIdx + 2] = (byte)(chunk >> 16);
|
|
||||||
|
|
||||||
if (dataIdx + 3 < length)
|
|
||||||
data[dataIdx + 3] = (byte)(chunk >> 24);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FromArray(byte[] data, int length) {
|
|
||||||
int numChunks = (length / 4) + 1;
|
|
||||||
|
|
||||||
if (chunks.Length < numChunks)
|
|
||||||
chunks = new uint[numChunks];
|
|
||||||
|
|
||||||
for (int i = 0; i < numChunks; i++) {
|
|
||||||
int dataIdx = i * 4;
|
|
||||||
uint chunk = 0;
|
|
||||||
|
|
||||||
if (dataIdx < length)
|
|
||||||
chunk = (uint)data[dataIdx];
|
|
||||||
|
|
||||||
if (dataIdx + 1 < length)
|
|
||||||
chunk = chunk | (uint)data[dataIdx + 1] << 8;
|
|
||||||
|
|
||||||
if (dataIdx + 2 < length)
|
|
||||||
chunk = chunk | (uint)data[dataIdx + 2] << 16;
|
|
||||||
|
|
||||||
if (dataIdx + 3 < length)
|
|
||||||
chunk = chunk | (uint)data[dataIdx + 3] << 24;
|
|
||||||
|
|
||||||
chunks[i] = chunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
int positionInByte = FindHighestBitPosition(data[length - 1]);
|
|
||||||
|
|
||||||
nextPosition = ((length - 1) * 8) + (positionInByte - 1);
|
|
||||||
readPosition = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if NETSTACK_SPAN
|
|
||||||
public int ToSpan(ref Span<byte> data) {
|
|
||||||
Add(1, 1);
|
|
||||||
|
|
||||||
int numChunks = (nextPosition >> 5) + 1;
|
|
||||||
int length = data.Length;
|
|
||||||
|
|
||||||
for (int i = 0; i < numChunks; i++) {
|
|
||||||
int dataIdx = i * 4;
|
|
||||||
uint chunk = chunks[i];
|
|
||||||
|
|
||||||
if (dataIdx < length)
|
|
||||||
data[dataIdx] = (byte)(chunk);
|
|
||||||
|
|
||||||
if (dataIdx + 1 < length)
|
|
||||||
data[dataIdx + 1] = (byte)(chunk >> 8);
|
|
||||||
|
|
||||||
if (dataIdx + 2 < length)
|
|
||||||
data[dataIdx + 2] = (byte)(chunk >> 16);
|
|
||||||
|
|
||||||
if (dataIdx + 3 < length)
|
|
||||||
data[dataIdx + 3] = (byte)(chunk >> 24);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FromSpan(ref ReadOnlySpan<byte> data, int length) {
|
|
||||||
int numChunks = (length / 4) + 1;
|
|
||||||
|
|
||||||
if (chunks.Length < numChunks)
|
|
||||||
chunks = new uint[numChunks];
|
|
||||||
|
|
||||||
for (int i = 0; i < numChunks; i++) {
|
|
||||||
int dataIdx = i * 4;
|
|
||||||
uint chunk = 0;
|
|
||||||
|
|
||||||
if (dataIdx < length)
|
|
||||||
chunk = (uint)data[dataIdx];
|
|
||||||
|
|
||||||
if (dataIdx + 1 < length)
|
|
||||||
chunk = chunk | (uint)data[dataIdx + 1] << 8;
|
|
||||||
|
|
||||||
if (dataIdx + 2 < length)
|
|
||||||
chunk = chunk | (uint)data[dataIdx + 2] << 16;
|
|
||||||
|
|
||||||
if (dataIdx + 3 < length)
|
|
||||||
chunk = chunk | (uint)data[dataIdx + 3] << 24;
|
|
||||||
|
|
||||||
chunks[i] = chunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
int positionInByte = FindHighestBitPosition(data[length - 1]);
|
|
||||||
|
|
||||||
nextPosition = ((length - 1) * 8) + (positionInByte - 1);
|
|
||||||
readPosition = 0;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public BitBuffer AddBool(bool value) {
|
|
||||||
Add(1, value ? 1U : 0U);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public bool ReadBool() {
|
|
||||||
return Read(1) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public bool PeekBool() {
|
|
||||||
return Peek(1) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public BitBuffer AddByte(byte value) {
|
|
||||||
Add(8, value);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public byte ReadByte() {
|
|
||||||
return (byte)Read(8);
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public byte PeekByte() {
|
|
||||||
return (byte)Peek(8);
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public BitBuffer AddShort(short value) {
|
|
||||||
AddInt(value);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public short ReadShort() {
|
|
||||||
return (short)ReadInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public short PeekShort() {
|
|
||||||
return (short)PeekInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public BitBuffer AddUShort(ushort value) {
|
|
||||||
AddUInt(value);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public ushort ReadUShort() {
|
|
||||||
return (ushort)ReadUInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public ushort PeekUShort() {
|
|
||||||
return (ushort)PeekUInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public BitBuffer AddInt(int value) {
|
|
||||||
uint zigzag = (uint)((value << 1) ^ (value >> 31));
|
|
||||||
|
|
||||||
AddUInt(zigzag);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public int ReadInt() {
|
|
||||||
uint value = ReadUInt();
|
|
||||||
int zagzig = (int)((value >> 1) ^ (-(int)(value & 1)));
|
|
||||||
|
|
||||||
return zagzig;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public int PeekInt() {
|
|
||||||
uint value = PeekUInt();
|
|
||||||
int zagzig = (int)((value >> 1) ^ (-(int)(value & 1)));
|
|
||||||
|
|
||||||
return zagzig;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public BitBuffer AddUInt(uint value) {
|
|
||||||
uint buffer = 0x0u;
|
|
||||||
|
|
||||||
do {
|
|
||||||
buffer = value & 0x7Fu;
|
|
||||||
value >>= 7;
|
|
||||||
|
|
||||||
if (value > 0)
|
|
||||||
buffer |= 0x80u;
|
|
||||||
|
|
||||||
Add(8, buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (value > 0);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public uint ReadUInt() {
|
|
||||||
uint buffer = 0x0u;
|
|
||||||
uint value = 0x0u;
|
|
||||||
int shift = 0;
|
|
||||||
|
|
||||||
do {
|
|
||||||
buffer = Read(8);
|
|
||||||
|
|
||||||
value |= (buffer & 0x7Fu) << shift;
|
|
||||||
shift += 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
while ((buffer & 0x80u) > 0);
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public uint PeekUInt() {
|
|
||||||
int tempPosition = readPosition;
|
|
||||||
uint value = ReadUInt();
|
|
||||||
|
|
||||||
readPosition = tempPosition;
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public BitBuffer AddLong(long value) {
|
|
||||||
AddInt((int)(value & uint.MaxValue));
|
|
||||||
AddInt((int)(value >> 32));
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public long ReadLong() {
|
|
||||||
int low = ReadInt();
|
|
||||||
int high = ReadInt();
|
|
||||||
long value = high;
|
|
||||||
|
|
||||||
return value << 32 | (uint)low;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public long PeekLong() {
|
|
||||||
int tempPosition = readPosition;
|
|
||||||
long value = ReadLong();
|
|
||||||
|
|
||||||
readPosition = tempPosition;
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public BitBuffer AddULong(ulong value) {
|
|
||||||
AddUInt((uint)(value & uint.MaxValue));
|
|
||||||
AddUInt((uint)(value >> 32));
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public ulong ReadULong() {
|
|
||||||
uint low = ReadUInt();
|
|
||||||
uint high = ReadUInt();
|
|
||||||
|
|
||||||
return (ulong)high << 32 | low;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public ulong PeekULong() {
|
|
||||||
int tempPosition = readPosition;
|
|
||||||
ulong value = ReadULong();
|
|
||||||
|
|
||||||
readPosition = tempPosition;
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public BitBuffer AddString(string value) {
|
|
||||||
if (value == null)
|
|
||||||
throw new ArgumentNullException("value");
|
|
||||||
|
|
||||||
uint length = (uint)value.Length;
|
|
||||||
|
|
||||||
if (length > stringLengthMax) {
|
|
||||||
length = (uint)stringLengthMax;
|
|
||||||
|
|
||||||
throw new ArgumentOutOfRangeException("value length exceeded");
|
|
||||||
}
|
|
||||||
|
|
||||||
Add(stringLengthBits, length);
|
|
||||||
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
Add(bitsASCII, ToASCII(value[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public string ReadString() {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
uint length = Read(stringLengthBits);
|
|
||||||
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
builder.Append((char)Read(bitsASCII));
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString() {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
|
|
||||||
for (int i = chunks.Length - 1; i >= 0; i--) {
|
|
||||||
builder.Append(Convert.ToString(chunks[i], 2).PadLeft(32, '0'));
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder spaced = new StringBuilder();
|
|
||||||
|
|
||||||
for (int i = 0; i < builder.Length; i++) {
|
|
||||||
spaced.Append(builder[i]);
|
|
||||||
|
|
||||||
if (((i + 1) % 8) == 0)
|
|
||||||
spaced.Append(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
return spaced.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ExpandArray() {
|
|
||||||
int newCapacity = (chunks.Length * growFactor) + minGrow;
|
|
||||||
uint[] newChunks = new uint[newCapacity];
|
|
||||||
|
|
||||||
Array.Copy(chunks, newChunks, chunks.Length);
|
|
||||||
chunks = newChunks;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
private static int FindHighestBitPosition(byte data) {
|
|
||||||
int shiftCount = 0;
|
|
||||||
|
|
||||||
while (data > 0) {
|
|
||||||
data >>= 1;
|
|
||||||
shiftCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return shiftCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte ToASCII(char character) {
|
|
||||||
byte value = 0;
|
|
||||||
|
|
||||||
try {
|
|
||||||
value = Convert.ToByte(character);
|
|
||||||
}
|
|
||||||
|
|
||||||
catch (OverflowException) {
|
|
||||||
throw new Exception("Cannot convert to ASCII: " + character);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value > 127)
|
|
||||||
throw new Exception("Cannot convert to ASCII: " + character);
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019 Stanislav Denisov
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace NetStack.Threading {
|
|
||||||
[StructLayout(LayoutKind.Explicit, Size = 192)]
|
|
||||||
public sealed class ArrayQueue {
|
|
||||||
[FieldOffset(0)]
|
|
||||||
private readonly Entry[] _array;
|
|
||||||
[FieldOffset(8)]
|
|
||||||
private readonly int _arrayMask;
|
|
||||||
[FieldOffset(64)]
|
|
||||||
private int _enqueuePosition;
|
|
||||||
[FieldOffset(128)]
|
|
||||||
private int _dequeuePosition;
|
|
||||||
|
|
||||||
public int Count {
|
|
||||||
get {
|
|
||||||
return _enqueuePosition - _dequeuePosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArrayQueue(int capacity) {
|
|
||||||
if (capacity < 2)
|
|
||||||
throw new ArgumentException("Queue size should be greater than or equal to two");
|
|
||||||
|
|
||||||
if ((capacity & (capacity - 1)) != 0)
|
|
||||||
throw new ArgumentException("Queue size should be a power of two");
|
|
||||||
|
|
||||||
_arrayMask = capacity - 1;
|
|
||||||
_array = new Entry[capacity];
|
|
||||||
_enqueuePosition = 0;
|
|
||||||
_dequeuePosition = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Enqueue(object item) {
|
|
||||||
while (true) {
|
|
||||||
if (TryEnqueue(item))
|
|
||||||
break;
|
|
||||||
|
|
||||||
Thread.SpinWait(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryEnqueue(object item) {
|
|
||||||
var array = _array;
|
|
||||||
var position = _enqueuePosition;
|
|
||||||
var index = position & _arrayMask;
|
|
||||||
|
|
||||||
if (array[index].IsSet != 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
array[index].element = item;
|
|
||||||
array[index].IsSet = 1;
|
|
||||||
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
Volatile.Write(ref _enqueuePosition, position + 1);
|
|
||||||
#else
|
|
||||||
Thread.MemoryBarrier();
|
|
||||||
_enqueuePosition = position + 1;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Dequeue() {
|
|
||||||
while (true) {
|
|
||||||
object element;
|
|
||||||
|
|
||||||
if (TryDequeue(out element))
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryDequeue(out object result) {
|
|
||||||
var array = _array;
|
|
||||||
var position = _dequeuePosition;
|
|
||||||
var index = position & _arrayMask;
|
|
||||||
|
|
||||||
if (array[index].IsSet == 0) {
|
|
||||||
result = default(object);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = array[index].element;
|
|
||||||
array[index].element = default(object);
|
|
||||||
array[index].IsSet = 0;
|
|
||||||
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
Volatile.Write(ref _dequeuePosition, position + 1);
|
|
||||||
#else
|
|
||||||
Thread.MemoryBarrier();
|
|
||||||
_dequeuePosition = position + 1;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit, Size = 16)]
|
|
||||||
private struct Entry {
|
|
||||||
[FieldOffset(0)]
|
|
||||||
private int isSet;
|
|
||||||
[FieldOffset(8)]
|
|
||||||
internal object element;
|
|
||||||
|
|
||||||
internal int IsSet {
|
|
||||||
get {
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
return Volatile.Read(ref isSet);
|
|
||||||
#else
|
|
||||||
Thread.MemoryBarrier();
|
|
||||||
return isSet;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
set {
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
Volatile.Write(ref isSet, value);
|
|
||||||
#else
|
|
||||||
Thread.MemoryBarrier();
|
|
||||||
isSet = value;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018 Alexander Nikitin, Stanislav Denisov
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace NetStack.Threading {
|
|
||||||
[StructLayout(LayoutKind.Explicit, Size = 192)]
|
|
||||||
public sealed class ConcurrentBuffer {
|
|
||||||
[FieldOffset(0)]
|
|
||||||
private readonly Cell[] _buffer;
|
|
||||||
[FieldOffset(8)]
|
|
||||||
private readonly int _bufferMask;
|
|
||||||
[FieldOffset(64)]
|
|
||||||
private int _enqueuePosition;
|
|
||||||
[FieldOffset(128)]
|
|
||||||
private int _dequeuePosition;
|
|
||||||
|
|
||||||
public int Count {
|
|
||||||
get {
|
|
||||||
return _enqueuePosition - _dequeuePosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConcurrentBuffer(int bufferSize) {
|
|
||||||
if (bufferSize < 2)
|
|
||||||
throw new ArgumentException("Buffer size should be greater than or equal to two");
|
|
||||||
|
|
||||||
if ((bufferSize & (bufferSize - 1)) != 0)
|
|
||||||
throw new ArgumentException("Buffer size should be a power of two");
|
|
||||||
|
|
||||||
_bufferMask = bufferSize - 1;
|
|
||||||
_buffer = new Cell[bufferSize];
|
|
||||||
|
|
||||||
for (var i = 0; i < bufferSize; i++) {
|
|
||||||
_buffer[i] = new Cell(i, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
_enqueuePosition = 0;
|
|
||||||
_dequeuePosition = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Enqueue(object item) {
|
|
||||||
while (true) {
|
|
||||||
if (TryEnqueue(item))
|
|
||||||
break;
|
|
||||||
|
|
||||||
Thread.SpinWait(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryEnqueue(object item) {
|
|
||||||
do {
|
|
||||||
var buffer = _buffer;
|
|
||||||
var position = _enqueuePosition;
|
|
||||||
var index = position & _bufferMask;
|
|
||||||
var cell = buffer[index];
|
|
||||||
|
|
||||||
if (cell.Sequence == position && Interlocked.CompareExchange(ref _enqueuePosition, position + 1, position) == position) {
|
|
||||||
buffer[index].Element = item;
|
|
||||||
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
Volatile.Write(ref buffer[index].Sequence, position + 1);
|
|
||||||
#else
|
|
||||||
Thread.MemoryBarrier();
|
|
||||||
buffer[index].Sequence = position + 1;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cell.Sequence < position)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Dequeue() {
|
|
||||||
while (true) {
|
|
||||||
object element;
|
|
||||||
|
|
||||||
if (TryDequeue(out element))
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryDequeue(out object result) {
|
|
||||||
do {
|
|
||||||
var buffer = _buffer;
|
|
||||||
var bufferMask = _bufferMask;
|
|
||||||
var position = _dequeuePosition;
|
|
||||||
var index = position & bufferMask;
|
|
||||||
var cell = buffer[index];
|
|
||||||
|
|
||||||
if (cell.Sequence == position + 1 && Interlocked.CompareExchange(ref _dequeuePosition, position + 1, position) == position) {
|
|
||||||
result = cell.Element;
|
|
||||||
buffer[index].Element = null;
|
|
||||||
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
Volatile.Write(ref buffer[index].Sequence, position + bufferMask + 1);
|
|
||||||
#else
|
|
||||||
Thread.MemoryBarrier();
|
|
||||||
buffer[index].Sequence = position + bufferMask + 1;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cell.Sequence < position + 1) {
|
|
||||||
result = default(object);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true);
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit, Size = 16)]
|
|
||||||
private struct Cell {
|
|
||||||
[FieldOffset(0)]
|
|
||||||
public int Sequence;
|
|
||||||
[FieldOffset(8)]
|
|
||||||
public object Element;
|
|
||||||
|
|
||||||
public Cell(int sequence, object element) {
|
|
||||||
Sequence = sequence;
|
|
||||||
Element = element;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018 Virgile Bello, Stanislav Denisov
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace NetStack.Threading {
|
|
||||||
public sealed class ConcurrentPool<T> where T : class {
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
private SpinLock _lock;
|
|
||||||
#else
|
|
||||||
private object _lock;
|
|
||||||
#endif
|
|
||||||
private readonly Func<T> _factory;
|
|
||||||
private Segment _head;
|
|
||||||
private Segment _tail;
|
|
||||||
|
|
||||||
public ConcurrentPool(int capacity, Func<T> factory) {
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
_lock = new SpinLock();
|
|
||||||
#else
|
|
||||||
_lock = new Object();
|
|
||||||
#endif
|
|
||||||
_head = _tail = new Segment(capacity);
|
|
||||||
_factory = factory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public T Acquire() {
|
|
||||||
while (true) {
|
|
||||||
var localHead = _head;
|
|
||||||
var count = localHead.Count;
|
|
||||||
|
|
||||||
if (count == 0) {
|
|
||||||
if (localHead.Next != null) {
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
bool lockTaken = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
_lock.Enter(ref lockTaken);
|
|
||||||
|
|
||||||
if (_head.Next != null && _head.Count == 0)
|
|
||||||
_head = _head.Next;
|
|
||||||
}
|
|
||||||
|
|
||||||
finally {
|
|
||||||
if (lockTaken)
|
|
||||||
_lock.Exit(false);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
try {
|
|
||||||
Monitor.Enter(_lock);
|
|
||||||
|
|
||||||
if (_head.Next != null && _head.Count == 0)
|
|
||||||
_head = _head.Next;
|
|
||||||
}
|
|
||||||
|
|
||||||
finally {
|
|
||||||
Monitor.Exit(_lock);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
} else {
|
|
||||||
return _factory();
|
|
||||||
}
|
|
||||||
} else if (Interlocked.CompareExchange(ref localHead.Count, count - 1, count) == count) {
|
|
||||||
var localLow = Interlocked.Increment(ref localHead.Low) - 1;
|
|
||||||
var index = localLow & localHead.Mask;
|
|
||||||
T item;
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
var spinWait = new SpinWait();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
while ((item = Interlocked.Exchange(ref localHead.Items[index], null)) == null) {
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
spinWait.SpinOnce();
|
|
||||||
#else
|
|
||||||
Thread.SpinWait(1);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Release(T item) {
|
|
||||||
while (true) {
|
|
||||||
var localTail = _tail;
|
|
||||||
var count = localTail.Count;
|
|
||||||
|
|
||||||
if (count == localTail.Items.Length) {
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
bool lockTaken = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
_lock.Enter(ref lockTaken);
|
|
||||||
|
|
||||||
if (_tail.Next == null && count == _tail.Items.Length)
|
|
||||||
_tail = _tail.Next = new Segment(_tail.Items.Length << 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
finally {
|
|
||||||
if (lockTaken)
|
|
||||||
_lock.Exit(false);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
try {
|
|
||||||
Monitor.Enter(_lock);
|
|
||||||
|
|
||||||
if (_tail.Next == null && count == _tail.Items.Length)
|
|
||||||
_tail = _tail.Next = new Segment(_tail.Items.Length << 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
finally {
|
|
||||||
Monitor.Exit(_lock);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
} else if (Interlocked.CompareExchange(ref localTail.Count, count + 1, count) == count) {
|
|
||||||
var localHigh = Interlocked.Increment(ref localTail.High) - 1;
|
|
||||||
var index = localHigh & localTail.Mask;
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
var spinWait = new SpinWait();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
while (Interlocked.CompareExchange(ref localTail.Items[index], item, null) != null) {
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
spinWait.SpinOnce();
|
|
||||||
#else
|
|
||||||
Thread.SpinWait(1);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Segment {
|
|
||||||
public readonly T[] Items;
|
|
||||||
public readonly int Mask;
|
|
||||||
public int Low;
|
|
||||||
public int High;
|
|
||||||
public int Count;
|
|
||||||
public Segment Next;
|
|
||||||
|
|
||||||
public Segment(int size) {
|
|
||||||
if (size <= 0 || ((size & (size - 1)) != 0))
|
|
||||||
throw new ArgumentOutOfRangeException("Segment size must be power of two");
|
|
||||||
|
|
||||||
Items = new T[size];
|
|
||||||
Mask = size - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-63
@@ -1,63 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018 Stanislav Denisov
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace NetStack.Unsafe {
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
public static class Memory {
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public static unsafe void Copy(IntPtr source, int sourceOffset, byte[] destination, int destinationOffset, int length) {
|
|
||||||
if (length > 0) {
|
|
||||||
fixed (byte* destinationPointer = &destination[destinationOffset]) {
|
|
||||||
byte* sourcePointer = (byte*)source + sourceOffset;
|
|
||||||
|
|
||||||
Buffer.MemoryCopy(sourcePointer, destinationPointer, length, length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public static unsafe void Copy(byte[] source, int sourceOffset, IntPtr destination, int destinationOffset, int length) {
|
|
||||||
if (length > 0) {
|
|
||||||
fixed (byte* sourcePointer = &source[sourceOffset]) {
|
|
||||||
byte* destinationPointer = (byte*)destination + destinationOffset;
|
|
||||||
|
|
||||||
Buffer.MemoryCopy(sourcePointer, destinationPointer, length, length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public static unsafe void Copy(byte[] source, int sourceOffset, byte[] destination, int destinationOffset, int length) {
|
|
||||||
if (length > 0) {
|
|
||||||
fixed (byte* sourcePointer = &source[sourceOffset]) {
|
|
||||||
fixed (byte* destinationPointer = &destination[destinationOffset]) {
|
|
||||||
Buffer.MemoryCopy(sourcePointer, destinationPointer, length, length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
@@ -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,10 +0,0 @@
|
|||||||
using NetStack.Serialization;
|
|
||||||
|
|
||||||
namespace Ragon.Common
|
|
||||||
{
|
|
||||||
public interface IRagonSerializable
|
|
||||||
{
|
|
||||||
public void Serialize(BitBuffer buffer);
|
|
||||||
public void Deserialize(BitBuffer buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,8 +18,4 @@
|
|||||||
<OutputPath></OutputPath>
|
<OutputPath></OutputPath>
|
||||||
<DefineConstants>TRACE;NETSTACK_SPAN</DefineConstants>
|
<DefineConstants>TRACE;NETSTACK_SPAN</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="External" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ namespace Ragon.Common
|
|||||||
{
|
{
|
||||||
public enum RagonAuthority: byte
|
public enum RagonAuthority: byte
|
||||||
{
|
{
|
||||||
OWNER_ONLY,
|
None,
|
||||||
ALL,
|
OwnerOnly,
|
||||||
|
All,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,29 +1,25 @@
|
|||||||
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,
|
||||||
|
CREATE_ROOM,
|
||||||
JOIN_ROOM,
|
JOIN_ROOM,
|
||||||
LEAVE_ROOM,
|
LEAVE_ROOM,
|
||||||
|
OWNERSHIP_CHANGED,
|
||||||
|
JOIN_SUCCESS,
|
||||||
|
JOIN_FAILED,
|
||||||
LOAD_SCENE,
|
LOAD_SCENE,
|
||||||
SCENE_IS_LOADED,
|
SCENE_LOADED,
|
||||||
|
|
||||||
PLAYER_JOINED,
|
PLAYER_JOINED,
|
||||||
PLAYER_LEAVED,
|
PLAYER_LEAVED,
|
||||||
|
|
||||||
CREATE_ENTITY,
|
CREATE_ENTITY,
|
||||||
DESTROY_ENTITY,
|
DESTROY_ENTITY,
|
||||||
|
SNAPSHOT,
|
||||||
RESTORE_BEGIN,
|
|
||||||
RESTORE_END,
|
|
||||||
RESTORED,
|
|
||||||
|
|
||||||
REPLICATE_ENTITY_STATE,
|
REPLICATE_ENTITY_STATE,
|
||||||
REPLICATE_ENTITY_EVENT,
|
REPLICATE_ENTITY_EVENT,
|
||||||
REPLICATE_EVENT,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Ragon.Common
|
||||||
|
{
|
||||||
|
public enum RagonReplicationMode: byte
|
||||||
|
{
|
||||||
|
Local,
|
||||||
|
Server,
|
||||||
|
LocalAndServer,
|
||||||
|
Buffered,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
namespace Ragon.Common
|
||||||
|
{
|
||||||
|
public class RagonRoomParameters: IRagonSerializable
|
||||||
|
{
|
||||||
|
public string Map { get; set; }
|
||||||
|
public int Min { get; set; }
|
||||||
|
public int Max { get; set; }
|
||||||
|
|
||||||
|
public void Serialize(RagonSerializer buffer)
|
||||||
|
{
|
||||||
|
buffer.WriteString(Map);
|
||||||
|
buffer.WriteInt(Min);
|
||||||
|
buffer.WriteInt(Max);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Deserialize(RagonSerializer buffer)
|
||||||
|
{
|
||||||
|
Map = buffer.ReadString();
|
||||||
|
Min = buffer.ReadInt();
|
||||||
|
Max = buffer.ReadInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Ragon.Common
|
||||||
|
{
|
||||||
|
public interface IRagonSerializable
|
||||||
|
{
|
||||||
|
public void Serialize(RagonSerializer serializer);
|
||||||
|
public void Deserialize(RagonSerializer serializer);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,335 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Ragon.Common
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
|
internal struct ValueConverter
|
||||||
|
{
|
||||||
|
[FieldOffset(0)] public int Int;
|
||||||
|
[FieldOffset(0)] public float Float;
|
||||||
|
[FieldOffset(0)] public long Long;
|
||||||
|
[FieldOffset(0)] public byte Byte0;
|
||||||
|
[FieldOffset(1)] public byte Byte1;
|
||||||
|
[FieldOffset(2)] public byte Byte2;
|
||||||
|
[FieldOffset(3)] public byte Byte3;
|
||||||
|
[FieldOffset(4)] public byte Byte4;
|
||||||
|
[FieldOffset(5)] public byte Byte5;
|
||||||
|
[FieldOffset(6)] public byte Byte6;
|
||||||
|
[FieldOffset(7)] public byte Byte7;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Reset()
|
||||||
|
{
|
||||||
|
_size = _offset;
|
||||||
|
_offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void AddOffset(int offset)
|
||||||
|
{
|
||||||
|
_offset += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int WriteByte(byte value)
|
||||||
|
{
|
||||||
|
ResizeIfNeed(1);
|
||||||
|
_data[_offset] = value;
|
||||||
|
_offset += 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public byte ReadByte()
|
||||||
|
{
|
||||||
|
var value = _data[_offset];
|
||||||
|
_offset += 1;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int WriteBool(bool value)
|
||||||
|
{
|
||||||
|
ResizeIfNeed(1);
|
||||||
|
_data[_offset] = value ? (byte) 1 : (byte) 0;
|
||||||
|
_offset += 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool ReadBool()
|
||||||
|
{
|
||||||
|
var value = _data[_offset];
|
||||||
|
_offset += 1;
|
||||||
|
return value == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int WriteInt(int value)
|
||||||
|
{
|
||||||
|
ResizeIfNeed(4);
|
||||||
|
var converter = new ValueConverter() {Int = value};
|
||||||
|
_data[_offset] = converter.Byte0;
|
||||||
|
_data[_offset + 1] = converter.Byte1;
|
||||||
|
_data[_offset + 2] = converter.Byte2;
|
||||||
|
_data[_offset + 3] = converter.Byte3;
|
||||||
|
_offset += 4;
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int WriteInt(int value, int offset)
|
||||||
|
{
|
||||||
|
ResizeIfNeed(4);
|
||||||
|
var converter = new ValueConverter() {Int = value};
|
||||||
|
_data[offset] = converter.Byte0;
|
||||||
|
_data[offset + 1] = converter.Byte1;
|
||||||
|
_data[offset + 2] = converter.Byte2;
|
||||||
|
_data[offset + 3] = converter.Byte3;
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int ReadInt()
|
||||||
|
{
|
||||||
|
var converter = new ValueConverter {Byte0 = _data[_offset], Byte1 = _data[_offset + 1], Byte2 = _data[_offset + 2], Byte3 = _data[_offset + 3]};
|
||||||
|
_offset += 4;
|
||||||
|
return converter.Int;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteLong(long value)
|
||||||
|
{
|
||||||
|
ResizeIfNeed(8);
|
||||||
|
WriteLong(value, _offset);
|
||||||
|
_offset += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int WriteLong(long value, int offset)
|
||||||
|
{
|
||||||
|
var converter = new ValueConverter() {Long = value};
|
||||||
|
_data[offset] = converter.Byte0;
|
||||||
|
_data[offset + 1] = converter.Byte1;
|
||||||
|
_data[offset + 2] = converter.Byte2;
|
||||||
|
_data[offset + 3] = converter.Byte3;
|
||||||
|
_data[offset + 4] = converter.Byte4;
|
||||||
|
_data[offset + 5] = converter.Byte5;
|
||||||
|
_data[offset + 6] = converter.Byte6;
|
||||||
|
_data[offset + 7] = converter.Byte7;
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public long ReadLong()
|
||||||
|
{
|
||||||
|
var converter = new ValueConverter
|
||||||
|
{
|
||||||
|
Byte0 = _data[_offset],
|
||||||
|
Byte1 = _data[_offset + 1],
|
||||||
|
Byte2 = _data[_offset + 2],
|
||||||
|
Byte3 = _data[_offset + 3],
|
||||||
|
Byte4 = _data[_offset + 4],
|
||||||
|
Byte5 = _data[_offset + 5],
|
||||||
|
Byte6 = _data[_offset + 6],
|
||||||
|
Byte7 = _data[_offset + 7],
|
||||||
|
};
|
||||||
|
_offset += 8;
|
||||||
|
return converter.Long;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int WriteFloat(float value)
|
||||||
|
{
|
||||||
|
var converter = new ValueConverter() {Float = value};
|
||||||
|
WriteInt(converter.Int);
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public float ReadFloat()
|
||||||
|
{
|
||||||
|
var rawValue = ReadInt();
|
||||||
|
var converter = new ValueConverter() {Int = rawValue};
|
||||||
|
var value = converter.Float;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int WriteString(string value)
|
||||||
|
{
|
||||||
|
var rawData = Encoding.UTF8.GetBytes(value).AsSpan();
|
||||||
|
ResizeIfNeed(2 + rawData.Length);
|
||||||
|
WriteUShort((ushort) rawData.Length);
|
||||||
|
var data = _data.AsSpan().Slice(_offset, rawData.Length);
|
||||||
|
rawData.CopyTo(data);
|
||||||
|
_offset += rawData.Length;
|
||||||
|
|
||||||
|
return rawData.Length + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
[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 int 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;
|
||||||
|
return 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 int WriteOperation(RagonOperation ragonOperation)
|
||||||
|
{
|
||||||
|
ResizeIfNeed(1);
|
||||||
|
|
||||||
|
_data[_offset] = (byte) ragonOperation;
|
||||||
|
_offset += 1;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public RagonOperation ReadOperation()
|
||||||
|
{
|
||||||
|
var op = (RagonOperation) _data[_offset];
|
||||||
|
_offset += 1;
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int WriteUShort(ushort value)
|
||||||
|
{
|
||||||
|
ResizeIfNeed(2);
|
||||||
|
|
||||||
|
_data[_offset] = (byte) (value & 0x00FF);
|
||||||
|
_data[_offset + 1] = (byte) ((value & 0xFF00) >> 8);
|
||||||
|
_offset += 2;
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int WriteUShort(ushort value, int offset)
|
||||||
|
{
|
||||||
|
ResizeIfNeed(2);
|
||||||
|
_data[offset] = (byte) (value & 0x00FF);
|
||||||
|
_data[offset + 1] = (byte) ((value & 0xFF00) >> 8);
|
||||||
|
return 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, data.Length);
|
||||||
|
_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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Ragon.Common
|
||||||
|
{
|
||||||
|
public enum RagonTarget: byte
|
||||||
|
{
|
||||||
|
Owner,
|
||||||
|
ExceptOwner,
|
||||||
|
All,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Threading;
|
||||||
using Game.Source;
|
using Game.Source;
|
||||||
using Ragon.Core;
|
using Ragon.Core;
|
||||||
|
|
||||||
@@ -9,9 +10,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,44 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ragon.Core;
|
||||||
|
|
||||||
|
namespace Game.Source;
|
||||||
|
|
||||||
|
public class ApplicationHandlerByKey: IApplicationHandler
|
||||||
|
{
|
||||||
|
private Configuration _configuration;
|
||||||
|
public ApplicationHandlerByKey(Configuration configuration)
|
||||||
|
{
|
||||||
|
_configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OnAuthorizationRequest(string key, string name, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnCustomEvent(ushort peerId, ReadOnlySpan<byte> payload)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnJoin(ushort peerId)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnLeave(ushort peerId)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
using NLog.Fluent;
|
||||||
|
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({Room.Id})");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnPlayerLeaved(Player player)
|
||||||
|
{
|
||||||
|
// Logger.Info($"Player({player.PlayerName}) left from Room({Room.Id})");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnEntityCreated(Player player, Entity entity)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
// Logger.Info($"Player({player.PlayerName}) create entity {entity.EntityId}:{entity.EntityType}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnEntityDestroyed(Player player, Entity entity)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
// Logger.Info($"Player({player.PlayerName}) destroy entity {entity.EntityId}:{entity.EntityType}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+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 IApplicationHandler CreateAuthorizationProvider(Configuration configuration)
|
||||||
{
|
{
|
||||||
return new AuthorizerByKey(configuration);
|
return new ApplicationHandlerByKey(configuration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Executable
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"key": "defaultkey",
|
||||||
|
"protocol": "1.0.0",
|
||||||
|
"statisticsInterval": 5,
|
||||||
|
"sendRate": 50,
|
||||||
|
"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_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>
|
||||||
|
|
||||||
|
|||||||
+104
-62
@@ -1,90 +1,132 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Ragon.Common;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
|
||||||
namespace Ragon.Core
|
namespace Ragon.Core
|
||||||
{
|
{
|
||||||
public class Application : IDisposable
|
public class Application : IEventHandler
|
||||||
{
|
{
|
||||||
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
private readonly RoomManager _roomManager;
|
||||||
private readonly List<RoomThread> _roomThreads = new();
|
private readonly Thread _thread;
|
||||||
private readonly Dictionary<uint, RoomThread> _socketByRoomThreads = new();
|
private readonly Lobby _lobby;
|
||||||
private readonly Dictionary<RoomThread, int> _roomThreadCounter = new();
|
private readonly ISocketServer _socketServer;
|
||||||
|
private readonly Dispatcher _dispatcher;
|
||||||
|
private readonly ILogger _logger = LogManager.GetCurrentClassLogger();
|
||||||
|
private readonly float _deltaTime = 0.0f;
|
||||||
private readonly Configuration _configuration;
|
private readonly Configuration _configuration;
|
||||||
private readonly ENetServer _socketServer;
|
private readonly RagonSerializer _serializer;
|
||||||
private int _roomThreadBalancer = 0;
|
|
||||||
|
|
||||||
public Application(PluginFactory factory, Configuration configuration, int threadsCount)
|
public ISocketServer SocketServer => _socketServer;
|
||||||
|
public Dispatcher Dispatcher => _dispatcher;
|
||||||
|
|
||||||
|
public Application(PluginFactory factory, Configuration configuration)
|
||||||
{
|
{
|
||||||
_socketServer = new ENetServer();
|
var authorizationProvider = factory.CreateAuthorizationProvider(configuration);
|
||||||
_configuration = configuration;
|
|
||||||
|
|
||||||
for (var i = 0; i < threadsCount; i++)
|
_configuration = configuration;
|
||||||
{
|
_serializer = new RagonSerializer();
|
||||||
var roomThread = new RoomThread(factory, configuration);
|
|
||||||
_roomThreadCounter.Add(roomThread, 0);
|
var dispatcher = new Dispatcher();
|
||||||
_roomThreads.Add(roomThread);
|
_dispatcher = dispatcher;
|
||||||
}
|
|
||||||
|
_socketServer = new ENetServer(this);
|
||||||
|
_deltaTime = 1000.0f / configuration.SendRate;
|
||||||
|
|
||||||
|
_roomManager = new RoomManager(factory, this);
|
||||||
|
_lobby = new Lobby(authorizationProvider, _roomManager, this);
|
||||||
|
|
||||||
|
_thread = new Thread(Execute);
|
||||||
|
_thread.Name = "Game Thread";
|
||||||
|
_thread.IsBackground = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
_socketServer.Start(_configuration.Server.Port);
|
var strings = _configuration.Protocol.Split(".");
|
||||||
|
if (strings.Length < 3)
|
||||||
foreach (var roomThread in _roomThreads)
|
|
||||||
roomThread.Start();
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
{
|
||||||
foreach (var roomThread in _roomThreads)
|
_logger.Error("Wrong protocol passed to connect method");
|
||||||
while (roomThread.ReadOutEvent(out var evnt))
|
return;
|
||||||
_socketServer.WriteEvent(evnt);
|
}
|
||||||
|
|
||||||
while (_socketServer.ReadEvent(out var evnt))
|
var parts = new uint[] {0, 0, 0};
|
||||||
|
for (int i = 0; i < parts.Length; i++)
|
||||||
|
{
|
||||||
|
if (!uint.TryParse(strings[i], out var v))
|
||||||
{
|
{
|
||||||
if (evnt.Type == EventType.CONNECTED)
|
_logger.Error("Wrong protocol");
|
||||||
{
|
return;
|
||||||
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);
|
parts[i] = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint encoded = (parts[0] << 16) | (parts[1] << 8) | parts[2];
|
||||||
|
_socketServer.Start(_configuration.Port, _configuration.MaxConnections, encoded);
|
||||||
|
_thread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
_socketServer.Stop();
|
||||||
|
_thread.Interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Execute()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
_socketServer.Process();
|
||||||
|
_dispatcher.Process();
|
||||||
|
_roomManager.Tick(_deltaTime);
|
||||||
|
//
|
||||||
|
Thread.Sleep((int) _deltaTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void OnConnected(ushort peerId)
|
||||||
{
|
{
|
||||||
foreach (var roomThread in _roomThreads)
|
_logger.Trace("Connected " + peerId);
|
||||||
roomThread.Dispose();
|
}
|
||||||
|
|
||||||
_roomThreads.Clear();
|
public void OnDisconnected(ushort peerId)
|
||||||
|
{
|
||||||
|
_logger.Trace("Disconnected " + peerId);
|
||||||
|
|
||||||
|
var player = _lobby.AuthorizationManager.GetPlayer(peerId);
|
||||||
|
if (player != null)
|
||||||
|
_roomManager.Left(player, Array.Empty<byte>());
|
||||||
|
|
||||||
|
_lobby.OnDisconnected(peerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnData(ushort peerId, byte[] data)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_serializer.Clear();
|
||||||
|
_serializer.FromArray(data);
|
||||||
|
|
||||||
|
var operation = _serializer.ReadOperation();
|
||||||
|
if (_roomManager.RoomsBySocket.TryGetValue(peerId, out var room))
|
||||||
|
room.ProcessEvent(peerId, operation, _serializer);
|
||||||
|
|
||||||
|
_lobby.ProcessEvent(peerId, operation, _serializer);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_logger.Error(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnTimeout(ushort peerId)
|
||||||
|
{
|
||||||
|
var player = _lobby.AuthorizationManager.GetPlayer(peerId);
|
||||||
|
if (player != null)
|
||||||
|
_roomManager.Left(player, Array.Empty<byte>());
|
||||||
|
|
||||||
|
_lobby.OnDisconnected(peerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NLog;
|
||||||
|
using Ragon.Common;
|
||||||
|
|
||||||
|
namespace Ragon.Core;
|
||||||
|
|
||||||
|
public class AuthorizationManager
|
||||||
|
{
|
||||||
|
private Logger _logger = LogManager.GetCurrentClassLogger();
|
||||||
|
private IApplicationHandler _provider;
|
||||||
|
private Application _gameThread;
|
||||||
|
private Lobby _lobby;
|
||||||
|
private RagonSerializer _serializer;
|
||||||
|
private readonly Dictionary<uint, Player> _playersByPeers;
|
||||||
|
private readonly Dictionary<string, Player> _playersByIds;
|
||||||
|
|
||||||
|
public AuthorizationManager(IApplicationHandler provider, Application 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(ushort peerId, string key, string name, ReadOnlySpan<byte> additionalData)
|
||||||
|
{
|
||||||
|
if (_playersByPeers.ContainsKey(peerId))
|
||||||
|
{
|
||||||
|
_logger.Warn($"Connection already authorized {peerId}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dispatcher = _gameThread.Dispatcher;
|
||||||
|
|
||||||
|
_provider.OnAuthorizationRequest(key, name, additionalData.ToArray(),
|
||||||
|
(playerId, playerName) => { dispatcher.Dispatch(() => Accepted(peerId, playerId, playerName)); },
|
||||||
|
(errorCode) => { dispatcher.Dispatch(() => Rejected(peerId, errorCode)); });
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Accepted(ushort 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<ushort>(),
|
||||||
|
};
|
||||||
|
|
||||||
|
_playersByIds.Add(playerId, player);
|
||||||
|
_playersByPeers.Add(peerId, player);
|
||||||
|
|
||||||
|
var sendData = _serializer.ToArray();
|
||||||
|
_gameThread.SocketServer.Send(peerId, sendData, DeliveryType.Reliable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Rejected(ushort peerId, uint code)
|
||||||
|
{
|
||||||
|
_serializer.Clear();
|
||||||
|
_serializer.WriteOperation(RagonOperation.AUTHORIZED_FAILED);
|
||||||
|
_serializer.WriteInt((int) code);
|
||||||
|
|
||||||
|
var sendData = _serializer.ToArray();
|
||||||
|
_gameThread.SocketServer.Send(peerId, sendData, DeliveryType.Reliable);
|
||||||
|
_gameThread.SocketServer.Disconnect(peerId, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Cleanup(ushort peerId)
|
||||||
|
{
|
||||||
|
if (_playersByPeers.Remove(peerId, out var player))
|
||||||
|
_playersByIds.Remove(player.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Player? GetPlayer(ushort peerId)
|
||||||
|
{
|
||||||
|
if (_playersByPeers.TryGetValue(peerId, out var player))
|
||||||
|
return player;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Player GetPlayer(string playerId)
|
||||||
|
{
|
||||||
|
return _playersByIds[playerId];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,7 @@
|
|||||||
using NLog;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using NLog;
|
||||||
|
|
||||||
namespace Ragon.Core
|
namespace Ragon.Core
|
||||||
{
|
{
|
||||||
@@ -6,12 +9,14 @@ 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 = Configuration.Load(filePath);
|
||||||
app.Start();
|
var app = new Application(factory, configuration);
|
||||||
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+17
-7
@@ -2,14 +2,25 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using NLog;
|
using NLog;
|
||||||
using Logger = NLog.Logger;
|
|
||||||
|
|
||||||
namespace Ragon.Core
|
namespace Ragon.Core
|
||||||
{
|
{
|
||||||
public static class ConfigurationLoader
|
[Serializable]
|
||||||
|
public struct Configuration
|
||||||
{
|
{
|
||||||
|
public string Key;
|
||||||
|
public string Protocol;
|
||||||
|
public int StatisticsInterval;
|
||||||
|
public ushort SendRate;
|
||||||
|
public ushort Port;
|
||||||
|
public int SkipTimeout;
|
||||||
|
public int ReconnectTimeout;
|
||||||
|
public int MaxConnections;
|
||||||
|
public int MaxPlayersPerRoom;
|
||||||
|
public int MaxRooms;
|
||||||
|
|
||||||
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.22-rc";
|
||||||
|
|
||||||
private static void CopyrightInfo()
|
private static void CopyrightInfo()
|
||||||
{
|
{
|
||||||
@@ -18,11 +29,10 @@ namespace Ragon.Core
|
|||||||
_logger.Info($"OS: {Environment.OSVersion}");
|
_logger.Info($"OS: {Environment.OSVersion}");
|
||||||
_logger.Info($"Processors: {Environment.ProcessorCount}");
|
_logger.Info($"Processors: {Environment.ProcessorCount}");
|
||||||
_logger.Info($"Runtime Version: {Environment.Version}");
|
_logger.Info($"Runtime Version: {Environment.Version}");
|
||||||
|
|
||||||
_logger.Info("==================================");
|
_logger.Info("==================================");
|
||||||
_logger.Info("= =");
|
_logger.Info("| |");
|
||||||
_logger.Info($"={"Ragon".PadBoth(32)}=");
|
_logger.Info("| Ragon |");
|
||||||
_logger.Info("= =");
|
_logger.Info("| |");
|
||||||
_logger.Info("==================================");
|
_logger.Info("==================================");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Ragon.Core
|
|
||||||
{
|
|
||||||
[Serializable]
|
|
||||||
public struct Server
|
|
||||||
{
|
|
||||||
public ushort Port;
|
|
||||||
public ushort TickRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable]
|
|
||||||
public struct Configuration
|
|
||||||
{
|
|
||||||
public string Key;
|
|
||||||
public Server Server;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ragon.Core;
|
||||||
|
|
||||||
|
public class Dispatcher
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,248 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NLog;
|
||||||
using Ragon.Common;
|
using Ragon.Common;
|
||||||
|
|
||||||
namespace Ragon.Core;
|
namespace Ragon.Core;
|
||||||
|
|
||||||
public class Entity
|
public class Entity
|
||||||
{
|
{
|
||||||
private static int _idGenerator = 0;
|
private ILogger _logger = LogManager.GetCurrentClassLogger();
|
||||||
public int EntityId { get; private set; }
|
private GameRoom _room;
|
||||||
public uint OwnerId { get; private set; }
|
|
||||||
public ushort EntityType { get; private set; }
|
|
||||||
public RagonAuthority Authority { get; private set; }
|
|
||||||
public EntityState State { get; private set; }
|
|
||||||
|
|
||||||
public Entity(uint ownerId, ushort entityType, RagonAuthority stateAuthority, RagonAuthority eventAuthority)
|
private static ushort _idGenerator = 0;
|
||||||
|
public ushort EntityId { get; private set; }
|
||||||
|
public ushort StaticId { get; private set; }
|
||||||
|
public ushort EntityType { get; private set; }
|
||||||
|
public ushort OwnerId { get; private set; }
|
||||||
|
public byte[] Payload { get; private set; }
|
||||||
|
public RagonAuthority Authority { get; private set; }
|
||||||
|
|
||||||
|
private List<EntityProperty> _properties;
|
||||||
|
private List<EntityEvent> _bufferedEvents;
|
||||||
|
|
||||||
|
public Entity(GameRoom room, ushort ownerId, ushort entityType, ushort staticId, RagonAuthority eventAuthority)
|
||||||
{
|
{
|
||||||
OwnerId = ownerId;
|
OwnerId = ownerId;
|
||||||
|
StaticId = staticId;
|
||||||
EntityType = entityType;
|
EntityType = entityType;
|
||||||
EntityId = _idGenerator++;
|
EntityId = _idGenerator++;
|
||||||
State = new EntityState(stateAuthority);
|
Payload = Array.Empty<byte>();
|
||||||
Authority = eventAuthority;
|
Authority = eventAuthority;
|
||||||
|
|
||||||
|
_room = room;
|
||||||
|
_properties = new List<EntityProperty>();
|
||||||
|
_bufferedEvents = new List<EntityEvent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPayload(byte[] payload)
|
||||||
|
{
|
||||||
|
Payload = payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetOwner(ushort ownerId)
|
||||||
|
{
|
||||||
|
OwnerId = ownerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddProperty(EntityProperty property)
|
||||||
|
{
|
||||||
|
_properties.Add(property);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReplicateEvent(ushort peerId, ushort eventId, ReadOnlySpan<byte> payload, RagonReplicationMode eventMode, RagonTarget targetMode)
|
||||||
|
{
|
||||||
|
if (Authority == RagonAuthority.OwnerOnly && OwnerId != peerId)
|
||||||
|
{
|
||||||
|
_logger.Warn($"Player have not enought authority for event with Id {eventId}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventMode == RagonReplicationMode.Buffered && targetMode != RagonTarget.Owner)
|
||||||
|
{
|
||||||
|
var bufferedEvent = new EntityEvent()
|
||||||
|
{
|
||||||
|
EventData = payload.ToArray(),
|
||||||
|
Target = targetMode,
|
||||||
|
EventId = eventId,
|
||||||
|
PeerId = peerId,
|
||||||
|
};
|
||||||
|
_bufferedEvents.Add(bufferedEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
var serializer = _room.GetSharedSerializer();
|
||||||
|
|
||||||
|
serializer.Clear();
|
||||||
|
serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
|
||||||
|
serializer.WriteUShort(eventId);
|
||||||
|
serializer.WriteUShort(peerId);
|
||||||
|
serializer.WriteByte((byte) eventMode);
|
||||||
|
serializer.WriteUShort(EntityId);
|
||||||
|
serializer.WriteData(ref payload);
|
||||||
|
|
||||||
|
var sendData = serializer.ToArray();
|
||||||
|
Send(targetMode, sendData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReadState(uint peerId, RagonSerializer serializer)
|
||||||
|
{
|
||||||
|
if (OwnerId != peerId)
|
||||||
|
{
|
||||||
|
_logger.Warn($"Not owner can't change properties of object {EntityId}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < _properties.Count; i++)
|
||||||
|
{
|
||||||
|
if (serializer.ReadBool())
|
||||||
|
{
|
||||||
|
var property = _properties[i];
|
||||||
|
var size = property.Size;
|
||||||
|
if (!property.IsFixed)
|
||||||
|
size = serializer.ReadUShort();
|
||||||
|
|
||||||
|
if (size > property.Capacity)
|
||||||
|
{
|
||||||
|
_logger.Warn($"Property {i} payload too large, size: {size}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var propertyPayload = serializer.ReadData(size);
|
||||||
|
property.Write(ref propertyPayload);
|
||||||
|
property.Size = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteProperties(RagonSerializer serializer)
|
||||||
|
{
|
||||||
|
serializer.WriteUShort(EntityId);
|
||||||
|
|
||||||
|
for (int propertyIndex = 0; propertyIndex < _properties.Count; propertyIndex++)
|
||||||
|
{
|
||||||
|
var property = _properties[propertyIndex];
|
||||||
|
if (property.IsDirty)
|
||||||
|
{
|
||||||
|
serializer.WriteBool(true);
|
||||||
|
var span = serializer.GetWritableData(property.Size);
|
||||||
|
var data = property.Read();
|
||||||
|
data.CopyTo(span);
|
||||||
|
property.Clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
serializer.WriteBool(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteSnapshot(RagonSerializer serializer)
|
||||||
|
{
|
||||||
|
for (int propertyIndex = 0; propertyIndex < _properties.Count; propertyIndex++)
|
||||||
|
{
|
||||||
|
var property = _properties[propertyIndex];
|
||||||
|
var hasPayload = property.IsFixed || property.Size > 0 && !property.IsFixed;
|
||||||
|
if (hasPayload)
|
||||||
|
{
|
||||||
|
serializer.WriteBool(true);
|
||||||
|
var span = serializer.GetWritableData(property.Size);
|
||||||
|
var data = property.Read();
|
||||||
|
data.CopyTo(span);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
serializer.WriteBool(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RestoreBufferedEvents(ushort peerId)
|
||||||
|
{
|
||||||
|
var serializer = _room.GetSharedSerializer();
|
||||||
|
foreach (var bufferedEvent in _bufferedEvents)
|
||||||
|
{
|
||||||
|
serializer.Clear();
|
||||||
|
serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
|
||||||
|
serializer.WriteUShort(bufferedEvent.EventId);
|
||||||
|
serializer.WriteUShort(bufferedEvent.PeerId);
|
||||||
|
serializer.WriteByte((byte) RagonReplicationMode.Server);
|
||||||
|
serializer.WriteUShort(EntityId);
|
||||||
|
|
||||||
|
ReadOnlySpan<byte> data = bufferedEvent.EventData.AsSpan();
|
||||||
|
serializer.WriteData(ref data);
|
||||||
|
|
||||||
|
var sendData = serializer.ToArray();
|
||||||
|
_room.Send(peerId, sendData, DeliveryType.Reliable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Create()
|
||||||
|
{
|
||||||
|
var serializer = _room.GetSharedSerializer();
|
||||||
|
|
||||||
|
serializer.Clear();
|
||||||
|
serializer.WriteOperation(RagonOperation.CREATE_ENTITY);
|
||||||
|
serializer.WriteUShort(EntityType);
|
||||||
|
serializer.WriteUShort(EntityId);
|
||||||
|
serializer.WriteUShort(OwnerId);
|
||||||
|
|
||||||
|
ReadOnlySpan<byte> entityPayload = Payload.AsSpan();
|
||||||
|
serializer.WriteUShort((ushort) entityPayload.Length);
|
||||||
|
serializer.WriteData(ref entityPayload);
|
||||||
|
|
||||||
|
var sendData = serializer.ToArray();
|
||||||
|
_room.BroadcastToReady(sendData, DeliveryType.Reliable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Destroy(ReadOnlySpan<byte> payload)
|
||||||
|
{
|
||||||
|
var serializer = _room.GetSharedSerializer();
|
||||||
|
serializer.Clear();
|
||||||
|
serializer.WriteOperation(RagonOperation.DESTROY_ENTITY);
|
||||||
|
serializer.WriteInt(EntityId);
|
||||||
|
serializer.WriteUShort((ushort) payload.Length);
|
||||||
|
serializer.WriteData(ref payload);
|
||||||
|
|
||||||
|
var sendData = serializer.ToArray();
|
||||||
|
_room.BroadcastToReady(sendData, DeliveryType.Reliable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Send(RagonTarget targetMode, byte[] sendData)
|
||||||
|
{
|
||||||
|
switch (targetMode)
|
||||||
|
{
|
||||||
|
case RagonTarget.Owner:
|
||||||
|
{
|
||||||
|
_room.Send(OwnerId, sendData, DeliveryType.Reliable);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RagonTarget.ExceptOwner:
|
||||||
|
{
|
||||||
|
_room.BroadcastToReady(sendData, new [] { OwnerId }, DeliveryType.Reliable);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RagonTarget.All:
|
||||||
|
{
|
||||||
|
_room.BroadcastToReady(sendData, DeliveryType.Reliable);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ProcessEvent(Player player, RagonSerializer reader)
|
||||||
|
{
|
||||||
|
var eventId = reader.ReadUShort();
|
||||||
|
var eventMode = (RagonReplicationMode) reader.ReadByte();
|
||||||
|
var targetMode = (RagonTarget) reader.ReadByte();
|
||||||
|
var payloadData = reader.ReadData(reader.Size);
|
||||||
|
|
||||||
|
Span<byte> payloadRaw = stackalloc byte[reader.Size];
|
||||||
|
ReadOnlySpan<byte> payload = payloadRaw;
|
||||||
|
payloadData.CopyTo(payloadRaw);
|
||||||
|
|
||||||
|
if (_room.Plugin.InternalHandle(player.PeerId, EntityId, eventId, ref payload))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ReplicateEvent(player.PeerId, eventId, payload, eventMode, targetMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using Ragon.Common;
|
||||||
|
|
||||||
|
namespace Ragon.Core;
|
||||||
|
|
||||||
|
public class EntityEvent
|
||||||
|
{
|
||||||
|
public ushort PeerId { get; set; }
|
||||||
|
public ushort EventId { get; set; }
|
||||||
|
public byte[] EventData { get; set; }
|
||||||
|
public RagonTarget Target { set; get; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using Ragon.Common;
|
||||||
|
|
||||||
|
namespace Ragon.Core;
|
||||||
|
|
||||||
|
public class EntityProperty
|
||||||
|
{
|
||||||
|
public int Size { get; set; }
|
||||||
|
public int Capacity { get; set; }
|
||||||
|
public bool IsDirty { get; private set; }
|
||||||
|
public bool IsFixed { get; private set; }
|
||||||
|
private byte[] _data;
|
||||||
|
|
||||||
|
public EntityProperty(int size, bool isFixed)
|
||||||
|
{
|
||||||
|
Capacity = 512;
|
||||||
|
Size = size;
|
||||||
|
IsFixed = isFixed;
|
||||||
|
IsDirty = true;
|
||||||
|
|
||||||
|
_data = new byte[Capacity];
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlySpan<byte> Read()
|
||||||
|
{
|
||||||
|
var dataSpan = _data.AsSpan();
|
||||||
|
|
||||||
|
return dataSpan.Slice(0, Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(ref ReadOnlySpan<byte> src)
|
||||||
|
{
|
||||||
|
src.CopyTo(_data);
|
||||||
|
IsDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
IsDirty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using Ragon.Common;
|
|
||||||
|
|
||||||
namespace Ragon.Core;
|
|
||||||
|
|
||||||
public class EntityState
|
|
||||||
{
|
|
||||||
public bool isDirty { get; private set; }
|
|
||||||
public RagonAuthority Authority { get; private set; }
|
|
||||||
|
|
||||||
public byte[] Data
|
|
||||||
{
|
|
||||||
get => Data;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
Data = value;
|
|
||||||
isDirty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public EntityState(RagonAuthority ragonAuthority)
|
|
||||||
{
|
|
||||||
Authority = ragonAuthority;
|
|
||||||
isDirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
isDirty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Ragon.Core
|
|
||||||
{
|
|
||||||
public static class StringExtensions
|
|
||||||
{
|
|
||||||
public static string PadBoth(this string str, int length)
|
|
||||||
{
|
|
||||||
int spaces = length - str.Length;
|
|
||||||
int padLeft = spaces / 2 + str.Length;
|
|
||||||
return str.PadLeft(padLeft).PadRight(length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Ragon.Core
|
|
||||||
{
|
|
||||||
public static class ValueExtensions
|
|
||||||
{
|
|
||||||
public static T Clamp<T>(this T val, T min, T max) where T : IComparable<T>
|
|
||||||
{
|
|
||||||
if (val.CompareTo(min) < 0) return min;
|
|
||||||
else if (val.CompareTo(max) > 0) return max;
|
|
||||||
else return val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Executable
+503
@@ -0,0 +1,503 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using Ragon.Common;
|
||||||
|
|
||||||
|
namespace Ragon.Core
|
||||||
|
{
|
||||||
|
public class GameRoom
|
||||||
|
{
|
||||||
|
public int PlayersMin { get; private set; }
|
||||||
|
public int PlayersMax { get; private set; }
|
||||||
|
public int PlayersCount => _players.Count;
|
||||||
|
public int EntitiesCount => _entities.Count;
|
||||||
|
public string Id { get; private set; }
|
||||||
|
public string Map { get; private set; }
|
||||||
|
public PluginBase Plugin => _plugin;
|
||||||
|
private ILogger _logger = LogManager.GetCurrentClassLogger();
|
||||||
|
private Dictionary<ushort, Player> _players = new();
|
||||||
|
private Dictionary<int, Entity> _entities = new();
|
||||||
|
private ushort _owner;
|
||||||
|
|
||||||
|
private readonly ISocketServer _socketServer;
|
||||||
|
private readonly Scheduler _scheduler;
|
||||||
|
private readonly Application _application;
|
||||||
|
private readonly PluginBase _plugin;
|
||||||
|
private readonly RagonSerializer _writer = new(512);
|
||||||
|
|
||||||
|
// Cache
|
||||||
|
private ushort[] _readyPlayers = Array.Empty<ushort>();
|
||||||
|
private ushort[] _allPlayers = Array.Empty<ushort>();
|
||||||
|
private Entity[] _entitiesAll = Array.Empty<Entity>();
|
||||||
|
private HashSet<Entity> _entitiesDirtySet = new HashSet<Entity>();
|
||||||
|
private List<Entity> _entitiesDirty = new List<Entity>();
|
||||||
|
private List<ushort> _peersCache = new List<ushort>();
|
||||||
|
private List<ushort> _awaitingPeers = new List<ushort>();
|
||||||
|
|
||||||
|
public Player GetPlayerByPeer(ushort peerId) => _players[peerId];
|
||||||
|
|
||||||
|
public Player GetPlayerById(string id) => _players.Values.FirstOrDefault(p => p.Id == id)!;
|
||||||
|
|
||||||
|
public Entity GetEntityById(int entityId) => _entities[entityId];
|
||||||
|
|
||||||
|
public Player GetOwner() => _players[_owner];
|
||||||
|
|
||||||
|
public RagonSerializer GetSharedSerializer() => _writer;
|
||||||
|
|
||||||
|
public GameRoom(Application application, PluginBase pluginBase, string roomId, string map, int min, int max)
|
||||||
|
{
|
||||||
|
_application = application;
|
||||||
|
_socketServer = application.SocketServer;
|
||||||
|
_plugin = pluginBase;
|
||||||
|
_scheduler = new Scheduler();
|
||||||
|
|
||||||
|
Map = map;
|
||||||
|
PlayersMin = min;
|
||||||
|
PlayersMax = max;
|
||||||
|
Id = roomId;
|
||||||
|
|
||||||
|
_plugin.Attach(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddPlayer(Player player, ReadOnlySpan<byte> payload)
|
||||||
|
{
|
||||||
|
if (_players.Count == 0)
|
||||||
|
{
|
||||||
|
_owner = player.PeerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
_players.Add(player.PeerId, player);
|
||||||
|
_allPlayers = _players.Select(p => p.Key).ToArray();
|
||||||
|
|
||||||
|
AcceptPlayer(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemovePlayer(ushort 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();
|
||||||
|
|
||||||
|
_plugin.OnPlayerLeaved(player);
|
||||||
|
|
||||||
|
BroadcastLeaved(player);
|
||||||
|
|
||||||
|
if (_allPlayers.Length > 0 && player.PeerId == _owner)
|
||||||
|
{
|
||||||
|
var nextOwnerId = _allPlayers[0];
|
||||||
|
var nextOwner = _players[nextOwnerId];
|
||||||
|
|
||||||
|
_owner = nextOwnerId;
|
||||||
|
|
||||||
|
BroadcastNewOwner(player, nextOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
_entitiesAll = _entities.Values.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ProcessEvent(ushort peerId, RagonOperation operation, RagonSerializer reader)
|
||||||
|
{
|
||||||
|
switch (operation)
|
||||||
|
{
|
||||||
|
case RagonOperation.LOAD_SCENE:
|
||||||
|
{
|
||||||
|
var sceneName = reader.ReadString();
|
||||||
|
BroadcastNewScene(sceneName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RagonOperation.SCENE_LOADED:
|
||||||
|
{
|
||||||
|
var player = _players[peerId];
|
||||||
|
if (peerId == _owner)
|
||||||
|
{
|
||||||
|
var statics = reader.ReadUShort();
|
||||||
|
for (var staticIndex = 0; staticIndex < statics; staticIndex++)
|
||||||
|
{
|
||||||
|
var entityType = reader.ReadUShort();
|
||||||
|
var entityAuthority = (RagonAuthority) reader.ReadByte();
|
||||||
|
var staticId = reader.ReadUShort();
|
||||||
|
var propertiesCount = reader.ReadUShort();
|
||||||
|
|
||||||
|
var entity = new Entity(this, player.PeerId, entityType, staticId, entityAuthority);
|
||||||
|
for (var propertyIndex = 0; propertyIndex < propertiesCount; propertyIndex++)
|
||||||
|
{
|
||||||
|
var propertyType = reader.ReadBool();
|
||||||
|
var propertySize = reader.ReadUShort();
|
||||||
|
entity.AddProperty(new EntityProperty(propertySize, propertyType));
|
||||||
|
}
|
||||||
|
|
||||||
|
player.AttachEntity(entity);
|
||||||
|
AttachEntity(player, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
_entitiesAll = _entities.Values.ToArray();
|
||||||
|
_logger.Trace($"Scene entities: {statics}");
|
||||||
|
|
||||||
|
_awaitingPeers.Add(peerId);
|
||||||
|
|
||||||
|
foreach (var peer in _awaitingPeers)
|
||||||
|
{
|
||||||
|
var joinedPlayer = _players[peer];
|
||||||
|
joinedPlayer.IsLoaded = true;
|
||||||
|
|
||||||
|
_plugin.OnPlayerJoined(joinedPlayer);
|
||||||
|
_logger.Trace($"[{_owner}][{peer}] Player {joinedPlayer.Id} restored");
|
||||||
|
|
||||||
|
BroadcastJoined(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
_readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray();
|
||||||
|
|
||||||
|
BroadcastSnapshot(_awaitingPeers.ToArray());
|
||||||
|
|
||||||
|
_awaitingPeers.Clear();
|
||||||
|
}
|
||||||
|
else if (GetOwner().IsLoaded)
|
||||||
|
{
|
||||||
|
_logger.Trace($"[{_owner}][{peerId}] Player {player.Id} restored instantly");
|
||||||
|
player.IsLoaded = true;
|
||||||
|
|
||||||
|
BroadcastJoined(player);
|
||||||
|
|
||||||
|
_readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray();
|
||||||
|
_plugin.OnPlayerJoined(player);
|
||||||
|
|
||||||
|
BroadcastSnapshot(new[] {peerId});
|
||||||
|
|
||||||
|
foreach (var (key, value) in _entities)
|
||||||
|
value.RestoreBufferedEvents(peerId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Trace($"[{_owner}][{peerId}] Player {player.Id} waiting");
|
||||||
|
_awaitingPeers.Add(peerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RagonOperation.REPLICATE_ENTITY_STATE:
|
||||||
|
{
|
||||||
|
var entitiesCount = reader.ReadUShort();
|
||||||
|
for (var entityIndex = 0; entityIndex < entitiesCount; entityIndex++)
|
||||||
|
{
|
||||||
|
var entityId = reader.ReadUShort();
|
||||||
|
if (_entities.TryGetValue(entityId, out var entity))
|
||||||
|
{
|
||||||
|
entity.ReadState(peerId, reader);
|
||||||
|
|
||||||
|
if (_entitiesDirtySet.Add(entity))
|
||||||
|
_entitiesDirty.Add(entity);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Error($"Entity with Id {entityId} not found, replication interrupted");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RagonOperation.REPLICATE_ENTITY_EVENT:
|
||||||
|
{
|
||||||
|
var entityId = reader.ReadUShort();
|
||||||
|
if (!_entities.TryGetValue(entityId, out var ent))
|
||||||
|
{
|
||||||
|
_logger.Warn($"Entity not found for event with Id {entityId}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var player = _players[peerId];
|
||||||
|
ent.ProcessEvent(player, reader);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RagonOperation.CREATE_ENTITY:
|
||||||
|
{
|
||||||
|
var entityType = reader.ReadUShort();
|
||||||
|
var eventAuthority = (RagonAuthority) reader.ReadByte();
|
||||||
|
var propertiesCount = reader.ReadUShort();
|
||||||
|
|
||||||
|
_logger.Trace($"[{peerId}] Create Entity {entityType}");
|
||||||
|
|
||||||
|
var player = _players[peerId];
|
||||||
|
var entity = new Entity(this, player.PeerId, entityType, 0, eventAuthority);
|
||||||
|
for (var i = 0; i < propertiesCount; i++)
|
||||||
|
{
|
||||||
|
var propertyType = reader.ReadBool();
|
||||||
|
var propertySize = reader.ReadUShort();
|
||||||
|
entity.AddProperty(new EntityProperty(propertySize, propertyType));
|
||||||
|
}
|
||||||
|
|
||||||
|
var entityPayload = reader.ReadData(reader.Size);
|
||||||
|
entity.SetPayload(entityPayload.ToArray());
|
||||||
|
|
||||||
|
if (_plugin.OnEntityCreated(player, entity))
|
||||||
|
return;
|
||||||
|
|
||||||
|
player.AttachEntity(entity);
|
||||||
|
AttachEntity(player, entity);
|
||||||
|
|
||||||
|
entity.Create();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RagonOperation.DESTROY_ENTITY:
|
||||||
|
{
|
||||||
|
var entityId = reader.ReadInt();
|
||||||
|
if (_entities.TryGetValue(entityId, out var entity))
|
||||||
|
{
|
||||||
|
var player = _players[peerId];
|
||||||
|
var payload = reader.ReadData(reader.Size);
|
||||||
|
DetachEntity(player, entity, payload);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Tick(float deltaTime)
|
||||||
|
{
|
||||||
|
_scheduler.Tick(deltaTime);
|
||||||
|
BroadcastState();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnStart()
|
||||||
|
{
|
||||||
|
_plugin.OnStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnStop()
|
||||||
|
{
|
||||||
|
foreach (var peerId in _allPlayers)
|
||||||
|
_application.SocketServer.Disconnect(peerId, 0);
|
||||||
|
|
||||||
|
_plugin.OnStop();
|
||||||
|
_plugin.Detach();
|
||||||
|
_scheduler.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AttachEntity(Player player, Entity entity)
|
||||||
|
{
|
||||||
|
_entities.Add(entity.EntityId, entity);
|
||||||
|
_entitiesAll = _entities.Values.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DetachEntity(Player player, Entity entity, ReadOnlySpan<byte> payload)
|
||||||
|
{
|
||||||
|
if (entity.Authority == RagonAuthority.OwnerOnly && entity.OwnerId != player.PeerId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_plugin.OnEntityDestroyed(player, entity))
|
||||||
|
return;
|
||||||
|
|
||||||
|
player.DetachEntity(entity);
|
||||||
|
entity.Destroy(payload);
|
||||||
|
|
||||||
|
_entities.Remove(entity.EntityId);
|
||||||
|
_entitiesAll = _entities.Values.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BroadcastNewOwner(Player prev, Player next)
|
||||||
|
{
|
||||||
|
var entitiesToUpdate = prev.Entities.Where(e => e.StaticId > 0).ToArray();
|
||||||
|
|
||||||
|
_writer.Clear();
|
||||||
|
_writer.WriteOperation(RagonOperation.OWNERSHIP_CHANGED);
|
||||||
|
_writer.WriteString(next.Id);
|
||||||
|
_writer.WriteUShort((ushort) entitiesToUpdate.Length);
|
||||||
|
foreach (var entity in entitiesToUpdate)
|
||||||
|
{
|
||||||
|
_writer.WriteUShort(entity.EntityId);
|
||||||
|
entity.SetOwner((ushort) next.PeerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
BroadcastToReady(_writer, DeliveryType.Reliable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BroadcastJoined(Player player)
|
||||||
|
{
|
||||||
|
_writer.Clear();
|
||||||
|
_writer.WriteOperation(RagonOperation.PLAYER_JOINED);
|
||||||
|
_writer.WriteUShort(player.PeerId);
|
||||||
|
_writer.WriteString(player.Id);
|
||||||
|
_writer.WriteString(player.PlayerName);
|
||||||
|
|
||||||
|
BroadcastToReady(_writer, new [] { player.PeerId }, DeliveryType.Reliable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BroadcastLeaved(Player player)
|
||||||
|
{
|
||||||
|
_writer.Clear();
|
||||||
|
_writer.WriteOperation(RagonOperation.PLAYER_LEAVED);
|
||||||
|
_writer.WriteString(player.Id);
|
||||||
|
|
||||||
|
var entitiesToDelete = player.Entities.Where(e => e.StaticId == 0).ToArray();
|
||||||
|
_writer.WriteUShort((ushort) entitiesToDelete.Length);
|
||||||
|
foreach (var entity in entitiesToDelete)
|
||||||
|
{
|
||||||
|
_writer.WriteUShort(entity.EntityId);
|
||||||
|
_entities.Remove(entity.EntityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
BroadcastToReady(_writer, DeliveryType.Reliable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BroadcastSnapshot(ushort[] peersIds)
|
||||||
|
{
|
||||||
|
_writer.Clear();
|
||||||
|
_writer.WriteOperation(RagonOperation.SNAPSHOT);
|
||||||
|
_writer.WriteUShort((ushort) _readyPlayers.Length);
|
||||||
|
foreach (var playerPeerId in _readyPlayers)
|
||||||
|
{
|
||||||
|
_writer.WriteUShort(playerPeerId);
|
||||||
|
_writer.WriteString(_players[playerPeerId].Id);
|
||||||
|
_writer.WriteString(_players[playerPeerId].PlayerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
var dynamicEntities = _entitiesAll.Where(e => e.StaticId == 0).ToArray();
|
||||||
|
var dynamicEntitiesCount = (ushort) dynamicEntities.Length;
|
||||||
|
_writer.WriteUShort(dynamicEntitiesCount);
|
||||||
|
foreach (var entity in dynamicEntities)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<byte> payload = entity.Payload.AsSpan();
|
||||||
|
|
||||||
|
_writer.WriteUShort(entity.EntityType);
|
||||||
|
_writer.WriteUShort(entity.EntityId);
|
||||||
|
_writer.WriteUShort(entity.OwnerId);
|
||||||
|
_writer.WriteUShort((ushort) payload.Length);
|
||||||
|
_writer.WriteData(ref payload);
|
||||||
|
|
||||||
|
entity.WriteSnapshot(_writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
var staticEntities = _entitiesAll.Where(e => e.StaticId != 0).ToArray();
|
||||||
|
var staticEntitiesCount = (ushort) staticEntities.Length;
|
||||||
|
_writer.WriteUShort(staticEntitiesCount);
|
||||||
|
foreach (var entity in staticEntities)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<byte> payload = entity.Payload.AsSpan();
|
||||||
|
|
||||||
|
_writer.WriteUShort(entity.EntityType);
|
||||||
|
_writer.WriteUShort(entity.EntityId);
|
||||||
|
_writer.WriteUShort(entity.StaticId);
|
||||||
|
_writer.WriteUShort(entity.OwnerId);
|
||||||
|
_writer.WriteUShort((ushort) payload.Length);
|
||||||
|
_writer.WriteData(ref payload);
|
||||||
|
|
||||||
|
entity.WriteSnapshot(_writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
var sendData = _writer.ToArray();
|
||||||
|
Broadcast(peersIds, sendData, DeliveryType.Reliable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BroadcastState()
|
||||||
|
{
|
||||||
|
var entities = (ushort) _entitiesDirty.Count;
|
||||||
|
if (entities > 0)
|
||||||
|
{
|
||||||
|
_writer.Clear();
|
||||||
|
_writer.WriteOperation(RagonOperation.REPLICATE_ENTITY_STATE);
|
||||||
|
_writer.WriteUShort(entities);
|
||||||
|
|
||||||
|
foreach (var entity in _entitiesDirty)
|
||||||
|
entity.WriteProperties(_writer);
|
||||||
|
|
||||||
|
_entitiesDirty.Clear();
|
||||||
|
_entitiesDirtySet.Clear();
|
||||||
|
|
||||||
|
BroadcastToReady(_writer, DeliveryType.Reliable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AcceptPlayer(Player player)
|
||||||
|
{
|
||||||
|
_writer.Clear();
|
||||||
|
_writer.WriteOperation(RagonOperation.JOIN_SUCCESS);
|
||||||
|
_writer.WriteString(Id);
|
||||||
|
_writer.WriteString(player.Id);
|
||||||
|
_writer.WriteString(GetOwner().Id);
|
||||||
|
_writer.WriteUShort((ushort) PlayersMin);
|
||||||
|
_writer.WriteUShort((ushort) PlayersMax);
|
||||||
|
_writer.WriteString(Map);
|
||||||
|
|
||||||
|
Send(player.PeerId, _writer, DeliveryType.Reliable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BroadcastNewScene(string sceneName)
|
||||||
|
{
|
||||||
|
_readyPlayers = Array.Empty<ushort>();
|
||||||
|
_entitiesAll = Array.Empty<Entity>();
|
||||||
|
_entities.Clear();
|
||||||
|
|
||||||
|
_writer.Clear();
|
||||||
|
_writer.WriteOperation(RagonOperation.LOAD_SCENE);
|
||||||
|
_writer.WriteString(sceneName);
|
||||||
|
|
||||||
|
BroadcastToAll(_writer, DeliveryType.Reliable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Send(ushort peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
|
||||||
|
{
|
||||||
|
_socketServer.Send(peerId, rawData, deliveryType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Send(ushort peerId, RagonSerializer writer, DeliveryType deliveryType = DeliveryType.Unreliable)
|
||||||
|
{
|
||||||
|
var sendData = writer.ToArray();
|
||||||
|
_socketServer.Send(peerId, sendData, deliveryType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Broadcast(ushort[] peersIds, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
|
||||||
|
{
|
||||||
|
_socketServer.Broadcast(peersIds, rawData, deliveryType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BroadcastToAll(byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
|
||||||
|
{
|
||||||
|
_socketServer.Broadcast(_allPlayers, rawData, deliveryType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BroadcastToAll(RagonSerializer writer, DeliveryType deliveryType = DeliveryType.Unreliable)
|
||||||
|
{
|
||||||
|
var sendData = writer.ToArray();
|
||||||
|
_socketServer.Broadcast(_allPlayers, sendData, deliveryType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BroadcastToReady(byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
|
||||||
|
{
|
||||||
|
_socketServer.Broadcast(_readyPlayers, rawData, deliveryType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BroadcastToReady(RagonSerializer writer, DeliveryType deliveryType = DeliveryType.Unreliable)
|
||||||
|
{
|
||||||
|
var sendData = writer.ToArray();
|
||||||
|
_socketServer.Broadcast(_readyPlayers, sendData, deliveryType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BroadcastToReady(byte[] rawData, ushort[] excludePeersIds, DeliveryType deliveryType = DeliveryType.Unreliable)
|
||||||
|
{
|
||||||
|
_peersCache.Clear();
|
||||||
|
foreach (var playerPeerId in _readyPlayers)
|
||||||
|
{
|
||||||
|
foreach (var excludePeersId in excludePeersIds)
|
||||||
|
{
|
||||||
|
if (playerPeerId != excludePeersId)
|
||||||
|
{
|
||||||
|
_peersCache.Add(playerPeerId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var peersIds = _peersCache.ToArray();
|
||||||
|
_socketServer.Broadcast(peersIds, rawData, deliveryType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BroadcastToReady(RagonSerializer writer, ushort[] excludePeersIds, DeliveryType deliveryType = DeliveryType.Unreliable)
|
||||||
|
{
|
||||||
|
var sendData = writer.ToArray();
|
||||||
|
BroadcastToReady(sendData, excludePeersIds, deliveryType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ragon.Core;
|
||||||
|
|
||||||
|
public interface IApplicationHandler
|
||||||
|
{
|
||||||
|
Task OnAuthorizationRequest(string key, string playerName, byte[] additionalData, Action<string, string> Accept, Action<uint> Reject);
|
||||||
|
public void OnCustomEvent(ushort peerId, ReadOnlySpan<byte> payload);
|
||||||
|
public void OnJoin(ushort peerId);
|
||||||
|
public void OnLeave(ushort peerId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using Ragon.Common;
|
||||||
|
|
||||||
|
namespace Ragon.Core;
|
||||||
|
|
||||||
|
public class Lobby
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger = LogManager.GetCurrentClassLogger();
|
||||||
|
private readonly Application _application;
|
||||||
|
private readonly RagonSerializer _writer;
|
||||||
|
private readonly RoomManager _roomManager;
|
||||||
|
private readonly AuthorizationManager _authorizationManager;
|
||||||
|
|
||||||
|
public AuthorizationManager AuthorizationManager => _authorizationManager;
|
||||||
|
|
||||||
|
public Lobby(IApplicationHandler provider, RoomManager manager, Application application)
|
||||||
|
{
|
||||||
|
_roomManager = manager;
|
||||||
|
_application = application;
|
||||||
|
_writer = new RagonSerializer();
|
||||||
|
_authorizationManager = new AuthorizationManager(provider, application, this, _writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ProcessEvent(ushort peerId, RagonOperation op, RagonSerializer reader)
|
||||||
|
{
|
||||||
|
var player = _authorizationManager.GetPlayer(peerId);
|
||||||
|
if (op == RagonOperation.AUTHORIZE)
|
||||||
|
{
|
||||||
|
if (player != null)
|
||||||
|
{
|
||||||
|
_logger.Warn("Player already authorized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = reader.ReadString();
|
||||||
|
var playerName = reader.ReadString();
|
||||||
|
var additionalData = reader.ReadData(reader.Size);
|
||||||
|
_authorizationManager.OnAuthorization(peerId, key, playerName, additionalData);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player == null)
|
||||||
|
{
|
||||||
|
_logger.Warn($"Peer not authorized {peerId} trying to {op}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case RagonOperation.JOIN_ROOM:
|
||||||
|
{
|
||||||
|
var roomId = reader.ReadString();
|
||||||
|
var exists = _roomManager.Rooms.Any(r => r.Id == roomId);
|
||||||
|
if (!exists)
|
||||||
|
{
|
||||||
|
_writer.Clear();
|
||||||
|
_writer.WriteOperation(RagonOperation.JOIN_FAILED);
|
||||||
|
_writer.WriteString($"Room with id {roomId} not exists");
|
||||||
|
var sendData = _writer.ToArray();
|
||||||
|
_application.SocketServer.Send(peerId, sendData, DeliveryType.Reliable);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_roomManager.RoomsBySocket.ContainsKey(peerId))
|
||||||
|
_roomManager.Left(player, Array.Empty<byte>());
|
||||||
|
|
||||||
|
_roomManager.Join(player, roomId, Array.Empty<byte>());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RagonOperation.CREATE_ROOM:
|
||||||
|
{
|
||||||
|
var roomId = Guid.NewGuid().ToString();
|
||||||
|
var custom = reader.ReadBool();
|
||||||
|
if (custom)
|
||||||
|
{
|
||||||
|
roomId = reader.ReadString();
|
||||||
|
var exists = _roomManager.Rooms.Any(r => r.Id == roomId);
|
||||||
|
if (exists)
|
||||||
|
{
|
||||||
|
_writer.Clear();
|
||||||
|
_writer.WriteOperation(RagonOperation.JOIN_FAILED);
|
||||||
|
_writer.WriteString($"Room with id {roomId} already exists");
|
||||||
|
|
||||||
|
var sendData = _writer.ToArray();
|
||||||
|
_application.SocketServer.Send(peerId, sendData, DeliveryType.Reliable);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var roomProperties = new RagonRoomParameters();
|
||||||
|
roomProperties.Deserialize(reader);
|
||||||
|
|
||||||
|
if (_roomManager.RoomsBySocket.ContainsKey(peerId))
|
||||||
|
_roomManager.Left(player, Array.Empty<byte>());
|
||||||
|
|
||||||
|
_roomManager.Create(player, roomId, roomProperties, Array.Empty<byte>());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RagonOperation.JOIN_OR_CREATE_ROOM:
|
||||||
|
{
|
||||||
|
var roomId = Guid.NewGuid().ToString();
|
||||||
|
var roomProperties = new RagonRoomParameters();
|
||||||
|
roomProperties.Deserialize(reader);
|
||||||
|
|
||||||
|
if (_roomManager.RoomsBySocket.ContainsKey(peerId))
|
||||||
|
_roomManager.Left(player, Array.Empty<byte>());
|
||||||
|
|
||||||
|
_roomManager.JoinOrCreate(player, roomId, roomProperties, Array.Empty<byte>());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RagonOperation.LEAVE_ROOM:
|
||||||
|
{
|
||||||
|
_roomManager.Left(player, Array.Empty<byte>());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnDisconnected(ushort peerId)
|
||||||
|
{
|
||||||
|
_authorizationManager.Cleanup(peerId);
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+28
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ragon.Core
|
||||||
|
{
|
||||||
|
public class Player
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string PlayerName { get; set; }
|
||||||
|
public ushort PeerId { get; set; }
|
||||||
|
public bool IsLoaded { get; set; }
|
||||||
|
|
||||||
|
public List<Entity> Entities;
|
||||||
|
public List<ushort> EntitiesIds;
|
||||||
|
|
||||||
|
public void AttachEntity(Entity entity)
|
||||||
|
{
|
||||||
|
Entities.Add(entity);
|
||||||
|
EntitiesIds.Add((entity.EntityId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DetachEntity(Entity entity)
|
||||||
|
{
|
||||||
|
Entities.Remove(entity);
|
||||||
|
EntitiesIds.Remove(entity.EntityId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Ragon.Core
|
|
||||||
{
|
|
||||||
public class Player
|
|
||||||
{
|
|
||||||
public uint PeerId { get; set; }
|
|
||||||
public string PlayerName { get; set; }
|
|
||||||
public bool IsLoaded { get; set; }
|
|
||||||
|
|
||||||
public List<Entity> Entities;
|
|
||||||
public List<int> EntitiesIds;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,8 +3,6 @@ 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 IApplicationHandler CreateAuthorizationProvider(Configuration configuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,44 +1,43 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using NetStack.Serialization;
|
|
||||||
using NLog;
|
using NLog;
|
||||||
using Ragon.Common;
|
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 RagonSerializer _serializer = new();
|
||||||
|
|
||||||
protected Room Room { get; private set; }
|
protected GameRoom Room { 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;
|
Room = gameRoom;
|
||||||
|
|
||||||
_globalEvents.Clear();
|
_globalEvents.Clear();
|
||||||
_entityEvents.Clear();
|
_entityEvents.Clear();
|
||||||
}
|
}
|
||||||
public void Dispose()
|
|
||||||
|
public void Detach()
|
||||||
{
|
{
|
||||||
_globalEvents.Clear();
|
_globalEvents.Clear();
|
||||||
_entityEvents.Clear();
|
_entityEvents.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Subscribe<T>(ushort evntCode, Action<Player, T> action) where T : IRagonSerializable, new()
|
public void OnEvent<T>(ushort evntCode, Action<Player, T> action) where T : IRagonSerializable, new()
|
||||||
{
|
{
|
||||||
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,38 +46,35 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
_buffer.Clear();
|
_serializer.Clear();
|
||||||
_buffer.FromSpan(ref raw, raw.Length);
|
_serializer.FromSpan(ref raw);
|
||||||
data.Deserialize(_buffer);
|
data.Deserialize(_serializer);
|
||||||
action.Invoke(player, data);
|
action.Invoke(player, data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Subscribe(ushort evntCode, Action<Player> action)
|
public void OnEvent(ushort evntCode, Action<Player> action)
|
||||||
{
|
{
|
||||||
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 OnEvent<T>(Entity entity, ushort evntCode, Action<Player, Entity, T> action) where T : IRagonSerializable, new()
|
||||||
{
|
{
|
||||||
if (_entityEvents.ContainsKey(entity.EntityId))
|
if (_entityEvents.ContainsKey(entity.EntityId))
|
||||||
{
|
{
|
||||||
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,13 +83,13 @@ 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();
|
_serializer.Clear();
|
||||||
_buffer.FromSpan(ref raw, raw.Length);
|
_serializer.FromSpan(ref raw);
|
||||||
data.Deserialize(_buffer);
|
data.Deserialize(_serializer);
|
||||||
action.Invoke(player, ent, data);
|
action.Invoke(player, ent, data);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -107,40 +103,35 @@ 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.FromSpan(ref raw, raw.Length);
|
_serializer.Clear();
|
||||||
data.Deserialize(_buffer);
|
_serializer.FromSpan(ref raw);
|
||||||
|
data.Deserialize(_serializer);
|
||||||
action.Invoke(player, ent, data);
|
action.Invoke(player, ent, data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Subscribe(Entity entity, ushort evntCode, Action<Player, Entity> action)
|
public void OnEvent(Entity entity, ushort evntCode, Action<Player, Entity> action)
|
||||||
{
|
{
|
||||||
if (_entityEvents.ContainsKey(entity.EntityId))
|
if (_entityEvents.ContainsKey(entity.EntityId))
|
||||||
{
|
{
|
||||||
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,9 +149,10 @@ namespace Ragon.Core
|
|||||||
if (!_entityEvents[entityId].ContainsKey(evntCode))
|
if (!_entityEvents[entityId].ContainsKey(evntCode))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var player = Room.GetPlayerById(peerId);
|
// var player = Room.GetPlayerById(peerId);
|
||||||
var entity = Room.GetEntityById(entityId);
|
var entity = Room.GetEntityById(entityId);
|
||||||
_entityEvents[entityId][evntCode].Invoke(player, entity, ref payload);
|
|
||||||
|
// _entityEvents[entityId][evntCode].Invoke(player, entity, ref payload);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -169,85 +161,14 @@ namespace Ragon.Core
|
|||||||
{
|
{
|
||||||
if (_globalEvents.ContainsKey(evntCode))
|
if (_globalEvents.ContainsKey(evntCode))
|
||||||
{
|
{
|
||||||
var player = Room.GetPlayerById(peerId);
|
// var player = Room.GetPlayerById(peerId);
|
||||||
_globalEvents[evntCode].Invoke(player, ref payload);
|
// _globalEvents[evntCode].Invoke(player, ref payload);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendEvent(Player player, uint eventCode, IRagonSerializable payload)
|
|
||||||
{
|
|
||||||
_buffer.Clear();
|
|
||||||
payload.Serialize(_buffer);
|
|
||||||
|
|
||||||
var sendData = new byte[_buffer.Length + 4];
|
|
||||||
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);
|
|
||||||
|
|
||||||
RagonHeader.WriteUShort((ushort) RagonOperation.REPLICATE_EVENT, ref operationData);
|
|
||||||
RagonHeader.WriteUShort((ushort) eventCode, ref eventCodeData);
|
|
||||||
|
|
||||||
Room.Send(player.PeerId, sendData);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SendEvent(ushort eventCode, IRagonSerializable payload)
|
|
||||||
{
|
|
||||||
_buffer.Clear();
|
|
||||||
payload.Serialize(_buffer);
|
|
||||||
|
|
||||||
var sendData = new byte[_buffer.Length + 4];
|
|
||||||
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);
|
|
||||||
|
|
||||||
Room.Broadcast(sendData);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SendEntityEvent(Player player, Entity entity, IRagonSerializable payload)
|
|
||||||
{
|
|
||||||
_buffer.Clear();
|
|
||||||
payload.Serialize(_buffer);
|
|
||||||
|
|
||||||
var sendData = new byte[_buffer.Length + 6];
|
|
||||||
Span<byte> data = sendData.AsSpan();
|
|
||||||
Span<byte> operationData = data.Slice(0, 2);
|
|
||||||
Span<byte> entityData = data.Slice(2, 4);
|
|
||||||
|
|
||||||
RagonHeader.WriteUShort((ushort) RagonOperation.REPLICATE_EVENT, ref operationData);
|
|
||||||
RagonHeader.WriteInt(entity.EntityId, ref entityData);
|
|
||||||
|
|
||||||
Room.Send(player.PeerId, sendData);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SendEntityEvent(Entity entity, IRagonSerializable payload)
|
|
||||||
{
|
|
||||||
_buffer.Clear();
|
|
||||||
payload.Serialize(_buffer);
|
|
||||||
|
|
||||||
var sendData = new byte[_buffer.Length + 6];
|
|
||||||
Span<byte> data = sendData.AsSpan();
|
|
||||||
Span<byte> operationData = data.Slice(0, 2);
|
|
||||||
Span<byte> entityData = data.Slice(2, 4);
|
|
||||||
|
|
||||||
RagonHeader.WriteUShort((ushort) RagonOperation.REPLICATE_EVENT, ref operationData);
|
|
||||||
RagonHeader.WriteInt(entity.EntityId, ref entityData);
|
|
||||||
|
|
||||||
Room.Broadcast(sendData);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#region VIRTUAL
|
#region VIRTUAL
|
||||||
|
|
||||||
public virtual void OnPlayerJoined(Player player)
|
public virtual void OnPlayerJoined(Player player)
|
||||||
@@ -258,18 +179,18 @@ 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 OnEntityDestroyed(Player destoyer, Entity entity)
|
public virtual bool OnEntityCreated(Player player, Entity entity)
|
||||||
{
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool OnEntityDestroyed(Player player, Entity entity)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnStart()
|
public virtual void OnStart()
|
||||||
@@ -280,10 +201,6 @@ namespace Ragon.Core
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnTick(ulong ticks, float deltaTime)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using NLog;
|
||||||
|
using Ragon.Common;
|
||||||
|
|
||||||
|
namespace Ragon.Core;
|
||||||
|
|
||||||
|
public class RoomManager
|
||||||
|
{
|
||||||
|
private readonly Application _gameThread;
|
||||||
|
private readonly PluginFactory _factory;
|
||||||
|
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||||
|
private readonly List<GameRoom> _rooms = new();
|
||||||
|
private readonly Dictionary<uint, GameRoom> _roomsBySocket;
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<uint, GameRoom> RoomsBySocket => _roomsBySocket;
|
||||||
|
public IReadOnlyList<GameRoom> Rooms => _rooms;
|
||||||
|
|
||||||
|
public RoomManager(PluginFactory factory, Application gameThread)
|
||||||
|
{
|
||||||
|
_gameThread = gameThread;
|
||||||
|
_factory = factory;
|
||||||
|
_roomsBySocket = new Dictionary<uint, GameRoom>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Join(Player player, string roomId, byte[] payload)
|
||||||
|
{
|
||||||
|
_logger.Trace($"Player ({player.PlayerName}|{player.Id}) joined to room with Id {roomId}");
|
||||||
|
|
||||||
|
if (_rooms.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var existRoom in _rooms)
|
||||||
|
{
|
||||||
|
if (existRoom.Id == roomId && existRoom.PlayersCount < existRoom.PlayersMax)
|
||||||
|
{
|
||||||
|
existRoom.AddPlayer(player, payload);
|
||||||
|
_roomsBySocket.Add(player.PeerId, existRoom);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Create(Player creator, string roomId, RagonRoomParameters parameters, byte[] payload)
|
||||||
|
{
|
||||||
|
var map = parameters.Map;
|
||||||
|
var min = parameters.Min;
|
||||||
|
var max = parameters.Max;
|
||||||
|
|
||||||
|
_logger.Trace($"Player ({creator.PlayerName}|{creator.Id}) create room with Id {roomId} and params ({map}|{min}|{max})");
|
||||||
|
|
||||||
|
var plugin = _factory.CreatePlugin(map);
|
||||||
|
if (plugin == null)
|
||||||
|
throw new NullReferenceException($"Plugin for map {map} is null");
|
||||||
|
|
||||||
|
var room = new GameRoom(_gameThread, plugin, roomId, map, min, max);
|
||||||
|
room.AddPlayer(creator, payload);
|
||||||
|
room.OnStart();
|
||||||
|
|
||||||
|
_roomsBySocket.Add(creator.PeerId, room);
|
||||||
|
_rooms.Add(room);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void JoinOrCreate(Player player, string roomId, RagonRoomParameters parameters, byte[] payload)
|
||||||
|
{
|
||||||
|
var map = parameters.Map;
|
||||||
|
var min = parameters.Min;
|
||||||
|
var max = parameters.Max;
|
||||||
|
|
||||||
|
if (_rooms.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var existRoom in _rooms)
|
||||||
|
{
|
||||||
|
if (existRoom.Map == map && existRoom.PlayersCount < existRoom.PlayersMax)
|
||||||
|
{
|
||||||
|
_logger.Trace($"Player ({player.PlayerName}|{player.Id}) joined to room with Id {roomId}");
|
||||||
|
|
||||||
|
existRoom.AddPlayer(player, payload);
|
||||||
|
_roomsBySocket.Add(player.PeerId, existRoom);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Trace($"Room not found for Player ({player.PlayerName}|{player.Id}), create room with Id {roomId} and params ({map}|{min}|{max})");
|
||||||
|
|
||||||
|
var plugin = _factory.CreatePlugin(map);
|
||||||
|
if (plugin == null)
|
||||||
|
throw new NullReferenceException($"Plugin for map {map} is null");
|
||||||
|
|
||||||
|
var room = new GameRoom(_gameThread, plugin, roomId, map, min, max);
|
||||||
|
room.AddPlayer(player, payload);
|
||||||
|
room.OnStart();
|
||||||
|
|
||||||
|
_roomsBySocket.Add(player.PeerId, room);
|
||||||
|
_rooms.Add(room);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Left(Player player, byte[] payload)
|
||||||
|
{
|
||||||
|
if (_roomsBySocket.Remove(player.PeerId, out var room))
|
||||||
|
{
|
||||||
|
_logger.Trace($"Player ({player.PlayerName}|{player.Id}) left room with Id {room.Id}");
|
||||||
|
room.RemovePlayer(player.PeerId);
|
||||||
|
if (room.PlayersCount < room.PlayersMin)
|
||||||
|
{
|
||||||
|
_logger.Trace($"Room with Id {room.Id} destroyed");
|
||||||
|
room.OnStop();
|
||||||
|
_rooms.Remove(room);
|
||||||
|
}
|
||||||
|
|
||||||
|
_gameThread.SocketServer.Send(player.PeerId, new byte[] {(byte) RagonOperation.LEAVE_ROOM}, DeliveryType.Reliable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Ragon.Core
|
||||||
|
{
|
||||||
|
public class Scheduler: IDisposable
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+156
@@ -0,0 +1,156 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Timers;
|
||||||
|
using ENet;
|
||||||
|
using NLog;
|
||||||
|
|
||||||
|
namespace Ragon.Core
|
||||||
|
{
|
||||||
|
public class ENetServer : ISocketServer
|
||||||
|
{
|
||||||
|
private ILogger _logger = LogManager.GetCurrentClassLogger();
|
||||||
|
private Host _host;
|
||||||
|
private uint _protocol;
|
||||||
|
private Address _address;
|
||||||
|
private Event _netEvent;
|
||||||
|
private Peer[] _peers;
|
||||||
|
private IEventHandler _eventHandler;
|
||||||
|
private Stopwatch _timer;
|
||||||
|
|
||||||
|
public ENetServer(IEventHandler eventHandler)
|
||||||
|
{
|
||||||
|
_eventHandler = eventHandler;
|
||||||
|
_timer = Stopwatch.StartNew();
|
||||||
|
_peers = Array.Empty<Peer>();
|
||||||
|
_host = new Host();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start(ushort port, int connections, uint protocol)
|
||||||
|
{
|
||||||
|
_address = default;
|
||||||
|
_address.Port = port;
|
||||||
|
_peers = new Peer[connections];
|
||||||
|
_protocol = protocol;
|
||||||
|
_host.Create(_address, connections, 2, 0, 0, 1024 * 1024);
|
||||||
|
|
||||||
|
|
||||||
|
var protocolDecoded = (protocol >> 16 & 0xFF) + "." + (protocol >> 8 & 0xFF) + "." + (protocol & 0xFF);
|
||||||
|
_logger.Info($"Network listening on {port}");
|
||||||
|
_logger.Info($"Protocol: {protocolDecoded}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Broadcast(ushort[] 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.UnreliableFragmented;
|
||||||
|
}
|
||||||
|
|
||||||
|
newPacket.Create(data, data.Length, packetFlags);
|
||||||
|
foreach (var peerId in peersIds)
|
||||||
|
_peers[peerId].Send(channel, ref newPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Send(ushort 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(ushort peerId, uint errorCode)
|
||||||
|
{
|
||||||
|
_peers[peerId].Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Process()
|
||||||
|
{
|
||||||
|
bool polled = false;
|
||||||
|
while (!polled)
|
||||||
|
{
|
||||||
|
if (_host.CheckEvents(out _netEvent) <= 0)
|
||||||
|
{
|
||||||
|
if (_host.Service(0, out _netEvent) <= 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
polled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (_netEvent.Type)
|
||||||
|
{
|
||||||
|
case EventType.None:
|
||||||
|
{
|
||||||
|
_logger.Trace("None event");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EventType.Connect:
|
||||||
|
{
|
||||||
|
// if (IsValidProtocol(_netEvent.Data))
|
||||||
|
// {
|
||||||
|
// _logger.Warn("Mismatched protocol, close connection");
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
_peers[_netEvent.Peer.ID] = _netEvent.Peer;
|
||||||
|
_eventHandler.OnConnected((ushort)_netEvent.Peer.ID);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EventType.Disconnect:
|
||||||
|
{
|
||||||
|
_eventHandler.OnDisconnected((ushort)_netEvent.Peer.ID);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EventType.Timeout:
|
||||||
|
{
|
||||||
|
_eventHandler.OnTimeout((ushort)_netEvent.Peer.ID);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EventType.Receive:
|
||||||
|
{
|
||||||
|
var peerId = (ushort) _netEvent.Peer.ID;
|
||||||
|
var dataRaw = new byte[_netEvent.Packet.Length];
|
||||||
|
|
||||||
|
_netEvent.Packet.CopyTo(dataRaw);
|
||||||
|
_netEvent.Packet.Dispose();
|
||||||
|
|
||||||
|
_eventHandler.OnData(peerId, dataRaw);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
_host?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsValidProtocol(uint protocol)
|
||||||
|
{
|
||||||
|
return protocol == _protocol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,7 @@
|
|||||||
|
namespace Ragon.Core;
|
||||||
|
|
||||||
|
public class WebSocketPacket
|
||||||
|
{
|
||||||
|
public ushort PeerId;
|
||||||
|
public byte[] Data;
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.WebSockets;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NLog;
|
||||||
|
using Ragon.Common;
|
||||||
|
|
||||||
|
namespace Ragon.Core;
|
||||||
|
|
||||||
|
public class WebSocketServer : ISocketServer
|
||||||
|
{
|
||||||
|
private ushort _idSequencer = 0;
|
||||||
|
private ILogger _logger = LogManager.GetCurrentClassLogger();
|
||||||
|
private Dictionary<ushort, WebSocket> _webSockets = new Dictionary<ushort, WebSocket>();
|
||||||
|
private Queue<WebSocketPacket> _events;
|
||||||
|
private IEventHandler _eventHandler;
|
||||||
|
private WebSocketTaskScheduler _webSocketScheduler;
|
||||||
|
private TaskFactory _taskFactory;
|
||||||
|
private HttpListener _httpListener;
|
||||||
|
|
||||||
|
public WebSocketServer(IEventHandler eventHandler)
|
||||||
|
{
|
||||||
|
_eventHandler = eventHandler;
|
||||||
|
_events = new Queue<WebSocketPacket>(1024);
|
||||||
|
_webSocketScheduler = new WebSocketTaskScheduler();
|
||||||
|
_taskFactory = new TaskFactory(_webSocketScheduler);
|
||||||
|
}
|
||||||
|
|
||||||
|
async void StartAccept()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var context = await _httpListener.GetContextAsync();
|
||||||
|
if (!context.Request.IsWebSocketRequest) continue;
|
||||||
|
|
||||||
|
var webSocketContext = await context.AcceptWebSocketAsync(null);
|
||||||
|
var webSocket = webSocketContext.WebSocket;
|
||||||
|
|
||||||
|
_idSequencer++;
|
||||||
|
_webSockets.Add(_idSequencer, webSocket);
|
||||||
|
|
||||||
|
_ = _taskFactory.StartNew(() => StartListen(webSocket, _idSequencer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async void StartListen(WebSocket webSocket, ushort peerId)
|
||||||
|
{
|
||||||
|
_eventHandler.OnConnected(peerId);
|
||||||
|
|
||||||
|
var bytes = new byte[2048];
|
||||||
|
var buffer = new Memory<byte>(bytes);
|
||||||
|
while (webSocket.State == WebSocketState.Open)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await webSocket.ReceiveAsync(buffer, CancellationToken.None);
|
||||||
|
var dataRaw = buffer.Slice(0, result.Count);
|
||||||
|
_eventHandler.OnData(peerId, dataRaw.ToArray());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_eventHandler.OnDisconnected(peerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async void ProcessQueue()
|
||||||
|
{
|
||||||
|
while (_events.TryDequeue(out var evnt))
|
||||||
|
{
|
||||||
|
if (_webSockets.TryGetValue(evnt.PeerId, out var ws) && ws.State == WebSocketState.Open)
|
||||||
|
{
|
||||||
|
await ws.SendAsync(evnt.Data, WebSocketMessageType.Binary, WebSocketMessageFlags.EndOfMessage, CancellationToken.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start(ushort port, int connections, uint protocol)
|
||||||
|
{
|
||||||
|
_httpListener = new HttpListener();
|
||||||
|
_httpListener.Prefixes.Add($"http://*:{port}/");
|
||||||
|
_httpListener.Start();
|
||||||
|
|
||||||
|
_taskFactory.StartNew(StartAccept);
|
||||||
|
|
||||||
|
var protocolDecoded = (protocol >> 16 & 0xFF) + "." + (protocol >> 8 & 0xFF) + "." + (protocol & 0xFF);
|
||||||
|
_logger.Info($"Network listening on http://*:{port}/");
|
||||||
|
_logger.Info($"Protocol: {protocolDecoded}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Process()
|
||||||
|
{
|
||||||
|
_webSocketScheduler.Process();
|
||||||
|
|
||||||
|
ProcessQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
_httpListener.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Send(ushort peerId, byte[] data, DeliveryType type)
|
||||||
|
{
|
||||||
|
_events.Enqueue(new WebSocketPacket() {PeerId = peerId, Data = data});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Broadcast(ushort[] peersIds, byte[] data, DeliveryType type)
|
||||||
|
{
|
||||||
|
foreach (var peerId in peersIds)
|
||||||
|
_events.Enqueue(new WebSocketPacket() {PeerId = peerId, Data = data});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Disconnect(ushort peerId, uint errorCode)
|
||||||
|
{
|
||||||
|
_webSockets[peerId].CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Channels;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NLog.LayoutRenderers.Wrappers;
|
||||||
|
|
||||||
|
namespace Ragon.Core;
|
||||||
|
|
||||||
|
public class WebSocketTaskScheduler: TaskScheduler
|
||||||
|
{
|
||||||
|
private ChannelReader<Task> _reader;
|
||||||
|
private ChannelWriter<Task> _writer;
|
||||||
|
private Queue<Task> _pendingTasks;
|
||||||
|
|
||||||
|
public WebSocketTaskScheduler()
|
||||||
|
{
|
||||||
|
var channel = Channel.CreateUnbounded<Task>();
|
||||||
|
_pendingTasks = new Queue<Task>();
|
||||||
|
_reader = channel.Reader;
|
||||||
|
_writer = channel.Writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IEnumerable<Task>? GetScheduledTasks()
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void QueueTask(Task task)
|
||||||
|
{
|
||||||
|
_writer.TryWrite(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Process()
|
||||||
|
{
|
||||||
|
while (_reader.TryRead(out var task))
|
||||||
|
{
|
||||||
|
TryExecuteTask(task);
|
||||||
|
|
||||||
|
if (task.Status == TaskStatus.Running)
|
||||||
|
_pendingTasks.Enqueue(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (_pendingTasks.TryDequeue(out var task))
|
||||||
|
_writer.TryWrite(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using ENet;
|
||||||
|
|
||||||
|
namespace Ragon.Core;
|
||||||
|
|
||||||
|
public interface IEventHandler
|
||||||
|
{
|
||||||
|
public void OnConnected(ushort peerId);
|
||||||
|
public void OnDisconnected(ushort peerId);
|
||||||
|
public void OnTimeout(ushort peerId);
|
||||||
|
public void OnData(ushort peerId, byte[] data);
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Ragon.Core;
|
||||||
|
|
||||||
|
public interface ISocketServer
|
||||||
|
{
|
||||||
|
public void Start(ushort port, int connections, uint protocol);
|
||||||
|
public void Process();
|
||||||
|
public void Stop();
|
||||||
|
public void Send(ushort peerId, byte[] data, DeliveryType type);
|
||||||
|
public void Broadcast(ushort[] peersIds, byte[] data, DeliveryType type);
|
||||||
|
public void Disconnect(ushort 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()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 +1,21 @@
|
|||||||
This program is free software: you can redistribute it and/or modify it under the terms of the Server Side Public License, version 1, as published by MongoDB, Inc. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Server Side Public License for more details. You should have received a copy of the Server Side Public License along with this program. If not, see http://www.mongodb.com/licensing/server-side-public-license.
|
Copyright (c) 2022 Eduard Kargin (theedison4@gmail.com)
|
||||||
|
|
||||||
|
MIT License:
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -1,49 +1,36 @@
|
|||||||
<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
|
|
||||||
- Room based architecture
|
- Room based architecture
|
||||||
- Extendable room logic via plugin
|
- Сustomizable authorization
|
||||||
- Custom authorization
|
- Сustomizable server-side logic via plugins with flexible API
|
||||||
- No CCU limitations*
|
- No CCU limitations*
|
||||||
- Multi-threaded
|
- No Room count limitations
|
||||||
- Engine agnostic
|
- Reliable UDP
|
||||||
- Support any client architecture (MonoBehaviors, ECS)
|
|
||||||
- UDP
|
|
||||||
|
|
||||||
### Roadmap:
|
|
||||||
- Allow customize matchmaking
|
|
||||||
- Use native memory
|
|
||||||
- Reduce allocations
|
|
||||||
- Dashboard for monitoring entities and players in realtime
|
|
||||||
- Statistics for monitoring state of server, cpu, memory
|
|
||||||
- Docker support
|
|
||||||
- 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
|
MIT
|
||||||
|
|
||||||
### Tips
|
### Tips
|
||||||
\* Limited to 4095 CCU by library ENet-Sharp
|
\* Limited to 4095 CCU by library ENet-Sharp
|
||||||
|
|||||||
Reference in New Issue
Block a user