Compare commits
45 Commits
v1.0.6-rc
...
v1.0.21-rc
| Author | SHA1 | Date | |
|---|---|---|---|
| 545ec02ecc | |||
| 73feb77169 | |||
| c2c75cb513 | |||
| 9a22566f79 | |||
| 783d2ce922 | |||
| 5771ec738b | |||
| 85081e1da7 | |||
| dbaa5d9d92 | |||
| 54786c065d | |||
| 0b3a0dd1ac | |||
| 72ff37e94a | |||
| 75dab9d16f | |||
| 25b0e54d13 | |||
| e4f664b557 | |||
| 174a4c4422 | |||
| a51d7c73cd | |||
| 957622a170 | |||
| 4a07424293 | |||
| 568a3282c3 | |||
| 6a71fe5fe0 | |||
| 082e183989 | |||
| 51c0482a40 | |||
| 9e16c53cad | |||
| 6ec29e5dcd | |||
| 65c1d9c6d4 | |||
| f2934bc8ee | |||
| 81879d4fe2 | |||
| 2d89d267f5 | |||
| 34107cbd5f | |||
| ba2ce25aec | |||
| ca17e7de83 | |||
| dff6dbcf97 | |||
| ae485f96d4 | |||
| 74904de16b | |||
| 655cf3503d | |||
| 8fd8558323 | |||
| 944cb65733 | |||
| 877ebdcde2 | |||
| e2032f381a | |||
| 85336f998e | |||
| 16b8d3a062 | |||
| f72b4c0904 | |||
| 19272609ef | |||
| 5f3b2d7ed8 | |||
| 773adeefb2 |
@@ -1,69 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018 Stanislav Denisov
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System.Threading;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace NetStack.Buffers {
|
|
||||||
public abstract class ArrayPool<T> {
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
private static ArrayPool<T> s_sharedInstance = null;
|
|
||||||
#else
|
|
||||||
private static volatile ArrayPool<T> s_sharedInstance = null;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public static ArrayPool<T> Shared {
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
[MethodImpl(256)]
|
|
||||||
get {
|
|
||||||
return Volatile.Read(ref s_sharedInstance) ?? EnsureSharedCreated();
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
[MethodImpl(256)]
|
|
||||||
get {
|
|
||||||
return s_sharedInstance ?? EnsureSharedCreated();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma warning disable 420
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
||||||
private static ArrayPool<T> EnsureSharedCreated() {
|
|
||||||
Interlocked.CompareExchange(ref s_sharedInstance, Create(), null);
|
|
||||||
|
|
||||||
return s_sharedInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArrayPool<T> Create() {
|
|
||||||
return new DefaultArrayPool<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArrayPool<T> Create(int maxArrayLength, int maxArraysPerBucket) {
|
|
||||||
return new DefaultArrayPool<T>(maxArrayLength, maxArraysPerBucket);
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract T[] Rent(int minimumLength);
|
|
||||||
|
|
||||||
public abstract void Return(T[] array, bool clearArray = false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018 Stanislav Denisov
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
using UnityEngine;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace NetStack.Buffers {
|
|
||||||
internal sealed class ArrayPoolEventSource {
|
|
||||||
#if NETSTACK_BUFFERS_LOG
|
|
||||||
internal static readonly ArrayPoolEventSource EventLog = new ArrayPoolEventSource();
|
|
||||||
|
|
||||||
internal enum BufferAllocatedReason : int {
|
|
||||||
Pooled,
|
|
||||||
OverMaximumSize,
|
|
||||||
PoolExhausted
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void BufferAllocated(int bufferId, int bufferSize, int poolId, int bucketId, BufferAllocatedReason reason) {
|
|
||||||
var message = "Buffer allocated (Buffer ID: " + bufferId + ", Buffer size: " + bufferSize + ", Pool ID: " + poolId + ", Bucket ID: " + bucketId + ", Reason: " + reason + ")";
|
|
||||||
|
|
||||||
if (reason == BufferAllocatedReason.Pooled)
|
|
||||||
Log.Info("Buffers", message);
|
|
||||||
else
|
|
||||||
Log.Warning("Buffers", message);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void BufferRented(int bufferId, int bufferSize, int poolId, int bucketId) {
|
|
||||||
Log.Info("Buffers", "Buffer rented (Buffer ID: " + bufferId + ", Buffer size: " + bufferSize + ", Pool ID: " + poolId + ", Bucket ID: " + bucketId + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void BufferReturned(int bufferId, int bufferSize, int poolId) {
|
|
||||||
Log.Info("Buffers", "Buffer returned (Buffer ID: " + bufferId + ", Buffer size: " + bufferSize + ", Pool ID: " + poolId + ")");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static class Log {
|
|
||||||
private static string Output(string module, string message) {
|
|
||||||
return DateTime.Now.ToString("[HH:mm:ss]") + " [NetStack." + module + "] " + message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Info(string module, string message) {
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
Debug.Log(Output(module, message));
|
|
||||||
#else
|
|
||||||
Console.WriteLine(Output(module, message));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Warning(string module, string message) {
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
Debug.LogWarning(Output(module, message));
|
|
||||||
#else
|
|
||||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
|
||||||
Console.WriteLine(Output(module, message));
|
|
||||||
Console.ResetColor();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018 Stanislav Denisov
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace NetStack.Buffers {
|
|
||||||
internal sealed partial class DefaultArrayPool<T> : ArrayPool<T> {
|
|
||||||
private const int DefaultMaxArrayLength = 1024 * 1024;
|
|
||||||
private const int DefaultMaxNumberOfArraysPerBucket = 50;
|
|
||||||
private static T[] s_emptyArray;
|
|
||||||
private readonly Bucket[] _buckets;
|
|
||||||
|
|
||||||
internal DefaultArrayPool() : this(DefaultMaxArrayLength, DefaultMaxNumberOfArraysPerBucket) { }
|
|
||||||
|
|
||||||
internal DefaultArrayPool(int maxArrayLength, int maxArraysPerBucket) {
|
|
||||||
if (maxArrayLength <= 0)
|
|
||||||
throw new ArgumentOutOfRangeException("maxArrayLength");
|
|
||||||
|
|
||||||
if (maxArraysPerBucket <= 0)
|
|
||||||
throw new ArgumentOutOfRangeException("maxArraysPerBucket");
|
|
||||||
|
|
||||||
const int MinimumArrayLength = 0x10, MaximumArrayLength = 0x40000000;
|
|
||||||
|
|
||||||
if (maxArrayLength > MaximumArrayLength)
|
|
||||||
maxArrayLength = MaximumArrayLength;
|
|
||||||
else if (maxArrayLength < MinimumArrayLength)
|
|
||||||
maxArrayLength = MinimumArrayLength;
|
|
||||||
|
|
||||||
int poolId = Id;
|
|
||||||
int maxBuckets = Utilities.SelectBucketIndex(maxArrayLength);
|
|
||||||
var buckets = new Bucket[maxBuckets + 1];
|
|
||||||
|
|
||||||
for (int i = 0; i < buckets.Length; i++) {
|
|
||||||
buckets[i] = new Bucket(Utilities.GetMaxSizeForBucket(i), maxArraysPerBucket, poolId);
|
|
||||||
}
|
|
||||||
|
|
||||||
_buckets = buckets;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int Id {
|
|
||||||
get {
|
|
||||||
return GetHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override T[] Rent(int minimumLength) {
|
|
||||||
if (minimumLength < 0)
|
|
||||||
throw new ArgumentOutOfRangeException("minimumLength");
|
|
||||||
else if (minimumLength == 0)
|
|
||||||
return s_emptyArray ?? (s_emptyArray = new T[0]);
|
|
||||||
|
|
||||||
#if NETSTACK_BUFFERS_LOG
|
|
||||||
var log = ArrayPoolEventSource.EventLog;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
T[] buffer = null;
|
|
||||||
int index = Utilities.SelectBucketIndex(minimumLength);
|
|
||||||
|
|
||||||
if (index < _buckets.Length) {
|
|
||||||
const int MaxBucketsToTry = 2;
|
|
||||||
|
|
||||||
int i = index;
|
|
||||||
|
|
||||||
do {
|
|
||||||
buffer = _buckets[i].Rent();
|
|
||||||
|
|
||||||
if (buffer != null) {
|
|
||||||
#if NETSTACK_BUFFERS_LOG
|
|
||||||
log.BufferRented(buffer.GetHashCode(), buffer.Length, Id, _buckets[i].Id);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (++i < _buckets.Length && i != index + MaxBucketsToTry);
|
|
||||||
|
|
||||||
buffer = new T[_buckets[index]._bufferLength];
|
|
||||||
} else {
|
|
||||||
buffer = new T[minimumLength];
|
|
||||||
}
|
|
||||||
|
|
||||||
#if NETSTACK_BUFFERS_LOG
|
|
||||||
int bufferId = buffer.GetHashCode(), bucketId = -1;
|
|
||||||
|
|
||||||
log.BufferRented(bufferId, buffer.Length, Id, bucketId);
|
|
||||||
log.BufferAllocated(bufferId, buffer.Length, Id, bucketId, index >= _buckets.Length ? ArrayPoolEventSource.BufferAllocatedReason.OverMaximumSize : ArrayPoolEventSource.BufferAllocatedReason.PoolExhausted);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Return(T[] array, bool clearArray = false) {
|
|
||||||
if (array == null)
|
|
||||||
throw new ArgumentNullException("array");
|
|
||||||
else if (array.Length == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
int bucket = Utilities.SelectBucketIndex(array.Length);
|
|
||||||
|
|
||||||
if (bucket < _buckets.Length) {
|
|
||||||
if (clearArray)
|
|
||||||
Array.Clear(array, 0, array.Length);
|
|
||||||
|
|
||||||
_buckets[bucket].Return(array);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if NETSTACK_BUFFERS_LOG
|
|
||||||
var log = ArrayPoolEventSource.EventLog;
|
|
||||||
|
|
||||||
log.BufferReturned(array.GetHashCode(), array.Length, Id);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018 Stanislav Denisov
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace NetStack.Buffers {
|
|
||||||
internal sealed partial class DefaultArrayPool<T> : ArrayPool<T> {
|
|
||||||
private sealed class Bucket {
|
|
||||||
internal readonly int _bufferLength;
|
|
||||||
private readonly T[][] _buffers;
|
|
||||||
#if NETSTACK_BUFFERS_LOG
|
|
||||||
private readonly int _poolId;
|
|
||||||
#endif
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
private SpinLock _lock;
|
|
||||||
#else
|
|
||||||
private object _lock;
|
|
||||||
#endif
|
|
||||||
private int _index;
|
|
||||||
|
|
||||||
internal Bucket(int bufferLength, int numberOfBuffers, int poolId) {
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
_lock = new SpinLock();
|
|
||||||
#else
|
|
||||||
_lock = new Object();
|
|
||||||
#endif
|
|
||||||
_buffers = new T[numberOfBuffers][];
|
|
||||||
_bufferLength = bufferLength;
|
|
||||||
|
|
||||||
#if NETSTACK_BUFFERS_LOG
|
|
||||||
_poolId = poolId;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
internal int Id => GetHashCode();
|
|
||||||
#else
|
|
||||||
internal int Id {
|
|
||||||
get {
|
|
||||||
return GetHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
internal T[] Rent() {
|
|
||||||
T[][] buffers = _buffers;
|
|
||||||
T[] buffer = null;
|
|
||||||
bool allocateBuffer = false;
|
|
||||||
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
bool lockTaken = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
_lock.Enter(ref lockTaken);
|
|
||||||
|
|
||||||
if (_index < buffers.Length) {
|
|
||||||
buffer = buffers[_index];
|
|
||||||
buffers[_index++] = null;
|
|
||||||
allocateBuffer = buffer == null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
finally {
|
|
||||||
if (lockTaken)
|
|
||||||
_lock.Exit(false);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
try {
|
|
||||||
Monitor.Enter(_lock);
|
|
||||||
|
|
||||||
if (_index < buffers.Length) {
|
|
||||||
buffer = buffers[_index];
|
|
||||||
buffers[_index++] = null;
|
|
||||||
allocateBuffer = buffer == null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
finally {
|
|
||||||
Monitor.Exit(_lock);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (allocateBuffer) {
|
|
||||||
buffer = new T[_bufferLength];
|
|
||||||
|
|
||||||
#if NETSTACK_BUFFERS_LOG
|
|
||||||
var log = ArrayPoolEventSource.EventLog;
|
|
||||||
|
|
||||||
log.BufferAllocated(buffer.GetHashCode(), _bufferLength, _poolId, Id, ArrayPoolEventSource.BufferAllocatedReason.Pooled);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void Return(T[] array) {
|
|
||||||
if (array.Length != _bufferLength)
|
|
||||||
throw new ArgumentException("BufferNotFromPool", "array");
|
|
||||||
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
bool lockTaken = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
_lock.Enter(ref lockTaken);
|
|
||||||
|
|
||||||
if (_index != 0)
|
|
||||||
_buffers[--_index] = array;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
finally {
|
|
||||||
if (lockTaken)
|
|
||||||
_lock.Exit(false);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
try {
|
|
||||||
Monitor.Enter(_lock);
|
|
||||||
|
|
||||||
if (_index != 0)
|
|
||||||
_buffers[--_index] = array;
|
|
||||||
}
|
|
||||||
|
|
||||||
finally {
|
|
||||||
Monitor.Exit(_lock);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018 Stanislav Denisov
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
using UnityEngine.Assertions;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace NetStack.Buffers {
|
|
||||||
internal static class Utilities {
|
|
||||||
[MethodImpl(256)]
|
|
||||||
internal static int SelectBucketIndex(int bufferSize) {
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
Assert.IsTrue(bufferSize > 0);
|
|
||||||
#else
|
|
||||||
Debug.Assert(bufferSize > 0);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
uint bitsRemaining = ((uint)bufferSize - 1) >> 4;
|
|
||||||
int poolIndex = 0;
|
|
||||||
|
|
||||||
if (bitsRemaining > 0xFFFF) {
|
|
||||||
bitsRemaining >>= 16;
|
|
||||||
poolIndex = 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bitsRemaining > 0xFF) {
|
|
||||||
bitsRemaining >>= 8;
|
|
||||||
poolIndex += 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bitsRemaining > 0xF) {
|
|
||||||
bitsRemaining >>= 4;
|
|
||||||
poolIndex += 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bitsRemaining > 0x3) {
|
|
||||||
bitsRemaining >>= 2;
|
|
||||||
poolIndex += 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bitsRemaining > 0x1) {
|
|
||||||
bitsRemaining >>= 1;
|
|
||||||
poolIndex += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return poolIndex + (int)bitsRemaining;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
internal static int GetMaxSizeForBucket(int binIndex) {
|
|
||||||
int maxSize = 16 << binIndex;
|
|
||||||
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
Assert.IsTrue(maxSize >= 0);
|
|
||||||
#else
|
|
||||||
Debug.Assert(maxSize >= 0);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return maxSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,184 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018 Stanislav Denisov
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
#if !(ENABLE_MONO || ENABLE_IL2CPP)
|
|
||||||
using System.Numerics;
|
|
||||||
#else
|
|
||||||
using UnityEngine;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace NetStack.Quantization {
|
|
||||||
public struct QuantizedVector2 {
|
|
||||||
public uint x;
|
|
||||||
public uint y;
|
|
||||||
|
|
||||||
public QuantizedVector2(uint x, uint y) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct QuantizedVector3 {
|
|
||||||
public uint x;
|
|
||||||
public uint y;
|
|
||||||
public uint z;
|
|
||||||
|
|
||||||
public QuantizedVector3(uint x, uint y, uint z) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.z = z;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct QuantizedVector4 {
|
|
||||||
public uint x;
|
|
||||||
public uint y;
|
|
||||||
public uint z;
|
|
||||||
public uint w;
|
|
||||||
|
|
||||||
public QuantizedVector4(uint x, uint y, uint z, uint w) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.z = z;
|
|
||||||
this.w = w;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class DeBruijn {
|
|
||||||
public static readonly int[] Lookup = new int[32] {
|
|
||||||
0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30,
|
|
||||||
8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BoundedRange {
|
|
||||||
private readonly float minValue;
|
|
||||||
private readonly float maxValue;
|
|
||||||
private readonly float precision;
|
|
||||||
private readonly int requiredBits;
|
|
||||||
private readonly uint mask;
|
|
||||||
|
|
||||||
public BoundedRange(float minValue, float maxValue, float precision) {
|
|
||||||
this.minValue = minValue;
|
|
||||||
this.maxValue = maxValue;
|
|
||||||
this.precision = precision;
|
|
||||||
|
|
||||||
requiredBits = Log2((uint)((maxValue - minValue) * (1.0f / precision) + 0.5f)) + 1;
|
|
||||||
mask = (uint)((1L << requiredBits) - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int Log2(uint value) {
|
|
||||||
value |= value >> 1;
|
|
||||||
value |= value >> 2;
|
|
||||||
value |= value >> 4;
|
|
||||||
value |= value >> 8;
|
|
||||||
value |= value >> 16;
|
|
||||||
|
|
||||||
return DeBruijn.Lookup[(value * 0x07C4ACDDU) >> 27];
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public uint Quantize(float value) {
|
|
||||||
if (value < minValue)
|
|
||||||
value = minValue;
|
|
||||||
else if (value > maxValue)
|
|
||||||
value = maxValue;
|
|
||||||
|
|
||||||
return (uint)((float)((value - minValue) * (1f / precision)) + 0.5f) & mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public float Dequantize(uint data) {
|
|
||||||
float adjusted = ((float)data * precision) + minValue;
|
|
||||||
|
|
||||||
if (adjusted < minValue)
|
|
||||||
adjusted = minValue;
|
|
||||||
else if (adjusted > maxValue)
|
|
||||||
adjusted = maxValue;
|
|
||||||
|
|
||||||
return adjusted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static QuantizedVector2 Quantize(Vector2 vector2, BoundedRange[] boundedRange) {
|
|
||||||
QuantizedVector2 data = default(QuantizedVector2);
|
|
||||||
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
data.x = boundedRange[0].Quantize(vector2.x);
|
|
||||||
data.y = boundedRange[1].Quantize(vector2.y);
|
|
||||||
#else
|
|
||||||
data.x = boundedRange[0].Quantize(vector2.X);
|
|
||||||
data.y = boundedRange[1].Quantize(vector2.Y);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static QuantizedVector3 Quantize(Vector3 vector3, BoundedRange[] boundedRange) {
|
|
||||||
QuantizedVector3 data = default(QuantizedVector3);
|
|
||||||
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
data.x = boundedRange[0].Quantize(vector3.x);
|
|
||||||
data.y = boundedRange[1].Quantize(vector3.y);
|
|
||||||
data.z = boundedRange[2].Quantize(vector3.z);
|
|
||||||
#else
|
|
||||||
data.x = boundedRange[0].Quantize(vector3.X);
|
|
||||||
data.y = boundedRange[1].Quantize(vector3.Y);
|
|
||||||
data.z = boundedRange[2].Quantize(vector3.Z);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static QuantizedVector4 Quantize(Vector4 vector4, BoundedRange[] boundedRange) {
|
|
||||||
QuantizedVector4 data = default(QuantizedVector4);
|
|
||||||
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
data.x = boundedRange[0].Quantize(vector4.x);
|
|
||||||
data.y = boundedRange[1].Quantize(vector4.y);
|
|
||||||
data.z = boundedRange[2].Quantize(vector4.z);
|
|
||||||
data.w = boundedRange[3].Quantize(vector4.w);
|
|
||||||
#else
|
|
||||||
data.x = boundedRange[0].Quantize(vector4.X);
|
|
||||||
data.y = boundedRange[1].Quantize(vector4.Y);
|
|
||||||
data.z = boundedRange[2].Quantize(vector4.Z);
|
|
||||||
data.w = boundedRange[3].Quantize(vector4.W);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Vector2 Dequantize(QuantizedVector2 data, BoundedRange[] boundedRange) {
|
|
||||||
return new Vector2(boundedRange[0].Dequantize(data.x), boundedRange[1].Dequantize(data.y));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Vector3 Dequantize(QuantizedVector3 data, BoundedRange[] boundedRange) {
|
|
||||||
return new Vector3(boundedRange[0].Dequantize(data.x), boundedRange[1].Dequantize(data.y), boundedRange[2].Dequantize(data.z));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Vector4 Dequantize(QuantizedVector4 data, BoundedRange[] boundedRange) {
|
|
||||||
return new Vector4(boundedRange[0].Dequantize(data.x), boundedRange[1].Dequantize(data.y), boundedRange[2].Dequantize(data.z), boundedRange[3].Dequantize(data.w));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018 Stanislav Denisov
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace NetStack.Quantization {
|
|
||||||
public static class HalfPrecision {
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
private struct Values {
|
|
||||||
[FieldOffset(0)]
|
|
||||||
public float f;
|
|
||||||
[FieldOffset(0)]
|
|
||||||
public int i;
|
|
||||||
[FieldOffset(0)]
|
|
||||||
public uint u;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public static ushort Quantize(float value) {
|
|
||||||
var values = new Values {
|
|
||||||
f = value
|
|
||||||
};
|
|
||||||
|
|
||||||
return Quantize(values.i);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ushort Quantize(int value) {
|
|
||||||
int s = (value >> 16) & 0x00008000;
|
|
||||||
int e = ((value >> 23) & 0X000000FF) - (127 - 15);
|
|
||||||
int m = value & 0X007FFFFF;
|
|
||||||
|
|
||||||
if (e <= 0) {
|
|
||||||
if (e < -10)
|
|
||||||
return (ushort)s;
|
|
||||||
|
|
||||||
m = m | 0x00800000;
|
|
||||||
|
|
||||||
int t = 14 - e;
|
|
||||||
int a = (1 << (t - 1)) - 1;
|
|
||||||
int b = (m >> t) & 1;
|
|
||||||
|
|
||||||
m = (m + a + b) >> t;
|
|
||||||
|
|
||||||
return (ushort)(s | m);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e == 0XFF - (127 - 15)) {
|
|
||||||
if (m == 0)
|
|
||||||
return (ushort)(s | 0X7C00);
|
|
||||||
|
|
||||||
m >>= 13;
|
|
||||||
|
|
||||||
return (ushort)(s | 0X7C00 | m | ((m == 0) ? 1 : 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
m = m + 0X00000FFF + ((m >> 13) & 1);
|
|
||||||
|
|
||||||
if ((m & 0x00800000) != 0) {
|
|
||||||
m = 0;
|
|
||||||
e++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e > 30)
|
|
||||||
return (ushort)(s | 0X7C00);
|
|
||||||
|
|
||||||
return (ushort)(s | (e << 10) | (m >> 13));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float Dequantize(ushort value) {
|
|
||||||
uint result;
|
|
||||||
uint mantissa = (uint)(value & 1023);
|
|
||||||
uint exponent = 0XFFFFFFF2;
|
|
||||||
|
|
||||||
if ((value & -33792) == 0) {
|
|
||||||
if (mantissa != 0) {
|
|
||||||
while ((mantissa & 1024) == 0) {
|
|
||||||
exponent--;
|
|
||||||
mantissa = mantissa << 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
mantissa &= 0XFFFFFBFF;
|
|
||||||
result = ((uint)((((uint)value & 0x8000) << 16) | ((exponent + 127) << 23))) | (mantissa << 13);
|
|
||||||
} else {
|
|
||||||
result = (uint)((value & 0x8000) << 16);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result = ((((uint)value & 0x8000) << 16) | ((((((uint)value >> 10) & 0X1F) - 15) + 127) << 23)) | (mantissa << 13);
|
|
||||||
}
|
|
||||||
|
|
||||||
var values = new Values {
|
|
||||||
u = result
|
|
||||||
};
|
|
||||||
|
|
||||||
return values.f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,223 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2020 Stanislav Denisov, Maxim Munning, Davin Carten
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
|
|
||||||
#if !(ENABLE_MONO || ENABLE_IL2CPP)
|
|
||||||
using System.Numerics;
|
|
||||||
#else
|
|
||||||
using UnityEngine;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace NetStack.Quantization {
|
|
||||||
public struct QuantizedQuaternion {
|
|
||||||
public uint m;
|
|
||||||
public uint a;
|
|
||||||
public uint b;
|
|
||||||
public uint c;
|
|
||||||
|
|
||||||
public QuantizedQuaternion(uint m, uint a, uint b, uint c) {
|
|
||||||
this.m = m;
|
|
||||||
this.a = a;
|
|
||||||
this.b = b;
|
|
||||||
this.c = c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class SmallestThree {
|
|
||||||
private const float smallestThreeUnpack = 0.70710678118654752440084436210485f + 0.0000001f;
|
|
||||||
private const float smallestThreePack = 1.0f / smallestThreeUnpack;
|
|
||||||
|
|
||||||
public static QuantizedQuaternion Quantize(Quaternion quaternion, int bitsPerElement = 12) {
|
|
||||||
float halfRange = (1 << bitsPerElement - 1);
|
|
||||||
float packer = smallestThreePack * halfRange;
|
|
||||||
float maxValue = float.MinValue;
|
|
||||||
bool signMinus = false;
|
|
||||||
uint m = 0;
|
|
||||||
uint a = 0;
|
|
||||||
uint b = 0;
|
|
||||||
uint c = 0;
|
|
||||||
|
|
||||||
for (uint i = 0; i <= 3; i++) {
|
|
||||||
float element = 0.0f;
|
|
||||||
float abs = 0.0f;
|
|
||||||
|
|
||||||
switch (i) {
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
case 0:
|
|
||||||
element = quaternion.x;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
element = quaternion.y;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
element = quaternion.z;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
element = quaternion.w;
|
|
||||||
|
|
||||||
break;
|
|
||||||
#else
|
|
||||||
case 0:
|
|
||||||
element = quaternion.X;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
element = quaternion.Y;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
element = quaternion.Z;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
element = quaternion.W;
|
|
||||||
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
abs = Math.Abs(element);
|
|
||||||
|
|
||||||
if (abs > maxValue) {
|
|
||||||
signMinus = (element < 0.0f);
|
|
||||||
m = i;
|
|
||||||
maxValue = abs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float af = 0.0f;
|
|
||||||
float bf = 0.0f;
|
|
||||||
float cf = 0.0f;
|
|
||||||
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
switch (m) {
|
|
||||||
case 0:
|
|
||||||
af = quaternion.y;
|
|
||||||
bf = quaternion.z;
|
|
||||||
cf = quaternion.w;
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
af = quaternion.x;
|
|
||||||
bf = quaternion.z;
|
|
||||||
cf = quaternion.w;
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
af = quaternion.x;
|
|
||||||
bf = quaternion.y;
|
|
||||||
cf = quaternion.w;
|
|
||||||
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
af = quaternion.x;
|
|
||||||
bf = quaternion.y;
|
|
||||||
cf = quaternion.z;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
switch (m) {
|
|
||||||
case 0:
|
|
||||||
af = quaternion.Y;
|
|
||||||
bf = quaternion.Z;
|
|
||||||
cf = quaternion.W;
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
af = quaternion.X;
|
|
||||||
bf = quaternion.Z;
|
|
||||||
cf = quaternion.W;
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
af = quaternion.X;
|
|
||||||
bf = quaternion.Y;
|
|
||||||
cf = quaternion.W;
|
|
||||||
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
af = quaternion.X;
|
|
||||||
bf = quaternion.Y;
|
|
||||||
cf = quaternion.Z;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (signMinus) {
|
|
||||||
a = (uint)((-af * packer) + halfRange);
|
|
||||||
b = (uint)((-bf * packer) + halfRange);
|
|
||||||
c = (uint)((-cf * packer) + halfRange);
|
|
||||||
} else {
|
|
||||||
a = (uint)((af * packer) + halfRange);
|
|
||||||
b = (uint)((bf * packer) + halfRange);
|
|
||||||
c = (uint)((cf * packer) + halfRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new QuantizedQuaternion(m, a, b, c);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Quaternion Dequantize(QuantizedQuaternion data, int bitsPerElement = 12) {
|
|
||||||
int halfRange = (1 << bitsPerElement - 1);
|
|
||||||
float unpacker = smallestThreeUnpack * (1.0f / halfRange);
|
|
||||||
uint m = data.m;
|
|
||||||
int ai = (int)data.a;
|
|
||||||
int bi = (int)data.b;
|
|
||||||
int ci = (int)data.c;
|
|
||||||
|
|
||||||
ai -= halfRange;
|
|
||||||
bi -= halfRange;
|
|
||||||
ci -= halfRange;
|
|
||||||
|
|
||||||
float a = ai * unpacker;
|
|
||||||
float b = bi * unpacker;
|
|
||||||
float c = ci * unpacker;
|
|
||||||
|
|
||||||
float d = (float)Math.Sqrt(1.0f - ((a * a) + (b * b) + (c * c)));
|
|
||||||
|
|
||||||
switch (m) {
|
|
||||||
case 0:
|
|
||||||
return new Quaternion(d, a, b, c);
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
return new Quaternion(a, d, b, c);
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
return new Quaternion(a, b, d, c);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return new Quaternion(a, b, c, d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,546 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018 Stanislav Denisov, Maxim Munnig
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copyright (c) 2018 Alexander Shoulson
|
|
||||||
*
|
|
||||||
* This software is provided 'as-is', without any express or implied
|
|
||||||
* warranty. In no event will the authors be held liable for any damages
|
|
||||||
* arising from the use of this software.
|
|
||||||
* Permission is granted to anyone to use this software for any purpose,
|
|
||||||
* including commercial applications, and to alter it and redistribute it
|
|
||||||
* freely, subject to the following restrictions:
|
|
||||||
*
|
|
||||||
* 1. The origin of this software must not be misrepresented; you must not
|
|
||||||
* claim that you wrote the original software. If you use this software
|
|
||||||
* in a product, an acknowledgment in the product documentation would be
|
|
||||||
* appreciated but is not required.
|
|
||||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
|
||||||
* misrepresented as being the original software.
|
|
||||||
* 3. This notice may not be removed or altered from any source distribution.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
using UnityEngine.Assertions;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace NetStack.Serialization {
|
|
||||||
public class BitBuffer {
|
|
||||||
private const int defaultCapacity = 375; // 375 * 4 = 1500 bytes
|
|
||||||
private const int stringLengthBits = 8;
|
|
||||||
private const int stringLengthMax = (1 << stringLengthBits) - 1; // 255
|
|
||||||
private const int bitsASCII = 7;
|
|
||||||
private const int growFactor = 2;
|
|
||||||
private const int minGrow = 1;
|
|
||||||
private int readPosition;
|
|
||||||
private int nextPosition;
|
|
||||||
private uint[] chunks;
|
|
||||||
|
|
||||||
public BitBuffer(int capacity = defaultCapacity) {
|
|
||||||
readPosition = 0;
|
|
||||||
nextPosition = 0;
|
|
||||||
chunks = new uint[capacity];
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Length {
|
|
||||||
get {
|
|
||||||
return ((nextPosition - 1) >> 3) + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsFinished {
|
|
||||||
get {
|
|
||||||
return nextPosition == readPosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public void Clear() {
|
|
||||||
readPosition = 0;
|
|
||||||
nextPosition = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public BitBuffer Add(int numBits, uint value) {
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
Assert.IsFalse(numBits < 0); // Pushing negative bits
|
|
||||||
Assert.IsFalse(numBits > 32); // Pushing too many bits
|
|
||||||
#else
|
|
||||||
Debug.Assert(!(numBits < 0));
|
|
||||||
Debug.Assert(!(numBits > 32));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int index = nextPosition >> 5;
|
|
||||||
int used = nextPosition & 0x0000001F;
|
|
||||||
|
|
||||||
if ((index + 1) >= chunks.Length)
|
|
||||||
ExpandArray();
|
|
||||||
|
|
||||||
ulong chunkMask = ((1UL << used) - 1);
|
|
||||||
ulong scratch = chunks[index] & chunkMask;
|
|
||||||
ulong result = scratch | ((ulong)value << used);
|
|
||||||
|
|
||||||
chunks[index] = (uint)result;
|
|
||||||
chunks[index + 1] = (uint)(result >> 32);
|
|
||||||
nextPosition += numBits;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public uint Read(int numBits) {
|
|
||||||
uint result = Peek(numBits);
|
|
||||||
|
|
||||||
readPosition += numBits;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public uint Peek(int numBits) {
|
|
||||||
#if ENABLE_MONO || ENABLE_IL2CPP
|
|
||||||
Assert.IsFalse(numBits < 0); // Pushing negative bits
|
|
||||||
Assert.IsFalse(numBits > 32); // Pushing too many bits
|
|
||||||
#else
|
|
||||||
Debug.Assert(!(numBits < 0));
|
|
||||||
Debug.Assert(!(numBits > 32));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int index = readPosition >> 5;
|
|
||||||
int used = readPosition & 0x0000001F;
|
|
||||||
|
|
||||||
ulong chunkMask = ((1UL << numBits) - 1) << used;
|
|
||||||
ulong scratch = (ulong)chunks[index];
|
|
||||||
|
|
||||||
if ((index + 1) < chunks.Length)
|
|
||||||
scratch |= (ulong)chunks[index + 1] << 32;
|
|
||||||
|
|
||||||
ulong result = (scratch & chunkMask) >> used;
|
|
||||||
|
|
||||||
return (uint)result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int ToArray(byte[] data) {
|
|
||||||
Add(1, 1);
|
|
||||||
|
|
||||||
int numChunks = (nextPosition >> 5) + 1;
|
|
||||||
int length = data.Length;
|
|
||||||
|
|
||||||
for (int i = 0; i < numChunks; i++) {
|
|
||||||
int dataIdx = i * 4;
|
|
||||||
uint chunk = chunks[i];
|
|
||||||
|
|
||||||
if (dataIdx < length)
|
|
||||||
data[dataIdx] = (byte)(chunk);
|
|
||||||
|
|
||||||
if (dataIdx + 1 < length)
|
|
||||||
data[dataIdx + 1] = (byte)(chunk >> 8);
|
|
||||||
|
|
||||||
if (dataIdx + 2 < length)
|
|
||||||
data[dataIdx + 2] = (byte)(chunk >> 16);
|
|
||||||
|
|
||||||
if (dataIdx + 3 < length)
|
|
||||||
data[dataIdx + 3] = (byte)(chunk >> 24);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FromArray(byte[] data, int length) {
|
|
||||||
int numChunks = (length / 4) + 1;
|
|
||||||
|
|
||||||
if (chunks.Length < numChunks)
|
|
||||||
chunks = new uint[numChunks];
|
|
||||||
|
|
||||||
for (int i = 0; i < numChunks; i++) {
|
|
||||||
int dataIdx = i * 4;
|
|
||||||
uint chunk = 0;
|
|
||||||
|
|
||||||
if (dataIdx < length)
|
|
||||||
chunk = (uint)data[dataIdx];
|
|
||||||
|
|
||||||
if (dataIdx + 1 < length)
|
|
||||||
chunk = chunk | (uint)data[dataIdx + 1] << 8;
|
|
||||||
|
|
||||||
if (dataIdx + 2 < length)
|
|
||||||
chunk = chunk | (uint)data[dataIdx + 2] << 16;
|
|
||||||
|
|
||||||
if (dataIdx + 3 < length)
|
|
||||||
chunk = chunk | (uint)data[dataIdx + 3] << 24;
|
|
||||||
|
|
||||||
chunks[i] = chunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
int positionInByte = FindHighestBitPosition(data[length - 1]);
|
|
||||||
|
|
||||||
nextPosition = ((length - 1) * 8) + (positionInByte - 1);
|
|
||||||
readPosition = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if NETSTACK_SPAN
|
|
||||||
public int ToSpan(ref Span<byte> data) {
|
|
||||||
Add(1, 1);
|
|
||||||
|
|
||||||
int numChunks = (nextPosition >> 5) + 1;
|
|
||||||
int length = data.Length;
|
|
||||||
|
|
||||||
for (int i = 0; i < numChunks; i++) {
|
|
||||||
int dataIdx = i * 4;
|
|
||||||
uint chunk = chunks[i];
|
|
||||||
|
|
||||||
if (dataIdx < length)
|
|
||||||
data[dataIdx] = (byte)(chunk);
|
|
||||||
|
|
||||||
if (dataIdx + 1 < length)
|
|
||||||
data[dataIdx + 1] = (byte)(chunk >> 8);
|
|
||||||
|
|
||||||
if (dataIdx + 2 < length)
|
|
||||||
data[dataIdx + 2] = (byte)(chunk >> 16);
|
|
||||||
|
|
||||||
if (dataIdx + 3 < length)
|
|
||||||
data[dataIdx + 3] = (byte)(chunk >> 24);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FromSpan(ref ReadOnlySpan<byte> data, int length) {
|
|
||||||
int numChunks = (length / 4) + 1;
|
|
||||||
|
|
||||||
if (chunks.Length < numChunks)
|
|
||||||
chunks = new uint[numChunks];
|
|
||||||
|
|
||||||
for (int i = 0; i < numChunks; i++) {
|
|
||||||
int dataIdx = i * 4;
|
|
||||||
uint chunk = 0;
|
|
||||||
|
|
||||||
if (dataIdx < length)
|
|
||||||
chunk = (uint)data[dataIdx];
|
|
||||||
|
|
||||||
if (dataIdx + 1 < length)
|
|
||||||
chunk = chunk | (uint)data[dataIdx + 1] << 8;
|
|
||||||
|
|
||||||
if (dataIdx + 2 < length)
|
|
||||||
chunk = chunk | (uint)data[dataIdx + 2] << 16;
|
|
||||||
|
|
||||||
if (dataIdx + 3 < length)
|
|
||||||
chunk = chunk | (uint)data[dataIdx + 3] << 24;
|
|
||||||
|
|
||||||
chunks[i] = chunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
int positionInByte = FindHighestBitPosition(data[length - 1]);
|
|
||||||
|
|
||||||
nextPosition = ((length - 1) * 8) + (positionInByte - 1);
|
|
||||||
readPosition = 0;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public BitBuffer AddBool(bool value) {
|
|
||||||
Add(1, value ? 1U : 0U);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public bool ReadBool() {
|
|
||||||
return Read(1) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public bool PeekBool() {
|
|
||||||
return Peek(1) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public BitBuffer AddByte(byte value) {
|
|
||||||
Add(8, value);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public byte ReadByte() {
|
|
||||||
return (byte)Read(8);
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public byte PeekByte() {
|
|
||||||
return (byte)Peek(8);
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public BitBuffer AddShort(short value) {
|
|
||||||
AddInt(value);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public short ReadShort() {
|
|
||||||
return (short)ReadInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public short PeekShort() {
|
|
||||||
return (short)PeekInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public BitBuffer AddUShort(ushort value) {
|
|
||||||
AddUInt(value);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public ushort ReadUShort() {
|
|
||||||
return (ushort)ReadUInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public ushort PeekUShort() {
|
|
||||||
return (ushort)PeekUInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public BitBuffer AddInt(int value) {
|
|
||||||
uint zigzag = (uint)((value << 1) ^ (value >> 31));
|
|
||||||
|
|
||||||
AddUInt(zigzag);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public int ReadInt() {
|
|
||||||
uint value = ReadUInt();
|
|
||||||
int zagzig = (int)((value >> 1) ^ (-(int)(value & 1)));
|
|
||||||
|
|
||||||
return zagzig;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public int PeekInt() {
|
|
||||||
uint value = PeekUInt();
|
|
||||||
int zagzig = (int)((value >> 1) ^ (-(int)(value & 1)));
|
|
||||||
|
|
||||||
return zagzig;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public BitBuffer AddUInt(uint value) {
|
|
||||||
uint buffer = 0x0u;
|
|
||||||
|
|
||||||
do {
|
|
||||||
buffer = value & 0x7Fu;
|
|
||||||
value >>= 7;
|
|
||||||
|
|
||||||
if (value > 0)
|
|
||||||
buffer |= 0x80u;
|
|
||||||
|
|
||||||
Add(8, buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (value > 0);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public uint ReadUInt() {
|
|
||||||
uint buffer = 0x0u;
|
|
||||||
uint value = 0x0u;
|
|
||||||
int shift = 0;
|
|
||||||
|
|
||||||
do {
|
|
||||||
buffer = Read(8);
|
|
||||||
|
|
||||||
value |= (buffer & 0x7Fu) << shift;
|
|
||||||
shift += 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
while ((buffer & 0x80u) > 0);
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public uint PeekUInt() {
|
|
||||||
int tempPosition = readPosition;
|
|
||||||
uint value = ReadUInt();
|
|
||||||
|
|
||||||
readPosition = tempPosition;
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public BitBuffer AddLong(long value) {
|
|
||||||
AddInt((int)(value & uint.MaxValue));
|
|
||||||
AddInt((int)(value >> 32));
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public long ReadLong() {
|
|
||||||
int low = ReadInt();
|
|
||||||
int high = ReadInt();
|
|
||||||
long value = high;
|
|
||||||
|
|
||||||
return value << 32 | (uint)low;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public long PeekLong() {
|
|
||||||
int tempPosition = readPosition;
|
|
||||||
long value = ReadLong();
|
|
||||||
|
|
||||||
readPosition = tempPosition;
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public BitBuffer AddULong(ulong value) {
|
|
||||||
AddUInt((uint)(value & uint.MaxValue));
|
|
||||||
AddUInt((uint)(value >> 32));
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public ulong ReadULong() {
|
|
||||||
uint low = ReadUInt();
|
|
||||||
uint high = ReadUInt();
|
|
||||||
|
|
||||||
return (ulong)high << 32 | low;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public ulong PeekULong() {
|
|
||||||
int tempPosition = readPosition;
|
|
||||||
ulong value = ReadULong();
|
|
||||||
|
|
||||||
readPosition = tempPosition;
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public BitBuffer AddString(string value) {
|
|
||||||
if (value == null)
|
|
||||||
throw new ArgumentNullException("value");
|
|
||||||
|
|
||||||
uint length = (uint)value.Length;
|
|
||||||
|
|
||||||
if (length > stringLengthMax) {
|
|
||||||
length = (uint)stringLengthMax;
|
|
||||||
|
|
||||||
throw new ArgumentOutOfRangeException("value length exceeded");
|
|
||||||
}
|
|
||||||
|
|
||||||
Add(stringLengthBits, length);
|
|
||||||
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
Add(bitsASCII, ToASCII(value[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public string ReadString() {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
uint length = Read(stringLengthBits);
|
|
||||||
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
builder.Append((char)Read(bitsASCII));
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString() {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
|
|
||||||
for (int i = chunks.Length - 1; i >= 0; i--) {
|
|
||||||
builder.Append(Convert.ToString(chunks[i], 2).PadLeft(32, '0'));
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder spaced = new StringBuilder();
|
|
||||||
|
|
||||||
for (int i = 0; i < builder.Length; i++) {
|
|
||||||
spaced.Append(builder[i]);
|
|
||||||
|
|
||||||
if (((i + 1) % 8) == 0)
|
|
||||||
spaced.Append(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
return spaced.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ExpandArray() {
|
|
||||||
int newCapacity = (chunks.Length * growFactor) + minGrow;
|
|
||||||
uint[] newChunks = new uint[newCapacity];
|
|
||||||
|
|
||||||
Array.Copy(chunks, newChunks, chunks.Length);
|
|
||||||
chunks = newChunks;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
private static int FindHighestBitPosition(byte data) {
|
|
||||||
int shiftCount = 0;
|
|
||||||
|
|
||||||
while (data > 0) {
|
|
||||||
data >>= 1;
|
|
||||||
shiftCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return shiftCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte ToASCII(char character) {
|
|
||||||
byte value = 0;
|
|
||||||
|
|
||||||
try {
|
|
||||||
value = Convert.ToByte(character);
|
|
||||||
}
|
|
||||||
|
|
||||||
catch (OverflowException) {
|
|
||||||
throw new Exception("Cannot convert to ASCII: " + character);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value > 127)
|
|
||||||
throw new Exception("Cannot convert to ASCII: " + character);
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2019 Stanislav Denisov
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace NetStack.Threading {
|
|
||||||
[StructLayout(LayoutKind.Explicit, Size = 192)]
|
|
||||||
public sealed class ArrayQueue {
|
|
||||||
[FieldOffset(0)]
|
|
||||||
private readonly Entry[] _array;
|
|
||||||
[FieldOffset(8)]
|
|
||||||
private readonly int _arrayMask;
|
|
||||||
[FieldOffset(64)]
|
|
||||||
private int _enqueuePosition;
|
|
||||||
[FieldOffset(128)]
|
|
||||||
private int _dequeuePosition;
|
|
||||||
|
|
||||||
public int Count {
|
|
||||||
get {
|
|
||||||
return _enqueuePosition - _dequeuePosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArrayQueue(int capacity) {
|
|
||||||
if (capacity < 2)
|
|
||||||
throw new ArgumentException("Queue size should be greater than or equal to two");
|
|
||||||
|
|
||||||
if ((capacity & (capacity - 1)) != 0)
|
|
||||||
throw new ArgumentException("Queue size should be a power of two");
|
|
||||||
|
|
||||||
_arrayMask = capacity - 1;
|
|
||||||
_array = new Entry[capacity];
|
|
||||||
_enqueuePosition = 0;
|
|
||||||
_dequeuePosition = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Enqueue(object item) {
|
|
||||||
while (true) {
|
|
||||||
if (TryEnqueue(item))
|
|
||||||
break;
|
|
||||||
|
|
||||||
Thread.SpinWait(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryEnqueue(object item) {
|
|
||||||
var array = _array;
|
|
||||||
var position = _enqueuePosition;
|
|
||||||
var index = position & _arrayMask;
|
|
||||||
|
|
||||||
if (array[index].IsSet != 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
array[index].element = item;
|
|
||||||
array[index].IsSet = 1;
|
|
||||||
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
Volatile.Write(ref _enqueuePosition, position + 1);
|
|
||||||
#else
|
|
||||||
Thread.MemoryBarrier();
|
|
||||||
_enqueuePosition = position + 1;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Dequeue() {
|
|
||||||
while (true) {
|
|
||||||
object element;
|
|
||||||
|
|
||||||
if (TryDequeue(out element))
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryDequeue(out object result) {
|
|
||||||
var array = _array;
|
|
||||||
var position = _dequeuePosition;
|
|
||||||
var index = position & _arrayMask;
|
|
||||||
|
|
||||||
if (array[index].IsSet == 0) {
|
|
||||||
result = default(object);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = array[index].element;
|
|
||||||
array[index].element = default(object);
|
|
||||||
array[index].IsSet = 0;
|
|
||||||
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
Volatile.Write(ref _dequeuePosition, position + 1);
|
|
||||||
#else
|
|
||||||
Thread.MemoryBarrier();
|
|
||||||
_dequeuePosition = position + 1;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit, Size = 16)]
|
|
||||||
private struct Entry {
|
|
||||||
[FieldOffset(0)]
|
|
||||||
private int isSet;
|
|
||||||
[FieldOffset(8)]
|
|
||||||
internal object element;
|
|
||||||
|
|
||||||
internal int IsSet {
|
|
||||||
get {
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
return Volatile.Read(ref isSet);
|
|
||||||
#else
|
|
||||||
Thread.MemoryBarrier();
|
|
||||||
return isSet;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
set {
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
Volatile.Write(ref isSet, value);
|
|
||||||
#else
|
|
||||||
Thread.MemoryBarrier();
|
|
||||||
isSet = value;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018 Alexander Nikitin, Stanislav Denisov
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace NetStack.Threading {
|
|
||||||
[StructLayout(LayoutKind.Explicit, Size = 192)]
|
|
||||||
public sealed class ConcurrentBuffer {
|
|
||||||
[FieldOffset(0)]
|
|
||||||
private readonly Cell[] _buffer;
|
|
||||||
[FieldOffset(8)]
|
|
||||||
private readonly int _bufferMask;
|
|
||||||
[FieldOffset(64)]
|
|
||||||
private int _enqueuePosition;
|
|
||||||
[FieldOffset(128)]
|
|
||||||
private int _dequeuePosition;
|
|
||||||
|
|
||||||
public int Count {
|
|
||||||
get {
|
|
||||||
return _enqueuePosition - _dequeuePosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConcurrentBuffer(int bufferSize) {
|
|
||||||
if (bufferSize < 2)
|
|
||||||
throw new ArgumentException("Buffer size should be greater than or equal to two");
|
|
||||||
|
|
||||||
if ((bufferSize & (bufferSize - 1)) != 0)
|
|
||||||
throw new ArgumentException("Buffer size should be a power of two");
|
|
||||||
|
|
||||||
_bufferMask = bufferSize - 1;
|
|
||||||
_buffer = new Cell[bufferSize];
|
|
||||||
|
|
||||||
for (var i = 0; i < bufferSize; i++) {
|
|
||||||
_buffer[i] = new Cell(i, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
_enqueuePosition = 0;
|
|
||||||
_dequeuePosition = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Enqueue(object item) {
|
|
||||||
while (true) {
|
|
||||||
if (TryEnqueue(item))
|
|
||||||
break;
|
|
||||||
|
|
||||||
Thread.SpinWait(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryEnqueue(object item) {
|
|
||||||
do {
|
|
||||||
var buffer = _buffer;
|
|
||||||
var position = _enqueuePosition;
|
|
||||||
var index = position & _bufferMask;
|
|
||||||
var cell = buffer[index];
|
|
||||||
|
|
||||||
if (cell.Sequence == position && Interlocked.CompareExchange(ref _enqueuePosition, position + 1, position) == position) {
|
|
||||||
buffer[index].Element = item;
|
|
||||||
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
Volatile.Write(ref buffer[index].Sequence, position + 1);
|
|
||||||
#else
|
|
||||||
Thread.MemoryBarrier();
|
|
||||||
buffer[index].Sequence = position + 1;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cell.Sequence < position)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Dequeue() {
|
|
||||||
while (true) {
|
|
||||||
object element;
|
|
||||||
|
|
||||||
if (TryDequeue(out element))
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryDequeue(out object result) {
|
|
||||||
do {
|
|
||||||
var buffer = _buffer;
|
|
||||||
var bufferMask = _bufferMask;
|
|
||||||
var position = _dequeuePosition;
|
|
||||||
var index = position & bufferMask;
|
|
||||||
var cell = buffer[index];
|
|
||||||
|
|
||||||
if (cell.Sequence == position + 1 && Interlocked.CompareExchange(ref _dequeuePosition, position + 1, position) == position) {
|
|
||||||
result = cell.Element;
|
|
||||||
buffer[index].Element = null;
|
|
||||||
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
Volatile.Write(ref buffer[index].Sequence, position + bufferMask + 1);
|
|
||||||
#else
|
|
||||||
Thread.MemoryBarrier();
|
|
||||||
buffer[index].Sequence = position + bufferMask + 1;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cell.Sequence < position + 1) {
|
|
||||||
result = default(object);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true);
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit, Size = 16)]
|
|
||||||
private struct Cell {
|
|
||||||
[FieldOffset(0)]
|
|
||||||
public int Sequence;
|
|
||||||
[FieldOffset(8)]
|
|
||||||
public object Element;
|
|
||||||
|
|
||||||
public Cell(int sequence, object element) {
|
|
||||||
Sequence = sequence;
|
|
||||||
Element = element;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018 Virgile Bello, Stanislav Denisov
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace NetStack.Threading {
|
|
||||||
public sealed class ConcurrentPool<T> where T : class {
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
private SpinLock _lock;
|
|
||||||
#else
|
|
||||||
private object _lock;
|
|
||||||
#endif
|
|
||||||
private readonly Func<T> _factory;
|
|
||||||
private Segment _head;
|
|
||||||
private Segment _tail;
|
|
||||||
|
|
||||||
public ConcurrentPool(int capacity, Func<T> factory) {
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
_lock = new SpinLock();
|
|
||||||
#else
|
|
||||||
_lock = new Object();
|
|
||||||
#endif
|
|
||||||
_head = _tail = new Segment(capacity);
|
|
||||||
_factory = factory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public T Acquire() {
|
|
||||||
while (true) {
|
|
||||||
var localHead = _head;
|
|
||||||
var count = localHead.Count;
|
|
||||||
|
|
||||||
if (count == 0) {
|
|
||||||
if (localHead.Next != null) {
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
bool lockTaken = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
_lock.Enter(ref lockTaken);
|
|
||||||
|
|
||||||
if (_head.Next != null && _head.Count == 0)
|
|
||||||
_head = _head.Next;
|
|
||||||
}
|
|
||||||
|
|
||||||
finally {
|
|
||||||
if (lockTaken)
|
|
||||||
_lock.Exit(false);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
try {
|
|
||||||
Monitor.Enter(_lock);
|
|
||||||
|
|
||||||
if (_head.Next != null && _head.Count == 0)
|
|
||||||
_head = _head.Next;
|
|
||||||
}
|
|
||||||
|
|
||||||
finally {
|
|
||||||
Monitor.Exit(_lock);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
} else {
|
|
||||||
return _factory();
|
|
||||||
}
|
|
||||||
} else if (Interlocked.CompareExchange(ref localHead.Count, count - 1, count) == count) {
|
|
||||||
var localLow = Interlocked.Increment(ref localHead.Low) - 1;
|
|
||||||
var index = localLow & localHead.Mask;
|
|
||||||
T item;
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
var spinWait = new SpinWait();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
while ((item = Interlocked.Exchange(ref localHead.Items[index], null)) == null) {
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
spinWait.SpinOnce();
|
|
||||||
#else
|
|
||||||
Thread.SpinWait(1);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Release(T item) {
|
|
||||||
while (true) {
|
|
||||||
var localTail = _tail;
|
|
||||||
var count = localTail.Count;
|
|
||||||
|
|
||||||
if (count == localTail.Items.Length) {
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
bool lockTaken = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
_lock.Enter(ref lockTaken);
|
|
||||||
|
|
||||||
if (_tail.Next == null && count == _tail.Items.Length)
|
|
||||||
_tail = _tail.Next = new Segment(_tail.Items.Length << 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
finally {
|
|
||||||
if (lockTaken)
|
|
||||||
_lock.Exit(false);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
try {
|
|
||||||
Monitor.Enter(_lock);
|
|
||||||
|
|
||||||
if (_tail.Next == null && count == _tail.Items.Length)
|
|
||||||
_tail = _tail.Next = new Segment(_tail.Items.Length << 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
finally {
|
|
||||||
Monitor.Exit(_lock);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
} else if (Interlocked.CompareExchange(ref localTail.Count, count + 1, count) == count) {
|
|
||||||
var localHigh = Interlocked.Increment(ref localTail.High) - 1;
|
|
||||||
var index = localHigh & localTail.Mask;
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
var spinWait = new SpinWait();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
while (Interlocked.CompareExchange(ref localTail.Items[index], item, null) != null) {
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
spinWait.SpinOnce();
|
|
||||||
#else
|
|
||||||
Thread.SpinWait(1);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Segment {
|
|
||||||
public readonly T[] Items;
|
|
||||||
public readonly int Mask;
|
|
||||||
public int Low;
|
|
||||||
public int High;
|
|
||||||
public int Count;
|
|
||||||
public Segment Next;
|
|
||||||
|
|
||||||
public Segment(int size) {
|
|
||||||
if (size <= 0 || ((size & (size - 1)) != 0))
|
|
||||||
throw new ArgumentOutOfRangeException("Segment size must be power of two");
|
|
||||||
|
|
||||||
Items = new T[size];
|
|
||||||
Mask = size - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-63
@@ -1,63 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018 Stanislav Denisov
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace NetStack.Unsafe {
|
|
||||||
#if NET_4_6 || NET_STANDARD_2_0
|
|
||||||
public static class Memory {
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public static unsafe void Copy(IntPtr source, int sourceOffset, byte[] destination, int destinationOffset, int length) {
|
|
||||||
if (length > 0) {
|
|
||||||
fixed (byte* destinationPointer = &destination[destinationOffset]) {
|
|
||||||
byte* sourcePointer = (byte*)source + sourceOffset;
|
|
||||||
|
|
||||||
Buffer.MemoryCopy(sourcePointer, destinationPointer, length, length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public static unsafe void Copy(byte[] source, int sourceOffset, IntPtr destination, int destinationOffset, int length) {
|
|
||||||
if (length > 0) {
|
|
||||||
fixed (byte* sourcePointer = &source[sourceOffset]) {
|
|
||||||
byte* destinationPointer = (byte*)destination + destinationOffset;
|
|
||||||
|
|
||||||
Buffer.MemoryCopy(sourcePointer, destinationPointer, length, length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(256)]
|
|
||||||
public static unsafe void Copy(byte[] source, int sourceOffset, byte[] destination, int destinationOffset, int length) {
|
|
||||||
if (length > 0) {
|
|
||||||
fixed (byte* sourcePointer = &source[sourceOffset]) {
|
|
||||||
fixed (byte* destinationPointer = &destination[destinationOffset]) {
|
|
||||||
Buffer.MemoryCopy(sourcePointer, destinationPointer, length, length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
using NetStack.Serialization;
|
|
||||||
|
|
||||||
namespace Ragon.Common
|
|
||||||
{
|
|
||||||
public interface IRagonSerializable
|
|
||||||
{
|
|
||||||
public void Serialize(BitBuffer buffer);
|
|
||||||
public void Deserialize(BitBuffer buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
|
|
||||||
namespace Ragon.Common
|
|
||||||
{
|
|
||||||
|
|
||||||
public class RagonSerializer
|
|
||||||
{
|
|
||||||
private byte[] _data;
|
|
||||||
private int _offset;
|
|
||||||
private int _size;
|
|
||||||
public int Lenght => _offset;
|
|
||||||
public int Size => _size - _offset;
|
|
||||||
|
|
||||||
public RagonSerializer(int capacity = 256)
|
|
||||||
{
|
|
||||||
_data = new byte[capacity];
|
|
||||||
_offset = 0;
|
|
||||||
_size = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public void WriteByte(byte value)
|
|
||||||
{
|
|
||||||
ResizeIfNeed(1);
|
|
||||||
|
|
||||||
_data[_offset] = value;
|
|
||||||
_offset += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public byte ReadByte()
|
|
||||||
{
|
|
||||||
var value = _data[_offset];
|
|
||||||
_offset += 1;
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public void WriteInt(int value)
|
|
||||||
{
|
|
||||||
ResizeIfNeed(4);
|
|
||||||
|
|
||||||
_data[_offset] = (byte) (value & 0x00FF);
|
|
||||||
_data[_offset + 1] = (byte) ((value & 0xFF00) >> 8);
|
|
||||||
_data[_offset + 2] = (byte) ((value & 0xFF00) >> 16);
|
|
||||||
_data[_offset + 3] = (byte) ((value & 0xFF00) >> 24);
|
|
||||||
_offset += 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public int ReadInt()
|
|
||||||
{
|
|
||||||
var value = _data[_offset] + (_data[_offset + 1] << 8) + (_data[_offset + 2] << 16) + (_data[_offset + 3] << 24);
|
|
||||||
_offset += 4;
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public void WriteString(string value)
|
|
||||||
{
|
|
||||||
var stringRaw = Encoding.UTF8.GetBytes(value).AsSpan();
|
|
||||||
ResizeIfNeed(2 + stringRaw.Length);
|
|
||||||
|
|
||||||
WriteUShort((ushort) stringRaw.Length);
|
|
||||||
var data = _data.AsSpan().Slice(_offset, stringRaw.Length);
|
|
||||||
stringRaw.CopyTo(data);
|
|
||||||
_offset += stringRaw.Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public string ReadString()
|
|
||||||
{
|
|
||||||
var lenght = ReadUShort();
|
|
||||||
var stringRaw = _data.AsSpan().Slice(_offset, lenght);
|
|
||||||
var str = Encoding.UTF8.GetString(stringRaw);
|
|
||||||
_offset += lenght;
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public ReadOnlySpan<byte> ReadData(int lenght)
|
|
||||||
{
|
|
||||||
var data = _data.AsSpan();
|
|
||||||
var payloadData = data.Slice(_offset, lenght);
|
|
||||||
|
|
||||||
_offset += payloadData.Length;
|
|
||||||
return payloadData;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public void WriteData(ref ReadOnlySpan<byte> payload)
|
|
||||||
{
|
|
||||||
ResizeIfNeed(payload.Length);
|
|
||||||
|
|
||||||
var data = _data.AsSpan();
|
|
||||||
var payloadData = data.Slice(_offset, payload.Length);
|
|
||||||
|
|
||||||
payload.CopyTo(payloadData);
|
|
||||||
_offset += payload.Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public Span<byte> GetWritableData(int lenght)
|
|
||||||
{
|
|
||||||
ResizeIfNeed(lenght);
|
|
||||||
|
|
||||||
var data = _data.AsSpan();
|
|
||||||
var payloadData = data.Slice(_offset, lenght);
|
|
||||||
|
|
||||||
_offset += lenght;
|
|
||||||
return payloadData;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public void WriteOperation(RagonOperation ragonOperation)
|
|
||||||
{
|
|
||||||
ResizeIfNeed(1);
|
|
||||||
|
|
||||||
_data[_offset] = (byte) ragonOperation;
|
|
||||||
_offset += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public RagonOperation ReadOperation()
|
|
||||||
{
|
|
||||||
var op = (RagonOperation) _data[_offset];
|
|
||||||
_offset += 1;
|
|
||||||
return op;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public void WriteUShort(ushort value)
|
|
||||||
{
|
|
||||||
ResizeIfNeed(2);
|
|
||||||
|
|
||||||
_data[_offset] = (byte) (value & 0x00FF);
|
|
||||||
_data[_offset + 1] = (byte) ((value & 0xFF00) >> 8);
|
|
||||||
_offset += 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public ushort ReadUShort()
|
|
||||||
{
|
|
||||||
var value = (ushort) (_data[_offset] + (_data[_offset + 1] << 8));
|
|
||||||
_offset += 2;
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
_offset = 0;
|
|
||||||
_size = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ToSpan(ref Span<byte> data)
|
|
||||||
{
|
|
||||||
var span = _data.AsSpan();
|
|
||||||
var dataSpan = span.Slice(0, _offset);
|
|
||||||
dataSpan.CopyTo(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FromSpan(ref ReadOnlySpan<byte> data)
|
|
||||||
{
|
|
||||||
Clear();
|
|
||||||
ResizeIfNeed(data.Length);
|
|
||||||
var dataSpan = _data.AsSpan();
|
|
||||||
data.CopyTo(dataSpan);
|
|
||||||
_size = data.Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FromArray(byte[] data)
|
|
||||||
{
|
|
||||||
Clear();
|
|
||||||
ResizeIfNeed(data.Length);
|
|
||||||
Buffer.BlockCopy(data, 0, _data, 0, _offset);
|
|
||||||
_size = data.Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public byte[] ToArray()
|
|
||||||
{
|
|
||||||
var bytes = new byte[_offset];
|
|
||||||
Buffer.BlockCopy(_data, 0, bytes, 0, _offset);
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ResizeIfNeed(int lenght)
|
|
||||||
{
|
|
||||||
if (_offset + lenght < _data.Length)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var newData = new byte[_data.Length * 4 + lenght];
|
|
||||||
Buffer.BlockCopy(_data, 0, newData, 0, _data.Length);
|
|
||||||
_data = newData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,8 +18,4 @@
|
|||||||
<OutputPath></OutputPath>
|
<OutputPath></OutputPath>
|
||||||
<DefineConstants>TRACE;NETSTACK_SPAN</DefineConstants>
|
<DefineConstants>TRACE;NETSTACK_SPAN</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="External" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ namespace Ragon.Common
|
|||||||
{
|
{
|
||||||
public enum RagonAuthority: byte
|
public enum RagonAuthority: byte
|
||||||
{
|
{
|
||||||
OWNER_ONLY,
|
None,
|
||||||
ALL,
|
OwnerOnly,
|
||||||
|
All,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,7 @@ namespace Ragon.Common
|
|||||||
AUTHORIZED_FAILED,
|
AUTHORIZED_FAILED,
|
||||||
|
|
||||||
JOIN_OR_CREATE_ROOM,
|
JOIN_OR_CREATE_ROOM,
|
||||||
|
CREATE_ROOM,
|
||||||
JOIN_ROOM,
|
JOIN_ROOM,
|
||||||
LEAVE_ROOM,
|
LEAVE_ROOM,
|
||||||
OWNERSHIP_CHANGED,
|
OWNERSHIP_CHANGED,
|
||||||
@@ -14,14 +15,13 @@ namespace Ragon.Common
|
|||||||
JOIN_FAILED,
|
JOIN_FAILED,
|
||||||
|
|
||||||
LOAD_SCENE,
|
LOAD_SCENE,
|
||||||
SCENE_IS_LOADED,
|
SCENE_LOADED,
|
||||||
|
|
||||||
PLAYER_JOINED,
|
PLAYER_JOINED,
|
||||||
PLAYER_LEAVED,
|
PLAYER_LEAVED,
|
||||||
|
|
||||||
CREATE_ENTITY,
|
CREATE_ENTITY,
|
||||||
DESTROY_ENTITY,
|
DESTROY_ENTITY,
|
||||||
|
|
||||||
SNAPSHOT,
|
SNAPSHOT,
|
||||||
|
|
||||||
REPLICATE_ENTITY_STATE,
|
REPLICATE_ENTITY_STATE,
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Ragon.Common
|
||||||
|
{
|
||||||
|
public enum RagonReplicationMode: byte
|
||||||
|
{
|
||||||
|
Local,
|
||||||
|
Server,
|
||||||
|
LocalAndServer,
|
||||||
|
Buffered,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
namespace Ragon.Common
|
||||||
|
{
|
||||||
|
public class RagonRoomParameters: IRagonSerializable
|
||||||
|
{
|
||||||
|
public string Map { get; set; }
|
||||||
|
public int Min { get; set; }
|
||||||
|
public int Max { get; set; }
|
||||||
|
|
||||||
|
public void Serialize(RagonSerializer buffer)
|
||||||
|
{
|
||||||
|
buffer.WriteString(Map);
|
||||||
|
buffer.WriteInt(Min);
|
||||||
|
buffer.WriteInt(Max);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Deserialize(RagonSerializer buffer)
|
||||||
|
{
|
||||||
|
Map = buffer.ReadString();
|
||||||
|
Min = buffer.ReadInt();
|
||||||
|
Max = buffer.ReadInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Ragon.Common
|
||||||
|
{
|
||||||
|
public interface IRagonSerializable
|
||||||
|
{
|
||||||
|
public void Serialize(RagonSerializer serializer);
|
||||||
|
public void Deserialize(RagonSerializer serializer);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,335 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Ragon.Common
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
|
internal struct ValueConverter
|
||||||
|
{
|
||||||
|
[FieldOffset(0)] public int Int;
|
||||||
|
[FieldOffset(0)] public float Float;
|
||||||
|
[FieldOffset(0)] public long Long;
|
||||||
|
[FieldOffset(0)] public byte Byte0;
|
||||||
|
[FieldOffset(1)] public byte Byte1;
|
||||||
|
[FieldOffset(2)] public byte Byte2;
|
||||||
|
[FieldOffset(3)] public byte Byte3;
|
||||||
|
[FieldOffset(4)] public byte Byte4;
|
||||||
|
[FieldOffset(5)] public byte Byte5;
|
||||||
|
[FieldOffset(6)] public byte Byte6;
|
||||||
|
[FieldOffset(7)] public byte Byte7;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RagonSerializer
|
||||||
|
{
|
||||||
|
private byte[] _data;
|
||||||
|
private int _offset;
|
||||||
|
private int _size;
|
||||||
|
|
||||||
|
public int Lenght => _offset;
|
||||||
|
public int Size => _size - _offset;
|
||||||
|
|
||||||
|
public RagonSerializer(int capacity = 256)
|
||||||
|
{
|
||||||
|
_data = new byte[capacity];
|
||||||
|
_offset = 0;
|
||||||
|
_size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_size = _offset;
|
||||||
|
_offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void AddOffset(int offset)
|
||||||
|
{
|
||||||
|
_offset += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int WriteByte(byte value)
|
||||||
|
{
|
||||||
|
ResizeIfNeed(1);
|
||||||
|
_data[_offset] = value;
|
||||||
|
_offset += 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public byte ReadByte()
|
||||||
|
{
|
||||||
|
var value = _data[_offset];
|
||||||
|
_offset += 1;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int WriteBool(bool value)
|
||||||
|
{
|
||||||
|
ResizeIfNeed(1);
|
||||||
|
_data[_offset] = value ? (byte) 1 : (byte) 0;
|
||||||
|
_offset += 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool ReadBool()
|
||||||
|
{
|
||||||
|
var value = _data[_offset];
|
||||||
|
_offset += 1;
|
||||||
|
return value == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int WriteInt(int value)
|
||||||
|
{
|
||||||
|
ResizeIfNeed(4);
|
||||||
|
var converter = new ValueConverter() {Int = value};
|
||||||
|
_data[_offset] = converter.Byte0;
|
||||||
|
_data[_offset + 1] = converter.Byte1;
|
||||||
|
_data[_offset + 2] = converter.Byte2;
|
||||||
|
_data[_offset + 3] = converter.Byte3;
|
||||||
|
_offset += 4;
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int WriteInt(int value, int offset)
|
||||||
|
{
|
||||||
|
ResizeIfNeed(4);
|
||||||
|
var converter = new ValueConverter() {Int = value};
|
||||||
|
_data[offset] = converter.Byte0;
|
||||||
|
_data[offset + 1] = converter.Byte1;
|
||||||
|
_data[offset + 2] = converter.Byte2;
|
||||||
|
_data[offset + 3] = converter.Byte3;
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int ReadInt()
|
||||||
|
{
|
||||||
|
var converter = new ValueConverter {Byte0 = _data[_offset], Byte1 = _data[_offset + 1], Byte2 = _data[_offset + 2], Byte3 = _data[_offset + 3]};
|
||||||
|
_offset += 4;
|
||||||
|
return converter.Int;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteLong(long value)
|
||||||
|
{
|
||||||
|
ResizeIfNeed(8);
|
||||||
|
WriteLong(value, _offset);
|
||||||
|
_offset += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int WriteLong(long value, int offset)
|
||||||
|
{
|
||||||
|
var converter = new ValueConverter() {Long = value};
|
||||||
|
_data[offset] = converter.Byte0;
|
||||||
|
_data[offset + 1] = converter.Byte1;
|
||||||
|
_data[offset + 2] = converter.Byte2;
|
||||||
|
_data[offset + 3] = converter.Byte3;
|
||||||
|
_data[offset + 4] = converter.Byte4;
|
||||||
|
_data[offset + 5] = converter.Byte5;
|
||||||
|
_data[offset + 6] = converter.Byte6;
|
||||||
|
_data[offset + 7] = converter.Byte7;
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public long ReadLong()
|
||||||
|
{
|
||||||
|
var converter = new ValueConverter
|
||||||
|
{
|
||||||
|
Byte0 = _data[_offset],
|
||||||
|
Byte1 = _data[_offset + 1],
|
||||||
|
Byte2 = _data[_offset + 2],
|
||||||
|
Byte3 = _data[_offset + 3],
|
||||||
|
Byte4 = _data[_offset + 4],
|
||||||
|
Byte5 = _data[_offset + 5],
|
||||||
|
Byte6 = _data[_offset + 6],
|
||||||
|
Byte7 = _data[_offset + 7],
|
||||||
|
};
|
||||||
|
_offset += 8;
|
||||||
|
return converter.Long;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int WriteFloat(float value)
|
||||||
|
{
|
||||||
|
var converter = new ValueConverter() {Float = value};
|
||||||
|
WriteInt(converter.Int);
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public float ReadFloat()
|
||||||
|
{
|
||||||
|
var rawValue = ReadInt();
|
||||||
|
var converter = new ValueConverter() {Int = rawValue};
|
||||||
|
var value = converter.Float;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int WriteString(string value)
|
||||||
|
{
|
||||||
|
var rawData = Encoding.UTF8.GetBytes(value).AsSpan();
|
||||||
|
ResizeIfNeed(2 + rawData.Length);
|
||||||
|
WriteUShort((ushort) rawData.Length);
|
||||||
|
var data = _data.AsSpan().Slice(_offset, rawData.Length);
|
||||||
|
rawData.CopyTo(data);
|
||||||
|
_offset += rawData.Length;
|
||||||
|
|
||||||
|
return rawData.Length + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public string ReadString()
|
||||||
|
{
|
||||||
|
var lenght = ReadUShort();
|
||||||
|
var stringRaw = _data.AsSpan().Slice(_offset, lenght);
|
||||||
|
var str = Encoding.UTF8.GetString(stringRaw);
|
||||||
|
_offset += lenght;
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ReadOnlySpan<byte> ReadData(int lenght)
|
||||||
|
{
|
||||||
|
var data = _data.AsSpan();
|
||||||
|
var payloadData = data.Slice(_offset, lenght);
|
||||||
|
|
||||||
|
_offset += payloadData.Length;
|
||||||
|
return payloadData;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int WriteData(ref ReadOnlySpan<byte> payload)
|
||||||
|
{
|
||||||
|
ResizeIfNeed(payload.Length);
|
||||||
|
|
||||||
|
var data = _data.AsSpan();
|
||||||
|
var payloadData = data.Slice(_offset, payload.Length);
|
||||||
|
|
||||||
|
payload.CopyTo(payloadData);
|
||||||
|
_offset += payload.Length;
|
||||||
|
return payload.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public Span<byte> GetWritableData(int lenght)
|
||||||
|
{
|
||||||
|
ResizeIfNeed(lenght);
|
||||||
|
|
||||||
|
var data = _data.AsSpan();
|
||||||
|
var payloadData = data.Slice(_offset, lenght);
|
||||||
|
|
||||||
|
_offset += lenght;
|
||||||
|
return payloadData;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int WriteOperation(RagonOperation ragonOperation)
|
||||||
|
{
|
||||||
|
ResizeIfNeed(1);
|
||||||
|
|
||||||
|
_data[_offset] = (byte) ragonOperation;
|
||||||
|
_offset += 1;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public RagonOperation ReadOperation()
|
||||||
|
{
|
||||||
|
var op = (RagonOperation) _data[_offset];
|
||||||
|
_offset += 1;
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int WriteUShort(ushort value)
|
||||||
|
{
|
||||||
|
ResizeIfNeed(2);
|
||||||
|
|
||||||
|
_data[_offset] = (byte) (value & 0x00FF);
|
||||||
|
_data[_offset + 1] = (byte) ((value & 0xFF00) >> 8);
|
||||||
|
_offset += 2;
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int WriteUShort(ushort value, int offset)
|
||||||
|
{
|
||||||
|
ResizeIfNeed(2);
|
||||||
|
_data[offset] = (byte) (value & 0x00FF);
|
||||||
|
_data[offset + 1] = (byte) ((value & 0xFF00) >> 8);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ushort ReadUShort()
|
||||||
|
{
|
||||||
|
var value = (ushort) (_data[_offset] + (_data[_offset + 1] << 8));
|
||||||
|
_offset += 2;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_offset = 0;
|
||||||
|
_size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ToSpan(ref Span<byte> data)
|
||||||
|
{
|
||||||
|
var span = _data.AsSpan();
|
||||||
|
var dataSpan = span.Slice(0, _offset);
|
||||||
|
dataSpan.CopyTo(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FromSpan(ref ReadOnlySpan<byte> data)
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
ResizeIfNeed(data.Length);
|
||||||
|
var dataSpan = _data.AsSpan();
|
||||||
|
data.CopyTo(dataSpan);
|
||||||
|
_size = data.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FromArray(byte[] data)
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
ResizeIfNeed(data.Length);
|
||||||
|
Buffer.BlockCopy(data, 0, _data, 0, data.Length);
|
||||||
|
_size = data.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public byte[] ToArray()
|
||||||
|
{
|
||||||
|
var bytes = new byte[_offset];
|
||||||
|
Buffer.BlockCopy(_data, 0, bytes, 0, _offset);
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResizeIfNeed(int lenght)
|
||||||
|
{
|
||||||
|
if (_offset + lenght < _data.Length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var newData = new byte[_data.Length * 4 + lenght];
|
||||||
|
Buffer.BlockCopy(_data, 0, newData, 0, _data.Length);
|
||||||
|
_data = newData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Ragon.Common
|
||||||
|
{
|
||||||
|
public enum RagonTarget: byte
|
||||||
|
{
|
||||||
|
Owner,
|
||||||
|
ExceptOwner,
|
||||||
|
All,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ public class AuthorizationProviderByKey: IAuthorizationProvider
|
|||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnAuthorizationRequest(string key, string name, byte protocol, byte[] additionalData, Action<string, string> accept, Action<uint> reject)
|
public async Task OnAuthorizationRequest(string key, string name, byte[] additionalData, Action<string, string> accept, Action<uint> reject)
|
||||||
{
|
{
|
||||||
if (key == _configuration.Key)
|
if (key == _configuration.Key)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
using Ragon.Core;
|
using NLog.Fluent;
|
||||||
|
using Ragon.Core;
|
||||||
|
|
||||||
namespace Game.Source
|
namespace Game.Source
|
||||||
{
|
{
|
||||||
public class SimplePlugin: PluginBase
|
public class SimplePlugin: PluginBase
|
||||||
{
|
{
|
||||||
|
|
||||||
public override void OnStart()
|
public override void OnStart()
|
||||||
{
|
{
|
||||||
// _logger.Info("Plugin started");
|
// _logger.Info("Plugin started");
|
||||||
@@ -16,12 +18,22 @@ namespace Game.Source
|
|||||||
|
|
||||||
public override void OnPlayerJoined(Player player)
|
public override void OnPlayerJoined(Player player)
|
||||||
{
|
{
|
||||||
// _logger.Info($"Player({player.PlayerName}) joined to Room({GameRoom.Id})");
|
// Logger.Info($"Player({player.PlayerName}) joined to Room({Room.Id})");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnPlayerLeaved(Player player)
|
public override void OnPlayerLeaved(Player player)
|
||||||
{
|
{
|
||||||
// _logger.Info($"Player({player.PlayerName}) left from Room({GameRoom.Id})");
|
// Logger.Info($"Player({player.PlayerName}) left from Room({Room.Id})");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnEntityCreated(Player player, Entity entity)
|
||||||
|
{
|
||||||
|
// Logger.Info($"Player({player.PlayerName}) create entity {entity.EntityId}:{entity.EntityType}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnEntityDestroyed(Player player, Entity entity)
|
||||||
|
{
|
||||||
|
// Logger.Info($"Player({player.PlayerName}) destroy entity {entity.EntityId}:{entity.EntityType}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
{
|
{
|
||||||
"key": "defaultkey",
|
"key": "defaultkey",
|
||||||
|
"protocol": "1.0.0",
|
||||||
"statisticsInterval": 5,
|
"statisticsInterval": 5,
|
||||||
"sendRate": 30,
|
"sendRate": 30,
|
||||||
"port": 4444,
|
"port": 4444,
|
||||||
"skipTimeout": 60,
|
"skipTimeout": 60,
|
||||||
|
"reconnectTimeout": 300,
|
||||||
"maxConnections": 4095,
|
"maxConnections": 4095,
|
||||||
"maxPlayersPerRoom": 20,
|
"maxPlayersPerRoom": 20,
|
||||||
"maxRooms": 200
|
"maxRooms": 200
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ namespace Stress
|
|||||||
simulationClient.InRoom = true;
|
simulationClient.InRoom = true;
|
||||||
|
|
||||||
ragonSerializer.Clear();
|
ragonSerializer.Clear();
|
||||||
ragonSerializer.WriteOperation(RagonOperation.SCENE_IS_LOADED);
|
ragonSerializer.WriteOperation(RagonOperation.SCENE_LOADED);
|
||||||
|
|
||||||
var sendData = ragonSerializer.ToArray();
|
var sendData = ragonSerializer.ToArray();
|
||||||
var packet = new Packet();
|
var packet = new Packet();
|
||||||
@@ -195,7 +195,7 @@ namespace Stress
|
|||||||
for (var i = 0; i < 80; i ++)
|
for (var i = 0; i < 80; i ++)
|
||||||
{
|
{
|
||||||
var thread = new SimulationThread();
|
var thread = new SimulationThread();
|
||||||
thread.Start("127.0.0.1", 4444, 50);
|
thread.Start("49.12.70.233", 4444, 50);
|
||||||
Thread.Sleep(300);
|
Thread.Sleep(300);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
using System;
|
using ENet;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using ENet;
|
|
||||||
using NLog;
|
using NLog;
|
||||||
|
|
||||||
namespace Ragon.Core
|
namespace Ragon.Core
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using NLog;
|
||||||
using NLog.Targets;
|
|
||||||
using Ragon.Common;
|
using Ragon.Common;
|
||||||
|
|
||||||
namespace Ragon.Core;
|
namespace Ragon.Core;
|
||||||
|
|
||||||
public class AuthorizationManager : IAuthorizationManager
|
public class AuthorizationManager : IAuthorizationManager
|
||||||
{
|
{
|
||||||
|
private Logger _logger = LogManager.GetCurrentClassLogger();
|
||||||
private IAuthorizationProvider _provider;
|
private IAuthorizationProvider _provider;
|
||||||
private IGameThread _gameThread;
|
private IGameThread _gameThread;
|
||||||
private Lobby _lobby;
|
private Lobby _lobby;
|
||||||
private RagonSerializer _serializer;
|
private RagonSerializer _serializer;
|
||||||
private readonly Dictionary<uint, Player> _playersByPeers;
|
private readonly Dictionary<uint, Player> _playersByPeers;
|
||||||
private readonly Dictionary<string, Player> _playersByIds;
|
private readonly Dictionary<string, Player> _playersByIds;
|
||||||
|
|
||||||
public AuthorizationManager(IAuthorizationProvider provider, IGameThread gameThread, Lobby lobby, RagonSerializer serializer)
|
public AuthorizationManager(IAuthorizationProvider provider, IGameThread gameThread, Lobby lobby, RagonSerializer serializer)
|
||||||
{
|
{
|
||||||
_serializer = serializer;
|
_serializer = serializer;
|
||||||
@@ -25,18 +25,19 @@ public class AuthorizationManager : IAuthorizationManager
|
|||||||
_playersByPeers = new Dictionary<uint, Player>();
|
_playersByPeers = new Dictionary<uint, Player>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnAuthorization(uint peerId, string key, string name, byte protocol)
|
public void OnAuthorization(uint peerId, string key, string name, ReadOnlySpan<byte> additionalData)
|
||||||
{
|
{
|
||||||
|
if (_playersByPeers.ContainsKey(peerId))
|
||||||
|
{
|
||||||
|
_logger.Warn($"Connection already authorized {peerId}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var dispatcher = _gameThread.ThreadDispatcher;
|
var dispatcher = _gameThread.ThreadDispatcher;
|
||||||
_provider.OnAuthorizationRequest(key, name, protocol, Array.Empty<byte>(),
|
|
||||||
(playerId, playerName) =>
|
_provider.OnAuthorizationRequest(key, name, additionalData.ToArray(),
|
||||||
{
|
(playerId, playerName) => { dispatcher.Dispatch(() => Accepted(peerId, playerId, playerName)); },
|
||||||
dispatcher.Dispatch(() => Accepted(peerId, playerId, playerName));
|
(errorCode) => { dispatcher.Dispatch(() => Rejected(peerId, errorCode)); });
|
||||||
},
|
|
||||||
(errorCode) =>
|
|
||||||
{
|
|
||||||
dispatcher.Dispatch(() => Rejected(peerId, errorCode));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Accepted(uint peerId, string playerId, string playerName)
|
public void Accepted(uint peerId, string playerId, string playerName)
|
||||||
@@ -45,7 +46,7 @@ public class AuthorizationManager : IAuthorizationManager
|
|||||||
_serializer.WriteOperation(RagonOperation.AUTHORIZED_SUCCESS);
|
_serializer.WriteOperation(RagonOperation.AUTHORIZED_SUCCESS);
|
||||||
_serializer.WriteString(playerId);
|
_serializer.WriteString(playerId);
|
||||||
_serializer.WriteString(playerName);
|
_serializer.WriteString(playerName);
|
||||||
|
|
||||||
var player = new Player()
|
var player = new Player()
|
||||||
{
|
{
|
||||||
Id = playerId,
|
Id = playerId,
|
||||||
@@ -53,12 +54,12 @@ public class AuthorizationManager : IAuthorizationManager
|
|||||||
PeerId = peerId,
|
PeerId = peerId,
|
||||||
IsLoaded = false,
|
IsLoaded = false,
|
||||||
Entities = new List<Entity>(),
|
Entities = new List<Entity>(),
|
||||||
EntitiesIds = new List<int>(),
|
EntitiesIds = new List<ushort>(),
|
||||||
};
|
};
|
||||||
|
|
||||||
_playersByIds.Add(playerId, player);
|
_playersByIds.Add(playerId, player);
|
||||||
_playersByPeers.Add(peerId, player);
|
_playersByPeers.Add(peerId, player);
|
||||||
|
|
||||||
var sendData = _serializer.ToArray();
|
var sendData = _serializer.ToArray();
|
||||||
_gameThread.Server.Send(peerId, sendData, DeliveryType.Reliable);
|
_gameThread.Server.Send(peerId, sendData, DeliveryType.Reliable);
|
||||||
}
|
}
|
||||||
@@ -84,10 +85,10 @@ public class AuthorizationManager : IAuthorizationManager
|
|||||||
{
|
{
|
||||||
if (_playersByPeers.TryGetValue(peerId, out var player))
|
if (_playersByPeers.TryGetValue(peerId, out var player))
|
||||||
return player;
|
return player;
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Player GetPlayer(string playerId)
|
public Player GetPlayer(string playerId)
|
||||||
{
|
{
|
||||||
return _playersByIds[playerId];
|
return _playersByIds[playerId];
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ namespace Ragon.Core;
|
|||||||
|
|
||||||
public interface IAuthorizationProvider
|
public interface IAuthorizationProvider
|
||||||
{
|
{
|
||||||
Task OnAuthorizationRequest(string key, string playerName, byte protocol, byte[] additionalData, Action<string, string> Accept, Action<uint> Reject);
|
Task OnAuthorizationRequest(string key, string playerName, byte[] additionalData, Action<string, string> Accept, Action<uint> Reject);
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
|
||||||
@@ -16,5 +17,6 @@ namespace Ragon.Core
|
|||||||
var app = new Application(factory, configuration);
|
var app = new Application(factory, configuration);
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,10 +6,12 @@ namespace Ragon.Core
|
|||||||
public struct Configuration
|
public struct Configuration
|
||||||
{
|
{
|
||||||
public string Key;
|
public string Key;
|
||||||
|
public string Protocol;
|
||||||
public int StatisticsInterval;
|
public int StatisticsInterval;
|
||||||
public ushort SendRate;
|
public ushort SendRate;
|
||||||
public ushort Port;
|
public ushort Port;
|
||||||
public int SkipTimeout;
|
public int SkipTimeout;
|
||||||
|
public int ReconnectTimeout;
|
||||||
public int MaxConnections;
|
public int MaxConnections;
|
||||||
public int MaxPlayersPerRoom;
|
public int MaxPlayersPerRoom;
|
||||||
public int MaxRooms;
|
public int MaxRooms;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace Ragon.Core
|
|||||||
public static class ConfigurationLoader
|
public static class ConfigurationLoader
|
||||||
{
|
{
|
||||||
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||||
private static readonly string _serverVersion = "1.0.6-rc";
|
private static readonly string _serverVersion = "1.0.21-rc";
|
||||||
|
|
||||||
private static void CopyrightInfo()
|
private static void CopyrightInfo()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,24 +1,75 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Ragon.Common;
|
using Ragon.Common;
|
||||||
|
|
||||||
namespace Ragon.Core;
|
namespace Ragon.Core;
|
||||||
|
|
||||||
public class Entity
|
public class Entity
|
||||||
{
|
{
|
||||||
private static int _idGenerator = 0;
|
private static ushort _idGenerator = 0;
|
||||||
public int EntityId { get; private set; }
|
public ushort EntityId { get; private set; }
|
||||||
public uint OwnerId { get; private set; }
|
public ushort StaticId { get; private set; }
|
||||||
public ushort EntityType { get; private set; }
|
public ushort EntityType { get; private set; }
|
||||||
|
public ushort OwnerId { get; private set; }
|
||||||
public RagonAuthority Authority { get; private set; }
|
public RagonAuthority Authority { get; private set; }
|
||||||
public EntityState State { get; private set; }
|
public EntityProperty[] Properties { get; private set; }
|
||||||
public EntityState Payload { get; private set; }
|
public List<EntityEvent> BufferedEvents = new List<EntityEvent>();
|
||||||
|
|
||||||
|
public byte[] Payload { get; set; }
|
||||||
|
|
||||||
public Entity(uint ownerId, ushort entityType, RagonAuthority stateAuthority, RagonAuthority eventAuthority)
|
public Entity(ushort ownerId, ushort entityType, ushort staticId, RagonAuthority eventAuthority, int props)
|
||||||
{
|
{
|
||||||
OwnerId = ownerId;
|
OwnerId = ownerId;
|
||||||
|
StaticId = staticId;
|
||||||
EntityType = entityType;
|
EntityType = entityType;
|
||||||
EntityId = _idGenerator++;
|
EntityId = _idGenerator++;
|
||||||
State = new EntityState(stateAuthority);
|
Properties = new EntityProperty[props];
|
||||||
Payload = new EntityState(stateAuthority);
|
Payload = Array.Empty<byte>();
|
||||||
Authority = eventAuthority;
|
Authority = eventAuthority;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdateOwner(ushort ownerId) => OwnerId = ownerId;
|
||||||
|
|
||||||
|
public void ReplicateProperties(RagonSerializer serializer)
|
||||||
|
{
|
||||||
|
serializer.WriteUShort(EntityId);
|
||||||
|
|
||||||
|
for (int propertyIndex = 0; propertyIndex < Properties.Length; propertyIndex++)
|
||||||
|
{
|
||||||
|
var property = Properties[propertyIndex];
|
||||||
|
if (property.IsDirty)
|
||||||
|
{
|
||||||
|
serializer.WriteBool(true);
|
||||||
|
var span = serializer.GetWritableData(property.Size);
|
||||||
|
var data = property.Read();
|
||||||
|
data.CopyTo(span);
|
||||||
|
property.Clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
serializer.WriteBool(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Snapshot(RagonSerializer serializer)
|
||||||
|
{
|
||||||
|
for (int propertyIndex = 0; propertyIndex < Properties.Length; propertyIndex++)
|
||||||
|
{
|
||||||
|
var property = Properties[propertyIndex];
|
||||||
|
var hasPayload = property.IsFixed || property.Size > 0 && !property.IsFixed;
|
||||||
|
if (hasPayload)
|
||||||
|
{
|
||||||
|
serializer.WriteBool(true);
|
||||||
|
var span = serializer.GetWritableData(property.Size);
|
||||||
|
var data = property.Read();
|
||||||
|
data.CopyTo(span);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
serializer.WriteBool(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using Ragon.Common;
|
||||||
|
|
||||||
|
namespace Ragon.Core;
|
||||||
|
|
||||||
|
public class EntityEvent
|
||||||
|
{
|
||||||
|
public ushort PeerId { get; set; }
|
||||||
|
public ushort EventId { get; set; }
|
||||||
|
public byte[] EventData { get; set; }
|
||||||
|
public RagonTarget Target { set; get; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using Ragon.Common;
|
||||||
|
|
||||||
|
namespace Ragon.Core;
|
||||||
|
|
||||||
|
public class EntityProperty
|
||||||
|
{
|
||||||
|
public int Size { get; set; }
|
||||||
|
public int Capacity { get; set; }
|
||||||
|
public bool IsDirty { get; private set; }
|
||||||
|
public bool IsFixed { get; private set; }
|
||||||
|
private byte[] _data;
|
||||||
|
|
||||||
|
public EntityProperty(int size, bool isFixed)
|
||||||
|
{
|
||||||
|
Capacity = 512;
|
||||||
|
Size = size;
|
||||||
|
IsFixed = isFixed;
|
||||||
|
IsDirty = true;
|
||||||
|
|
||||||
|
_data = new byte[Capacity];
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlySpan<byte> Read()
|
||||||
|
{
|
||||||
|
var dataSpan = _data.AsSpan();
|
||||||
|
|
||||||
|
return dataSpan.Slice(0, Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(ref ReadOnlySpan<byte> src)
|
||||||
|
{
|
||||||
|
src.CopyTo(_data);
|
||||||
|
IsDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
IsDirty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+374
-183
@@ -1,17 +1,20 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using Ragon.Common;
|
using Ragon.Common;
|
||||||
|
|
||||||
namespace Ragon.Core
|
namespace Ragon.Core
|
||||||
{
|
{
|
||||||
|
// TODO: Replace all serialization and packing into dedicated structures
|
||||||
|
// TODO: Split class on different managers
|
||||||
public class GameRoom : IGameRoom
|
public class GameRoom : IGameRoom
|
||||||
{
|
{
|
||||||
public int PlayersMin { get; private set; }
|
public int PlayersMin { get; private set; }
|
||||||
public int PlayersMax { get; private set; }
|
public int PlayersMax { get; private set; }
|
||||||
public int PlayersCount => _players.Count;
|
public int PlayersCount => _players.Count;
|
||||||
|
public int EntitiesCount => _entities.Count;
|
||||||
public string Id { get; private set; }
|
public string Id { get; private set; }
|
||||||
public string Map { get; private set; }
|
public string Map { get; private set; }
|
||||||
|
|
||||||
@@ -20,80 +23,62 @@ namespace Ragon.Core
|
|||||||
private Dictionary<int, Entity> _entities = new();
|
private Dictionary<int, Entity> _entities = new();
|
||||||
private uint _owner;
|
private uint _owner;
|
||||||
|
|
||||||
private readonly PluginBase _plugin;
|
private readonly IScheduler _scheduler;
|
||||||
private readonly IGameThread _gameThread;
|
private readonly IGameThread _gameThread;
|
||||||
|
private readonly PluginBase _plugin;
|
||||||
private readonly RagonSerializer _serializer = new(512);
|
private readonly RagonSerializer _serializer = new(512);
|
||||||
|
|
||||||
// Cache
|
// Cache
|
||||||
private uint[] _readyPlayers = Array.Empty<uint>();
|
private uint[] _readyPlayers = Array.Empty<uint>();
|
||||||
private uint[] _allPlayers = Array.Empty<uint>();
|
private uint[] _allPlayers = Array.Empty<uint>();
|
||||||
private Entity[] _entitiesAll = Array.Empty<Entity>();
|
private Entity[] _entitiesAll = Array.Empty<Entity>();
|
||||||
|
private HashSet<Entity> _entitiesDirtySet = new HashSet<Entity>();
|
||||||
public GameRoom(IGameThread gameThread, PluginBase pluginBase, string map, int min, int max)
|
private List<Entity> _entitiesDirty = new List<Entity>();
|
||||||
|
private List<uint> _peersCache = new List<uint>();
|
||||||
|
private List<uint> _awaitingPeers = new List<uint>();
|
||||||
|
|
||||||
|
public GameRoom(IGameThread gameThread, PluginBase pluginBase, string roomId, string map, int min, int max)
|
||||||
{
|
{
|
||||||
_gameThread = gameThread;
|
_gameThread = gameThread;
|
||||||
_plugin = pluginBase;
|
_plugin = pluginBase;
|
||||||
|
_scheduler = new Scheduler();
|
||||||
|
|
||||||
Map = map;
|
Map = map;
|
||||||
PlayersMin = min;
|
PlayersMin = min;
|
||||||
PlayersMax = max;
|
PlayersMax = max;
|
||||||
Id = Guid.NewGuid().ToString();
|
Id = roomId;
|
||||||
|
|
||||||
_plugin.Attach(this);
|
_plugin.Attach(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Joined(Player player, ReadOnlySpan<byte> payload)
|
public void AddPlayer(Player player, ReadOnlySpan<byte> payload)
|
||||||
{
|
{
|
||||||
if (_players.Count == 0)
|
if (_players.Count == 0)
|
||||||
{
|
{
|
||||||
_owner = player.PeerId;
|
_owner = player.PeerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
_serializer.Clear();
|
|
||||||
_serializer.WriteOperation(RagonOperation.PLAYER_JOINED);
|
|
||||||
_serializer.WriteUShort((ushort) player.PeerId);
|
|
||||||
_serializer.WriteString(player.Id);
|
|
||||||
_serializer.WriteString(player.PlayerName);
|
|
||||||
|
|
||||||
var sendData = _serializer.ToArray();
|
|
||||||
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
|
|
||||||
}
|
|
||||||
|
|
||||||
_players.Add(player.PeerId, player);
|
_players.Add(player.PeerId, player);
|
||||||
_allPlayers = _players.Select(p => p.Key).ToArray();
|
_allPlayers = _players.Select(p => p.Key).ToArray();
|
||||||
|
|
||||||
{
|
|
||||||
_serializer.Clear();
|
|
||||||
_serializer.WriteOperation(RagonOperation.JOIN_SUCCESS);
|
|
||||||
_serializer.WriteString(Id);
|
|
||||||
_serializer.WriteString(player.Id);
|
|
||||||
_serializer.WriteString(GetOwner().Id);
|
|
||||||
_serializer.WriteUShort((ushort) PlayersMin);
|
|
||||||
_serializer.WriteUShort((ushort) PlayersMax);
|
|
||||||
|
|
||||||
var sendData = _serializer.ToArray();
|
_serializer.Clear();
|
||||||
Send(player.PeerId, sendData, DeliveryType.Reliable);
|
_serializer.WriteOperation(RagonOperation.JOIN_SUCCESS);
|
||||||
}
|
_serializer.WriteString(Id);
|
||||||
|
_serializer.WriteString(player.Id);
|
||||||
{
|
_serializer.WriteString(GetOwner().Id);
|
||||||
_serializer.Clear();
|
_serializer.WriteUShort((ushort) PlayersMin);
|
||||||
_serializer.WriteOperation(RagonOperation.LOAD_SCENE);
|
_serializer.WriteUShort((ushort) PlayersMax);
|
||||||
_serializer.WriteString(Map);
|
_serializer.WriteString(Map);
|
||||||
|
var sendData = _serializer.ToArray();
|
||||||
var sendData = _serializer.ToArray();
|
Send(player.PeerId, sendData, DeliveryType.Reliable);
|
||||||
Send(player.PeerId, sendData, DeliveryType.Reliable);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Leave(uint peerId)
|
public void RemovePlayer(uint peerId)
|
||||||
{
|
{
|
||||||
if (_players.Remove(peerId, out var player))
|
if (_players.Remove(peerId, out var player))
|
||||||
{
|
{
|
||||||
_allPlayers = _players.Select(p => p.Key).ToArray();
|
_allPlayers = _players.Select(p => p.Key).ToArray();
|
||||||
_readyPlayers = _players.Where(p => p.Value.IsLoaded).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);
|
_plugin.OnPlayerLeaved(player);
|
||||||
@@ -102,130 +87,299 @@ namespace Ragon.Core
|
|||||||
_serializer.WriteOperation(RagonOperation.PLAYER_LEAVED);
|
_serializer.WriteOperation(RagonOperation.PLAYER_LEAVED);
|
||||||
_serializer.WriteString(player.Id);
|
_serializer.WriteString(player.Id);
|
||||||
|
|
||||||
_serializer.WriteUShort((ushort) player.EntitiesIds.Count);
|
var entitiesToDelete = player.Entities.Where(e => e.StaticId == 0).ToArray();
|
||||||
foreach (var entityId in player.EntitiesIds)
|
_serializer.WriteUShort((ushort) entitiesToDelete.Length);
|
||||||
|
foreach (var entity in entitiesToDelete)
|
||||||
{
|
{
|
||||||
_serializer.WriteInt(entityId);
|
_serializer.WriteUShort(entity.EntityId);
|
||||||
_entities.Remove(entityId);
|
_entities.Remove(entity.EntityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
var sendData = _serializer.ToArray();
|
var sendData = _serializer.ToArray();
|
||||||
Broadcast(_readyPlayers, sendData);
|
Broadcast(_readyPlayers, sendData);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_allPlayers.Length > 0 && isOwnershipChange)
|
if (_allPlayers.Length > 0 && player.PeerId == _owner)
|
||||||
{
|
{
|
||||||
var newRoomOwnerId = _allPlayers[0];
|
var nextOwnerId = _allPlayers[0];
|
||||||
var newRoomOwner = _players[newRoomOwnerId];
|
_owner = nextOwnerId;
|
||||||
|
var nextOwner = _players[nextOwnerId];
|
||||||
_owner = newRoomOwnerId;
|
|
||||||
|
var entitiesToUpdate = player.Entities.Where(e => e.StaticId > 0).ToArray();
|
||||||
|
|
||||||
|
_serializer.Clear();
|
||||||
|
_serializer.WriteOperation(RagonOperation.OWNERSHIP_CHANGED);
|
||||||
|
_serializer.WriteString(nextOwner.Id);
|
||||||
|
_serializer.WriteUShort((ushort) entitiesToUpdate.Length);
|
||||||
|
foreach (var entity in entitiesToUpdate)
|
||||||
{
|
{
|
||||||
_plugin.OnOwnershipChanged(newRoomOwner);
|
_serializer.WriteUShort(entity.EntityId);
|
||||||
|
entity.UpdateOwner((ushort) nextOwnerId);
|
||||||
_serializer.Clear();
|
|
||||||
_serializer.WriteOperation(RagonOperation.OWNERSHIP_CHANGED);
|
|
||||||
_serializer.WriteString(newRoomOwner.Id);
|
|
||||||
|
|
||||||
var sendData = _serializer.ToArray();
|
|
||||||
Broadcast(_readyPlayers, sendData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sendData = _serializer.ToArray();
|
||||||
|
Broadcast(_readyPlayers, sendData);
|
||||||
}
|
}
|
||||||
|
|
||||||
_entitiesAll = _entities.Values.ToArray();
|
_entitiesAll = _entities.Values.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ProcessEvent(uint peerId, ReadOnlySpan<byte> rawData)
|
// TODO: Move this processing to specialized classes with structures
|
||||||
|
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.Clear();
|
||||||
_serializer.FromSpan(ref payloadRawData);
|
_serializer.FromSpan(ref payloadRawData);
|
||||||
|
|
||||||
switch (operation)
|
switch (operation)
|
||||||
{
|
{
|
||||||
|
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.SCENE_LOADED:
|
||||||
|
{
|
||||||
|
var player = _players[peerId];
|
||||||
|
if (peerId == _owner)
|
||||||
|
{
|
||||||
|
var statics = _serializer.ReadUShort();
|
||||||
|
for (var staticIndex = 0; staticIndex < statics; staticIndex++)
|
||||||
|
{
|
||||||
|
var entityType = _serializer.ReadUShort();
|
||||||
|
var entityAuthority = (RagonAuthority) _serializer.ReadByte();
|
||||||
|
var staticId = _serializer.ReadUShort();
|
||||||
|
|
||||||
|
var propertiesCount = _serializer.ReadUShort();
|
||||||
|
var entity = new Entity(peerId, entityType, staticId, entityAuthority, propertiesCount);
|
||||||
|
for (var propertyIndex = 0; propertyIndex < propertiesCount; propertyIndex++)
|
||||||
|
{
|
||||||
|
var propertyType = _serializer.ReadBool();
|
||||||
|
var propertySize = _serializer.ReadUShort();
|
||||||
|
entity.Properties[propertyIndex] = new EntityProperty(propertySize, propertyType);
|
||||||
|
}
|
||||||
|
|
||||||
|
player.Entities.Add(entity);
|
||||||
|
player.EntitiesIds.Add(entity.EntityId);
|
||||||
|
|
||||||
|
_entities.Add(entity.EntityId, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
_entitiesAll = _entities.Values.ToArray();
|
||||||
|
_logger.Trace($"Scene entities: {statics}");
|
||||||
|
|
||||||
|
_awaitingPeers.Add(peerId);
|
||||||
|
|
||||||
|
foreach (var peer in _awaitingPeers)
|
||||||
|
{
|
||||||
|
var joinedPlayer = _players[peer];
|
||||||
|
joinedPlayer.IsLoaded = true;
|
||||||
|
_plugin.OnPlayerJoined(joinedPlayer);
|
||||||
|
_logger.Trace($"[{_owner}][{peer}] Player {joinedPlayer.Id} restored");
|
||||||
|
{
|
||||||
|
_serializer.Clear();
|
||||||
|
_serializer.WriteOperation(RagonOperation.PLAYER_JOINED);
|
||||||
|
_serializer.WriteUShort((ushort) player.PeerId);
|
||||||
|
_serializer.WriteString(player.Id);
|
||||||
|
_serializer.WriteString(player.PlayerName);
|
||||||
|
|
||||||
|
var sendData = _serializer.ToArray();
|
||||||
|
var readyPlayersWithExcludedPeer = _readyPlayers.Where(p => p != peerId).ToArray();
|
||||||
|
Broadcast(readyPlayersWithExcludedPeer, sendData, DeliveryType.Reliable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray();
|
||||||
|
foreach (var peer in _awaitingPeers)
|
||||||
|
{
|
||||||
|
ReplicateSnapshot(peer);
|
||||||
|
}
|
||||||
|
|
||||||
|
_awaitingPeers.Clear();
|
||||||
|
}
|
||||||
|
else if (GetOwner().IsLoaded)
|
||||||
|
{
|
||||||
|
_logger.Trace($"[{_owner}][{peerId}] Player {player.Id} restored instantly");
|
||||||
|
player.IsLoaded = true;
|
||||||
|
|
||||||
|
{
|
||||||
|
_serializer.Clear();
|
||||||
|
_serializer.WriteOperation(RagonOperation.PLAYER_JOINED);
|
||||||
|
_serializer.WriteUShort((ushort) player.PeerId);
|
||||||
|
_serializer.WriteString(player.Id);
|
||||||
|
_serializer.WriteString(player.PlayerName);
|
||||||
|
|
||||||
|
var sendData = _serializer.ToArray();
|
||||||
|
var readyPlayersWithExcludedPeer = _readyPlayers.Where(p => p != peerId).ToArray();
|
||||||
|
Broadcast(readyPlayersWithExcludedPeer, sendData, DeliveryType.Reliable);
|
||||||
|
}
|
||||||
|
|
||||||
|
_readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray();
|
||||||
|
_plugin.OnPlayerJoined(player);
|
||||||
|
|
||||||
|
ReplicateSnapshot(peerId);
|
||||||
|
|
||||||
|
foreach (var (key, value) in _entities)
|
||||||
|
{
|
||||||
|
foreach (var bufferedEvent in value.BufferedEvents)
|
||||||
|
{
|
||||||
|
_serializer.Clear();
|
||||||
|
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
|
||||||
|
_serializer.WriteUShort(bufferedEvent.EventId);
|
||||||
|
_serializer.WriteUShort(bufferedEvent.PeerId);
|
||||||
|
_serializer.WriteByte((byte) RagonReplicationMode.Server);
|
||||||
|
_serializer.WriteUShort(value.EntityId);
|
||||||
|
|
||||||
|
ReadOnlySpan<byte> data = bufferedEvent.EventData.AsSpan();
|
||||||
|
_serializer.WriteData(ref data);
|
||||||
|
|
||||||
|
_logger.Trace($"[{peerId}] Restored buffered event {bufferedEvent.EventId}");
|
||||||
|
var sendData = _serializer.ToArray();
|
||||||
|
Send(peerId, sendData, DeliveryType.Reliable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Trace($"[{_owner}][{peerId}] Player {player.Id} waiting");
|
||||||
|
_awaitingPeers.Add(peerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
case RagonOperation.REPLICATE_ENTITY_STATE:
|
case RagonOperation.REPLICATE_ENTITY_STATE:
|
||||||
{
|
{
|
||||||
var entityId = _serializer.ReadInt();
|
var entitiesCount = _serializer.ReadUShort();
|
||||||
if (_entities.TryGetValue(entityId, out var ent))
|
for (var entityIndex = 0; entityIndex < entitiesCount; entityIndex++)
|
||||||
{
|
{
|
||||||
if (ent.State.Authority == RagonAuthority.OWNER_ONLY && ent.OwnerId != peerId)
|
var entityId = _serializer.ReadUShort();
|
||||||
return;
|
if (_entities.TryGetValue(entityId, out var ent))
|
||||||
|
{
|
||||||
|
if (ent.OwnerId != peerId)
|
||||||
|
{
|
||||||
|
_logger.Warn($"Not owner can't change properties of object {entityId}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var entityStateData = _serializer.ReadData(_serializer.Size);
|
for (var i = 0; i < ent.Properties.Length; i++)
|
||||||
ent.State.Write(ref entityStateData);
|
{
|
||||||
|
if (_serializer.ReadBool())
|
||||||
|
{
|
||||||
|
var property = ent.Properties[i];
|
||||||
|
var size = property.Size;
|
||||||
|
if (!property.IsFixed)
|
||||||
|
size = _serializer.ReadUShort();
|
||||||
|
|
||||||
|
if (size > property.Capacity)
|
||||||
|
{
|
||||||
|
_logger.Warn($"Property {i} payload too large, size: {size}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var propertyPayload = _serializer.ReadData(size);
|
||||||
|
property.Write(ref propertyPayload);
|
||||||
|
property.Size = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_entitiesDirtySet.Add(ent))
|
||||||
|
_entitiesDirty.Add(ent);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Error($"Entity with Id {entityId} not found, replication interrupted");
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case RagonOperation.REPLICATE_ENTITY_EVENT:
|
case RagonOperation.REPLICATE_ENTITY_EVENT:
|
||||||
{
|
{
|
||||||
var evntId = _serializer.ReadUShort();
|
var eventId = _serializer.ReadUShort();
|
||||||
var entityId = _serializer.ReadInt();
|
var eventMode = (RagonReplicationMode) _serializer.ReadByte();
|
||||||
|
var targetMode = (RagonTarget) _serializer.ReadByte();
|
||||||
|
var entityId = _serializer.ReadUShort();
|
||||||
|
|
||||||
if (!_entities.TryGetValue(entityId, out var ent))
|
if (!_entities.TryGetValue(entityId, out var ent))
|
||||||
|
{
|
||||||
|
_logger.Warn($"Entity not found for event with Id {eventId}");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (ent.Authority == RagonAuthority.OWNER_ONLY && ent.OwnerId != peerId)
|
if (ent.Authority == RagonAuthority.OwnerOnly && ent.OwnerId != peerId)
|
||||||
|
{
|
||||||
|
_logger.Warn($"Player have not enought authority for event with Id {eventId}");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Span<byte> payloadRaw = stackalloc byte[_serializer.Size];
|
Span<byte> payloadRaw = stackalloc byte[_serializer.Size];
|
||||||
var payloadData = _serializer.ReadData(_serializer.Size);
|
var payloadData = _serializer.ReadData(_serializer.Size);
|
||||||
payloadData.CopyTo(payloadRaw);
|
payloadData.CopyTo(payloadRaw);
|
||||||
|
|
||||||
ReadOnlySpan<byte> payload = payloadRaw;
|
ReadOnlySpan<byte> payload = payloadRaw;
|
||||||
if (_plugin.InternalHandle(peerId, entityId, evntId, ref payload))
|
if (_plugin.InternalHandle(peerId, entityId, eventId, ref payload))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (eventMode == RagonReplicationMode.Buffered && targetMode != RagonTarget.Owner)
|
||||||
|
{
|
||||||
|
var bufferedEvent = new EntityEvent()
|
||||||
|
{
|
||||||
|
EventData = payload.ToArray(),
|
||||||
|
Target = targetMode,
|
||||||
|
EventId = eventId,
|
||||||
|
PeerId = peerId,
|
||||||
|
};
|
||||||
|
ent.BufferedEvents.Add(bufferedEvent);
|
||||||
|
}
|
||||||
|
|
||||||
_serializer.Clear();
|
_serializer.Clear();
|
||||||
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
|
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
|
||||||
_serializer.WriteUShort(evntId);
|
_serializer.WriteUShort(eventId);
|
||||||
_serializer.WriteInt(entityId);
|
_serializer.WriteUShort(peerId);
|
||||||
|
_serializer.WriteByte((byte) eventMode);
|
||||||
|
_serializer.WriteUShort(entityId);
|
||||||
_serializer.WriteData(ref payload);
|
_serializer.WriteData(ref payload);
|
||||||
var sendData = _serializer.ToArray();
|
|
||||||
|
|
||||||
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case RagonOperation.REPLICATE_EVENT:
|
|
||||||
{
|
|
||||||
var evntId = _serializer.ReadUShort();
|
|
||||||
|
|
||||||
Span<byte> payloadRaw = stackalloc byte[_serializer.Size];
|
|
||||||
var payloadData = _serializer.ReadData(_serializer.Size);
|
|
||||||
payloadData.CopyTo(payloadRaw);
|
|
||||||
|
|
||||||
ReadOnlySpan<byte> payload = payloadRaw;
|
|
||||||
if (_plugin.InternalHandle(peerId, evntId, ref payload))
|
|
||||||
return;
|
|
||||||
|
|
||||||
_serializer.Clear();
|
|
||||||
_serializer.WriteOperation(RagonOperation.REPLICATE_EVENT);
|
|
||||||
_serializer.WriteUShort(evntId);
|
|
||||||
_serializer.WriteData(ref payload);
|
|
||||||
|
|
||||||
var sendData = _serializer.ToArray();
|
var sendData = _serializer.ToArray();
|
||||||
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
|
SendEvent(ent, targetMode, sendData);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case RagonOperation.CREATE_ENTITY:
|
case RagonOperation.CREATE_ENTITY:
|
||||||
{
|
{
|
||||||
var entityType = _serializer.ReadUShort();
|
var entityType = _serializer.ReadUShort();
|
||||||
var stateAuthority = (RagonAuthority) _serializer.ReadByte();
|
|
||||||
var eventAuthority = (RagonAuthority) _serializer.ReadByte();
|
var eventAuthority = (RagonAuthority) _serializer.ReadByte();
|
||||||
var entity = new Entity(peerId, entityType, stateAuthority, eventAuthority);
|
var propertiesCount = _serializer.ReadUShort();
|
||||||
|
|
||||||
|
_logger.Trace($"[{peerId}] Create Entity {entityType}");
|
||||||
|
|
||||||
|
var entity = new Entity(peerId, entityType, 0, eventAuthority, propertiesCount);
|
||||||
|
for (var i = 0; i < propertiesCount; i++)
|
||||||
|
{
|
||||||
|
var propertyType = _serializer.ReadBool();
|
||||||
|
var propertySize = _serializer.ReadUShort();
|
||||||
|
entity.Properties[i] = new EntityProperty(propertySize, propertyType);
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
var entityPayload = _serializer.ReadData(_serializer.Size);
|
var entityPayload = _serializer.ReadData(_serializer.Size);
|
||||||
entity.Payload.Write(ref entityPayload);
|
entity.Payload = entityPayload.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
var player = _players[peerId];
|
var player = _players[peerId];
|
||||||
player.Entities.Add(entity);
|
player.Entities.Add(entity);
|
||||||
player.EntitiesIds.Add(entity.EntityId);
|
player.EntitiesIds.Add(entity.EntityId);
|
||||||
|
|
||||||
var ownerId = (ushort) peerId;
|
var ownerId = peerId;
|
||||||
|
|
||||||
_entities.Add(entity.EntityId, entity);
|
_entities.Add(entity.EntityId, entity);
|
||||||
_entitiesAll = _entities.Values.ToArray();
|
_entitiesAll = _entities.Values.ToArray();
|
||||||
@@ -235,16 +389,15 @@ namespace Ragon.Core
|
|||||||
_serializer.Clear();
|
_serializer.Clear();
|
||||||
_serializer.WriteOperation(RagonOperation.CREATE_ENTITY);
|
_serializer.WriteOperation(RagonOperation.CREATE_ENTITY);
|
||||||
_serializer.WriteUShort(entityType);
|
_serializer.WriteUShort(entityType);
|
||||||
_serializer.WriteByte((byte) stateAuthority);
|
_serializer.WriteUShort(entity.EntityId);
|
||||||
_serializer.WriteByte((byte) eventAuthority);
|
|
||||||
_serializer.WriteInt(entity.EntityId);
|
|
||||||
_serializer.WriteUShort(ownerId);
|
_serializer.WriteUShort(ownerId);
|
||||||
|
|
||||||
{
|
{
|
||||||
var entityPayload = entity.Payload.Read();
|
ReadOnlySpan<byte> entityPayload = entity.Payload.AsSpan();
|
||||||
|
_serializer.WriteUShort((ushort) entityPayload.Length);
|
||||||
_serializer.WriteData(ref entityPayload);
|
_serializer.WriteData(ref entityPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
var sendData = _serializer.ToArray();
|
var sendData = _serializer.ToArray();
|
||||||
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
|
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
|
||||||
break;
|
break;
|
||||||
@@ -254,7 +407,7 @@ namespace Ragon.Core
|
|||||||
var entityId = _serializer.ReadInt();
|
var entityId = _serializer.ReadInt();
|
||||||
if (_entities.TryGetValue(entityId, out var entity))
|
if (_entities.TryGetValue(entityId, out var entity))
|
||||||
{
|
{
|
||||||
if (entity.Authority == RagonAuthority.OWNER_ONLY && entity.OwnerId != peerId)
|
if (entity.Authority == RagonAuthority.OwnerOnly && entity.OwnerId != peerId)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var player = _players[peerId];
|
var player = _players[peerId];
|
||||||
@@ -271,6 +424,7 @@ namespace Ragon.Core
|
|||||||
_serializer.Clear();
|
_serializer.Clear();
|
||||||
_serializer.WriteOperation(RagonOperation.DESTROY_ENTITY);
|
_serializer.WriteOperation(RagonOperation.DESTROY_ENTITY);
|
||||||
_serializer.WriteInt(entityId);
|
_serializer.WriteInt(entityId);
|
||||||
|
_serializer.WriteUShort((ushort) destroyPayload.Length);
|
||||||
_serializer.WriteData(ref destroyPayload);
|
_serializer.WriteData(ref destroyPayload);
|
||||||
|
|
||||||
var sendData = _serializer.ToArray();
|
var sendData = _serializer.ToArray();
|
||||||
@@ -279,68 +433,83 @@ namespace Ragon.Core
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case RagonOperation.SCENE_IS_LOADED:
|
|
||||||
{
|
|
||||||
_serializer.Clear();
|
|
||||||
_serializer.WriteOperation(RagonOperation.SNAPSHOT);
|
|
||||||
|
|
||||||
_serializer.WriteInt(_allPlayers.Length);
|
|
||||||
foreach (var playerPeerId in _allPlayers)
|
|
||||||
{
|
|
||||||
_serializer.WriteString(_players[playerPeerId].Id);
|
|
||||||
_serializer.WriteUShort((ushort) playerPeerId);
|
|
||||||
_serializer.WriteString(_players[playerPeerId].PlayerName);
|
|
||||||
}
|
|
||||||
|
|
||||||
_serializer.WriteInt(_entitiesAll.Length);
|
|
||||||
foreach (var entity in _entitiesAll)
|
|
||||||
{
|
|
||||||
var payload = entity.Payload.Read();
|
|
||||||
var state = entity.State.Read();
|
|
||||||
|
|
||||||
_serializer.WriteInt(entity.EntityId);
|
|
||||||
_serializer.WriteByte((byte) entity.State.Authority);
|
|
||||||
_serializer.WriteByte((byte) entity.Authority);
|
|
||||||
_serializer.WriteUShort(entity.EntityType);
|
|
||||||
_serializer.WriteUShort((ushort) entity.OwnerId);
|
|
||||||
_serializer.WriteUShort((ushort) payload.Length);
|
|
||||||
_serializer.WriteData(ref payload);
|
|
||||||
_serializer.WriteUShort((ushort) state.Length);
|
|
||||||
_serializer.WriteData(ref state);
|
|
||||||
}
|
|
||||||
|
|
||||||
var sendData = _serializer.ToArray();
|
|
||||||
Send(peerId, sendData, DeliveryType.Reliable);
|
|
||||||
|
|
||||||
_players[peerId].IsLoaded = true;
|
|
||||||
_readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray();
|
|
||||||
|
|
||||||
_plugin.OnPlayerJoined(_players[peerId]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Tick(float deltaTime)
|
public void Tick(float deltaTime)
|
||||||
{
|
{
|
||||||
_plugin.OnTick(deltaTime);
|
_scheduler.Tick(deltaTime);
|
||||||
|
|
||||||
foreach (var entity in _entitiesAll)
|
ReplicateProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Move this to specialized class
|
||||||
|
void ReplicateSnapshot(uint peerId)
|
||||||
|
{
|
||||||
|
_serializer.Clear();
|
||||||
|
_serializer.WriteOperation(RagonOperation.SNAPSHOT);
|
||||||
|
_serializer.WriteUShort((ushort) _readyPlayers.Length);
|
||||||
|
foreach (var playerPeerId in _readyPlayers)
|
||||||
{
|
{
|
||||||
if (entity.State.isDirty)
|
_serializer.WriteUShort((ushort) playerPeerId);
|
||||||
{
|
_serializer.WriteString(_players[playerPeerId].Id);
|
||||||
var state = entity.State.Read();
|
_serializer.WriteString(_players[playerPeerId].PlayerName);
|
||||||
|
}
|
||||||
|
|
||||||
_serializer.Clear();
|
var dynamicEntities = _entitiesAll.Where(e => e.StaticId == 0).ToArray();
|
||||||
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_STATE);
|
var dynamicEntitiesCount = (ushort) dynamicEntities.Length;
|
||||||
_serializer.WriteInt(entity.EntityId);
|
_serializer.WriteUShort(dynamicEntitiesCount);
|
||||||
_serializer.WriteData(ref state);
|
foreach (var entity in dynamicEntities)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<byte> payload = entity.Payload.AsSpan();
|
||||||
|
|
||||||
var sendData = _serializer.ToArray();
|
_serializer.WriteUShort(entity.EntityType);
|
||||||
Broadcast(_readyPlayers, sendData, DeliveryType.Unreliable);
|
_serializer.WriteUShort(entity.EntityId);
|
||||||
|
_serializer.WriteUShort((ushort) entity.OwnerId);
|
||||||
|
_serializer.WriteUShort((ushort) payload.Length);
|
||||||
|
_serializer.WriteData(ref payload);
|
||||||
|
|
||||||
entity.State.Clear();
|
entity.Snapshot(_serializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var staticEntities = _entitiesAll.Where(e => e.StaticId != 0).ToArray();
|
||||||
|
var staticEntitiesCount = (ushort) staticEntities.Length;
|
||||||
|
_serializer.WriteUShort(staticEntitiesCount);
|
||||||
|
foreach (var entity in staticEntities)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<byte> payload = entity.Payload.AsSpan();
|
||||||
|
|
||||||
|
_serializer.WriteUShort(entity.EntityType);
|
||||||
|
_serializer.WriteUShort(entity.EntityId);
|
||||||
|
_serializer.WriteUShort(entity.StaticId);
|
||||||
|
_serializer.WriteUShort(entity.OwnerId);
|
||||||
|
_serializer.WriteUShort((ushort) payload.Length);
|
||||||
|
_serializer.WriteData(ref payload);
|
||||||
|
|
||||||
|
entity.Snapshot(_serializer);
|
||||||
|
}
|
||||||
|
|
||||||
|
var sendData = _serializer.ToArray();
|
||||||
|
Send(peerId, sendData, DeliveryType.Reliable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReplicateProperties()
|
||||||
|
{
|
||||||
|
var entities = (ushort) _entitiesDirty.Count;
|
||||||
|
if (entities > 0)
|
||||||
|
{
|
||||||
|
_serializer.Clear();
|
||||||
|
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_STATE);
|
||||||
|
_serializer.WriteUShort(entities);
|
||||||
|
|
||||||
|
foreach (var entity in _entitiesDirty)
|
||||||
|
entity.ReplicateProperties(_serializer);
|
||||||
|
|
||||||
|
_entitiesDirty.Clear();
|
||||||
|
_entitiesDirtySet.Clear();
|
||||||
|
|
||||||
|
var sendData = _serializer.ToArray();
|
||||||
|
Broadcast(_readyPlayers, sendData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,6 +520,9 @@ namespace Ragon.Core
|
|||||||
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
|
foreach (var peerId in _allPlayers)
|
||||||
|
_gameThread.Server.Disconnect(peerId, 0);
|
||||||
|
|
||||||
_plugin.OnStop();
|
_plugin.OnStop();
|
||||||
_plugin.Detach();
|
_plugin.Detach();
|
||||||
}
|
}
|
||||||
@@ -358,30 +530,49 @@ namespace Ragon.Core
|
|||||||
public Player GetPlayerById(uint peerId) => _players[peerId];
|
public Player GetPlayerById(uint peerId) => _players[peerId];
|
||||||
|
|
||||||
public Entity GetEntityById(int entityId) => _entities[entityId];
|
public Entity GetEntityById(int entityId) => _entities[entityId];
|
||||||
|
|
||||||
public Player GetOwner() => _players[_owner];
|
public Player GetOwner() => _players[_owner];
|
||||||
|
|
||||||
public IDispatcher GetThreadDispatcher() => _gameThread.ThreadDispatcher;
|
|
||||||
|
|
||||||
public void Send(uint peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
|
public IDispatcher GetThreadDispatcher() => _gameThread.ThreadDispatcher;
|
||||||
|
|
||||||
|
public IScheduler GetScheduler() => _scheduler;
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Move this to Entity Event Manager
|
||||||
|
public void SendEvent(Entity ent, RagonTarget targetMode, byte[] sendData)
|
||||||
{
|
{
|
||||||
|
switch (targetMode)
|
||||||
|
{
|
||||||
|
case RagonTarget.Owner:
|
||||||
|
{
|
||||||
|
Send(ent.OwnerId, sendData, DeliveryType.Reliable);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RagonTarget.ExceptOwner:
|
||||||
|
{
|
||||||
|
_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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Send(uint peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable) =>
|
||||||
_gameThread.Server.Send(peerId, rawData, deliveryType);
|
_gameThread.Server.Send(peerId, rawData, deliveryType);
|
||||||
}
|
|
||||||
|
|
||||||
public void Broadcast(uint[] peersIds, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
|
public void Broadcast(uint[] peersIds, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable) =>
|
||||||
{
|
_gameThread.Server.Broadcast(peersIds, rawData, deliveryType);
|
||||||
foreach (var peer in peersIds)
|
|
||||||
{
|
|
||||||
_gameThread.Server.Send(peer, rawData, deliveryType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Broadcast(byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
|
public void Broadcast(byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable) =>
|
||||||
{
|
_gameThread.Server.Broadcast(_allPlayers, rawData, deliveryType);
|
||||||
foreach (var peer in _allPlayers)
|
|
||||||
{
|
|
||||||
_gameThread.Server.Send(peer, rawData, deliveryType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Ragon.Common;
|
||||||
using ENet;
|
using ENet;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
|
||||||
@@ -11,36 +10,33 @@ namespace Ragon.Core
|
|||||||
{
|
{
|
||||||
private readonly RoomManager _roomManager;
|
private readonly RoomManager _roomManager;
|
||||||
private readonly Thread _thread;
|
private readonly Thread _thread;
|
||||||
private readonly Stopwatch _gameLoopTimer;
|
|
||||||
private readonly Lobby _lobby;
|
private readonly Lobby _lobby;
|
||||||
|
private readonly ISocketServer _server;
|
||||||
|
private readonly IDispatcherInternal _dispatcherInternal;
|
||||||
|
private readonly IDispatcher _dispatcher;
|
||||||
private readonly ILogger _logger = LogManager.GetCurrentClassLogger();
|
private readonly ILogger _logger = LogManager.GetCurrentClassLogger();
|
||||||
private readonly float _deltaTime = 0.0f;
|
private readonly float _deltaTime = 0.0f;
|
||||||
private readonly Stopwatch _statisticsTimer;
|
|
||||||
private readonly Configuration _configuration;
|
private readonly Configuration _configuration;
|
||||||
private readonly IDispatcherInternal _dispatcherInternal;
|
|
||||||
|
|
||||||
public IDispatcher ThreadDispatcher { get; private set; }
|
public ISocketServer Server => _server;
|
||||||
public ISocketServer Server { get; private set; }
|
public IDispatcher ThreadDispatcher => _dispatcher;
|
||||||
|
|
||||||
public GameThread(PluginFactory factory, Configuration configuration)
|
public GameThread(PluginFactory factory, Configuration configuration)
|
||||||
{
|
{
|
||||||
|
var authorizationProvider = factory.CreateAuthorizationProvider(configuration);
|
||||||
|
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
|
|
||||||
var authorizationProvider = factory.CreateAuthorizationProvider(configuration);
|
|
||||||
var dispatcher = new Dispatcher();
|
var dispatcher = new Dispatcher();
|
||||||
_dispatcherInternal = dispatcher;
|
_dispatcherInternal = dispatcher;
|
||||||
|
_dispatcher = dispatcher;
|
||||||
ThreadDispatcher = dispatcher;
|
|
||||||
Server = new ENetServer(this);
|
_server = new ENetServer(this);
|
||||||
|
|
||||||
_deltaTime = 1000.0f / configuration.SendRate;
|
_deltaTime = 1000.0f / configuration.SendRate;
|
||||||
|
|
||||||
_roomManager = new RoomManager(factory, this);
|
_roomManager = new RoomManager(factory, this);
|
||||||
_lobby = new Lobby(authorizationProvider, _roomManager, this);
|
_lobby = new Lobby(authorizationProvider, _roomManager, this);
|
||||||
|
|
||||||
_gameLoopTimer = new Stopwatch();
|
|
||||||
_statisticsTimer = new Stopwatch();
|
|
||||||
|
|
||||||
_thread = new Thread(Execute);
|
_thread = new Thread(Execute);
|
||||||
_thread.Name = "Game Thread";
|
_thread.Name = "Game Thread";
|
||||||
_thread.IsBackground = true;
|
_thread.IsBackground = true;
|
||||||
@@ -48,19 +44,33 @@ namespace Ragon.Core
|
|||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
Server.Start(_configuration.Port, _configuration.MaxConnections);
|
var strings = _configuration.Protocol.Split(".");
|
||||||
|
if (strings.Length < 3)
|
||||||
|
{
|
||||||
|
_logger.Error("Wrong protocol passed to connect method");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_gameLoopTimer.Start();
|
var parts = new uint[] {0, 0, 0};
|
||||||
_statisticsTimer.Start();
|
for (int i = 0; i < parts.Length; i++)
|
||||||
|
{
|
||||||
|
if (!uint.TryParse(strings[i], out var v))
|
||||||
|
{
|
||||||
|
_logger.Error("Wrong protocol");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
parts[i] = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint encoded = (parts[0] << 16) | (parts[1] << 8) | parts[2];
|
||||||
|
_server.Start(_configuration.Port, _configuration.MaxConnections, encoded);
|
||||||
_thread.Start();
|
_thread.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
Server.Stop();
|
_server.Stop();
|
||||||
|
|
||||||
_gameLoopTimer.Stop();
|
|
||||||
_statisticsTimer.Stop();
|
|
||||||
_thread.Interrupt();
|
_thread.Interrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,23 +78,11 @@ namespace Ragon.Core
|
|||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
Server.Process();
|
_server.Process();
|
||||||
|
|
||||||
_dispatcherInternal.Process();
|
_dispatcherInternal.Process();
|
||||||
|
_roomManager.Tick(_deltaTime);
|
||||||
|
|
||||||
var elapsedMilliseconds = _gameLoopTimer.ElapsedMilliseconds;
|
Thread.Sleep((int) _deltaTime);
|
||||||
if (elapsedMilliseconds > _deltaTime)
|
|
||||||
{
|
|
||||||
_roomManager.Tick(elapsedMilliseconds / 1000.0f);
|
|
||||||
_gameLoopTimer.Restart();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_statisticsTimer.Elapsed.Seconds > _configuration.StatisticsInterval && _roomManager.RoomsBySocket.Count > 0)
|
|
||||||
{
|
|
||||||
_logger.Trace($"Rooms: {_roomManager.Rooms.Count} Clients: {_roomManager.RoomsBySocket.Count}");
|
|
||||||
_statisticsTimer.Restart();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,19 +102,18 @@ namespace Ragon.Core
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var peerId = evnt.Peer.ID;
|
var peerId = (ushort) evnt.Peer.ID;
|
||||||
var dataRaw = new byte[evnt.Packet.Length];
|
var dataRaw = new byte[evnt.Packet.Length];
|
||||||
evnt.Packet.CopyTo(dataRaw);
|
evnt.Packet.CopyTo(dataRaw);
|
||||||
|
|
||||||
var data = new ReadOnlySpan<byte>(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))
|
if (_roomManager.RoomsBySocket.TryGetValue(peerId, out var room))
|
||||||
{
|
room.ProcessEvent(peerId, operation, payload);
|
||||||
room.ProcessEvent(peerId, data);
|
|
||||||
}
|
_lobby.ProcessEvent(peerId, operation, payload);
|
||||||
else
|
|
||||||
{
|
|
||||||
_lobby.ProcessEvent(peerId, data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ public interface IGameRoom
|
|||||||
public Entity GetEntityById(int entityId);
|
public Entity GetEntityById(int entityId);
|
||||||
public Player GetOwner();
|
public Player GetOwner();
|
||||||
public IDispatcher GetThreadDispatcher();
|
public IDispatcher GetThreadDispatcher();
|
||||||
|
public IScheduler GetScheduler();
|
||||||
|
|
||||||
public void Send(uint peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable);
|
public void Send(uint peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable);
|
||||||
public void Broadcast(uint[] peersIds, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable);
|
public void Broadcast(uint[] peersIds, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable);
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace Ragon.Core;
|
|
||||||
|
|
||||||
public interface ILobby
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,64 +1,121 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
using Ragon.Common;
|
using Ragon.Common;
|
||||||
|
|
||||||
namespace Ragon.Core;
|
namespace Ragon.Core;
|
||||||
|
|
||||||
public class Lobby : ILobby
|
public class Lobby
|
||||||
{
|
{
|
||||||
|
private readonly ILogger _logger = LogManager.GetCurrentClassLogger();
|
||||||
private readonly RagonSerializer _serializer;
|
private readonly RagonSerializer _serializer;
|
||||||
private readonly RoomManager _roomManager;
|
private readonly RoomManager _roomManager;
|
||||||
|
private readonly AuthorizationManager _authorizationManager;
|
||||||
|
private readonly IGameThread _gameThread;
|
||||||
|
|
||||||
public AuthorizationManager AuthorizationManager { get; private set; }
|
public AuthorizationManager AuthorizationManager => _authorizationManager;
|
||||||
|
|
||||||
public Lobby(IAuthorizationProvider provider, RoomManager manager, IGameThread gameThread)
|
public Lobby(IAuthorizationProvider provider, RoomManager manager, IGameThread gameThread)
|
||||||
{
|
{
|
||||||
_roomManager = manager;
|
_roomManager = manager;
|
||||||
|
_gameThread = gameThread;
|
||||||
_serializer = new RagonSerializer();
|
_serializer = new RagonSerializer();
|
||||||
AuthorizationManager = new AuthorizationManager(provider, gameThread, this, _serializer);
|
_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.Clear();
|
||||||
_serializer.FromSpan(ref payload);
|
_serializer.FromSpan(ref payload);
|
||||||
|
|
||||||
|
var player = _authorizationManager.GetPlayer(peerId);
|
||||||
|
if (op == RagonOperation.AUTHORIZE)
|
||||||
|
{
|
||||||
|
if (player != null)
|
||||||
|
{
|
||||||
|
_logger.Warn("Player already authorized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = _serializer.ReadString();
|
||||||
|
var playerName = _serializer.ReadString();
|
||||||
|
var additionalData = _serializer.ReadData(_serializer.Size);
|
||||||
|
_authorizationManager.OnAuthorization(peerId, key, playerName, additionalData);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player == null)
|
||||||
|
{
|
||||||
|
_logger.Warn($"Peer not authorized {peerId} trying to {op}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (op)
|
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:
|
case RagonOperation.JOIN_ROOM:
|
||||||
{
|
{
|
||||||
var roomId = _serializer.ReadString();
|
var roomId = _serializer.ReadString();
|
||||||
var player = AuthorizationManager.GetPlayer(peerId);
|
var exists = _roomManager.Rooms.Any(r => r.Id == roomId);
|
||||||
if (player != null)
|
if (!exists)
|
||||||
_roomManager.Join(player, roomId, Array.Empty<byte>());
|
{
|
||||||
|
_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;
|
break;
|
||||||
}
|
}
|
||||||
case RagonOperation.JOIN_OR_CREATE_ROOM:
|
case RagonOperation.JOIN_OR_CREATE_ROOM:
|
||||||
{
|
{
|
||||||
var map = _serializer.ReadString();
|
var roomId = Guid.NewGuid().ToString();
|
||||||
var min = _serializer.ReadInt();
|
var roomProperties = new RagonRoomParameters();
|
||||||
var max = _serializer.ReadInt();
|
roomProperties.Deserialize(_serializer);
|
||||||
var player = AuthorizationManager.GetPlayer(peerId);
|
|
||||||
if (player != null)
|
if (_roomManager.RoomsBySocket.ContainsKey(peerId))
|
||||||
_roomManager.JoinOrCreate(player, map, min, max, Array.Empty<byte>());
|
_roomManager.Left(player, Array.Empty<byte>());
|
||||||
|
|
||||||
|
_roomManager.JoinOrCreate(player, roomId, roomProperties, Array.Empty<byte>());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case RagonOperation.LEAVE_ROOM:
|
case RagonOperation.LEAVE_ROOM:
|
||||||
{
|
{
|
||||||
var player = AuthorizationManager.GetPlayer(peerId);
|
_roomManager.Left(player, Array.Empty<byte>());
|
||||||
if (player != null)
|
|
||||||
_roomManager.Left(player, Array.Empty<byte>());
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,6 +123,6 @@ public class Lobby : ILobby
|
|||||||
|
|
||||||
public void OnDisconnected(uint peerId)
|
public void OnDisconnected(uint peerId)
|
||||||
{
|
{
|
||||||
AuthorizationManager.Cleanup(peerId);
|
_authorizationManager.Cleanup(peerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,11 +6,11 @@ namespace Ragon.Core
|
|||||||
public class Player
|
public class Player
|
||||||
{
|
{
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
public uint PeerId { get; set; }
|
|
||||||
public string PlayerName { get; set; }
|
public string PlayerName { get; set; }
|
||||||
|
public uint PeerId { get; set; }
|
||||||
public bool IsLoaded { get; set; }
|
public bool IsLoaded { get; set; }
|
||||||
|
|
||||||
public List<Entity> Entities;
|
public List<Entity> Entities;
|
||||||
public List<int> EntitiesIds;
|
public List<ushort> EntitiesIds;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using NetStack.Serialization;
|
|
||||||
using NLog;
|
using NLog;
|
||||||
using Ragon.Common;
|
using Ragon.Common;
|
||||||
|
|
||||||
@@ -10,22 +8,20 @@ namespace Ragon.Core
|
|||||||
public class PluginBase
|
public class PluginBase
|
||||||
{
|
{
|
||||||
private delegate void SubscribeDelegate(Player player, ref ReadOnlySpan<byte> data);
|
private delegate void SubscribeDelegate(Player player, ref ReadOnlySpan<byte> data);
|
||||||
|
|
||||||
private delegate void SubscribeEntityDelegate(Player player, Entity entity, ref ReadOnlySpan<byte> data);
|
private delegate void SubscribeEntityDelegate(Player player, Entity entity, ref ReadOnlySpan<byte> data);
|
||||||
|
|
||||||
private Dictionary<ushort, SubscribeDelegate> _globalEvents = new();
|
private Dictionary<ushort, SubscribeDelegate> _globalEvents = new();
|
||||||
private Dictionary<int, Dictionary<ushort, SubscribeEntityDelegate>> _entityEvents = new();
|
private Dictionary<int, Dictionary<ushort, SubscribeEntityDelegate>> _entityEvents = new();
|
||||||
private readonly BitBuffer _buffer = new();
|
|
||||||
private readonly RagonSerializer _serializer = new();
|
private readonly RagonSerializer _serializer = new();
|
||||||
|
|
||||||
protected IGameRoom GameRoom { get; private set; } = null!;
|
protected IGameRoom Room { get; private set; } = null!;
|
||||||
protected ILogger Logger = null!;
|
protected ILogger Logger = null!;
|
||||||
|
|
||||||
public void Attach(GameRoom gameRoom)
|
public void Attach(GameRoom gameRoom)
|
||||||
{
|
{
|
||||||
Logger = LogManager.GetLogger($"Plugin<{GetType().Name}>");
|
Logger = LogManager.GetLogger($"Plugin<{GetType().Name}>");
|
||||||
|
|
||||||
GameRoom = gameRoom;
|
Room = gameRoom;
|
||||||
|
|
||||||
_globalEvents.Clear();
|
_globalEvents.Clear();
|
||||||
_entityEvents.Clear();
|
_entityEvents.Clear();
|
||||||
@@ -37,7 +33,7 @@ namespace Ragon.Core
|
|||||||
_entityEvents.Clear();
|
_entityEvents.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Subscribe<T>(ushort evntCode, Action<Player, T> action) where T : IRagonSerializable, new()
|
public void OnEvent<T>(ushort evntCode, Action<Player, T> action) where T : IRagonSerializable, new()
|
||||||
{
|
{
|
||||||
if (_globalEvents.ContainsKey(evntCode))
|
if (_globalEvents.ContainsKey(evntCode))
|
||||||
{
|
{
|
||||||
@@ -54,14 +50,14 @@ namespace Ragon.Core
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_buffer.Clear();
|
_serializer.Clear();
|
||||||
_buffer.FromSpan(ref raw, raw.Length);
|
_serializer.FromSpan(ref raw);
|
||||||
data.Deserialize(_buffer);
|
data.Deserialize(_serializer);
|
||||||
action.Invoke(player, data);
|
action.Invoke(player, data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Subscribe(ushort evntCode, Action<Player> action)
|
public void OnEvent(ushort evntCode, Action<Player> action)
|
||||||
{
|
{
|
||||||
if (_globalEvents.ContainsKey(evntCode))
|
if (_globalEvents.ContainsKey(evntCode))
|
||||||
{
|
{
|
||||||
@@ -72,7 +68,7 @@ namespace Ragon.Core
|
|||||||
_globalEvents.Add(evntCode, (Player player, ref ReadOnlySpan<byte> raw) => { action.Invoke(player); });
|
_globalEvents.Add(evntCode, (Player player, ref ReadOnlySpan<byte> raw) => { action.Invoke(player); });
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Subscribe<T>(Entity entity, ushort evntCode, Action<Player, Entity, T> action) where T : IRagonSerializable, new()
|
public void OnEvent<T>(Entity entity, ushort evntCode, Action<Player, Entity, T> action) where T : IRagonSerializable, new()
|
||||||
{
|
{
|
||||||
if (_entityEvents.ContainsKey(entity.EntityId))
|
if (_entityEvents.ContainsKey(entity.EntityId))
|
||||||
{
|
{
|
||||||
@@ -91,9 +87,9 @@ namespace Ragon.Core
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_buffer.Clear();
|
_serializer.Clear();
|
||||||
_buffer.FromSpan(ref raw, raw.Length);
|
_serializer.FromSpan(ref raw);
|
||||||
data.Deserialize(_buffer);
|
data.Deserialize(_serializer);
|
||||||
action.Invoke(player, ent, data);
|
action.Invoke(player, ent, data);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -111,15 +107,15 @@ namespace Ragon.Core
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_buffer.Clear();
|
_serializer.Clear();
|
||||||
_buffer.FromSpan(ref raw, raw.Length);
|
_serializer.FromSpan(ref raw);
|
||||||
data.Deserialize(_buffer);
|
data.Deserialize(_serializer);
|
||||||
action.Invoke(player, ent, data);
|
action.Invoke(player, ent, data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Subscribe(Entity entity, ushort evntCode, Action<Player, Entity> action)
|
public void OnEvent(Entity entity, ushort evntCode, Action<Player, Entity> action)
|
||||||
{
|
{
|
||||||
if (_entityEvents.ContainsKey(entity.EntityId))
|
if (_entityEvents.ContainsKey(entity.EntityId))
|
||||||
{
|
{
|
||||||
@@ -153,8 +149,9 @@ namespace Ragon.Core
|
|||||||
if (!_entityEvents[entityId].ContainsKey(evntCode))
|
if (!_entityEvents[entityId].ContainsKey(evntCode))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var player = GameRoom.GetPlayerById(peerId);
|
var player = Room.GetPlayerById(peerId);
|
||||||
var entity = GameRoom.GetEntityById(entityId);
|
var entity = Room.GetEntityById(entityId);
|
||||||
|
|
||||||
_entityEvents[entityId][evntCode].Invoke(player, entity, ref payload);
|
_entityEvents[entityId][evntCode].Invoke(player, entity, ref payload);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -164,7 +161,7 @@ namespace Ragon.Core
|
|||||||
{
|
{
|
||||||
if (_globalEvents.ContainsKey(evntCode))
|
if (_globalEvents.ContainsKey(evntCode))
|
||||||
{
|
{
|
||||||
var player = GameRoom.GetPlayerById(peerId);
|
var player = Room.GetPlayerById(peerId);
|
||||||
_globalEvents[evntCode].Invoke(player, ref payload);
|
_globalEvents[evntCode].Invoke(player, ref payload);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -172,66 +169,50 @@ namespace Ragon.Core
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendEvent(Player player, uint eventCode, IRagonSerializable payload)
|
public void ReplicateEvent(Player player, uint eventCode, IRagonSerializable payload)
|
||||||
{
|
{
|
||||||
_serializer.Clear();
|
_serializer.Clear();
|
||||||
_serializer.WriteOperation(RagonOperation.REPLICATE_EVENT);
|
_serializer.WriteOperation(RagonOperation.REPLICATE_EVENT);
|
||||||
|
|
||||||
_buffer.Clear();
|
payload.Serialize(_serializer);
|
||||||
payload.Serialize(_buffer);
|
|
||||||
|
|
||||||
var payloadData = _serializer.GetWritableData(_buffer.Length);
|
|
||||||
_buffer.ToSpan(ref payloadData);
|
|
||||||
|
|
||||||
var sendData = _serializer.ToArray();
|
var sendData = _serializer.ToArray();
|
||||||
GameRoom.Send(player.PeerId, sendData);
|
Room.Send(player.PeerId, sendData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BroadcastEvent(ushort eventCode, IRagonSerializable payload)
|
public void ReplicateEvent(ushort eventCode, IRagonSerializable payload)
|
||||||
{
|
{
|
||||||
_serializer.Clear();
|
_serializer.Clear();
|
||||||
_serializer.WriteOperation(RagonOperation.REPLICATE_EVENT);
|
_serializer.WriteOperation(RagonOperation.REPLICATE_EVENT);
|
||||||
|
|
||||||
_buffer.Clear();
|
payload.Serialize(_serializer);
|
||||||
payload.Serialize(_buffer);
|
|
||||||
|
|
||||||
var payloadData = _serializer.GetWritableData(_buffer.Length);
|
|
||||||
_buffer.ToSpan(ref payloadData);
|
|
||||||
|
|
||||||
var sendData = _serializer.ToArray();
|
var sendData = _serializer.ToArray();
|
||||||
GameRoom.Broadcast(sendData, DeliveryType.Reliable);
|
Room.Broadcast(sendData, DeliveryType.Reliable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendEntityEvent(Player player, Entity entity, IRagonSerializable payload)
|
public void ReplicateEntityEvent(Player player, Entity entity, IRagonSerializable payload)
|
||||||
{
|
{
|
||||||
_serializer.Clear();
|
_serializer.Clear();
|
||||||
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
|
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
|
||||||
_serializer.WriteInt(entity.EntityId);
|
_serializer.WriteInt(entity.EntityId);
|
||||||
|
|
||||||
_buffer.Clear();
|
payload.Serialize(_serializer);
|
||||||
payload.Serialize(_buffer);
|
|
||||||
|
|
||||||
var payloadData = _serializer.GetWritableData(_buffer.Length);
|
|
||||||
_buffer.ToSpan(ref payloadData);
|
|
||||||
|
|
||||||
var sendData = _serializer.ToArray();
|
var sendData = _serializer.ToArray();
|
||||||
GameRoom.Send(player.PeerId, sendData, DeliveryType.Reliable);
|
Room.Send(player.PeerId, sendData, DeliveryType.Reliable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BroadcastEntityEvent(Entity entity, IRagonSerializable payload)
|
public void ReplicateEntityEvent(Entity entity, IRagonSerializable payload)
|
||||||
{
|
{
|
||||||
_serializer.Clear();
|
_serializer.Clear();
|
||||||
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
|
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
|
||||||
_serializer.WriteInt(entity.EntityId);
|
_serializer.WriteInt(entity.EntityId);
|
||||||
|
|
||||||
_buffer.Clear();
|
payload.Serialize(_serializer);
|
||||||
payload.Serialize(_buffer);
|
|
||||||
|
|
||||||
var payloadData = _serializer.GetWritableData(_buffer.Length);
|
|
||||||
_buffer.ToSpan(ref payloadData);
|
|
||||||
|
|
||||||
var sendData = _serializer.ToArray();
|
var sendData = _serializer.ToArray();
|
||||||
GameRoom.Broadcast(sendData);
|
Room.Broadcast(sendData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -264,11 +245,7 @@ namespace Ragon.Core
|
|||||||
public virtual void OnStop()
|
public virtual void OnStop()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnTick(float deltaTime)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,12 +11,12 @@ public class RoomManager
|
|||||||
private readonly IGameThread _gameThread;
|
private readonly IGameThread _gameThread;
|
||||||
private readonly PluginFactory _factory;
|
private readonly PluginFactory _factory;
|
||||||
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
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;
|
private readonly Dictionary<uint, GameRoom> _roomsBySocket;
|
||||||
|
|
||||||
public IReadOnlyDictionary<uint, GameRoom> RoomsBySocket => _roomsBySocket;
|
public IReadOnlyDictionary<uint, GameRoom> RoomsBySocket => _roomsBySocket;
|
||||||
public IReadOnlyList<GameRoom> Rooms => _rooms;
|
public IReadOnlyList<GameRoom> Rooms => _rooms;
|
||||||
|
|
||||||
public RoomManager(PluginFactory factory, IGameThread gameThread)
|
public RoomManager(PluginFactory factory, IGameThread gameThread)
|
||||||
{
|
{
|
||||||
_gameThread = gameThread;
|
_gameThread = gameThread;
|
||||||
@@ -26,41 +26,71 @@ public class RoomManager
|
|||||||
|
|
||||||
public void Join(Player player, string roomId, byte[] payload)
|
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)
|
if (_rooms.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (var existRoom in _rooms)
|
foreach (var existRoom in _rooms)
|
||||||
{
|
{
|
||||||
if (existRoom.Id == roomId && existRoom.PlayersCount < existRoom.PlayersMax)
|
if (existRoom.Id == roomId && existRoom.PlayersCount < existRoom.PlayersMax)
|
||||||
{
|
{
|
||||||
existRoom.Joined(player, payload);
|
existRoom.AddPlayer(player, payload);
|
||||||
_roomsBySocket.Add(player.PeerId, existRoom);
|
_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.AddPlayer(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)
|
if (_rooms.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (var existRoom in _rooms)
|
foreach (var existRoom in _rooms)
|
||||||
{
|
{
|
||||||
if (existRoom.Map == map && existRoom.PlayersCount < existRoom.PlayersMax)
|
if (existRoom.Map == map && existRoom.PlayersCount < existRoom.PlayersMax)
|
||||||
{
|
{
|
||||||
existRoom.Joined(player, payload);
|
_logger.Trace($"Player ({player.PlayerName}|{player.Id}) joined to room with Id {roomId}");
|
||||||
|
|
||||||
|
existRoom.AddPlayer(player, payload);
|
||||||
_roomsBySocket.Add(player.PeerId, existRoom);
|
_roomsBySocket.Add(player.PeerId, existRoom);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_logger.Trace($"Room not found for Player ({player.PlayerName}|{player.Id}), create room with Id {roomId} and params ({map}|{min}|{max})");
|
||||||
|
|
||||||
var plugin = _factory.CreatePlugin(map);
|
var plugin = _factory.CreatePlugin(map);
|
||||||
if (plugin == null)
|
if (plugin == null)
|
||||||
throw new NullReferenceException($"Plugin for map {map} is 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.AddPlayer(player, payload);
|
||||||
room.Start();
|
room.Start();
|
||||||
|
|
||||||
_roomsBySocket.Add(player.PeerId, room);
|
_roomsBySocket.Add(player.PeerId, room);
|
||||||
@@ -71,12 +101,16 @@ public class RoomManager
|
|||||||
{
|
{
|
||||||
if (_roomsBySocket.Remove(player.PeerId, out var room))
|
if (_roomsBySocket.Remove(player.PeerId, out var room))
|
||||||
{
|
{
|
||||||
room.Leave(player.PeerId);
|
_logger.Trace($"Player ({player.PlayerName}|{player.Id}) left room with Id {room.Id}");
|
||||||
|
room.RemovePlayer(player.PeerId);
|
||||||
if (room.PlayersCount < room.PlayersMin)
|
if (room.PlayersCount < room.PlayersMin)
|
||||||
{
|
{
|
||||||
|
_logger.Trace($"Room with Id {room.Id} destroyed");
|
||||||
room.Stop();
|
room.Stop();
|
||||||
_rooms.Remove(room);
|
_rooms.Remove(room);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_gameThread.Server.Send(player.PeerId, new byte[] {(byte) RagonOperation.LEAVE_ROOM}, DeliveryType.Reliable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,46 +1,64 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Timers;
|
||||||
using ENet;
|
using ENet;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
|
||||||
namespace Ragon.Core
|
namespace Ragon.Core
|
||||||
{
|
{
|
||||||
public enum Status
|
|
||||||
{
|
|
||||||
Stopped,
|
|
||||||
Listening,
|
|
||||||
Disconnecting,
|
|
||||||
Connecting,
|
|
||||||
Assigning,
|
|
||||||
Connected
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ENetServer : ISocketServer
|
public class ENetServer : ISocketServer
|
||||||
{
|
{
|
||||||
public Status Status { get; private set; }
|
|
||||||
|
|
||||||
private ILogger _logger = LogManager.GetCurrentClassLogger();
|
private ILogger _logger = LogManager.GetCurrentClassLogger();
|
||||||
private Host _host;
|
private Host _host;
|
||||||
|
private uint _protocol;
|
||||||
private Address _address;
|
private Address _address;
|
||||||
private Event _netEvent;
|
private Event _netEvent;
|
||||||
private Peer[] _peers;
|
private Peer[] _peers;
|
||||||
private IHandler _handler;
|
private IHandler _handler;
|
||||||
|
private Stopwatch _timer;
|
||||||
|
|
||||||
public ENetServer(IHandler handler)
|
public ENetServer(IHandler handler)
|
||||||
{
|
{
|
||||||
_handler = handler;
|
_handler = handler;
|
||||||
|
_timer = Stopwatch.StartNew();
|
||||||
|
_peers = Array.Empty<Peer>();
|
||||||
|
_host = new Host();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Start(ushort port, int connections)
|
public void Start(ushort port, int connections, uint protocol)
|
||||||
{
|
{
|
||||||
_address = default;
|
_address = default;
|
||||||
_address.Port = port;
|
_address.Port = port;
|
||||||
_peers = new Peer[connections];
|
_peers = new Peer[connections];
|
||||||
|
_protocol = protocol;
|
||||||
_host = new Host();
|
|
||||||
_host.Create(_address, connections, 2, 0, 0, 1024 * 1024);
|
_host.Create(_address, connections, 2, 0, 0, 1024 * 1024);
|
||||||
|
|
||||||
Status = Status.Listening;
|
|
||||||
|
var protocolDecoded = (protocol >> 16 & 0xFF) + "." + (protocol >> 8 & 0xFF) + "." + (protocol & 0xFF);
|
||||||
_logger.Info($"Network listening on {port}");
|
_logger.Info($"Network listening on {port}");
|
||||||
|
_logger.Info($"Protocol: {protocolDecoded}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Broadcast(uint[] peersIds, byte[] data, DeliveryType type)
|
||||||
|
{
|
||||||
|
var newPacket = new Packet();
|
||||||
|
var packetFlags = PacketFlags.Instant;
|
||||||
|
byte channel = 1;
|
||||||
|
|
||||||
|
if (type == DeliveryType.Reliable)
|
||||||
|
{
|
||||||
|
packetFlags = PacketFlags.Reliable;
|
||||||
|
channel = 0;
|
||||||
|
}
|
||||||
|
else if (type == DeliveryType.Unreliable)
|
||||||
|
{
|
||||||
|
channel = 1;
|
||||||
|
packetFlags = PacketFlags.UnreliableFragmented;
|
||||||
|
}
|
||||||
|
|
||||||
|
newPacket.Create(data, data.Length, packetFlags);
|
||||||
|
foreach (var peerId in peersIds)
|
||||||
|
_peers[peerId].Send(channel, ref newPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Send(uint peerId, byte[] data, DeliveryType type)
|
public void Send(uint peerId, byte[] data, DeliveryType type)
|
||||||
@@ -76,7 +94,7 @@ namespace Ragon.Core
|
|||||||
{
|
{
|
||||||
if (_host.CheckEvents(out _netEvent) <= 0)
|
if (_host.CheckEvents(out _netEvent) <= 0)
|
||||||
{
|
{
|
||||||
if (_host.Service(15, out _netEvent) <= 0)
|
if (_host.Service(0, out _netEvent) <= 0)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
polled = true;
|
polled = true;
|
||||||
@@ -85,11 +103,17 @@ namespace Ragon.Core
|
|||||||
switch (_netEvent.Type)
|
switch (_netEvent.Type)
|
||||||
{
|
{
|
||||||
case EventType.None:
|
case EventType.None:
|
||||||
Console.WriteLine("None event");
|
{
|
||||||
|
_logger.Trace("None event");
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case EventType.Connect:
|
case EventType.Connect:
|
||||||
{
|
{
|
||||||
|
// if (IsValidProtocol(_netEvent.Data))
|
||||||
|
// {
|
||||||
|
// _logger.Warn("Mismatched protocol, close connection");
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
_peers[_netEvent.Peer.ID] = _netEvent.Peer;
|
_peers[_netEvent.Peer.ID] = _netEvent.Peer;
|
||||||
_handler.OnEvent(_netEvent);
|
_handler.OnEvent(_netEvent);
|
||||||
break;
|
break;
|
||||||
@@ -118,5 +142,10 @@ namespace Ragon.Core
|
|||||||
{
|
{
|
||||||
_host?.Dispose();
|
_host?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsValidProtocol(uint protocol)
|
||||||
|
{
|
||||||
|
return protocol == _protocol;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,9 +2,10 @@ namespace Ragon.Core;
|
|||||||
|
|
||||||
public interface ISocketServer
|
public interface ISocketServer
|
||||||
{
|
{
|
||||||
public void Start(ushort port, int connections);
|
public void Start(ushort port, int connections, uint protocol);
|
||||||
public void Process();
|
public void Process();
|
||||||
public void Stop();
|
public void Stop();
|
||||||
public void Send(uint peerId, byte[] data, DeliveryType type);
|
public void Send(uint peerId, byte[] data, DeliveryType type);
|
||||||
|
public void Broadcast(uint[] peersIds, byte[] data, DeliveryType type);
|
||||||
public void Disconnect(uint peerId, uint errorCode);
|
public void Disconnect(uint peerId, uint errorCode);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ragon.Core;
|
||||||
|
|
||||||
|
public interface IScheduler
|
||||||
|
{
|
||||||
|
public SchedulerTask Schedule(Action<SchedulerTask> action, float interval, int count = 1);
|
||||||
|
public SchedulerTask ScheduleForever(Action<SchedulerTask> action, float interval);
|
||||||
|
public void StopSchedule(SchedulerTask schedulerTask);
|
||||||
|
|
||||||
|
public void Tick(float deltaTime);
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace Ragon.Core
|
||||||
|
{
|
||||||
|
public class Scheduler: IScheduler
|
||||||
|
{
|
||||||
|
List<SchedulerTask> _scheduledTasks;
|
||||||
|
|
||||||
|
public Scheduler(int defaultCapacity = 100)
|
||||||
|
{
|
||||||
|
_scheduledTasks = new List<SchedulerTask>(defaultCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SchedulerTask Schedule(Action<SchedulerTask> action, float interval, int count = 1)
|
||||||
|
{
|
||||||
|
var newTask = new SchedulerTask(action, interval, count - 1);
|
||||||
|
_scheduledTasks.Add(newTask);
|
||||||
|
return newTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SchedulerTask ScheduleForever(Action<SchedulerTask> action, float interval)
|
||||||
|
{
|
||||||
|
var newTask = new SchedulerTask(action, interval, -1);
|
||||||
|
_scheduledTasks.Add(newTask);
|
||||||
|
return newTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopSchedule(SchedulerTask schedulerTask)
|
||||||
|
{
|
||||||
|
if (_scheduledTasks.Contains(schedulerTask))
|
||||||
|
_scheduledTasks.Remove(schedulerTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Tick(float deltaTime)
|
||||||
|
{
|
||||||
|
for (int i = _scheduledTasks.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var scheduledTask = _scheduledTasks[i];
|
||||||
|
scheduledTask.Tick(deltaTime);
|
||||||
|
|
||||||
|
if (!scheduledTask.IsActive)
|
||||||
|
_scheduledTasks.Remove(scheduledTask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_scheduledTasks.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SchedulerTask
|
||||||
|
{
|
||||||
|
private Action<SchedulerTask> _action;
|
||||||
|
private float _timer = 0;
|
||||||
|
private float _interval = 0;
|
||||||
|
private int _repeats = 0;
|
||||||
|
private bool _active;
|
||||||
|
|
||||||
|
public int Repeats => _repeats;
|
||||||
|
public bool IsActive => _active;
|
||||||
|
|
||||||
|
public SchedulerTask(Action<SchedulerTask> task, float interval, int repeatCount = 0)
|
||||||
|
{
|
||||||
|
_action = task;
|
||||||
|
_interval = interval;
|
||||||
|
_timer = 0;
|
||||||
|
_active = true;
|
||||||
|
_repeats = repeatCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Tick(float deltaTime)
|
||||||
|
{
|
||||||
|
_timer += deltaTime;
|
||||||
|
if (_timer >= _interval)
|
||||||
|
{
|
||||||
|
_action.Invoke(this);
|
||||||
|
if (_repeats == -1)
|
||||||
|
{
|
||||||
|
_timer = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_repeats > 0)
|
||||||
|
{
|
||||||
|
_timer = 0;
|
||||||
|
_repeats--;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_repeats == 0)
|
||||||
|
_active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1,21 @@
|
|||||||
This program is free software: you can redistribute it and/or modify it under the terms of the Server Side Public License, version 1, as published by MongoDB, Inc. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Server Side Public License for more details. You should have received a copy of the Server Side Public License along with this program. If not, see http://www.mongodb.com/licensing/server-side-public-license.
|
Copyright (c) 2022 Eduard Kargin (theedison4@gmail.com)
|
||||||
|
|
||||||
|
MIT License:
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -14,23 +14,12 @@ Ragon is fully free, small and high perfomance room based game server with plugi
|
|||||||
- Effective
|
- Effective
|
||||||
- Free
|
- Free
|
||||||
- Simple matchmaking
|
- Simple matchmaking
|
||||||
- Flexiable API
|
|
||||||
- Room based architecture
|
- Room based architecture
|
||||||
- Extendable room logic via plugin
|
- Сustomizable authorization
|
||||||
- Custom authorization
|
- Сustomizable server-side logic via plugins with flexible API
|
||||||
- No CCU limitations*
|
- No CCU limitations*
|
||||||
- Engine agnostic
|
- No Room count limitations
|
||||||
- Support any client architecture (MonoBehaviors, ECS)
|
- Reliable UDP
|
||||||
- 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
|
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
- OSX, Windows, Linux(Ubuntu, Debian)
|
- 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]
|
* NetStack [latest]
|
||||||
|
|
||||||
### License
|
### License
|
||||||
SSPL-1.0
|
MIT
|
||||||
|
|
||||||
### Tips
|
### Tips
|
||||||
\* Limited to 4095 CCU by library ENet-Sharp
|
\* Limited to 4095 CCU by library ENet-Sharp
|
||||||
|
|||||||
Reference in New Issue
Block a user