commit b26e7c1402a85d4ac343589e6351a4c8210e37bf Author: Edmand46 Date: Sun Apr 24 09:05:15 2022 +0400 initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ec1bc2b --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +.idea +.vs +obj +bin diff --git a/Ragon.Common/BitBufferExtension.cs b/Ragon.Common/BitBufferExtension.cs new file mode 100644 index 0000000..958cdcf --- /dev/null +++ b/Ragon.Common/BitBufferExtension.cs @@ -0,0 +1,29 @@ +using System; +using NetStack.Serialization; + +namespace Ragon.Common +{ + public static class BitBufferExtension + { + public static BitBuffer AddBytes(this BitBuffer buffer, byte[] data) + { + buffer.AddInt(data.Length); + foreach (var b in data) + buffer.AddByte(b); + + return buffer; + } + + public static byte[] ReadBytes(this BitBuffer buffer) + { + var size = buffer.ReadInt(); + var data = new byte[size]; + var i = 0; + + while (i < size) + data[i] = buffer.ReadByte(); + + return data; + } + } +} \ No newline at end of file diff --git a/Ragon.Common/External/DisruptorUnity3d/RingBuffer.cs b/Ragon.Common/External/DisruptorUnity3d/RingBuffer.cs new file mode 100755 index 0000000..fe0e70d --- /dev/null +++ b/Ragon.Common/External/DisruptorUnity3d/RingBuffer.cs @@ -0,0 +1,275 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace DisruptorUnity3d +{ + /// + /// Implementation of the Disruptor pattern + /// + /// the type of item to be stored + public class RingBuffer + { + private readonly T[] _entries; + private readonly int _modMask; + private Volatile.PaddedLong _consumerCursor = new Volatile.PaddedLong(); + private Volatile.PaddedLong _producerCursor = new Volatile.PaddedLong(); + + /// + /// Creates a new RingBuffer with the given capacity + /// + /// The capacity of the buffer + /// Only a single thread may attempt to consume at any one time + public RingBuffer(int capacity) + { + capacity = NextPowerOfTwo(capacity); + _modMask = capacity - 1; + _entries = new T[capacity]; + } + + /// + /// The maximum number of items that can be stored + /// + public int Capacity + { + get { return _entries.Length; } + } + + public T this[long index] + { + get { unchecked { return _entries[index & _modMask]; } } + set { unchecked { _entries[index & _modMask] = value; } } + } + + /// + /// Removes an item from the buffer. + /// + /// The next available item + 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; + } + + /// + /// Attempts to remove an items from the queue + /// + /// the items + /// True if successful + public bool TryDequeue(out T obj) + { + var next = _consumerCursor.ReadAcquireFence() + 1; + + if (_producerCursor.ReadAcquireFence() < next) + { + obj = default(T); + return false; + } + obj = Dequeue(); + return true; + } + + /// + /// Add an item to the buffer + /// + /// + 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 + } + + /// + /// The number of items in the buffer + /// + /// for indicative purposes only, may contain stale data + 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; + + /// + /// Create a new with the given initial value. + /// + /// Initial value + public PaddedLong(long value) + { + _value = value; + } + + /// + /// Read the value without applying any fence + /// + /// The current value + public long ReadUnfenced() + { + return _value; + } + + /// + /// Read the value applying acquire fence semantic + /// + /// The current value + public long ReadAcquireFence() + { + var value = _value; + Thread.MemoryBarrier(); + return value; + } + + /// + /// Read the value applying full fence semantic + /// + /// The current value + public long ReadFullFence() + { + Thread.MemoryBarrier(); + return _value; + } + + /// + /// Read the value applying a compiler only fence, no CPU fence is applied + /// + /// The current value + [MethodImpl(MethodImplOptions.NoOptimization)] + public long ReadCompilerOnlyFence() + { + return _value; + } + + /// + /// Write the value applying release fence semantic + /// + /// The new value + public void WriteReleaseFence(long newValue) + { + Thread.MemoryBarrier(); + _value = newValue; + } + + /// + /// Write the value applying full fence semantic + /// + /// The new value + public void WriteFullFence(long newValue) + { + Thread.MemoryBarrier(); + _value = newValue; + } + + /// + /// Write the value applying a compiler fence only, no CPU fence is applied + /// + /// The new value + [MethodImpl(MethodImplOptions.NoOptimization)] + public void WriteCompilerOnlyFence(long newValue) + { + _value = newValue; + } + + /// + /// Write without applying any fence + /// + /// The new value + public void WriteUnfenced(long newValue) + { + _value = newValue; + } + + /// + /// Atomically set the value to the given updated value if the current value equals the comparand + /// + /// The new value + /// The comparand (expected value) + /// + public bool AtomicCompareExchange(long newValue, long comparand) + { + return Interlocked.CompareExchange(ref _value, newValue, comparand) == comparand; + } + + /// + /// Atomically set the value to the given updated value + /// + /// The new value + /// The original value + public long AtomicExchange(long newValue) + { + return Interlocked.Exchange(ref _value, newValue); + } + + /// + /// Atomically add the given value to the current value and return the sum + /// + /// The value to be added + /// The sum of the current value and the given value + public long AtomicAddAndGet(long delta) + { + return Interlocked.Add(ref _value, delta); + } + + /// + /// Atomically increment the current value and return the new value + /// + /// The incremented value. + public long AtomicIncrementAndGet() + { + return Interlocked.Increment(ref _value); + } + + /// + /// Atomically increment the current value and return the new value + /// + /// The decremented value. + public long AtomicDecrementAndGet() + { + return Interlocked.Decrement(ref _value); + } + + /// + /// Returns the string representation of the current value. + /// + /// the string representation of the current value. + public override string ToString() + { + var value = ReadFullFence(); + return value.ToString(); + } + } + } +} \ No newline at end of file diff --git a/Ragon.Common/External/NetStack.Buffers/ArrayPool.cs b/Ragon.Common/External/NetStack.Buffers/ArrayPool.cs new file mode 100755 index 0000000..120fab8 --- /dev/null +++ b/Ragon.Common/External/NetStack.Buffers/ArrayPool.cs @@ -0,0 +1,69 @@ +/* + * 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 { + #if NET_4_6 || NET_STANDARD_2_0 + private static ArrayPool s_sharedInstance = null; + #else + private static volatile ArrayPool s_sharedInstance = null; + #endif + + public static ArrayPool 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 EnsureSharedCreated() { + Interlocked.CompareExchange(ref s_sharedInstance, Create(), null); + + return s_sharedInstance; + } + + public static ArrayPool Create() { + return new DefaultArrayPool(); + } + + public static ArrayPool Create(int maxArrayLength, int maxArraysPerBucket) { + return new DefaultArrayPool(maxArrayLength, maxArraysPerBucket); + } + + public abstract T[] Rent(int minimumLength); + + public abstract void Return(T[] array, bool clearArray = false); + } +} \ No newline at end of file diff --git a/Ragon.Common/External/NetStack.Buffers/ArrayPoolEventSource.cs b/Ragon.Common/External/NetStack.Buffers/ArrayPoolEventSource.cs new file mode 100755 index 0000000..7f20ddc --- /dev/null +++ b/Ragon.Common/External/NetStack.Buffers/ArrayPoolEventSource.cs @@ -0,0 +1,82 @@ +/* + * 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 + } + } +} \ No newline at end of file diff --git a/Ragon.Common/External/NetStack.Buffers/DefaultArrayPool.cs b/Ragon.Common/External/NetStack.Buffers/DefaultArrayPool.cs new file mode 100755 index 0000000..3407403 --- /dev/null +++ b/Ragon.Common/External/NetStack.Buffers/DefaultArrayPool.cs @@ -0,0 +1,134 @@ +/* + * 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 : ArrayPool { + 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 + } + } +} \ No newline at end of file diff --git a/Ragon.Common/External/NetStack.Buffers/DefaultArrayPoolBucket.cs b/Ragon.Common/External/NetStack.Buffers/DefaultArrayPoolBucket.cs new file mode 100755 index 0000000..1a7de48 --- /dev/null +++ b/Ragon.Common/External/NetStack.Buffers/DefaultArrayPoolBucket.cs @@ -0,0 +1,151 @@ +/* + * 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 : ArrayPool { + 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 + } + } + } +} \ No newline at end of file diff --git a/Ragon.Common/External/NetStack.Buffers/Utilities.cs b/Ragon.Common/External/NetStack.Buffers/Utilities.cs new file mode 100755 index 0000000..d4dc0f9 --- /dev/null +++ b/Ragon.Common/External/NetStack.Buffers/Utilities.cs @@ -0,0 +1,84 @@ +/* + * 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; + } + } +} \ No newline at end of file diff --git a/Ragon.Common/External/NetStack.Quantization/BoundedRange.cs b/Ragon.Common/External/NetStack.Quantization/BoundedRange.cs new file mode 100755 index 0000000..b9e3282 --- /dev/null +++ b/Ragon.Common/External/NetStack.Quantization/BoundedRange.cs @@ -0,0 +1,184 @@ +/* + * 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)); + } + } +} \ No newline at end of file diff --git a/Ragon.Common/External/NetStack.Quantization/HalfPrecision.cs b/Ragon.Common/External/NetStack.Quantization/HalfPrecision.cs new file mode 100755 index 0000000..9e88219 --- /dev/null +++ b/Ragon.Common/External/NetStack.Quantization/HalfPrecision.cs @@ -0,0 +1,117 @@ +/* + * 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; + } + } +} \ No newline at end of file diff --git a/Ragon.Common/External/NetStack.Quantization/SmallestThree.cs b/Ragon.Common/External/NetStack.Quantization/SmallestThree.cs new file mode 100755 index 0000000..e84b004 --- /dev/null +++ b/Ragon.Common/External/NetStack.Quantization/SmallestThree.cs @@ -0,0 +1,223 @@ +/* + * 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); + } + } + } +} \ No newline at end of file diff --git a/Ragon.Common/External/NetStack.Serialization/BitBuffer.cs b/Ragon.Common/External/NetStack.Serialization/BitBuffer.cs new file mode 100755 index 0000000..b578db8 --- /dev/null +++ b/Ragon.Common/External/NetStack.Serialization/BitBuffer.cs @@ -0,0 +1,546 @@ +/* + * 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 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 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; + } + } +} diff --git a/Ragon.Common/External/NetStack.Threading/ArrayQueue.cs b/Ragon.Common/External/NetStack.Threading/ArrayQueue.cs new file mode 100755 index 0000000..c02c68c --- /dev/null +++ b/Ragon.Common/External/NetStack.Threading/ArrayQueue.cs @@ -0,0 +1,150 @@ +/* + * 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 + } + } + } + } +} \ No newline at end of file diff --git a/Ragon.Common/External/NetStack.Threading/ConcurrentBuffer.cs b/Ragon.Common/External/NetStack.Threading/ConcurrentBuffer.cs new file mode 100755 index 0000000..0abbaca --- /dev/null +++ b/Ragon.Common/External/NetStack.Threading/ConcurrentBuffer.cs @@ -0,0 +1,153 @@ +/* + * 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; + } + } + } +} \ No newline at end of file diff --git a/Ragon.Common/External/NetStack.Threading/ConcurrentPool.cs b/Ragon.Common/External/NetStack.Threading/ConcurrentPool.cs new file mode 100755 index 0000000..785f20b --- /dev/null +++ b/Ragon.Common/External/NetStack.Threading/ConcurrentPool.cs @@ -0,0 +1,173 @@ +/* + * 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 where T : class { + #if NET_4_6 || NET_STANDARD_2_0 + private SpinLock _lock; + #else + private object _lock; + #endif + private readonly Func _factory; + private Segment _head; + private Segment _tail; + + public ConcurrentPool(int capacity, Func 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; + } + } + } +} \ No newline at end of file diff --git a/Ragon.Common/External/NetStack.Unsafe/Memory.cs b/Ragon.Common/External/NetStack.Unsafe/Memory.cs new file mode 100755 index 0000000..e9df17c --- /dev/null +++ b/Ragon.Common/External/NetStack.Unsafe/Memory.cs @@ -0,0 +1,63 @@ +/* + * 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 +} \ No newline at end of file diff --git a/Ragon.Common/Protocol/Data/Abstract/IData.cs b/Ragon.Common/Protocol/Data/Abstract/IData.cs new file mode 100644 index 0000000..81dc013 --- /dev/null +++ b/Ragon.Common/Protocol/Data/Abstract/IData.cs @@ -0,0 +1,10 @@ +using NetStack.Serialization; + +namespace Ragon.Core +{ + public interface IData + { + public void Serialize(BitBuffer buffer); + public void Deserialize(BitBuffer buffer); + } +} \ No newline at end of file diff --git a/Ragon.Common/Protocol/Data/AuthorizeData.cs b/Ragon.Common/Protocol/Data/AuthorizeData.cs new file mode 100644 index 0000000..828889e --- /dev/null +++ b/Ragon.Common/Protocol/Data/AuthorizeData.cs @@ -0,0 +1,23 @@ +using NetStack.Serialization; + +namespace Ragon.Core +{ + + public class AuthorationData : IData + { + public string Login { get; set; } + public string Password { get; set; } + + public void Serialize(BitBuffer buffer) + { + buffer.AddString(Login); + buffer.AddString(Password); + } + + public void Deserialize(BitBuffer buffer) + { + Login = buffer.ReadString(); + Password = buffer.ReadString(); + } + } +} \ No newline at end of file diff --git a/Ragon.Common/Protocol/Data/FindAndJoinData.cs b/Ragon.Common/Protocol/Data/FindAndJoinData.cs new file mode 100644 index 0000000..bdb0a48 --- /dev/null +++ b/Ragon.Common/Protocol/Data/FindAndJoinData.cs @@ -0,0 +1,19 @@ +using NetStack.Serialization; + +namespace Ragon.Core +{ + public class FindAndJoinData: IData + { + public string Map; + + public void Serialize(BitBuffer buffer) + { + buffer.AddString(Map); + } + + public void Deserialize(BitBuffer buffer) + { + Map = buffer.ReadString(); + } + } +} \ No newline at end of file diff --git a/Ragon.Common/Protocol/Data/SceneLoadData.cs b/Ragon.Common/Protocol/Data/SceneLoadData.cs new file mode 100644 index 0000000..9c990f0 --- /dev/null +++ b/Ragon.Common/Protocol/Data/SceneLoadData.cs @@ -0,0 +1,19 @@ +using NetStack.Serialization; + +namespace Ragon.Core +{ + public class SceneLoadData: IData + { + public string Scene; + + public void Serialize(BitBuffer buffer) + { + buffer.AddString(Scene); + } + + public void Deserialize(BitBuffer buffer) + { + Scene = buffer.ReadString(); + } + } +} \ No newline at end of file diff --git a/Ragon.Common/Protocol/Data/SnapshotData.cs b/Ragon.Common/Protocol/Data/SnapshotData.cs new file mode 100644 index 0000000..6d702ba --- /dev/null +++ b/Ragon.Common/Protocol/Data/SnapshotData.cs @@ -0,0 +1,45 @@ +using NetStack.Serialization; +using Ragon.Common; + +namespace Ragon.Core +{ + + public class EntityData: IData + { + public int EntityId; + public byte[] State; + public void Serialize(BitBuffer buffer) + { + buffer.AddInt(EntityId); + buffer.AddBytes(State); + } + + public void Deserialize(BitBuffer buffer) + { + EntityId = buffer.ReadInt(); + State = buffer.ReadBytes(); + } + } + public class SnapshotData: IData + { + public EntityData[] Entities; + public void Serialize(BitBuffer buffer) + { + buffer.AddInt(Entities.Length); + foreach (var entityData in Entities) + entityData.Serialize(buffer); + } + public void Deserialize(BitBuffer buffer) + { + var entitiesSize = buffer.ReadInt(); + var i = 0; + + Entities = new EntityData[entitiesSize]; + while (i < entitiesSize) + { + Entities[i] = new EntityData(); + Entities[i].Deserialize(buffer); + } + } + } +} \ No newline at end of file diff --git a/Ragon.Common/Protocol/Opcode.cs b/Ragon.Common/Protocol/Opcode.cs new file mode 100644 index 0000000..96d5e86 --- /dev/null +++ b/Ragon.Common/Protocol/Opcode.cs @@ -0,0 +1,29 @@ +namespace Ragon.Common.Protocol +{ + public enum RagonOperation: ushort + { + AUTHORIZE, + AUTHORIZED_SUCCESS, + AUTHORIZED_FAILED, + + JOIN_ROOM, + LEAVE_ROOM, + + LOAD_SCENE, + SCENE_IS_LOADED, + + PLAYER_CONNECTED, + PLAYER_DISCONNECTED, + + CREATE_ENTITY, + DESTROY_ENTITY, + + RESTORE_BEGIN, + RESTORE_END, + RESTORED, + + REPLICATE_ENTITY_STATE, + REPLICATE_ENTITY_PROPERTY, + REPLICATE_EVENT, + } +} \ No newline at end of file diff --git a/Ragon.Common/Protocol/Wrapper.cs b/Ragon.Common/Protocol/Wrapper.cs new file mode 100644 index 0000000..2c35937 --- /dev/null +++ b/Ragon.Common/Protocol/Wrapper.cs @@ -0,0 +1,55 @@ +using System; +using System.Runtime.CompilerServices; +using NetStack.Buffers; + +namespace Ragon.Common.Protocol +{ + public static class ProtocolHeader + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteOperation(ushort id, byte[] data) { + data[0] = (byte)(id & 0x00FF); + data[1] = (byte)((id & 0xFF00) >> 8); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort ReadOperation(byte[] data, int offset = 0) + { + return (ushort)(data[offset] + (data[offset + 1] << 8)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteEntity(int id, byte[] data, int offset = 2) { + data[offset] = (byte)(id & 0x00FF); + data[offset + 1] = (byte)((id & 0xFF00) >> 8); + data[offset + 2] = (byte)((id & 0xFF00) >> 16); + data[offset + 3] = (byte)((id & 0xFF00) >> 24); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ReadEntity(byte[] data, int offset = 2) + { + return (ushort)(data[offset] + (data[offset + 1] << 8) + (data[offset + 2] << 16) + (data[offset + 3] << 24)); + } + + public static int ReadProperty(byte[] data, int offset = 2) + { + return ReadEntity(data, offset); + } + + public static void WriteProperty(int id, byte[] data) + { + WriteEntity(id, data); + } + + public static int ReadEvent(byte[] data, int offset = 2) + { + return ReadEntity(data, offset); + } + + public static void WriteEvent(int id, byte[] data) + { + WriteEntity(id, data); + } + } +} \ No newline at end of file diff --git a/Ragon.Common/Ragon.Common.csproj b/Ragon.Common/Ragon.Common.csproj new file mode 100644 index 0000000..9d21669 --- /dev/null +++ b/Ragon.Common/Ragon.Common.csproj @@ -0,0 +1,23 @@ + + + + disable + disable + 8 + netstandard2.1 + + + + /Users/edmand46/UnityProjects/ragon-client/Assets/RagonSDK/ + false + true + + + + /Users/edmand46/UnityProjects/ragon-client/Assets/RagonSDK/ + + + + + + diff --git a/Ragon.sln b/Ragon.sln new file mode 100755 index 0000000..05a9c4c --- /dev/null +++ b/Ragon.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon", "Ragon\Ragon.csproj", "{BABA1AF0-CF91-43F2-9577-53800068ACCF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YohohoArenaPlugin", "YohohoArenaPlugin\YohohoArenaPlugin.csproj", "{C2B87ADE-A122-4366-8EB7-24DAE11EBAF0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Common", "Ragon.Common\Ragon.Common.csproj", "{F478B2A2-36F4-43B9-9BB7-382A57C449B2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BABA1AF0-CF91-43F2-9577-53800068ACCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BABA1AF0-CF91-43F2-9577-53800068ACCF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BABA1AF0-CF91-43F2-9577-53800068ACCF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BABA1AF0-CF91-43F2-9577-53800068ACCF}.Release|Any CPU.Build.0 = Release|Any CPU + {C2B87ADE-A122-4366-8EB7-24DAE11EBAF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C2B87ADE-A122-4366-8EB7-24DAE11EBAF0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C2B87ADE-A122-4366-8EB7-24DAE11EBAF0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C2B87ADE-A122-4366-8EB7-24DAE11EBAF0}.Release|Any CPU.Build.0 = Release|Any CPU + {F478B2A2-36F4-43B9-9BB7-382A57C449B2}.Debug|Any CPU.ActiveCfg = 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.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Ragon/Ragon.csproj b/Ragon/Ragon.csproj new file mode 100755 index 0000000..4205978 --- /dev/null +++ b/Ragon/Ragon.csproj @@ -0,0 +1,19 @@ + + + + 10 + enable + net6.0;netstandard2.1 + + + + + + + + + + + + + diff --git a/Ragon/Ragon.csproj.DotSettings b/Ragon/Ragon.csproj.DotSettings new file mode 100644 index 0000000..fadcd66 --- /dev/null +++ b/Ragon/Ragon.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Ragon/Sources/Application.cs b/Ragon/Sources/Application.cs new file mode 100755 index 0000000..bdb6d8c --- /dev/null +++ b/Ragon/Sources/Application.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using DisruptorUnity3d; +using NetStack.Serialization; +using NLog; +using Ragon.Common.Protocol; + +namespace Ragon.Core +{ + public class Application : IDisposable + { + private readonly Logger _logger = LogManager.GetCurrentClassLogger(); + private readonly List _roomThreads = new(); + private readonly Dictionary _socketByRoomThreads = new(); + private readonly Dictionary _roomThreadCounter = new(); + + private readonly ENetServer _socketServer; + public Application(PluginFactory factory, Configuration configuration, int threadsCount) + { + _socketServer = new ENetServer(); + + for (var i = 0; i < threadsCount; i++) + { + var roomThread = new RoomThread(factory); + _roomThreadCounter.Add(roomThread, 0); + _roomThreads.Add(roomThread); + } + } + + public void Start() + { + _socketServer.Start(5000); + + foreach (var roomThread in _roomThreads) + roomThread.Start(); + + while (true) + { + foreach (var roomThread in _roomThreads) + while (roomThread.ReadOutEvent(out var evnt)) + _socketServer.WriteEvent(evnt); + + while (_socketServer.ReadEvent(out var evnt)) + { + if (evnt.Type == EventType.CONNECTED) + { + var roomThread = _roomThreads.First(); + _roomThreadCounter[roomThread] += 1; + _socketByRoomThreads.Add(evnt.PeerId, roomThread); + } + + if (_socketByRoomThreads.TryGetValue(evnt.PeerId, out var existsRoomThread)) + existsRoomThread.WriteInEvent(evnt); + + if (evnt.Type == EventType.DISCONNECTED) + { + _socketByRoomThreads.Remove(evnt.PeerId, out var roomThread); + _roomThreadCounter[roomThread] =- 1; + } + + if (evnt.Type == EventType.TIMEOUT) + { + _socketByRoomThreads.Remove(evnt.PeerId, out var roomThread); + _roomThreadCounter[roomThread] =- 1; + } + } + + Thread.Sleep(1); + } + } + + public void Dispose() + { + foreach (var roomThread in _roomThreads) + roomThread.Dispose(); + + _roomThreads.Clear(); + } + } +} \ No newline at end of file diff --git a/Ragon/Sources/Bootstrap.cs b/Ragon/Sources/Bootstrap.cs new file mode 100755 index 0000000..6584ef4 --- /dev/null +++ b/Ragon/Sources/Bootstrap.cs @@ -0,0 +1,17 @@ +using NLog; + +namespace Ragon.Core +{ + public class Bootstrap + { + private ILogger _logger = LogManager.GetCurrentClassLogger(); + + public void Configure(PluginFactory factory) + { + _logger.Info("Configure application..."); + var configuration = ConfigurationLoader.Load("config.json"); + var app = new Application(factory, configuration, 2); + app.Start(); + } + } +} \ No newline at end of file diff --git a/Ragon/Sources/Configuration/Configuration.cs b/Ragon/Sources/Configuration/Configuration.cs new file mode 100755 index 0000000..35002d2 --- /dev/null +++ b/Ragon/Sources/Configuration/Configuration.cs @@ -0,0 +1,7 @@ +namespace Ragon.Core +{ + public class Configuration + { + public string[] blacklist; + } +} \ No newline at end of file diff --git a/Ragon/Sources/Configuration/ConfigurationLoader.cs b/Ragon/Sources/Configuration/ConfigurationLoader.cs new file mode 100755 index 0000000..eb3589d --- /dev/null +++ b/Ragon/Sources/Configuration/ConfigurationLoader.cs @@ -0,0 +1,38 @@ +using System; +using System.IO; +using Newtonsoft.Json; +using NLog; +using Logger = NLog.Logger; + +namespace Ragon.Core +{ + public static class ConfigurationLoader + { + private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); + private static readonly string _serverVersion = "2.0.0-preview"; + + private static void CopyrightInfo() + { + _logger.Info($"Server Version: {_serverVersion}"); + _logger.Info($"Machine Name: {Environment.MachineName}"); + _logger.Info($"OS: {Environment.OSVersion}"); + _logger.Info($"Processors: {Environment.ProcessorCount}"); + _logger.Info($"Runtime Version: {Environment.Version}"); + + _logger.Info("=================================="); + _logger.Info("= ="); + _logger.Info($"={"Yohoho Server".PadBoth(32)}="); + _logger.Info("= ="); + _logger.Info("=================================="); + } + + public static Configuration Load(string filePath) + { + CopyrightInfo(); + + var data = File.ReadAllText(filePath); + var configuration = JsonConvert.DeserializeObject(data); + return configuration; + } + } +} \ No newline at end of file diff --git a/Ragon/Sources/ENetServer.cs b/Ragon/Sources/ENetServer.cs new file mode 100755 index 0000000..b2745bb --- /dev/null +++ b/Ragon/Sources/ENetServer.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using DisruptorUnity3d; +using ENet; +using NLog; +using NLog.LayoutRenderers.Wrappers; + +namespace Ragon.Core +{ + public enum Status + { + Stopped, + Listening, + Disconnecting, + Connecting, + Assigning, + Connected + } + + public enum DeliveryType + { + UnreliableUnsequenced, + UnreliableSequenced, + UnreliableFragmented, + ReliableSequenced + } + + + 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 _receiveBuffer; + private RingBuffer _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(8192 + 8192); + _receiveBuffer = new RingBuffer(8192 + 8192); + + Status = Status.Listening; + + _thread = new Thread(Execute); + _thread.Name = "NetworkThread"; + _thread.Start(); + _logger.Info($"Socket Server Started at port {port}"); + } + + private void Execute() + { + while (true) + { + while (_sendBuffer.TryDequeue(out var data)) + { + var newPacket = new Packet(); + newPacket.Create(data.Data, data.Data.Length, PacketFlags.Reliable); + _peers[data.PeerId].Send(0, ref newPacket); + } + + 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}; + // Console.WriteLine("Client connected - ID: " + _netEvent.Peer.ID + ", IP: " + _netEvent.Peer.IP); + _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}; + // Console.WriteLine("Client disconnected - ID: " + _netEvent.Peer.ID + ", IP: " + _netEvent.Peer.IP); + _receiveBuffer.Enqueue(@event); + break; + } + case ENet.EventType.Timeout: + { + // Console.WriteLine("Client timeout - ID: " + _netEvent.Peer.ID + ", IP: " + _netEvent.Peer.IP); + var @event = new Event {PeerId = _netEvent.Peer.ID, Type = EventType.TIMEOUT}; + // Console.WriteLine("Client disconnected - ID: " + _netEvent.Peer.ID + ", IP: " + _netEvent.Peer.IP); + _receiveBuffer.Enqueue(@event); + break; + } + case ENet.EventType.Receive: + { + // Console.WriteLine("Packet received from - ID: " + _netEvent.Peer.ID + ", IP: " + _netEvent.Peer.IP + ", Channel ID: " + _netEvent.ChannelID + ", Data length: " + _netEvent.Packet.Length); + var data = new byte[_netEvent.Packet.Length]; + _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(); + } + } +} \ No newline at end of file diff --git a/Ragon/Sources/Entity/Entity.cs b/Ragon/Sources/Entity/Entity.cs new file mode 100644 index 0000000..f5a88ca --- /dev/null +++ b/Ragon/Sources/Entity/Entity.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace Ragon.Core; + +public class Entity +{ + private static int _idGenerator = 0; + public int EntityId { get; private set; } + public uint OwnerId { get; private set; } + public byte[] State { get; set; } + public Dictionary Properties { get; set; } + + public Entity(uint ownerId) + { + OwnerId = ownerId; + EntityId = _idGenerator++; + } +} \ No newline at end of file diff --git a/Ragon/Sources/Event/Event.cs b/Ragon/Sources/Event/Event.cs new file mode 100644 index 0000000..4f8902d --- /dev/null +++ b/Ragon/Sources/Event/Event.cs @@ -0,0 +1,9 @@ +namespace Ragon.Core +{ + public struct Event + { + public EventType Type; + public uint PeerId; + public byte[] Data; + } +} \ No newline at end of file diff --git a/Ragon/Sources/Event/EventType.cs b/Ragon/Sources/Event/EventType.cs new file mode 100644 index 0000000..8f147cd --- /dev/null +++ b/Ragon/Sources/Event/EventType.cs @@ -0,0 +1,10 @@ +namespace Ragon.Core +{ + public enum EventType + { + CONNECTED, + DISCONNECTED, + TIMEOUT, + DATA, + } +} \ No newline at end of file diff --git a/Ragon/Sources/Extensions/StringExtensions.cs b/Ragon/Sources/Extensions/StringExtensions.cs new file mode 100755 index 0000000..d8ec5ac --- /dev/null +++ b/Ragon/Sources/Extensions/StringExtensions.cs @@ -0,0 +1,14 @@ +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); + } + } +} \ No newline at end of file diff --git a/Ragon/Sources/Extensions/ValueExtensions.cs b/Ragon/Sources/Extensions/ValueExtensions.cs new file mode 100755 index 0000000..c51a904 --- /dev/null +++ b/Ragon/Sources/Extensions/ValueExtensions.cs @@ -0,0 +1,14 @@ +using System; + +namespace Ragon.Core +{ + public static class ValueExtensions + { + public static T Clamp(this T val, T min, T max) where T : IComparable + { + if (val.CompareTo(min) < 0) return min; + else if (val.CompareTo(max) > 0) return max; + else return val; + } + } +} \ No newline at end of file diff --git a/Ragon/Sources/Player/Player.cs b/Ragon/Sources/Player/Player.cs new file mode 100755 index 0000000..b5d1441 --- /dev/null +++ b/Ragon/Sources/Player/Player.cs @@ -0,0 +1,15 @@ +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 Entities; + public List EntitiesIds; + } +} \ No newline at end of file diff --git a/Ragon/Sources/Plugin/PluginBase.cs b/Ragon/Sources/Plugin/PluginBase.cs new file mode 100755 index 0000000..79f44d5 --- /dev/null +++ b/Ragon/Sources/Plugin/PluginBase.cs @@ -0,0 +1,36 @@ + +using System; +using System.Collections.Generic; + +namespace Ragon.Core +{ + public class PluginBase + { + static class Storage + { + public static Dictionary>> Subscribes = new(); + } + + + protected Room _room; + // protected Dictionary _subscribes = new Dictionary(); + public void Attach(Room room) => _room = room; + + public void Subscribe(ushort evntCode, Action val) + { + Storage.Subscribes.Add(_room, val); + } + public virtual void OnStart() + { + } + + public virtual void OnStop() + { + } + + public virtual void OnTick(ulong ticks, float deltaTime) + { + + } + } +} \ No newline at end of file diff --git a/Ragon/Sources/Plugin/PluginFactory.cs b/Ragon/Sources/Plugin/PluginFactory.cs new file mode 100644 index 0000000..c4c3854 --- /dev/null +++ b/Ragon/Sources/Plugin/PluginFactory.cs @@ -0,0 +1,7 @@ +namespace Ragon.Core +{ + public interface PluginFactory + { + public PluginBase CreatePlugin(string map); + } +} \ No newline at end of file diff --git a/Ragon/Sources/Rooms/Room.cs b/Ragon/Sources/Rooms/Room.cs new file mode 100755 index 0000000..04057bc --- /dev/null +++ b/Ragon/Sources/Rooms/Room.cs @@ -0,0 +1,344 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NetStack.Serialization; +using NLog; +using Ragon.Common.Protocol; + +namespace Ragon.Core +{ + public class Room : IDisposable + { + private ILogger _logger = LogManager.GetCurrentClassLogger(); + private Dictionary _players = new(); + private Dictionary _entities = new(); + private uint Owner; + + private BitBuffer _buffer = new BitBuffer(8192); + private byte[] _bytes = new byte[8192]; + + private readonly PluginBase _plugin; + private readonly RoomThread _roomThread; + private readonly string _map; + private ulong _ticks = 0; + + // Cache + private uint[] _readyPlayers = Array.Empty(); + + public int Players => _players.Count; + public int MaxPlayers { get; } = 0; + + public Room(RoomThread roomThread, PluginBase pluginBase, string map) + { + _roomThread = roomThread; + _plugin = pluginBase; + _map = map; + + _plugin.Attach(this); + } + + public void Joined(uint peerId, byte[] payload) + { + if (_players.Count == 0) + { + Owner = peerId; + } + + var player = new Player() + { + PlayerName = "Player " + peerId, + PeerId = peerId, + IsLoaded = false, + Entities = new List(), + EntitiesIds = new List(), + }; + + _players.Add(peerId, player); + _plugin.OnPlayerConnected(player); + + var data = new byte[8]; + ProtocolHeader.WriteEntity((int) peerId, data, 0); + ProtocolHeader.WriteEntity((int) Owner, data, 4); + Send(peerId, RagonOperation.JOIN_ROOM, data); + + var sceneRawData = Encoding.UTF8.GetBytes(_map); + Send(peerId, RagonOperation.LOAD_SCENE, sceneRawData); + } + + public void Leave(uint peerId) + { + if (_players.Remove(peerId, out var player)) + { + _plugin.OnPlayerDisconnected(player); + foreach (var entityId in player.EntitiesIds) + { + var entityData = new byte[4]; + ProtocolHeader.WriteEntity(entityId, entityData, 0); + Broadcast(_readyPlayers, RagonOperation.DESTROY_ENTITY, entityData); + + _entities.Remove(entityId); + } + } + } + + public void ProcessEvent(RagonOperation operation, uint peerId, byte[] rawData) + { + switch (operation) + { + case RagonOperation.REPLICATE_ENTITY_STATE: + { + var entityId = ProtocolHeader.ReadEntity(rawData, 2); + if (_entities.TryGetValue(entityId, out var ent)) + { + var data = new byte[rawData.Length - 6]; // opcode(ushort)(2) + entity(int)(4) + Array.Copy(rawData, 6, data, 0, rawData.Length - 6); + _entities[entityId].State = data; + + Broadcast(_readyPlayers, rawData); + } + + break; + } + case RagonOperation.REPLICATE_ENTITY_PROPERTY: + { + var entityId = ProtocolHeader.ReadEntity(rawData, 2); + if (_entities.TryGetValue(entityId, out var ent)) + { + var propertyId = ProtocolHeader.ReadProperty(rawData, 6); + var data = new byte[rawData.Length - 10]; // opcode(ushort)(2) + entity(int)(4) + propertyId(int)(4) + Array.Copy(rawData, 10, data, 0, rawData.Length - 10); + + var props = _entities[entityId].Properties; + if (props.ContainsKey(propertyId)) + { + props[propertyId] = data; + } + else + { + props.Add(propertyId, data); + } + + Broadcast(_readyPlayers, RagonOperation.REPLICATE_ENTITY_PROPERTY, rawData); + } + + break; + } + case RagonOperation.REPLICATE_EVENT: + { + + Broadcast(_readyPlayers, rawData); + break; + } + case RagonOperation.CREATE_ENTITY: + { + var entity = new Entity(peerId); + var data = new byte[rawData.Length - 2]; // opcode(ushort)(2) + Array.Copy(rawData, 2, data, 0, rawData.Length - 2); + + entity.State = data; + entity.Properties = new Dictionary(); + + var player = _players[peerId]; + player.Entities.Add(entity); + player.EntitiesIds.Add(entity.EntityId); + + _entities.Add(entity.EntityId, entity); + + var entityData = new byte[entity.State.Length + 8]; + ProtocolHeader.WriteEntity(entity.EntityId, entityData, 0); + ProtocolHeader.WriteEntity((int) peerId, entityData, 4); + + Array.Copy(entity.State, 0, entityData, 8, entity.State.Length); + + _logger.Trace("Create entity Owner:" + peerId + " Id: " + entity.EntityId); + + Broadcast(_readyPlayers, RagonOperation.CREATE_ENTITY, entityData); + break; + } + case RagonOperation.DESTROY_ENTITY: + { + var entityId = ProtocolHeader.ReadEntity(rawData); + if (_entities.TryGetValue(entityId, out var entity)) + { + if (entity.OwnerId == peerId) + { + var player = _players[peerId]; + + player.Entities.Remove(entity); + player.EntitiesIds.Remove(entity.EntityId); + + _entities.Remove(entityId); + + Broadcast(_readyPlayers, rawData); + } + } + + break; + } + case RagonOperation.SCENE_IS_LOADED: + { + Send(peerId, RagonOperation.RESTORE_BEGIN, Array.Empty()); + foreach (var entity in _entities.Values) + { + var entityData = new byte[entity.State.Length + 8]; + ProtocolHeader.WriteEntity(entity.EntityId, entityData, 0); + ProtocolHeader.WriteEntity((int) entity.OwnerId, entityData, 4); + + Array.Copy(entity.State, 0, entityData, 8, entity.State.Length); + Send(peerId, RagonOperation.CREATE_ENTITY, entityData); + } + Send(peerId, RagonOperation.RESTORE_END, Array.Empty()); + break; + } + case RagonOperation.RESTORED: + { + _players[peerId].IsLoaded = true; + _readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray(); + break; + } + } + } + + public void Tick(float deltaTime) + { + _ticks++; + + _plugin.OnTick(_ticks, deltaTime); + } + + public void Start() + { + _plugin.OnStart(); + } + + public void Stop() + { + _plugin.OnStop(); + } + + public void Dispose() + { + } + + public void Send(uint peerId, RagonOperation operation, byte[] payload) + { + if (payload.Length > 0) + { + var data = new byte[payload.Length + 2]; + + Array.Copy(payload, 0, data, 2, payload.Length); + ProtocolHeader.WriteOperation((ushort) operation, data); + + _roomThread.WriteOutEvent(new Event() + { + PeerId = peerId, + Data = data, + Type = EventType.DATA, + }); + } + else + { + var data = new byte[2]; + + ProtocolHeader.WriteOperation((ushort) operation, data); + + _roomThread.WriteOutEvent(new Event() + { + PeerId = peerId, + Data = data, + Type = EventType.DATA, + }); + } + } + + public void Send(uint peerId, RagonOperation operation, IData payload) + { + _buffer.Clear(); + payload.Serialize(_buffer); + _buffer.ToArray(_bytes); + + var data = new byte[_buffer.Length + 2]; + + Array.Copy(_bytes, 0, data, 2, _buffer.Length); + + ProtocolHeader.WriteOperation((ushort) operation, data); + + _roomThread.WriteOutEvent(new Event() + { + PeerId = peerId, + Data = data, + Type = EventType.DATA, + }); + } + + + public void Broadcast(uint[] peersIds, RagonOperation operation, IData payload) + { + _buffer.Clear(); + payload.Serialize(_buffer); + _buffer.ToArray(_bytes); + + var data = new byte[_buffer.Length + 2]; + + Array.Copy(_bytes, 0, data, 2, _buffer.Length); + + ProtocolHeader.WriteOperation((ushort) operation, data); + + foreach (var peer in peersIds) + { + _roomThread.WriteOutEvent(new Event() + { + PeerId = peer, + Data = data, + Type = EventType.DATA, + }); + } + } + + public void Broadcast(uint[] peersIds, RagonOperation operation, byte[] payload) + { + var data = new byte[payload.Length + 2]; + + Array.Copy(payload, 0, data, 2, payload.Length); + + ProtocolHeader.WriteOperation((ushort) operation, data); + + foreach (var peer in peersIds) + { + _roomThread.WriteOutEvent(new Event() + { + PeerId = peer, + Data = data, + Type = EventType.DATA, + }); + } + } + + public void Broadcast(uint[] peersIds, byte[] rawData) + { + foreach (var peer in peersIds) + { + _roomThread.WriteOutEvent(new Event() + { + PeerId = peer, + Data = rawData, + Type = EventType.DATA, + }); + } + } + + public void Broadcast(byte[] rawData) + { + foreach (var player in _players.Values.ToArray()) + { + _roomThread.WriteOutEvent(new Event() + { + PeerId = player.PeerId, + Data = rawData, + Type = EventType.DATA, + }); + } + } + } +} \ No newline at end of file diff --git a/Ragon/Sources/Rooms/RoomManager.cs b/Ragon/Sources/Rooms/RoomManager.cs new file mode 100644 index 0000000..4dedda6 --- /dev/null +++ b/Ragon/Sources/Rooms/RoomManager.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Text; +using NetStack.Serialization; +using NLog; +using Ragon.Common.Protocol; + +namespace Ragon.Core +{ + public class RoomManager + { + private readonly Logger _logger = LogManager.GetCurrentClassLogger(); + private List _rooms; + private Dictionary _peersByRoom; + private PluginFactory _factory; + private RoomThread _roomThread; + private BitBuffer _bitBuffer; + + public Action<(uint, Room)> OnJoined; + public Action<(uint, Room)> OnLeaved; + + public RoomManager(RoomThread roomThread, PluginFactory factory) + { + _roomThread = roomThread; + _factory = factory; + _rooms = new List(); + _peersByRoom = new Dictionary(); + _bitBuffer = new BitBuffer(1024); + } + + public void ProccessEvent(RagonOperation operation, uint peerId, 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, byte[] payload) + { + _bitBuffer.Clear(); + // _bitBuffer.FromArray(payload, payload.Length); + + // var authorizePacket = new AuthorationData(); + // authorizePacket.Deserialize(_bitBuffer); + + var data = new byte[2]; + + ProtocolHeader.WriteOperation((ushort) RagonOperation.AUTHORIZED_SUCCESS, data); + + _roomThread.WriteOutEvent(new Event() + { + Type = EventType.DATA, + Data = data, + PeerId = peerId, + }); + } + + public Room Join(uint peerId, byte[] payload) + { + var map = Encoding.UTF8.GetString(payload); + + if (_rooms.Count > 0) + { + var existsRoom = _rooms[0]; + existsRoom.Joined(peerId, payload); + _peersByRoom.Add(peerId, existsRoom); + + return existsRoom; + } + + var plugin = _factory.CreatePlugin(map); + if (plugin == null) + throw new NullReferenceException($"Plugin for map {map} is null"); + + _logger.Info("Room created"); + + var room = new Room(_roomThread, plugin, map); + room.Joined(peerId, payload); + _peersByRoom.Add(peerId, room); + + _rooms.Add(room); + + return room; + } + + public Room Left(uint peerId, byte[] payload) + { + _peersByRoom.Remove(peerId, out var room); + room?.Leave(peerId); + + return room; + } + + public void Tick(float deltaTime) + { + foreach (Room room in _rooms) + room.Tick(deltaTime); + } + } +} \ No newline at end of file diff --git a/Ragon/Sources/Rooms/RoomThread.cs b/Ragon/Sources/Rooms/RoomThread.cs new file mode 100755 index 0000000..1da823c --- /dev/null +++ b/Ragon/Sources/Rooms/RoomThread.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using DisruptorUnity3d; +using ENet; +using NetStack.Serialization; +using Ragon.Common.Protocol; + +namespace Ragon.Core +{ + public class RoomThread : IDisposable + { + private readonly RoomManager _roomManager; + private readonly Dictionary _socketByRooms; + private readonly Thread _thread; + private readonly Stopwatch _timer; + + private RingBuffer _receiveBuffer = new RingBuffer(8192 + 8192); + private RingBuffer _sendBuffer = new RingBuffer(8192 + 8192); + + 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) + { + _thread = new Thread(Execute); + _thread.IsBackground = true; + _timer = new Stopwatch(); + _socketByRooms = new Dictionary(); + + _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) + { + var deltaTime = _timer.ElapsedMilliseconds; + if (deltaTime > 1000 / 60) + { + while (_receiveBuffer.TryDequeue(out var evnt)) + { + if (evnt.Type == EventType.DISCONNECTED || evnt.Type == EventType.TIMEOUT) + { + + if (_socketByRooms.ContainsKey(evnt.PeerId)) + { + _roomManager.Left(evnt.PeerId, Array.Empty()); + _socketByRooms.Remove(evnt.PeerId); + } + } + + if (evnt.Type == EventType.DATA) + { + var operation = (RagonOperation) ProtocolHeader.ReadOperation(evnt.Data, 0); + if (_socketByRooms.TryGetValue(evnt.PeerId, out var room)) + { + room.ProcessEvent(operation, evnt.PeerId, evnt.Data); + } + else + { + var payload = new byte[evnt.Data.Length - 2]; + + Array.Copy(evnt.Data, 2, payload, 0, evnt.Data.Length - 2); + + _roomManager.ProccessEvent(operation, evnt.PeerId, payload); + } + } + } + + _roomManager.Tick(deltaTime / 1000.0f); + + _timer.Restart(); + } + else + { + Thread.Sleep(1); + } + } + } + + public void Dispose() + { + } + } +} \ No newline at end of file diff --git a/Ragon/Sources/Rooms/RoomThreadInfo.cs b/Ragon/Sources/Rooms/RoomThreadInfo.cs new file mode 100644 index 0000000..280e8e0 --- /dev/null +++ b/Ragon/Sources/Rooms/RoomThreadInfo.cs @@ -0,0 +1,9 @@ +namespace Ragon.Core +{ + public struct RoomThreadInfo + { + public int PlayersCount; + public int PlayersMax; + public bool Available; + } +} \ No newline at end of file diff --git a/Ragon/Sources/WebsocketServer.cs b/Ragon/Sources/WebsocketServer.cs new file mode 100644 index 0000000..1fb1f50 --- /dev/null +++ b/Ragon/Sources/WebsocketServer.cs @@ -0,0 +1,6 @@ +namespace Ragon.Core; + +public class WebsocketServer +{ + +} \ No newline at end of file diff --git a/Ragon/todo b/Ragon/todo new file mode 100644 index 0000000..60a5551 --- /dev/null +++ b/Ragon/todo @@ -0,0 +1,2 @@ +- Send states only on scene loaded +- Add events global and by entity \ No newline at end of file diff --git a/ThirdParty/ENet-CSharp/ENet-CSharp.dll b/ThirdParty/ENet-CSharp/ENet-CSharp.dll new file mode 100644 index 0000000..0f3f9ca Binary files /dev/null and b/ThirdParty/ENet-CSharp/ENet-CSharp.dll differ diff --git a/ThirdParty/ENet-CSharp/enet.dll b/ThirdParty/ENet-CSharp/enet.dll new file mode 100644 index 0000000..3f329c4 Binary files /dev/null and b/ThirdParty/ENet-CSharp/enet.dll differ diff --git a/ThirdParty/ENet-CSharp/libenet.dylib b/ThirdParty/ENet-CSharp/libenet.dylib new file mode 100755 index 0000000..2c8d6f3 Binary files /dev/null and b/ThirdParty/ENet-CSharp/libenet.dylib differ diff --git a/ThirdParty/ENet-CSharp/libenet.so b/ThirdParty/ENet-CSharp/libenet.so new file mode 100644 index 0000000..1617ec6 Binary files /dev/null and b/ThirdParty/ENet-CSharp/libenet.so differ diff --git a/YohohoArenaPlugin/NLog.config b/YohohoArenaPlugin/NLog.config new file mode 100755 index 0000000..55111aa --- /dev/null +++ b/YohohoArenaPlugin/NLog.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/YohohoArenaPlugin/Program.cs b/YohohoArenaPlugin/Program.cs new file mode 100755 index 0000000..4a24121 --- /dev/null +++ b/YohohoArenaPlugin/Program.cs @@ -0,0 +1,18 @@ +using System; +using Game.Source; +using NetStack.Serialization; +using Ragon.Core; + +namespace Game +{ + class Program + { + static void Main(string[] args) + { + var bootstrap = new Bootstrap(); + bootstrap.Configure(new GameFactory()); + + Console.Read(); + } + } +} \ No newline at end of file diff --git a/YohohoArenaPlugin/Source/ArenaPlugin.cs b/YohohoArenaPlugin/Source/ArenaPlugin.cs new file mode 100755 index 0000000..71f0658 --- /dev/null +++ b/YohohoArenaPlugin/Source/ArenaPlugin.cs @@ -0,0 +1,46 @@ +using NLog; +using Ragon.Core; + +namespace Game.Source +{ + public class ArenaPlugin: PluginBase + { + private ILogger _logger = LogManager.GetCurrentClassLogger(); + + public override void OnStart() + { + // _logger.Info("Plugin started"); + } + + public override void OnStop() + { + // _logger.Info("Plugin stopped"); + } + + public long FindPrimeNumber(int n) + { + int count=0; + long a = 2; + while(count 0) + { + count++; + } + a++; + } + return (--a); + } + } +} \ No newline at end of file diff --git a/YohohoArenaPlugin/Source/Events/SpawnEvent.cs b/YohohoArenaPlugin/Source/Events/SpawnEvent.cs new file mode 100644 index 0000000..d04a97d --- /dev/null +++ b/YohohoArenaPlugin/Source/Events/SpawnEvent.cs @@ -0,0 +1,6 @@ +namespace Game.Source.Events; + +public class SpawnEvent +{ + +} \ No newline at end of file diff --git a/YohohoArenaPlugin/Source/GameFactory.cs b/YohohoArenaPlugin/Source/GameFactory.cs new file mode 100644 index 0000000..2f5887d --- /dev/null +++ b/YohohoArenaPlugin/Source/GameFactory.cs @@ -0,0 +1,17 @@ +using Ragon.Core; + +namespace Game.Source +{ + public class GameFactory : PluginFactory + { + public PluginBase CreatePlugin(string map) + { + // if (map == "spawn") + // return new SpawnPlugin(); + // + // if (map == "arena") + // return new ArenaPlugin(); + return new SpawnPlugin(); + } + } +} \ No newline at end of file diff --git a/YohohoArenaPlugin/Source/SpawnPlugin.cs b/YohohoArenaPlugin/Source/SpawnPlugin.cs new file mode 100755 index 0000000..4779a31 --- /dev/null +++ b/YohohoArenaPlugin/Source/SpawnPlugin.cs @@ -0,0 +1,27 @@ +using Game.Source.Events; +using NLog; +using Ragon.Core; + +namespace Game.Source +{ + public class SpawnPlugin: PluginBase + { + private ILogger _logger = LogManager.GetCurrentClassLogger(); + + public void SpawnEvent(SpawnEvent evnt) + { + + } + public override void OnStart() + { + Subscribe(SpawnEvent); + + _logger.Info("Plugin started"); + } + + public override void OnStop() + { + _logger.Info("Plugin stopped"); + } + } +} \ No newline at end of file diff --git a/YohohoArenaPlugin/YohohoArenaPlugin.csproj b/YohohoArenaPlugin/YohohoArenaPlugin.csproj new file mode 100755 index 0000000..e15ecc8 --- /dev/null +++ b/YohohoArenaPlugin/YohohoArenaPlugin.csproj @@ -0,0 +1,22 @@ + + + + Exe + net6.0 + Game + + + + + + + + + Always + + + Always + + + + diff --git a/YohohoArenaPlugin/config.json b/YohohoArenaPlugin/config.json new file mode 100755 index 0000000..9a87d33 --- /dev/null +++ b/YohohoArenaPlugin/config.json @@ -0,0 +1,26 @@ +{ + "server": { + "port": 4444, + "skipTimeout": 60 + }, + "blacklist": [ + "пидор", + "сука", + "хуидор", + "хуй", + "Hitler", + "Гитлер", + "Гей", + "Админ", + "admin", + "падла", + "уебок", + "собака", + "пидорас", + "мразь", + "ебасос", + "еблан", + "ебучий", + "дрочь" + ] +} \ No newline at end of file diff --git a/global.json b/global.json new file mode 100755 index 0000000..cbde930 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "5.0.0", + "rollForward": "latestMajor", + "allowPrerelease": true + } +} \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100755 index 0000000..3750443 --- /dev/null +++ b/readme.md @@ -0,0 +1,3 @@ +#Ragon + +##Ragon - high perfomance game server with plugin based architecture. \ No newline at end of file