CHANGED TO MIRROR

This commit is contained in:
DerTyp187
2021-10-25 09:20:01 +02:00
parent bd712107b7
commit e509a919b6
611 changed files with 38291 additions and 1216 deletions

View File

@@ -0,0 +1,12 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Mirror.Tests.Common")]
[assembly: InternalsVisibleTo("Mirror.Tests")]
// need to use Unity.*.CodeGen assembly name to import Unity.CompilationPipeline
// for ILPostProcessor tests.
[assembly: InternalsVisibleTo("Unity.Mirror.Tests.CodeGen")]
[assembly: InternalsVisibleTo("Mirror.Tests.Generated")]
[assembly: InternalsVisibleTo("Mirror.Tests.Runtime")]
[assembly: InternalsVisibleTo("Mirror.Tests.Performance.Editor")]
[assembly: InternalsVisibleTo("Mirror.Tests.Performance.Runtime")]
[assembly: InternalsVisibleTo("Mirror.Editor")]

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e28d5f410e25b42e6a76a2ffc10e4675
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,85 @@
using System;
using UnityEngine;
namespace Mirror
{
/// <summary>
/// SyncVars are used to synchronize a variable from the server to all clients automatically.
/// <para>Value must be changed on server, not directly by clients. Hook parameter allows you to define a client-side method to be invoked when the client gets an update from the server.</para>
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class SyncVarAttribute : PropertyAttribute
{
public string hook;
}
/// <summary>
/// Call this from a client to run this function on the server.
/// <para>Make sure to validate input etc. It's not possible to call this from a server.</para>
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class CommandAttribute : Attribute
{
public int channel = Channels.Reliable;
public bool requiresAuthority = true;
}
/// <summary>
/// The server uses a Remote Procedure Call (RPC) to run this function on clients.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class ClientRpcAttribute : Attribute
{
public int channel = Channels.Reliable;
public bool includeOwner = true;
}
/// <summary>
/// The server uses a Remote Procedure Call (RPC) to run this function on a specific client.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class TargetRpcAttribute : Attribute
{
public int channel = Channels.Reliable;
}
/// <summary>
/// Prevents clients from running this method.
/// <para>Prints a warning if a client tries to execute this method.</para>
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class ServerAttribute : Attribute {}
/// <summary>
/// Prevents clients from running this method.
/// <para>No warning is thrown.</para>
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class ServerCallbackAttribute : Attribute {}
/// <summary>
/// Prevents the server from running this method.
/// <para>Prints a warning if the server tries to execute this method.</para>
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class ClientAttribute : Attribute {}
/// <summary>
/// Prevents the server from running this method.
/// <para>No warning is printed.</para>
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class ClientCallbackAttribute : Attribute {}
/// <summary>
/// Converts a string property into a Scene property in the inspector
/// </summary>
public class SceneAttribute : PropertyAttribute {}
/// <summary>
/// Used to show private SyncList in the inspector,
/// <para> Use instead of SerializeField for non Serializable types </para>
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class ShowInInspectorAttribute : Attribute {}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c04c722ee2ffd49c8a56ab33667b10b0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1c38e1bebe9947f8b842a8a57aa2b71c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,97 @@
// batching functionality encapsulated into one class.
// -> less complexity
// -> easy to test
//
// IMPORTANT: we use THRESHOLD batching, not MAXED SIZE batching.
// see threshold comments below.
//
// includes timestamp for tick batching.
// -> allows NetworkTransform etc. to use timestamp without including it in
// every single message
using System;
using System.Collections.Generic;
namespace Mirror
{
public class Batcher
{
// batching threshold instead of max size.
// -> small messages are fit into threshold sized batches
// -> messages larger than threshold are single batches
//
// in other words, we fit up to 'threshold' but still allow larger ones
// for two reasons:
// 1.) data races: skipping batching for larger messages would send a
// large spawn message immediately, while others are batched and
// only flushed at the end of the frame
// 2) timestamp batching: if each batch is expected to contain a
// timestamp, then large messages have to be a batch too. otherwise
// they would not contain a timestamp
readonly int threshold;
// TimeStamp header size for those who need it
public const int HeaderSize = sizeof(double);
// batched messages
// IMPORTANT: we queue the serialized messages!
// queueing NetworkMessage would box and allocate!
Queue<PooledNetworkWriter> messages = new Queue<PooledNetworkWriter>();
public Batcher(int threshold)
{
this.threshold = threshold;
}
// add a message for batching
// we allow any sized messages.
// caller needs to make sure they are within max packet size.
public void AddMessage(ArraySegment<byte> message)
{
// put into a (pooled) writer
// -> WriteBytes instead of WriteSegment because the latter
// would add a size header. we want to write directly.
// -> will be returned to pool when making the batch!
// IMPORTANT: NOT adding a size header / msg saves LOTS of bandwidth
PooledNetworkWriter writer = NetworkWriterPool.GetWriter();
writer.WriteBytes(message.Array, message.Offset, message.Count);
messages.Enqueue(writer);
}
// batch as many messages as possible into writer
// returns true if any batch was made.
public bool MakeNextBatch(NetworkWriter writer, double timeStamp)
{
// if we have no messages then there's nothing to do
if (messages.Count == 0)
return false;
// make sure the writer is fresh to avoid uncertain situations
if (writer.Position != 0)
throw new ArgumentException($"MakeNextBatch needs a fresh writer!");
// write timestamp first
// -> double precision for accuracy over long periods of time
writer.WriteDouble(timeStamp);
// do start no matter what
do
{
// add next message no matter what. even if > threshold.
// (we do allow > threshold sized messages as single batch)
PooledNetworkWriter message = messages.Dequeue();
ArraySegment<byte> segment = message.ToArraySegment();
writer.WriteBytes(segment.Array, segment.Offset, segment.Count);
// return the writer to pool
NetworkWriterPool.Recycle(message);
}
// keep going as long as we have more messages,
// AND the next one would fit into threshold.
while (messages.Count > 0 &&
writer.Position + messages.Peek().Position <= threshold);
// we had messages, so a batch was made
return true;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0afaaa611a2142d48a07bdd03b68b2b3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,142 @@
// un-batching functionality encapsulated into one class.
// -> less complexity
// -> easy to test
//
// includes timestamp for tick batching.
// -> allows NetworkTransform etc. to use timestamp without including it in
// every single message
using System;
using System.Collections.Generic;
namespace Mirror
{
public class Unbatcher
{
// supporting adding multiple batches before GetNextMessage is called.
// just in case.
Queue<PooledNetworkWriter> batches = new Queue<PooledNetworkWriter>();
public int BatchesCount => batches.Count;
// NetworkReader is only created once,
// then pointed to the first batch.
NetworkReader reader = new NetworkReader(new byte[0]);
// timestamp that was written into the batch remotely.
// for the batch that our reader is currently pointed at.
double readerRemoteTimeStamp;
// helper function to start reading a batch.
void StartReadingBatch(PooledNetworkWriter batch)
{
// point reader to it
reader.SetBuffer(batch.ToArraySegment());
// read remote timestamp (double)
// -> AddBatch quarantees that we have at least 8 bytes to read
readerRemoteTimeStamp = reader.ReadDouble();
}
// add a new batch.
// returns true if valid.
// returns false if not, in which case the connection should be disconnected.
public bool AddBatch(ArraySegment<byte> batch)
{
// IMPORTANT: ArraySegment is only valid until returning. we copy it!
//
// NOTE: it's not possible to create empty ArraySegments, so we
// don't need to check against that.
// make sure we have at least 8 bytes to read for tick timestamp
if (batch.Count < Batcher.HeaderSize)
return false;
// put into a (pooled) writer
// -> WriteBytes instead of WriteSegment because the latter
// would add a size header. we want to write directly.
// -> will be returned to pool when sending!
PooledNetworkWriter writer = NetworkWriterPool.GetWriter();
writer.WriteBytes(batch.Array, batch.Offset, batch.Count);
// first batch? then point reader there
if (batches.Count == 0)
StartReadingBatch(writer);
// add batch
batches.Enqueue(writer);
//Debug.Log($"Adding Batch {BitConverter.ToString(batch.Array, batch.Offset, batch.Count)} => batches={batches.Count} reader={reader}");
return true;
}
// get next message, unpacked from batch (if any)
// timestamp is the REMOTE time when the batch was created remotely.
public bool GetNextMessage(out NetworkReader message, out double remoteTimeStamp)
{
// getting messages would be easy via
// <<size, message, size, message, ...>>
// but to save A LOT of bandwidth, we use
// <<message, message, ...>
// in other words, we don't know where the current message ends
//
// BUT: it doesn't matter!
// -> we simply return the reader
// * if we have one yet
// * and if there's more to read
// -> the caller can then read one message from it
// -> when the end is reached, we retire the batch!
//
// for example:
// while (GetNextMessage(out message))
// ProcessMessage(message);
//
message = null;
// do nothing if we don't have any batches.
// otherwise the below queue.Dequeue() would throw an
// InvalidOperationException if operating on empty queue.
if (batches.Count == 0)
{
remoteTimeStamp = 0;
return false;
}
// was our reader pointed to anything yet?
if (reader.Length == 0)
{
remoteTimeStamp = 0;
return false;
}
// no more data to read?
if (reader.Remaining == 0)
{
// retire the batch
PooledNetworkWriter writer = batches.Dequeue();
NetworkWriterPool.Recycle(writer);
// do we have another batch?
if (batches.Count > 0)
{
// point reader to the next batch.
// we'll return the reader below.
PooledNetworkWriter next = batches.Peek();
StartReadingBatch(next);
}
// otherwise there's nothing more to read
else
{
remoteTimeStamp = 0;
return false;
}
}
// use the current batch's remote timestamp
// AFTER potentially moving to the next batch ABOVE!
remoteTimeStamp = readerRemoteTimeStamp;
// if we got here, then we have more data to read.
message = reader;
return true;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 328562d71e1c45c58581b958845aa7a4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,357 @@
// Quaternion compression from DOTSNET
using System;
using UnityEngine;
namespace Mirror
{
/// <summary>Functions to Compress Quaternions and Floats</summary>
public static class Compression
{
// quaternion compression //////////////////////////////////////////////
// smallest three: https://gafferongames.com/post/snapshot_compression/
// compresses 16 bytes quaternion into 4 bytes
// helper function to find largest absolute element
// returns the index of the largest one
public static int LargestAbsoluteComponentIndex(Vector4 value, out float largestAbs, out Vector3 withoutLargest)
{
// convert to abs
Vector4 abs = new Vector4(Mathf.Abs(value.x), Mathf.Abs(value.y), Mathf.Abs(value.z), Mathf.Abs(value.w));
// set largest to first abs (x)
largestAbs = abs.x;
withoutLargest = new Vector3(value.y, value.z, value.w);
int largestIndex = 0;
// compare to the others, starting at second value
// performance for 100k calls
// for-loop: 25ms
// manual checks: 22ms
if (abs.y > largestAbs)
{
largestIndex = 1;
largestAbs = abs.y;
withoutLargest = new Vector3(value.x, value.z, value.w);
}
if (abs.z > largestAbs)
{
largestIndex = 2;
largestAbs = abs.z;
withoutLargest = new Vector3(value.x, value.y, value.w);
}
if (abs.w > largestAbs)
{
largestIndex = 3;
largestAbs = abs.w;
withoutLargest = new Vector3(value.x, value.y, value.z);
}
return largestIndex;
}
// scale a float within min/max range to an ushort between min/max range
// note: can also use this for byte range from byte.MinValue to byte.MaxValue
public static ushort ScaleFloatToUShort(float value, float minValue, float maxValue, ushort minTarget, ushort maxTarget)
{
// note: C# ushort - ushort => int, hence so many casts
// max ushort - min ushort only fits into something bigger
int targetRange = maxTarget - minTarget;
float valueRange = maxValue - minValue;
float valueRelative = value - minValue;
return (ushort)(minTarget + (ushort)(valueRelative / valueRange * targetRange));
}
// scale an ushort within min/max range to a float between min/max range
// note: can also use this for byte range from byte.MinValue to byte.MaxValue
public static float ScaleUShortToFloat(ushort value, ushort minValue, ushort maxValue, float minTarget, float maxTarget)
{
// note: C# ushort - ushort => int, hence so many casts
float targetRange = maxTarget - minTarget;
ushort valueRange = (ushort)(maxValue - minValue);
ushort valueRelative = (ushort)(value - minValue);
return minTarget + (valueRelative / (float)valueRange * targetRange);
}
const float QuaternionMinRange = -0.707107f;
const float QuaternionMaxRange = 0.707107f;
const ushort TenBitsMax = 0x3FF;
// helper function to access 'nth' component of quaternion
static float QuaternionElement(Quaternion q, int element)
{
switch (element)
{
case 0: return q.x;
case 1: return q.y;
case 2: return q.z;
case 3: return q.w;
default: return 0;
}
}
// note: assumes normalized quaternions
public static uint CompressQuaternion(Quaternion q)
{
// note: assuming normalized quaternions is enough. no need to force
// normalize here. we already normalize when decompressing.
// find the largest component index [0,3] + value
int largestIndex = LargestAbsoluteComponentIndex(new Vector4(q.x, q.y, q.z, q.w), out float _, out Vector3 withoutLargest);
// from here on, we work with the 3 components without largest!
// "You might think you need to send a sign bit for [largest] in
// case it is negative, but you dont, because you can make
// [largest] always positive by negating the entire quaternion if
// [largest] is negative. in quaternion space (x,y,z,w) and
// (-x,-y,-z,-w) represent the same rotation."
if (QuaternionElement(q, largestIndex) < 0)
withoutLargest = -withoutLargest;
// put index & three floats into one integer.
// => index is 2 bits (4 values require 2 bits to store them)
// => the three floats are between [-0.707107,+0.707107] because:
// "If v is the absolute value of the largest quaternion
// component, the next largest possible component value occurs
// when two components have the same absolute value and the
// other two components are zero. The length of that quaternion
// (v,v,0,0) is 1, therefore v^2 + v^2 = 1, 2v^2 = 1,
// v = 1/sqrt(2). This means you can encode the smallest three
// components in [-0.707107,+0.707107] instead of [-1,+1] giving
// you more precision with the same number of bits."
// => the article recommends storing each float in 9 bits
// => our uint has 32 bits, so we might as well store in (32-2)/3=10
// 10 bits max value: 1023=0x3FF (use OSX calc to flip 10 bits)
ushort aScaled = ScaleFloatToUShort(withoutLargest.x, QuaternionMinRange, QuaternionMaxRange, 0, TenBitsMax);
ushort bScaled = ScaleFloatToUShort(withoutLargest.y, QuaternionMinRange, QuaternionMaxRange, 0, TenBitsMax);
ushort cScaled = ScaleFloatToUShort(withoutLargest.z, QuaternionMinRange, QuaternionMaxRange, 0, TenBitsMax);
// now we just need to pack them into one integer
// -> index is 2 bit and needs to be shifted to 31..32
// -> a is 10 bit and needs to be shifted 20..30
// -> b is 10 bit and needs to be shifted 10..20
// -> c is 10 bit and needs to be at 0..10
return (uint)(largestIndex << 30 | aScaled << 20 | bScaled << 10 | cScaled);
}
// Quaternion normalizeSAFE from ECS math.normalizesafe()
// => useful to produce valid quaternions even if client sends invalid
// data
static Quaternion QuaternionNormalizeSafe(Quaternion value)
{
// The smallest positive normal number representable in a float.
const float FLT_MIN_NORMAL = 1.175494351e-38F;
Vector4 v = new Vector4(value.x, value.y, value.z, value.w);
float length = Vector4.Dot(v, v);
return length > FLT_MIN_NORMAL
? value.normalized
: Quaternion.identity;
}
// note: gives normalized quaternions
public static Quaternion DecompressQuaternion(uint data)
{
// get cScaled which is at 0..10 and ignore the rest
ushort cScaled = (ushort)(data & TenBitsMax);
// get bScaled which is at 10..20 and ignore the rest
ushort bScaled = (ushort)((data >> 10) & TenBitsMax);
// get aScaled which is at 20..30 and ignore the rest
ushort aScaled = (ushort)((data >> 20) & TenBitsMax);
// get 2 bit largest index, which is at 31..32
int largestIndex = (int)(data >> 30);
// scale back to floats
float a = ScaleUShortToFloat(aScaled, 0, TenBitsMax, QuaternionMinRange, QuaternionMaxRange);
float b = ScaleUShortToFloat(bScaled, 0, TenBitsMax, QuaternionMinRange, QuaternionMaxRange);
float c = ScaleUShortToFloat(cScaled, 0, TenBitsMax, QuaternionMinRange, QuaternionMaxRange);
// calculate the omitted component based on a²+b²+c²+d²=1
float d = Mathf.Sqrt(1 - a*a - b*b - c*c);
// reconstruct based on largest index
Vector4 value;
switch (largestIndex)
{
case 0: value = new Vector4(d, a, b, c); break;
case 1: value = new Vector4(a, d, b, c); break;
case 2: value = new Vector4(a, b, d, c); break;
default: value = new Vector4(a, b, c, d); break;
}
// ECS Rotation only works with normalized quaternions.
// make sure that's always the case here to avoid ECS bugs where
// everything stops moving if the quaternion isn't normalized.
// => NormalizeSafe returns a normalized quaternion even if we pass
// in NaN from deserializing invalid values!
return QuaternionNormalizeSafe(new Quaternion(value.x, value.y, value.z, value.w));
}
// varint compression //////////////////////////////////////////////////
// compress ulong varint.
// same result for int, short and byte. only need one function.
// NOT an extension. otherwise weaver might accidentally use it.
public static void CompressVarUInt(NetworkWriter writer, ulong value)
{
if (value <= 240)
{
writer.Write((byte)value);
return;
}
if (value <= 2287)
{
writer.Write((byte)(((value - 240) >> 8) + 241));
writer.Write((byte)((value - 240) & 0xFF));
return;
}
if (value <= 67823)
{
writer.Write((byte)249);
writer.Write((byte)((value - 2288) >> 8));
writer.Write((byte)((value - 2288) & 0xFF));
return;
}
if (value <= 16777215)
{
writer.Write((byte)250);
writer.Write((byte)(value & 0xFF));
writer.Write((byte)((value >> 8) & 0xFF));
writer.Write((byte)((value >> 16) & 0xFF));
return;
}
if (value <= 4294967295)
{
writer.Write((byte)251);
writer.Write((byte)(value & 0xFF));
writer.Write((byte)((value >> 8) & 0xFF));
writer.Write((byte)((value >> 16) & 0xFF));
writer.Write((byte)((value >> 24) & 0xFF));
return;
}
if (value <= 1099511627775)
{
writer.Write((byte)252);
writer.Write((byte)(value & 0xFF));
writer.Write((byte)((value >> 8) & 0xFF));
writer.Write((byte)((value >> 16) & 0xFF));
writer.Write((byte)((value >> 24) & 0xFF));
writer.Write((byte)((value >> 32) & 0xFF));
return;
}
if (value <= 281474976710655)
{
writer.Write((byte)253);
writer.Write((byte)(value & 0xFF));
writer.Write((byte)((value >> 8) & 0xFF));
writer.Write((byte)((value >> 16) & 0xFF));
writer.Write((byte)((value >> 24) & 0xFF));
writer.Write((byte)((value >> 32) & 0xFF));
writer.Write((byte)((value >> 40) & 0xFF));
return;
}
if (value <= 72057594037927935)
{
writer.Write((byte)254);
writer.Write((byte)(value & 0xFF));
writer.Write((byte)((value >> 8) & 0xFF));
writer.Write((byte)((value >> 16) & 0xFF));
writer.Write((byte)((value >> 24) & 0xFF));
writer.Write((byte)((value >> 32) & 0xFF));
writer.Write((byte)((value >> 40) & 0xFF));
writer.Write((byte)((value >> 48) & 0xFF));
return;
}
// all others
{
writer.Write((byte)255);
writer.Write((byte)(value & 0xFF));
writer.Write((byte)((value >> 8) & 0xFF));
writer.Write((byte)((value >> 16) & 0xFF));
writer.Write((byte)((value >> 24) & 0xFF));
writer.Write((byte)((value >> 32) & 0xFF));
writer.Write((byte)((value >> 40) & 0xFF));
writer.Write((byte)((value >> 48) & 0xFF));
writer.Write((byte)((value >> 56) & 0xFF));
}
}
// zigzag encoding https://gist.github.com/mfuerstenau/ba870a29e16536fdbaba
public static void CompressVarInt(NetworkWriter writer, long i)
{
ulong zigzagged = (ulong)((i >> 63) ^ (i << 1));
CompressVarUInt(writer, zigzagged);
}
// NOT an extension. otherwise weaver might accidentally use it.
public static ulong DecompressVarUInt(NetworkReader reader)
{
byte a0 = reader.ReadByte();
if (a0 < 241)
{
return a0;
}
byte a1 = reader.ReadByte();
if (a0 >= 241 && a0 <= 248)
{
return 240 + ((a0 - (ulong)241) << 8) + a1;
}
byte a2 = reader.ReadByte();
if (a0 == 249)
{
return 2288 + ((ulong)a1 << 8) + a2;
}
byte a3 = reader.ReadByte();
if (a0 == 250)
{
return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16);
}
byte a4 = reader.ReadByte();
if (a0 == 251)
{
return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16) + (((ulong)a4) << 24);
}
byte a5 = reader.ReadByte();
if (a0 == 252)
{
return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16) + (((ulong)a4) << 24) + (((ulong)a5) << 32);
}
byte a6 = reader.ReadByte();
if (a0 == 253)
{
return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16) + (((ulong)a4) << 24) + (((ulong)a5) << 32) + (((ulong)a6) << 40);
}
byte a7 = reader.ReadByte();
if (a0 == 254)
{
return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16) + (((ulong)a4) << 24) + (((ulong)a5) << 32) + (((ulong)a6) << 40) + (((ulong)a7) << 48);
}
byte a8 = reader.ReadByte();
if (a0 == 255)
{
return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16) + (((ulong)a4) << 24) + (((ulong)a5) << 32) + (((ulong)a6) << 40) + (((ulong)a7) << 48) + (((ulong)a8) << 56);
}
throw new IndexOutOfRangeException($"DecompressVarInt failure: {a0}");
}
// zigzag decoding https://gist.github.com/mfuerstenau/ba870a29e16536fdbaba
public static long DecompressVarInt(NetworkReader reader)
{
ulong data = DecompressVarUInt(reader);
return ((long)(data >> 1)) ^ -((long)data & 1);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5c28963f9c4b97e418252a55500fb91e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a99666a026b14cf6ba1a2b65946b1b27
timeCreated: 1615288671

View File

@@ -0,0 +1 @@
// moved into NetworkClient on 2021-03-07

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 96fc7967f813e4960b9119d7c2118494
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 73a9bb2dacafa8141bce8feef34e33a7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8bdb99a29e179d14cb0acc43f175d9ad
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1f6e5d5acb5879f45a2235ae0f44dc92
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b4e9cc0829b13e54594a80883836bda7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9cc796972dc396a42ba3686bd952e329
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 70f563b7a7210ae43bbcde5cb7721a94
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c7c472a3ea1bc4348bd5a0b05bf7cc3b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 97501e783fc67a4459b15d10e6c63563
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 43472c60a7c72e54eafe559290dd0fc6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b80b95532a9d6e8418aa676a261e4f69
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 05185b973ba389a4588fc8a99c75a4f6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dbabb497385c20346a3c8bda4ae69508
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0688c0fdae5376e4ea74d5c3904eed17
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6f0311899162c5b49a3c11fa9bd9c133
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b6838f9df45594d48873518cbb75b329
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d49649fb32cb96b46b10f013b38a4b50
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a963606335eae0f47abe7ecb5fd028ea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 675f0d0fd4e82b04290c4d30c8d78ede
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 457ba2df6cb6e1542996c17c715ee81b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 95bebb8e810e2954485291a26324f7d5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 068feff770f710141afa4a90063a5e6c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2b6cfd54b79bb464dbc6ae7f331ed45f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 07d1ea5260bc06e4d831c4b61d494bff
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 76dab753e7255254687cd57985d8d675
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cfaa626443cc7c94eae138a2e3a04d7c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bfc354d4a7f63ca45a653bf5d479afa0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ed11184fcffcdc04c9850d82c8014926
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c67eda1b451338a428df87fda1e3a7c9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-02-16

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b307f850ccbbe450295acf24d70e5c28
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-05-13

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 330c9aab13d2d42069c6ebbe582b73ca
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-02-16

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 353c7c9e14e82f349b1679112050b196
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-03-08

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f6928b080072948f7b2909b4025fcc79
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 63d647500ca1bfa4a845bc1f4cff9dcc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-02-16

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2a9618569c20a504aa86feb5913c70e9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-02-16

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a39aa1e48aa54eb4e964f0191c1dcdce
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-02-16

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d06522432d5a44e1587967a4731cd279
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,2 @@
// removed 2021-02-16

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 633889a39717fde4fa28dd6b948dfac7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-02-16

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c7627623f2b9fad4484082517cd73e67
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-02-16

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ac6e8eccf4b6f4dc7b24c276ef47fde8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
// removed 2021-02-16

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 733f020f9b76d453da841089579fd7a7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,37 @@
namespace Mirror
{
// implementation of N-day EMA
// it calculates an exponential moving average roughly equivalent to the last n observations
// https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
public class ExponentialMovingAverage
{
readonly float alpha;
bool initialized;
public double Value { get; private set; }
public double Var { get; private set; }
public ExponentialMovingAverage(int n)
{
// standard N-day EMA alpha calculation
alpha = 2.0f / (n + 1);
}
public void Add(double newValue)
{
// simple algorithm for EMA described here:
// https://en.wikipedia.org/wiki/Moving_average#Exponentially_weighted_moving_variance_and_standard_deviation
if (initialized)
{
double delta = newValue - Value;
Value += alpha * delta;
Var = (1 - alpha) * (Var + alpha * delta * delta);
}
else
{
Value = newValue;
initialized = true;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 05e858cbaa54b4ce4a48c8c7f50c1914
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
namespace Mirror
{
public static class Extensions
{
// string.GetHashCode is not guaranteed to be the same on all machines, but
// we need one that is the same on all machines. simple and stupid:
public static int GetStableHashCode(this string text)
{
unchecked
{
int hash = 23;
foreach (char c in text)
hash = hash * 31 + c;
return hash;
}
}
// previously in DotnetCompatibility.cs
// leftover from the UNET days. supposedly for windows store?
internal static string GetMethodName(this Delegate func)
{
#if NETFX_CORE
return func.GetMethodInfo().Name;
#else
return func.Method.Name;
#endif
}
// helper function to copy to List<T>
// C# only provides CopyTo(T[])
public static void CopyTo<T>(this IEnumerable<T> source, List<T> destination)
{
// foreach allocates. use AddRange.
destination.AddRange(source);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: decf32fd053744d18f35712b7a6f5116
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,89 @@
// interest management component for custom solutions like
// distance based, spatial hashing, raycast based, etc.
using System.Collections.Generic;
using UnityEngine;
namespace Mirror
{
[DisallowMultipleComponent]
public abstract class InterestManagement : MonoBehaviour
{
// Awake configures InterestManagement in NetworkServer/Client
void Awake()
{
if (NetworkServer.aoi == null)
{
NetworkServer.aoi = this;
}
else Debug.LogError($"Only one InterestManagement component allowed. {NetworkServer.aoi.GetType()} has been set up already.");
if (NetworkClient.aoi == null)
{
NetworkClient.aoi = this;
}
else Debug.LogError($"Only one InterestManagement component allowed. {NetworkClient.aoi.GetType()} has been set up already.");
}
// Callback used by the visibility system to determine if an observer
// (player) can see the NetworkIdentity. If this function returns true,
// the network connection will be added as an observer.
// conn: Network connection of a player.
// returns True if the player can see this object.
public abstract bool OnCheckObserver(NetworkIdentity identity, NetworkConnection newObserver);
// rebuild observers for the given NetworkIdentity.
// Server will automatically spawn/despawn added/removed ones.
// newObservers: cached hashset to put the result into
// initialize: true if being rebuilt for the first time
//
// IMPORTANT:
// => global rebuild would be more simple, BUT
// => local rebuild is way faster for spawn/despawn because we can
// simply rebuild a select NetworkIdentity only
// => having both .observers and .observing is necessary for local
// rebuilds
//
// in other words, this is the perfect solution even though it's not
// completely simple (due to .observers & .observing).
//
// Mirror maintains .observing automatically in the background. best of
// both worlds without any worrying now!
public abstract void OnRebuildObservers(NetworkIdentity identity, HashSet<NetworkConnection> newObservers, bool initialize);
// helper function to trigger a full rebuild.
// most implementations should call this in a certain interval.
// some might call this all the time, or only on team changes or
// scene changes and so on.
//
// IMPORTANT: check if NetworkServer.active when using Update()!
protected void RebuildAll()
{
foreach (NetworkIdentity identity in NetworkServer.spawned.Values)
{
NetworkServer.RebuildObservers(identity, false);
}
}
// Callback used by the visibility system for objects on a host.
// Objects on a host (with a local client) cannot be disabled or
// destroyed when they are not visible to the local client. So this
// function is called to allow custom code to hide these objects. A
// typical implementation will disable renderer components on the
// object. This is only called on local clients on a host.
// => need the function in here and virtual so people can overwrite!
// => not everyone wants to hide renderers!
public virtual void SetHostVisibility(NetworkIdentity identity, bool visible)
{
foreach (Renderer rend in identity.GetComponentsInChildren<Renderer>())
rend.enabled = visible;
}
/// <summary>Called on the server when a new networked object is spawned.</summary>
// (useful for 'only rebuild if changed' interest management algorithms)
public virtual void OnSpawned(NetworkIdentity identity) {}
/// <summary>Called on the server when a networked object is destroyed.</summary>
// (useful for 'only rebuild if changed' interest management algorithms)
public virtual void OnDestroyed(NetworkIdentity identity) {}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 41d809934003479f97e992eebb7ed6af
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,169 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Mirror
{
// a server's connection TO a LocalClient.
// sending messages on this connection causes the client's handler function to be invoked directly
public class LocalConnectionToClient : NetworkConnectionToClient
{
internal LocalConnectionToServer connectionToServer;
public LocalConnectionToClient() : base(LocalConnectionId) {}
public override string address => "localhost";
// Send stage two: serialized NetworkMessage as ArraySegment<byte>
internal override void Send(ArraySegment<byte> segment, int channelId = Channels.Reliable)
{
// get a writer to copy the message into since the segment is only
// valid until returning.
// => pooled writer will be returned to pool when dequeuing.
// => WriteBytes instead of WriteArraySegment because the latter
// includes a 4 bytes header. we just want to write raw.
//Debug.Log($"Enqueue {BitConverter.ToString(segment.Array, segment.Offset, segment.Count)}");
PooledNetworkWriter writer = NetworkWriterPool.GetWriter();
writer.WriteBytes(segment.Array, segment.Offset, segment.Count);
connectionToServer.queue.Enqueue(writer);
}
// true because local connections never timeout
internal override bool IsAlive(float timeout) => true;
internal void DisconnectInternal()
{
// set not ready and handle clientscene disconnect in any case
// (might be client or host mode here)
isReady = false;
RemoveFromObservingsObservers();
}
/// <summary>Disconnects this connection.</summary>
public override void Disconnect()
{
DisconnectInternal();
connectionToServer.DisconnectInternal();
}
}
// a localClient's connection TO a server.
// send messages on this connection causes the server's handler function to be invoked directly.
public class LocalConnectionToServer : NetworkConnectionToServer
{
internal LocalConnectionToClient connectionToClient;
// packet queue
internal readonly Queue<PooledNetworkWriter> queue = new Queue<PooledNetworkWriter>();
public override string address => "localhost";
// see caller for comments on why we need this
bool connectedEventPending;
bool disconnectedEventPending;
internal void QueueConnectedEvent() => connectedEventPending = true;
internal void QueueDisconnectedEvent() => disconnectedEventPending = true;
// Send stage two: serialized NetworkMessage as ArraySegment<byte>
internal override void Send(ArraySegment<byte> segment, int channelId = Channels.Reliable)
{
if (segment.Count == 0)
{
Debug.LogError("LocalConnection.SendBytes cannot send zero bytes");
return;
}
// OnTransportData assumes batching.
// so let's make a batch with proper timestamp prefix.
Batcher batcher = GetBatchForChannelId(channelId);
batcher.AddMessage(segment);
// flush it to the server's OnTransportData immediately.
// local connection to server always invokes immediately.
using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter())
{
// make a batch with our local time (double precision)
if (batcher.MakeNextBatch(writer, NetworkTime.localTime))
{
NetworkServer.OnTransportData(connectionId, writer.ToArraySegment(), channelId);
}
else Debug.LogError("Local connection failed to make batch. This should never happen.");
}
}
internal override void Update()
{
base.Update();
// should we still process a connected event?
if (connectedEventPending)
{
connectedEventPending = false;
NetworkClient.OnConnectedEvent?.Invoke();
}
// process internal messages so they are applied at the correct time
while (queue.Count > 0)
{
// call receive on queued writer's content, return to pool
PooledNetworkWriter writer = queue.Dequeue();
ArraySegment<byte> message = writer.ToArraySegment();
// OnTransportData assumes a proper batch with timestamp etc.
// let's make a proper batch and pass it to OnTransportData.
Batcher batcher = GetBatchForChannelId(Channels.Reliable);
batcher.AddMessage(message);
using (PooledNetworkWriter batchWriter = NetworkWriterPool.GetWriter())
{
// make a batch with our local time (double precision)
if (batcher.MakeNextBatch(batchWriter, NetworkTime.localTime))
{
NetworkClient.OnTransportData(batchWriter.ToArraySegment(), Channels.Reliable);
}
}
NetworkWriterPool.Recycle(writer);
}
// should we still process a disconnected event?
if (disconnectedEventPending)
{
disconnectedEventPending = false;
NetworkClient.OnDisconnectedEvent?.Invoke();
}
}
/// <summary>Disconnects this connection.</summary>
internal void DisconnectInternal()
{
// set not ready and handle clientscene disconnect in any case
// (might be client or host mode here)
// TODO remove redundant state. have one source of truth for .ready!
isReady = false;
NetworkClient.ready = false;
}
/// <summary>Disconnects this connection.</summary>
public override void Disconnect()
{
connectionToClient.DisconnectInternal();
DisconnectInternal();
// simulate what a true remote connection would do:
// first, the server should remove it:
// TODO should probably be in connectionToClient.DisconnectInternal
// because that's the NetworkServer's connection!
NetworkServer.RemoveLocalConnection();
// then call OnTransportDisconnected for proper disconnect handling,
// callbacks & cleanups.
// => otherwise OnClientDisconnected() is never called!
// => see NetworkClientTests.DisconnectCallsOnClientDisconnect_HostMode()
NetworkClient.OnTransportDisconnected();
}
// true because local connections never timeout
internal override bool IsAlive(float timeout) => true;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a88758df7db2043d6a9d926e0b6d4191
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More