mirror of
https://github.com/smyalygames/monopoly.git
synced 2025-12-29 07:48:48 +01:00
Added multiplayer plugin
This commit is contained in:
9
Assets/Mirror/Runtime/AssemblyInfo.cs
Normal file
9
Assets/Mirror/Runtime/AssemblyInfo.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Mirror.Tests.Common")]
|
||||
[assembly: InternalsVisibleTo("Mirror.Tests")]
|
||||
[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")]
|
||||
11
Assets/Mirror/Runtime/AssemblyInfo.cs.meta
Normal file
11
Assets/Mirror/Runtime/AssemblyInfo.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e28d5f410e25b42e6a76a2ffc10e4675
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1047
Assets/Mirror/Runtime/ClientScene.cs
Normal file
1047
Assets/Mirror/Runtime/ClientScene.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Assets/Mirror/Runtime/ClientScene.cs.meta
Normal file
11
Assets/Mirror/Runtime/ClientScene.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96fc7967f813e4960b9119d7c2118494
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
225
Assets/Mirror/Runtime/Compression.cs
Normal file
225
Assets/Mirror/Runtime/Compression.cs
Normal file
@@ -0,0 +1,225 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
/// <summary>
|
||||
/// Functions to Compress Quaternions and Floats
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Uncompressed Quaternion = 32 * 4 = 128 bits => send 16 bytes
|
||||
///
|
||||
/// <para>
|
||||
/// Quaternion is always normalized so we drop largest value and re-calculate it.
|
||||
/// We can encode which one is the largest using 2 bits
|
||||
/// <code>
|
||||
/// x^2 + y^2 + z^2 + w^2 = 1
|
||||
/// </code>
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// 2nd largest value has max size of 1/sqrt(2)
|
||||
/// We can encode the smallest three components in [-1/sqrt(2),+1/sqrt(2)] instead of [-1,+1]
|
||||
/// <code>
|
||||
/// c^2 + c^2 + 0 + 0 = 1
|
||||
/// </code>
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Sign of largest value doesnt matter
|
||||
/// <code>
|
||||
/// Q * vec3 == (-Q) * vec3
|
||||
/// </code>
|
||||
/// </para>
|
||||
///
|
||||
/// <list type="bullet">
|
||||
/// <listheader><description>
|
||||
/// RotationPrecision <br/>
|
||||
/// <code>
|
||||
/// 2/sqrt(2) / (2^bitCount - 1)
|
||||
/// </code>
|
||||
/// </description></listheader>
|
||||
///
|
||||
/// <item><description>
|
||||
/// rotation precision +-0.00138 in range [-1,+1]
|
||||
/// <code>
|
||||
/// 10 bits per value
|
||||
/// 2 + 10 * 3 = 32 bits => send 4 bytes
|
||||
/// </code>
|
||||
/// </description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para>
|
||||
/// Links for more info:
|
||||
/// <br/><see href="https://youtu.be/Z9X4lysFr64">GDC Talk</see>
|
||||
/// <br/><see href="https://gafferongames.com/post/snapshot_compression/">Post on Snapshot Compression</see>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static class Compression
|
||||
{
|
||||
const float QuaternionMinValue = -1f / 1.414214f; // 1/ sqrt(2)
|
||||
const float QuaternionMaxValue = 1f / 1.414214f;
|
||||
|
||||
const int QuaternionBitLength = 10;
|
||||
// same as Mathf.Pow(2, targetBitLength) - 1
|
||||
const uint QuaternionUintRange = (1 << QuaternionBitLength) - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Used to Compress Quaternion into 4 bytes
|
||||
/// </summary>
|
||||
public static uint CompressQuaternion(Quaternion value)
|
||||
{
|
||||
value = value.normalized;
|
||||
|
||||
int largestIndex = FindLargestIndex(value);
|
||||
Vector3 small = GetSmallerDimensions(largestIndex, value);
|
||||
// largest needs to be positive to be calculated by reader
|
||||
// if largest is negative flip sign of others because Q = -Q
|
||||
if (value[largestIndex] < 0)
|
||||
{
|
||||
small *= -1;
|
||||
}
|
||||
|
||||
uint a = ScaleToUInt(small.x, QuaternionMinValue, QuaternionMaxValue, 0, QuaternionUintRange);
|
||||
uint b = ScaleToUInt(small.y, QuaternionMinValue, QuaternionMaxValue, 0, QuaternionUintRange);
|
||||
uint c = ScaleToUInt(small.z, QuaternionMinValue, QuaternionMaxValue, 0, QuaternionUintRange);
|
||||
|
||||
// pack each 10 bits and extra 2 bits into uint32
|
||||
uint packed = a | b << 10 | c << 20 | (uint)largestIndex << 30;
|
||||
|
||||
return packed;
|
||||
}
|
||||
|
||||
internal static int FindLargestIndex(Quaternion q)
|
||||
{
|
||||
int index = default;
|
||||
float current = default;
|
||||
|
||||
// check each value to see which one is largest (ignoring +-)
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
float next = Mathf.Abs(q[i]);
|
||||
if (next > current)
|
||||
{
|
||||
index = i;
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
static Vector3 GetSmallerDimensions(int largestIndex, Quaternion value)
|
||||
{
|
||||
float x = value.x;
|
||||
float y = value.y;
|
||||
float z = value.z;
|
||||
float w = value.w;
|
||||
|
||||
switch (largestIndex)
|
||||
{
|
||||
case 0:
|
||||
return new Vector3(y, z, w);
|
||||
case 1:
|
||||
return new Vector3(x, z, w);
|
||||
case 2:
|
||||
return new Vector3(x, y, w);
|
||||
case 3:
|
||||
return new Vector3(x, y, z);
|
||||
default:
|
||||
throw new IndexOutOfRangeException("Invalid Quaternion index!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Used to read a Compressed Quaternion from 4 bytes
|
||||
/// <para>Quaternion is normalized</para>
|
||||
/// </summary>
|
||||
public static Quaternion DecompressQuaternion(uint packed)
|
||||
{
|
||||
// 10 bits
|
||||
const uint mask = 0b11_1111_1111;
|
||||
Quaternion result;
|
||||
|
||||
|
||||
uint a = packed & mask;
|
||||
uint b = (packed >> 10) & mask;
|
||||
uint c = (packed >> 20) & mask;
|
||||
uint largestIndex = (packed >> 30) & mask;
|
||||
|
||||
float x = ScaleFromUInt(a, QuaternionMinValue, QuaternionMaxValue, 0, QuaternionUintRange);
|
||||
float y = ScaleFromUInt(b, QuaternionMinValue, QuaternionMaxValue, 0, QuaternionUintRange);
|
||||
float z = ScaleFromUInt(c, QuaternionMinValue, QuaternionMaxValue, 0, QuaternionUintRange);
|
||||
|
||||
Vector3 small = new Vector3(x, y, z);
|
||||
result = FromSmallerDimensions(largestIndex, small);
|
||||
return result;
|
||||
}
|
||||
|
||||
static Quaternion FromSmallerDimensions(uint largestIndex, Vector3 smallest)
|
||||
{
|
||||
float a = smallest.x;
|
||||
float b = smallest.y;
|
||||
float c = smallest.z;
|
||||
|
||||
float largest = Mathf.Sqrt(1 - a * a - b * b - c * c);
|
||||
switch (largestIndex)
|
||||
{
|
||||
case 0:
|
||||
return new Quaternion(largest, a, b, c).normalized;
|
||||
case 1:
|
||||
return new Quaternion(a, largest, b, c).normalized;
|
||||
case 2:
|
||||
return new Quaternion(a, b, largest, c).normalized;
|
||||
case 3:
|
||||
return new Quaternion(a, b, c, largest).normalized;
|
||||
default:
|
||||
throw new IndexOutOfRangeException("Invalid Quaternion index!");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Scales float from minFloat->maxFloat to minUint->maxUint
|
||||
/// <para>values out side of minFloat/maxFloat will return either 0 or maxUint</para>
|
||||
/// </summary>
|
||||
public static uint ScaleToUInt(float value, float minFloat, float maxFloat, uint minUint, uint maxUint)
|
||||
{
|
||||
// if out of range return min/max
|
||||
if (value > maxFloat) { return maxUint; }
|
||||
if (value < minFloat) { return minUint; }
|
||||
|
||||
float rangeFloat = maxFloat - minFloat;
|
||||
uint rangeUint = maxUint - minUint;
|
||||
|
||||
// scale value to 0->1 (as float)
|
||||
float valueRelative = (value - minFloat) / rangeFloat;
|
||||
// scale value to uMin->uMax
|
||||
float outValue = valueRelative * rangeUint + minUint;
|
||||
|
||||
return (uint)outValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales uint from minUint->maxUint to minFloat->maxFloat
|
||||
/// </summary>
|
||||
public static float ScaleFromUInt(uint value, float minFloat, float maxFloat, uint minUint, uint maxUint)
|
||||
{
|
||||
// if out of range return min/max
|
||||
if (value > maxUint) { return maxFloat; }
|
||||
if (value < minUint) { return minFloat; }
|
||||
|
||||
float rangeFloat = maxFloat - minFloat;
|
||||
uint rangeUint = maxUint - minUint;
|
||||
|
||||
// scale value to 0->1 (as float)
|
||||
// make sure divide is float
|
||||
float valueRelative = (value - minUint) / (float)rangeUint;
|
||||
// scale value to fMin->fMax
|
||||
float outValue = valueRelative * rangeFloat + minFloat;
|
||||
return outValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/Compression.cs.meta
Normal file
11
Assets/Mirror/Runtime/Compression.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c28963f9c4b97e418252a55500fb91e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
88
Assets/Mirror/Runtime/CustomAttributes.cs
Normal file
88
Assets/Mirror/Runtime/CustomAttributes.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
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
|
||||
{
|
||||
// this is zero
|
||||
public int channel = Channels.DefaultReliable;
|
||||
public bool ignoreAuthority = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The server uses a Remote Procedure Call (RPC) to run this function on clients.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class ClientRpcAttribute : Attribute
|
||||
{
|
||||
// this is zero
|
||||
public int channel = Channels.DefaultReliable;
|
||||
public bool excludeOwner = false;
|
||||
}
|
||||
|
||||
/// <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
|
||||
{
|
||||
// this is zero
|
||||
public int channel = Channels.DefaultReliable;
|
||||
}
|
||||
|
||||
/// <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 { }
|
||||
}
|
||||
11
Assets/Mirror/Runtime/CustomAttributes.cs.meta
Normal file
11
Assets/Mirror/Runtime/CustomAttributes.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c04c722ee2ffd49c8a56ab33667b10b0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
16
Assets/Mirror/Runtime/DotNetCompatibility.cs
Normal file
16
Assets/Mirror/Runtime/DotNetCompatibility.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
internal static class DotNetCompatibility
|
||||
{
|
||||
internal static string GetMethodName(this Delegate func)
|
||||
{
|
||||
#if NETFX_CORE
|
||||
return func.GetMethodInfo().Name;
|
||||
#else
|
||||
return func.Method.Name;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/DotNetCompatibility.cs.meta
Normal file
11
Assets/Mirror/Runtime/DotNetCompatibility.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b307f850ccbbe450295acf24d70e5c28
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
38
Assets/Mirror/Runtime/ExponentialMovingAverage.cs
Normal file
38
Assets/Mirror/Runtime/ExponentialMovingAverage.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
namespace Mirror
|
||||
{
|
||||
// implementation of N-day EMA
|
||||
// it calculates an exponential moving average roughy 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 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;
|
||||
}
|
||||
}
|
||||
|
||||
public double Value { get; private set; }
|
||||
|
||||
public double Var { get; private set; }
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/ExponentialMovingAverage.cs.meta
Normal file
11
Assets/Mirror/Runtime/ExponentialMovingAverage.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05e858cbaa54b4ce4a48c8c7f50c1914
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
2
Assets/Mirror/Runtime/FloatBytePacker.cs
Normal file
2
Assets/Mirror/Runtime/FloatBytePacker.cs
Normal file
@@ -0,0 +1,2 @@
|
||||
// File Removed 24-Mar-20 - keeping it in here so AssetStore updates overwrite
|
||||
// the old one.
|
||||
11
Assets/Mirror/Runtime/FloatBytePacker.cs.meta
Normal file
11
Assets/Mirror/Runtime/FloatBytePacker.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: afd3cca6a786d4208b1d0f7f2b168901
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
141
Assets/Mirror/Runtime/LocalConnections.cs
Normal file
141
Assets/Mirror/Runtime/LocalConnections.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
using System;
|
||||
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
|
||||
class ULocalConnectionToClient : NetworkConnectionToClient
|
||||
{
|
||||
internal ULocalConnectionToServer connectionToServer;
|
||||
|
||||
public ULocalConnectionToClient() : base(LocalConnectionId) { }
|
||||
|
||||
public override string address => "localhost";
|
||||
|
||||
internal override void Send(ArraySegment<byte> segment, int channelId = Channels.DefaultReliable)
|
||||
{
|
||||
connectionToServer.buffer.Write(segment);
|
||||
}
|
||||
|
||||
// true because local connections never timeout
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
RemoveObservers();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects this connection.
|
||||
/// </summary>
|
||||
public override void Disconnect()
|
||||
{
|
||||
DisconnectInternal();
|
||||
connectionToServer.DisconnectInternal();
|
||||
}
|
||||
}
|
||||
|
||||
internal class LocalConnectionBuffer
|
||||
{
|
||||
readonly NetworkWriter writer = new NetworkWriter();
|
||||
readonly NetworkReader reader = new NetworkReader(default(ArraySegment<byte>));
|
||||
// The buffer is atleast 1500 bytes long. So need to keep track of
|
||||
// packet count to know how many ArraySegments are in the buffer
|
||||
int packetCount;
|
||||
|
||||
public void Write(ArraySegment<byte> segment)
|
||||
{
|
||||
writer.WriteBytesAndSizeSegment(segment);
|
||||
packetCount++;
|
||||
|
||||
// update buffer incase writer's length has changed
|
||||
reader.buffer = writer.ToArraySegment();
|
||||
}
|
||||
|
||||
public bool HasPackets()
|
||||
{
|
||||
return packetCount > 0;
|
||||
}
|
||||
public ArraySegment<byte> GetNextPacket()
|
||||
{
|
||||
ArraySegment<byte> packet = reader.ReadBytesAndSizeSegment();
|
||||
packetCount--;
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
public void ResetBuffer()
|
||||
{
|
||||
writer.Reset();
|
||||
reader.Position = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// a localClient's connection TO a server.
|
||||
// send messages on this connection causes the server's handler function to be invoked directly.
|
||||
internal class ULocalConnectionToServer : NetworkConnectionToServer
|
||||
{
|
||||
static readonly ILogger logger = LogFactory.GetLogger(typeof(ULocalConnectionToClient));
|
||||
|
||||
internal ULocalConnectionToClient connectionToClient;
|
||||
internal readonly LocalConnectionBuffer buffer = new LocalConnectionBuffer();
|
||||
|
||||
public override string address => "localhost";
|
||||
|
||||
internal override void Send(ArraySegment<byte> segment, int channelId = Channels.DefaultReliable)
|
||||
{
|
||||
if (segment.Count == 0)
|
||||
{
|
||||
logger.LogError("LocalConnection.SendBytes cannot send zero bytes");
|
||||
return;
|
||||
}
|
||||
|
||||
// handle the server's message directly
|
||||
connectionToClient.TransportReceive(segment, channelId);
|
||||
}
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
// process internal messages so they are applied at the correct time
|
||||
while (buffer.HasPackets())
|
||||
{
|
||||
ArraySegment<byte> packet = buffer.GetNextPacket();
|
||||
|
||||
// Treat host player messages exactly like connected client
|
||||
// to avoid deceptive / misleading behavior differences
|
||||
TransportReceive(packet, Channels.DefaultReliable);
|
||||
}
|
||||
|
||||
buffer.ResetBuffer();
|
||||
}
|
||||
|
||||
/// <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)
|
||||
isReady = false;
|
||||
ClientScene.HandleClientDisconnect(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects this connection.
|
||||
/// </summary>
|
||||
public override void Disconnect()
|
||||
{
|
||||
connectionToClient.DisconnectInternal();
|
||||
DisconnectInternal();
|
||||
}
|
||||
|
||||
// true because local connections never timeout
|
||||
/// <inheritdoc/>
|
||||
internal override bool IsAlive(float timeout) => true;
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/LocalConnections.cs.meta
Normal file
11
Assets/Mirror/Runtime/LocalConnections.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a88758df7db2043d6a9d926e0b6d4191
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1
Assets/Mirror/Runtime/LogFactory.cs
Normal file
1
Assets/Mirror/Runtime/LogFactory.cs
Normal file
@@ -0,0 +1 @@
|
||||
// File moved to Mirror/Runtime/Logging/LogFactory.cs
|
||||
11
Assets/Mirror/Runtime/LogFactory.cs.meta
Normal file
11
Assets/Mirror/Runtime/LogFactory.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 353c7c9e14e82f349b1679112050b196
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
Assets/Mirror/Runtime/LogFilter.cs
Normal file
7
Assets/Mirror/Runtime/LogFilter.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Mirror
|
||||
{
|
||||
public static class LogFilter
|
||||
{
|
||||
public static bool Debug = false;
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/LogFilter.cs.meta
Normal file
11
Assets/Mirror/Runtime/LogFilter.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f6928b080072948f7b2909b4025fcc79
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Mirror/Runtime/Logging.meta
Normal file
8
Assets/Mirror/Runtime/Logging.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 63d647500ca1bfa4a845bc1f4cff9dcc
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
44
Assets/Mirror/Runtime/Logging/ConsoleColorLogHandler.cs
Normal file
44
Assets/Mirror/Runtime/Logging/ConsoleColorLogHandler.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Logging
|
||||
{
|
||||
public class ConsoleColorLogHandler : ILogHandler
|
||||
{
|
||||
readonly bool showExceptionStackTrace;
|
||||
|
||||
public ConsoleColorLogHandler(bool showExceptionStackTrace)
|
||||
{
|
||||
this.showExceptionStackTrace = showExceptionStackTrace;
|
||||
}
|
||||
|
||||
public void LogException(Exception exception, UnityEngine.Object context)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine($"Exception: {exception.Message}");
|
||||
if (showExceptionStackTrace)
|
||||
{
|
||||
Console.WriteLine($" {exception.StackTrace}");
|
||||
}
|
||||
Console.ResetColor();
|
||||
}
|
||||
|
||||
public void LogFormat(LogType logType, UnityEngine.Object context, string format, params object[] args)
|
||||
{
|
||||
switch (logType)
|
||||
{
|
||||
case LogType.Exception:
|
||||
case LogType.Error:
|
||||
case LogType.Assert:
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
break;
|
||||
case LogType.Warning:
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
break;
|
||||
}
|
||||
|
||||
Console.WriteLine(string.Format(format, args));
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/Logging/ConsoleColorLogHandler.cs.meta
Normal file
11
Assets/Mirror/Runtime/Logging/ConsoleColorLogHandler.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2a9618569c20a504aa86feb5913c70e9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
50
Assets/Mirror/Runtime/Logging/EditorLogSettingsLoader.cs
Normal file
50
Assets/Mirror/Runtime/Logging/EditorLogSettingsLoader.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Logging
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
public static class EditorLogSettingsLoader
|
||||
{
|
||||
[InitializeOnLoadMethod]
|
||||
static void Init()
|
||||
{
|
||||
// load settings first time LogFactory is used in the editor
|
||||
LoadLogSettingsIntoDictionary();
|
||||
}
|
||||
|
||||
public static void LoadLogSettingsIntoDictionary()
|
||||
{
|
||||
LogSettings settings = FindLogSettings();
|
||||
if (settings != null)
|
||||
{
|
||||
settings.LoadIntoDictionary(LogFactory.loggers);
|
||||
}
|
||||
}
|
||||
|
||||
static LogSettings cache;
|
||||
public static LogSettings FindLogSettings()
|
||||
{
|
||||
if (cache != null)
|
||||
return cache;
|
||||
|
||||
string[] assetGuids = AssetDatabase.FindAssets("t:" + nameof(LogSettings));
|
||||
if (assetGuids.Length == 0)
|
||||
return null;
|
||||
|
||||
string firstGuid = assetGuids[0];
|
||||
|
||||
string path = AssetDatabase.GUIDToAssetPath(firstGuid);
|
||||
cache = AssetDatabase.LoadAssetAtPath<LogSettings>(path);
|
||||
|
||||
if (assetGuids.Length > 2)
|
||||
{
|
||||
Debug.LogWarning("Found more than one LogSettings, Delete extra settings. Using first asset found: " + path);
|
||||
}
|
||||
Debug.Assert(cache != null, "Failed to load asset at: " + path);
|
||||
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a39aa1e48aa54eb4e964f0191c1dcdce
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
100
Assets/Mirror/Runtime/Logging/LogFactory.cs
Normal file
100
Assets/Mirror/Runtime/Logging/LogFactory.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
public static class LogFactory
|
||||
{
|
||||
internal static readonly SortedDictionary<string, ILogger> loggers = new SortedDictionary<string, ILogger>();
|
||||
|
||||
public static SortedDictionary<string, ILogger>.ValueCollection AllLoggers => loggers.Values;
|
||||
|
||||
/// <summary>
|
||||
/// logHandler used for new loggers
|
||||
/// </summary>
|
||||
static ILogHandler defaultLogHandler = Debug.unityLogger;
|
||||
|
||||
/// <summary>
|
||||
/// if true sets all log level to LogType.Log
|
||||
/// </summary>
|
||||
static bool debugMode = false;
|
||||
|
||||
public static ILogger GetLogger<T>(LogType defaultLogLevel = LogType.Warning)
|
||||
{
|
||||
return GetLogger(typeof(T).Name, defaultLogLevel);
|
||||
}
|
||||
|
||||
public static ILogger GetLogger(System.Type type, LogType defaultLogLevel = LogType.Warning)
|
||||
{
|
||||
return GetLogger(type.Name, defaultLogLevel);
|
||||
}
|
||||
|
||||
public static ILogger GetLogger(string loggerName, LogType defaultLogLevel = LogType.Warning)
|
||||
{
|
||||
if (loggers.TryGetValue(loggerName, out ILogger logger))
|
||||
{
|
||||
return logger;
|
||||
}
|
||||
|
||||
logger = new Logger(defaultLogHandler)
|
||||
{
|
||||
// by default, log warnings and up
|
||||
filterLogType = debugMode ? LogType.Log : defaultLogLevel
|
||||
};
|
||||
|
||||
loggers[loggerName] = logger;
|
||||
return logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes all log levels LogType.Log, this is so that NetworkManger.showDebugMessages can still be used
|
||||
/// </summary>
|
||||
public static void EnableDebugMode()
|
||||
{
|
||||
debugMode = true;
|
||||
|
||||
foreach (ILogger logger in loggers.Values)
|
||||
{
|
||||
logger.filterLogType = LogType.Log;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replacing log handler for all existing loggers and sets defaultLogHandler for new loggers
|
||||
/// </summary>
|
||||
/// <param name="logHandler"></param>
|
||||
public static void ReplaceLogHandler(ILogHandler logHandler)
|
||||
{
|
||||
defaultLogHandler = logHandler;
|
||||
|
||||
foreach (ILogger logger in loggers.Values)
|
||||
{
|
||||
logger.logHandler = logHandler;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class ILoggerExtensions
|
||||
{
|
||||
public static void LogError(this ILogger logger, object message)
|
||||
{
|
||||
logger.Log(LogType.Error, message);
|
||||
}
|
||||
|
||||
public static void Assert(this ILogger logger, bool condition, string message)
|
||||
{
|
||||
if (!condition)
|
||||
logger.Log(LogType.Assert, message);
|
||||
}
|
||||
|
||||
public static void LogWarning(this ILogger logger, object message)
|
||||
{
|
||||
logger.Log(LogType.Warning, message);
|
||||
}
|
||||
|
||||
public static bool LogEnabled(this ILogger logger) => logger.IsLogTypeAllowed(LogType.Log);
|
||||
public static bool WarnEnabled(this ILogger logger) => logger.IsLogTypeAllowed(LogType.Warning);
|
||||
public static bool ErrorEnabled(this ILogger logger) => logger.IsLogTypeAllowed(LogType.Error);
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/Logging/LogFactory.cs.meta
Normal file
11
Assets/Mirror/Runtime/Logging/LogFactory.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d06522432d5a44e1587967a4731cd279
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
67
Assets/Mirror/Runtime/Logging/LogSettings.cs
Normal file
67
Assets/Mirror/Runtime/Logging/LogSettings.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Logging
|
||||
{
|
||||
public class LogSettings : ScriptableObject
|
||||
{
|
||||
public List<LoggerSettings> loglevels = new List<LoggerSettings>();
|
||||
|
||||
[Serializable]
|
||||
public struct LoggerSettings
|
||||
{
|
||||
public string name;
|
||||
public LogType logLevel;
|
||||
}
|
||||
}
|
||||
|
||||
public static class LogSettingsExt
|
||||
{
|
||||
public static void SaveFromDictionary(this LogSettings settings, SortedDictionary<string, ILogger> dictionary)
|
||||
{
|
||||
if (settings == null)
|
||||
{
|
||||
Debug.LogWarning("Could not SaveFromDictionary because LogSettings were null");
|
||||
return;
|
||||
}
|
||||
|
||||
settings.loglevels.Clear();
|
||||
|
||||
foreach (KeyValuePair<string, ILogger> kvp in dictionary)
|
||||
{
|
||||
settings.loglevels.Add(new LogSettings.LoggerSettings { name = kvp.Key, logLevel = kvp.Value.filterLogType });
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.EditorUtility.SetDirty(settings);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void LoadIntoDictionary(this LogSettings settings, SortedDictionary<string, ILogger> dictionary)
|
||||
{
|
||||
if (settings == null)
|
||||
{
|
||||
Debug.LogWarning("Could not LoadIntoDictionary because LogSettings were null");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (LogSettings.LoggerSettings logLevel in settings.loglevels)
|
||||
{
|
||||
if (dictionary.TryGetValue(logLevel.name, out ILogger logger))
|
||||
{
|
||||
logger.filterLogType = logLevel.logLevel;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger = new Logger(Debug.unityLogger)
|
||||
{
|
||||
filterLogType = logLevel.logLevel
|
||||
};
|
||||
|
||||
dictionary[logLevel.name] = logger;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/Logging/LogSettings.cs.meta
Normal file
11
Assets/Mirror/Runtime/Logging/LogSettings.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 633889a39717fde4fa28dd6b948dfac7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
24
Assets/Mirror/Runtime/Logging/NetworkHeadlessLogger.cs
Normal file
24
Assets/Mirror/Runtime/Logging/NetworkHeadlessLogger.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to replace log hanlder with Console Color LogHandler
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("Network/NetworkHeadlessLogger")]
|
||||
[HelpURL("https://mirror-networking.com/docs/Components/NetworkHeadlessLogger.html")]
|
||||
public class NetworkHeadlessLogger : MonoBehaviour
|
||||
{
|
||||
#pragma warning disable CS0414 // unused private members
|
||||
[SerializeField] bool showExceptionStackTrace = false;
|
||||
#pragma warning restore CS0414 // unused private members
|
||||
|
||||
void Awake()
|
||||
{
|
||||
#if UNITY_SERVER
|
||||
LogFactory.ReplaceLogHandler(new ConsoleColorLogHandler(showExceptionStackTrace));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/Logging/NetworkHeadlessLogger.cs.meta
Normal file
11
Assets/Mirror/Runtime/Logging/NetworkHeadlessLogger.cs.meta
Normal 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:
|
||||
46
Assets/Mirror/Runtime/Logging/NetworkLogSettings.cs
Normal file
46
Assets/Mirror/Runtime/Logging/NetworkLogSettings.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to load LogSettings in build
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("Network/NetworkLogSettings")]
|
||||
[HelpURL("https://mirror-networking.com/docs/Components/NetworkLogSettings.html")]
|
||||
public class NetworkLogSettings : MonoBehaviour
|
||||
{
|
||||
[Header("Log Settings Asset")]
|
||||
[SerializeField] internal LogSettings settings;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// called when component is added to GameObject
|
||||
void Reset()
|
||||
{
|
||||
LogSettings existingSettings = EditorLogSettingsLoader.FindLogSettings();
|
||||
if (existingSettings != null)
|
||||
{
|
||||
settings = existingSettings;
|
||||
|
||||
UnityEditor.EditorUtility.SetDirty(this);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void Awake()
|
||||
{
|
||||
RefreshDictionary();
|
||||
}
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
// if settings field is changed
|
||||
RefreshDictionary();
|
||||
}
|
||||
|
||||
void RefreshDictionary()
|
||||
{
|
||||
settings.LoadIntoDictionary(LogFactory.loggers);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/Logging/NetworkLogSettings.cs.meta
Normal file
11
Assets/Mirror/Runtime/Logging/NetworkLogSettings.cs.meta
Normal 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:
|
||||
131
Assets/Mirror/Runtime/MessagePacker.cs
Normal file
131
Assets/Mirror/Runtime/MessagePacker.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
// message packing all in one place, instead of constructing headers in all
|
||||
// kinds of different places
|
||||
//
|
||||
// MsgType (1-n bytes)
|
||||
// Content (ContentSize bytes)
|
||||
//
|
||||
// -> we use varint for headers because most messages will result in 1 byte
|
||||
// type/size headers then instead of always
|
||||
// using 2 bytes for shorts.
|
||||
// -> this reduces bandwidth by 10% if average message size is 20 bytes
|
||||
// (probably even shorter)
|
||||
public static class MessagePacker
|
||||
{
|
||||
static readonly ILogger logger = LogFactory.GetLogger(typeof(MessagePacker));
|
||||
|
||||
public static int GetId<T>() where T : NetworkMessage
|
||||
{
|
||||
return GetId(typeof(T));
|
||||
}
|
||||
|
||||
public static int GetId(Type type)
|
||||
{
|
||||
// paul: 16 bits is enough to avoid collisions
|
||||
// - keeps the message size small because it gets varinted
|
||||
// - in case of collisions, Mirror will display an error
|
||||
return type.FullName.GetStableHashCode() & 0xFFFF;
|
||||
}
|
||||
|
||||
// pack message before sending
|
||||
// -> NetworkWriter passed as arg so that we can use .ToArraySegment
|
||||
// and do an allocation free send before recycling it.
|
||||
public static void Pack<T>(T message, NetworkWriter writer) where T : NetworkMessage
|
||||
{
|
||||
// if it is a value type, just use typeof(T) to avoid boxing
|
||||
// this works because value types cannot be derived
|
||||
// if it is a reference type (for example NetworkMessage),
|
||||
// ask the message for the real type
|
||||
int msgType = GetId(default(T) != null ? typeof(T) : message.GetType());
|
||||
writer.WriteUInt16((ushort)msgType);
|
||||
|
||||
// serialize message into writer
|
||||
writer.Write(message);
|
||||
}
|
||||
|
||||
// unpack a message we received
|
||||
public static T Unpack<T>(byte[] data) where T : NetworkMessage
|
||||
{
|
||||
using (PooledNetworkReader networkReader = NetworkReaderPool.GetReader(data))
|
||||
{
|
||||
int msgType = GetId<T>();
|
||||
|
||||
int id = networkReader.ReadUInt16();
|
||||
if (id != msgType)
|
||||
throw new FormatException("Invalid message, could not unpack " + typeof(T).FullName);
|
||||
|
||||
return networkReader.Read<T>();
|
||||
}
|
||||
}
|
||||
|
||||
// unpack message after receiving
|
||||
// -> pass NetworkReader so it's less strange if we create it in here
|
||||
// and pass it upwards.
|
||||
// -> NetworkReader will point at content afterwards!
|
||||
public static bool UnpackMessage(NetworkReader messageReader, out int msgType)
|
||||
{
|
||||
// read message type (varint)
|
||||
try
|
||||
{
|
||||
msgType = messageReader.ReadUInt16();
|
||||
return true;
|
||||
}
|
||||
catch (System.IO.EndOfStreamException)
|
||||
{
|
||||
msgType = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal static NetworkMessageDelegate MessageHandler<T, C>(Action<C, T> handler, bool requireAuthenication)
|
||||
where T : NetworkMessage
|
||||
where C : NetworkConnection
|
||||
=> (conn, reader, channelId) =>
|
||||
{
|
||||
// protect against DOS attacks if attackers try to send invalid
|
||||
// data packets to crash the server/client. there are a thousand
|
||||
// ways to cause an exception in data handling:
|
||||
// - invalid headers
|
||||
// - invalid message ids
|
||||
// - invalid data causing exceptions
|
||||
// - negative ReadBytesAndSize prefixes
|
||||
// - invalid utf8 strings
|
||||
// - etc.
|
||||
//
|
||||
// let's catch them all and then disconnect that connection to avoid
|
||||
// further attacks.
|
||||
T message = default;
|
||||
try
|
||||
{
|
||||
if (requireAuthenication && !conn.isAuthenticated)
|
||||
{
|
||||
// message requires authentication, but the connection was not authenticated
|
||||
logger.LogWarning($"Closing connection: {conn}. Received message {typeof(T)} that required authentication, but the user has not authenticated yet");
|
||||
conn.Disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
// if it is a value type, just use defult(T)
|
||||
// otherwise allocate a new instance
|
||||
message = reader.Read<T>();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
logger.LogError("Closed connection: " + conn + ". This can happen if the other side accidentally (or an attacker intentionally) sent invalid data. Reason: " + exception);
|
||||
conn.Disconnect();
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// TODO: Figure out the correct channel
|
||||
NetworkDiagnostics.OnReceive(message, channelId, reader.Length);
|
||||
}
|
||||
|
||||
handler((C)conn, message);
|
||||
};
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/MessagePacker.cs.meta
Normal file
11
Assets/Mirror/Runtime/MessagePacker.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2db134099f0df4d96a84ae7a0cd9b4bc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
161
Assets/Mirror/Runtime/Messages.cs
Normal file
161
Assets/Mirror/Runtime/Messages.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
// Deprecated 10/06/2020
|
||||
[Obsolete("Implement NetworkMessage instead. Use extension methods instead of Serialize/Deserialize, see https://github.com/vis2k/Mirror/pull/2317", true)]
|
||||
public interface IMessageBase { }
|
||||
|
||||
// Deprecated 10/06/2020
|
||||
[Obsolete("Implement NetworkMessage instead. Use extension methods instead of Serialize/Deserialize, see https://github.com/vis2k/Mirror/pull/2317", true)]
|
||||
public class MessageBase : IMessageBase { }
|
||||
|
||||
public interface NetworkMessage { }
|
||||
|
||||
#region Public System Messages
|
||||
public struct ErrorMessage : NetworkMessage
|
||||
{
|
||||
public byte value;
|
||||
|
||||
public ErrorMessage(byte v)
|
||||
{
|
||||
value = v;
|
||||
}
|
||||
}
|
||||
|
||||
public struct ReadyMessage : NetworkMessage { }
|
||||
|
||||
public struct NotReadyMessage : NetworkMessage { }
|
||||
|
||||
public struct AddPlayerMessage : NetworkMessage { }
|
||||
|
||||
public struct DisconnectMessage : NetworkMessage { }
|
||||
|
||||
public struct ConnectMessage : NetworkMessage { }
|
||||
|
||||
public struct SceneMessage : NetworkMessage
|
||||
{
|
||||
public string sceneName;
|
||||
// Normal = 0, LoadAdditive = 1, UnloadAdditive = 2
|
||||
public SceneOperation sceneOperation;
|
||||
public bool customHandling;
|
||||
}
|
||||
|
||||
public enum SceneOperation : byte
|
||||
{
|
||||
Normal,
|
||||
LoadAdditive,
|
||||
UnloadAdditive
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region System Messages requried for code gen path
|
||||
public struct CommandMessage : NetworkMessage
|
||||
{
|
||||
public uint netId;
|
||||
public int componentIndex;
|
||||
public int functionHash;
|
||||
// the parameters for the Cmd function
|
||||
// -> ArraySegment to avoid unnecessary allocations
|
||||
public ArraySegment<byte> payload;
|
||||
}
|
||||
|
||||
public struct RpcMessage : NetworkMessage
|
||||
{
|
||||
public uint netId;
|
||||
public int componentIndex;
|
||||
public int functionHash;
|
||||
// the parameters for the Cmd function
|
||||
// -> ArraySegment to avoid unnecessary allocations
|
||||
public ArraySegment<byte> payload;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Internal System Messages
|
||||
public struct SpawnMessage : NetworkMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// netId of new or existing object
|
||||
/// </summary>
|
||||
public uint netId;
|
||||
/// <summary>
|
||||
/// Is the spawning object the local player. Sets ClientScene.localPlayer
|
||||
/// </summary>
|
||||
public bool isLocalPlayer;
|
||||
/// <summary>
|
||||
/// Sets hasAuthority on the spawned object
|
||||
/// </summary>
|
||||
public bool isOwner;
|
||||
/// <summary>
|
||||
/// The id of the scene object to spawn
|
||||
/// </summary>
|
||||
public ulong sceneId;
|
||||
/// <summary>
|
||||
/// The id of the prefab to spawn
|
||||
/// <para>If sceneId != 0 then it is used instead of assetId</para>
|
||||
/// </summary>
|
||||
public Guid assetId;
|
||||
/// <summary>
|
||||
/// Local position
|
||||
/// </summary>
|
||||
public Vector3 position;
|
||||
/// <summary>
|
||||
/// Local rotation
|
||||
/// </summary>
|
||||
public Quaternion rotation;
|
||||
/// <summary>
|
||||
/// Local scale
|
||||
/// </summary>
|
||||
public Vector3 scale;
|
||||
/// <summary>
|
||||
/// The serialized component data
|
||||
/// <remark>ArraySegment to avoid unnecessary allocations</remark>
|
||||
/// </summary>
|
||||
public ArraySegment<byte> payload;
|
||||
}
|
||||
|
||||
public struct ObjectSpawnStartedMessage : NetworkMessage { }
|
||||
|
||||
public struct ObjectSpawnFinishedMessage : NetworkMessage { }
|
||||
|
||||
public struct ObjectDestroyMessage : NetworkMessage
|
||||
{
|
||||
public uint netId;
|
||||
}
|
||||
|
||||
public struct ObjectHideMessage : NetworkMessage
|
||||
{
|
||||
public uint netId;
|
||||
}
|
||||
|
||||
public struct UpdateVarsMessage : NetworkMessage
|
||||
{
|
||||
public uint netId;
|
||||
// the serialized component data
|
||||
// -> ArraySegment to avoid unnecessary allocations
|
||||
public ArraySegment<byte> payload;
|
||||
}
|
||||
|
||||
// A client sends this message to the server
|
||||
// to calculate RTT and synchronize time
|
||||
public struct NetworkPingMessage : NetworkMessage
|
||||
{
|
||||
public double clientTime;
|
||||
|
||||
public NetworkPingMessage(double value)
|
||||
{
|
||||
clientTime = value;
|
||||
}
|
||||
}
|
||||
|
||||
// The server responds with this message
|
||||
// The client can use this to calculate RTT and sync time
|
||||
public struct NetworkPongMessage : NetworkMessage
|
||||
{
|
||||
public double clientTime;
|
||||
public double serverTime;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
11
Assets/Mirror/Runtime/Messages.cs.meta
Normal file
11
Assets/Mirror/Runtime/Messages.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 938f6f28a6c5b48a0bbd7782342d763b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
16
Assets/Mirror/Runtime/Mirror.asmdef
Normal file
16
Assets/Mirror/Runtime/Mirror.asmdef
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "Mirror",
|
||||
"references": [
|
||||
"Mirror.CompilerSymbols",
|
||||
"Telepathy",
|
||||
"kcp2k"
|
||||
],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": []
|
||||
}
|
||||
7
Assets/Mirror/Runtime/Mirror.asmdef.meta
Normal file
7
Assets/Mirror/Runtime/Mirror.asmdef.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 30817c1a0e6d646d99c048fc403f5979
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
101
Assets/Mirror/Runtime/NetworkAuthenticator.cs
Normal file
101
Assets/Mirror/Runtime/NetworkAuthenticator.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
/// <summary>
|
||||
/// Unity Event for the NetworkConnection
|
||||
/// </summary>
|
||||
[Serializable] public class UnityEventNetworkConnection : UnityEvent<NetworkConnection> { }
|
||||
|
||||
/// <summary>
|
||||
/// Base class for implementing component-based authentication during the Connect phase
|
||||
/// </summary>
|
||||
[HelpURL("https://mirror-networking.com/docs/Guides/Authentication.html")]
|
||||
public abstract class NetworkAuthenticator : MonoBehaviour
|
||||
{
|
||||
[Header("Event Listeners (optional)")]
|
||||
|
||||
/// <summary>
|
||||
/// Notify subscribers on the server when a client is authenticated
|
||||
/// </summary>
|
||||
[Tooltip("Mirror has an internal subscriber to this event. You can add your own here.")]
|
||||
public UnityEventNetworkConnection OnServerAuthenticated = new UnityEventNetworkConnection();
|
||||
|
||||
/// <summary>
|
||||
/// Notify subscribers on the client when the client is authenticated
|
||||
/// </summary>
|
||||
[Tooltip("Mirror has an internal subscriber to this event. You can add your own here.")]
|
||||
public UnityEventNetworkConnection OnClientAuthenticated = new UnityEventNetworkConnection();
|
||||
|
||||
#region server
|
||||
|
||||
/// <summary>
|
||||
/// Called on server from StartServer to initialize the Authenticator
|
||||
/// <para>Server message handlers should be registered in this method.</para>
|
||||
/// </summary>
|
||||
public virtual void OnStartServer() { }
|
||||
|
||||
/// <summary>
|
||||
/// Called on server from OnServerAuthenticateInternal when a client needs to authenticate
|
||||
/// </summary>
|
||||
/// <param name="conn">Connection to client.</param>
|
||||
public abstract void OnServerAuthenticate(NetworkConnection conn);
|
||||
|
||||
protected void ServerAccept(NetworkConnection conn)
|
||||
{
|
||||
OnServerAuthenticated.Invoke(conn);
|
||||
}
|
||||
|
||||
protected void ServerReject(NetworkConnection conn)
|
||||
{
|
||||
conn.Disconnect();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region client
|
||||
|
||||
/// <summary>
|
||||
/// Called on client from StartClient to initialize the Authenticator
|
||||
/// <para>Client message handlers should be registered in this method.</para>
|
||||
/// </summary>
|
||||
public virtual void OnStartClient() { }
|
||||
|
||||
/// <summary>
|
||||
/// Called on client from OnClientAuthenticateInternal when a client needs to authenticate
|
||||
/// </summary>
|
||||
/// <param name="conn">Connection of the client.</param>
|
||||
public abstract void OnClientAuthenticate(NetworkConnection conn);
|
||||
|
||||
protected void ClientAccept(NetworkConnection conn)
|
||||
{
|
||||
OnClientAuthenticated.Invoke(conn);
|
||||
}
|
||||
|
||||
protected void ClientReject(NetworkConnection conn)
|
||||
{
|
||||
// Set this on the client for local reference
|
||||
conn.isAuthenticated = false;
|
||||
|
||||
// disconnect the client
|
||||
conn.Disconnect();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
// automatically assign authenticator field if we add this to NetworkManager
|
||||
NetworkManager manager = GetComponent<NetworkManager>();
|
||||
if (manager != null && manager.authenticator == null)
|
||||
{
|
||||
manager.authenticator = this;
|
||||
UnityEditor.Undo.RecordObject(gameObject, "Assigned NetworkManager authenticator");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/NetworkAuthenticator.cs.meta
Normal file
11
Assets/Mirror/Runtime/NetworkAuthenticator.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 407fc95d4a8257f448799f26cdde0c2a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
682
Assets/Mirror/Runtime/NetworkBehaviour.cs
Normal file
682
Assets/Mirror/Runtime/NetworkBehaviour.cs
Normal file
@@ -0,0 +1,682 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Mirror.RemoteCalls;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
/// <summary>
|
||||
/// Sync to everyone, or only to owner.
|
||||
/// </summary>
|
||||
public enum SyncMode { Observers, Owner }
|
||||
|
||||
/// <summary>
|
||||
/// Base class which should be inherited by scripts which contain networking functionality.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This is a MonoBehaviour class so scripts which need to use the networking feature should inherit this class instead of MonoBehaviour. It allows you to invoke networked actions, receive various callbacks, and automatically synchronize state from server-to-client.</para>
|
||||
/// <para>The NetworkBehaviour component requires a NetworkIdentity on the game object. There can be multiple NetworkBehaviours on a single game object. For an object with sub-components in a hierarchy, the NetworkIdentity must be on the root object, and NetworkBehaviour scripts must also be on the root object.</para>
|
||||
/// <para>Some of the built-in components of the networking system are derived from NetworkBehaviour, including NetworkTransport, NetworkAnimator and NetworkProximityChecker.</para>
|
||||
/// </remarks>
|
||||
[AddComponentMenu("")]
|
||||
[RequireComponent(typeof(NetworkIdentity))]
|
||||
[HelpURL("https://mirror-networking.com/docs/Guides/NetworkBehaviour.html")]
|
||||
public abstract class NetworkBehaviour : MonoBehaviour
|
||||
{
|
||||
static readonly ILogger logger = LogFactory.GetLogger(typeof(NetworkBehaviour));
|
||||
|
||||
internal float lastSyncTime;
|
||||
|
||||
// hidden because NetworkBehaviourInspector shows it only if has OnSerialize.
|
||||
/// <summary>
|
||||
/// sync mode for OnSerialize
|
||||
/// </summary>
|
||||
[HideInInspector] public SyncMode syncMode = SyncMode.Observers;
|
||||
|
||||
// hidden because NetworkBehaviourInspector shows it only if has OnSerialize.
|
||||
/// <summary>
|
||||
/// sync interval for OnSerialize (in seconds)
|
||||
/// </summary>
|
||||
[Tooltip("Time in seconds until next change is synchronized to the client. '0' means send immediately if changed. '0.5' means only send changes every 500ms.\n(This is for state synchronization like SyncVars, SyncLists, OnSerialize. Not for Cmds, Rpcs, etc.)")]
|
||||
// [0,2] should be enough. anything >2s is too laggy anyway.
|
||||
[Range(0, 2)]
|
||||
[HideInInspector] public float syncInterval = 0.1f;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this object is active on an active server.
|
||||
/// <para>This is only true if the object has been spawned. This is different from NetworkServer.active, which is true if the server itself is active rather than this object being active.</para>
|
||||
/// </summary>
|
||||
public bool isServer => netIdentity.isServer;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if running as a client and this object was spawned by a server.
|
||||
/// </summary>
|
||||
public bool isClient => netIdentity.isClient;
|
||||
|
||||
/// <summary>
|
||||
/// This returns true if this object is the one that represents the player on the local machine.
|
||||
/// <para>In multiplayer games, there are multiple instances of the Player object. The client needs to know which one is for "themselves" so that only that player processes input and potentially has a camera attached. The IsLocalPlayer function will return true only for the player instance that belongs to the player on the local machine, so it can be used to filter out input for non-local players.</para>
|
||||
/// </summary>
|
||||
public bool isLocalPlayer => netIdentity.isLocalPlayer;
|
||||
|
||||
/// <summary>
|
||||
/// True if this object only exists on the server
|
||||
/// </summary>
|
||||
public bool isServerOnly => isServer && !isClient;
|
||||
|
||||
/// <summary>
|
||||
/// True if this object exists on a client that is not also acting as a server
|
||||
/// </summary>
|
||||
public bool isClientOnly => isClient && !isServer;
|
||||
|
||||
/// <summary>
|
||||
/// This returns true if this object is the authoritative version of the object in the distributed network application.
|
||||
/// <para>The <see cref="NetworkIdentity.hasAuthority">NetworkIdentity.hasAuthority</see> value on the NetworkIdentity determines how authority is determined. For most objects, authority is held by the server. For objects with <see cref="NetworkIdentity.hasAuthority">NetworkIdentity.hasAuthority</see> set, authority is held by the client of that player.</para>
|
||||
/// </summary>
|
||||
public bool hasAuthority => netIdentity.hasAuthority;
|
||||
|
||||
/// <summary>
|
||||
/// The unique network Id of this object.
|
||||
/// <para>This is assigned at runtime by the network server and will be unique for all objects for that network session.</para>
|
||||
/// </summary>
|
||||
public uint netId => netIdentity.netId;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="NetworkConnection">NetworkConnection</see> associated with this <see cref="NetworkIdentity">NetworkIdentity.</see> This is only valid for player objects on the client.
|
||||
/// </summary>
|
||||
public NetworkConnection connectionToServer => netIdentity.connectionToServer;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="NetworkConnection">NetworkConnection</see> associated with this <see cref="NetworkIdentity">NetworkIdentity.</see> This is only valid for player objects on the server.
|
||||
/// </summary>
|
||||
public NetworkConnection connectionToClient => netIdentity.connectionToClient;
|
||||
|
||||
protected ulong syncVarDirtyBits { get; private set; }
|
||||
ulong syncVarHookGuard;
|
||||
|
||||
protected bool getSyncVarHookGuard(ulong dirtyBit)
|
||||
{
|
||||
return (syncVarHookGuard & dirtyBit) != 0UL;
|
||||
}
|
||||
|
||||
protected void setSyncVarHookGuard(ulong dirtyBit, bool value)
|
||||
{
|
||||
if (value)
|
||||
syncVarHookGuard |= dirtyBit;
|
||||
else
|
||||
syncVarHookGuard &= ~dirtyBit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// objects that can synchronize themselves, such as synclists
|
||||
/// </summary>
|
||||
protected readonly List<SyncObject> syncObjects = new List<SyncObject>();
|
||||
|
||||
/// <summary>
|
||||
/// NetworkIdentity component caching for easier access
|
||||
/// </summary>
|
||||
NetworkIdentity netIdentityCache;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the NetworkIdentity of this object
|
||||
/// </summary>
|
||||
public NetworkIdentity netIdentity
|
||||
{
|
||||
get
|
||||
{
|
||||
if (netIdentityCache is null)
|
||||
{
|
||||
netIdentityCache = GetComponent<NetworkIdentity>();
|
||||
// do this 2nd check inside first if so that we are not checking == twice on unity Object
|
||||
if (netIdentityCache is null)
|
||||
{
|
||||
logger.LogError("There is no NetworkIdentity on " + name + ". Please add one.");
|
||||
}
|
||||
}
|
||||
return netIdentityCache;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the index of the component on this object
|
||||
/// </summary>
|
||||
public int ComponentIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
// note: FindIndex causes allocations, we search manually instead
|
||||
for (int i = 0; i < netIdentity.NetworkBehaviours.Length; i++)
|
||||
{
|
||||
NetworkBehaviour component = netIdentity.NetworkBehaviours[i];
|
||||
if (component == this)
|
||||
return i;
|
||||
}
|
||||
|
||||
// this should never happen
|
||||
logger.LogError("Could not find component in GameObject. You should not add/remove components in networked objects dynamically", this);
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// this gets called in the constructor by the weaver
|
||||
// for every SyncObject in the component (e.g. SyncLists).
|
||||
// We collect all of them and we synchronize them with OnSerialize/OnDeserialize
|
||||
protected void InitSyncObject(SyncObject syncObject)
|
||||
{
|
||||
if (syncObject == null)
|
||||
logger.LogError("Uninitialized SyncObject. Manually call the constructor on your SyncList, SyncSet or SyncDictionary", this);
|
||||
else
|
||||
syncObjects.Add(syncObject);
|
||||
}
|
||||
|
||||
#region Commands
|
||||
protected void SendCommandInternal(Type invokeClass, string cmdName, NetworkWriter writer, int channelId, bool ignoreAuthority = false)
|
||||
{
|
||||
// this was in Weaver before
|
||||
// NOTE: we could remove this later to allow calling Cmds on Server
|
||||
// to avoid Wrapper functions. a lot of people requested this.
|
||||
if (!NetworkClient.active)
|
||||
{
|
||||
logger.LogError($"Command Function {cmdName} called without an active client.");
|
||||
return;
|
||||
}
|
||||
// local players can always send commands, regardless of authority, other objects must have authority.
|
||||
if (!(ignoreAuthority || isLocalPlayer || hasAuthority))
|
||||
{
|
||||
logger.LogWarning($"Trying to send command for object without authority. {invokeClass.ToString()}.{cmdName}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ClientScene.readyConnection == null)
|
||||
{
|
||||
logger.LogError("Send command attempted with no client running [client=" + connectionToServer + "].");
|
||||
return;
|
||||
}
|
||||
|
||||
// construct the message
|
||||
CommandMessage message = new CommandMessage
|
||||
{
|
||||
netId = netId,
|
||||
componentIndex = ComponentIndex,
|
||||
// type+func so Inventory.RpcUse != Equipment.RpcUse
|
||||
functionHash = RemoteCallHelper.GetMethodHash(invokeClass, cmdName),
|
||||
// segment to avoid reader allocations
|
||||
payload = writer.ToArraySegment()
|
||||
};
|
||||
|
||||
ClientScene.readyConnection.Send(message, channelId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Client RPCs
|
||||
protected void SendRPCInternal(Type invokeClass, string rpcName, NetworkWriter writer, int channelId, bool excludeOwner)
|
||||
{
|
||||
// this was in Weaver before
|
||||
if (!NetworkServer.active)
|
||||
{
|
||||
logger.LogError("RPC Function " + rpcName + " called on Client.");
|
||||
return;
|
||||
}
|
||||
// This cannot use NetworkServer.active, as that is not specific to this object.
|
||||
if (!isServer)
|
||||
{
|
||||
logger.LogWarning("ClientRpc " + rpcName + " called on un-spawned object: " + name);
|
||||
return;
|
||||
}
|
||||
|
||||
// construct the message
|
||||
RpcMessage message = new RpcMessage
|
||||
{
|
||||
netId = netId,
|
||||
componentIndex = ComponentIndex,
|
||||
// type+func so Inventory.RpcUse != Equipment.RpcUse
|
||||
functionHash = RemoteCallHelper.GetMethodHash(invokeClass, rpcName),
|
||||
// segment to avoid reader allocations
|
||||
payload = writer.ToArraySegment()
|
||||
};
|
||||
|
||||
// The public facing parameter is excludeOwner in [ClientRpc]
|
||||
// so we negate it here to logically align with SendToReady.
|
||||
bool includeOwner = !excludeOwner;
|
||||
NetworkServer.SendToReady(netIdentity, message, includeOwner, channelId);
|
||||
}
|
||||
|
||||
protected void SendTargetRPCInternal(NetworkConnection conn, Type invokeClass, string rpcName, NetworkWriter writer, int channelId)
|
||||
{
|
||||
if (!NetworkServer.active)
|
||||
{
|
||||
logger.LogError($"TargetRPC {rpcName} called when server not active");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isServer)
|
||||
{
|
||||
logger.LogWarning($"TargetRpc {rpcName} called on {name} but that object has not been spawned or has been unspawned");
|
||||
return;
|
||||
}
|
||||
|
||||
// connection parameter is optional. assign if null.
|
||||
if (conn is null)
|
||||
{
|
||||
conn = connectionToClient;
|
||||
}
|
||||
|
||||
// if still null
|
||||
if (conn is null)
|
||||
{
|
||||
logger.LogError($"TargetRPC {rpcName} was given a null connection, make sure the object has an owner or you pass in the target connection");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(conn is NetworkConnectionToClient))
|
||||
{
|
||||
logger.LogError($"TargetRPC {rpcName} requires a NetworkConnectionToClient but was given {conn.GetType().Name}");
|
||||
return;
|
||||
}
|
||||
|
||||
// construct the message
|
||||
RpcMessage message = new RpcMessage
|
||||
{
|
||||
netId = netId,
|
||||
componentIndex = ComponentIndex,
|
||||
// type+func so Inventory.RpcUse != Equipment.RpcUse
|
||||
functionHash = RemoteCallHelper.GetMethodHash(invokeClass, rpcName),
|
||||
// segment to avoid reader allocations
|
||||
payload = writer.ToArraySegment()
|
||||
};
|
||||
|
||||
conn.Send(message, channelId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
// helper function for [SyncVar] GameObjects.
|
||||
// IMPORTANT: keep as 'protected', not 'internal', otherwise Weaver
|
||||
// can't resolve it
|
||||
protected bool SyncVarGameObjectEqual(GameObject newGameObject, uint netIdField)
|
||||
{
|
||||
uint newNetId = 0;
|
||||
if (newGameObject != null)
|
||||
{
|
||||
NetworkIdentity identity = newGameObject.GetComponent<NetworkIdentity>();
|
||||
if (identity != null)
|
||||
{
|
||||
newNetId = identity.netId;
|
||||
if (newNetId == 0)
|
||||
{
|
||||
logger.LogWarning("SetSyncVarGameObject GameObject " + newGameObject + " has a zero netId. Maybe it is not spawned yet?");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newNetId == netIdField;
|
||||
}
|
||||
|
||||
// helper function for [SyncVar] GameObjects.
|
||||
protected void SetSyncVarGameObject(GameObject newGameObject, ref GameObject gameObjectField, ulong dirtyBit, ref uint netIdField)
|
||||
{
|
||||
if (getSyncVarHookGuard(dirtyBit))
|
||||
return;
|
||||
|
||||
uint newNetId = 0;
|
||||
if (newGameObject != null)
|
||||
{
|
||||
NetworkIdentity identity = newGameObject.GetComponent<NetworkIdentity>();
|
||||
if (identity != null)
|
||||
{
|
||||
newNetId = identity.netId;
|
||||
if (newNetId == 0)
|
||||
{
|
||||
logger.LogWarning("SetSyncVarGameObject GameObject " + newGameObject + " has a zero netId. Maybe it is not spawned yet?");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.LogEnabled()) logger.Log("SetSyncVar GameObject " + GetType().Name + " bit [" + dirtyBit + "] netfieldId:" + netIdField + "->" + newNetId);
|
||||
SetDirtyBit(dirtyBit);
|
||||
// assign new one on the server, and in case we ever need it on client too
|
||||
gameObjectField = newGameObject;
|
||||
netIdField = newNetId;
|
||||
}
|
||||
|
||||
// helper function for [SyncVar] GameObjects.
|
||||
// -> ref GameObject as second argument makes OnDeserialize processing easier
|
||||
protected GameObject GetSyncVarGameObject(uint netId, ref GameObject gameObjectField)
|
||||
{
|
||||
// server always uses the field
|
||||
if (isServer)
|
||||
{
|
||||
return gameObjectField;
|
||||
}
|
||||
|
||||
// client always looks up based on netId because objects might get in and out of range
|
||||
// over and over again, which shouldn't null them forever
|
||||
if (NetworkIdentity.spawned.TryGetValue(netId, out NetworkIdentity identity) && identity != null)
|
||||
return gameObjectField = identity.gameObject;
|
||||
return null;
|
||||
}
|
||||
|
||||
// helper function for [SyncVar] NetworkIdentities.
|
||||
// IMPORTANT: keep as 'protected', not 'internal', otherwise Weaver
|
||||
// can't resolve it
|
||||
protected bool SyncVarNetworkIdentityEqual(NetworkIdentity newIdentity, uint netIdField)
|
||||
{
|
||||
uint newNetId = 0;
|
||||
if (newIdentity != null)
|
||||
{
|
||||
newNetId = newIdentity.netId;
|
||||
if (newNetId == 0)
|
||||
{
|
||||
logger.LogWarning("SetSyncVarNetworkIdentity NetworkIdentity " + newIdentity + " has a zero netId. Maybe it is not spawned yet?");
|
||||
}
|
||||
}
|
||||
|
||||
// netId changed?
|
||||
return newNetId == netIdField;
|
||||
}
|
||||
|
||||
// helper function for [SyncVar] NetworkIdentities.
|
||||
protected void SetSyncVarNetworkIdentity(NetworkIdentity newIdentity, ref NetworkIdentity identityField, ulong dirtyBit, ref uint netIdField)
|
||||
{
|
||||
if (getSyncVarHookGuard(dirtyBit))
|
||||
return;
|
||||
|
||||
uint newNetId = 0;
|
||||
if (newIdentity != null)
|
||||
{
|
||||
newNetId = newIdentity.netId;
|
||||
if (newNetId == 0)
|
||||
{
|
||||
logger.LogWarning("SetSyncVarNetworkIdentity NetworkIdentity " + newIdentity + " has a zero netId. Maybe it is not spawned yet?");
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.LogEnabled()) logger.Log("SetSyncVarNetworkIdentity NetworkIdentity " + GetType().Name + " bit [" + dirtyBit + "] netIdField:" + netIdField + "->" + newNetId);
|
||||
SetDirtyBit(dirtyBit);
|
||||
netIdField = newNetId;
|
||||
// assign new one on the server, and in case we ever need it on client too
|
||||
identityField = newIdentity;
|
||||
}
|
||||
|
||||
// helper function for [SyncVar] NetworkIdentities.
|
||||
// -> ref GameObject as second argument makes OnDeserialize processing easier
|
||||
protected NetworkIdentity GetSyncVarNetworkIdentity(uint netId, ref NetworkIdentity identityField)
|
||||
{
|
||||
// server always uses the field
|
||||
if (isServer)
|
||||
{
|
||||
return identityField;
|
||||
}
|
||||
|
||||
// client always looks up based on netId because objects might get in and out of range
|
||||
// over and over again, which shouldn't null them forever
|
||||
NetworkIdentity.spawned.TryGetValue(netId, out identityField);
|
||||
return identityField;
|
||||
}
|
||||
|
||||
protected bool SyncVarEqual<T>(T value, ref T fieldValue)
|
||||
{
|
||||
// newly initialized or changed value?
|
||||
return EqualityComparer<T>.Default.Equals(value, fieldValue);
|
||||
}
|
||||
|
||||
protected void SetSyncVar<T>(T value, ref T fieldValue, ulong dirtyBit)
|
||||
{
|
||||
if (logger.LogEnabled()) logger.Log("SetSyncVar " + GetType().Name + " bit [" + dirtyBit + "] " + fieldValue + "->" + value);
|
||||
SetDirtyBit(dirtyBit);
|
||||
fieldValue = value;
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Used to set the behaviour as dirty, so that a network update will be sent for the object.
|
||||
/// these are masks, not bit numbers, ie. 0x004 not 2
|
||||
/// </summary>
|
||||
/// <param name="dirtyBit">Bit mask to set.</param>
|
||||
public void SetDirtyBit(ulong dirtyBit)
|
||||
{
|
||||
syncVarDirtyBits |= dirtyBit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This clears all the dirty bits that were set on this script by SetDirtyBits();
|
||||
/// <para>This is automatically invoked when an update is sent for this object, but can be called manually as well.</para>
|
||||
/// </summary>
|
||||
public void ClearAllDirtyBits()
|
||||
{
|
||||
lastSyncTime = Time.time;
|
||||
syncVarDirtyBits = 0L;
|
||||
|
||||
// flush all unsynchronized changes in syncobjects
|
||||
// note: don't use List.ForEach here, this is a hot path
|
||||
// List.ForEach: 432b/frame
|
||||
// for: 231b/frame
|
||||
for (int i = 0; i < syncObjects.Count; ++i)
|
||||
{
|
||||
syncObjects[i].Flush();
|
||||
}
|
||||
}
|
||||
|
||||
bool AnySyncObjectDirty()
|
||||
{
|
||||
// note: don't use Linq here. 1200 networked objects:
|
||||
// Linq: 187KB GC/frame;, 2.66ms time
|
||||
// for: 8KB GC/frame; 1.28ms time
|
||||
for (int i = 0; i < syncObjects.Count; ++i)
|
||||
{
|
||||
if (syncObjects[i].IsDirty)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsDirty()
|
||||
{
|
||||
if (Time.time - lastSyncTime >= syncInterval)
|
||||
{
|
||||
return syncVarDirtyBits != 0L || AnySyncObjectDirty();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Virtual function to override to send custom serialization data. The corresponding function to send serialization data is OnDeserialize().
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The initialState flag is useful to differentiate between the first time an object is serialized and when incremental updates can be sent. The first time an object is sent to a client, it must include a full state snapshot, but subsequent updates can save on bandwidth by including only incremental changes. Note that SyncVar hook functions are not called when initialState is true, only for incremental updates.</para>
|
||||
/// <para>If a class has SyncVars, then an implementation of this function and OnDeserialize() are added automatically to the class. So a class that has SyncVars cannot also have custom serialization functions.</para>
|
||||
/// <para>The OnSerialize function should return true to indicate that an update should be sent. If it returns true, then the dirty bits for that script are set to zero, if it returns false then the dirty bits are not changed. This allows multiple changes to a script to be accumulated over time and sent when the system is ready, instead of every frame.</para>
|
||||
/// </remarks>
|
||||
/// <param name="writer">Writer to use to write to the stream.</param>
|
||||
/// <param name="initialState">If this is being called to send initial state.</param>
|
||||
/// <returns>True if data was written.</returns>
|
||||
public virtual bool OnSerialize(NetworkWriter writer, bool initialState)
|
||||
{
|
||||
bool objectWritten = false;
|
||||
// if initialState: write all SyncVars.
|
||||
// otherwise write dirtyBits+dirty SyncVars
|
||||
if (initialState)
|
||||
{
|
||||
objectWritten = SerializeObjectsAll(writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
objectWritten = SerializeObjectsDelta(writer);
|
||||
}
|
||||
|
||||
bool syncVarWritten = SerializeSyncVars(writer, initialState);
|
||||
|
||||
return objectWritten || syncVarWritten;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Virtual function to override to receive custom serialization data. The corresponding function to send serialization data is OnSerialize().
|
||||
/// </summary>
|
||||
/// <param name="reader">Reader to read from the stream.</param>
|
||||
/// <param name="initialState">True if being sent initial state.</param>
|
||||
public virtual void OnDeserialize(NetworkReader reader, bool initialState)
|
||||
{
|
||||
if (initialState)
|
||||
{
|
||||
DeSerializeObjectsAll(reader);
|
||||
}
|
||||
else
|
||||
{
|
||||
DeSerializeObjectsDelta(reader);
|
||||
}
|
||||
|
||||
DeserializeSyncVars(reader, initialState);
|
||||
}
|
||||
|
||||
// Don't rename. Weaver uses this exact function name.
|
||||
protected virtual bool SerializeSyncVars(NetworkWriter writer, bool initialState)
|
||||
{
|
||||
return false;
|
||||
|
||||
// SyncVar are writen here in subclass
|
||||
|
||||
// if initialState
|
||||
// write all SyncVars
|
||||
// else
|
||||
// write syncVarDirtyBits
|
||||
// write dirty SyncVars
|
||||
}
|
||||
|
||||
// Don't rename. Weaver uses this exact function name.
|
||||
protected virtual void DeserializeSyncVars(NetworkReader reader, bool initialState)
|
||||
{
|
||||
// SyncVars are read here in subclass
|
||||
|
||||
// if initialState
|
||||
// read all SyncVars
|
||||
// else
|
||||
// read syncVarDirtyBits
|
||||
// read dirty SyncVars
|
||||
}
|
||||
|
||||
internal ulong DirtyObjectBits()
|
||||
{
|
||||
ulong dirtyObjects = 0;
|
||||
for (int i = 0; i < syncObjects.Count; i++)
|
||||
{
|
||||
SyncObject syncObject = syncObjects[i];
|
||||
if (syncObject.IsDirty)
|
||||
{
|
||||
dirtyObjects |= 1UL << i;
|
||||
}
|
||||
}
|
||||
return dirtyObjects;
|
||||
}
|
||||
|
||||
public bool SerializeObjectsAll(NetworkWriter writer)
|
||||
{
|
||||
bool dirty = false;
|
||||
for (int i = 0; i < syncObjects.Count; i++)
|
||||
{
|
||||
SyncObject syncObject = syncObjects[i];
|
||||
syncObject.OnSerializeAll(writer);
|
||||
dirty = true;
|
||||
}
|
||||
return dirty;
|
||||
}
|
||||
|
||||
public bool SerializeObjectsDelta(NetworkWriter writer)
|
||||
{
|
||||
bool dirty = false;
|
||||
// write the mask
|
||||
writer.WritePackedUInt64(DirtyObjectBits());
|
||||
// serializable objects, such as synclists
|
||||
for (int i = 0; i < syncObjects.Count; i++)
|
||||
{
|
||||
SyncObject syncObject = syncObjects[i];
|
||||
if (syncObject.IsDirty)
|
||||
{
|
||||
syncObject.OnSerializeDelta(writer);
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
return dirty;
|
||||
}
|
||||
|
||||
internal void DeSerializeObjectsAll(NetworkReader reader)
|
||||
{
|
||||
for (int i = 0; i < syncObjects.Count; i++)
|
||||
{
|
||||
SyncObject syncObject = syncObjects[i];
|
||||
syncObject.OnDeserializeAll(reader);
|
||||
}
|
||||
}
|
||||
|
||||
internal void DeSerializeObjectsDelta(NetworkReader reader)
|
||||
{
|
||||
ulong dirty = reader.ReadPackedUInt64();
|
||||
for (int i = 0; i < syncObjects.Count; i++)
|
||||
{
|
||||
SyncObject syncObject = syncObjects[i];
|
||||
if ((dirty & (1UL << i)) != 0)
|
||||
{
|
||||
syncObject.OnDeserializeDelta(reader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void ResetSyncObjects()
|
||||
{
|
||||
foreach (SyncObject syncObject in syncObjects)
|
||||
{
|
||||
syncObject.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is invoked on clients when the server has caused this object to be destroyed.
|
||||
/// <para>This can be used as a hook to invoke effects or do client specific cleanup.</para>
|
||||
/// </summary>
|
||||
public virtual void OnStopClient() { }
|
||||
|
||||
/// <summary>
|
||||
/// This is invoked for NetworkBehaviour objects when they become active on the server.
|
||||
/// <para>This could be triggered by NetworkServer.Listen() for objects in the scene, or by NetworkServer.Spawn() for objects that are dynamically created.</para>
|
||||
/// <para>This will be called for objects on a "host" as well as for object on a dedicated server.</para>
|
||||
/// </summary>
|
||||
public virtual void OnStartServer() { }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked on the server when the object is unspawned
|
||||
/// <para>Useful for saving object data in persistant storage</para>
|
||||
/// </summary>
|
||||
public virtual void OnStopServer() { }
|
||||
|
||||
/// <summary>
|
||||
/// Called on every NetworkBehaviour when it is activated on a client.
|
||||
/// <para>Objects on the host have this function called, as there is a local client on the host. The values of SyncVars on object are guaranteed to be initialized correctly with the latest state from the server when this function is called on the client.</para>
|
||||
/// </summary>
|
||||
public virtual void OnStartClient() { }
|
||||
|
||||
/// <summary>
|
||||
/// Called when the local player object has been set up.
|
||||
/// <para>This happens after OnStartClient(), as it is triggered by an ownership message from the server. This is an appropriate place to activate components or functionality that should only be active for the local player, such as cameras and input.</para>
|
||||
/// </summary>
|
||||
public virtual void OnStartLocalPlayer() { }
|
||||
|
||||
/// <summary>
|
||||
/// This is invoked on behaviours that have authority, based on context and <see cref="NetworkIdentity.hasAuthority">NetworkIdentity.hasAuthority</see>.
|
||||
/// <para>This is called after <see cref="OnStartServer">OnStartServer</see> and before <see cref="OnStartClient">OnStartClient.</see></para>
|
||||
/// <para>When <see cref="NetworkIdentity.AssignClientAuthority">AssignClientAuthority</see> is called on the server, this will be called on the client that owns the object. When an object is spawned with <see cref="NetworkServer.Spawn">NetworkServer.Spawn</see> with a NetworkConnection parameter included, this will be called on the client that owns the object.</para>
|
||||
/// </summary>
|
||||
public virtual void OnStartAuthority() { }
|
||||
|
||||
/// <summary>
|
||||
/// This is invoked on behaviours when authority is removed.
|
||||
/// <para>When NetworkIdentity.RemoveClientAuthority is called on the server, this will be called on the client that owns the object.</para>
|
||||
/// </summary>
|
||||
public virtual void OnStopAuthority() { }
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/NetworkBehaviour.cs.meta
Normal file
11
Assets/Mirror/Runtime/NetworkBehaviour.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 655ee8cba98594f70880da5cc4dc442d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
384
Assets/Mirror/Runtime/NetworkClient.cs
Normal file
384
Assets/Mirror/Runtime/NetworkClient.cs
Normal file
@@ -0,0 +1,384 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
public enum ConnectState
|
||||
{
|
||||
None,
|
||||
Connecting,
|
||||
Connected,
|
||||
Disconnected
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a network client class used by the networking system. It contains a NetworkConnection that is used to connect to a network server.
|
||||
/// <para>The <see cref="NetworkClient">NetworkClient</see> handle connection state, messages handlers, and connection configuration. There can be many <see cref="NetworkClient">NetworkClient</see> instances in a process at a time, but only one that is connected to a game server (<see cref="NetworkServer">NetworkServer</see>) that uses spawned objects.</para>
|
||||
/// <para><see cref="NetworkClient">NetworkClient</see> has an internal update function where it handles events from the transport layer. This includes asynchronous connect events, disconnect events and incoming data from a server.</para>
|
||||
/// <para>The <see cref="NetworkManager">NetworkManager</see> has a NetworkClient instance that it uses for games that it starts, but the NetworkClient may be used by itself.</para>
|
||||
/// </summary>
|
||||
public static class NetworkClient
|
||||
{
|
||||
static readonly ILogger logger = LogFactory.GetLogger(typeof(NetworkClient));
|
||||
|
||||
/// <summary>
|
||||
/// The registered network message handlers.
|
||||
/// </summary>
|
||||
static readonly Dictionary<int, NetworkMessageDelegate> handlers = new Dictionary<int, NetworkMessageDelegate>();
|
||||
|
||||
/// <summary>
|
||||
/// The NetworkConnection object this client is using.
|
||||
/// </summary>
|
||||
public static NetworkConnection connection { get; internal set; }
|
||||
|
||||
internal static ConnectState connectState = ConnectState.None;
|
||||
|
||||
/// <summary>
|
||||
/// The IP address of the server that this client is connected to.
|
||||
/// <para>This will be empty if the client has not connected yet.</para>
|
||||
/// </summary>
|
||||
public static string serverIp => connection.address;
|
||||
|
||||
/// <summary>
|
||||
/// active is true while a client is connecting/connected
|
||||
/// (= while the network is active)
|
||||
/// </summary>
|
||||
public static bool active => connectState == ConnectState.Connecting || connectState == ConnectState.Connected;
|
||||
|
||||
/// <summary>
|
||||
/// This gives the current connection status of the client.
|
||||
/// </summary>
|
||||
public static bool isConnected => connectState == ConnectState.Connected;
|
||||
|
||||
/// <summary>
|
||||
/// NetworkClient can connect to local server in host mode too
|
||||
/// </summary>
|
||||
public static bool isLocalClient => connection is ULocalConnectionToServer;
|
||||
|
||||
/// <summary>
|
||||
/// Connect client to a NetworkServer instance.
|
||||
/// </summary>
|
||||
/// <param name="address"></param>
|
||||
public static void Connect(string address)
|
||||
{
|
||||
if (logger.LogEnabled()) logger.Log("Client Connect: " + address);
|
||||
logger.Assert(Transport.activeTransport != null, "There was no active transport when calling NetworkClient.Connect, If you are calling Connect manually then make sure to set 'Transport.activeTransport' first");
|
||||
|
||||
RegisterSystemHandlers(false);
|
||||
Transport.activeTransport.enabled = true;
|
||||
InitializeTransportHandlers();
|
||||
|
||||
connectState = ConnectState.Connecting;
|
||||
Transport.activeTransport.ClientConnect(address);
|
||||
|
||||
// setup all the handlers
|
||||
connection = new NetworkConnectionToServer();
|
||||
connection.SetHandlers(handlers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connect client to a NetworkServer instance.
|
||||
/// </summary>
|
||||
/// <param name="uri">Address of the server to connect to</param>
|
||||
public static void Connect(Uri uri)
|
||||
{
|
||||
if (logger.LogEnabled()) logger.Log("Client Connect: " + uri);
|
||||
logger.Assert(Transport.activeTransport != null, "There was no active transport when calling NetworkClient.Connect, If you are calling Connect manually then make sure to set 'Transport.activeTransport' first");
|
||||
|
||||
RegisterSystemHandlers(false);
|
||||
Transport.activeTransport.enabled = true;
|
||||
InitializeTransportHandlers();
|
||||
|
||||
connectState = ConnectState.Connecting;
|
||||
Transport.activeTransport.ClientConnect(uri);
|
||||
|
||||
// setup all the handlers
|
||||
connection = new NetworkConnectionToServer();
|
||||
connection.SetHandlers(handlers);
|
||||
}
|
||||
|
||||
public static void ConnectHost()
|
||||
{
|
||||
logger.Log("Client Connect Host to Server");
|
||||
|
||||
RegisterSystemHandlers(true);
|
||||
|
||||
connectState = ConnectState.Connected;
|
||||
|
||||
// create local connection objects and connect them
|
||||
ULocalConnectionToServer connectionToServer = new ULocalConnectionToServer();
|
||||
ULocalConnectionToClient connectionToClient = new ULocalConnectionToClient();
|
||||
connectionToServer.connectionToClient = connectionToClient;
|
||||
connectionToClient.connectionToServer = connectionToServer;
|
||||
|
||||
connection = connectionToServer;
|
||||
connection.SetHandlers(handlers);
|
||||
|
||||
// create server connection to local client
|
||||
NetworkServer.SetLocalConnection(connectionToClient);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// connect host mode
|
||||
/// </summary>
|
||||
public static void ConnectLocalServer()
|
||||
{
|
||||
NetworkServer.OnConnected(NetworkServer.localConnection);
|
||||
NetworkServer.localConnection.Send(new ConnectMessage());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// disconnect host mode. this is needed to call DisconnectMessage for
|
||||
/// the host client too.
|
||||
/// </summary>
|
||||
public static void DisconnectLocalServer()
|
||||
{
|
||||
// only if host connection is running
|
||||
if (NetworkServer.localConnection != null)
|
||||
{
|
||||
// TODO ConnectLocalServer manually sends a ConnectMessage to the
|
||||
// local connection. should we send a DisconnectMessage here too?
|
||||
// (if we do then we get an Unknown Message ID log)
|
||||
//NetworkServer.localConnection.Send(new DisconnectMessage());
|
||||
NetworkServer.OnDisconnected(NetworkServer.localConnection.connectionId);
|
||||
}
|
||||
}
|
||||
|
||||
static void InitializeTransportHandlers()
|
||||
{
|
||||
Transport.activeTransport.OnClientConnected.AddListener(OnConnected);
|
||||
Transport.activeTransport.OnClientDataReceived.AddListener(OnDataReceived);
|
||||
Transport.activeTransport.OnClientDisconnected.AddListener(OnDisconnected);
|
||||
Transport.activeTransport.OnClientError.AddListener(OnError);
|
||||
}
|
||||
|
||||
static void OnError(Exception exception)
|
||||
{
|
||||
logger.LogException(exception);
|
||||
}
|
||||
|
||||
static void OnDisconnected()
|
||||
{
|
||||
connectState = ConnectState.Disconnected;
|
||||
|
||||
ClientScene.HandleClientDisconnect(connection);
|
||||
|
||||
connection?.InvokeHandler(new DisconnectMessage(), -1);
|
||||
}
|
||||
|
||||
internal static void OnDataReceived(ArraySegment<byte> data, int channelId)
|
||||
{
|
||||
if (connection != null)
|
||||
{
|
||||
connection.TransportReceive(data, channelId);
|
||||
}
|
||||
else logger.LogError("Skipped Data message handling because connection is null.");
|
||||
}
|
||||
|
||||
static void OnConnected()
|
||||
{
|
||||
if (connection != null)
|
||||
{
|
||||
// reset network time stats
|
||||
NetworkTime.Reset();
|
||||
|
||||
// the handler may want to send messages to the client
|
||||
// thus we should set the connected state before calling the handler
|
||||
connectState = ConnectState.Connected;
|
||||
NetworkTime.UpdateClient();
|
||||
connection.InvokeHandler(new ConnectMessage(), -1);
|
||||
}
|
||||
else logger.LogError("Skipped Connect message handling because connection is null.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect from server.
|
||||
/// <para>The disconnect message will be invoked.</para>
|
||||
/// </summary>
|
||||
public static void Disconnect()
|
||||
{
|
||||
connectState = ConnectState.Disconnected;
|
||||
ClientScene.HandleClientDisconnect(connection);
|
||||
|
||||
// local or remote connection?
|
||||
if (isLocalClient)
|
||||
{
|
||||
if (isConnected)
|
||||
{
|
||||
NetworkServer.localConnection.Send(new DisconnectMessage());
|
||||
}
|
||||
NetworkServer.RemoveLocalConnection();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (connection != null)
|
||||
{
|
||||
connection.Disconnect();
|
||||
connection = null;
|
||||
RemoveTransportHandlers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void RemoveTransportHandlers()
|
||||
{
|
||||
// so that we don't register them more than once
|
||||
Transport.activeTransport.OnClientConnected.RemoveListener(OnConnected);
|
||||
Transport.activeTransport.OnClientDataReceived.RemoveListener(OnDataReceived);
|
||||
Transport.activeTransport.OnClientDisconnected.RemoveListener(OnDisconnected);
|
||||
Transport.activeTransport.OnClientError.RemoveListener(OnError);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This sends a network message with a message Id to the server. This message is sent on channel zero, which by default is the reliable channel.
|
||||
/// <para>The message must be an instance of a class derived from MessageBase.</para>
|
||||
/// <para>The message id passed to Send() is used to identify the handler function to invoke on the server when the message is received.</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type to unregister.</typeparam>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="channelId"></param>
|
||||
public static void Send<T>(T message, int channelId = Channels.DefaultReliable) where T : NetworkMessage
|
||||
{
|
||||
if (connection != null)
|
||||
{
|
||||
if (connectState == ConnectState.Connected)
|
||||
{
|
||||
connection.Send(message, channelId);
|
||||
}
|
||||
else logger.LogError("NetworkClient Send when not connected to a server");
|
||||
}
|
||||
else logger.LogError("NetworkClient Send with no connection");
|
||||
}
|
||||
|
||||
public static void Update()
|
||||
{
|
||||
// local connection?
|
||||
if (connection is ULocalConnectionToServer localConnection)
|
||||
{
|
||||
localConnection.Update();
|
||||
}
|
||||
// remote connection?
|
||||
else
|
||||
{
|
||||
// only update things while connected
|
||||
if (active && connectState == ConnectState.Connected)
|
||||
{
|
||||
NetworkTime.UpdateClient();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RegisterSystemHandlers(bool hostMode)
|
||||
{
|
||||
// host mode client / regular client react to some messages differently.
|
||||
// but we still need to add handlers for all of them to avoid
|
||||
// 'message id not found' errors.
|
||||
if (hostMode)
|
||||
{
|
||||
RegisterHandler<ObjectDestroyMessage>(ClientScene.OnHostClientObjectDestroy);
|
||||
RegisterHandler<ObjectHideMessage>(ClientScene.OnHostClientObjectHide);
|
||||
RegisterHandler<NetworkPongMessage>((conn, msg) => { }, false);
|
||||
RegisterHandler<SpawnMessage>(ClientScene.OnHostClientSpawn);
|
||||
// host mode doesn't need spawning
|
||||
RegisterHandler<ObjectSpawnStartedMessage>((conn, msg) => { });
|
||||
// host mode doesn't need spawning
|
||||
RegisterHandler<ObjectSpawnFinishedMessage>((conn, msg) => { });
|
||||
RegisterHandler<UpdateVarsMessage>((conn, msg) => { });
|
||||
}
|
||||
else
|
||||
{
|
||||
RegisterHandler<ObjectDestroyMessage>(ClientScene.OnObjectDestroy);
|
||||
RegisterHandler<ObjectHideMessage>(ClientScene.OnObjectHide);
|
||||
RegisterHandler<NetworkPongMessage>(NetworkTime.OnClientPong, false);
|
||||
RegisterHandler<SpawnMessage>(ClientScene.OnSpawn);
|
||||
RegisterHandler<ObjectSpawnStartedMessage>(ClientScene.OnObjectSpawnStarted);
|
||||
RegisterHandler<ObjectSpawnFinishedMessage>(ClientScene.OnObjectSpawnFinished);
|
||||
RegisterHandler<UpdateVarsMessage>(ClientScene.OnUpdateVarsMessage);
|
||||
}
|
||||
RegisterHandler<RpcMessage>(ClientScene.OnRPCMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a handler for a particular message type.
|
||||
/// <para>There are several system message types which you can add handlers for. You can also add your own message types.</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Message type</typeparam>
|
||||
/// <param name="handler">Function handler which will be invoked when this message type is received.</param>
|
||||
/// <param name="requireAuthentication">True if the message requires an authenticated connection</param>
|
||||
public static void RegisterHandler<T>(Action<NetworkConnection, T> handler, bool requireAuthentication = true) where T : NetworkMessage
|
||||
{
|
||||
int msgType = MessagePacker.GetId<T>();
|
||||
if (handlers.ContainsKey(msgType))
|
||||
{
|
||||
logger.LogWarning($"NetworkClient.RegisterHandler replacing handler for {typeof(T).FullName}, id={msgType}. If replacement is intentional, use ReplaceHandler instead to avoid this warning.");
|
||||
}
|
||||
handlers[msgType] = MessagePacker.MessageHandler(handler, requireAuthentication);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a handler for a particular message type.
|
||||
/// <para>There are several system message types which you can add handlers for. You can also add your own message types.</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Message type</typeparam>
|
||||
/// <param name="handler">Function handler which will be invoked when this message type is received.</param>
|
||||
/// <param name="requireAuthentication">True if the message requires an authenticated connection</param>
|
||||
public static void RegisterHandler<T>(Action<T> handler, bool requireAuthentication = true) where T : NetworkMessage
|
||||
{
|
||||
RegisterHandler((NetworkConnection _, T value) => { handler(value); }, requireAuthentication);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces a handler for a particular message type.
|
||||
/// <para>See also <see cref="RegisterHandler{T}(Action{NetworkConnection, T}, bool)">RegisterHandler(T)(Action(NetworkConnection, T), bool)</see></para>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Message type</typeparam>
|
||||
/// <param name="handler">Function handler which will be invoked when this message type is received.</param>
|
||||
/// <param name="requireAuthentication">True if the message requires an authenticated connection</param>
|
||||
public static void ReplaceHandler<T>(Action<NetworkConnection, T> handler, bool requireAuthentication = true) where T : NetworkMessage
|
||||
{
|
||||
int msgType = MessagePacker.GetId<T>();
|
||||
handlers[msgType] = MessagePacker.MessageHandler(handler, requireAuthentication);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces a handler for a particular message type.
|
||||
/// <para>See also <see cref="RegisterHandler{T}(Action{NetworkConnection, T}, bool)">RegisterHandler(T)(Action(NetworkConnection, T), bool)</see></para>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Message type</typeparam>
|
||||
/// <param name="handler">Function handler which will be invoked when this message type is received.</param>
|
||||
/// <param name="requireAuthentication">True if the message requires an authenticated connection</param>
|
||||
public static void ReplaceHandler<T>(Action<T> handler, bool requireAuthentication = true) where T : NetworkMessage
|
||||
{
|
||||
ReplaceHandler((NetworkConnection _, T value) => { handler(value); }, requireAuthentication);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a network message handler.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type to unregister.</typeparam>
|
||||
public static bool UnregisterHandler<T>() where T : NetworkMessage
|
||||
{
|
||||
// use int to minimize collisions
|
||||
int msgType = MessagePacker.GetId<T>();
|
||||
return handlers.Remove(msgType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shut down a client.
|
||||
/// <para>This should be done when a client is no longer going to be used.</para>
|
||||
/// </summary>
|
||||
public static void Shutdown()
|
||||
{
|
||||
logger.Log("Shutting down client.");
|
||||
ClientScene.Shutdown();
|
||||
connectState = ConnectState.None;
|
||||
handlers.Clear();
|
||||
// disconnect the client connection.
|
||||
// we do NOT call Transport.Shutdown, because someone only called
|
||||
// NetworkClient.Shutdown. we can't assume that the server is
|
||||
// supposed to be shut down too!
|
||||
Transport.activeTransport.ClientDisconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/NetworkClient.cs.meta
Normal file
11
Assets/Mirror/Runtime/NetworkClient.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: abe6be14204d94224a3e7cd99dd2ea73
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
301
Assets/Mirror/Runtime/NetworkConnection.cs
Normal file
301
Assets/Mirror/Runtime/NetworkConnection.cs
Normal file
@@ -0,0 +1,301 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
/// <summary>
|
||||
/// A High level network connection. This is used for connections from client-to-server and for connection from server-to-client.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>A NetworkConnection corresponds to a specific connection for a host in the transport layer. It has a connectionId that is assigned by the transport layer and passed to the Initialize function.</para>
|
||||
/// <para>A NetworkClient has one NetworkConnection. A NetworkServerSimple manages multiple NetworkConnections. The NetworkServer has multiple "remote" connections and a "local" connection for the local client.</para>
|
||||
/// <para>The NetworkConnection class provides message sending and handling facilities. For sending data over a network, there are methods to send message objects, byte arrays, and NetworkWriter objects. To handle data arriving from the network, handler functions can be registered for message Ids, byte arrays can be processed by HandleBytes(), and NetworkReader object can be processed by HandleReader().</para>
|
||||
/// <para>NetworkConnection objects also act as observers for networked objects. When a connection is an observer of a networked object with a NetworkIdentity, then the object will be visible to corresponding client for the connection, and incremental state changes will be sent to the client.</para>
|
||||
/// <para>There are many virtual functions on NetworkConnection that allow its behaviour to be customized. NetworkClient and NetworkServer can both be made to instantiate custom classes derived from NetworkConnection by setting their networkConnectionClass member variable.</para>
|
||||
/// </remarks>
|
||||
public abstract class NetworkConnection
|
||||
{
|
||||
public const int LocalConnectionId = 0;
|
||||
static readonly ILogger logger = LogFactory.GetLogger<NetworkConnection>();
|
||||
|
||||
// internal so it can be tested
|
||||
internal readonly HashSet<NetworkIdentity> visList = new HashSet<NetworkIdentity>();
|
||||
|
||||
Dictionary<int, NetworkMessageDelegate> messageHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// Unique identifier for this connection that is assigned by the transport layer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>On a server, this Id is unique for every connection on the server. On a client this Id is local to the client, it is not the same as the Id on the server for this connection.</para>
|
||||
/// <para>Transport layers connections begin at one. So on a client with a single connection to a server, the connectionId of that connection will be one. In NetworkServer, the connectionId of the local connection is zero.</para>
|
||||
/// <para>Clients do not know their connectionId on the server, and do not know the connectionId of other clients on the server.</para>
|
||||
/// </remarks>
|
||||
public readonly int connectionId;
|
||||
|
||||
/// <summary>
|
||||
/// Flag that indicates the client has been authenticated.
|
||||
/// </summary>
|
||||
public bool isAuthenticated;
|
||||
|
||||
/// <summary>
|
||||
/// General purpose object to hold authentication data, character selection, tokens, etc.
|
||||
/// associated with the connection for reference after Authentication completes.
|
||||
/// </summary>
|
||||
public object authenticationData;
|
||||
|
||||
/// <summary>
|
||||
/// Flag that tells if the connection has been marked as "ready" by a client calling ClientScene.Ready().
|
||||
/// <para>This property is read-only. It is set by the system on the client when ClientScene.Ready() is called, and set by the system on the server when a ready message is received from a client.</para>
|
||||
/// <para>A client that is ready is sent spawned objects by the server and updates to the state of spawned objects. A client that is not ready is not sent spawned objects.</para>
|
||||
/// </summary>
|
||||
public bool isReady;
|
||||
|
||||
/// <summary>
|
||||
/// The IP address / URL / FQDN associated with the connection.
|
||||
/// Can be useful for a game master to do IP Bans etc.
|
||||
/// </summary>
|
||||
public abstract string address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The last time that a message was received on this connection.
|
||||
/// <para>This includes internal system messages (such as Commands and ClientRpc calls) and user messages.</para>
|
||||
/// </summary>
|
||||
public float lastMessageTime;
|
||||
|
||||
/// <summary>
|
||||
/// The NetworkIdentity for this connection.
|
||||
/// </summary>
|
||||
public NetworkIdentity identity { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of the NetworkIdentity objects owned by this connection. This list is read-only.
|
||||
/// <para>This includes the player object for the connection - if it has localPlayerAutority set, and any objects spawned with local authority or set with AssignLocalAuthority.</para>
|
||||
/// <para>This list can be used to validate messages from clients, to ensure that clients are only trying to control objects that they own.</para>
|
||||
/// </summary>
|
||||
// IMPORTANT: this needs to be <NetworkIdentity>, not <uint netId>. fixes a bug where DestroyOwnedObjects wouldn't find
|
||||
// the netId anymore: https://github.com/vis2k/Mirror/issues/1380 . Works fine with NetworkIdentity pointers though.
|
||||
public readonly HashSet<NetworkIdentity> clientOwnedObjects = new HashSet<NetworkIdentity>();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new NetworkConnection
|
||||
/// </summary>
|
||||
internal NetworkConnection()
|
||||
{
|
||||
// set lastTime to current time when creating connection to make sure it isn't instantly kicked for inactivity
|
||||
lastMessageTime = Time.time;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new NetworkConnection with the specified connectionId
|
||||
/// </summary>
|
||||
/// <param name="networkConnectionId"></param>
|
||||
internal NetworkConnection(int networkConnectionId) : this()
|
||||
{
|
||||
connectionId = networkConnectionId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects this connection.
|
||||
/// </summary>
|
||||
public abstract void Disconnect();
|
||||
|
||||
internal void SetHandlers(Dictionary<int, NetworkMessageDelegate> handlers)
|
||||
{
|
||||
messageHandlers = handlers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This sends a network message with a message ID on the connection. This message is sent on channel zero, which by default is the reliable channel.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type to unregister.</typeparam>
|
||||
/// <param name="msg">The message to send.</param>
|
||||
/// <param name="channelId">The transport layer channel to send on.</param>
|
||||
public void Send<T>(T msg, int channelId = Channels.DefaultReliable) where T : NetworkMessage
|
||||
{
|
||||
using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter())
|
||||
{
|
||||
// pack message and send allocation free
|
||||
MessagePacker.Pack(msg, writer);
|
||||
NetworkDiagnostics.OnSend(msg, channelId, writer.Position, 1);
|
||||
Send(writer.ToArraySegment(), channelId);
|
||||
}
|
||||
}
|
||||
|
||||
// validate packet size before sending. show errors if too big/small.
|
||||
// => it's best to check this here, we can't assume that all transports
|
||||
// would check max size and show errors internally. best to do it
|
||||
// in one place in hlapi.
|
||||
// => it's important to log errors, so the user knows what went wrong.
|
||||
protected internal static bool ValidatePacketSize(ArraySegment<byte> segment, int channelId)
|
||||
{
|
||||
if (segment.Count > Transport.activeTransport.GetMaxPacketSize(channelId))
|
||||
{
|
||||
logger.LogError("NetworkConnection.ValidatePacketSize: cannot send packet larger than " + Transport.activeTransport.GetMaxPacketSize(channelId) + " bytes");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (segment.Count == 0)
|
||||
{
|
||||
// zero length packets getting into the packet queues are bad.
|
||||
logger.LogError("NetworkConnection.ValidatePacketSize: cannot send zero bytes");
|
||||
return false;
|
||||
}
|
||||
|
||||
// good size
|
||||
return true;
|
||||
}
|
||||
|
||||
// internal because no one except Mirror should send bytes directly to
|
||||
// the client. they would be detected as a message. send messages instead.
|
||||
internal abstract void Send(ArraySegment<byte> segment, int channelId = Channels.DefaultReliable);
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"connection({connectionId})";
|
||||
}
|
||||
|
||||
internal void AddToVisList(NetworkIdentity identity)
|
||||
{
|
||||
visList.Add(identity);
|
||||
|
||||
// spawn identity for this conn
|
||||
NetworkServer.ShowForConnection(identity, this);
|
||||
}
|
||||
|
||||
internal void RemoveFromVisList(NetworkIdentity identity, bool isDestroyed)
|
||||
{
|
||||
visList.Remove(identity);
|
||||
|
||||
if (!isDestroyed)
|
||||
{
|
||||
// hide identity for this conn
|
||||
NetworkServer.HideForConnection(identity, this);
|
||||
}
|
||||
}
|
||||
|
||||
internal void RemoveObservers()
|
||||
{
|
||||
foreach (NetworkIdentity identity in visList)
|
||||
{
|
||||
identity.RemoveObserverInternal(this);
|
||||
}
|
||||
visList.Clear();
|
||||
}
|
||||
|
||||
internal bool InvokeHandler(int msgType, NetworkReader reader, int channelId)
|
||||
{
|
||||
if (messageHandlers.TryGetValue(msgType, out NetworkMessageDelegate msgDelegate))
|
||||
{
|
||||
msgDelegate(this, reader, channelId);
|
||||
return true;
|
||||
}
|
||||
if (logger.LogEnabled()) logger.Log("Unknown message ID " + msgType + " " + this + ". May be due to no existing RegisterHandler for this message.");
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function invokes the registered handler function for a message.
|
||||
/// <para>Network connections used by the NetworkClient and NetworkServer use this function for handling network messages.</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type to unregister.</typeparam>
|
||||
/// <param name="msg">The message object to process.</param>
|
||||
/// <returns>Returns true if the handler was successfully invoked</returns>
|
||||
public bool InvokeHandler<T>(T msg, int channelId) where T : NetworkMessage
|
||||
{
|
||||
using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter())
|
||||
{
|
||||
// if it is a value type, just use typeof(T) to avoid boxing
|
||||
// this works because value types cannot be derived
|
||||
// if it is a reference type (for example NetworkMessage),
|
||||
// ask the message for the real type
|
||||
int msgType = MessagePacker.GetId(default(T) != null ? typeof(T) : msg.GetType());
|
||||
|
||||
MessagePacker.Pack(msg, writer);
|
||||
ArraySegment<byte> segment = writer.ToArraySegment();
|
||||
using (PooledNetworkReader networkReader = NetworkReaderPool.GetReader(segment))
|
||||
return InvokeHandler(msgType, networkReader, channelId);
|
||||
}
|
||||
}
|
||||
|
||||
// note: original HLAPI HandleBytes function handled >1 message in a while loop, but this wasn't necessary
|
||||
// anymore because NetworkServer/NetworkClient Update both use while loops to handle >1 data events per
|
||||
// frame already.
|
||||
// -> in other words, we always receive 1 message per Receive call, never two.
|
||||
// -> can be tested easily with a 1000ms send delay and then logging amount received in while loops here
|
||||
// and in NetworkServer/Client Update. HandleBytes already takes exactly one.
|
||||
/// <summary>
|
||||
/// This function allows custom network connection classes to process data from the network before it is passed to the application.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The data received.</param>
|
||||
internal void TransportReceive(ArraySegment<byte> buffer, int channelId)
|
||||
{
|
||||
if (buffer.Count == 0)
|
||||
{
|
||||
logger.LogError($"ConnectionRecv {this} Message was empty");
|
||||
return;
|
||||
}
|
||||
|
||||
// unpack message
|
||||
using (PooledNetworkReader networkReader = NetworkReaderPool.GetReader(buffer))
|
||||
{
|
||||
if (MessagePacker.UnpackMessage(networkReader, out int msgType))
|
||||
{
|
||||
// logging
|
||||
if (logger.LogEnabled()) logger.Log("ConnectionRecv " + this + " msgType:" + msgType + " content:" + BitConverter.ToString(buffer.Array, buffer.Offset, buffer.Count));
|
||||
|
||||
// try to invoke the handler for that message
|
||||
if (InvokeHandler(msgType, networkReader, channelId))
|
||||
{
|
||||
lastMessageTime = Time.time;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogError("Closed connection: " + this + ". Invalid message header.");
|
||||
Disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if cliet has sent a message within timeout
|
||||
/// <para>
|
||||
/// Some transports are unreliable at sending disconnect message to the server
|
||||
/// so this acts as a failsafe to make sure clients are kicked
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Client should send ping message to server every 2 seconds to keep this alive
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <returns>True if server has recently recieved a message</returns>
|
||||
internal virtual bool IsAlive(float timeout) => Time.time - lastMessageTime < timeout;
|
||||
|
||||
internal void AddOwnedObject(NetworkIdentity obj)
|
||||
{
|
||||
clientOwnedObjects.Add(obj);
|
||||
}
|
||||
|
||||
internal void RemoveOwnedObject(NetworkIdentity obj)
|
||||
{
|
||||
clientOwnedObjects.Remove(obj);
|
||||
}
|
||||
|
||||
internal void DestroyOwnedObjects()
|
||||
{
|
||||
// create a copy because the list might be modified when destroying
|
||||
HashSet<NetworkIdentity> tmp = new HashSet<NetworkIdentity>(clientOwnedObjects);
|
||||
foreach (NetworkIdentity netIdentity in tmp)
|
||||
{
|
||||
if (netIdentity != null)
|
||||
{
|
||||
NetworkServer.Destroy(netIdentity.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
// clear the hashset because we destroyed them all
|
||||
clientOwnedObjects.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/NetworkConnection.cs.meta
Normal file
11
Assets/Mirror/Runtime/NetworkConnection.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 11ea41db366624109af1f0834bcdde2f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
37
Assets/Mirror/Runtime/NetworkConnectionToClient.cs
Normal file
37
Assets/Mirror/Runtime/NetworkConnectionToClient.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
public class NetworkConnectionToClient : NetworkConnection
|
||||
{
|
||||
static readonly ILogger logger = LogFactory.GetLogger<NetworkConnectionToClient>();
|
||||
|
||||
public NetworkConnectionToClient(int networkConnectionId) : base(networkConnectionId) { }
|
||||
|
||||
public override string address => Transport.activeTransport.ServerGetClientAddress(connectionId);
|
||||
|
||||
internal override void Send(ArraySegment<byte> segment, int channelId = Channels.DefaultReliable)
|
||||
{
|
||||
if (logger.LogEnabled()) logger.Log("ConnectionSend " + this + " bytes:" + BitConverter.ToString(segment.Array, segment.Offset, segment.Count));
|
||||
|
||||
// validate packet size first.
|
||||
if (ValidatePacketSize(segment, channelId))
|
||||
{
|
||||
Transport.activeTransport.ServerSend(connectionId, channelId, segment);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects this connection.
|
||||
/// </summary>
|
||||
public override void Disconnect()
|
||||
{
|
||||
// set not ready and handle clientscene disconnect in any case
|
||||
// (might be client or host mode here)
|
||||
isReady = false;
|
||||
Transport.activeTransport.ServerDisconnect(connectionId);
|
||||
RemoveObservers();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/NetworkConnectionToClient.cs.meta
Normal file
11
Assets/Mirror/Runtime/NetworkConnectionToClient.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb2195f8b29d24f0680a57fde2e9fd09
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
35
Assets/Mirror/Runtime/NetworkConnectionToServer.cs
Normal file
35
Assets/Mirror/Runtime/NetworkConnectionToServer.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
public class NetworkConnectionToServer : NetworkConnection
|
||||
{
|
||||
static readonly ILogger logger = LogFactory.GetLogger<NetworkConnectionToServer>();
|
||||
|
||||
public override string address => "";
|
||||
|
||||
internal override void Send(ArraySegment<byte> segment, int channelId = Channels.DefaultReliable)
|
||||
{
|
||||
if (logger.LogEnabled()) logger.Log("ConnectionSend " + this + " bytes:" + BitConverter.ToString(segment.Array, segment.Offset, segment.Count));
|
||||
|
||||
// validate packet size first.
|
||||
if (ValidatePacketSize(segment, channelId))
|
||||
{
|
||||
Transport.activeTransport.ClientSend(channelId, segment);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects this connection.
|
||||
/// </summary>
|
||||
public override void Disconnect()
|
||||
{
|
||||
// set not ready and handle clientscene disconnect in any case
|
||||
// (might be client or host mode here)
|
||||
isReady = false;
|
||||
ClientScene.HandleClientDisconnect(this);
|
||||
Transport.activeTransport.ClientDisconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/NetworkConnectionToServer.cs.meta
Normal file
11
Assets/Mirror/Runtime/NetworkConnectionToServer.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 761977cbf38a34ded9dd89de45445675
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
80
Assets/Mirror/Runtime/NetworkDiagnostics.cs
Normal file
80
Assets/Mirror/Runtime/NetworkDiagnostics.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides profiling information from mirror
|
||||
/// A profiler can subscribe to these events and
|
||||
/// present the data in a friendly way to the user
|
||||
/// </summary>
|
||||
public static class NetworkDiagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes an outgoing message
|
||||
/// </summary>
|
||||
public readonly struct MessageInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The message being sent
|
||||
/// </summary>
|
||||
public readonly NetworkMessage message;
|
||||
/// <summary>
|
||||
/// channel through which the message was sent
|
||||
/// </summary>
|
||||
public readonly int channel;
|
||||
/// <summary>
|
||||
/// how big was the message (does not include transport headers)
|
||||
/// </summary>
|
||||
public readonly int bytes;
|
||||
/// <summary>
|
||||
/// How many connections was the message sent to
|
||||
/// If an object has a lot of observers this count could be high
|
||||
/// </summary>
|
||||
public readonly int count;
|
||||
|
||||
internal MessageInfo(NetworkMessage message, int channel, int bytes, int count)
|
||||
{
|
||||
this.message = message;
|
||||
this.channel = channel;
|
||||
this.bytes = bytes;
|
||||
this.count = count;
|
||||
}
|
||||
}
|
||||
|
||||
#region Out messages
|
||||
/// <summary>
|
||||
/// Event that gets raised when Mirror sends a message
|
||||
/// Subscribe to this if you want to diagnose the network
|
||||
/// </summary>
|
||||
public static event Action<MessageInfo> OutMessageEvent;
|
||||
|
||||
internal static void OnSend<T>(T message, int channel, int bytes, int count) where T : NetworkMessage
|
||||
{
|
||||
if (count > 0 && OutMessageEvent != null)
|
||||
{
|
||||
MessageInfo outMessage = new MessageInfo(message, channel, bytes, count);
|
||||
OutMessageEvent?.Invoke(outMessage);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region In messages
|
||||
|
||||
/// <summary>
|
||||
/// Event that gets raised when Mirror receives a message
|
||||
/// Subscribe to this if you want to profile the network
|
||||
/// </summary>
|
||||
public static event Action<MessageInfo> InMessageEvent;
|
||||
|
||||
internal static void OnReceive<T>(T message, int channel, int bytes) where T : NetworkMessage
|
||||
{
|
||||
if (InMessageEvent != null)
|
||||
{
|
||||
MessageInfo inMessage = new MessageInfo(message, channel, bytes, 1);
|
||||
InMessageEvent?.Invoke(inMessage);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/NetworkDiagnostics.cs.meta
Normal file
11
Assets/Mirror/Runtime/NetworkDiagnostics.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3754b39e5f8740fd93f3337b2c4274e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1518
Assets/Mirror/Runtime/NetworkIdentity.cs
Normal file
1518
Assets/Mirror/Runtime/NetworkIdentity.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Assets/Mirror/Runtime/NetworkIdentity.cs.meta
Normal file
11
Assets/Mirror/Runtime/NetworkIdentity.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9b91ecbcc199f4492b9a91e820070131
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1463
Assets/Mirror/Runtime/NetworkManager.cs
Normal file
1463
Assets/Mirror/Runtime/NetworkManager.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Assets/Mirror/Runtime/NetworkManager.cs.meta
Normal file
11
Assets/Mirror/Runtime/NetworkManager.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8aab4c8111b7c411b9b92cf3dbc5bd4e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
158
Assets/Mirror/Runtime/NetworkManagerHUD.cs
Normal file
158
Assets/Mirror/Runtime/NetworkManagerHUD.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
// vis2k: GUILayout instead of spacey += ...; removed Update hotkeys to avoid
|
||||
// confusion if someone accidentally presses one.
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
/// <summary>
|
||||
/// An extension for the NetworkManager that displays a default HUD for controlling the network state of the game.
|
||||
/// <para>This component also shows useful internal state for the networking system in the inspector window of the editor. It allows users to view connections, networked objects, message handlers, and packet statistics. This information can be helpful when debugging networked games.</para>
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("Network/NetworkManagerHUD")]
|
||||
[RequireComponent(typeof(NetworkManager))]
|
||||
[HelpURL("https://mirror-networking.com/docs/Components/NetworkManagerHUD.html")]
|
||||
public class NetworkManagerHUD : MonoBehaviour
|
||||
{
|
||||
NetworkManager manager;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to show the default control HUD at runtime.
|
||||
/// </summary>
|
||||
public bool showGUI = true;
|
||||
|
||||
/// <summary>
|
||||
/// The horizontal offset in pixels to draw the HUD runtime GUI at.
|
||||
/// </summary>
|
||||
public int offsetX;
|
||||
|
||||
/// <summary>
|
||||
/// The vertical offset in pixels to draw the HUD runtime GUI at.
|
||||
/// </summary>
|
||||
public int offsetY;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
manager = GetComponent<NetworkManager>();
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
if (!showGUI)
|
||||
return;
|
||||
|
||||
GUILayout.BeginArea(new Rect(10 + offsetX, 40 + offsetY, 215, 9999));
|
||||
if (!NetworkClient.isConnected && !NetworkServer.active)
|
||||
{
|
||||
StartButtons();
|
||||
}
|
||||
else
|
||||
{
|
||||
StatusLabels();
|
||||
}
|
||||
|
||||
// client ready
|
||||
if (NetworkClient.isConnected && !ClientScene.ready)
|
||||
{
|
||||
if (GUILayout.Button("Client Ready"))
|
||||
{
|
||||
ClientScene.Ready(NetworkClient.connection);
|
||||
|
||||
if (ClientScene.localPlayer == null)
|
||||
{
|
||||
ClientScene.AddPlayer(NetworkClient.connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StopButtons();
|
||||
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
|
||||
void StartButtons()
|
||||
{
|
||||
if (!NetworkClient.active)
|
||||
{
|
||||
// Server + Client
|
||||
if (Application.platform != RuntimePlatform.WebGLPlayer)
|
||||
{
|
||||
if (GUILayout.Button("Host (Server + Client)"))
|
||||
{
|
||||
manager.StartHost();
|
||||
}
|
||||
}
|
||||
|
||||
// Client + IP
|
||||
GUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("Client"))
|
||||
{
|
||||
manager.StartClient();
|
||||
}
|
||||
manager.networkAddress = GUILayout.TextField(manager.networkAddress);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// Server Only
|
||||
if (Application.platform == RuntimePlatform.WebGLPlayer)
|
||||
{
|
||||
// cant be a server in webgl build
|
||||
GUILayout.Box("( WebGL cannot be server )");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("Server Only")) manager.StartServer();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Connecting
|
||||
GUILayout.Label("Connecting to " + manager.networkAddress + "..");
|
||||
if (GUILayout.Button("Cancel Connection Attempt"))
|
||||
{
|
||||
manager.StopClient();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StatusLabels()
|
||||
{
|
||||
// server / client status message
|
||||
if (NetworkServer.active)
|
||||
{
|
||||
GUILayout.Label("Server: active. Transport: " + Transport.activeTransport);
|
||||
}
|
||||
if (NetworkClient.isConnected)
|
||||
{
|
||||
GUILayout.Label("Client: address=" + manager.networkAddress);
|
||||
}
|
||||
}
|
||||
|
||||
void StopButtons()
|
||||
{
|
||||
// stop host if host mode
|
||||
if (NetworkServer.active && NetworkClient.isConnected)
|
||||
{
|
||||
if (GUILayout.Button("Stop Host"))
|
||||
{
|
||||
manager.StopHost();
|
||||
}
|
||||
}
|
||||
// stop client if client-only
|
||||
else if (NetworkClient.isConnected)
|
||||
{
|
||||
if (GUILayout.Button("Stop Client"))
|
||||
{
|
||||
manager.StopClient();
|
||||
}
|
||||
}
|
||||
// stop server if server-only
|
||||
else if (NetworkServer.active)
|
||||
{
|
||||
if (GUILayout.Button("Stop Server"))
|
||||
{
|
||||
manager.StopServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/NetworkManagerHUD.cs.meta
Normal file
11
Assets/Mirror/Runtime/NetworkManagerHUD.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6442dc8070ceb41f094e44de0bf87274
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1
Assets/Mirror/Runtime/NetworkMessage.cs
Normal file
1
Assets/Mirror/Runtime/NetworkMessage.cs
Normal file
@@ -0,0 +1 @@
|
||||
// file removed 03/17/2020
|
||||
11
Assets/Mirror/Runtime/NetworkMessage.cs.meta
Normal file
11
Assets/Mirror/Runtime/NetworkMessage.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb04e4848a2e4452aa2dbd7adb801c51
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
426
Assets/Mirror/Runtime/NetworkReader.cs
Normal file
426
Assets/Mirror/Runtime/NetworkReader.cs
Normal file
@@ -0,0 +1,426 @@
|
||||
// Custom NetworkReader that doesn't use C#'s built in MemoryStream in order to
|
||||
// avoid allocations.
|
||||
//
|
||||
// Benchmark: 100kb byte[] passed to NetworkReader constructor 1000x
|
||||
// before with MemoryStream
|
||||
// 0.8% CPU time, 250KB memory, 3.82ms
|
||||
// now:
|
||||
// 0.0% CPU time, 32KB memory, 0.02ms
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
/// <summary>
|
||||
/// a class that holds readers for the different types
|
||||
/// Note that c# creates a different static variable for each
|
||||
/// type
|
||||
/// This will be populated by the weaver
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public static class Reader<T>
|
||||
{
|
||||
public static Func<NetworkReader, T> read;
|
||||
}
|
||||
|
||||
// Note: This class is intended to be extremely pedantic, and
|
||||
// throw exceptions whenever stuff is going slightly wrong.
|
||||
// The exceptions will be handled in NetworkServer/NetworkClient.
|
||||
/// <summary>
|
||||
/// Binary stream Reader. Supports simple types, buffers, arrays, structs, and nested types
|
||||
/// <para>Use <see cref="NetworkReaderPool.GetReader">NetworkReaderPool.GetReader</see> to reduce memory allocation</para>
|
||||
/// </summary>
|
||||
public class NetworkReader
|
||||
{
|
||||
// internal buffer
|
||||
// byte[] pointer would work, but we use ArraySegment to also support
|
||||
// the ArraySegment constructor
|
||||
internal ArraySegment<byte> buffer;
|
||||
|
||||
// 'int' is the best type for .Position. 'short' is too small if we send >32kb which would result in negative .Position
|
||||
// -> converting long to int is fine until 2GB of data (MAX_INT), so we don't have to worry about overflows here
|
||||
public int Position;
|
||||
public int Length => buffer.Count;
|
||||
|
||||
public NetworkReader(byte[] bytes)
|
||||
{
|
||||
buffer = new ArraySegment<byte>(bytes);
|
||||
}
|
||||
|
||||
public NetworkReader(ArraySegment<byte> segment)
|
||||
{
|
||||
buffer = segment;
|
||||
}
|
||||
|
||||
public byte ReadByte()
|
||||
{
|
||||
if (Position + 1 > buffer.Count)
|
||||
{
|
||||
throw new EndOfStreamException("ReadByte out of range:" + ToString());
|
||||
}
|
||||
return buffer.Array[buffer.Offset + Position++];
|
||||
}
|
||||
public int ReadInt32() => (int)ReadUInt32();
|
||||
public uint ReadUInt32()
|
||||
{
|
||||
uint value = 0;
|
||||
value |= ReadByte();
|
||||
value |= (uint)(ReadByte() << 8);
|
||||
value |= (uint)(ReadByte() << 16);
|
||||
value |= (uint)(ReadByte() << 24);
|
||||
return value;
|
||||
}
|
||||
public long ReadInt64() => (long)ReadUInt64();
|
||||
public ulong ReadUInt64()
|
||||
{
|
||||
ulong value = 0;
|
||||
value |= ReadByte();
|
||||
value |= ((ulong)ReadByte()) << 8;
|
||||
value |= ((ulong)ReadByte()) << 16;
|
||||
value |= ((ulong)ReadByte()) << 24;
|
||||
value |= ((ulong)ReadByte()) << 32;
|
||||
value |= ((ulong)ReadByte()) << 40;
|
||||
value |= ((ulong)ReadByte()) << 48;
|
||||
value |= ((ulong)ReadByte()) << 56;
|
||||
return value;
|
||||
}
|
||||
|
||||
// read bytes into the passed buffer
|
||||
public byte[] ReadBytes(byte[] bytes, int count)
|
||||
{
|
||||
// check if passed byte array is big enough
|
||||
if (count > bytes.Length)
|
||||
{
|
||||
throw new EndOfStreamException("ReadBytes can't read " + count + " + bytes because the passed byte[] only has length " + bytes.Length);
|
||||
}
|
||||
|
||||
ArraySegment<byte> data = ReadBytesSegment(count);
|
||||
Array.Copy(data.Array, data.Offset, bytes, 0, count);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// useful to parse payloads etc. without allocating
|
||||
public ArraySegment<byte> ReadBytesSegment(int count)
|
||||
{
|
||||
// check if within buffer limits
|
||||
if (Position + count > buffer.Count)
|
||||
{
|
||||
throw new EndOfStreamException("ReadBytesSegment can't read " + count + " bytes because it would read past the end of the stream. " + ToString());
|
||||
}
|
||||
|
||||
// return the segment
|
||||
ArraySegment<byte> result = new ArraySegment<byte>(buffer.Array, buffer.Offset + Position, count);
|
||||
Position += count;
|
||||
return result;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "NetworkReader pos=" + Position + " len=" + Length + " buffer=" + BitConverter.ToString(buffer.Array, buffer.Offset, buffer.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads any data type that mirror supports
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public T Read<T>()
|
||||
{
|
||||
return Reader<T>.read(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Mirror's Weaver automatically detects all NetworkReader function types,
|
||||
// but they do all need to be extensions.
|
||||
public static class NetworkReaderExtensions
|
||||
{
|
||||
static readonly ILogger logger = LogFactory.GetLogger(typeof(NetworkReaderExtensions));
|
||||
|
||||
// cache encoding instead of creating it each time
|
||||
// 1000 readers before: 1MB GC, 30ms
|
||||
// 1000 readers after: 0.8MB GC, 18ms
|
||||
static readonly UTF8Encoding encoding = new UTF8Encoding(false, true);
|
||||
|
||||
public static byte ReadByte(this NetworkReader reader) => reader.ReadByte();
|
||||
public static sbyte ReadSByte(this NetworkReader reader) => (sbyte)reader.ReadByte();
|
||||
public static char ReadChar(this NetworkReader reader) => (char)reader.ReadUInt16();
|
||||
public static bool ReadBoolean(this NetworkReader reader) => reader.ReadByte() != 0;
|
||||
public static short ReadInt16(this NetworkReader reader) => (short)reader.ReadUInt16();
|
||||
public static ushort ReadUInt16(this NetworkReader reader)
|
||||
{
|
||||
ushort value = 0;
|
||||
value |= reader.ReadByte();
|
||||
value |= (ushort)(reader.ReadByte() << 8);
|
||||
return value;
|
||||
}
|
||||
public static int ReadInt32(this NetworkReader reader) => (int)reader.ReadUInt32();
|
||||
public static uint ReadUInt32(this NetworkReader reader)
|
||||
{
|
||||
uint value = 0;
|
||||
value |= reader.ReadByte();
|
||||
value |= (uint)(reader.ReadByte() << 8);
|
||||
value |= (uint)(reader.ReadByte() << 16);
|
||||
value |= (uint)(reader.ReadByte() << 24);
|
||||
return value;
|
||||
}
|
||||
public static long ReadInt64(this NetworkReader reader) => (long)reader.ReadUInt64();
|
||||
public static ulong ReadUInt64(this NetworkReader reader)
|
||||
{
|
||||
ulong value = 0;
|
||||
value |= reader.ReadByte();
|
||||
value |= ((ulong)reader.ReadByte()) << 8;
|
||||
value |= ((ulong)reader.ReadByte()) << 16;
|
||||
value |= ((ulong)reader.ReadByte()) << 24;
|
||||
value |= ((ulong)reader.ReadByte()) << 32;
|
||||
value |= ((ulong)reader.ReadByte()) << 40;
|
||||
value |= ((ulong)reader.ReadByte()) << 48;
|
||||
value |= ((ulong)reader.ReadByte()) << 56;
|
||||
return value;
|
||||
}
|
||||
public static float ReadSingle(this NetworkReader reader)
|
||||
{
|
||||
UIntFloat converter = new UIntFloat();
|
||||
converter.intValue = reader.ReadUInt32();
|
||||
return converter.floatValue;
|
||||
}
|
||||
public static double ReadDouble(this NetworkReader reader)
|
||||
{
|
||||
UIntDouble converter = new UIntDouble();
|
||||
converter.longValue = reader.ReadUInt64();
|
||||
return converter.doubleValue;
|
||||
}
|
||||
public static decimal ReadDecimal(this NetworkReader reader)
|
||||
{
|
||||
UIntDecimal converter = new UIntDecimal();
|
||||
converter.longValue1 = reader.ReadUInt64();
|
||||
converter.longValue2 = reader.ReadUInt64();
|
||||
return converter.decimalValue;
|
||||
}
|
||||
|
||||
// note: this will throw an ArgumentException if an invalid utf8 string is sent
|
||||
// null support, see NetworkWriter
|
||||
public static string ReadString(this NetworkReader reader)
|
||||
{
|
||||
// read number of bytes
|
||||
ushort size = reader.ReadUInt16();
|
||||
|
||||
if (size == 0)
|
||||
return null;
|
||||
|
||||
int realSize = size - 1;
|
||||
|
||||
// make sure it's within limits to avoid allocation attacks etc.
|
||||
if (realSize >= NetworkWriter.MaxStringLength)
|
||||
{
|
||||
throw new EndOfStreamException("ReadString too long: " + realSize + ". Limit is: " + NetworkWriter.MaxStringLength);
|
||||
}
|
||||
|
||||
ArraySegment<byte> data = reader.ReadBytesSegment(realSize);
|
||||
|
||||
// convert directly from buffer to string via encoding
|
||||
return encoding.GetString(data.Array, data.Offset, data.Count);
|
||||
}
|
||||
|
||||
// Use checked() to force it to throw OverflowException if data is invalid
|
||||
// null support, see NetworkWriter
|
||||
public static byte[] ReadBytesAndSize(this NetworkReader reader)
|
||||
{
|
||||
// count = 0 means the array was null
|
||||
// otherwise count -1 is the length of the array
|
||||
uint count = reader.ReadPackedUInt32();
|
||||
return count == 0 ? null : reader.ReadBytes(checked((int)(count - 1u)));
|
||||
}
|
||||
|
||||
public static ArraySegment<byte> ReadBytesAndSizeSegment(this NetworkReader reader)
|
||||
{
|
||||
// count = 0 means the array was null
|
||||
// otherwise count - 1 is the length of the array
|
||||
uint count = reader.ReadPackedUInt32();
|
||||
return count == 0 ? default : reader.ReadBytesSegment(checked((int)(count - 1u)));
|
||||
}
|
||||
|
||||
// zigzag decoding https://gist.github.com/mfuerstenau/ba870a29e16536fdbaba
|
||||
public static int ReadPackedInt32(this NetworkReader reader)
|
||||
{
|
||||
uint data = reader.ReadPackedUInt32();
|
||||
return (int)((data >> 1) ^ -(data & 1));
|
||||
}
|
||||
|
||||
// http://sqlite.org/src4/doc/trunk/www/varint.wiki
|
||||
// NOTE: big endian.
|
||||
// Use checked() to force it to throw OverflowException if data is invalid
|
||||
public static uint ReadPackedUInt32(this NetworkReader reader) => checked((uint)reader.ReadPackedUInt64());
|
||||
|
||||
// zigzag decoding https://gist.github.com/mfuerstenau/ba870a29e16536fdbaba
|
||||
public static long ReadPackedInt64(this NetworkReader reader)
|
||||
{
|
||||
ulong data = reader.ReadPackedUInt64();
|
||||
return ((long)(data >> 1)) ^ -((long)data & 1);
|
||||
}
|
||||
|
||||
public static ulong ReadPackedUInt64(this 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("ReadPackedUInt64() failure: " + a0);
|
||||
}
|
||||
|
||||
public static Vector2 ReadVector2(this NetworkReader reader) => new Vector2(reader.ReadSingle(), reader.ReadSingle());
|
||||
public static Vector3 ReadVector3(this NetworkReader reader) => new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
|
||||
public static Vector4 ReadVector4(this NetworkReader reader) => new Vector4(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
|
||||
public static Vector2Int ReadVector2Int(this NetworkReader reader) => new Vector2Int(reader.ReadPackedInt32(), reader.ReadPackedInt32());
|
||||
public static Vector3Int ReadVector3Int(this NetworkReader reader) => new Vector3Int(reader.ReadPackedInt32(), reader.ReadPackedInt32(), reader.ReadPackedInt32());
|
||||
public static Color ReadColor(this NetworkReader reader) => new Color(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
|
||||
public static Color32 ReadColor32(this NetworkReader reader) => new Color32(reader.ReadByte(), reader.ReadByte(), reader.ReadByte(), reader.ReadByte());
|
||||
public static Quaternion ReadQuaternion(this NetworkReader reader) => new Quaternion(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
|
||||
public static Rect ReadRect(this NetworkReader reader) => new Rect(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
|
||||
public static Plane ReadPlane(this NetworkReader reader) => new Plane(reader.ReadVector3(), reader.ReadSingle());
|
||||
public static Ray ReadRay(this NetworkReader reader) => new Ray(reader.ReadVector3(), reader.ReadVector3());
|
||||
|
||||
public static Matrix4x4 ReadMatrix4x4(this NetworkReader reader)
|
||||
{
|
||||
return new Matrix4x4
|
||||
{
|
||||
m00 = reader.ReadSingle(),
|
||||
m01 = reader.ReadSingle(),
|
||||
m02 = reader.ReadSingle(),
|
||||
m03 = reader.ReadSingle(),
|
||||
m10 = reader.ReadSingle(),
|
||||
m11 = reader.ReadSingle(),
|
||||
m12 = reader.ReadSingle(),
|
||||
m13 = reader.ReadSingle(),
|
||||
m20 = reader.ReadSingle(),
|
||||
m21 = reader.ReadSingle(),
|
||||
m22 = reader.ReadSingle(),
|
||||
m23 = reader.ReadSingle(),
|
||||
m30 = reader.ReadSingle(),
|
||||
m31 = reader.ReadSingle(),
|
||||
m32 = reader.ReadSingle(),
|
||||
m33 = reader.ReadSingle()
|
||||
};
|
||||
}
|
||||
|
||||
public static byte[] ReadBytes(this NetworkReader reader, int count)
|
||||
{
|
||||
byte[] bytes = new byte[count];
|
||||
reader.ReadBytes(bytes, count);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static Guid ReadGuid(this NetworkReader reader) => new Guid(reader.ReadBytes(16));
|
||||
public static Transform ReadTransform(this NetworkReader reader)
|
||||
{
|
||||
// Dont use null propagation here as it could lead to MissingReferenceException
|
||||
NetworkIdentity networkIdentity = reader.ReadNetworkIdentity();
|
||||
return networkIdentity != null ? networkIdentity.transform : null;
|
||||
}
|
||||
|
||||
public static GameObject ReadGameObject(this NetworkReader reader)
|
||||
{
|
||||
// Dont use null propagation here as it could lead to MissingReferenceException
|
||||
NetworkIdentity networkIdentity = reader.ReadNetworkIdentity();
|
||||
return networkIdentity != null ? networkIdentity.gameObject : null;
|
||||
}
|
||||
|
||||
public static NetworkIdentity ReadNetworkIdentity(this NetworkReader reader)
|
||||
{
|
||||
uint netId = reader.ReadPackedUInt32();
|
||||
if (netId == 0)
|
||||
return null;
|
||||
|
||||
if (NetworkIdentity.spawned.TryGetValue(netId, out NetworkIdentity identity))
|
||||
{
|
||||
return identity;
|
||||
}
|
||||
|
||||
if (logger.WarnEnabled()) logger.LogFormat(LogType.Warning, "ReadNetworkIdentity netId:{0} not found in spawned", netId);
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<T> ReadList<T>(this NetworkReader reader)
|
||||
{
|
||||
int length = reader.ReadPackedInt32();
|
||||
if (length < 0)
|
||||
return null;
|
||||
List<T> result = new List<T>(length);
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
result.Add(reader.Read<T>());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static T[] ReadArray<T>(this NetworkReader reader)
|
||||
{
|
||||
int length = reader.ReadPackedInt32();
|
||||
if (length < 0)
|
||||
return null;
|
||||
T[] result = new T[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
result[i] = reader.Read<T>();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Uri ReadUri(this NetworkReader reader)
|
||||
{
|
||||
return new Uri(reader.ReadString());
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/NetworkReader.cs.meta
Normal file
11
Assets/Mirror/Runtime/NetworkReader.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1610f05ec5bd14d6882e689f7372596a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
135
Assets/Mirror/Runtime/NetworkReaderPool.cs
Normal file
135
Assets/Mirror/Runtime/NetworkReaderPool.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
/// <summary>
|
||||
/// NetworkReader to be used with <see cref="NetworkReaderPool">NetworkReaderPool</see>
|
||||
/// </summary>
|
||||
public class PooledNetworkReader : NetworkReader, IDisposable
|
||||
{
|
||||
internal PooledNetworkReader(byte[] bytes) : base(bytes) { }
|
||||
|
||||
internal PooledNetworkReader(ArraySegment<byte> segment) : base(segment) { }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
NetworkReaderPool.Recycle(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pool of NetworkReaders
|
||||
/// <para>Use this pool instead of <see cref="NetworkReader">NetworkReader</see> to reduce memory allocation</para>
|
||||
/// <para>Use <see cref="Capacity">Capacity</see> to change size of pool</para>
|
||||
/// </summary>
|
||||
public static class NetworkReaderPool
|
||||
{
|
||||
static readonly ILogger logger = LogFactory.GetLogger(typeof(NetworkReaderPool), LogType.Error);
|
||||
|
||||
/// <summary>
|
||||
/// Size of the pool
|
||||
/// <para>If pool is too small getting readers will causes memory allocation</para>
|
||||
/// <para>Default value: 100</para>
|
||||
/// </summary>
|
||||
public static int Capacity
|
||||
{
|
||||
get => pool.Length;
|
||||
set
|
||||
{
|
||||
// resize the array
|
||||
Array.Resize(ref pool, value);
|
||||
|
||||
// if capacity is smaller than before, then we need to adjust
|
||||
// 'next' so it doesn't point to an index out of range
|
||||
// -> if we set '0' then next = min(_, 0-1) => -1
|
||||
// -> if we set '2' then next = min(_, 2-1) => 1
|
||||
next = Mathf.Min(next, pool.Length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mirror usually only uses up to 4 readers in nested usings,
|
||||
/// 100 is a good margin for edge cases when users need a lot readers at
|
||||
/// the same time.
|
||||
///
|
||||
/// <para>keep in mind, most entries of the pool will be null in most cases</para>
|
||||
/// </summary>
|
||||
///
|
||||
/// Note: we use an Array instead of a Stack because it's significantly
|
||||
/// faster: https://github.com/vis2k/Mirror/issues/1614
|
||||
static PooledNetworkReader[] pool = new PooledNetworkReader[100];
|
||||
|
||||
static int next = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Get the next reader in the pool
|
||||
/// <para>If pool is empty, creates a new Reader</para>
|
||||
/// </summary>
|
||||
public static PooledNetworkReader GetReader(byte[] bytes)
|
||||
{
|
||||
if (next == -1)
|
||||
{
|
||||
return new PooledNetworkReader(bytes);
|
||||
}
|
||||
|
||||
PooledNetworkReader reader = pool[next];
|
||||
pool[next] = null;
|
||||
next--;
|
||||
|
||||
// reset buffer
|
||||
SetBuffer(reader, bytes);
|
||||
return reader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the next reader in the pool
|
||||
/// <para>If pool is empty, creates a new Reader</para>
|
||||
/// </summary>
|
||||
public static PooledNetworkReader GetReader(ArraySegment<byte> segment)
|
||||
{
|
||||
if (next == -1)
|
||||
{
|
||||
return new PooledNetworkReader(segment);
|
||||
}
|
||||
|
||||
PooledNetworkReader reader = pool[next];
|
||||
pool[next] = null;
|
||||
next--;
|
||||
|
||||
// reset buffer
|
||||
SetBuffer(reader, segment);
|
||||
return reader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Puts reader back into pool
|
||||
/// <para>When pool is full, the extra reader is left for the GC</para>
|
||||
/// </summary>
|
||||
public static void Recycle(PooledNetworkReader reader)
|
||||
{
|
||||
if (next < pool.Length - 1)
|
||||
{
|
||||
next++;
|
||||
pool[next] = reader;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogWarning("NetworkReaderPool.Recycle, Pool was full leaving extra reader for GC");
|
||||
}
|
||||
}
|
||||
|
||||
// SetBuffer methods mirror constructor for ReaderPool
|
||||
static void SetBuffer(NetworkReader reader, byte[] bytes)
|
||||
{
|
||||
reader.buffer = new ArraySegment<byte>(bytes);
|
||||
reader.Position = 0;
|
||||
}
|
||||
|
||||
static void SetBuffer(NetworkReader reader, ArraySegment<byte> segment)
|
||||
{
|
||||
reader.buffer = segment;
|
||||
reader.Position = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/NetworkReaderPool.cs.meta
Normal file
11
Assets/Mirror/Runtime/NetworkReaderPool.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2bacff63613ad634a98f9e4d15d29dbf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1306
Assets/Mirror/Runtime/NetworkServer.cs
Normal file
1306
Assets/Mirror/Runtime/NetworkServer.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Assets/Mirror/Runtime/NetworkServer.cs.meta
Normal file
11
Assets/Mirror/Runtime/NetworkServer.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5f5ec068f5604c32b160bc49ee97b75
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
24
Assets/Mirror/Runtime/NetworkStartPosition.cs
Normal file
24
Assets/Mirror/Runtime/NetworkStartPosition.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
/// <summary>
|
||||
/// This component is used to make a gameObject a starting position for spawning player objects in multiplayer games.
|
||||
/// <para>This object's transform will be automatically registered and unregistered with the NetworkManager as a starting position.</para>
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("Network/NetworkStartPosition")]
|
||||
[HelpURL("https://mirror-networking.com/docs/Components/NetworkStartPosition.html")]
|
||||
public class NetworkStartPosition : MonoBehaviour
|
||||
{
|
||||
public void Awake()
|
||||
{
|
||||
NetworkManager.RegisterStartPosition(transform);
|
||||
}
|
||||
|
||||
public void OnDestroy()
|
||||
{
|
||||
NetworkManager.UnRegisterStartPosition(transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/NetworkStartPosition.cs.meta
Normal file
11
Assets/Mirror/Runtime/NetworkStartPosition.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 41f84591ce72545258ea98cb7518d8b9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
168
Assets/Mirror/Runtime/NetworkTime.cs
Normal file
168
Assets/Mirror/Runtime/NetworkTime.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using Stopwatch = System.Diagnostics.Stopwatch;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
/// <summary>
|
||||
/// Synchronize time between the server and the clients
|
||||
/// </summary>
|
||||
public static class NetworkTime
|
||||
{
|
||||
static readonly ILogger logger = LogFactory.GetLogger(typeof(NetworkTime));
|
||||
|
||||
/// <summary>
|
||||
/// how often are we sending ping messages
|
||||
/// used to calculate network time and RTT
|
||||
/// </summary>
|
||||
public static float PingFrequency = 2.0f;
|
||||
|
||||
/// <summary>
|
||||
/// average out the last few results from Ping
|
||||
/// </summary>
|
||||
public static int PingWindowSize = 10;
|
||||
|
||||
static double lastPingTime;
|
||||
|
||||
// Date and time when the application started
|
||||
static readonly Stopwatch stopwatch = new Stopwatch();
|
||||
|
||||
static NetworkTime()
|
||||
{
|
||||
stopwatch.Start();
|
||||
}
|
||||
|
||||
static ExponentialMovingAverage _rtt = new ExponentialMovingAverage(10);
|
||||
static ExponentialMovingAverage _offset = new ExponentialMovingAverage(10);
|
||||
|
||||
// the true offset guaranteed to be in this range
|
||||
static double offsetMin = double.MinValue;
|
||||
static double offsetMax = double.MaxValue;
|
||||
|
||||
// returns the clock time _in this system_
|
||||
static double LocalTime()
|
||||
{
|
||||
return stopwatch.Elapsed.TotalSeconds;
|
||||
}
|
||||
|
||||
public static void Reset()
|
||||
{
|
||||
_rtt = new ExponentialMovingAverage(PingWindowSize);
|
||||
_offset = new ExponentialMovingAverage(PingWindowSize);
|
||||
offsetMin = double.MinValue;
|
||||
offsetMax = double.MaxValue;
|
||||
}
|
||||
|
||||
internal static void UpdateClient()
|
||||
{
|
||||
if (Time.time - lastPingTime >= PingFrequency)
|
||||
{
|
||||
NetworkPingMessage pingMessage = new NetworkPingMessage(LocalTime());
|
||||
NetworkClient.Send(pingMessage);
|
||||
lastPingTime = Time.time;
|
||||
}
|
||||
}
|
||||
|
||||
// executed at the server when we receive a ping message
|
||||
// reply with a pong containing the time from the client
|
||||
// and time from the server
|
||||
internal static void OnServerPing(NetworkConnection conn, NetworkPingMessage msg)
|
||||
{
|
||||
if (logger.LogEnabled()) logger.Log("OnPingServerMessage conn=" + conn);
|
||||
|
||||
NetworkPongMessage pongMsg = new NetworkPongMessage
|
||||
{
|
||||
clientTime = msg.clientTime,
|
||||
serverTime = LocalTime()
|
||||
};
|
||||
|
||||
conn.Send(pongMsg);
|
||||
}
|
||||
|
||||
// Executed at the client when we receive a Pong message
|
||||
// find out how long it took since we sent the Ping
|
||||
// and update time offset
|
||||
internal static void OnClientPong(NetworkPongMessage msg)
|
||||
{
|
||||
double now = LocalTime();
|
||||
|
||||
// how long did this message take to come back
|
||||
double newRtt = now - msg.clientTime;
|
||||
_rtt.Add(newRtt);
|
||||
|
||||
// the difference in time between the client and the server
|
||||
// but subtract half of the rtt to compensate for latency
|
||||
// half of rtt is the best approximation we have
|
||||
double newOffset = now - newRtt * 0.5f - msg.serverTime;
|
||||
|
||||
double newOffsetMin = now - newRtt - msg.serverTime;
|
||||
double newOffsetMax = now - msg.serverTime;
|
||||
offsetMin = Math.Max(offsetMin, newOffsetMin);
|
||||
offsetMax = Math.Min(offsetMax, newOffsetMax);
|
||||
|
||||
if (_offset.Value < offsetMin || _offset.Value > offsetMax)
|
||||
{
|
||||
// the old offset was offrange, throw it away and use new one
|
||||
_offset = new ExponentialMovingAverage(PingWindowSize);
|
||||
_offset.Add(newOffset);
|
||||
}
|
||||
else if (newOffset >= offsetMin || newOffset <= offsetMax)
|
||||
{
|
||||
// new offset looks reasonable, add to the average
|
||||
_offset.Add(newOffset);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The time in seconds since the server started.
|
||||
/// </summary>
|
||||
//
|
||||
// I measured the accuracy of float and I got this:
|
||||
// for the same day, accuracy is better than 1 ms
|
||||
// after 1 day, accuracy goes down to 7 ms
|
||||
// after 10 days, accuracy is 61 ms
|
||||
// after 30 days , accuracy is 238 ms
|
||||
// after 60 days, accuracy is 454 ms
|
||||
// in other words, if the server is running for 2 months,
|
||||
// and you cast down to float, then the time will jump in 0.4s intervals.
|
||||
public static double time => LocalTime() - _offset.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Measurement of the variance of time.
|
||||
/// <para>The higher the variance, the less accurate the time is</para>
|
||||
/// </summary>
|
||||
public static double timeVar => _offset.Var;
|
||||
|
||||
/// <summary>
|
||||
/// standard deviation of time.
|
||||
/// <para>The higher the variance, the less accurate the time is</para>
|
||||
/// </summary>
|
||||
public static double timeSd => Math.Sqrt(timeVar);
|
||||
|
||||
/// <summary>
|
||||
/// Clock difference in seconds between the client and the server
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note this value is always 0 at the server
|
||||
/// </remarks>
|
||||
public static double offset => _offset.Value;
|
||||
|
||||
/// <summary>
|
||||
/// how long in seconds does it take for a message to go
|
||||
/// to the server and come back
|
||||
/// </summary>
|
||||
public static double rtt => _rtt.Value;
|
||||
|
||||
/// <summary>
|
||||
/// measure variance of rtt
|
||||
/// the higher the number, the less accurate rtt is
|
||||
/// </summary>
|
||||
public static double rttVar => _rtt.Var;
|
||||
|
||||
/// <summary>
|
||||
/// Measure the standard deviation of rtt
|
||||
/// the higher the number, the less accurate rtt is
|
||||
/// </summary>
|
||||
public static double rttSd => Math.Sqrt(rttVar);
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/NetworkTime.cs.meta
Normal file
11
Assets/Mirror/Runtime/NetworkTime.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09a0c241fc4a5496dbf4a0ab6e9a312c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
42
Assets/Mirror/Runtime/NetworkVisibility.cs
Normal file
42
Assets/Mirror/Runtime/NetworkVisibility.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
// the name NetworkProximityCheck implies that it's only about objects in
|
||||
// proximity to the player. But we might have room based, guild based,
|
||||
// instanced based checks too, so NetworkVisibility is more fitting.
|
||||
//
|
||||
// note: we inherit from NetworkBehaviour so we can reuse .netIdentity, etc.
|
||||
// note: unlike UNET, we only allow 1 proximity checker per NetworkIdentity.
|
||||
[DisallowMultipleComponent]
|
||||
public abstract class NetworkVisibility : NetworkBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Callback used by the visibility system to determine if an observer (player) can see this object.
|
||||
/// <para>If this function returns true, the network connection will be added as an observer.</para>
|
||||
/// </summary>
|
||||
/// <param name="conn">Network connection of a player.</param>
|
||||
/// <returns>True if the player can see this object.</returns>
|
||||
public abstract bool OnCheckObserver(NetworkConnection conn);
|
||||
|
||||
/// <summary>
|
||||
/// Callback used by the visibility system to (re)construct the set of observers that can see this object.
|
||||
/// <para>Implementations of this callback should add network connections of players that can see this object to the observers set.</para>
|
||||
/// </summary>
|
||||
/// <param name="observers">The new set of observers for this object.</param>
|
||||
/// <param name="initialize">True if the set of observers is being built for the first time.</param>
|
||||
public abstract void OnRebuildObservers(HashSet<NetworkConnection> observers, bool initialize);
|
||||
|
||||
/// <summary>
|
||||
/// Callback used by the visibility system for objects on a host.
|
||||
/// <para>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.</para>
|
||||
/// </summary>
|
||||
/// <param name="visible">New visibility state.</param>
|
||||
public virtual void OnSetHostVisibility(bool visible)
|
||||
{
|
||||
foreach (Renderer rend in GetComponentsInChildren<Renderer>())
|
||||
rend.enabled = visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/NetworkVisibility.cs.meta
Normal file
11
Assets/Mirror/Runtime/NetworkVisibility.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c08f1a030234d49d391d7223a8592f15
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
596
Assets/Mirror/Runtime/NetworkWriter.cs
Normal file
596
Assets/Mirror/Runtime/NetworkWriter.cs
Normal file
@@ -0,0 +1,596 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
/// <summary>
|
||||
/// a class that holds writers for the different types
|
||||
/// Note that c# creates a different static variable for each
|
||||
/// type
|
||||
/// This will be populated by the weaver
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public static class Writer<T>
|
||||
{
|
||||
public static Action<NetworkWriter, T> write;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binary stream Writer. Supports simple types, buffers, arrays, structs, and nested types
|
||||
/// <para>Use <see cref="NetworkWriterPool.GetWriter">NetworkWriter.GetWriter</see> to reduce memory allocation</para>
|
||||
/// </summary>
|
||||
public class NetworkWriter
|
||||
{
|
||||
public const int MaxStringLength = 1024 * 32;
|
||||
|
||||
// create writer immediately with it's own buffer so no one can mess with it and so that we can resize it.
|
||||
// note: BinaryWriter allocates too much, so we only use a MemoryStream
|
||||
// => 1500 bytes by default because on average, most packets will be <= MTU
|
||||
byte[] buffer = new byte[1500];
|
||||
|
||||
// 'int' is the best type for .Position. 'short' is too small if we send >32kb which would result in negative .Position
|
||||
// -> converting long to int is fine until 2GB of data (MAX_INT), so we don't have to worry about overflows here
|
||||
int position;
|
||||
int length;
|
||||
|
||||
public int Length => length;
|
||||
|
||||
public int Position
|
||||
{
|
||||
get => position;
|
||||
set
|
||||
{
|
||||
position = value;
|
||||
EnsureLength(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset both the position and length of the stream
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Leaves the capacity the same so that we can reuse this writer without extra allocations
|
||||
/// </remarks>
|
||||
public void Reset()
|
||||
{
|
||||
position = 0;
|
||||
length = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets length, moves position if it is greater than new length
|
||||
/// </summary>
|
||||
/// <param name="newLength"></param>
|
||||
/// <remarks>
|
||||
/// Zeros out any extra length created by setlength
|
||||
/// </remarks>
|
||||
public void SetLength(int newLength)
|
||||
{
|
||||
int oldLength = length;
|
||||
|
||||
// ensure length & capacity
|
||||
EnsureLength(newLength);
|
||||
|
||||
// zero out new length
|
||||
if (oldLength < newLength)
|
||||
{
|
||||
Array.Clear(buffer, oldLength, newLength - oldLength);
|
||||
}
|
||||
|
||||
length = newLength;
|
||||
position = Mathf.Min(position, length);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
void EnsureLength(int value)
|
||||
{
|
||||
if (length < value)
|
||||
{
|
||||
length = value;
|
||||
EnsureCapacity(value);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
void EnsureCapacity(int value)
|
||||
{
|
||||
if (buffer.Length < value)
|
||||
{
|
||||
int capacity = Math.Max(value, buffer.Length * 2);
|
||||
Array.Resize(ref buffer, capacity);
|
||||
}
|
||||
}
|
||||
|
||||
// MemoryStream has 3 values: Position, Length and Capacity.
|
||||
// Position is used to indicate where we are writing
|
||||
// Length is how much data we have written
|
||||
// capacity is how much memory we have allocated
|
||||
// ToArray returns all the data we have written, regardless of the current position
|
||||
public byte[] ToArray()
|
||||
{
|
||||
byte[] data = new byte[length];
|
||||
Array.ConstrainedCopy(buffer, 0, data, 0, length);
|
||||
return data;
|
||||
}
|
||||
|
||||
// Gets the serialized data in an ArraySegment<byte>
|
||||
// this is similar to ToArray(), but it gets the data in O(1)
|
||||
// and without allocations.
|
||||
// Do not write anything else or modify the NetworkWriter
|
||||
// while you are using the ArraySegment
|
||||
public ArraySegment<byte> ToArraySegment()
|
||||
{
|
||||
return new ArraySegment<byte>(buffer, 0, length);
|
||||
}
|
||||
|
||||
public void WriteByte(byte value)
|
||||
{
|
||||
EnsureLength(position + 1);
|
||||
buffer[position++] = value;
|
||||
}
|
||||
|
||||
|
||||
// for byte arrays with consistent size, where the reader knows how many to read
|
||||
// (like a packet opcode that's always the same)
|
||||
public void WriteBytes(byte[] buffer, int offset, int count)
|
||||
{
|
||||
EnsureLength(position + count);
|
||||
Array.ConstrainedCopy(buffer, offset, this.buffer, position, count);
|
||||
position += count;
|
||||
}
|
||||
|
||||
public void WriteUInt32(uint value)
|
||||
{
|
||||
EnsureLength(position + 4);
|
||||
buffer[position++] = (byte)value;
|
||||
buffer[position++] = (byte)(value >> 8);
|
||||
buffer[position++] = (byte)(value >> 16);
|
||||
buffer[position++] = (byte)(value >> 24);
|
||||
}
|
||||
|
||||
public void WriteInt32(int value) => WriteUInt32((uint)value);
|
||||
|
||||
public void WriteUInt64(ulong value)
|
||||
{
|
||||
EnsureLength(position + 8);
|
||||
buffer[position++] = (byte)value;
|
||||
buffer[position++] = (byte)(value >> 8);
|
||||
buffer[position++] = (byte)(value >> 16);
|
||||
buffer[position++] = (byte)(value >> 24);
|
||||
buffer[position++] = (byte)(value >> 32);
|
||||
buffer[position++] = (byte)(value >> 40);
|
||||
buffer[position++] = (byte)(value >> 48);
|
||||
buffer[position++] = (byte)(value >> 56);
|
||||
}
|
||||
|
||||
public void WriteInt64(long value) => WriteUInt64((ulong)value);
|
||||
|
||||
/// <summary>
|
||||
/// Writes any type that mirror supports
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="value"></param>
|
||||
public void Write<T>(T value)
|
||||
{
|
||||
Writer<T>.write(this, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Mirror's Weaver automatically detects all NetworkWriter function types,
|
||||
// but they do all need to be extensions.
|
||||
public static class NetworkWriterExtensions
|
||||
{
|
||||
static readonly ILogger logger = LogFactory.GetLogger(typeof(NetworkWriterExtensions));
|
||||
|
||||
// cache encoding instead of creating it with BinaryWriter each time
|
||||
// 1000 readers before: 1MB GC, 30ms
|
||||
// 1000 readers after: 0.8MB GC, 18ms
|
||||
static readonly UTF8Encoding encoding = new UTF8Encoding(false, true);
|
||||
static readonly byte[] stringBuffer = new byte[NetworkWriter.MaxStringLength];
|
||||
|
||||
public static void WriteByte(this NetworkWriter writer, byte value) => writer.WriteByte(value);
|
||||
|
||||
public static void WriteSByte(this NetworkWriter writer, sbyte value) => writer.WriteByte((byte)value);
|
||||
|
||||
public static void WriteChar(this NetworkWriter writer, char value) => writer.WriteUInt16(value);
|
||||
|
||||
public static void WriteBoolean(this NetworkWriter writer, bool value) => writer.WriteByte((byte)(value ? 1 : 0));
|
||||
|
||||
public static void WriteUInt16(this NetworkWriter writer, ushort value)
|
||||
{
|
||||
writer.WriteByte((byte)value);
|
||||
writer.WriteByte((byte)(value >> 8));
|
||||
}
|
||||
|
||||
public static void WriteInt16(this NetworkWriter writer, short value) => writer.WriteUInt16((ushort)value);
|
||||
|
||||
public static void WriteSingle(this NetworkWriter writer, float value)
|
||||
{
|
||||
UIntFloat converter = new UIntFloat
|
||||
{
|
||||
floatValue = value
|
||||
};
|
||||
writer.WriteUInt32(converter.intValue);
|
||||
}
|
||||
|
||||
public static void WriteDouble(this NetworkWriter writer, double value)
|
||||
{
|
||||
UIntDouble converter = new UIntDouble
|
||||
{
|
||||
doubleValue = value
|
||||
};
|
||||
writer.WriteUInt64(converter.longValue);
|
||||
}
|
||||
|
||||
public static void WriteDecimal(this NetworkWriter writer, decimal value)
|
||||
{
|
||||
// the only way to read it without allocations is to both read and
|
||||
// write it with the FloatConverter (which is not binary compatible
|
||||
// to writer.Write(decimal), hence why we use it here too)
|
||||
UIntDecimal converter = new UIntDecimal
|
||||
{
|
||||
decimalValue = value
|
||||
};
|
||||
writer.WriteUInt64(converter.longValue1);
|
||||
writer.WriteUInt64(converter.longValue2);
|
||||
}
|
||||
|
||||
public static void WriteString(this NetworkWriter writer, string value)
|
||||
{
|
||||
// write 0 for null support, increment real size by 1
|
||||
// (note: original HLAPI would write "" for null strings, but if a
|
||||
// string is null on the server then it should also be null
|
||||
// on the client)
|
||||
if (value == null)
|
||||
{
|
||||
writer.WriteUInt16(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// write string with same method as NetworkReader
|
||||
// convert to byte[]
|
||||
int size = encoding.GetBytes(value, 0, value.Length, stringBuffer, 0);
|
||||
|
||||
// check if within max size
|
||||
if (size >= NetworkWriter.MaxStringLength)
|
||||
{
|
||||
throw new IndexOutOfRangeException("NetworkWriter.Write(string) too long: " + size + ". Limit: " + NetworkWriter.MaxStringLength);
|
||||
}
|
||||
|
||||
// write size and bytes
|
||||
writer.WriteUInt16(checked((ushort)(size + 1)));
|
||||
writer.WriteBytes(stringBuffer, 0, size);
|
||||
}
|
||||
|
||||
// for byte arrays with dynamic size, where the reader doesn't know how many will come
|
||||
// (like an inventory with different items etc.)
|
||||
public static void WriteBytesAndSize(this NetworkWriter writer, byte[] buffer, int offset, int count)
|
||||
{
|
||||
// null is supported because [SyncVar]s might be structs with null byte[] arrays
|
||||
// write 0 for null array, increment normal size by 1 to save bandwith
|
||||
// (using size=-1 for null would limit max size to 32kb instead of 64kb)
|
||||
if (buffer == null)
|
||||
{
|
||||
writer.WritePackedUInt32(0u);
|
||||
return;
|
||||
}
|
||||
writer.WritePackedUInt32(checked((uint)count) + 1u);
|
||||
writer.WriteBytes(buffer, offset, count);
|
||||
}
|
||||
|
||||
// Weaver needs a write function with just one byte[] parameter
|
||||
// (we don't name it .Write(byte[]) because it's really a WriteBytesAndSize since we write size / null info too)
|
||||
public static void WriteBytesAndSize(this NetworkWriter writer, byte[] buffer)
|
||||
{
|
||||
// buffer might be null, so we can't use .Length in that case
|
||||
writer.WriteBytesAndSize(buffer, 0, buffer != null ? buffer.Length : 0);
|
||||
}
|
||||
|
||||
public static void WriteBytesAndSizeSegment(this NetworkWriter writer, ArraySegment<byte> buffer)
|
||||
{
|
||||
writer.WriteBytesAndSize(buffer.Array, buffer.Offset, buffer.Count);
|
||||
}
|
||||
|
||||
// zigzag encoding https://gist.github.com/mfuerstenau/ba870a29e16536fdbaba
|
||||
public static void WritePackedInt32(this NetworkWriter writer, int i)
|
||||
{
|
||||
uint zigzagged = (uint)((i >> 31) ^ (i << 1));
|
||||
writer.WritePackedUInt32(zigzagged);
|
||||
}
|
||||
|
||||
// http://sqlite.org/src4/doc/trunk/www/varint.wiki
|
||||
public static void WritePackedUInt32(this NetworkWriter writer, uint value)
|
||||
{
|
||||
// for 32 bit values WritePackedUInt64 writes the
|
||||
// same exact thing bit by bit
|
||||
writer.WritePackedUInt64(value);
|
||||
}
|
||||
|
||||
// zigzag encoding https://gist.github.com/mfuerstenau/ba870a29e16536fdbaba
|
||||
public static void WritePackedInt64(this NetworkWriter writer, long i)
|
||||
{
|
||||
ulong zigzagged = (ulong)((i >> 63) ^ (i << 1));
|
||||
writer.WritePackedUInt64(zigzagged);
|
||||
}
|
||||
|
||||
public static void WritePackedUInt64(this NetworkWriter writer, ulong value)
|
||||
{
|
||||
if (value <= 240)
|
||||
{
|
||||
writer.WriteByte((byte)value);
|
||||
return;
|
||||
}
|
||||
if (value <= 2287)
|
||||
{
|
||||
writer.WriteByte((byte)(((value - 240) >> 8) + 241));
|
||||
writer.WriteByte((byte)(value - 240));
|
||||
return;
|
||||
}
|
||||
if (value <= 67823)
|
||||
{
|
||||
writer.WriteByte(249);
|
||||
writer.WriteByte((byte)((value - 2288) >> 8));
|
||||
writer.WriteByte((byte)(value - 2288));
|
||||
return;
|
||||
}
|
||||
if (value <= 16777215)
|
||||
{
|
||||
writer.WriteByte(250);
|
||||
writer.WriteByte((byte)value);
|
||||
writer.WriteByte((byte)(value >> 8));
|
||||
writer.WriteByte((byte)(value >> 16));
|
||||
return;
|
||||
}
|
||||
if (value <= 4294967295)
|
||||
{
|
||||
writer.WriteByte(251);
|
||||
writer.WriteByte((byte)value);
|
||||
writer.WriteByte((byte)(value >> 8));
|
||||
writer.WriteByte((byte)(value >> 16));
|
||||
writer.WriteByte((byte)(value >> 24));
|
||||
return;
|
||||
}
|
||||
if (value <= 1099511627775)
|
||||
{
|
||||
writer.WriteByte(252);
|
||||
writer.WriteByte((byte)value);
|
||||
writer.WriteByte((byte)(value >> 8));
|
||||
writer.WriteByte((byte)(value >> 16));
|
||||
writer.WriteByte((byte)(value >> 24));
|
||||
writer.WriteByte((byte)(value >> 32));
|
||||
return;
|
||||
}
|
||||
if (value <= 281474976710655)
|
||||
{
|
||||
writer.WriteByte(253);
|
||||
writer.WriteByte((byte)value);
|
||||
writer.WriteByte((byte)(value >> 8));
|
||||
writer.WriteByte((byte)(value >> 16));
|
||||
writer.WriteByte((byte)(value >> 24));
|
||||
writer.WriteByte((byte)(value >> 32));
|
||||
writer.WriteByte((byte)(value >> 40));
|
||||
return;
|
||||
}
|
||||
if (value <= 72057594037927935)
|
||||
{
|
||||
writer.WriteByte(254);
|
||||
writer.WriteByte((byte)value);
|
||||
writer.WriteByte((byte)(value >> 8));
|
||||
writer.WriteByte((byte)(value >> 16));
|
||||
writer.WriteByte((byte)(value >> 24));
|
||||
writer.WriteByte((byte)(value >> 32));
|
||||
writer.WriteByte((byte)(value >> 40));
|
||||
writer.WriteByte((byte)(value >> 48));
|
||||
return;
|
||||
}
|
||||
|
||||
// all others
|
||||
{
|
||||
writer.WriteByte(255);
|
||||
writer.WriteByte((byte)value);
|
||||
writer.WriteByte((byte)(value >> 8));
|
||||
writer.WriteByte((byte)(value >> 16));
|
||||
writer.WriteByte((byte)(value >> 24));
|
||||
writer.WriteByte((byte)(value >> 32));
|
||||
writer.WriteByte((byte)(value >> 40));
|
||||
writer.WriteByte((byte)(value >> 48));
|
||||
writer.WriteByte((byte)(value >> 56));
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteVector2(this NetworkWriter writer, Vector2 value)
|
||||
{
|
||||
writer.WriteSingle(value.x);
|
||||
writer.WriteSingle(value.y);
|
||||
}
|
||||
|
||||
public static void WriteVector3(this NetworkWriter writer, Vector3 value)
|
||||
{
|
||||
writer.WriteSingle(value.x);
|
||||
writer.WriteSingle(value.y);
|
||||
writer.WriteSingle(value.z);
|
||||
}
|
||||
|
||||
public static void WriteVector4(this NetworkWriter writer, Vector4 value)
|
||||
{
|
||||
writer.WriteSingle(value.x);
|
||||
writer.WriteSingle(value.y);
|
||||
writer.WriteSingle(value.z);
|
||||
writer.WriteSingle(value.w);
|
||||
}
|
||||
|
||||
public static void WriteVector2Int(this NetworkWriter writer, Vector2Int value)
|
||||
{
|
||||
writer.WritePackedInt32(value.x);
|
||||
writer.WritePackedInt32(value.y);
|
||||
}
|
||||
|
||||
public static void WriteVector3Int(this NetworkWriter writer, Vector3Int value)
|
||||
{
|
||||
writer.WritePackedInt32(value.x);
|
||||
writer.WritePackedInt32(value.y);
|
||||
writer.WritePackedInt32(value.z);
|
||||
}
|
||||
|
||||
public static void WriteColor(this NetworkWriter writer, Color value)
|
||||
{
|
||||
writer.WriteSingle(value.r);
|
||||
writer.WriteSingle(value.g);
|
||||
writer.WriteSingle(value.b);
|
||||
writer.WriteSingle(value.a);
|
||||
}
|
||||
|
||||
public static void WriteColor32(this NetworkWriter writer, Color32 value)
|
||||
{
|
||||
writer.WriteByte(value.r);
|
||||
writer.WriteByte(value.g);
|
||||
writer.WriteByte(value.b);
|
||||
writer.WriteByte(value.a);
|
||||
}
|
||||
|
||||
public static void WriteQuaternion(this NetworkWriter writer, Quaternion value)
|
||||
{
|
||||
writer.WriteSingle(value.x);
|
||||
writer.WriteSingle(value.y);
|
||||
writer.WriteSingle(value.z);
|
||||
writer.WriteSingle(value.w);
|
||||
}
|
||||
|
||||
public static void WriteRect(this NetworkWriter writer, Rect value)
|
||||
{
|
||||
writer.WriteSingle(value.xMin);
|
||||
writer.WriteSingle(value.yMin);
|
||||
writer.WriteSingle(value.width);
|
||||
writer.WriteSingle(value.height);
|
||||
}
|
||||
|
||||
public static void WritePlane(this NetworkWriter writer, Plane value)
|
||||
{
|
||||
writer.WriteVector3(value.normal);
|
||||
writer.WriteSingle(value.distance);
|
||||
}
|
||||
|
||||
public static void WriteRay(this NetworkWriter writer, Ray value)
|
||||
{
|
||||
writer.WriteVector3(value.origin);
|
||||
writer.WriteVector3(value.direction);
|
||||
}
|
||||
|
||||
public static void WriteMatrix4x4(this NetworkWriter writer, Matrix4x4 value)
|
||||
{
|
||||
writer.WriteSingle(value.m00);
|
||||
writer.WriteSingle(value.m01);
|
||||
writer.WriteSingle(value.m02);
|
||||
writer.WriteSingle(value.m03);
|
||||
writer.WriteSingle(value.m10);
|
||||
writer.WriteSingle(value.m11);
|
||||
writer.WriteSingle(value.m12);
|
||||
writer.WriteSingle(value.m13);
|
||||
writer.WriteSingle(value.m20);
|
||||
writer.WriteSingle(value.m21);
|
||||
writer.WriteSingle(value.m22);
|
||||
writer.WriteSingle(value.m23);
|
||||
writer.WriteSingle(value.m30);
|
||||
writer.WriteSingle(value.m31);
|
||||
writer.WriteSingle(value.m32);
|
||||
writer.WriteSingle(value.m33);
|
||||
}
|
||||
|
||||
public static void WriteGuid(this NetworkWriter writer, Guid value)
|
||||
{
|
||||
byte[] data = value.ToByteArray();
|
||||
writer.WriteBytes(data, 0, data.Length);
|
||||
}
|
||||
|
||||
public static void WriteNetworkIdentity(this NetworkWriter writer, NetworkIdentity value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
writer.WritePackedUInt32(0);
|
||||
return;
|
||||
}
|
||||
writer.WritePackedUInt32(value.netId);
|
||||
}
|
||||
|
||||
public static void WriteTransform(this NetworkWriter writer, Transform value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
writer.WritePackedUInt32(0);
|
||||
return;
|
||||
}
|
||||
NetworkIdentity identity = value.GetComponent<NetworkIdentity>();
|
||||
if (identity != null)
|
||||
{
|
||||
writer.WritePackedUInt32(identity.netId);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogWarning("NetworkWriter " + value + " has no NetworkIdentity");
|
||||
writer.WritePackedUInt32(0);
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteGameObject(this NetworkWriter writer, GameObject value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
writer.WritePackedUInt32(0);
|
||||
return;
|
||||
}
|
||||
NetworkIdentity identity = value.GetComponent<NetworkIdentity>();
|
||||
if (identity != null)
|
||||
{
|
||||
writer.WritePackedUInt32(identity.netId);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogWarning("NetworkWriter " + value + " has no NetworkIdentity");
|
||||
writer.WritePackedUInt32(0);
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteUri(this NetworkWriter writer, Uri uri)
|
||||
{
|
||||
writer.WriteString(uri.ToString());
|
||||
}
|
||||
|
||||
public static void WriteList<T>(this NetworkWriter writer, List<T> list)
|
||||
{
|
||||
if (list is null)
|
||||
{
|
||||
writer.WritePackedInt32(-1);
|
||||
return;
|
||||
}
|
||||
writer.WritePackedInt32(list.Count);
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
writer.Write(list[i]);
|
||||
}
|
||||
|
||||
public static void WriteArray<T>(this NetworkWriter writer, T[] array)
|
||||
{
|
||||
if (array is null)
|
||||
{
|
||||
writer.WritePackedInt32(-1);
|
||||
return;
|
||||
}
|
||||
writer.WritePackedInt32(array.Length);
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
writer.Write(array[i]);
|
||||
}
|
||||
|
||||
public static void WriteArraySegment<T>(this NetworkWriter writer, ArraySegment<T> segment)
|
||||
{
|
||||
int length = segment.Count;
|
||||
writer.WritePackedInt32(length);
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
writer.Write(segment.Array[segment.Offset + i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/NetworkWriter.cs.meta
Normal file
11
Assets/Mirror/Runtime/NetworkWriter.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 48d2207bcef1f4477b624725f075f9bd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
98
Assets/Mirror/Runtime/NetworkWriterPool.cs
Normal file
98
Assets/Mirror/Runtime/NetworkWriterPool.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
/// <summary>
|
||||
/// NetworkWriter to be used with <see cref="NetworkWriterPool">NetworkWriterPool</see>
|
||||
/// </summary>
|
||||
public class PooledNetworkWriter : NetworkWriter, IDisposable
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
NetworkWriterPool.Recycle(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pool of NetworkWriters
|
||||
/// <para>Use this pool instead of <see cref="NetworkWriter">NetworkWriter</see> to reduce memory allocation</para>
|
||||
/// <para>Use <see cref="Capacity">Capacity</see> to change size of pool</para>
|
||||
/// </summary>
|
||||
public static class NetworkWriterPool
|
||||
{
|
||||
static readonly ILogger logger = LogFactory.GetLogger(typeof(NetworkWriterPool), LogType.Error);
|
||||
|
||||
/// <summary>
|
||||
/// Size of the pool
|
||||
/// <para>If pool is too small getting writers will causes memory allocation</para>
|
||||
/// <para>Default value: 100 </para>
|
||||
/// </summary>
|
||||
public static int Capacity
|
||||
{
|
||||
get => pool.Length;
|
||||
set
|
||||
{
|
||||
// resize the array
|
||||
Array.Resize(ref pool, value);
|
||||
|
||||
// if capacity is smaller than before, then we need to adjust
|
||||
// 'next' so it doesn't point to an index out of range
|
||||
// -> if we set '0' then next = min(_, 0-1) => -1
|
||||
// -> if we set '2' then next = min(_, 2-1) => 1
|
||||
next = Mathf.Min(next, pool.Length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mirror usually only uses up to 4 writes in nested usings,
|
||||
/// 100 is a good margin for edge cases when users need a lot writers at
|
||||
/// the same time.
|
||||
///
|
||||
/// <para>keep in mind, most entries of the pool will be null in most cases</para>
|
||||
/// </summary>
|
||||
///
|
||||
/// Note: we use an Array instead of a Stack because it's significantly
|
||||
/// faster: https://github.com/vis2k/Mirror/issues/1614
|
||||
static PooledNetworkWriter[] pool = new PooledNetworkWriter[100];
|
||||
|
||||
static int next = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Get the next writer in the pool
|
||||
/// <para>If pool is empty, creates a new Writer</para>
|
||||
/// </summary>
|
||||
public static PooledNetworkWriter GetWriter()
|
||||
{
|
||||
if (next == -1)
|
||||
{
|
||||
return new PooledNetworkWriter();
|
||||
}
|
||||
|
||||
PooledNetworkWriter writer = pool[next];
|
||||
pool[next] = null;
|
||||
next--;
|
||||
|
||||
// reset cached writer length and position
|
||||
writer.Reset();
|
||||
return writer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Puts writer back into pool
|
||||
/// <para>When pool is full, the extra writer is left for the GC</para>
|
||||
/// </summary>
|
||||
public static void Recycle(PooledNetworkWriter writer)
|
||||
{
|
||||
if (next < pool.Length - 1)
|
||||
{
|
||||
next++;
|
||||
pool[next] = writer;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogWarning("NetworkWriterPool.Recycle, Pool was full leaving extra writer for GC");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/NetworkWriterPool.cs.meta
Normal file
11
Assets/Mirror/Runtime/NetworkWriterPool.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3f34b53bea38e4f259eb8dc211e4fdb6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
188
Assets/Mirror/Runtime/RemoteCallHelper.cs
Normal file
188
Assets/Mirror/Runtime/RemoteCallHelper.cs
Normal file
@@ -0,0 +1,188 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.RemoteCalls
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate for Command functions.
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <param name="reader"></param>
|
||||
public delegate void CmdDelegate(NetworkBehaviour obj, NetworkReader reader, NetworkConnectionToClient senderConnection);
|
||||
|
||||
class Invoker
|
||||
{
|
||||
public Type invokeClass;
|
||||
public MirrorInvokeType invokeType;
|
||||
public CmdDelegate invokeFunction;
|
||||
public bool cmdIgnoreAuthority;
|
||||
|
||||
public bool AreEqual(Type invokeClass, MirrorInvokeType invokeType, CmdDelegate invokeFunction)
|
||||
{
|
||||
return (this.invokeClass == invokeClass &&
|
||||
this.invokeType == invokeType &&
|
||||
this.invokeFunction == invokeFunction);
|
||||
}
|
||||
}
|
||||
|
||||
public struct CommandInfo
|
||||
{
|
||||
public bool ignoreAuthority;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to help manage remote calls for NetworkBehaviours
|
||||
/// </summary>
|
||||
public static class RemoteCallHelper
|
||||
{
|
||||
static readonly ILogger logger = LogFactory.GetLogger(typeof(RemoteCallHelper));
|
||||
|
||||
static readonly Dictionary<int, Invoker> cmdHandlerDelegates = new Dictionary<int, Invoker>();
|
||||
|
||||
/// <summary>
|
||||
/// Creates hash from Type and method name
|
||||
/// </summary>
|
||||
/// <param name="invokeClass"></param>
|
||||
/// <param name="methodName"></param>
|
||||
/// <returns></returns>
|
||||
internal static int GetMethodHash(Type invokeClass, string methodName)
|
||||
{
|
||||
// (invokeClass + ":" + cmdName).GetStableHashCode() would cause allocations.
|
||||
// so hash1 + hash2 is better.
|
||||
unchecked
|
||||
{
|
||||
int hash = invokeClass.FullName.GetStableHashCode();
|
||||
return hash * 503 + methodName.GetStableHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// helper function register a Command/Rpc delegate
|
||||
/// </summary>
|
||||
/// <param name="invokeClass"></param>
|
||||
/// <param name="cmdName"></param>
|
||||
/// <param name="invokerType"></param>
|
||||
/// <param name="func"></param>
|
||||
/// <param name="cmdIgnoreAuthority"></param>
|
||||
/// <returns>remote function hash</returns>
|
||||
internal static int RegisterDelegate(Type invokeClass, string cmdName, MirrorInvokeType invokerType, CmdDelegate func, bool cmdIgnoreAuthority = false)
|
||||
{
|
||||
// type+func so Inventory.RpcUse != Equipment.RpcUse
|
||||
int cmdHash = GetMethodHash(invokeClass, cmdName);
|
||||
|
||||
if (CheckIfDeligateExists(invokeClass, invokerType, func, cmdHash))
|
||||
return cmdHash;
|
||||
|
||||
Invoker invoker = new Invoker
|
||||
{
|
||||
invokeType = invokerType,
|
||||
invokeClass = invokeClass,
|
||||
invokeFunction = func,
|
||||
cmdIgnoreAuthority = cmdIgnoreAuthority,
|
||||
};
|
||||
|
||||
cmdHandlerDelegates[cmdHash] = invoker;
|
||||
|
||||
if (logger.LogEnabled())
|
||||
{
|
||||
string ingoreAuthorityMessage = invokerType == MirrorInvokeType.Command ? $" IgnoreAuthority:{cmdIgnoreAuthority}" : "";
|
||||
logger.Log($"RegisterDelegate hash: {cmdHash} invokerType: {invokerType} method: {func.GetMethodName()}{ingoreAuthorityMessage}");
|
||||
}
|
||||
|
||||
return cmdHash;
|
||||
}
|
||||
|
||||
static bool CheckIfDeligateExists(Type invokeClass, MirrorInvokeType invokerType, CmdDelegate func, int cmdHash)
|
||||
{
|
||||
if (cmdHandlerDelegates.ContainsKey(cmdHash))
|
||||
{
|
||||
// something already registered this hash
|
||||
Invoker oldInvoker = cmdHandlerDelegates[cmdHash];
|
||||
if (oldInvoker.AreEqual(invokeClass, invokerType, func))
|
||||
{
|
||||
// it's all right, it was the same function
|
||||
return true;
|
||||
}
|
||||
|
||||
logger.LogError($"Function {oldInvoker.invokeClass}.{oldInvoker.invokeFunction.GetMethodName()} and {invokeClass}.{func.GetMethodName()} have the same hash. Please rename one of them");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void RegisterCommandDelegate(Type invokeClass, string cmdName, CmdDelegate func, bool ignoreAuthority)
|
||||
{
|
||||
RegisterDelegate(invokeClass, cmdName, MirrorInvokeType.Command, func, ignoreAuthority);
|
||||
}
|
||||
|
||||
public static void RegisterRpcDelegate(Type invokeClass, string rpcName, CmdDelegate func)
|
||||
{
|
||||
RegisterDelegate(invokeClass, rpcName, MirrorInvokeType.ClientRpc, func);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We need this in order to clean up tests
|
||||
/// </summary>
|
||||
internal static void RemoveDelegate(int hash)
|
||||
{
|
||||
cmdHandlerDelegates.Remove(hash);
|
||||
}
|
||||
|
||||
static bool GetInvokerForHash(int cmdHash, MirrorInvokeType invokeType, out Invoker invoker)
|
||||
{
|
||||
if (cmdHandlerDelegates.TryGetValue(cmdHash, out invoker) && invoker != null && invoker.invokeType == invokeType)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// debug message if not found, or null, or mismatched type
|
||||
// (no need to throw an error, an attacker might just be trying to
|
||||
// call an cmd with an rpc's hash)
|
||||
if (logger.LogEnabled()) logger.Log("GetInvokerForHash hash:" + cmdHash + " not found");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// InvokeCmd/Rpc Delegate can all use the same function here
|
||||
internal static bool InvokeHandlerDelegate(int cmdHash, MirrorInvokeType invokeType, NetworkReader reader, NetworkBehaviour invokingType, NetworkConnectionToClient senderConnection = null)
|
||||
{
|
||||
if (GetInvokerForHash(cmdHash, invokeType, out Invoker invoker) && invoker.invokeClass.IsInstanceOfType(invokingType))
|
||||
{
|
||||
invoker.invokeFunction(invokingType, reader, senderConnection);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static CommandInfo GetCommandInfo(int cmdHash, NetworkBehaviour invokingType)
|
||||
{
|
||||
if (GetInvokerForHash(cmdHash, MirrorInvokeType.Command, out Invoker invoker) && invoker.invokeClass.IsInstanceOfType(invokingType))
|
||||
{
|
||||
return new CommandInfo
|
||||
{
|
||||
ignoreAuthority = invoker.cmdIgnoreAuthority
|
||||
};
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the handler function for a given hash
|
||||
/// Can be used by profilers and debuggers
|
||||
/// </summary>
|
||||
/// <param name="cmdHash">rpc function hash</param>
|
||||
/// <returns>The function delegate that will handle the command</returns>
|
||||
public static CmdDelegate GetDelegate(int cmdHash)
|
||||
{
|
||||
if (cmdHandlerDelegates.TryGetValue(cmdHash, out Invoker invoker))
|
||||
{
|
||||
return invoker.invokeFunction;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
Assets/Mirror/Runtime/RemoteCallHelper.cs.meta
Normal file
11
Assets/Mirror/Runtime/RemoteCallHelper.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2cdbcbd1e377d6408a91acbec31ba16
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
18
Assets/Mirror/Runtime/StringHash.cs
Normal file
18
Assets/Mirror/Runtime/StringHash.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace Mirror
|
||||
{
|
||||
public static class StringHash
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/StringHash.cs.meta
Normal file
11
Assets/Mirror/Runtime/StringHash.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 733f020f9b76d453da841089579fd7a7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
311
Assets/Mirror/Runtime/SyncDictionary.cs
Normal file
311
Assets/Mirror/Runtime/SyncDictionary.cs
Normal file
@@ -0,0 +1,311 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
public class SyncIDictionary<TKey, TValue> : IDictionary<TKey, TValue>, SyncObject, IReadOnlyDictionary<TKey, TValue>
|
||||
{
|
||||
public delegate void SyncDictionaryChanged(Operation op, TKey key, TValue item);
|
||||
|
||||
protected readonly IDictionary<TKey, TValue> objects;
|
||||
|
||||
public int Count => objects.Count;
|
||||
public bool IsReadOnly { get; private set; }
|
||||
public event SyncDictionaryChanged Callback;
|
||||
|
||||
public enum Operation : byte
|
||||
{
|
||||
OP_ADD,
|
||||
OP_CLEAR,
|
||||
OP_REMOVE,
|
||||
OP_SET
|
||||
}
|
||||
|
||||
struct Change
|
||||
{
|
||||
internal Operation operation;
|
||||
internal TKey key;
|
||||
internal TValue item;
|
||||
}
|
||||
|
||||
readonly List<Change> changes = new List<Change>();
|
||||
// how many changes we need to ignore
|
||||
// this is needed because when we initialize the list,
|
||||
// we might later receive changes that have already been applied
|
||||
// so we need to skip them
|
||||
int changesAhead;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
IsReadOnly = false;
|
||||
changes.Clear();
|
||||
changesAhead = 0;
|
||||
objects.Clear();
|
||||
}
|
||||
|
||||
public bool IsDirty => changes.Count > 0;
|
||||
|
||||
public ICollection<TKey> Keys => objects.Keys;
|
||||
|
||||
public ICollection<TValue> Values => objects.Values;
|
||||
|
||||
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => objects.Keys;
|
||||
|
||||
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => objects.Values;
|
||||
|
||||
// throw away all the changes
|
||||
// this should be called after a successfull sync
|
||||
public void Flush() => changes.Clear();
|
||||
|
||||
public SyncIDictionary(IDictionary<TKey, TValue> objects)
|
||||
{
|
||||
this.objects = objects;
|
||||
}
|
||||
|
||||
void AddOperation(Operation op, TKey key, TValue item)
|
||||
{
|
||||
if (IsReadOnly)
|
||||
{
|
||||
throw new System.InvalidOperationException("SyncDictionaries can only be modified by the server");
|
||||
}
|
||||
|
||||
Change change = new Change
|
||||
{
|
||||
operation = op,
|
||||
key = key,
|
||||
item = item
|
||||
};
|
||||
|
||||
changes.Add(change);
|
||||
|
||||
Callback?.Invoke(op, key, item);
|
||||
}
|
||||
|
||||
public void OnSerializeAll(NetworkWriter writer)
|
||||
{
|
||||
// if init, write the full list content
|
||||
writer.WritePackedUInt32((uint)objects.Count);
|
||||
|
||||
foreach (KeyValuePair<TKey, TValue> syncItem in objects)
|
||||
{
|
||||
writer.Write(syncItem.Key);
|
||||
writer.Write(syncItem.Value);
|
||||
}
|
||||
|
||||
// all changes have been applied already
|
||||
// thus the client will need to skip all the pending changes
|
||||
// or they would be applied again.
|
||||
// So we write how many changes are pending
|
||||
writer.WritePackedUInt32((uint)changes.Count);
|
||||
}
|
||||
|
||||
public void OnSerializeDelta(NetworkWriter writer)
|
||||
{
|
||||
// write all the queued up changes
|
||||
writer.WritePackedUInt32((uint)changes.Count);
|
||||
|
||||
for (int i = 0; i < changes.Count; i++)
|
||||
{
|
||||
Change change = changes[i];
|
||||
writer.WriteByte((byte)change.operation);
|
||||
|
||||
switch (change.operation)
|
||||
{
|
||||
case Operation.OP_ADD:
|
||||
case Operation.OP_REMOVE:
|
||||
case Operation.OP_SET:
|
||||
writer.Write(change.key);
|
||||
writer.Write(change.item);
|
||||
break;
|
||||
case Operation.OP_CLEAR:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnDeserializeAll(NetworkReader reader)
|
||||
{
|
||||
// This list can now only be modified by synchronization
|
||||
IsReadOnly = true;
|
||||
|
||||
// if init, write the full list content
|
||||
int count = (int)reader.ReadPackedUInt32();
|
||||
|
||||
objects.Clear();
|
||||
changes.Clear();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
TKey key = reader.Read<TKey>();
|
||||
TValue obj = reader.Read<TValue>();
|
||||
objects.Add(key, obj);
|
||||
}
|
||||
|
||||
// We will need to skip all these changes
|
||||
// the next time the list is synchronized
|
||||
// because they have already been applied
|
||||
changesAhead = (int)reader.ReadPackedUInt32();
|
||||
}
|
||||
|
||||
public void OnDeserializeDelta(NetworkReader reader)
|
||||
{
|
||||
// This list can now only be modified by synchronization
|
||||
IsReadOnly = true;
|
||||
|
||||
int changesCount = (int)reader.ReadPackedUInt32();
|
||||
|
||||
for (int i = 0; i < changesCount; i++)
|
||||
{
|
||||
Operation operation = (Operation)reader.ReadByte();
|
||||
|
||||
// apply the operation only if it is a new change
|
||||
// that we have not applied yet
|
||||
bool apply = changesAhead == 0;
|
||||
TKey key = default;
|
||||
TValue item = default;
|
||||
|
||||
switch (operation)
|
||||
{
|
||||
case Operation.OP_ADD:
|
||||
case Operation.OP_SET:
|
||||
key = reader.Read<TKey>();
|
||||
item = reader.Read<TValue>();
|
||||
if (apply)
|
||||
{
|
||||
objects[key] = item;
|
||||
}
|
||||
break;
|
||||
|
||||
case Operation.OP_CLEAR:
|
||||
if (apply)
|
||||
{
|
||||
objects.Clear();
|
||||
}
|
||||
break;
|
||||
|
||||
case Operation.OP_REMOVE:
|
||||
key = reader.Read<TKey>();
|
||||
item = reader.Read<TValue>();
|
||||
if (apply)
|
||||
{
|
||||
objects.Remove(key);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (apply)
|
||||
{
|
||||
Callback?.Invoke(operation, key, item);
|
||||
}
|
||||
// we just skipped this change
|
||||
else
|
||||
{
|
||||
changesAhead--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
objects.Clear();
|
||||
AddOperation(Operation.OP_CLEAR, default, default);
|
||||
}
|
||||
|
||||
public bool ContainsKey(TKey key) => objects.ContainsKey(key);
|
||||
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
if (objects.TryGetValue(key, out TValue item) && objects.Remove(key))
|
||||
{
|
||||
AddOperation(Operation.OP_REMOVE, key, item);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public TValue this[TKey i]
|
||||
{
|
||||
get => objects[i];
|
||||
set
|
||||
{
|
||||
if (ContainsKey(i))
|
||||
{
|
||||
objects[i] = value;
|
||||
AddOperation(Operation.OP_SET, i, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
objects[i] = value;
|
||||
AddOperation(Operation.OP_ADD, i, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetValue(TKey key, out TValue value) => objects.TryGetValue(key, out value);
|
||||
|
||||
public void Add(TKey key, TValue value)
|
||||
{
|
||||
objects.Add(key, value);
|
||||
AddOperation(Operation.OP_ADD, key, value);
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<TKey, TValue> item) => Add(item.Key, item.Value);
|
||||
|
||||
public bool Contains(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
return TryGetValue(item.Key, out TValue val) && EqualityComparer<TValue>.Default.Equals(val, item.Value);
|
||||
}
|
||||
|
||||
public void CopyTo([NotNull] KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
||||
{
|
||||
if (arrayIndex < 0 || arrayIndex > array.Length)
|
||||
{
|
||||
throw new System.ArgumentOutOfRangeException(nameof(arrayIndex), "Array Index Out of Range");
|
||||
}
|
||||
if (array.Length - arrayIndex < Count)
|
||||
{
|
||||
throw new System.ArgumentException("The number of items in the SyncDictionary is greater than the available space from arrayIndex to the end of the destination array");
|
||||
}
|
||||
|
||||
int i = arrayIndex;
|
||||
foreach (KeyValuePair<TKey, TValue> item in objects)
|
||||
{
|
||||
array[i] = item;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
bool result = objects.Remove(item.Key);
|
||||
if (result)
|
||||
{
|
||||
AddOperation(Operation.OP_REMOVE, item.Key, item.Value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => objects.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => objects.GetEnumerator();
|
||||
}
|
||||
|
||||
public class SyncDictionary<TKey, TValue> : SyncIDictionary<TKey, TValue>
|
||||
{
|
||||
public SyncDictionary() : base(new Dictionary<TKey, TValue>())
|
||||
{
|
||||
}
|
||||
|
||||
public SyncDictionary(IEqualityComparer<TKey> eq) : base(new Dictionary<TKey, TValue>(eq))
|
||||
{
|
||||
}
|
||||
|
||||
public new Dictionary<TKey, TValue>.ValueCollection Values => ((Dictionary<TKey, TValue>)objects).Values;
|
||||
|
||||
public new Dictionary<TKey, TValue>.KeyCollection Keys => ((Dictionary<TKey, TValue>)objects).Keys;
|
||||
|
||||
public new Dictionary<TKey, TValue>.Enumerator GetEnumerator() => ((Dictionary<TKey, TValue>)objects).GetEnumerator();
|
||||
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/SyncDictionary.cs.meta
Normal file
11
Assets/Mirror/Runtime/SyncDictionary.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b346c49cfdb668488a364c3023590e2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
431
Assets/Mirror/Runtime/SyncList.cs
Normal file
431
Assets/Mirror/Runtime/SyncList.cs
Normal file
@@ -0,0 +1,431 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
// Deprecated 10/02/2020
|
||||
[Obsolete("Use SyncList<string> instead")]
|
||||
public class SyncListString : SyncList<string>
|
||||
{
|
||||
}
|
||||
|
||||
// Deprecated 10/02/2020
|
||||
[Obsolete("Use SyncList<float> instead")]
|
||||
public class SyncListFloat : SyncList<float>
|
||||
{
|
||||
}
|
||||
|
||||
// Deprecated 10/02/2020
|
||||
[Obsolete("Use SyncList<int> instead")]
|
||||
public class SyncListInt : SyncList<int>
|
||||
{
|
||||
}
|
||||
|
||||
// Deprecated 10/02/2020
|
||||
[Obsolete("Use SyncList<uint> instead")]
|
||||
public class SyncListUInt : SyncList<uint>
|
||||
{
|
||||
}
|
||||
|
||||
// Deprecated 10/02/2020
|
||||
[Obsolete("Use SyncList<bool> instead")]
|
||||
public class SyncListBool : SyncList<bool>
|
||||
{
|
||||
}
|
||||
|
||||
public class SyncList<T> : IList<T>, IReadOnlyList<T>, SyncObject
|
||||
{
|
||||
public delegate void SyncListChanged(Operation op, int itemIndex, T oldItem, T newItem);
|
||||
|
||||
readonly IList<T> objects;
|
||||
readonly IEqualityComparer<T> comparer;
|
||||
|
||||
public int Count => objects.Count;
|
||||
public bool IsReadOnly { get; private set; }
|
||||
public event SyncListChanged Callback;
|
||||
|
||||
public enum Operation : byte
|
||||
{
|
||||
OP_ADD,
|
||||
OP_CLEAR,
|
||||
OP_INSERT,
|
||||
OP_REMOVEAT,
|
||||
OP_SET
|
||||
}
|
||||
|
||||
struct Change
|
||||
{
|
||||
internal Operation operation;
|
||||
internal int index;
|
||||
internal T item;
|
||||
}
|
||||
|
||||
readonly List<Change> changes = new List<Change>();
|
||||
// how many changes we need to ignore
|
||||
// this is needed because when we initialize the list,
|
||||
// we might later receive changes that have already been applied
|
||||
// so we need to skip them
|
||||
int changesAhead;
|
||||
|
||||
public SyncList() : this(EqualityComparer<T>.Default)
|
||||
{
|
||||
}
|
||||
|
||||
public SyncList(IEqualityComparer<T> comparer)
|
||||
{
|
||||
this.comparer = comparer ?? EqualityComparer<T>.Default;
|
||||
objects = new List<T>();
|
||||
}
|
||||
|
||||
public SyncList(IList<T> objects, IEqualityComparer<T> comparer = null)
|
||||
{
|
||||
this.comparer = comparer ?? EqualityComparer<T>.Default;
|
||||
this.objects = objects;
|
||||
}
|
||||
|
||||
public bool IsDirty => changes.Count > 0;
|
||||
|
||||
// throw away all the changes
|
||||
// this should be called after a successfull sync
|
||||
public void Flush() => changes.Clear();
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
IsReadOnly = false;
|
||||
changes.Clear();
|
||||
changesAhead = 0;
|
||||
objects.Clear();
|
||||
}
|
||||
|
||||
void AddOperation(Operation op, int itemIndex, T oldItem, T newItem)
|
||||
{
|
||||
if (IsReadOnly)
|
||||
{
|
||||
throw new InvalidOperationException("Synclists can only be modified at the server");
|
||||
}
|
||||
|
||||
Change change = new Change
|
||||
{
|
||||
operation = op,
|
||||
index = itemIndex,
|
||||
item = newItem
|
||||
};
|
||||
|
||||
changes.Add(change);
|
||||
|
||||
Callback?.Invoke(op, itemIndex, oldItem, newItem);
|
||||
}
|
||||
|
||||
public void OnSerializeAll(NetworkWriter writer)
|
||||
{
|
||||
// if init, write the full list content
|
||||
writer.WritePackedUInt32((uint)objects.Count);
|
||||
|
||||
for (int i = 0; i < objects.Count; i++)
|
||||
{
|
||||
T obj = objects[i];
|
||||
writer.Write(obj);
|
||||
}
|
||||
|
||||
// all changes have been applied already
|
||||
// thus the client will need to skip all the pending changes
|
||||
// or they would be applied again.
|
||||
// So we write how many changes are pending
|
||||
writer.WritePackedUInt32((uint)changes.Count);
|
||||
}
|
||||
|
||||
public void OnSerializeDelta(NetworkWriter writer)
|
||||
{
|
||||
// write all the queued up changes
|
||||
writer.WritePackedUInt32((uint)changes.Count);
|
||||
|
||||
for (int i = 0; i < changes.Count; i++)
|
||||
{
|
||||
Change change = changes[i];
|
||||
writer.WriteByte((byte)change.operation);
|
||||
|
||||
switch (change.operation)
|
||||
{
|
||||
case Operation.OP_ADD:
|
||||
writer.Write(change.item);
|
||||
break;
|
||||
|
||||
case Operation.OP_CLEAR:
|
||||
break;
|
||||
|
||||
case Operation.OP_REMOVEAT:
|
||||
writer.WritePackedUInt32((uint)change.index);
|
||||
break;
|
||||
|
||||
case Operation.OP_INSERT:
|
||||
case Operation.OP_SET:
|
||||
writer.WritePackedUInt32((uint)change.index);
|
||||
writer.Write(change.item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnDeserializeAll(NetworkReader reader)
|
||||
{
|
||||
// This list can now only be modified by synchronization
|
||||
IsReadOnly = true;
|
||||
|
||||
// if init, write the full list content
|
||||
int count = (int)reader.ReadPackedUInt32();
|
||||
|
||||
objects.Clear();
|
||||
changes.Clear();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
T obj = reader.Read<T>();
|
||||
objects.Add(obj);
|
||||
}
|
||||
|
||||
// We will need to skip all these changes
|
||||
// the next time the list is synchronized
|
||||
// because they have already been applied
|
||||
changesAhead = (int)reader.ReadPackedUInt32();
|
||||
}
|
||||
|
||||
public void OnDeserializeDelta(NetworkReader reader)
|
||||
{
|
||||
// This list can now only be modified by synchronization
|
||||
IsReadOnly = true;
|
||||
|
||||
int changesCount = (int)reader.ReadPackedUInt32();
|
||||
|
||||
for (int i = 0; i < changesCount; i++)
|
||||
{
|
||||
Operation operation = (Operation)reader.ReadByte();
|
||||
|
||||
// apply the operation only if it is a new change
|
||||
// that we have not applied yet
|
||||
bool apply = changesAhead == 0;
|
||||
int index = 0;
|
||||
T oldItem = default;
|
||||
T newItem = default;
|
||||
|
||||
switch (operation)
|
||||
{
|
||||
case Operation.OP_ADD:
|
||||
newItem = reader.Read<T>();
|
||||
if (apply)
|
||||
{
|
||||
index = objects.Count;
|
||||
objects.Add(newItem);
|
||||
}
|
||||
break;
|
||||
|
||||
case Operation.OP_CLEAR:
|
||||
if (apply)
|
||||
{
|
||||
objects.Clear();
|
||||
}
|
||||
break;
|
||||
|
||||
case Operation.OP_INSERT:
|
||||
index = (int)reader.ReadPackedUInt32();
|
||||
newItem = reader.Read<T>();
|
||||
if (apply)
|
||||
{
|
||||
objects.Insert(index, newItem);
|
||||
}
|
||||
break;
|
||||
|
||||
case Operation.OP_REMOVEAT:
|
||||
index = (int)reader.ReadPackedUInt32();
|
||||
if (apply)
|
||||
{
|
||||
oldItem = objects[index];
|
||||
objects.RemoveAt(index);
|
||||
}
|
||||
break;
|
||||
|
||||
case Operation.OP_SET:
|
||||
index = (int)reader.ReadPackedUInt32();
|
||||
newItem = reader.Read<T>();
|
||||
if (apply)
|
||||
{
|
||||
oldItem = objects[index];
|
||||
objects[index] = newItem;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (apply)
|
||||
{
|
||||
Callback?.Invoke(operation, index, oldItem, newItem);
|
||||
}
|
||||
// we just skipped this change
|
||||
else
|
||||
{
|
||||
changesAhead--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(T item)
|
||||
{
|
||||
objects.Add(item);
|
||||
AddOperation(Operation.OP_ADD, objects.Count - 1, default, item);
|
||||
}
|
||||
|
||||
public void AddRange(IEnumerable<T> range)
|
||||
{
|
||||
foreach (T entry in range)
|
||||
{
|
||||
Add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
objects.Clear();
|
||||
AddOperation(Operation.OP_CLEAR, 0, default, default);
|
||||
}
|
||||
|
||||
public bool Contains(T item) => IndexOf(item) >= 0;
|
||||
|
||||
public void CopyTo(T[] array, int index) => objects.CopyTo(array, index);
|
||||
|
||||
public int IndexOf(T item)
|
||||
{
|
||||
for (int i = 0; i < objects.Count; ++i)
|
||||
if (comparer.Equals(item, objects[i]))
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int FindIndex(Predicate<T> match)
|
||||
{
|
||||
for (int i = 0; i < objects.Count; ++i)
|
||||
if (match(objects[i]))
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public T Find(Predicate<T> match)
|
||||
{
|
||||
int i = FindIndex(match);
|
||||
return (i != -1) ? objects[i] : default;
|
||||
}
|
||||
|
||||
public List<T> FindAll(Predicate<T> match)
|
||||
{
|
||||
List<T> results = new List<T>();
|
||||
for (int i = 0; i < objects.Count; ++i)
|
||||
if (match(objects[i]))
|
||||
results.Add(objects[i]);
|
||||
return results;
|
||||
}
|
||||
|
||||
public void Insert(int index, T item)
|
||||
{
|
||||
objects.Insert(index, item);
|
||||
AddOperation(Operation.OP_INSERT, index, default, item);
|
||||
}
|
||||
|
||||
public void InsertRange(int index, IEnumerable<T> range)
|
||||
{
|
||||
foreach (T entry in range)
|
||||
{
|
||||
Insert(index, entry);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(T item)
|
||||
{
|
||||
int index = IndexOf(item);
|
||||
bool result = index >= 0;
|
||||
if (result)
|
||||
{
|
||||
RemoveAt(index);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
T oldItem = objects[index];
|
||||
objects.RemoveAt(index);
|
||||
AddOperation(Operation.OP_REMOVEAT, index, oldItem, default);
|
||||
}
|
||||
|
||||
public int RemoveAll(Predicate<T> match)
|
||||
{
|
||||
List<T> toRemove = new List<T>();
|
||||
for (int i = 0; i < objects.Count; ++i)
|
||||
if (match(objects[i]))
|
||||
toRemove.Add(objects[i]);
|
||||
|
||||
foreach (T entry in toRemove)
|
||||
{
|
||||
Remove(entry);
|
||||
}
|
||||
|
||||
return toRemove.Count;
|
||||
}
|
||||
|
||||
public T this[int i]
|
||||
{
|
||||
get => objects[i];
|
||||
set
|
||||
{
|
||||
if (!comparer.Equals(objects[i], value))
|
||||
{
|
||||
T oldItem = objects[i];
|
||||
objects[i] = value;
|
||||
AddOperation(Operation.OP_SET, i, oldItem, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator() => new Enumerator(this);
|
||||
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator() => new Enumerator(this);
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);
|
||||
|
||||
// default Enumerator allocates. we need a custom struct Enumerator to
|
||||
// not allocate on the heap.
|
||||
// (System.Collections.Generic.List<T> source code does the same)
|
||||
//
|
||||
// benchmark:
|
||||
// uMMORPG with 800 monsters, Skills.GetHealthBonus() which runs a
|
||||
// foreach on skills SyncList:
|
||||
// before: 81.2KB GC per frame
|
||||
// after: 0KB GC per frame
|
||||
// => this is extremely important for MMO scale networking
|
||||
public struct Enumerator : IEnumerator<T>
|
||||
{
|
||||
readonly SyncList<T> list;
|
||||
int index;
|
||||
public T Current { get; private set; }
|
||||
|
||||
public Enumerator(SyncList<T> list)
|
||||
{
|
||||
this.list = list;
|
||||
index = -1;
|
||||
Current = default;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (++index >= list.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Current = list[index];
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset() => index = -1;
|
||||
object IEnumerator.Current => Current;
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/SyncList.cs.meta
Normal file
11
Assets/Mirror/Runtime/SyncList.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 744fc71f748fe40d5940e04bf42b29f3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
49
Assets/Mirror/Runtime/SyncObject.cs
Normal file
49
Assets/Mirror/Runtime/SyncObject.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
namespace Mirror
|
||||
{
|
||||
/// <summary>
|
||||
/// A sync object is an object that can synchronize it's state
|
||||
/// between server and client, such as a SyncList
|
||||
/// </summary>
|
||||
public interface SyncObject
|
||||
{
|
||||
/// <summary>
|
||||
/// true if there are changes since the last flush
|
||||
/// </summary>
|
||||
bool IsDirty { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Discard all the queued changes
|
||||
/// <para>Consider the object fully synchronized with clients</para>
|
||||
/// </summary>
|
||||
void Flush();
|
||||
|
||||
/// <summary>
|
||||
/// Write a full copy of the object
|
||||
/// </summary>
|
||||
/// <param name="writer"></param>
|
||||
void OnSerializeAll(NetworkWriter writer);
|
||||
|
||||
/// <summary>
|
||||
/// Write the changes made to the object since last sync
|
||||
/// </summary>
|
||||
/// <param name="writer"></param>
|
||||
void OnSerializeDelta(NetworkWriter writer);
|
||||
|
||||
/// <summary>
|
||||
/// Reads a full copy of the object
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
void OnDeserializeAll(NetworkReader reader);
|
||||
|
||||
/// <summary>
|
||||
/// Reads the changes made to the object since last sync
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
void OnDeserializeDelta(NetworkReader reader);
|
||||
|
||||
/// <summary>
|
||||
/// Resets the SyncObject so that it can be re-used
|
||||
/// </summary>
|
||||
void Reset();
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/SyncObject.cs.meta
Normal file
11
Assets/Mirror/Runtime/SyncObject.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae226d17a0c844041aa24cc2c023dd49
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
340
Assets/Mirror/Runtime/SyncSet.cs
Normal file
340
Assets/Mirror/Runtime/SyncSet.cs
Normal file
@@ -0,0 +1,340 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
public class SyncSet<T> : ISet<T>, SyncObject
|
||||
{
|
||||
public delegate void SyncSetChanged(Operation op, T item);
|
||||
|
||||
protected readonly ISet<T> objects;
|
||||
|
||||
public int Count => objects.Count;
|
||||
public bool IsReadOnly { get; private set; }
|
||||
public event SyncSetChanged Callback;
|
||||
|
||||
public enum Operation : byte
|
||||
{
|
||||
OP_ADD,
|
||||
OP_CLEAR,
|
||||
OP_REMOVE
|
||||
}
|
||||
|
||||
struct Change
|
||||
{
|
||||
internal Operation operation;
|
||||
internal T item;
|
||||
}
|
||||
|
||||
readonly List<Change> changes = new List<Change>();
|
||||
// how many changes we need to ignore
|
||||
// this is needed because when we initialize the list,
|
||||
// we might later receive changes that have already been applied
|
||||
// so we need to skip them
|
||||
int changesAhead;
|
||||
|
||||
public SyncSet(ISet<T> objects)
|
||||
{
|
||||
this.objects = objects;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
IsReadOnly = false;
|
||||
changes.Clear();
|
||||
changesAhead = 0;
|
||||
objects.Clear();
|
||||
}
|
||||
|
||||
public bool IsDirty => changes.Count > 0;
|
||||
|
||||
// throw away all the changes
|
||||
// this should be called after a successfull sync
|
||||
public void Flush() => changes.Clear();
|
||||
|
||||
void AddOperation(Operation op, T item)
|
||||
{
|
||||
if (IsReadOnly)
|
||||
{
|
||||
throw new InvalidOperationException("SyncSets can only be modified at the server");
|
||||
}
|
||||
|
||||
Change change = new Change
|
||||
{
|
||||
operation = op,
|
||||
item = item
|
||||
};
|
||||
|
||||
changes.Add(change);
|
||||
|
||||
Callback?.Invoke(op, item);
|
||||
}
|
||||
|
||||
void AddOperation(Operation op) => AddOperation(op, default);
|
||||
|
||||
public void OnSerializeAll(NetworkWriter writer)
|
||||
{
|
||||
// if init, write the full list content
|
||||
writer.WritePackedUInt32((uint)objects.Count);
|
||||
|
||||
foreach (T obj in objects)
|
||||
{
|
||||
writer.Write(obj);
|
||||
}
|
||||
|
||||
// all changes have been applied already
|
||||
// thus the client will need to skip all the pending changes
|
||||
// or they would be applied again.
|
||||
// So we write how many changes are pending
|
||||
writer.WritePackedUInt32((uint)changes.Count);
|
||||
}
|
||||
|
||||
public void OnSerializeDelta(NetworkWriter writer)
|
||||
{
|
||||
// write all the queued up changes
|
||||
writer.WritePackedUInt32((uint)changes.Count);
|
||||
|
||||
for (int i = 0; i < changes.Count; i++)
|
||||
{
|
||||
Change change = changes[i];
|
||||
writer.WriteByte((byte)change.operation);
|
||||
|
||||
switch (change.operation)
|
||||
{
|
||||
case Operation.OP_ADD:
|
||||
writer.Write(change.item);
|
||||
break;
|
||||
|
||||
case Operation.OP_CLEAR:
|
||||
break;
|
||||
|
||||
case Operation.OP_REMOVE:
|
||||
writer.Write(change.item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnDeserializeAll(NetworkReader reader)
|
||||
{
|
||||
// This list can now only be modified by synchronization
|
||||
IsReadOnly = true;
|
||||
|
||||
// if init, write the full list content
|
||||
int count = (int)reader.ReadPackedUInt32();
|
||||
|
||||
objects.Clear();
|
||||
changes.Clear();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
T obj = reader.Read<T>();
|
||||
objects.Add(obj);
|
||||
}
|
||||
|
||||
// We will need to skip all these changes
|
||||
// the next time the list is synchronized
|
||||
// because they have already been applied
|
||||
changesAhead = (int)reader.ReadPackedUInt32();
|
||||
}
|
||||
|
||||
public void OnDeserializeDelta(NetworkReader reader)
|
||||
{
|
||||
// This list can now only be modified by synchronization
|
||||
IsReadOnly = true;
|
||||
|
||||
int changesCount = (int)reader.ReadPackedUInt32();
|
||||
|
||||
for (int i = 0; i < changesCount; i++)
|
||||
{
|
||||
Operation operation = (Operation)reader.ReadByte();
|
||||
|
||||
// apply the operation only if it is a new change
|
||||
// that we have not applied yet
|
||||
bool apply = changesAhead == 0;
|
||||
T item = default;
|
||||
|
||||
switch (operation)
|
||||
{
|
||||
case Operation.OP_ADD:
|
||||
item = reader.Read<T>();
|
||||
if (apply)
|
||||
{
|
||||
objects.Add(item);
|
||||
}
|
||||
break;
|
||||
|
||||
case Operation.OP_CLEAR:
|
||||
if (apply)
|
||||
{
|
||||
objects.Clear();
|
||||
}
|
||||
break;
|
||||
|
||||
case Operation.OP_REMOVE:
|
||||
item = reader.Read<T>();
|
||||
if (apply)
|
||||
{
|
||||
objects.Remove(item);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (apply)
|
||||
{
|
||||
Callback?.Invoke(operation, item);
|
||||
}
|
||||
// we just skipped this change
|
||||
else
|
||||
{
|
||||
changesAhead--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Add(T item)
|
||||
{
|
||||
if (objects.Add(item))
|
||||
{
|
||||
AddOperation(Operation.OP_ADD, item);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ICollection<T>.Add(T item)
|
||||
{
|
||||
if (objects.Add(item))
|
||||
{
|
||||
AddOperation(Operation.OP_ADD, item);
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
objects.Clear();
|
||||
AddOperation(Operation.OP_CLEAR);
|
||||
}
|
||||
|
||||
public bool Contains(T item) => objects.Contains(item);
|
||||
|
||||
public void CopyTo(T[] array, int index) => objects.CopyTo(array, index);
|
||||
|
||||
public bool Remove(T item)
|
||||
{
|
||||
if (objects.Remove(item))
|
||||
{
|
||||
AddOperation(Operation.OP_REMOVE, item);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator() => objects.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public void ExceptWith(IEnumerable<T> other)
|
||||
{
|
||||
if (other == this)
|
||||
{
|
||||
Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// remove every element in other from this
|
||||
foreach (T element in other)
|
||||
{
|
||||
Remove(element);
|
||||
}
|
||||
}
|
||||
|
||||
public void IntersectWith(IEnumerable<T> other)
|
||||
{
|
||||
if (other is ISet<T> otherSet)
|
||||
{
|
||||
IntersectWithSet(otherSet);
|
||||
}
|
||||
else
|
||||
{
|
||||
HashSet<T> otherAsSet = new HashSet<T>(other);
|
||||
IntersectWithSet(otherAsSet);
|
||||
}
|
||||
}
|
||||
|
||||
void IntersectWithSet(ISet<T> otherSet)
|
||||
{
|
||||
List<T> elements = new List<T>(objects);
|
||||
|
||||
foreach (T element in elements)
|
||||
{
|
||||
if (!otherSet.Contains(element))
|
||||
{
|
||||
Remove(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsProperSubsetOf(IEnumerable<T> other) => objects.IsProperSubsetOf(other);
|
||||
|
||||
public bool IsProperSupersetOf(IEnumerable<T> other) => objects.IsProperSupersetOf(other);
|
||||
|
||||
public bool IsSubsetOf(IEnumerable<T> other) => objects.IsSubsetOf(other);
|
||||
|
||||
public bool IsSupersetOf(IEnumerable<T> other) => objects.IsSupersetOf(other);
|
||||
|
||||
public bool Overlaps(IEnumerable<T> other) => objects.Overlaps(other);
|
||||
|
||||
public bool SetEquals(IEnumerable<T> other) => objects.SetEquals(other);
|
||||
|
||||
public void SymmetricExceptWith(IEnumerable<T> other)
|
||||
{
|
||||
if (other == this)
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (T element in other)
|
||||
{
|
||||
if (!Remove(element))
|
||||
{
|
||||
Add(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void UnionWith(IEnumerable<T> other)
|
||||
{
|
||||
if (other != this)
|
||||
{
|
||||
foreach (T element in other)
|
||||
{
|
||||
Add(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SyncHashSet<T> : SyncSet<T>
|
||||
{
|
||||
public SyncHashSet() : this(EqualityComparer<T>.Default) { }
|
||||
|
||||
public SyncHashSet(IEqualityComparer<T> comparer) : base(new HashSet<T>(comparer ?? EqualityComparer<T>.Default)) { }
|
||||
|
||||
// allocation free enumerator
|
||||
public new HashSet<T>.Enumerator GetEnumerator() => ((HashSet<T>)objects).GetEnumerator();
|
||||
}
|
||||
|
||||
public class SyncSortedSet<T> : SyncSet<T>
|
||||
{
|
||||
public SyncSortedSet() : this(Comparer<T>.Default) { }
|
||||
|
||||
public SyncSortedSet(IComparer<T> comparer) : base(new SortedSet<T>(comparer ?? Comparer<T>.Default)) { }
|
||||
|
||||
// allocation free enumerator
|
||||
public new SortedSet<T>.Enumerator GetEnumerator() => ((SortedSet<T>)objects).GetEnumerator();
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/SyncSet.cs.meta
Normal file
11
Assets/Mirror/Runtime/SyncSet.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8a31599d9f9dd4ef9999f7b9707c832c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Mirror/Runtime/Transport.meta
Normal file
8
Assets/Mirror/Runtime/Transport.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7825d46cd73fe47938869eb5427b40fa
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
189
Assets/Mirror/Runtime/Transport/FallbackTransport.cs
Normal file
189
Assets/Mirror/Runtime/Transport/FallbackTransport.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
// uses the first available transport for server and client.
|
||||
// example: to use Apathy if on Windows/Mac/Linux and fall back to Telepathy
|
||||
// otherwise.
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
[HelpURL("https://mirror-networking.com/docs/Transports/Fallback.html")]
|
||||
public class FallbackTransport : Transport
|
||||
{
|
||||
public Transport[] transports;
|
||||
|
||||
// the first transport that is available on this platform
|
||||
Transport available;
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
if (transports == null || transports.Length == 0)
|
||||
{
|
||||
throw new Exception("FallbackTransport requires at least 1 underlying transport");
|
||||
}
|
||||
InitClient();
|
||||
InitServer();
|
||||
available = GetAvailableTransport();
|
||||
Debug.Log("FallbackTransport available: " + available.GetType());
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
available.enabled = true;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
available.enabled = false;
|
||||
}
|
||||
|
||||
// The client just uses the first transport available
|
||||
Transport GetAvailableTransport()
|
||||
{
|
||||
foreach (Transport transport in transports)
|
||||
{
|
||||
if (transport.Available())
|
||||
{
|
||||
return transport;
|
||||
}
|
||||
}
|
||||
throw new Exception("No transport suitable for this platform");
|
||||
}
|
||||
|
||||
public override bool Available()
|
||||
{
|
||||
return available.Available();
|
||||
}
|
||||
|
||||
// clients always pick the first transport
|
||||
void InitClient()
|
||||
{
|
||||
// wire all the base transports to our events
|
||||
foreach (Transport transport in transports)
|
||||
{
|
||||
transport.OnClientConnected.AddListener(OnClientConnected.Invoke);
|
||||
transport.OnClientDataReceived.AddListener(OnClientDataReceived.Invoke);
|
||||
transport.OnClientError.AddListener(OnClientError.Invoke);
|
||||
transport.OnClientDisconnected.AddListener(OnClientDisconnected.Invoke);
|
||||
}
|
||||
}
|
||||
|
||||
public override void ClientConnect(string address)
|
||||
{
|
||||
available.ClientConnect(address);
|
||||
}
|
||||
|
||||
public override void ClientConnect(Uri uri)
|
||||
{
|
||||
foreach (Transport transport in transports)
|
||||
{
|
||||
if (transport.Available())
|
||||
{
|
||||
try
|
||||
{
|
||||
transport.ClientConnect(uri);
|
||||
available = transport;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// transport does not support the schema, just move on to the next one
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Exception("No transport suitable for this platform");
|
||||
}
|
||||
|
||||
public override bool ClientConnected()
|
||||
{
|
||||
return available.ClientConnected();
|
||||
}
|
||||
|
||||
public override void ClientDisconnect()
|
||||
{
|
||||
available.ClientDisconnect();
|
||||
}
|
||||
|
||||
public override void ClientSend(int channelId, ArraySegment<byte> segment)
|
||||
{
|
||||
available.ClientSend(channelId, segment);
|
||||
}
|
||||
|
||||
void InitServer()
|
||||
{
|
||||
// wire all the base transports to our events
|
||||
foreach (Transport transport in transports)
|
||||
{
|
||||
transport.OnServerConnected.AddListener(OnServerConnected.Invoke);
|
||||
transport.OnServerDataReceived.AddListener(OnServerDataReceived.Invoke);
|
||||
transport.OnServerError.AddListener(OnServerError.Invoke);
|
||||
transport.OnServerDisconnected.AddListener(OnServerDisconnected.Invoke);
|
||||
}
|
||||
}
|
||||
|
||||
// right now this just returns the first available uri,
|
||||
// should we return the list of all available uri?
|
||||
public override Uri ServerUri() => available.ServerUri();
|
||||
|
||||
public override bool ServerActive()
|
||||
{
|
||||
return available.ServerActive();
|
||||
}
|
||||
|
||||
public override string ServerGetClientAddress(int connectionId)
|
||||
{
|
||||
return available.ServerGetClientAddress(connectionId);
|
||||
}
|
||||
|
||||
public override bool ServerDisconnect(int connectionId)
|
||||
{
|
||||
return available.ServerDisconnect(connectionId);
|
||||
}
|
||||
|
||||
public override void ServerSend(int connectionId, int channelId, ArraySegment<byte> segment)
|
||||
{
|
||||
available.ServerSend(connectionId, channelId, segment);
|
||||
}
|
||||
|
||||
public override void ServerStart()
|
||||
{
|
||||
available.ServerStart();
|
||||
}
|
||||
|
||||
public override void ServerStop()
|
||||
{
|
||||
available.ServerStop();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
available.Shutdown();
|
||||
}
|
||||
|
||||
public override int GetMaxPacketSize(int channelId = 0)
|
||||
{
|
||||
// finding the max packet size in a fallback environment has to be
|
||||
// done very carefully:
|
||||
// * servers and clients might run different transports depending on
|
||||
// which platform they are on.
|
||||
// * there should only ever be ONE true max packet size for everyone,
|
||||
// otherwise a spawn message might be sent to all tcp sockets, but
|
||||
// be too big for some udp sockets. that would be a debugging
|
||||
// nightmare and allow for possible exploits and players on
|
||||
// different platforms seeing a different game state.
|
||||
// => the safest solution is to use the smallest max size for all
|
||||
// transports. that will never fail.
|
||||
int mininumAllowedSize = int.MaxValue;
|
||||
foreach (Transport transport in transports)
|
||||
{
|
||||
int size = transport.GetMaxPacketSize(channelId);
|
||||
mininumAllowedSize = Mathf.Min(size, mininumAllowedSize);
|
||||
}
|
||||
return mininumAllowedSize;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return available.ToString();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Runtime/Transport/FallbackTransport.cs.meta
Normal file
11
Assets/Mirror/Runtime/Transport/FallbackTransport.cs.meta
Normal 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:
|
||||
8
Assets/Mirror/Runtime/Transport/KCP.meta
Normal file
8
Assets/Mirror/Runtime/Transport/KCP.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 953bb5ec5ab2346a092f58061e01ba65
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Mirror/Runtime/Transport/KCP/MirrorTransport.meta
Normal file
8
Assets/Mirror/Runtime/Transport/KCP/MirrorTransport.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7bdb797750d0a490684410110bf48192
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,172 @@
|
||||
//#if MIRROR <- commented out because MIRROR isn't defined on first import yet
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using Mirror;
|
||||
using UnityEngine;
|
||||
|
||||
namespace kcp2k
|
||||
{
|
||||
public class KcpTransport : Transport
|
||||
{
|
||||
// scheme used by this transport
|
||||
public const string Scheme = "kcp";
|
||||
|
||||
// common
|
||||
[Header("Transport Configuration")]
|
||||
public ushort Port = 7777;
|
||||
[Tooltip("NoDelay is recommended to reduce latency. This also scales better without buffers getting full.")]
|
||||
public bool NoDelay = true;
|
||||
[Tooltip("KCP internal update interval. 100ms is KCP default, but a lower interval is recommended to minimize latency and to scale to more networked entities.")]
|
||||
public uint Interval = 10;
|
||||
[Header("Advanced")]
|
||||
[Tooltip("KCP fastresend parameter. Faster resend for the cost of higher bandwidth.")]
|
||||
public int FastResend = 0;
|
||||
[Tooltip("KCP congestion window can be disabled. This is necessary to Mirror 10k Benchmark. Disable this for high scale games if connections get chocked regularly.")]
|
||||
public bool CongestionWindow = true; // KCP 'NoCongestionWindow' is false by default. here we negate it for ease of use.
|
||||
[Tooltip("KCP window size can be modified to support higher loads. For example, Mirror Benchmark requires 128 for 4k monsters, 256 for 10k monsters (if CongestionWindow is disabled.)")]
|
||||
public uint SendWindowSize = 128; //Kcp.WND_SND; 32 by default. 128 is better for 4k Benchmark etc.
|
||||
[Tooltip("KCP window size can be modified to support higher loads. For example, Mirror Benchmark requires 128 for 4k monsters, 256 for 10k monsters (if CongestionWindow is disabled.)")]
|
||||
public uint ReceiveWindowSize = Kcp.WND_RCV;
|
||||
|
||||
// server & client
|
||||
KcpServer server;
|
||||
KcpClient client;
|
||||
|
||||
// debugging
|
||||
[Header("Debug")]
|
||||
public bool debugGUI;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
// TODO simplify after converting Mirror Transport events to Action
|
||||
client = new KcpClient(
|
||||
() => OnClientConnected.Invoke(),
|
||||
(message) => OnClientDataReceived.Invoke(message, Channels.DefaultReliable),
|
||||
() => OnClientDisconnected.Invoke()
|
||||
);
|
||||
// TODO simplify after converting Mirror Transport events to Action
|
||||
server = new KcpServer(
|
||||
(connectionId) => OnServerConnected.Invoke(connectionId),
|
||||
(connectionId, message) => OnServerDataReceived.Invoke(connectionId, message, Channels.DefaultReliable),
|
||||
(connectionId) => OnServerDisconnected.Invoke(connectionId),
|
||||
NoDelay,
|
||||
Interval,
|
||||
FastResend,
|
||||
CongestionWindow,
|
||||
SendWindowSize,
|
||||
ReceiveWindowSize
|
||||
);
|
||||
Debug.Log("KcpTransport initialized!");
|
||||
}
|
||||
|
||||
// all except WebGL
|
||||
public override bool Available() =>
|
||||
Application.platform != RuntimePlatform.WebGLPlayer;
|
||||
|
||||
// client
|
||||
public override bool ClientConnected() => client.connected;
|
||||
public override void ClientConnect(string address)
|
||||
{
|
||||
client.Connect(address, Port, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize);
|
||||
}
|
||||
public override void ClientSend(int channelId, ArraySegment<byte> segment)
|
||||
{
|
||||
client.Send(segment);
|
||||
}
|
||||
public override void ClientDisconnect() => client.Disconnect();
|
||||
|
||||
// IMPORTANT: set script execution order to >1000 to call Transport's
|
||||
// LateUpdate after all others. Fixes race condition where
|
||||
// e.g. in uSurvival Transport would apply Cmds before
|
||||
// ShoulderRotation.LateUpdate, resulting in projectile
|
||||
// spawns at the point before shoulder rotation.
|
||||
public void LateUpdate()
|
||||
{
|
||||
// note: we need to check enabled in case we set it to false
|
||||
// when LateUpdate already started.
|
||||
// (https://github.com/vis2k/Mirror/pull/379)
|
||||
if (!enabled)
|
||||
return;
|
||||
|
||||
server.Tick();
|
||||
client.Tick();
|
||||
}
|
||||
|
||||
// server
|
||||
public override Uri ServerUri()
|
||||
{
|
||||
UriBuilder builder = new UriBuilder();
|
||||
builder.Scheme = Scheme;
|
||||
builder.Host = Dns.GetHostName();
|
||||
builder.Port = Port;
|
||||
return builder.Uri;
|
||||
}
|
||||
public override bool ServerActive() => server.IsActive();
|
||||
public override void ServerStart() => server.Start(Port);
|
||||
public override void ServerSend(int connectionId, int channelId, ArraySegment<byte> segment)
|
||||
{
|
||||
server.Send(connectionId, segment);
|
||||
}
|
||||
public override bool ServerDisconnect(int connectionId)
|
||||
{
|
||||
server.Disconnect(connectionId);
|
||||
return true;
|
||||
}
|
||||
public override string ServerGetClientAddress(int connectionId) => server.GetClientAddress(connectionId);
|
||||
public override void ServerStop() => server.Stop();
|
||||
|
||||
// common
|
||||
public override void Shutdown() {}
|
||||
|
||||
// MTU
|
||||
public override int GetMaxPacketSize(int channelId = Channels.DefaultReliable) => Kcp.MTU_DEF;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "KCP";
|
||||
}
|
||||
|
||||
int GetTotalSendQueue() =>
|
||||
server.connections.Values.Sum(conn => conn.SendQueueCount);
|
||||
int GetTotalReceiveQueue() =>
|
||||
server.connections.Values.Sum(conn => conn.ReceiveQueueCount);
|
||||
int GetTotalSendBuffer() =>
|
||||
server.connections.Values.Sum(conn => conn.SendBufferCount);
|
||||
int GetTotalReceiveBuffer() =>
|
||||
server.connections.Values.Sum(conn => conn.ReceiveBufferCount);
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
if (!debugGUI) return;
|
||||
|
||||
GUILayout.BeginArea(new Rect(5, 100, 300, 300));
|
||||
|
||||
if (ServerActive())
|
||||
{
|
||||
GUILayout.BeginVertical("Box");
|
||||
GUILayout.Label("SERVER");
|
||||
GUILayout.Label(" connections: " + server.connections.Count);
|
||||
GUILayout.Label(" SendQueue: " + GetTotalSendQueue());
|
||||
GUILayout.Label(" ReceiveQueue: " + GetTotalReceiveQueue());
|
||||
GUILayout.Label(" SendBuffer: " + GetTotalSendBuffer());
|
||||
GUILayout.Label(" ReceiveBuffer: " + GetTotalReceiveBuffer());
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
if (ClientConnected())
|
||||
{
|
||||
GUILayout.BeginVertical("Box");
|
||||
GUILayout.Label("CLIENT");
|
||||
GUILayout.Label(" SendQueue: " + client.connection.SendQueueCount);
|
||||
GUILayout.Label(" ReceiveQueue: " + client.connection.ReceiveQueueCount);
|
||||
GUILayout.Label(" SendBuffer: " + client.connection.SendBufferCount);
|
||||
GUILayout.Label(" ReceiveBuffer: " + client.connection.ReceiveBufferCount);
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
}
|
||||
}
|
||||
//#endif MIRROR <- commented out because MIRROR isn't defined on first import yet
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b0fecffa3f624585964b0d0eb21b18e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Mirror/Runtime/Transport/KCP/kcp2k.meta
Normal file
8
Assets/Mirror/Runtime/Transport/KCP/kcp2k.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 71a1c8e8c022d4731a481c1808f37e5d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
24
Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE
Normal file
24
Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE
Normal file
@@ -0,0 +1,24 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 limpo1989
|
||||
Copyright (c) 2020 Paul Pacheco
|
||||
Copyright (c) 2020 Lymdun
|
||||
Copyright (c) 2020 vis2k
|
||||
|
||||
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.
|
||||
7
Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE.meta
Normal file
7
Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9a3e8369060cf4e94ac117603de47aa6
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
9
Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION
Normal file
9
Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION
Normal file
@@ -0,0 +1,9 @@
|
||||
V1.1
|
||||
- high level cleanup, fixes, improvements
|
||||
|
||||
V1.0
|
||||
- Kcp.cs now mirrors original Kcp.c behaviour
|
||||
(this fixes dozens of bugs)
|
||||
|
||||
V0.1
|
||||
- initial kcp-csharp based version
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user