Keep local version of files

This commit is contained in:
2026-10-08 00:49:10 +05:30
parent 7fa0621ff8
commit 053665c690
7343 changed files with 5470578 additions and 5470528 deletions
+86 -86
View File
@@ -1,86 +1,86 @@
// accurate interval from Mirror II.
// for sync / send intervals where it matters.
// does not(!) do catch-up.
//
// first, let's understand the problem.
// say we need an interval of 10 Hz, so every 100ms in Update we do:
// if (Time.time >= lastTime + interval)
// {
// lastTime = Time.time;
// ...
// }
//
// this seems fine, but actually Time.time will always be a few ms beyond
// the interval. but since lastTime is reset to Time.time, the remainder
// is always ignored away.
// with fixed tickRate servers (say 30 Hz), the remainder is significant!
//
// in practice if we have a 30 Hz tickRate server with a 30 Hz sendRate,
// the above way to measure the interval would result in a 18-19 Hz sendRate!
// => this is not just a little off. this is _way_ off, by almost half.
// => displaying actual + target tick/send rate will show this very easily.
//
// we need an accurate way to measure intervals for where it matters.
// and it needs to be testable to guarantee results.
using System.Runtime.CompilerServices;
namespace Mirror
{
public static class AccurateInterval
{
// static func instead of storing interval + lastTime struct.
// + don't need to initialize struct ctor with interval in Awake
// + allows for interval changes at runtime too
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Elapsed(double time, double interval, ref double lastTime)
{
// enough time elapsed?
if (time < lastTime + interval)
return false;
// naive implementation:
//lastTime = time;
// accurate but doesn't handle heavy load situations:
//lastTime += interval;
// heavy load edge case:
// * interval is 100ms
// * server is under heavy load, Updates slow down to 1/s
// * Elapsed(1.000) returns true.
// technically 10 intervals have elapsed.
// * server recovers to normal, Updates are every 10ms again
// * Elapsed(1.010) should return false again until 1.100.
//
// increasing lastTime by interval would require 10 more calls
// to ever catch up again:
// lastTime += interval
//
// as result, the next 10 calls to Elapsed would return true.
// Elapsed(1.001) => true
// Elapsed(1.002) => true
// Elapsed(1.003) => true
// ...
// even though technically the delta was not >= interval.
//
// this would keep the server under heavy load, and it may never
// catch-up. this is not ideal for large virtual worlds.
//
// instead, we want to skip multiples of 'interval' and only
// keep the remainder.
//
// see also: AccurateIntervalTests.Slowdown()
// easy to understand:
//double elapsed = time - lastTime;
//double remainder = elapsed % interval;
//lastTime = time - remainder;
// easier: set to rounded multiples of interval (fholm).
// long to match double time.
long multiplier = (long)(time / interval);
lastTime = multiplier * interval;
return true;
}
}
}
// accurate interval from Mirror II.
// for sync / send intervals where it matters.
// does not(!) do catch-up.
//
// first, let's understand the problem.
// say we need an interval of 10 Hz, so every 100ms in Update we do:
// if (Time.time >= lastTime + interval)
// {
// lastTime = Time.time;
// ...
// }
//
// this seems fine, but actually Time.time will always be a few ms beyond
// the interval. but since lastTime is reset to Time.time, the remainder
// is always ignored away.
// with fixed tickRate servers (say 30 Hz), the remainder is significant!
//
// in practice if we have a 30 Hz tickRate server with a 30 Hz sendRate,
// the above way to measure the interval would result in a 18-19 Hz sendRate!
// => this is not just a little off. this is _way_ off, by almost half.
// => displaying actual + target tick/send rate will show this very easily.
//
// we need an accurate way to measure intervals for where it matters.
// and it needs to be testable to guarantee results.
using System.Runtime.CompilerServices;
namespace Mirror
{
public static class AccurateInterval
{
// static func instead of storing interval + lastTime struct.
// + don't need to initialize struct ctor with interval in Awake
// + allows for interval changes at runtime too
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Elapsed(double time, double interval, ref double lastTime)
{
// enough time elapsed?
if (time < lastTime + interval)
return false;
// naive implementation:
//lastTime = time;
// accurate but doesn't handle heavy load situations:
//lastTime += interval;
// heavy load edge case:
// * interval is 100ms
// * server is under heavy load, Updates slow down to 1/s
// * Elapsed(1.000) returns true.
// technically 10 intervals have elapsed.
// * server recovers to normal, Updates are every 10ms again
// * Elapsed(1.010) should return false again until 1.100.
//
// increasing lastTime by interval would require 10 more calls
// to ever catch up again:
// lastTime += interval
//
// as result, the next 10 calls to Elapsed would return true.
// Elapsed(1.001) => true
// Elapsed(1.002) => true
// Elapsed(1.003) => true
// ...
// even though technically the delta was not >= interval.
//
// this would keep the server under heavy load, and it may never
// catch-up. this is not ideal for large virtual worlds.
//
// instead, we want to skip multiples of 'interval' and only
// keep the remainder.
//
// see also: AccurateIntervalTests.Slowdown()
// easy to understand:
//double elapsed = time - lastTime;
//double remainder = elapsed % interval;
//lastTime = time - remainder;
// easier: set to rounded multiples of interval (fholm).
// long to match double time.
long multiplier = (long)(time / interval);
lastTime = multiplier * interval;
return true;
}
}
}
@@ -1,18 +1,18 @@
fileFormatVersion: 2
guid: c1b18064e25046f28b88db65a4012ec1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/AccurateInterval.cs
uploadId: 736421
fileFormatVersion: 2
guid: c1b18064e25046f28b88db65a4012ec1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/AccurateInterval.cs
uploadId: 736421
File diff suppressed because it is too large Load Diff
+18 -18
View File
@@ -1,18 +1,18 @@
fileFormatVersion: 2
guid: 5c28963f9c4b97e418252a55500fb91e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/Compression.cs
uploadId: 736421
fileFormatVersion: 2
guid: 5c28963f9c4b97e418252a55500fb91e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/Compression.cs
uploadId: 736421
+58 -58
View File
@@ -1,58 +1,58 @@
// manual delta compression for some types.
// varint(b-a)
// Mirror can't use Mirror II's bit-tree delta compression.
using System.Runtime.CompilerServices;
namespace Mirror
{
public static class DeltaCompression
{
// delta (usually small), then zigzag varint to support +- changes
// parameter order: (last, current) makes most sense (Q3 does this too).
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Compress(NetworkWriter writer, long last, long current) =>
Compression.CompressVarInt(writer, current - last);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static long Decompress(NetworkReader reader, long last) =>
last + Compression.DecompressVarInt(reader);
// delta (usually small), then zigzag varint to support +- changes
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Compress(NetworkWriter writer, Vector3Long last, Vector3Long current)
{
Compress(writer, last.x, current.x);
Compress(writer, last.y, current.y);
Compress(writer, last.z, current.z);
}
// delta (usually small), then zigzag varint to support +- changes
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Compress(NetworkWriter writer, Vector4Long last, Vector4Long current)
{
Compress(writer, last.x, current.x);
Compress(writer, last.y, current.y);
Compress(writer, last.z, current.z);
Compress(writer, last.w, current.w);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3Long Decompress(NetworkReader reader, Vector3Long last)
{
long x = Decompress(reader, last.x);
long y = Decompress(reader, last.y);
long z = Decompress(reader, last.z);
return new Vector3Long(x, y, z);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4Long Decompress(NetworkReader reader, Vector4Long last)
{
long x = Decompress(reader, last.x);
long y = Decompress(reader, last.y);
long z = Decompress(reader, last.z);
long w = Decompress(reader, last.w);
return new Vector4Long(x, y, z, w);
}
}
}
// manual delta compression for some types.
// varint(b-a)
// Mirror can't use Mirror II's bit-tree delta compression.
using System.Runtime.CompilerServices;
namespace Mirror
{
public static class DeltaCompression
{
// delta (usually small), then zigzag varint to support +- changes
// parameter order: (last, current) makes most sense (Q3 does this too).
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Compress(NetworkWriter writer, long last, long current) =>
Compression.CompressVarInt(writer, current - last);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static long Decompress(NetworkReader reader, long last) =>
last + Compression.DecompressVarInt(reader);
// delta (usually small), then zigzag varint to support +- changes
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Compress(NetworkWriter writer, Vector3Long last, Vector3Long current)
{
Compress(writer, last.x, current.x);
Compress(writer, last.y, current.y);
Compress(writer, last.z, current.z);
}
// delta (usually small), then zigzag varint to support +- changes
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Compress(NetworkWriter writer, Vector4Long last, Vector4Long current)
{
Compress(writer, last.x, current.x);
Compress(writer, last.y, current.y);
Compress(writer, last.z, current.z);
Compress(writer, last.w, current.w);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3Long Decompress(NetworkReader reader, Vector3Long last)
{
long x = Decompress(reader, last.x);
long y = Decompress(reader, last.y);
long z = Decompress(reader, last.z);
return new Vector3Long(x, y, z);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4Long Decompress(NetworkReader reader, Vector4Long last)
{
long x = Decompress(reader, last.x);
long y = Decompress(reader, last.y);
long z = Decompress(reader, last.z);
long w = Decompress(reader, last.w);
return new Vector4Long(x, y, z, w);
}
}
}
@@ -1,18 +1,18 @@
fileFormatVersion: 2
guid: 6b8f3fffcb4754c15bc5ed4c33e2497b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/DeltaCompression.cs
uploadId: 736421
fileFormatVersion: 2
guid: 6b8f3fffcb4754c15bc5ed4c33e2497b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/DeltaCompression.cs
uploadId: 736421
@@ -1,53 +1,53 @@
// N-day EMA implementation from Mirror with a few changes (struct etc.)
// it calculates an exponential moving average roughly equivalent to the last n observations
// https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
using System;
namespace Mirror
{
public struct ExponentialMovingAverage
{
readonly double alpha;
bool initialized;
public double Value;
public double Variance;
public double StandardDeviation; // absolute value, see test
public ExponentialMovingAverage(int n)
{
// standard N-day EMA alpha calculation
alpha = 2.0 / (n + 1);
initialized = false;
Value = 0;
Variance = 0;
StandardDeviation = 0;
}
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;
Variance = (1 - alpha) * (Variance + alpha * delta * delta);
StandardDeviation = Math.Sqrt(Variance);
}
else
{
Value = newValue;
initialized = true;
}
}
public void Reset()
{
initialized = false;
Value = 0;
Variance = 0;
StandardDeviation = 0;
}
}
}
// N-day EMA implementation from Mirror with a few changes (struct etc.)
// it calculates an exponential moving average roughly equivalent to the last n observations
// https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
using System;
namespace Mirror
{
public struct ExponentialMovingAverage
{
readonly double alpha;
bool initialized;
public double Value;
public double Variance;
public double StandardDeviation; // absolute value, see test
public ExponentialMovingAverage(int n)
{
// standard N-day EMA alpha calculation
alpha = 2.0 / (n + 1);
initialized = false;
Value = 0;
Variance = 0;
StandardDeviation = 0;
}
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;
Variance = (1 - alpha) * (Variance + alpha * delta * delta);
StandardDeviation = Math.Sqrt(Variance);
}
else
{
Value = newValue;
initialized = true;
}
}
public void Reset()
{
initialized = false;
Value = 0;
Variance = 0;
StandardDeviation = 0;
}
}
}
@@ -1,18 +1,18 @@
fileFormatVersion: 2
guid: 05e858cbaa54b4ce4a48c8c7f50c1914
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/ExponentialMovingAverage.cs
uploadId: 736421
fileFormatVersion: 2
guid: 05e858cbaa54b4ce4a48c8c7f50c1914
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/ExponentialMovingAverage.cs
uploadId: 736421
+145 -145
View File
@@ -1,145 +1,145 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace Mirror
{
public static class Extensions
{
public static string ToHexString(this ArraySegment<byte> segment) =>
BitConverter.ToString(segment.Array, segment.Offset, segment.Count);
// string.GetHashCode is not guaranteed to be the same on all
// machines, but we need one that is the same on all machines.
// Uses fnv1a as hash function for more uniform distribution http://www.isthe.com/chongo/tech/comp/fnv/
// Tests: https://softwareengineering.stackexchange.com/questions/49550/which-hashing-algorithm-is-best-for-uniqueness-and-speed
// NOTE: Do not call this from hot path because it's slow O(N) for long method names.
// - As of 2012-02-16 There are 2 design-time callers (weaver) and 1 runtime caller that caches.
public static int GetStableHashCode(this string text)
{
unchecked
{
uint hash = 0x811c9dc5;
uint prime = 0x1000193;
for (int i = 0; i < text.Length; ++i)
{
byte value = (byte)text[i];
hash = hash ^ value;
hash *= prime;
}
//UnityEngine.Debug.Log($"Created stable hash {(ushort)hash} for {text}");
return (int)hash;
}
}
// smaller version of our GetStableHashCode.
// careful, this significantly increases chance of collisions.
public static ushort GetStableHashCode16(this string text)
{
// deterministic hash
int hash = GetStableHashCode(text);
// Gets the 32bit fnv1a hash
// To get it down to 16bit but still reduce hash collisions we cant just cast it to ushort
// Instead we take the highest 16bits of the 32bit hash and fold them with xor into the lower 16bits
// This will create a more uniform 16bit hash, the method is described in:
// http://www.isthe.com/chongo/tech/comp/fnv/ in section "Changing the FNV hash size - xor-folding"
return (ushort)((hash >> 16) ^ hash);
}
// previously in DotnetCompatibility.cs
// leftover from the UNET days. supposedly for windows store?
public static string GetMethodName(this Delegate func)
{
#if NETFX_CORE
return func.GetMethodInfo().Name;
#else
return func.Method.Name;
#endif
}
// helper function to copy to List<T>
// C# only provides CopyTo(T[])
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CopyTo<T>(this IEnumerable<T> source, List<T> destination)
{
// foreach allocates. use AddRange.
destination.AddRange(source);
}
#if !UNITY_2021_OR_NEWER
// Unity 2020 and earlier don't have Queue.TryDequeue which we need for batching.
public static bool TryDequeue<T>(this Queue<T> source, out T element)
{
if (source.Count > 0)
{
element = source.Dequeue();
return true;
}
element = default;
return false;
}
#endif
#if !UNITY_2021_OR_NEWER
// Unity 2020 and earlier don't have ConcurrentQueue.Clear which we need for ThreadedTransport.
public static void Clear<T>(this ConcurrentQueue<T> source)
{
// while count > 0 risks deadlock if other thread write at the same time.
// our safest solution is a best-effort approach to clear 'Count' once.
int count = source.Count; // get it only once
for (int i = 0; i < count; ++i)
{
source.TryDequeue(out _);
}
}
#endif
#if !UNITY_2021_3_OR_NEWER
// Some patch versions of Unity 2021.3 and earlier don't have transform.GetPositionAndRotation which we use for performance in some places
public static void GetPositionAndRotation(this Transform transform, out Vector3 position, out Quaternion rotation)
{
position = transform.position;
rotation = transform.rotation;
}
public static void SetPositionAndRotation(this Transform transform, Vector3 position, Quaternion rotation)
{
transform.position = position;
transform.rotation = rotation;
}
public static void GetLocalPositionAndRotation(this Transform transform, out Vector3 position, out Quaternion rotation)
{
position = transform.localPosition;
rotation = transform.localRotation;
}
public static void SetLocalPositionAndRotation(this Transform transform, Vector3 position, Quaternion rotation)
{
transform.localPosition = position;
transform.localRotation = rotation;
}
#endif
// IPEndPoint address only to pretty string.
// useful for to get a connection's address for IP bans etc.
public static string PrettyAddress(this IPEndPoint endPoint)
{
if (endPoint == null) return "";
// Map to IPv4 if "IsIPv4MappedToIPv6" for readability
// "::ffff:127.0.0.1" -> "127.0.0.1"
return
endPoint.Address.IsIPv4MappedToIPv6
? endPoint.Address.MapToIPv4().ToString()
: endPoint.Address.ToString();
}
}
}
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace Mirror
{
public static class Extensions
{
public static string ToHexString(this ArraySegment<byte> segment) =>
BitConverter.ToString(segment.Array, segment.Offset, segment.Count);
// string.GetHashCode is not guaranteed to be the same on all
// machines, but we need one that is the same on all machines.
// Uses fnv1a as hash function for more uniform distribution http://www.isthe.com/chongo/tech/comp/fnv/
// Tests: https://softwareengineering.stackexchange.com/questions/49550/which-hashing-algorithm-is-best-for-uniqueness-and-speed
// NOTE: Do not call this from hot path because it's slow O(N) for long method names.
// - As of 2012-02-16 There are 2 design-time callers (weaver) and 1 runtime caller that caches.
public static int GetStableHashCode(this string text)
{
unchecked
{
uint hash = 0x811c9dc5;
uint prime = 0x1000193;
for (int i = 0; i < text.Length; ++i)
{
byte value = (byte)text[i];
hash = hash ^ value;
hash *= prime;
}
//UnityEngine.Debug.Log($"Created stable hash {(ushort)hash} for {text}");
return (int)hash;
}
}
// smaller version of our GetStableHashCode.
// careful, this significantly increases chance of collisions.
public static ushort GetStableHashCode16(this string text)
{
// deterministic hash
int hash = GetStableHashCode(text);
// Gets the 32bit fnv1a hash
// To get it down to 16bit but still reduce hash collisions we cant just cast it to ushort
// Instead we take the highest 16bits of the 32bit hash and fold them with xor into the lower 16bits
// This will create a more uniform 16bit hash, the method is described in:
// http://www.isthe.com/chongo/tech/comp/fnv/ in section "Changing the FNV hash size - xor-folding"
return (ushort)((hash >> 16) ^ hash);
}
// previously in DotnetCompatibility.cs
// leftover from the UNET days. supposedly for windows store?
public static string GetMethodName(this Delegate func)
{
#if NETFX_CORE
return func.GetMethodInfo().Name;
#else
return func.Method.Name;
#endif
}
// helper function to copy to List<T>
// C# only provides CopyTo(T[])
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CopyTo<T>(this IEnumerable<T> source, List<T> destination)
{
// foreach allocates. use AddRange.
destination.AddRange(source);
}
#if !UNITY_2021_OR_NEWER
// Unity 2020 and earlier don't have Queue.TryDequeue which we need for batching.
public static bool TryDequeue<T>(this Queue<T> source, out T element)
{
if (source.Count > 0)
{
element = source.Dequeue();
return true;
}
element = default;
return false;
}
#endif
#if !UNITY_2021_OR_NEWER
// Unity 2020 and earlier don't have ConcurrentQueue.Clear which we need for ThreadedTransport.
public static void Clear<T>(this ConcurrentQueue<T> source)
{
// while count > 0 risks deadlock if other thread write at the same time.
// our safest solution is a best-effort approach to clear 'Count' once.
int count = source.Count; // get it only once
for (int i = 0; i < count; ++i)
{
source.TryDequeue(out _);
}
}
#endif
#if !UNITY_2021_3_OR_NEWER
// Some patch versions of Unity 2021.3 and earlier don't have transform.GetPositionAndRotation which we use for performance in some places
public static void GetPositionAndRotation(this Transform transform, out Vector3 position, out Quaternion rotation)
{
position = transform.position;
rotation = transform.rotation;
}
public static void SetPositionAndRotation(this Transform transform, Vector3 position, Quaternion rotation)
{
transform.position = position;
transform.rotation = rotation;
}
public static void GetLocalPositionAndRotation(this Transform transform, out Vector3 position, out Quaternion rotation)
{
position = transform.localPosition;
rotation = transform.localRotation;
}
public static void SetLocalPositionAndRotation(this Transform transform, Vector3 position, Quaternion rotation)
{
transform.localPosition = position;
transform.localRotation = rotation;
}
#endif
// IPEndPoint address only to pretty string.
// useful for to get a connection's address for IP bans etc.
public static string PrettyAddress(this IPEndPoint endPoint)
{
if (endPoint == null) return "";
// Map to IPv4 if "IsIPv4MappedToIPv6" for readability
// "::ffff:127.0.0.1" -> "127.0.0.1"
return
endPoint.Address.IsIPv4MappedToIPv6
? endPoint.Address.MapToIPv4().ToString()
: endPoint.Address.ToString();
}
}
}
+18 -18
View File
@@ -1,18 +1,18 @@
fileFormatVersion: 2
guid: decf32fd053744d18f35712b7a6f5116
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/Extensions.cs
uploadId: 736421
fileFormatVersion: 2
guid: decf32fd053744d18f35712b7a6f5116
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/Extensions.cs
uploadId: 736421
File diff suppressed because it is too large Load Diff
+10 -10
View File
@@ -1,10 +1,10 @@
fileFormatVersion: 2
guid: 70b5947c49174f3c90121edd21ea6b15
timeCreated: 1729783714
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/Half.cs
uploadId: 736421
fileFormatVersion: 2
guid: 70b5947c49174f3c90121edd21ea6b15
timeCreated: 1729783714
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/Half.cs
uploadId: 736421
+32 -32
View File
@@ -1,32 +1,32 @@
// 'double' precision variants for some of Unity's Mathf functions.
using System.Runtime.CompilerServices;
namespace Mirror
{
public static class Mathd
{
// Unity 2020 doesn't have Math.Clamp yet.
/// <summary>Clamps value between 0 and 1 and returns value.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double Clamp(double value, double min, double max)
{
if (value < min) return min;
if (value > max) return max;
return value;
}
/// <summary>Clamps value between 0 and 1 and returns value.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double Clamp01(double value) => Clamp(value, 0, 1);
/// <summary>Calculates the linear parameter t that produces the interpolant value within the range [a, b].</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double InverseLerp(double a, double b, double value) =>
a != b ? Clamp01((value - a) / (b - a)) : 0;
/// <summary>Linearly interpolates between a and b by t with no limit to t.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double LerpUnclamped(double a, double b, double t) =>
a + (b - a) * t;
}
}
// 'double' precision variants for some of Unity's Mathf functions.
using System.Runtime.CompilerServices;
namespace Mirror
{
public static class Mathd
{
// Unity 2020 doesn't have Math.Clamp yet.
/// <summary>Clamps value between 0 and 1 and returns value.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double Clamp(double value, double min, double max)
{
if (value < min) return min;
if (value > max) return max;
return value;
}
/// <summary>Clamps value between 0 and 1 and returns value.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double Clamp01(double value) => Clamp(value, 0, 1);
/// <summary>Calculates the linear parameter t that produces the interpolant value within the range [a, b].</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double InverseLerp(double a, double b, double value) =>
a != b ? Clamp01((value - a) / (b - a)) : 0;
/// <summary>Linearly interpolates between a and b by t with no limit to t.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double LerpUnclamped(double a, double b, double t) =>
a + (b - a) * t;
}
}
+18 -18
View File
@@ -1,18 +1,18 @@
fileFormatVersion: 2
guid: 5f74084b91c74df2839b426c4a381373
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/Mathd.cs
uploadId: 736421
fileFormatVersion: 2
guid: 5f74084b91c74df2839b426c4a381373
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/Mathd.cs
uploadId: 736421
+48 -48
View File
@@ -1,48 +1,48 @@
// Pool to avoid allocations (from libuv2k)
// API consistent with Microsoft's ObjectPool<T>.
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Mirror
{
public class Pool<T>
{
// Mirror is single threaded, no need for concurrent collections.
// stack increases the chance that a reused writer remains in cache.
readonly Stack<T> objects = new Stack<T>();
// some types might need additional parameters in their constructor, so
// we use a Func<T> generator
readonly Func<T> objectGenerator;
public Pool(Func<T> objectGenerator, int initialCapacity)
{
this.objectGenerator = objectGenerator;
// allocate an initial pool so we have fewer (if any)
// allocations in the first few frames (or seconds).
for (int i = 0; i < initialCapacity; ++i)
objects.Push(objectGenerator());
}
// take an element from the pool, or create a new one if empty
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Get() => objects.Count > 0 ? objects.Pop() : objectGenerator();
// return an element to the pool
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Return(T item)
{
// make sure we can't accidentally insert null values into the pool.
// debugging this would be hard since it would only show on get().
if (item == null)
throw new ArgumentNullException(nameof(item));
objects.Push(item);
}
// count to see how many objects are in the pool. useful for tests.
public int Count => objects.Count;
}
}
// Pool to avoid allocations (from libuv2k)
// API consistent with Microsoft's ObjectPool<T>.
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Mirror
{
public class Pool<T>
{
// Mirror is single threaded, no need for concurrent collections.
// stack increases the chance that a reused writer remains in cache.
readonly Stack<T> objects = new Stack<T>();
// some types might need additional parameters in their constructor, so
// we use a Func<T> generator
readonly Func<T> objectGenerator;
public Pool(Func<T> objectGenerator, int initialCapacity)
{
this.objectGenerator = objectGenerator;
// allocate an initial pool so we have fewer (if any)
// allocations in the first few frames (or seconds).
for (int i = 0; i < initialCapacity; ++i)
objects.Push(objectGenerator());
}
// take an element from the pool, or create a new one if empty
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Get() => objects.Count > 0 ? objects.Pop() : objectGenerator();
// return an element to the pool
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Return(T item)
{
// make sure we can't accidentally insert null values into the pool.
// debugging this would be hard since it would only show on get().
if (item == null)
throw new ArgumentNullException(nameof(item));
objects.Push(item);
}
// count to see how many objects are in the pool. useful for tests.
public int Count => objects.Count;
}
}
+18 -18
View File
@@ -1,18 +1,18 @@
fileFormatVersion: 2
guid: 845bb05fa349344c3811022f4f15dfbc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/Pool.cs
uploadId: 736421
fileFormatVersion: 2
guid: 845bb05fa349344c3811022f4f15dfbc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/Pool.cs
uploadId: 736421
+10 -10
View File
@@ -1,10 +1,10 @@
fileFormatVersion: 2
guid: da033671de7d49e0838223a997c56bf1
timeCreated: 1667486850
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/Readme.txt
uploadId: 736421
fileFormatVersion: 2
guid: da033671de7d49e0838223a997c56bf1
timeCreated: 1667486850
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/Readme.txt
uploadId: 736421
+61 -61
View File
@@ -1,61 +1,61 @@
// TimeSample from Mirror II.
// simple profiling sample, averaged for display in statistics.
// usable in builds without unitiy profiler overhead etc.
//
// .average may safely be called from main thread while Begin/End is in another.
// i.e. worker threads, transport, etc.
using System.Diagnostics;
using System.Threading;
namespace Mirror
{
public struct TimeSample
{
// UnityEngine.Time isn't thread safe. use stopwatch instead.
readonly Stopwatch watch;
// remember when Begin was called
double beginTime;
// keep accumulating times over the given interval.
// (not readonly. we modify its contents.)
ExponentialMovingAverage ema;
// average in seconds.
// code often runs in sub-millisecond time. float is more precise.
//
// set with Interlocked for thread safety.
// can be read from main thread while sampling happens in other thread.
public double average; // THREAD SAFE
// average over N begin/end captures
public TimeSample(int n)
{
watch = new Stopwatch();
watch.Start();
ema = new ExponentialMovingAverage(n);
beginTime = 0;
average = 0;
}
// begin is called before the code to be sampled
public void Begin()
{
// remember when Begin was called.
// keep StopWatch running so we can average over the given interval.
beginTime = watch.Elapsed.TotalSeconds;
// Debug.Log($"Begin @ {beginTime:F4}");
}
// end is called after the code to be sampled
public void End()
{
// add duration in seconds to accumulated durations
double elapsed = watch.Elapsed.TotalSeconds - beginTime;
ema.Add(elapsed);
// expose new average thread safely
Interlocked.Exchange(ref average, ema.Value);
}
}
}
// TimeSample from Mirror II.
// simple profiling sample, averaged for display in statistics.
// usable in builds without unitiy profiler overhead etc.
//
// .average may safely be called from main thread while Begin/End is in another.
// i.e. worker threads, transport, etc.
using System.Diagnostics;
using System.Threading;
namespace Mirror
{
public struct TimeSample
{
// UnityEngine.Time isn't thread safe. use stopwatch instead.
readonly Stopwatch watch;
// remember when Begin was called
double beginTime;
// keep accumulating times over the given interval.
// (not readonly. we modify its contents.)
ExponentialMovingAverage ema;
// average in seconds.
// code often runs in sub-millisecond time. float is more precise.
//
// set with Interlocked for thread safety.
// can be read from main thread while sampling happens in other thread.
public double average; // THREAD SAFE
// average over N begin/end captures
public TimeSample(int n)
{
watch = new Stopwatch();
watch.Start();
ema = new ExponentialMovingAverage(n);
beginTime = 0;
average = 0;
}
// begin is called before the code to be sampled
public void Begin()
{
// remember when Begin was called.
// keep StopWatch running so we can average over the given interval.
beginTime = watch.Elapsed.TotalSeconds;
// Debug.Log($"Begin @ {beginTime:F4}");
}
// end is called after the code to be sampled
public void End()
{
// add duration in seconds to accumulated durations
double elapsed = watch.Elapsed.TotalSeconds - beginTime;
ema.Add(elapsed);
// expose new average thread safely
Interlocked.Exchange(ref average, ema.Value);
}
}
}
+18 -18
View File
@@ -1,18 +1,18 @@
fileFormatVersion: 2
guid: 26c32f6429554546a88d800c846c74ed
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/TimeSample.cs
uploadId: 736421
fileFormatVersion: 2
guid: 26c32f6429554546a88d800c846c74ed
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/TimeSample.cs
uploadId: 736421
+222 -222
View File
@@ -1,222 +1,222 @@
using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;
namespace Mirror
{
// Handles network messages on client and server
public delegate void NetworkMessageDelegate(NetworkConnection conn, NetworkReader reader, int channelId);
// Handles requests to spawn objects on the client
public delegate GameObject SpawnDelegate(Vector3 position, uint assetId);
public delegate GameObject SpawnHandlerDelegate(SpawnMessage msg);
// Handles requests to unspawn objects on the client
public delegate void UnSpawnDelegate(GameObject spawned);
// channels are const ints instead of an enum so people can add their own
// channels (can't extend an enum otherwise).
//
// note that Mirror is slowly moving towards quake style networking which
// will only require reliable for handshake, and unreliable for the rest.
// so eventually we can change this to an Enum and transports shouldn't
// add custom channels anymore.
public static class Channels
{
public const int Reliable = 0; // ordered
public const int Unreliable = 1; // unordered
}
public static class Utils
{
// detect headless / dedicated server mode
// SystemInfo.graphicsDeviceType is never null in the editor.
// UNITY_SERVER works in builds for all Unity versions 2019 LTS and later.
// For Unity 2019 / 2020, there is no way to detect Server Build checkbox
// state in Build Settings, so they never auto-start headless server / client.
// UNITY_SERVER works in the editor in Unity 2021 LTS and later
// because that's when Dedicated Server platform was added.
// It is intentional for editor play mode to auto-start headless server / client
// when Dedicated Server platform is selected in the editor so that editor
// acts like a headless build to every extent possible for testing / debugging.
public static bool IsHeadless() =>
#if UNITY_SERVER
true;
#else
SystemInfo.graphicsDeviceType == GraphicsDeviceType.Null;
#endif
// detect WebGL mode
public const bool IsWebGL =
#if UNITY_WEBGL
true;
#else
false;
#endif
// detect Debug mode
public const bool IsDebug =
#if DEBUG
true;
#else
false;
#endif
public static uint GetTrueRandomUInt()
{
// use Crypto RNG to avoid having time based duplicates
using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
{
byte[] bytes = new byte[4];
rng.GetBytes(bytes);
return BitConverter.ToUInt32(bytes, 0);
}
}
public static bool IsPrefab(GameObject obj)
{
#if UNITY_EDITOR
return UnityEditor.PrefabUtility.IsPartOfPrefabAsset(obj);
#else
return false;
#endif
}
// simplified IsSceneObject check from Mirror II
public static bool IsSceneObject(NetworkIdentity identity)
{
// original UNET / Mirror still had the IsPersistent check.
// it never fires though. even for Prefabs dragged to the Scene.
// (see Scene Objects example scene.)
// #if UNITY_EDITOR
// if (UnityEditor.EditorUtility.IsPersistent(identity.gameObject))
// return false;
// #endif
return identity.gameObject.hideFlags != HideFlags.NotEditable &&
identity.gameObject.hideFlags != HideFlags.HideAndDontSave &&
identity.sceneId != 0;
}
public static bool IsSceneObjectWithPrefabParent(GameObject gameObject, out GameObject prefab)
{
prefab = null;
#if UNITY_EDITOR
if (!UnityEditor.PrefabUtility.IsPartOfPrefabInstance(gameObject))
{
return false;
}
prefab = UnityEditor.PrefabUtility.GetCorrespondingObjectFromSource(gameObject);
#endif
if (prefab == null)
{
Debug.LogError($"Failed to find prefab parent for scene object [name:{gameObject.name}]");
return false;
}
return true;
}
// is a 2D point in screen? (from ummorpg)
// (if width = 1024, then indices from 0..1023 are valid (=1024 indices)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsPointInScreen(Vector2 point) =>
0 <= point.x && point.x < Screen.width &&
0 <= point.y && point.y < Screen.height;
// pretty print bytes as KB/MB/GB/etc. from DOTSNET
// long to support > 2GB
// divides by floats to return "2.5MB" etc.
public static string PrettyBytes(long bytes)
{
// bytes
if (bytes < 1024)
return $"{bytes} B";
// kilobytes
else if (bytes < 1024L * 1024L)
return $"{(bytes / 1024f):F2} KB";
// megabytes
else if (bytes < 1024 * 1024L * 1024L)
return $"{(bytes / (1024f * 1024f)):F2} MB";
// gigabytes
return $"{(bytes / (1024f * 1024f * 1024f)):F2} GB";
}
// pretty print seconds as hours:minutes:seconds(.milliseconds/100)s.
// double for long running servers.
public static string PrettySeconds(double seconds)
{
TimeSpan t = TimeSpan.FromSeconds(seconds);
string res = "";
if (t.Days > 0) res += $"{t.Days}d";
if (t.Hours > 0) res += $"{(res.Length > 0 ? " " : "")}{t.Hours}h";
if (t.Minutes > 0) res += $"{(res.Length > 0 ? " " : "")}{t.Minutes}m";
// 0.5s, 1.5s etc. if any milliseconds. 1s, 2s etc. if any seconds
if (t.Milliseconds > 0) res += $"{(res.Length > 0 ? " " : "")}{t.Seconds}.{(t.Milliseconds / 100)}s";
else if (t.Seconds > 0) res += $"{(res.Length > 0 ? " " : "")}{t.Seconds}s";
// if the string is still empty because the value was '0', then at least
// return the seconds instead of returning an empty string
return res != "" ? res : "0s";
}
// universal .spawned function
public static NetworkIdentity GetSpawnedInServerOrClient(uint netId)
{
// server / host mode: use the one from server.
// host mode has access to all spawned.
if (NetworkServer.active)
{
NetworkServer.spawned.TryGetValue(netId, out NetworkIdentity entry);
return entry;
}
// client
if (NetworkClient.active)
{
NetworkClient.spawned.TryGetValue(netId, out NetworkIdentity entry);
return entry;
}
return null;
}
// keep a GUI window in screen.
// for example. if it's at x=1000 and screen is resized to w=500,
// it won't get lost in the invisible area etc.
public static Rect KeepInScreen(Rect rect)
{
// ensure min
rect.x = Math.Max(rect.x, 0);
rect.y = Math.Max(rect.y, 0);
// ensure max
rect.x = Math.Min(rect.x, Screen.width - rect.width);
rect.y = Math.Min(rect.y, Screen.width - rect.height);
return rect;
}
// create local connections pair and connect them
public static void CreateLocalConnections(
out LocalConnectionToClient connectionToClient,
out LocalConnectionToServer connectionToServer)
{
connectionToServer = new LocalConnectionToServer();
connectionToClient = new LocalConnectionToClient();
connectionToServer.connectionToClient = connectionToClient;
connectionToClient.connectionToServer = connectionToServer;
}
public static bool IsSceneActive(string scene)
{
Scene activeScene = SceneManager.GetActiveScene();
return activeScene.path == scene ||
activeScene.name == scene;
}
}
}
using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;
namespace Mirror
{
// Handles network messages on client and server
public delegate void NetworkMessageDelegate(NetworkConnection conn, NetworkReader reader, int channelId);
// Handles requests to spawn objects on the client
public delegate GameObject SpawnDelegate(Vector3 position, uint assetId);
public delegate GameObject SpawnHandlerDelegate(SpawnMessage msg);
// Handles requests to unspawn objects on the client
public delegate void UnSpawnDelegate(GameObject spawned);
// channels are const ints instead of an enum so people can add their own
// channels (can't extend an enum otherwise).
//
// note that Mirror is slowly moving towards quake style networking which
// will only require reliable for handshake, and unreliable for the rest.
// so eventually we can change this to an Enum and transports shouldn't
// add custom channels anymore.
public static class Channels
{
public const int Reliable = 0; // ordered
public const int Unreliable = 1; // unordered
}
public static class Utils
{
// detect headless / dedicated server mode
// SystemInfo.graphicsDeviceType is never null in the editor.
// UNITY_SERVER works in builds for all Unity versions 2019 LTS and later.
// For Unity 2019 / 2020, there is no way to detect Server Build checkbox
// state in Build Settings, so they never auto-start headless server / client.
// UNITY_SERVER works in the editor in Unity 2021 LTS and later
// because that's when Dedicated Server platform was added.
// It is intentional for editor play mode to auto-start headless server / client
// when Dedicated Server platform is selected in the editor so that editor
// acts like a headless build to every extent possible for testing / debugging.
public static bool IsHeadless() =>
#if UNITY_SERVER
true;
#else
SystemInfo.graphicsDeviceType == GraphicsDeviceType.Null;
#endif
// detect WebGL mode
public const bool IsWebGL =
#if UNITY_WEBGL
true;
#else
false;
#endif
// detect Debug mode
public const bool IsDebug =
#if DEBUG
true;
#else
false;
#endif
public static uint GetTrueRandomUInt()
{
// use Crypto RNG to avoid having time based duplicates
using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
{
byte[] bytes = new byte[4];
rng.GetBytes(bytes);
return BitConverter.ToUInt32(bytes, 0);
}
}
public static bool IsPrefab(GameObject obj)
{
#if UNITY_EDITOR
return UnityEditor.PrefabUtility.IsPartOfPrefabAsset(obj);
#else
return false;
#endif
}
// simplified IsSceneObject check from Mirror II
public static bool IsSceneObject(NetworkIdentity identity)
{
// original UNET / Mirror still had the IsPersistent check.
// it never fires though. even for Prefabs dragged to the Scene.
// (see Scene Objects example scene.)
// #if UNITY_EDITOR
// if (UnityEditor.EditorUtility.IsPersistent(identity.gameObject))
// return false;
// #endif
return identity.gameObject.hideFlags != HideFlags.NotEditable &&
identity.gameObject.hideFlags != HideFlags.HideAndDontSave &&
identity.sceneId != 0;
}
public static bool IsSceneObjectWithPrefabParent(GameObject gameObject, out GameObject prefab)
{
prefab = null;
#if UNITY_EDITOR
if (!UnityEditor.PrefabUtility.IsPartOfPrefabInstance(gameObject))
{
return false;
}
prefab = UnityEditor.PrefabUtility.GetCorrespondingObjectFromSource(gameObject);
#endif
if (prefab == null)
{
Debug.LogError($"Failed to find prefab parent for scene object [name:{gameObject.name}]");
return false;
}
return true;
}
// is a 2D point in screen? (from ummorpg)
// (if width = 1024, then indices from 0..1023 are valid (=1024 indices)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsPointInScreen(Vector2 point) =>
0 <= point.x && point.x < Screen.width &&
0 <= point.y && point.y < Screen.height;
// pretty print bytes as KB/MB/GB/etc. from DOTSNET
// long to support > 2GB
// divides by floats to return "2.5MB" etc.
public static string PrettyBytes(long bytes)
{
// bytes
if (bytes < 1024)
return $"{bytes} B";
// kilobytes
else if (bytes < 1024L * 1024L)
return $"{(bytes / 1024f):F2} KB";
// megabytes
else if (bytes < 1024 * 1024L * 1024L)
return $"{(bytes / (1024f * 1024f)):F2} MB";
// gigabytes
return $"{(bytes / (1024f * 1024f * 1024f)):F2} GB";
}
// pretty print seconds as hours:minutes:seconds(.milliseconds/100)s.
// double for long running servers.
public static string PrettySeconds(double seconds)
{
TimeSpan t = TimeSpan.FromSeconds(seconds);
string res = "";
if (t.Days > 0) res += $"{t.Days}d";
if (t.Hours > 0) res += $"{(res.Length > 0 ? " " : "")}{t.Hours}h";
if (t.Minutes > 0) res += $"{(res.Length > 0 ? " " : "")}{t.Minutes}m";
// 0.5s, 1.5s etc. if any milliseconds. 1s, 2s etc. if any seconds
if (t.Milliseconds > 0) res += $"{(res.Length > 0 ? " " : "")}{t.Seconds}.{(t.Milliseconds / 100)}s";
else if (t.Seconds > 0) res += $"{(res.Length > 0 ? " " : "")}{t.Seconds}s";
// if the string is still empty because the value was '0', then at least
// return the seconds instead of returning an empty string
return res != "" ? res : "0s";
}
// universal .spawned function
public static NetworkIdentity GetSpawnedInServerOrClient(uint netId)
{
// server / host mode: use the one from server.
// host mode has access to all spawned.
if (NetworkServer.active)
{
NetworkServer.spawned.TryGetValue(netId, out NetworkIdentity entry);
return entry;
}
// client
if (NetworkClient.active)
{
NetworkClient.spawned.TryGetValue(netId, out NetworkIdentity entry);
return entry;
}
return null;
}
// keep a GUI window in screen.
// for example. if it's at x=1000 and screen is resized to w=500,
// it won't get lost in the invisible area etc.
public static Rect KeepInScreen(Rect rect)
{
// ensure min
rect.x = Math.Max(rect.x, 0);
rect.y = Math.Max(rect.y, 0);
// ensure max
rect.x = Math.Min(rect.x, Screen.width - rect.width);
rect.y = Math.Min(rect.y, Screen.width - rect.height);
return rect;
}
// create local connections pair and connect them
public static void CreateLocalConnections(
out LocalConnectionToClient connectionToClient,
out LocalConnectionToServer connectionToServer)
{
connectionToServer = new LocalConnectionToServer();
connectionToClient = new LocalConnectionToClient();
connectionToServer.connectionToClient = connectionToClient;
connectionToClient.connectionToServer = connectionToServer;
}
public static bool IsSceneActive(string scene)
{
Scene activeScene = SceneManager.GetActiveScene();
return activeScene.path == scene ||
activeScene.name == scene;
}
}
}
+18 -18
View File
@@ -1,18 +1,18 @@
fileFormatVersion: 2
guid: b530ce39098b54374a29ad308c8e4554
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/Utils.cs
uploadId: 736421
fileFormatVersion: 2
guid: b530ce39098b54374a29ad308c8e4554
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/Utils.cs
uploadId: 736421
+125 -125
View File
@@ -1,125 +1,125 @@
#pragma warning disable CS0659 // 'Vector3Long' overrides Object.Equals(object o) but does not override Object.GetHashCode()
#pragma warning disable CS0661 // 'Vector3Long' defines operator == or operator != but does not override Object.GetHashCode()
// Vector3Long by mischa (based on game engine project)
using System;
using System.Runtime.CompilerServices;
namespace Mirror
{
public struct Vector3Long
{
public long x;
public long y;
public long z;
public static readonly Vector3Long zero = new Vector3Long(0, 0, 0);
public static readonly Vector3Long one = new Vector3Long(1, 1, 1);
public static readonly Vector3Long forward = new Vector3Long(0, 0, 1);
public static readonly Vector3Long back = new Vector3Long(0, 0, -1);
public static readonly Vector3Long left = new Vector3Long(-1, 0, 0);
public static readonly Vector3Long right = new Vector3Long(1, 0, 0);
public static readonly Vector3Long up = new Vector3Long(0, 1, 0);
public static readonly Vector3Long down = new Vector3Long(0, -1, 0);
// constructor /////////////////////////////////////////////////////////
public Vector3Long(long x, long y, long z)
{
this.x = x;
this.y = y;
this.z = z;
}
// operators ///////////////////////////////////////////////////////////
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3Long operator +(Vector3Long a, Vector3Long b) =>
new Vector3Long(a.x + b.x, a.y + b.y, a.z + b.z);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3Long operator -(Vector3Long a, Vector3Long b) =>
new Vector3Long(a.x - b.x, a.y - b.y, a.z - b.z);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3Long operator -(Vector3Long v) =>
new Vector3Long(-v.x, -v.y, -v.z);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3Long operator *(Vector3Long a, long n) =>
new Vector3Long(a.x * n, a.y * n, a.z * n);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3Long operator *(long n, Vector3Long a) =>
new Vector3Long(a.x * n, a.y * n, a.z * n);
// == returns true if approximately equal (with epsilon).
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Vector3Long a, Vector3Long b) =>
a.x == b.x &&
a.y == b.y &&
a.z == b.z;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Vector3Long a, Vector3Long b) => !(a == b);
// NO IMPLICIT System.Numerics.Vector3Long conversion because double<->float
// would silently lose precision in large worlds.
// [i] component index. useful for iterating all components etc.
public long this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
switch (index)
{
case 0: return x;
case 1: return y;
case 2: return z;
default: throw new IndexOutOfRangeException($"Vector3Long[{index}] out of range.");
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
switch (index)
{
case 0:
x = value;
break;
case 1:
y = value;
break;
case 2:
z = value;
break;
default: throw new IndexOutOfRangeException($"Vector3Long[{index}] out of range.");
}
}
}
// instance functions //////////////////////////////////////////////////
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override string ToString() => $"({x} {y} {z})";
// equality ////////////////////////////////////////////////////////////
// implement Equals & HashCode explicitly for performance.
// calling .Equals (instead of "==") checks for exact equality.
// (API compatibility)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Vector3Long other) =>
x == other.x && y == other.y && z == other.z;
// Equals(object) can reuse Equals(Vector4)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object other) =>
other is Vector3Long vector4 && Equals(vector4);
#if UNITY_2021_3_OR_NEWER
// Unity 2019/2020 don't have HashCode.Combine yet.
// this is only to avoid reflection. without defining, it works too.
// default generated by rider
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => HashCode.Combine(x, y, z);
#endif
}
}
#pragma warning disable CS0659 // 'Vector3Long' overrides Object.Equals(object o) but does not override Object.GetHashCode()
#pragma warning disable CS0661 // 'Vector3Long' defines operator == or operator != but does not override Object.GetHashCode()
// Vector3Long by mischa (based on game engine project)
using System;
using System.Runtime.CompilerServices;
namespace Mirror
{
public struct Vector3Long
{
public long x;
public long y;
public long z;
public static readonly Vector3Long zero = new Vector3Long(0, 0, 0);
public static readonly Vector3Long one = new Vector3Long(1, 1, 1);
public static readonly Vector3Long forward = new Vector3Long(0, 0, 1);
public static readonly Vector3Long back = new Vector3Long(0, 0, -1);
public static readonly Vector3Long left = new Vector3Long(-1, 0, 0);
public static readonly Vector3Long right = new Vector3Long(1, 0, 0);
public static readonly Vector3Long up = new Vector3Long(0, 1, 0);
public static readonly Vector3Long down = new Vector3Long(0, -1, 0);
// constructor /////////////////////////////////////////////////////////
public Vector3Long(long x, long y, long z)
{
this.x = x;
this.y = y;
this.z = z;
}
// operators ///////////////////////////////////////////////////////////
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3Long operator +(Vector3Long a, Vector3Long b) =>
new Vector3Long(a.x + b.x, a.y + b.y, a.z + b.z);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3Long operator -(Vector3Long a, Vector3Long b) =>
new Vector3Long(a.x - b.x, a.y - b.y, a.z - b.z);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3Long operator -(Vector3Long v) =>
new Vector3Long(-v.x, -v.y, -v.z);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3Long operator *(Vector3Long a, long n) =>
new Vector3Long(a.x * n, a.y * n, a.z * n);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3Long operator *(long n, Vector3Long a) =>
new Vector3Long(a.x * n, a.y * n, a.z * n);
// == returns true if approximately equal (with epsilon).
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Vector3Long a, Vector3Long b) =>
a.x == b.x &&
a.y == b.y &&
a.z == b.z;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Vector3Long a, Vector3Long b) => !(a == b);
// NO IMPLICIT System.Numerics.Vector3Long conversion because double<->float
// would silently lose precision in large worlds.
// [i] component index. useful for iterating all components etc.
public long this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
switch (index)
{
case 0: return x;
case 1: return y;
case 2: return z;
default: throw new IndexOutOfRangeException($"Vector3Long[{index}] out of range.");
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
switch (index)
{
case 0:
x = value;
break;
case 1:
y = value;
break;
case 2:
z = value;
break;
default: throw new IndexOutOfRangeException($"Vector3Long[{index}] out of range.");
}
}
}
// instance functions //////////////////////////////////////////////////
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override string ToString() => $"({x} {y} {z})";
// equality ////////////////////////////////////////////////////////////
// implement Equals & HashCode explicitly for performance.
// calling .Equals (instead of "==") checks for exact equality.
// (API compatibility)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Vector3Long other) =>
x == other.x && y == other.y && z == other.z;
// Equals(object) can reuse Equals(Vector4)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object other) =>
other is Vector3Long vector4 && Equals(vector4);
#if UNITY_2021_3_OR_NEWER
// Unity 2019/2020 don't have HashCode.Combine yet.
// this is only to avoid reflection. without defining, it works too.
// default generated by rider
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => HashCode.Combine(x, y, z);
#endif
}
}
+18 -18
View File
@@ -1,18 +1,18 @@
fileFormatVersion: 2
guid: 18efa4e349254185ad257401dd24628b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/Vector3Long.cs
uploadId: 736421
fileFormatVersion: 2
guid: 18efa4e349254185ad257401dd24628b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/Vector3Long.cs
uploadId: 736421
+126 -126
View File
@@ -1,126 +1,126 @@
#pragma warning disable CS0659 // 'Vector4Long' overrides Object.Equals(object o) but does not override Object.GetHashCode()
#pragma warning disable CS0661 // 'Vector4Long' defines operator == or operator != but does not override Object.GetHashCode()
// Vector4Long by mischa (based on game engine project)
using System;
using System.Runtime.CompilerServices;
namespace Mirror
{
public struct Vector4Long
{
public long x;
public long y;
public long z;
public long w;
public static readonly Vector4Long zero = new Vector4Long(0, 0, 0, 0);
public static readonly Vector4Long one = new Vector4Long(1, 1, 1, 1);
// constructor /////////////////////////////////////////////////////////
public Vector4Long(long x, long y, long z, long w)
{
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
// operators ///////////////////////////////////////////////////////////
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4Long operator +(Vector4Long a, Vector4Long b) =>
new Vector4Long(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4Long operator -(Vector4Long a, Vector4Long b) =>
new Vector4Long(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4Long operator -(Vector4Long v) =>
new Vector4Long(-v.x, -v.y, -v.z, -v.w);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4Long operator *(Vector4Long a, long n) =>
new Vector4Long(a.x * n, a.y * n, a.z * n, a.w * n);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4Long operator *(long n, Vector4Long a) =>
new Vector4Long(a.x * n, a.y * n, a.z * n, a.w * n);
// == returns true if approximately equal (with epsilon).
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Vector4Long a, Vector4Long b) =>
a.x == b.x &&
a.y == b.y &&
a.z == b.z &&
a.w == b.w;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Vector4Long a, Vector4Long b) => !(a == b);
// NO IMPLICIT System.Numerics.Vector4Long conversion because double<->float
// would silently lose precision in large worlds.
// [i] component index. useful for iterating all components etc.
public long this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
switch (index)
{
case 0: return x;
case 1: return y;
case 2: return z;
case 3: return w;
default: throw new IndexOutOfRangeException($"Vector4Long[{index}] out of range.");
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
switch (index)
{
case 0:
x = value;
break;
case 1:
y = value;
break;
case 2:
z = value;
break;
case 3:
w = value;
break;
default: throw new IndexOutOfRangeException($"Vector4Long[{index}] out of range.");
}
}
}
// instance functions //////////////////////////////////////////////////
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override string ToString() => $"({x} {y} {z} {w})";
// equality ////////////////////////////////////////////////////////////
// implement Equals & HashCode explicitly for performance.
// calling .Equals (instead of "==") checks for exact equality.
// (API compatibility)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Vector4Long other) =>
x == other.x && y == other.y && z == other.z && w == other.w;
// Equals(object) can reuse Equals(Vector4)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object other) =>
other is Vector4Long vector4 && Equals(vector4);
#if UNITY_2021_3_OR_NEWER
// Unity 2019/2020 don't have HashCode.Combine yet.
// this is only to avoid reflection. without defining, it works too.
// default generated by rider
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => HashCode.Combine(x, y, z, w);
#endif
}
}
#pragma warning disable CS0659 // 'Vector4Long' overrides Object.Equals(object o) but does not override Object.GetHashCode()
#pragma warning disable CS0661 // 'Vector4Long' defines operator == or operator != but does not override Object.GetHashCode()
// Vector4Long by mischa (based on game engine project)
using System;
using System.Runtime.CompilerServices;
namespace Mirror
{
public struct Vector4Long
{
public long x;
public long y;
public long z;
public long w;
public static readonly Vector4Long zero = new Vector4Long(0, 0, 0, 0);
public static readonly Vector4Long one = new Vector4Long(1, 1, 1, 1);
// constructor /////////////////////////////////////////////////////////
public Vector4Long(long x, long y, long z, long w)
{
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
// operators ///////////////////////////////////////////////////////////
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4Long operator +(Vector4Long a, Vector4Long b) =>
new Vector4Long(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4Long operator -(Vector4Long a, Vector4Long b) =>
new Vector4Long(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4Long operator -(Vector4Long v) =>
new Vector4Long(-v.x, -v.y, -v.z, -v.w);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4Long operator *(Vector4Long a, long n) =>
new Vector4Long(a.x * n, a.y * n, a.z * n, a.w * n);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4Long operator *(long n, Vector4Long a) =>
new Vector4Long(a.x * n, a.y * n, a.z * n, a.w * n);
// == returns true if approximately equal (with epsilon).
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Vector4Long a, Vector4Long b) =>
a.x == b.x &&
a.y == b.y &&
a.z == b.z &&
a.w == b.w;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Vector4Long a, Vector4Long b) => !(a == b);
// NO IMPLICIT System.Numerics.Vector4Long conversion because double<->float
// would silently lose precision in large worlds.
// [i] component index. useful for iterating all components etc.
public long this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
switch (index)
{
case 0: return x;
case 1: return y;
case 2: return z;
case 3: return w;
default: throw new IndexOutOfRangeException($"Vector4Long[{index}] out of range.");
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
switch (index)
{
case 0:
x = value;
break;
case 1:
y = value;
break;
case 2:
z = value;
break;
case 3:
w = value;
break;
default: throw new IndexOutOfRangeException($"Vector4Long[{index}] out of range.");
}
}
}
// instance functions //////////////////////////////////////////////////
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override string ToString() => $"({x} {y} {z} {w})";
// equality ////////////////////////////////////////////////////////////
// implement Equals & HashCode explicitly for performance.
// calling .Equals (instead of "==") checks for exact equality.
// (API compatibility)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Vector4Long other) =>
x == other.x && y == other.y && z == other.z && w == other.w;
// Equals(object) can reuse Equals(Vector4)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object other) =>
other is Vector4Long vector4 && Equals(vector4);
#if UNITY_2021_3_OR_NEWER
// Unity 2019/2020 don't have HashCode.Combine yet.
// this is only to avoid reflection. without defining, it works too.
// default generated by rider
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => HashCode.Combine(x, y, z, w);
#endif
}
}
+10 -10
View File
@@ -1,10 +1,10 @@
fileFormatVersion: 2
guid: 9f69bf6b6d73476ab3f31dd635bb6497
timeCreated: 1726777293
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/Vector4Long.cs
uploadId: 736421
fileFormatVersion: 2
guid: 9f69bf6b6d73476ab3f31dd635bb6497
timeCreated: 1726777293
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Core/Tools/Vector4Long.cs
uploadId: 736421