Compare commits

...

19 Commits

Author SHA1 Message Date
edmand46 65c1d9c6d4 feat: added checks and new ragon serializer methods 2022-08-14 15:39:22 +04:00
edmand46 f2934bc8ee fix: static in snapshot 2022-08-14 12:36:12 +04:00
edmand46 81879d4fe2 fix: event replication 2022-08-14 11:31:56 +04:00
edmand46 2d89d267f5 Merge remote-tracking branch 'origin/main' 2022-08-14 11:17:59 +04:00
edmand46 34107cbd5f feat: added checking of owner 2022-08-14 11:17:22 +04:00
edmand46 ba2ce25aec chore: removed some logs 2022-08-14 11:14:11 +04:00
edmand46 ca17e7de83 Merge pull request #6 from edmand46/dev
feature: State -> Properties
2022-08-13 23:28:55 +04:00
edmand46 dff6dbcf97 feat: support static entity 2022-08-13 23:22:46 +04:00
edmand46 ae485f96d4 wip 2022-08-13 18:54:02 +04:00
edmand46 74904de16b Merge pull request #5 from edmand46/dev
Target of replication event
2022-07-31 19:13:36 +04:00
edmand46 655cf3503d feat: farget for replication 2022-07-31 19:06:56 +04:00
edmand46 8fd8558323 Merge pull request #4 from edmand46/dev
Remove NetStack dependency
2022-07-31 18:20:43 +04:00
edmand46 944cb65733 refactor: remove NetStack dependency 2022-07-31 18:02:17 +04:00
edmand46 877ebdcde2 Merge pull request #3 from edmand46/dev
Replication level
2022-07-31 09:37:35 +04:00
edmand46 e2032f381a refactor: added error message on creation or joining to room 2022-07-31 09:37:05 +04:00
edmand46 85336f998e refactor: allow set roomId from client, added base room parameters 2022-07-30 16:02:53 +04:00
edmand46 16b8d3a062 feat: replication level 2022-07-30 14:07:48 +04:00
edmand46 f72b4c0904 chore: updated readme.md 2022-07-13 22:44:51 +04:00
edmand46 19272609ef chore: updated license 2022-07-13 22:30:55 +04:00
34 changed files with 564 additions and 2395 deletions
-69
View File
@@ -1,69 +0,0 @@
/*
* Copyright (c) 2018 Stanislav Denisov
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System.Threading;
using System.Runtime.CompilerServices;
namespace NetStack.Buffers {
public abstract class ArrayPool<T> {
#if NET_4_6 || NET_STANDARD_2_0
private static ArrayPool<T> s_sharedInstance = null;
#else
private static volatile ArrayPool<T> s_sharedInstance = null;
#endif
public static ArrayPool<T> Shared {
#if NET_4_6 || NET_STANDARD_2_0
[MethodImpl(256)]
get {
return Volatile.Read(ref s_sharedInstance) ?? EnsureSharedCreated();
}
#else
[MethodImpl(256)]
get {
return s_sharedInstance ?? EnsureSharedCreated();
}
#endif
}
#pragma warning disable 420
[MethodImpl(MethodImplOptions.NoInlining)]
private static ArrayPool<T> EnsureSharedCreated() {
Interlocked.CompareExchange(ref s_sharedInstance, Create(), null);
return s_sharedInstance;
}
public static ArrayPool<T> Create() {
return new DefaultArrayPool<T>();
}
public static ArrayPool<T> Create(int maxArrayLength, int maxArraysPerBucket) {
return new DefaultArrayPool<T>(maxArrayLength, maxArraysPerBucket);
}
public abstract T[] Rent(int minimumLength);
public abstract void Return(T[] array, bool clearArray = false);
}
}
@@ -1,82 +0,0 @@
/*
* Copyright (c) 2018 Stanislav Denisov
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System;
#if ENABLE_MONO || ENABLE_IL2CPP
using UnityEngine;
#endif
namespace NetStack.Buffers {
internal sealed class ArrayPoolEventSource {
#if NETSTACK_BUFFERS_LOG
internal static readonly ArrayPoolEventSource EventLog = new ArrayPoolEventSource();
internal enum BufferAllocatedReason : int {
Pooled,
OverMaximumSize,
PoolExhausted
}
internal void BufferAllocated(int bufferId, int bufferSize, int poolId, int bucketId, BufferAllocatedReason reason) {
var message = "Buffer allocated (Buffer ID: " + bufferId + ", Buffer size: " + bufferSize + ", Pool ID: " + poolId + ", Bucket ID: " + bucketId + ", Reason: " + reason + ")";
if (reason == BufferAllocatedReason.Pooled)
Log.Info("Buffers", message);
else
Log.Warning("Buffers", message);
}
internal void BufferRented(int bufferId, int bufferSize, int poolId, int bucketId) {
Log.Info("Buffers", "Buffer rented (Buffer ID: " + bufferId + ", Buffer size: " + bufferSize + ", Pool ID: " + poolId + ", Bucket ID: " + bucketId + ")");
}
internal void BufferReturned(int bufferId, int bufferSize, int poolId) {
Log.Info("Buffers", "Buffer returned (Buffer ID: " + bufferId + ", Buffer size: " + bufferSize + ", Pool ID: " + poolId + ")");
}
#endif
}
internal static class Log {
private static string Output(string module, string message) {
return DateTime.Now.ToString("[HH:mm:ss]") + " [NetStack." + module + "] " + message;
}
public static void Info(string module, string message) {
#if ENABLE_MONO || ENABLE_IL2CPP
Debug.Log(Output(module, message));
#else
Console.WriteLine(Output(module, message));
#endif
}
public static void Warning(string module, string message) {
#if ENABLE_MONO || ENABLE_IL2CPP
Debug.LogWarning(Output(module, message));
#else
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(Output(module, message));
Console.ResetColor();
#endif
}
}
}
@@ -1,134 +0,0 @@
/*
* Copyright (c) 2018 Stanislav Denisov
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System;
namespace NetStack.Buffers {
internal sealed partial class DefaultArrayPool<T> : ArrayPool<T> {
private const int DefaultMaxArrayLength = 1024 * 1024;
private const int DefaultMaxNumberOfArraysPerBucket = 50;
private static T[] s_emptyArray;
private readonly Bucket[] _buckets;
internal DefaultArrayPool() : this(DefaultMaxArrayLength, DefaultMaxNumberOfArraysPerBucket) { }
internal DefaultArrayPool(int maxArrayLength, int maxArraysPerBucket) {
if (maxArrayLength <= 0)
throw new ArgumentOutOfRangeException("maxArrayLength");
if (maxArraysPerBucket <= 0)
throw new ArgumentOutOfRangeException("maxArraysPerBucket");
const int MinimumArrayLength = 0x10, MaximumArrayLength = 0x40000000;
if (maxArrayLength > MaximumArrayLength)
maxArrayLength = MaximumArrayLength;
else if (maxArrayLength < MinimumArrayLength)
maxArrayLength = MinimumArrayLength;
int poolId = Id;
int maxBuckets = Utilities.SelectBucketIndex(maxArrayLength);
var buckets = new Bucket[maxBuckets + 1];
for (int i = 0; i < buckets.Length; i++) {
buckets[i] = new Bucket(Utilities.GetMaxSizeForBucket(i), maxArraysPerBucket, poolId);
}
_buckets = buckets;
}
private int Id {
get {
return GetHashCode();
}
}
public override T[] Rent(int minimumLength) {
if (minimumLength < 0)
throw new ArgumentOutOfRangeException("minimumLength");
else if (minimumLength == 0)
return s_emptyArray ?? (s_emptyArray = new T[0]);
#if NETSTACK_BUFFERS_LOG
var log = ArrayPoolEventSource.EventLog;
#endif
T[] buffer = null;
int index = Utilities.SelectBucketIndex(minimumLength);
if (index < _buckets.Length) {
const int MaxBucketsToTry = 2;
int i = index;
do {
buffer = _buckets[i].Rent();
if (buffer != null) {
#if NETSTACK_BUFFERS_LOG
log.BufferRented(buffer.GetHashCode(), buffer.Length, Id, _buckets[i].Id);
#endif
return buffer;
}
}
while (++i < _buckets.Length && i != index + MaxBucketsToTry);
buffer = new T[_buckets[index]._bufferLength];
} else {
buffer = new T[minimumLength];
}
#if NETSTACK_BUFFERS_LOG
int bufferId = buffer.GetHashCode(), bucketId = -1;
log.BufferRented(bufferId, buffer.Length, Id, bucketId);
log.BufferAllocated(bufferId, buffer.Length, Id, bucketId, index >= _buckets.Length ? ArrayPoolEventSource.BufferAllocatedReason.OverMaximumSize : ArrayPoolEventSource.BufferAllocatedReason.PoolExhausted);
#endif
return buffer;
}
public override void Return(T[] array, bool clearArray = false) {
if (array == null)
throw new ArgumentNullException("array");
else if (array.Length == 0)
return;
int bucket = Utilities.SelectBucketIndex(array.Length);
if (bucket < _buckets.Length) {
if (clearArray)
Array.Clear(array, 0, array.Length);
_buckets[bucket].Return(array);
}
#if NETSTACK_BUFFERS_LOG
var log = ArrayPoolEventSource.EventLog;
log.BufferReturned(array.GetHashCode(), array.Length, Id);
#endif
}
}
}
@@ -1,151 +0,0 @@
/*
* Copyright (c) 2018 Stanislav Denisov
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System;
using System.Diagnostics;
using System.Threading;
namespace NetStack.Buffers {
internal sealed partial class DefaultArrayPool<T> : ArrayPool<T> {
private sealed class Bucket {
internal readonly int _bufferLength;
private readonly T[][] _buffers;
#if NETSTACK_BUFFERS_LOG
private readonly int _poolId;
#endif
#if NET_4_6 || NET_STANDARD_2_0
private SpinLock _lock;
#else
private object _lock;
#endif
private int _index;
internal Bucket(int bufferLength, int numberOfBuffers, int poolId) {
#if NET_4_6 || NET_STANDARD_2_0
_lock = new SpinLock();
#else
_lock = new Object();
#endif
_buffers = new T[numberOfBuffers][];
_bufferLength = bufferLength;
#if NETSTACK_BUFFERS_LOG
_poolId = poolId;
#endif
}
#if NET_4_6 || NET_STANDARD_2_0
internal int Id => GetHashCode();
#else
internal int Id {
get {
return GetHashCode();
}
}
#endif
internal T[] Rent() {
T[][] buffers = _buffers;
T[] buffer = null;
bool allocateBuffer = false;
#if NET_4_6 || NET_STANDARD_2_0
bool lockTaken = false;
try {
_lock.Enter(ref lockTaken);
if (_index < buffers.Length) {
buffer = buffers[_index];
buffers[_index++] = null;
allocateBuffer = buffer == null;
}
}
finally {
if (lockTaken)
_lock.Exit(false);
}
#else
try {
Monitor.Enter(_lock);
if (_index < buffers.Length) {
buffer = buffers[_index];
buffers[_index++] = null;
allocateBuffer = buffer == null;
}
}
finally {
Monitor.Exit(_lock);
}
#endif
if (allocateBuffer) {
buffer = new T[_bufferLength];
#if NETSTACK_BUFFERS_LOG
var log = ArrayPoolEventSource.EventLog;
log.BufferAllocated(buffer.GetHashCode(), _bufferLength, _poolId, Id, ArrayPoolEventSource.BufferAllocatedReason.Pooled);
#endif
}
return buffer;
}
internal void Return(T[] array) {
if (array.Length != _bufferLength)
throw new ArgumentException("BufferNotFromPool", "array");
#if NET_4_6 || NET_STANDARD_2_0
bool lockTaken = false;
try {
_lock.Enter(ref lockTaken);
if (_index != 0)
_buffers[--_index] = array;
}
finally {
if (lockTaken)
_lock.Exit(false);
}
#else
try {
Monitor.Enter(_lock);
if (_index != 0)
_buffers[--_index] = array;
}
finally {
Monitor.Exit(_lock);
}
#endif
}
}
}
}
-84
View File
@@ -1,84 +0,0 @@
/*
* Copyright (c) 2018 Stanislav Denisov
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System.Diagnostics;
using System.Runtime.CompilerServices;
#if ENABLE_MONO || ENABLE_IL2CPP
using UnityEngine.Assertions;
#endif
namespace NetStack.Buffers {
internal static class Utilities {
[MethodImpl(256)]
internal static int SelectBucketIndex(int bufferSize) {
#if ENABLE_MONO || ENABLE_IL2CPP
Assert.IsTrue(bufferSize > 0);
#else
Debug.Assert(bufferSize > 0);
#endif
uint bitsRemaining = ((uint)bufferSize - 1) >> 4;
int poolIndex = 0;
if (bitsRemaining > 0xFFFF) {
bitsRemaining >>= 16;
poolIndex = 16;
}
if (bitsRemaining > 0xFF) {
bitsRemaining >>= 8;
poolIndex += 8;
}
if (bitsRemaining > 0xF) {
bitsRemaining >>= 4;
poolIndex += 4;
}
if (bitsRemaining > 0x3) {
bitsRemaining >>= 2;
poolIndex += 2;
}
if (bitsRemaining > 0x1) {
bitsRemaining >>= 1;
poolIndex += 1;
}
return poolIndex + (int)bitsRemaining;
}
[MethodImpl(256)]
internal static int GetMaxSizeForBucket(int binIndex) {
int maxSize = 16 << binIndex;
#if ENABLE_MONO || ENABLE_IL2CPP
Assert.IsTrue(maxSize >= 0);
#else
Debug.Assert(maxSize >= 0);
#endif
return maxSize;
}
}
}
@@ -1,184 +0,0 @@
/*
* Copyright (c) 2018 Stanislav Denisov
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System;
using System.Runtime.CompilerServices;
#if !(ENABLE_MONO || ENABLE_IL2CPP)
using System.Numerics;
#else
using UnityEngine;
#endif
namespace NetStack.Quantization {
public struct QuantizedVector2 {
public uint x;
public uint y;
public QuantizedVector2(uint x, uint y) {
this.x = x;
this.y = y;
}
}
public struct QuantizedVector3 {
public uint x;
public uint y;
public uint z;
public QuantizedVector3(uint x, uint y, uint z) {
this.x = x;
this.y = y;
this.z = z;
}
}
public struct QuantizedVector4 {
public uint x;
public uint y;
public uint z;
public uint w;
public QuantizedVector4(uint x, uint y, uint z, uint w) {
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
}
public static class DeBruijn {
public static readonly int[] Lookup = new int[32] {
0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30,
8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31
};
}
public class BoundedRange {
private readonly float minValue;
private readonly float maxValue;
private readonly float precision;
private readonly int requiredBits;
private readonly uint mask;
public BoundedRange(float minValue, float maxValue, float precision) {
this.minValue = minValue;
this.maxValue = maxValue;
this.precision = precision;
requiredBits = Log2((uint)((maxValue - minValue) * (1.0f / precision) + 0.5f)) + 1;
mask = (uint)((1L << requiredBits) - 1);
}
private int Log2(uint value) {
value |= value >> 1;
value |= value >> 2;
value |= value >> 4;
value |= value >> 8;
value |= value >> 16;
return DeBruijn.Lookup[(value * 0x07C4ACDDU) >> 27];
}
[MethodImpl(256)]
public uint Quantize(float value) {
if (value < minValue)
value = minValue;
else if (value > maxValue)
value = maxValue;
return (uint)((float)((value - minValue) * (1f / precision)) + 0.5f) & mask;
}
[MethodImpl(256)]
public float Dequantize(uint data) {
float adjusted = ((float)data * precision) + minValue;
if (adjusted < minValue)
adjusted = minValue;
else if (adjusted > maxValue)
adjusted = maxValue;
return adjusted;
}
public static QuantizedVector2 Quantize(Vector2 vector2, BoundedRange[] boundedRange) {
QuantizedVector2 data = default(QuantizedVector2);
#if ENABLE_MONO || ENABLE_IL2CPP
data.x = boundedRange[0].Quantize(vector2.x);
data.y = boundedRange[1].Quantize(vector2.y);
#else
data.x = boundedRange[0].Quantize(vector2.X);
data.y = boundedRange[1].Quantize(vector2.Y);
#endif
return data;
}
public static QuantizedVector3 Quantize(Vector3 vector3, BoundedRange[] boundedRange) {
QuantizedVector3 data = default(QuantizedVector3);
#if ENABLE_MONO || ENABLE_IL2CPP
data.x = boundedRange[0].Quantize(vector3.x);
data.y = boundedRange[1].Quantize(vector3.y);
data.z = boundedRange[2].Quantize(vector3.z);
#else
data.x = boundedRange[0].Quantize(vector3.X);
data.y = boundedRange[1].Quantize(vector3.Y);
data.z = boundedRange[2].Quantize(vector3.Z);
#endif
return data;
}
public static QuantizedVector4 Quantize(Vector4 vector4, BoundedRange[] boundedRange) {
QuantizedVector4 data = default(QuantizedVector4);
#if ENABLE_MONO || ENABLE_IL2CPP
data.x = boundedRange[0].Quantize(vector4.x);
data.y = boundedRange[1].Quantize(vector4.y);
data.z = boundedRange[2].Quantize(vector4.z);
data.w = boundedRange[3].Quantize(vector4.w);
#else
data.x = boundedRange[0].Quantize(vector4.X);
data.y = boundedRange[1].Quantize(vector4.Y);
data.z = boundedRange[2].Quantize(vector4.Z);
data.w = boundedRange[3].Quantize(vector4.W);
#endif
return data;
}
public static Vector2 Dequantize(QuantizedVector2 data, BoundedRange[] boundedRange) {
return new Vector2(boundedRange[0].Dequantize(data.x), boundedRange[1].Dequantize(data.y));
}
public static Vector3 Dequantize(QuantizedVector3 data, BoundedRange[] boundedRange) {
return new Vector3(boundedRange[0].Dequantize(data.x), boundedRange[1].Dequantize(data.y), boundedRange[2].Dequantize(data.z));
}
public static Vector4 Dequantize(QuantizedVector4 data, BoundedRange[] boundedRange) {
return new Vector4(boundedRange[0].Dequantize(data.x), boundedRange[1].Dequantize(data.y), boundedRange[2].Dequantize(data.z), boundedRange[3].Dequantize(data.w));
}
}
}
@@ -1,117 +0,0 @@
/*
* Copyright (c) 2018 Stanislav Denisov
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace NetStack.Quantization {
public static class HalfPrecision {
[StructLayout(LayoutKind.Explicit)]
private struct Values {
[FieldOffset(0)]
public float f;
[FieldOffset(0)]
public int i;
[FieldOffset(0)]
public uint u;
}
[MethodImpl(256)]
public static ushort Quantize(float value) {
var values = new Values {
f = value
};
return Quantize(values.i);
}
public static ushort Quantize(int value) {
int s = (value >> 16) & 0x00008000;
int e = ((value >> 23) & 0X000000FF) - (127 - 15);
int m = value & 0X007FFFFF;
if (e <= 0) {
if (e < -10)
return (ushort)s;
m = m | 0x00800000;
int t = 14 - e;
int a = (1 << (t - 1)) - 1;
int b = (m >> t) & 1;
m = (m + a + b) >> t;
return (ushort)(s | m);
}
if (e == 0XFF - (127 - 15)) {
if (m == 0)
return (ushort)(s | 0X7C00);
m >>= 13;
return (ushort)(s | 0X7C00 | m | ((m == 0) ? 1 : 0));
}
m = m + 0X00000FFF + ((m >> 13) & 1);
if ((m & 0x00800000) != 0) {
m = 0;
e++;
}
if (e > 30)
return (ushort)(s | 0X7C00);
return (ushort)(s | (e << 10) | (m >> 13));
}
public static float Dequantize(ushort value) {
uint result;
uint mantissa = (uint)(value & 1023);
uint exponent = 0XFFFFFFF2;
if ((value & -33792) == 0) {
if (mantissa != 0) {
while ((mantissa & 1024) == 0) {
exponent--;
mantissa = mantissa << 1;
}
mantissa &= 0XFFFFFBFF;
result = ((uint)((((uint)value & 0x8000) << 16) | ((exponent + 127) << 23))) | (mantissa << 13);
} else {
result = (uint)((value & 0x8000) << 16);
}
} else {
result = ((((uint)value & 0x8000) << 16) | ((((((uint)value >> 10) & 0X1F) - 15) + 127) << 23)) | (mantissa << 13);
}
var values = new Values {
u = result
};
return values.f;
}
}
}
@@ -1,223 +0,0 @@
/*
* Copyright (c) 2020 Stanislav Denisov, Maxim Munning, Davin Carten
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System;
#if !(ENABLE_MONO || ENABLE_IL2CPP)
using System.Numerics;
#else
using UnityEngine;
#endif
namespace NetStack.Quantization {
public struct QuantizedQuaternion {
public uint m;
public uint a;
public uint b;
public uint c;
public QuantizedQuaternion(uint m, uint a, uint b, uint c) {
this.m = m;
this.a = a;
this.b = b;
this.c = c;
}
}
public static class SmallestThree {
private const float smallestThreeUnpack = 0.70710678118654752440084436210485f + 0.0000001f;
private const float smallestThreePack = 1.0f / smallestThreeUnpack;
public static QuantizedQuaternion Quantize(Quaternion quaternion, int bitsPerElement = 12) {
float halfRange = (1 << bitsPerElement - 1);
float packer = smallestThreePack * halfRange;
float maxValue = float.MinValue;
bool signMinus = false;
uint m = 0;
uint a = 0;
uint b = 0;
uint c = 0;
for (uint i = 0; i <= 3; i++) {
float element = 0.0f;
float abs = 0.0f;
switch (i) {
#if ENABLE_MONO || ENABLE_IL2CPP
case 0:
element = quaternion.x;
break;
case 1:
element = quaternion.y;
break;
case 2:
element = quaternion.z;
break;
case 3:
element = quaternion.w;
break;
#else
case 0:
element = quaternion.X;
break;
case 1:
element = quaternion.Y;
break;
case 2:
element = quaternion.Z;
break;
case 3:
element = quaternion.W;
break;
#endif
}
abs = Math.Abs(element);
if (abs > maxValue) {
signMinus = (element < 0.0f);
m = i;
maxValue = abs;
}
}
float af = 0.0f;
float bf = 0.0f;
float cf = 0.0f;
#if ENABLE_MONO || ENABLE_IL2CPP
switch (m) {
case 0:
af = quaternion.y;
bf = quaternion.z;
cf = quaternion.w;
break;
case 1:
af = quaternion.x;
bf = quaternion.z;
cf = quaternion.w;
break;
case 2:
af = quaternion.x;
bf = quaternion.y;
cf = quaternion.w;
break;
default:
af = quaternion.x;
bf = quaternion.y;
cf = quaternion.z;
break;
}
#else
switch (m) {
case 0:
af = quaternion.Y;
bf = quaternion.Z;
cf = quaternion.W;
break;
case 1:
af = quaternion.X;
bf = quaternion.Z;
cf = quaternion.W;
break;
case 2:
af = quaternion.X;
bf = quaternion.Y;
cf = quaternion.W;
break;
default:
af = quaternion.X;
bf = quaternion.Y;
cf = quaternion.Z;
break;
}
#endif
if (signMinus) {
a = (uint)((-af * packer) + halfRange);
b = (uint)((-bf * packer) + halfRange);
c = (uint)((-cf * packer) + halfRange);
} else {
a = (uint)((af * packer) + halfRange);
b = (uint)((bf * packer) + halfRange);
c = (uint)((cf * packer) + halfRange);
}
return new QuantizedQuaternion(m, a, b, c);
}
public static Quaternion Dequantize(QuantizedQuaternion data, int bitsPerElement = 12) {
int halfRange = (1 << bitsPerElement - 1);
float unpacker = smallestThreeUnpack * (1.0f / halfRange);
uint m = data.m;
int ai = (int)data.a;
int bi = (int)data.b;
int ci = (int)data.c;
ai -= halfRange;
bi -= halfRange;
ci -= halfRange;
float a = ai * unpacker;
float b = bi * unpacker;
float c = ci * unpacker;
float d = (float)Math.Sqrt(1.0f - ((a * a) + (b * b) + (c * c)));
switch (m) {
case 0:
return new Quaternion(d, a, b, c);
case 1:
return new Quaternion(a, d, b, c);
case 2:
return new Quaternion(a, b, d, c);
default:
return new Quaternion(a, b, c, d);
}
}
}
}
@@ -1,546 +0,0 @@
/*
* Copyright (c) 2018 Stanislav Denisov, Maxim Munnig
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/*
* Copyright (c) 2018 Alexander Shoulson
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text;
#if ENABLE_MONO || ENABLE_IL2CPP
using UnityEngine.Assertions;
#endif
namespace NetStack.Serialization {
public class BitBuffer {
private const int defaultCapacity = 375; // 375 * 4 = 1500 bytes
private const int stringLengthBits = 8;
private const int stringLengthMax = (1 << stringLengthBits) - 1; // 255
private const int bitsASCII = 7;
private const int growFactor = 2;
private const int minGrow = 1;
private int readPosition;
private int nextPosition;
private uint[] chunks;
public BitBuffer(int capacity = defaultCapacity) {
readPosition = 0;
nextPosition = 0;
chunks = new uint[capacity];
}
public int Length {
get {
return ((nextPosition - 1) >> 3) + 1;
}
}
public bool IsFinished {
get {
return nextPosition == readPosition;
}
}
[MethodImpl(256)]
public void Clear() {
readPosition = 0;
nextPosition = 0;
}
[MethodImpl(256)]
public BitBuffer Add(int numBits, uint value) {
#if ENABLE_MONO || ENABLE_IL2CPP
Assert.IsFalse(numBits < 0); // Pushing negative bits
Assert.IsFalse(numBits > 32); // Pushing too many bits
#else
Debug.Assert(!(numBits < 0));
Debug.Assert(!(numBits > 32));
#endif
int index = nextPosition >> 5;
int used = nextPosition & 0x0000001F;
if ((index + 1) >= chunks.Length)
ExpandArray();
ulong chunkMask = ((1UL << used) - 1);
ulong scratch = chunks[index] & chunkMask;
ulong result = scratch | ((ulong)value << used);
chunks[index] = (uint)result;
chunks[index + 1] = (uint)(result >> 32);
nextPosition += numBits;
return this;
}
[MethodImpl(256)]
public uint Read(int numBits) {
uint result = Peek(numBits);
readPosition += numBits;
return result;
}
[MethodImpl(256)]
public uint Peek(int numBits) {
#if ENABLE_MONO || ENABLE_IL2CPP
Assert.IsFalse(numBits < 0); // Pushing negative bits
Assert.IsFalse(numBits > 32); // Pushing too many bits
#else
Debug.Assert(!(numBits < 0));
Debug.Assert(!(numBits > 32));
#endif
int index = readPosition >> 5;
int used = readPosition & 0x0000001F;
ulong chunkMask = ((1UL << numBits) - 1) << used;
ulong scratch = (ulong)chunks[index];
if ((index + 1) < chunks.Length)
scratch |= (ulong)chunks[index + 1] << 32;
ulong result = (scratch & chunkMask) >> used;
return (uint)result;
}
public int ToArray(byte[] data) {
Add(1, 1);
int numChunks = (nextPosition >> 5) + 1;
int length = data.Length;
for (int i = 0; i < numChunks; i++) {
int dataIdx = i * 4;
uint chunk = chunks[i];
if (dataIdx < length)
data[dataIdx] = (byte)(chunk);
if (dataIdx + 1 < length)
data[dataIdx + 1] = (byte)(chunk >> 8);
if (dataIdx + 2 < length)
data[dataIdx + 2] = (byte)(chunk >> 16);
if (dataIdx + 3 < length)
data[dataIdx + 3] = (byte)(chunk >> 24);
}
return Length;
}
public void FromArray(byte[] data, int length) {
int numChunks = (length / 4) + 1;
if (chunks.Length < numChunks)
chunks = new uint[numChunks];
for (int i = 0; i < numChunks; i++) {
int dataIdx = i * 4;
uint chunk = 0;
if (dataIdx < length)
chunk = (uint)data[dataIdx];
if (dataIdx + 1 < length)
chunk = chunk | (uint)data[dataIdx + 1] << 8;
if (dataIdx + 2 < length)
chunk = chunk | (uint)data[dataIdx + 2] << 16;
if (dataIdx + 3 < length)
chunk = chunk | (uint)data[dataIdx + 3] << 24;
chunks[i] = chunk;
}
int positionInByte = FindHighestBitPosition(data[length - 1]);
nextPosition = ((length - 1) * 8) + (positionInByte - 1);
readPosition = 0;
}
#if NETSTACK_SPAN
public int ToSpan(ref Span<byte> data) {
Add(1, 1);
int numChunks = (nextPosition >> 5) + 1;
int length = data.Length;
for (int i = 0; i < numChunks; i++) {
int dataIdx = i * 4;
uint chunk = chunks[i];
if (dataIdx < length)
data[dataIdx] = (byte)(chunk);
if (dataIdx + 1 < length)
data[dataIdx + 1] = (byte)(chunk >> 8);
if (dataIdx + 2 < length)
data[dataIdx + 2] = (byte)(chunk >> 16);
if (dataIdx + 3 < length)
data[dataIdx + 3] = (byte)(chunk >> 24);
}
return Length;
}
public void FromSpan(ref ReadOnlySpan<byte> data, int length) {
int numChunks = (length / 4) + 1;
if (chunks.Length < numChunks)
chunks = new uint[numChunks];
for (int i = 0; i < numChunks; i++) {
int dataIdx = i * 4;
uint chunk = 0;
if (dataIdx < length)
chunk = (uint)data[dataIdx];
if (dataIdx + 1 < length)
chunk = chunk | (uint)data[dataIdx + 1] << 8;
if (dataIdx + 2 < length)
chunk = chunk | (uint)data[dataIdx + 2] << 16;
if (dataIdx + 3 < length)
chunk = chunk | (uint)data[dataIdx + 3] << 24;
chunks[i] = chunk;
}
int positionInByte = FindHighestBitPosition(data[length - 1]);
nextPosition = ((length - 1) * 8) + (positionInByte - 1);
readPosition = 0;
}
#endif
[MethodImpl(256)]
public BitBuffer AddBool(bool value) {
Add(1, value ? 1U : 0U);
return this;
}
[MethodImpl(256)]
public bool ReadBool() {
return Read(1) > 0;
}
[MethodImpl(256)]
public bool PeekBool() {
return Peek(1) > 0;
}
[MethodImpl(256)]
public BitBuffer AddByte(byte value) {
Add(8, value);
return this;
}
[MethodImpl(256)]
public byte ReadByte() {
return (byte)Read(8);
}
[MethodImpl(256)]
public byte PeekByte() {
return (byte)Peek(8);
}
[MethodImpl(256)]
public BitBuffer AddShort(short value) {
AddInt(value);
return this;
}
[MethodImpl(256)]
public short ReadShort() {
return (short)ReadInt();
}
[MethodImpl(256)]
public short PeekShort() {
return (short)PeekInt();
}
[MethodImpl(256)]
public BitBuffer AddUShort(ushort value) {
AddUInt(value);
return this;
}
[MethodImpl(256)]
public ushort ReadUShort() {
return (ushort)ReadUInt();
}
[MethodImpl(256)]
public ushort PeekUShort() {
return (ushort)PeekUInt();
}
[MethodImpl(256)]
public BitBuffer AddInt(int value) {
uint zigzag = (uint)((value << 1) ^ (value >> 31));
AddUInt(zigzag);
return this;
}
[MethodImpl(256)]
public int ReadInt() {
uint value = ReadUInt();
int zagzig = (int)((value >> 1) ^ (-(int)(value & 1)));
return zagzig;
}
[MethodImpl(256)]
public int PeekInt() {
uint value = PeekUInt();
int zagzig = (int)((value >> 1) ^ (-(int)(value & 1)));
return zagzig;
}
[MethodImpl(256)]
public BitBuffer AddUInt(uint value) {
uint buffer = 0x0u;
do {
buffer = value & 0x7Fu;
value >>= 7;
if (value > 0)
buffer |= 0x80u;
Add(8, buffer);
}
while (value > 0);
return this;
}
[MethodImpl(256)]
public uint ReadUInt() {
uint buffer = 0x0u;
uint value = 0x0u;
int shift = 0;
do {
buffer = Read(8);
value |= (buffer & 0x7Fu) << shift;
shift += 7;
}
while ((buffer & 0x80u) > 0);
return value;
}
[MethodImpl(256)]
public uint PeekUInt() {
int tempPosition = readPosition;
uint value = ReadUInt();
readPosition = tempPosition;
return value;
}
[MethodImpl(256)]
public BitBuffer AddLong(long value) {
AddInt((int)(value & uint.MaxValue));
AddInt((int)(value >> 32));
return this;
}
[MethodImpl(256)]
public long ReadLong() {
int low = ReadInt();
int high = ReadInt();
long value = high;
return value << 32 | (uint)low;
}
[MethodImpl(256)]
public long PeekLong() {
int tempPosition = readPosition;
long value = ReadLong();
readPosition = tempPosition;
return value;
}
[MethodImpl(256)]
public BitBuffer AddULong(ulong value) {
AddUInt((uint)(value & uint.MaxValue));
AddUInt((uint)(value >> 32));
return this;
}
[MethodImpl(256)]
public ulong ReadULong() {
uint low = ReadUInt();
uint high = ReadUInt();
return (ulong)high << 32 | low;
}
[MethodImpl(256)]
public ulong PeekULong() {
int tempPosition = readPosition;
ulong value = ReadULong();
readPosition = tempPosition;
return value;
}
[MethodImpl(256)]
public BitBuffer AddString(string value) {
if (value == null)
throw new ArgumentNullException("value");
uint length = (uint)value.Length;
if (length > stringLengthMax) {
length = (uint)stringLengthMax;
throw new ArgumentOutOfRangeException("value length exceeded");
}
Add(stringLengthBits, length);
for (int i = 0; i < length; i++) {
Add(bitsASCII, ToASCII(value[i]));
}
return this;
}
[MethodImpl(256)]
public string ReadString() {
StringBuilder builder = new StringBuilder();
uint length = Read(stringLengthBits);
for (int i = 0; i < length; i++) {
builder.Append((char)Read(bitsASCII));
}
return builder.ToString();
}
public override string ToString() {
StringBuilder builder = new StringBuilder();
for (int i = chunks.Length - 1; i >= 0; i--) {
builder.Append(Convert.ToString(chunks[i], 2).PadLeft(32, '0'));
}
StringBuilder spaced = new StringBuilder();
for (int i = 0; i < builder.Length; i++) {
spaced.Append(builder[i]);
if (((i + 1) % 8) == 0)
spaced.Append(" ");
}
return spaced.ToString();
}
private void ExpandArray() {
int newCapacity = (chunks.Length * growFactor) + minGrow;
uint[] newChunks = new uint[newCapacity];
Array.Copy(chunks, newChunks, chunks.Length);
chunks = newChunks;
}
[MethodImpl(256)]
private static int FindHighestBitPosition(byte data) {
int shiftCount = 0;
while (data > 0) {
data >>= 1;
shiftCount++;
}
return shiftCount;
}
private static byte ToASCII(char character) {
byte value = 0;
try {
value = Convert.ToByte(character);
}
catch (OverflowException) {
throw new Exception("Cannot convert to ASCII: " + character);
}
if (value > 127)
throw new Exception("Cannot convert to ASCII: " + character);
return value;
}
}
}
-150
View File
@@ -1,150 +0,0 @@
/*
* Copyright (c) 2019 Stanislav Denisov
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace NetStack.Threading {
[StructLayout(LayoutKind.Explicit, Size = 192)]
public sealed class ArrayQueue {
[FieldOffset(0)]
private readonly Entry[] _array;
[FieldOffset(8)]
private readonly int _arrayMask;
[FieldOffset(64)]
private int _enqueuePosition;
[FieldOffset(128)]
private int _dequeuePosition;
public int Count {
get {
return _enqueuePosition - _dequeuePosition;
}
}
public ArrayQueue(int capacity) {
if (capacity < 2)
throw new ArgumentException("Queue size should be greater than or equal to two");
if ((capacity & (capacity - 1)) != 0)
throw new ArgumentException("Queue size should be a power of two");
_arrayMask = capacity - 1;
_array = new Entry[capacity];
_enqueuePosition = 0;
_dequeuePosition = 0;
}
public void Enqueue(object item) {
while (true) {
if (TryEnqueue(item))
break;
Thread.SpinWait(1);
}
}
public bool TryEnqueue(object item) {
var array = _array;
var position = _enqueuePosition;
var index = position & _arrayMask;
if (array[index].IsSet != 0)
return false;
array[index].element = item;
array[index].IsSet = 1;
#if NET_4_6 || NET_STANDARD_2_0
Volatile.Write(ref _enqueuePosition, position + 1);
#else
Thread.MemoryBarrier();
_enqueuePosition = position + 1;
#endif
return true;
}
public object Dequeue() {
while (true) {
object element;
if (TryDequeue(out element))
return element;
}
}
public bool TryDequeue(out object result) {
var array = _array;
var position = _dequeuePosition;
var index = position & _arrayMask;
if (array[index].IsSet == 0) {
result = default(object);
return false;
}
result = array[index].element;
array[index].element = default(object);
array[index].IsSet = 0;
#if NET_4_6 || NET_STANDARD_2_0
Volatile.Write(ref _dequeuePosition, position + 1);
#else
Thread.MemoryBarrier();
_dequeuePosition = position + 1;
#endif
return true;
}
[StructLayout(LayoutKind.Explicit, Size = 16)]
private struct Entry {
[FieldOffset(0)]
private int isSet;
[FieldOffset(8)]
internal object element;
internal int IsSet {
get {
#if NET_4_6 || NET_STANDARD_2_0
return Volatile.Read(ref isSet);
#else
Thread.MemoryBarrier();
return isSet;
#endif
}
set {
#if NET_4_6 || NET_STANDARD_2_0
Volatile.Write(ref isSet, value);
#else
Thread.MemoryBarrier();
isSet = value;
#endif
}
}
}
}
}
@@ -1,153 +0,0 @@
/*
* Copyright (c) 2018 Alexander Nikitin, Stanislav Denisov
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace NetStack.Threading {
[StructLayout(LayoutKind.Explicit, Size = 192)]
public sealed class ConcurrentBuffer {
[FieldOffset(0)]
private readonly Cell[] _buffer;
[FieldOffset(8)]
private readonly int _bufferMask;
[FieldOffset(64)]
private int _enqueuePosition;
[FieldOffset(128)]
private int _dequeuePosition;
public int Count {
get {
return _enqueuePosition - _dequeuePosition;
}
}
public ConcurrentBuffer(int bufferSize) {
if (bufferSize < 2)
throw new ArgumentException("Buffer size should be greater than or equal to two");
if ((bufferSize & (bufferSize - 1)) != 0)
throw new ArgumentException("Buffer size should be a power of two");
_bufferMask = bufferSize - 1;
_buffer = new Cell[bufferSize];
for (var i = 0; i < bufferSize; i++) {
_buffer[i] = new Cell(i, null);
}
_enqueuePosition = 0;
_dequeuePosition = 0;
}
public void Enqueue(object item) {
while (true) {
if (TryEnqueue(item))
break;
Thread.SpinWait(1);
}
}
public bool TryEnqueue(object item) {
do {
var buffer = _buffer;
var position = _enqueuePosition;
var index = position & _bufferMask;
var cell = buffer[index];
if (cell.Sequence == position && Interlocked.CompareExchange(ref _enqueuePosition, position + 1, position) == position) {
buffer[index].Element = item;
#if NET_4_6 || NET_STANDARD_2_0
Volatile.Write(ref buffer[index].Sequence, position + 1);
#else
Thread.MemoryBarrier();
buffer[index].Sequence = position + 1;
#endif
return true;
}
if (cell.Sequence < position)
return false;
}
while (true);
}
public object Dequeue() {
while (true) {
object element;
if (TryDequeue(out element))
return element;
}
}
public bool TryDequeue(out object result) {
do {
var buffer = _buffer;
var bufferMask = _bufferMask;
var position = _dequeuePosition;
var index = position & bufferMask;
var cell = buffer[index];
if (cell.Sequence == position + 1 && Interlocked.CompareExchange(ref _dequeuePosition, position + 1, position) == position) {
result = cell.Element;
buffer[index].Element = null;
#if NET_4_6 || NET_STANDARD_2_0
Volatile.Write(ref buffer[index].Sequence, position + bufferMask + 1);
#else
Thread.MemoryBarrier();
buffer[index].Sequence = position + bufferMask + 1;
#endif
return true;
}
if (cell.Sequence < position + 1) {
result = default(object);
return false;
}
}
while (true);
}
[StructLayout(LayoutKind.Explicit, Size = 16)]
private struct Cell {
[FieldOffset(0)]
public int Sequence;
[FieldOffset(8)]
public object Element;
public Cell(int sequence, object element) {
Sequence = sequence;
Element = element;
}
}
}
}
@@ -1,173 +0,0 @@
/*
* Copyright (c) 2018 Virgile Bello, Stanislav Denisov
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System;
using System.Threading;
namespace NetStack.Threading {
public sealed class ConcurrentPool<T> where T : class {
#if NET_4_6 || NET_STANDARD_2_0
private SpinLock _lock;
#else
private object _lock;
#endif
private readonly Func<T> _factory;
private Segment _head;
private Segment _tail;
public ConcurrentPool(int capacity, Func<T> factory) {
#if NET_4_6 || NET_STANDARD_2_0
_lock = new SpinLock();
#else
_lock = new Object();
#endif
_head = _tail = new Segment(capacity);
_factory = factory;
}
public T Acquire() {
while (true) {
var localHead = _head;
var count = localHead.Count;
if (count == 0) {
if (localHead.Next != null) {
#if NET_4_6 || NET_STANDARD_2_0
bool lockTaken = false;
try {
_lock.Enter(ref lockTaken);
if (_head.Next != null && _head.Count == 0)
_head = _head.Next;
}
finally {
if (lockTaken)
_lock.Exit(false);
}
#else
try {
Monitor.Enter(_lock);
if (_head.Next != null && _head.Count == 0)
_head = _head.Next;
}
finally {
Monitor.Exit(_lock);
}
#endif
} else {
return _factory();
}
} else if (Interlocked.CompareExchange(ref localHead.Count, count - 1, count) == count) {
var localLow = Interlocked.Increment(ref localHead.Low) - 1;
var index = localLow & localHead.Mask;
T item;
#if NET_4_6 || NET_STANDARD_2_0
var spinWait = new SpinWait();
#endif
while ((item = Interlocked.Exchange(ref localHead.Items[index], null)) == null) {
#if NET_4_6 || NET_STANDARD_2_0
spinWait.SpinOnce();
#else
Thread.SpinWait(1);
#endif
}
return item;
}
}
}
public void Release(T item) {
while (true) {
var localTail = _tail;
var count = localTail.Count;
if (count == localTail.Items.Length) {
#if NET_4_6 || NET_STANDARD_2_0
bool lockTaken = false;
try {
_lock.Enter(ref lockTaken);
if (_tail.Next == null && count == _tail.Items.Length)
_tail = _tail.Next = new Segment(_tail.Items.Length << 1);
}
finally {
if (lockTaken)
_lock.Exit(false);
}
#else
try {
Monitor.Enter(_lock);
if (_tail.Next == null && count == _tail.Items.Length)
_tail = _tail.Next = new Segment(_tail.Items.Length << 1);
}
finally {
Monitor.Exit(_lock);
}
#endif
} else if (Interlocked.CompareExchange(ref localTail.Count, count + 1, count) == count) {
var localHigh = Interlocked.Increment(ref localTail.High) - 1;
var index = localHigh & localTail.Mask;
#if NET_4_6 || NET_STANDARD_2_0
var spinWait = new SpinWait();
#endif
while (Interlocked.CompareExchange(ref localTail.Items[index], item, null) != null) {
#if NET_4_6 || NET_STANDARD_2_0
spinWait.SpinOnce();
#else
Thread.SpinWait(1);
#endif
}
return;
}
}
}
private class Segment {
public readonly T[] Items;
public readonly int Mask;
public int Low;
public int High;
public int Count;
public Segment Next;
public Segment(int size) {
if (size <= 0 || ((size & (size - 1)) != 0))
throw new ArgumentOutOfRangeException("Segment size must be power of two");
Items = new T[size];
Mask = size - 1;
}
}
}
}
-63
View File
@@ -1,63 +0,0 @@
/*
* Copyright (c) 2018 Stanislav Denisov
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System;
using System.Runtime.CompilerServices;
namespace NetStack.Unsafe {
#if NET_4_6 || NET_STANDARD_2_0
public static class Memory {
[MethodImpl(256)]
public static unsafe void Copy(IntPtr source, int sourceOffset, byte[] destination, int destinationOffset, int length) {
if (length > 0) {
fixed (byte* destinationPointer = &destination[destinationOffset]) {
byte* sourcePointer = (byte*)source + sourceOffset;
Buffer.MemoryCopy(sourcePointer, destinationPointer, length, length);
}
}
}
[MethodImpl(256)]
public static unsafe void Copy(byte[] source, int sourceOffset, IntPtr destination, int destinationOffset, int length) {
if (length > 0) {
fixed (byte* sourcePointer = &source[sourceOffset]) {
byte* destinationPointer = (byte*)destination + destinationOffset;
Buffer.MemoryCopy(sourcePointer, destinationPointer, length, length);
}
}
}
[MethodImpl(256)]
public static unsafe void Copy(byte[] source, int sourceOffset, byte[] destination, int destinationOffset, int length) {
if (length > 0) {
fixed (byte* sourcePointer = &source[sourceOffset]) {
fixed (byte* destinationPointer = &destination[destinationOffset]) {
Buffer.MemoryCopy(sourcePointer, destinationPointer, length, length);
}
}
}
}
}
#endif
}
@@ -1,10 +0,0 @@
using NetStack.Serialization;
namespace Ragon.Common
{
public interface IRagonSerializable
{
public void Serialize(BitBuffer buffer);
public void Deserialize(BitBuffer buffer);
}
}
-4
View File
@@ -18,8 +18,4 @@
<OutputPath></OutputPath>
<DefineConstants>TRACE;NETSTACK_SPAN</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Folder Include="External" />
</ItemGroup>
</Project>
@@ -7,6 +7,7 @@ namespace Ragon.Common
AUTHORIZED_FAILED,
JOIN_OR_CREATE_ROOM,
CREATE_ROOM,
JOIN_ROOM,
LEAVE_ROOM,
OWNERSHIP_CHANGED,
@@ -19,10 +20,11 @@ namespace Ragon.Common
PLAYER_JOINED,
PLAYER_LEAVED,
CREATE_STATES,
CREATE_ENTITY,
CREATE_STATIC_ENTITY,
DESTROY_ENTITY,
CREATE_STATIC_ENTITY,
SNAPSHOT,
REPLICATE_ENTITY_STATE,
@@ -0,0 +1,24 @@
namespace Ragon.Common
{
public class RagonRoomParameters: IRagonSerializable
{
public string Map { get; set; }
public int Min { get; set; }
public int Max { get; set; }
public void Serialize(RagonSerializer buffer)
{
buffer.WriteString(Map);
buffer.WriteInt(Min);
buffer.WriteInt(Max);
}
public void Deserialize(RagonSerializer buffer)
{
Map = buffer.ReadString();
Min = buffer.ReadInt();
Max = buffer.ReadInt();
}
}
}
@@ -0,0 +1,8 @@
namespace Ragon.Common
{
public interface IRagonSerializable
{
public void Serialize(RagonSerializer serializer);
public void Deserialize(RagonSerializer serializer);
}
}
@@ -1,10 +1,26 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace Ragon.Common
{
[StructLayout(LayoutKind.Explicit)]
internal struct ValueConverter
{
[FieldOffset(0)] public int Int;
[FieldOffset(0)] public float Float;
[FieldOffset(0)] public long Long;
[FieldOffset(0)] public byte Byte0;
[FieldOffset(1)] public byte Byte1;
[FieldOffset(2)] public byte Byte2;
[FieldOffset(3)] public byte Byte3;
[FieldOffset(4)] public byte Byte4;
[FieldOffset(5)] public byte Byte5;
[FieldOffset(6)] public byte Byte6;
[FieldOffset(7)] public byte Byte7;
}
public class RagonSerializer
{
@@ -13,7 +29,7 @@ namespace Ragon.Common
private int _size;
public int Lenght => _offset;
public int Size => _size - _offset;
public RagonSerializer(int capacity = 256)
{
_data = new byte[capacity];
@@ -21,11 +37,18 @@ namespace Ragon.Common
_size = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Reset()
{
_size = _offset;
_offset = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteByte(byte value)
{
ResizeIfNeed(1);
_data[_offset] = value;
_offset += 1;
}
@@ -37,33 +60,132 @@ namespace Ragon.Common
_offset += 1;
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBool(bool value)
{
ResizeIfNeed(1);
_data[_offset] = value ? (byte) 1 : (byte) 0;
_offset += 1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool ReadBool()
{
var value = _data[_offset];
_offset += 1;
return value == 1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteInt(int value)
{
ResizeIfNeed(4);
_data[_offset] = (byte) (value & 0x00FF);
_data[_offset + 1] = (byte) ((value & 0xFF00) >> 8);
_data[_offset + 2] = (byte) ((value & 0xFF00) >> 16);
_data[_offset + 3] = (byte) ((value & 0xFF00) >> 24);
var converter = new ValueConverter() {Int = value};
_data[_offset] = converter.Byte0;
_data[_offset + 1] = converter.Byte1;
_data[_offset + 2] = converter.Byte2;
_data[_offset + 3] = converter.Byte3;
_offset += 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadInt()
{
var value = _data[_offset] + (_data[_offset + 1] << 8) + (_data[_offset + 2] << 16) + (_data[_offset + 3] << 24);
var converter = new ValueConverter {Byte0 = _data[_offset], Byte1 = _data[_offset + 1], Byte2 = _data[_offset + 2], Byte3 = _data[_offset + 3]};
_offset += 4;
return converter.Int;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLong(long value)
{
ResizeIfNeed(8);
WriteLong(value, _offset);
_offset += 8;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLong(long value, int offset)
{
var converter = new ValueConverter() {Long = value};
_data[offset] = converter.Byte0;
_data[offset + 1] = converter.Byte1;
_data[offset + 2] = converter.Byte2;
_data[offset + 3] = converter.Byte3;
_data[offset + 4] = converter.Byte4;
_data[offset + 5] = converter.Byte5;
_data[offset + 6] = converter.Byte6;
_data[offset + 7] = converter.Byte7;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public long ReadLong()
{
var converter = new ValueConverter
{
Byte0 = _data[_offset],
Byte1 = _data[_offset + 1],
Byte2 = _data[_offset + 2],
Byte3 = _data[_offset + 3],
Byte4 = _data[_offset + 4],
Byte5 = _data[_offset + 5],
Byte6 = _data[_offset + 6],
Byte7 = _data[_offset + 7],
};
_offset += 8;
return converter.Long;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteFloat(float value)
{
var converter = new ValueConverter() {Float = value};
WriteInt(converter.Int);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float ReadFloat()
{
var rawValue = ReadInt();
var converter = new ValueConverter() {Int = rawValue};
var value = converter.Float;
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteString(string value, ushort size)
{
var stringRaw = Encoding.UTF8.GetBytes(value).AsSpan();
ResizeIfNeed(2 + size);
WriteUShort((ushort) stringRaw.Length);
var data = _data.AsSpan().Slice(_offset, size);
stringRaw.CopyTo(data);
_offset += stringRaw.Length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadString(ushort size)
{
var lenght = ReadUShort();
var stringRaw = _data.AsSpan().Slice(_offset, size);
var strData = stringRaw.Slice(0, lenght);
var str = Encoding.UTF8.GetString(strData);
_offset += size;
return str;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteString(string value)
{
var stringRaw = Encoding.UTF8.GetBytes(value).AsSpan();
ResizeIfNeed(2 + stringRaw.Length);
WriteUShort((ushort) stringRaw.Length);
var data = _data.AsSpan().Slice(_offset, stringRaw.Length);
stringRaw.CopyTo(data);
@@ -85,7 +207,7 @@ namespace Ragon.Common
{
var data = _data.AsSpan();
var payloadData = data.Slice(_offset, lenght);
_offset += payloadData.Length;
return payloadData;
}
@@ -94,19 +216,19 @@ namespace Ragon.Common
public void WriteData(ref ReadOnlySpan<byte> payload)
{
ResizeIfNeed(payload.Length);
var data = _data.AsSpan();
var payloadData = data.Slice(_offset, payload.Length);
payload.CopyTo(payloadData);
_offset += payload.Length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> GetWritableData(int lenght)
{
ResizeIfNeed(lenght);
var data = _data.AsSpan();
var payloadData = data.Slice(_offset, lenght);
@@ -118,7 +240,7 @@ namespace Ragon.Common
public void WriteOperation(RagonOperation ragonOperation)
{
ResizeIfNeed(1);
_data[_offset] = (byte) ragonOperation;
_offset += 1;
}
@@ -135,7 +257,7 @@ namespace Ragon.Common
public void WriteUShort(ushort value)
{
ResizeIfNeed(2);
_data[_offset] = (byte) (value & 0x00FF);
_data[_offset + 1] = (byte) ((value & 0xFF00) >> 8);
_offset += 2;
@@ -170,16 +292,16 @@ namespace Ragon.Common
data.CopyTo(dataSpan);
_size = data.Length;
}
public void FromArray(byte[] data)
{
Clear();
ResizeIfNeed(data.Length);
Buffer.BlockCopy(data, 0, _data, 0, _offset);
Buffer.BlockCopy(data, 0, _data, 0, data.Length);
_size = data.Length;
}
public byte[] ToArray()
{
var bytes = new byte[_offset];
@@ -191,7 +313,7 @@ namespace Ragon.Common
{
if (_offset + lenght < _data.Length)
return;
var newData = new byte[_data.Length * 4 + lenght];
Buffer.BlockCopy(_data, 0, newData, 0, _data.Length);
_data = newData;
+9
View File
@@ -0,0 +1,9 @@
namespace Ragon.Common
{
public enum RagonTarget: byte
{
OWNER,
EXCEPT_OWNER,
ALL,
}
}
@@ -16,7 +16,7 @@ namespace Game.Source
}
public override void OnPlayerJoined(Player player)
{
{
// _logger.Info($"Player({player.PlayerName}) joined to Room({GameRoom.Id})");
}
+1 -5
View File
@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using ENet;
using ENet;
using NLog;
namespace Ragon.Core
+2
View File
@@ -1,4 +1,5 @@
using System;
using System.Diagnostics;
using System.IO;
using NLog;
@@ -16,5 +17,6 @@ namespace Ragon.Core
var app = new Application(factory, configuration);
return app;
}
}
}
+9 -9
View File
@@ -4,23 +4,23 @@ namespace Ragon.Core;
public class Entity
{
private static int _idGenerator = 0;
public int EntityId { get; private set; }
public int StaticId { get; private set; }
public uint OwnerId { get; private set; }
private static ushort _idGenerator = 0;
public ushort EntityId { get; private set; }
public ushort StaticId { get; private set; }
public ushort EntityType { get; private set; }
public ushort OwnerId { get; private set; }
public RagonAuthority Authority { get; private set; }
public EntityState State { get; private set; }
public EntityState Payload { get; private set; }
public EntityProperty[] Properties { get; private set; }
public byte[] Payload { get; set; }
public Entity(uint ownerId, ushort entityType, int staticId, RagonAuthority stateAuthority, RagonAuthority eventAuthority)
public Entity(ushort ownerId, ushort entityType, ushort staticId, RagonAuthority stateAuthority, RagonAuthority eventAuthority, int props)
{
OwnerId = ownerId;
StaticId = staticId;
EntityType = entityType;
EntityId = _idGenerator++;
State = new EntityState(stateAuthority);
Payload = new EntityState(stateAuthority);
Properties = new EntityProperty[props];
Payload = new byte[1024];
Authority = eventAuthority;
}
}
+33
View File
@@ -0,0 +1,33 @@
using System;
namespace Ragon.Core;
public class EntityProperty
{
public int Size => _data.Length;
public bool IsDirty => _dirty;
private bool _dirty;
private byte[] _data;
public EntityProperty(int size)
{
_data = new byte[size];
}
public ReadOnlySpan<byte> Read()
{
return _data.AsSpan();
}
public void Write(ref ReadOnlySpan<byte> src)
{
src.CopyTo(_data);
_dirty = true;
}
public void Clear()
{
_dirty = false;
}
}
-38
View File
@@ -1,38 +0,0 @@
using System;
using System.Runtime.Serialization;
using Ragon.Common;
namespace Ragon.Core;
public class EntityState
{
public bool isDirty { get; private set; }
public RagonAuthority Authority { get; private set; }
public int Size => _size;
private int _size = 0;
private byte[] _data = new byte[2048];
public EntityState(RagonAuthority ragonAuthority)
{
Authority = ragonAuthority;
isDirty = false;
}
public ReadOnlySpan<byte> Read()
{
return _data.AsSpan().Slice(0, _size);
}
public void Write(ref ReadOnlySpan<byte> src)
{
src.CopyTo(_data);
_size = src.Length;
isDirty = true;
}
public void Clear()
{
isDirty = false;
}
}
+167 -86
View File
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using NLog;
using Ragon.Common;
@@ -28,8 +29,11 @@ namespace Ragon.Core
private uint[] _readyPlayers = Array.Empty<uint>();
private uint[] _allPlayers = Array.Empty<uint>();
private Entity[] _entitiesAll = Array.Empty<Entity>();
private HashSet<Entity> _entitiesDirtySet = new HashSet<Entity>();
private List<Entity> _entitiesDirty = new List<Entity>();
private List<uint> _peersCache = new List<uint>();
public GameRoom(IGameThread gameThread, PluginBase pluginBase, string map, int min, int max)
public GameRoom(IGameThread gameThread, PluginBase pluginBase, string roomId, string map, int min, int max)
{
_gameThread = gameThread;
_plugin = pluginBase;
@@ -38,7 +42,7 @@ namespace Ragon.Core
Map = map;
PlayersMin = min;
PlayersMax = max;
Id = Guid.NewGuid().ToString();
Id = roomId;
_plugin.Attach(this);
}
@@ -94,8 +98,6 @@ namespace Ragon.Core
_allPlayers = _players.Select(p => p.Key).ToArray();
_readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray();
var isOwnershipChange = player.PeerId == _owner;
{
_plugin.OnPlayerLeaved(player);
@@ -114,34 +116,12 @@ namespace Ragon.Core
Broadcast(_readyPlayers, sendData);
}
if (_allPlayers.Length > 0 && isOwnershipChange)
{
var newRoomOwnerId = _allPlayers[0];
var newRoomOwner = _players[newRoomOwnerId];
_owner = newRoomOwnerId;
{
_plugin.OnOwnershipChanged(newRoomOwner);
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.OWNERSHIP_CHANGED);
_serializer.WriteString(newRoomOwner.Id);
var sendData = _serializer.ToArray();
Broadcast(_readyPlayers, sendData);
}
}
_entitiesAll = _entities.Values.ToArray();
}
}
public void ProcessEvent(uint peerId, ReadOnlySpan<byte> rawData)
public void ProcessEvent(ushort peerId, RagonOperation operation, ReadOnlySpan<byte> payloadRawData)
{
var operation = (RagonOperation) rawData[0];
var payloadRawData = rawData.Slice(1, rawData.Length - 1);
_serializer.Clear();
_serializer.FromSpan(ref payloadRawData);
@@ -149,14 +129,27 @@ namespace Ragon.Core
{
case RagonOperation.REPLICATE_ENTITY_STATE:
{
var entityId = _serializer.ReadInt();
var entityId = _serializer.ReadUShort();
if (_entities.TryGetValue(entityId, out var ent))
{
if (ent.State.Authority == RagonAuthority.OWNER_ONLY && ent.OwnerId != peerId)
if (ent.OwnerId != peerId)
{
_logger.Warn($"Not owner can't change properties of object {entityId}");
return;
}
var entityStateData = _serializer.ReadData(_serializer.Size);
ent.State.Write(ref entityStateData);
var mask = _serializer.ReadLong();
for (var i = 0; i < ent.Properties.Length; i++)
{
if (((mask >> i) & 1) == 1)
{
var propertyPayload = _serializer.ReadData(ent.Properties[i].Size);
ent.Properties[i].Write(ref propertyPayload);
}
}
if (_entitiesDirtySet.Add(ent))
_entitiesDirty.Add(ent);
}
break;
@@ -165,7 +158,8 @@ namespace Ragon.Core
{
var evntId = _serializer.ReadUShort();
var evntMode = _serializer.ReadByte();
var entityId = _serializer.ReadInt();
var targetMode = (RagonTarget) _serializer.ReadByte();
var entityId = _serializer.ReadUShort();
if (!_entities.TryGetValue(entityId, out var ent))
return;
@@ -186,17 +180,57 @@ namespace Ragon.Core
_serializer.WriteUShort(evntId);
_serializer.WriteUShort((ushort) peerId);
_serializer.WriteByte(evntMode);
_serializer.WriteInt(entityId);
_serializer.WriteUShort(entityId);
_serializer.WriteData(ref payload);
var sendData = _serializer.ToArray();
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
switch (targetMode)
{
case RagonTarget.OWNER:
{
Send(ent.OwnerId, sendData, DeliveryType.Reliable);
break;
}
case RagonTarget.EXCEPT_OWNER:
{
_peersCache.Clear();
foreach (var playerPeerId in _readyPlayers)
if (playerPeerId != ent.OwnerId)
_peersCache.Add(playerPeerId);
Broadcast(_peersCache.ToArray(), sendData, DeliveryType.Reliable);
break;
}
case RagonTarget.ALL:
{
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
break;
}
}
break;
}
case RagonOperation.LOAD_SCENE:
{
var sceneName = _serializer.ReadString();
_readyPlayers = Array.Empty<uint>();
_entitiesAll = Array.Empty<Entity>();
_entities.Clear();
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.LOAD_SCENE);
_serializer.WriteString(sceneName);
var sendData = _serializer.ToArray();
Broadcast(_allPlayers, sendData, DeliveryType.Reliable);
break;
}
case RagonOperation.REPLICATE_EVENT:
{
var evntId = _serializer.ReadUShort();
var evntMode = _serializer.ReadByte();
var targetMode = (RagonTarget) _serializer.ReadByte();
Span<byte> payloadRaw = stackalloc byte[_serializer.Size];
var payloadData = _serializer.ReadData(_serializer.Size);
payloadData.CopyTo(payloadRaw);
@@ -213,20 +247,53 @@ namespace Ragon.Core
_serializer.WriteData(ref payload);
var sendData = _serializer.ToArray();
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
switch (targetMode)
{
case RagonTarget.OWNER:
{
Send(_owner, sendData, DeliveryType.Reliable);
break;
}
case RagonTarget.EXCEPT_OWNER:
{
_peersCache.Clear();
foreach (var playerPeerId in _readyPlayers)
if (playerPeerId != _owner)
_peersCache.Add(playerPeerId);
Broadcast(_peersCache.ToArray(), sendData, DeliveryType.Reliable);
break;
}
case RagonTarget.ALL:
{
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
break;
}
}
break;
}
case RagonOperation.CREATE_STATIC_ENTITY:
{
var entityType = _serializer.ReadUShort();
var staticId = _serializer.ReadUShort();
var stateAuthority = (RagonAuthority) _serializer.ReadByte();
var eventAuthority = (RagonAuthority) _serializer.ReadByte();
var entity = new Entity(peerId, entityType, staticId, stateAuthority, eventAuthority);
var propertiesCount = _serializer.ReadUShort();
if (propertiesCount > 63)
{
_logger.Warn($"Allowed only 64 properties per entity. EntityType(Static) {entityType}");
return;
}
var entity = new Entity(peerId, entityType, staticId, RagonAuthority.ALL, RagonAuthority.OWNER_ONLY, propertiesCount);
for (var i = 0; i < propertiesCount; i++)
{
var propertySize = _serializer.ReadUShort();
entity.Properties[i] = new EntityProperty(propertySize);
}
{
var entityPayload = _serializer.ReadData(_serializer.Size);
entity.Payload.Write(ref entityPayload);
entity.Payload = entityPayload.ToArray();
}
var player = _players[peerId];
@@ -243,14 +310,12 @@ namespace Ragon.Core
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.CREATE_STATIC_ENTITY);
_serializer.WriteUShort(entityType);
_serializer.WriteUShort(entity.EntityId);
_serializer.WriteUShort(staticId);
_serializer.WriteByte((byte) stateAuthority);
_serializer.WriteByte((byte) eventAuthority);
_serializer.WriteInt(entity.EntityId);
_serializer.WriteUShort(ownerId);
{
var entityPayload = entity.Payload.Read();
ReadOnlySpan<byte> entityPayload = entity.Payload.AsSpan();
_serializer.WriteData(ref entityPayload);
}
@@ -261,13 +326,23 @@ namespace Ragon.Core
case RagonOperation.CREATE_ENTITY:
{
var entityType = _serializer.ReadUShort();
var stateAuthority = (RagonAuthority) _serializer.ReadByte();
var eventAuthority = (RagonAuthority) _serializer.ReadByte();
var entity = new Entity(peerId, entityType, -1, stateAuthority, eventAuthority);
var propertiesCount = _serializer.ReadUShort();
var entity = new Entity(peerId, entityType, 0, RagonAuthority.ALL, RagonAuthority.ALL, propertiesCount);
if (propertiesCount > 63)
{
_logger.Warn($"Allowed only 64 properties per entity. EntityType(Static) {entityType}");
return;
}
for (var i = 0; i < propertiesCount; i++)
{
var propertySize = _serializer.ReadUShort();
entity.Properties[i] = new EntityProperty(propertySize);
}
{
var entityPayload = _serializer.ReadData(_serializer.Size);
entity.Payload.Write(ref entityPayload);
entity.Payload = entityPayload.ToArray();
}
var player = _players[peerId];
@@ -284,13 +359,11 @@ namespace Ragon.Core
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.CREATE_ENTITY);
_serializer.WriteUShort(entityType);
_serializer.WriteByte((byte) stateAuthority);
_serializer.WriteByte((byte) eventAuthority);
_serializer.WriteInt(entity.EntityId);
_serializer.WriteUShort(entity.EntityId);
_serializer.WriteUShort(ownerId);
{
var entityPayload = entity.Payload.Read();
ReadOnlySpan<byte> entityPayload = entity.Payload.AsSpan();
_serializer.WriteData(ref entityPayload);
}
@@ -333,7 +406,7 @@ namespace Ragon.Core
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.SNAPSHOT);
_serializer.WriteInt(_allPlayers.Length);
_serializer.WriteUShort((ushort) _allPlayers.Length);
foreach (var playerPeerId in _allPlayers)
{
_serializer.WriteString(_players[playerPeerId].Id);
@@ -341,43 +414,31 @@ namespace Ragon.Core
_serializer.WriteString(_players[playerPeerId].PlayerName);
}
var dynamicCount = _entitiesAll.Where(e => e.StaticId == -1).ToArray();
_serializer.WriteInt(dynamicCount.Length);
foreach (var entity in dynamicCount)
var dynamicEntities = _entitiesAll.Where(e => e.StaticId == 0).ToArray();
_serializer.WriteUShort((ushort) dynamicEntities.Length);
foreach (var entity in dynamicEntities)
{
if (entity.StaticId != -1) continue;
ReadOnlySpan<byte> payload = entity.Payload.AsSpan();
var payload = entity.Payload.Read();
var state = entity.State.Read();
_serializer.WriteInt(entity.EntityId);
_serializer.WriteByte((byte) entity.State.Authority);
_serializer.WriteByte((byte) entity.Authority);
_serializer.WriteUShort(entity.EntityType);
_serializer.WriteUShort(entity.EntityId);
_serializer.WriteUShort((ushort) entity.OwnerId);
_serializer.WriteUShort((ushort) payload.Length);
_serializer.WriteData(ref payload);
_serializer.WriteUShort((ushort) state.Length);
_serializer.WriteData(ref state);
}
var staticCount = _entitiesAll.Where(e => e.StaticId != -1).ToArray();
_serializer.WriteInt(staticCount.Length);
var staticCount = _entitiesAll.Where(e => e.StaticId != 0).ToArray();
_serializer.WriteUShort((ushort) staticCount.Length);
foreach (var entity in staticCount)
{
var payload = entity.Payload.Read();
var state = entity.State.Read();
ReadOnlySpan<byte> payload = entity.Payload.AsSpan();
_serializer.WriteInt(entity.EntityId);
_serializer.WriteUShort((ushort) entity.StaticId);
_serializer.WriteByte((byte) entity.State.Authority);
_serializer.WriteByte((byte) entity.Authority);
_serializer.WriteUShort(entity.EntityType);
_serializer.WriteUShort((ushort) entity.OwnerId);
_serializer.WriteUShort(entity.EntityId);
_serializer.WriteUShort(entity.StaticId);
_serializer.WriteUShort(entity.OwnerId);
_serializer.WriteUShort((ushort) payload.Length);
_serializer.WriteData(ref payload);
_serializer.WriteUShort((ushort) state.Length);
_serializer.WriteData(ref state);
}
var sendData = _serializer.ToArray();
@@ -385,7 +446,6 @@ namespace Ragon.Core
_players[peerId].IsLoaded = true;
_readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray();
_plugin.OnPlayerJoined(_players[peerId]);
break;
}
@@ -396,22 +456,43 @@ namespace Ragon.Core
{
_scheduler.Tick(deltaTime);
foreach (var entity in _entitiesAll)
if (_entitiesDirty.Count > 0)
{
if (entity.State.isDirty)
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_STATE);
_serializer.WriteUShort((ushort) _entitiesDirty.Count);
for (var entityIndex = 0; entityIndex < _entitiesDirty.Count; entityIndex++)
{
var state = entity.State.Read();
var entity = _entitiesDirty[entityIndex];
var mask = 0L;
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_STATE);
_serializer.WriteInt(entity.EntityId);
_serializer.WriteData(ref state);
_serializer.WriteUShort(entity.EntityId);
var sendData = _serializer.ToArray();
Broadcast(_readyPlayers, sendData, DeliveryType.Unreliable);
var offset = _serializer.Lenght;
_serializer.WriteLong(mask);
entity.State.Clear();
for (int propertyIndex = 0; propertyIndex < entity.Properties.Length; propertyIndex++)
{
var property = entity.Properties[propertyIndex];
if (property.IsDirty)
{
mask |= (uint) (1 << propertyIndex);
var span = _serializer.GetWritableData(property.Size);
var data = property.Read();
data.CopyTo(span);
property.Clear();
}
}
_serializer.WriteLong(mask, offset);
}
_entitiesDirty.Clear();
_entitiesDirtySet.Clear();
var sendData = _serializer.ToArray();
Broadcast(_readyPlayers, sendData, DeliveryType.Unreliable);
}
}
+11 -12
View File
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using Ragon.Common;
using ENet;
using NLog;
@@ -29,10 +29,10 @@ namespace Ragon.Core
var authorizationProvider = factory.CreateAuthorizationProvider(configuration);
var dispatcher = new Dispatcher();
_dispatcherInternal = dispatcher;
ThreadDispatcher = dispatcher;
Server = new ENetServer(this);
_deltaTime = 1000.0f / configuration.SendRate;
_roomManager = new RoomManager(factory, this);
@@ -69,7 +69,7 @@ namespace Ragon.Core
while (true)
{
Server.Process();
_dispatcherInternal.Process();
var elapsedMilliseconds = _gameLoopTimer.ElapsedMilliseconds;
@@ -104,19 +104,18 @@ namespace Ragon.Core
{
try
{
var peerId = evnt.Peer.ID;
var peerId = (ushort) evnt.Peer.ID;
var dataRaw = new byte[evnt.Packet.Length];
evnt.Packet.CopyTo(dataRaw);
var data = new ReadOnlySpan<byte>(dataRaw);
var operation = (RagonOperation) data[0];
var payload = data.Slice(1, data.Length - 1);
if (_roomManager.RoomsBySocket.TryGetValue(peerId, out var room))
{
room.ProcessEvent(peerId, data);
}
else
{
_lobby.ProcessEvent(peerId, data);
}
room.ProcessEvent(peerId, operation, payload);
_lobby.ProcessEvent(peerId, operation, payload);
}
catch (Exception exception)
{
+75 -25
View File
@@ -1,65 +1,115 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using Ragon.Common;
namespace Ragon.Core;
public class Lobby : ILobby
{
private readonly ILogger _logger = LogManager.GetCurrentClassLogger();
private readonly RagonSerializer _serializer;
private readonly RoomManager _roomManager;
private readonly AuthorizationManager _authorizationManager;
private readonly IGameThread _gameThread;
public AuthorizationManager AuthorizationManager => _authorizationManager;
public Lobby(IAuthorizationProvider provider, RoomManager manager, IGameThread gameThread)
{
_roomManager = manager;
_gameThread = gameThread;
_serializer = new RagonSerializer();
_authorizationManager = new AuthorizationManager(provider, gameThread, this, _serializer);
}
public void ProcessEvent(uint peerId, ReadOnlySpan<byte> data)
public void ProcessEvent(ushort peerId, RagonOperation op, ReadOnlySpan<byte> payload)
{
var op = (RagonOperation) data[0];
var payload = data.Slice(1, data.Length - 1);
_serializer.Clear();
_serializer.FromSpan(ref payload);
if (op == RagonOperation.AUTHORIZE)
{
var key = _serializer.ReadString();
var playerName = _serializer.ReadString();
var protocol = _serializer.ReadByte();
_authorizationManager.OnAuthorization(peerId, key, playerName, protocol);
return;
}
var player = _authorizationManager.GetPlayer(peerId);
if (player == null)
{
_logger.Warn($"Peer not authorized {peerId} trying to {op}");
return;
}
switch (op)
{
case RagonOperation.AUTHORIZE:
{
var key = _serializer.ReadString();
var playerName = _serializer.ReadString();
var protocol = _serializer.ReadByte();
_authorizationManager.OnAuthorization(peerId, key, playerName, protocol);
break;
}
case RagonOperation.JOIN_ROOM:
{
var roomId = _serializer.ReadString();
var player = _authorizationManager.GetPlayer(peerId);
if (player != null)
_roomManager.Join(player, roomId, Array.Empty<byte>());
var exists = _roomManager.Rooms.Any(r => r.Id == roomId);
if (!exists)
{
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.JOIN_FAILED);
_serializer.WriteString($"Room with id {roomId} not exists");
var sendData = _serializer.ToArray();
_gameThread.Server.Send(peerId, sendData, DeliveryType.Reliable);
return;
}
if (_roomManager.RoomsBySocket.ContainsKey(peerId))
_roomManager.Left(player, Array.Empty<byte>());
_roomManager.Join(player, roomId, Array.Empty<byte>());
break;
}
case RagonOperation.CREATE_ROOM:
{
var roomId = Guid.NewGuid().ToString();
var custom = _serializer.ReadBool();
if (custom)
{
roomId = _serializer.ReadString();
var exists = _roomManager.Rooms.Any(r => r.Id == roomId);
if (exists)
{
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.JOIN_FAILED);
_serializer.WriteString($"Room with id {roomId} already exists");
var sendData = _serializer.ToArray();
_gameThread.Server.Send(peerId, sendData, DeliveryType.Reliable);
return;
}
}
var roomProperties = new RagonRoomParameters();
roomProperties.Deserialize(_serializer);
if (_roomManager.RoomsBySocket.ContainsKey(peerId))
_roomManager.Left(player, Array.Empty<byte>());
_roomManager.Create(player, roomId, roomProperties, Array.Empty<byte>());
break;
}
case RagonOperation.JOIN_OR_CREATE_ROOM:
{
var min = _serializer.ReadUShort();
var max = _serializer.ReadUShort();
var map = _serializer.ReadString();
var player = _authorizationManager.GetPlayer(peerId);
if (player != null)
_roomManager.JoinOrCreate(player, map, min, max, Array.Empty<byte>());
var roomId = Guid.NewGuid().ToString();
var roomProperties = new RagonRoomParameters();
roomProperties.Deserialize(_serializer);
if (_roomManager.RoomsBySocket.ContainsKey(peerId))
_roomManager.Left(player, Array.Empty<byte>());
_roomManager.JoinOrCreate(player, roomId, roomProperties, Array.Empty<byte>());
break;
}
case RagonOperation.LEAVE_ROOM:
{
var player = _authorizationManager.GetPlayer(peerId);
if (player != null)
_roomManager.Left(player, Array.Empty<byte>());
_roomManager.Left(player, Array.Empty<byte>());
break;
}
}
+13 -32
View File
@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using NetStack.Serialization;
using NLog;
using Ragon.Common;
@@ -15,7 +13,6 @@ namespace Ragon.Core
private Dictionary<ushort, SubscribeDelegate> _globalEvents = new();
private Dictionary<int, Dictionary<ushort, SubscribeEntityDelegate>> _entityEvents = new();
private readonly BitBuffer _buffer = new();
private readonly RagonSerializer _serializer = new();
protected IGameRoom GameRoom { get; private set; } = null!;
@@ -54,9 +51,9 @@ namespace Ragon.Core
return;
}
_buffer.Clear();
_buffer.FromSpan(ref raw, raw.Length);
data.Deserialize(_buffer);
_serializer.Clear();
_serializer.FromSpan(ref raw);
data.Deserialize(_serializer);
action.Invoke(player, data);
});
}
@@ -91,9 +88,9 @@ namespace Ragon.Core
return;
}
_buffer.Clear();
_buffer.FromSpan(ref raw, raw.Length);
data.Deserialize(_buffer);
_serializer.Clear();
_serializer.FromSpan(ref raw);
data.Deserialize(_serializer);
action.Invoke(player, ent, data);
});
@@ -111,9 +108,9 @@ namespace Ragon.Core
return;
}
_buffer.Clear();
_buffer.FromSpan(ref raw, raw.Length);
data.Deserialize(_buffer);
_serializer.Clear();
_serializer.FromSpan(ref raw);
data.Deserialize(_serializer);
action.Invoke(player, ent, data);
});
}
@@ -177,12 +174,8 @@ namespace Ragon.Core
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_EVENT);
_buffer.Clear();
payload.Serialize(_buffer);
payload.Serialize(_serializer);
var payloadData = _serializer.GetWritableData(_buffer.Length);
_buffer.ToSpan(ref payloadData);
var sendData = _serializer.ToArray();
GameRoom.Send(player.PeerId, sendData);
}
@@ -192,11 +185,7 @@ namespace Ragon.Core
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_EVENT);
_buffer.Clear();
payload.Serialize(_buffer);
var payloadData = _serializer.GetWritableData(_buffer.Length);
_buffer.ToSpan(ref payloadData);
payload.Serialize(_serializer);
var sendData = _serializer.ToArray();
GameRoom.Broadcast(sendData, DeliveryType.Reliable);
@@ -208,11 +197,7 @@ namespace Ragon.Core
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
_serializer.WriteInt(entity.EntityId);
_buffer.Clear();
payload.Serialize(_buffer);
var payloadData = _serializer.GetWritableData(_buffer.Length);
_buffer.ToSpan(ref payloadData);
payload.Serialize(_serializer);
var sendData = _serializer.ToArray();
GameRoom.Send(player.PeerId, sendData, DeliveryType.Reliable);
@@ -224,11 +209,7 @@ namespace Ragon.Core
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
_serializer.WriteInt(entity.EntityId);
_buffer.Clear();
payload.Serialize(_buffer);
var payloadData = _serializer.GetWritableData(_buffer.Length);
_buffer.ToSpan(ref payloadData);
payload.Serialize(_serializer);
var sendData = _serializer.ToArray();
GameRoom.Broadcast(sendData);
+39 -5
View File
@@ -11,12 +11,12 @@ public class RoomManager
private readonly IGameThread _gameThread;
private readonly PluginFactory _factory;
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
private readonly List<GameRoom> _rooms = new List<GameRoom>();
private readonly List<GameRoom> _rooms = new();
private readonly Dictionary<uint, GameRoom> _roomsBySocket;
public IReadOnlyDictionary<uint, GameRoom> RoomsBySocket => _roomsBySocket;
public IReadOnlyList<GameRoom> Rooms => _rooms;
public RoomManager(PluginFactory factory, IGameThread gameThread)
{
_gameThread = gameThread;
@@ -26,6 +26,8 @@ public class RoomManager
public void Join(Player player, string roomId, byte[] payload)
{
_logger.Trace($"Player ({player.PlayerName}|{player.Id}) joined to room with Id {roomId}");
if (_rooms.Count > 0)
{
foreach (var existRoom in _rooms)
@@ -34,20 +36,46 @@ public class RoomManager
{
existRoom.Joined(player, payload);
_roomsBySocket.Add(player.PeerId, existRoom);
break;
return;
}
}
}
}
public void JoinOrCreate(Player player, string map, int min, int max, byte[] payload)
public void Create(Player creator, string roomId, RagonRoomParameters parameters, byte[] payload)
{
var map = parameters.Map;
var min = parameters.Min;
var max = parameters.Max;
_logger.Trace($"Player ({creator.PlayerName}|{creator.Id}) create room with Id {roomId} and params ({map}|{min}|{max})");
var plugin = _factory.CreatePlugin(map);
if (plugin == null)
throw new NullReferenceException($"Plugin for map {map} is null");
var room = new GameRoom(_gameThread, plugin, roomId, map, min, max);
room.Joined(creator, payload);
room.Start();
_roomsBySocket.Add(creator.PeerId, room);
_rooms.Add(room);
}
public void JoinOrCreate(Player player, string roomId, RagonRoomParameters parameters, byte[] payload)
{
var map = parameters.Map;
var min = parameters.Min;
var max = parameters.Max;
if (_rooms.Count > 0)
{
foreach (var existRoom in _rooms)
{
if (existRoom.Map == map && existRoom.PlayersCount < existRoom.PlayersMax)
{
_logger.Trace($"Player ({player.PlayerName}|{player.Id}) joined to room with Id {roomId}");
existRoom.Joined(player, payload);
_roomsBySocket.Add(player.PeerId, existRoom);
return;
@@ -55,11 +83,13 @@ public class RoomManager
}
}
_logger.Trace($"Room not found for Player ({player.PlayerName}|{player.Id}), create room with Id {roomId} and params ({map}|{min}|{max})");
var plugin = _factory.CreatePlugin(map);
if (plugin == null)
throw new NullReferenceException($"Plugin for map {map} is null");
var room = new GameRoom(_gameThread, plugin, map, min, max);
var room = new GameRoom(_gameThread, plugin, roomId, map, min, max);
room.Joined(player, payload);
room.Start();
@@ -71,12 +101,16 @@ public class RoomManager
{
if (_roomsBySocket.Remove(player.PeerId, out var room))
{
_logger.Trace($"Player ({player.PlayerName}|{player.Id}) left room with Id {room.Id}");
room.Leave(player.PeerId);
if (room.PlayersCount < room.PlayersMin)
{
_logger.Trace($"Room with Id {room.Id} destroyed");
room.Stop();
_rooms.Remove(room);
}
_gameThread.Server.Send(player.PeerId, new byte[] {(byte) RagonOperation.LEAVE_ROOM}, DeliveryType.Reliable);
}
}
+21 -1
View File
@@ -1 +1,21 @@
This program is free software: you can redistribute it and/or modify it under the terms of the Server Side Public License, version 1, as published by MongoDB, Inc. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Server Side Public License for more details. You should have received a copy of the Server Side Public License along with this program. If not, see http://www.mongodb.com/licensing/server-side-public-license.
MIT License
Copyright (c) 2022 Eduard Kargin (theedison4@gmail.com)
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.
+5 -16
View File
@@ -14,23 +14,12 @@ Ragon is fully free, small and high perfomance room based game server with plugi
- Effective
- Free
- Simple matchmaking
- Flexiable API
- Room based architecture
- Extendable room logic via plugin
- Custom authorization
- Сustomizable authorization
- Сustomizable server-side logic via plugins with flexible API
- No CCU limitations*
- Engine agnostic
- Support any client architecture (MonoBehaviors, ECS)
- RUDP
### Roadmap:
- Use native memory
- Reduce allocations
- Dashboard for monitoring entities and players in realtime
- Statistics for monitoring state of server, cpu, memory
- Horizontal Scaling
- Docker support
- Add additional API to plugin system
- No Room count limitations
- Reliable UDP
### Requirements
- OSX, Windows, Linux(Ubuntu, Debian)
@@ -41,7 +30,7 @@ Ragon is fully free, small and high perfomance room based game server with plugi
* NetStack [latest]
### License
SSPL-1.0
MIT
### Tips
\* Limited to 4095 CCU by library ENet-Sharp