[Add] FishNet
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FishNet.Broadcast.Helping
|
||||
{
|
||||
internal static class BroadcastsSerializers
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes a broadcast to writer.
|
||||
/// </summary>
|
||||
internal static PooledWriter WriteBroadcast<T>(NetworkManager networkManager, PooledWriter writer, T message, ref Channel channel)
|
||||
{
|
||||
writer.WritePacketIdUnpacked(PacketId.Broadcast);
|
||||
writer.WriteUInt16(typeof(T).FullName.GetStableHashU16());
|
||||
// Write data to a new writer.
|
||||
PooledWriter dataWriter = WriterPool.Retrieve();
|
||||
dataWriter.Write(message);
|
||||
// Write length of data.
|
||||
writer.WriteInt32(dataWriter.Length);
|
||||
// Write data.
|
||||
writer.WriteArraySegment(dataWriter.GetArraySegment());
|
||||
// Update channel to reliable if needed.
|
||||
networkManager.TransportManager.CheckSetReliableChannel(writer.Length, ref channel);
|
||||
|
||||
dataWriter.Store();
|
||||
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class BroadcastExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the key for a broadcast type.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T"></typeparam>
|
||||
/// <param name = "broadcastType"></param>
|
||||
/// <returns></returns>
|
||||
internal static ushort GetKey<T>()
|
||||
{
|
||||
return typeof(T).FullName.GetStableHashU16();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implemented by server and client broadcast handlers.
|
||||
/// </summary>
|
||||
public abstract class BroadcastHandlerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Current index when iterating invokes.
|
||||
/// This value will be -1 when not iterating.
|
||||
/// </summary>
|
||||
protected int IteratingIndex;
|
||||
public abstract void RegisterHandler(object obj);
|
||||
public abstract void UnregisterHandler(object obj);
|
||||
public virtual void InvokeHandlers(PooledReader reader, Channel channel) { }
|
||||
public virtual void InvokeHandlers(NetworkConnection conn, PooledReader reader, Channel channel) { }
|
||||
public virtual bool RequireAuthentication => false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles broadcasts received on server, from clients.
|
||||
/// </summary>
|
||||
internal class ClientBroadcastHandler<T> : BroadcastHandlerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Action handlers for the broadcast.
|
||||
/// </summary>
|
||||
private List<Action<NetworkConnection, T, Channel>> _handlers = new();
|
||||
/// <summary>
|
||||
/// True to require authentication for the broadcast type.
|
||||
/// </summary>
|
||||
private bool _requireAuthentication;
|
||||
|
||||
public ClientBroadcastHandler(bool requireAuthentication)
|
||||
{
|
||||
_requireAuthentication = requireAuthentication;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes handlers after reading broadcast.
|
||||
/// </summary>
|
||||
/// <returns>True if a rebuild was required.</returns>
|
||||
public override void InvokeHandlers(NetworkConnection conn, PooledReader reader, Channel channel)
|
||||
{
|
||||
T result = reader.Read<T>();
|
||||
for (IteratingIndex = 0; IteratingIndex < _handlers.Count; IteratingIndex++)
|
||||
{
|
||||
Action<NetworkConnection, T, Channel> item = _handlers[IteratingIndex];
|
||||
if (item != null)
|
||||
{
|
||||
item.Invoke(conn, result, channel);
|
||||
}
|
||||
else
|
||||
{
|
||||
_handlers.RemoveAt(IteratingIndex);
|
||||
IteratingIndex--;
|
||||
}
|
||||
}
|
||||
|
||||
IteratingIndex = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a handler for this type.
|
||||
/// </summary>
|
||||
public override void RegisterHandler(object obj)
|
||||
{
|
||||
Action<NetworkConnection, T, Channel> handler = (Action<NetworkConnection, T, Channel>)obj;
|
||||
_handlers.AddUnique(handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a handler from this type.
|
||||
/// </summary>
|
||||
/// <param name = "handler"></param>
|
||||
public override void UnregisterHandler(object obj)
|
||||
{
|
||||
Action<NetworkConnection, T, Channel> handler = (Action<NetworkConnection, T, Channel>)obj;
|
||||
int indexOf = _handlers.IndexOf(handler);
|
||||
// Not registered.
|
||||
if (indexOf == -1)
|
||||
return;
|
||||
|
||||
/* Has already been iterated over, need to subtract
|
||||
* 1 from iteratingIndex to accomodate
|
||||
* for the entry about to be removed. */
|
||||
if (IteratingIndex >= 0 && indexOf <= IteratingIndex)
|
||||
IteratingIndex--;
|
||||
|
||||
// Remove entry.
|
||||
_handlers.RemoveAt(indexOf);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True to require authentication for the broadcast type.
|
||||
/// </summary>
|
||||
public override bool RequireAuthentication => _requireAuthentication;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles broadcasts received on client, from server.
|
||||
/// </summary>
|
||||
internal class ServerBroadcastHandler<T> : BroadcastHandlerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Action handlers for the broadcast.
|
||||
/// Even though List lookups are slower this allows easy adding and removing of entries during iteration.
|
||||
/// </summary>
|
||||
private List<Action<T, Channel>> _handlers = new();
|
||||
|
||||
/// <summary>
|
||||
/// Invokes handlers after reading broadcast.
|
||||
/// </summary>
|
||||
/// <returns>True if a rebuild was required.</returns>
|
||||
public override void InvokeHandlers(PooledReader reader, Channel channel)
|
||||
{
|
||||
T result = reader.Read<T>();
|
||||
for (IteratingIndex = 0; IteratingIndex < _handlers.Count; IteratingIndex++)
|
||||
{
|
||||
Action<T, Channel> item = _handlers[IteratingIndex];
|
||||
if (item != null)
|
||||
{
|
||||
item.Invoke(result, channel);
|
||||
}
|
||||
else
|
||||
{
|
||||
_handlers.RemoveAt(IteratingIndex);
|
||||
IteratingIndex--;
|
||||
}
|
||||
}
|
||||
|
||||
IteratingIndex = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a handler for this type.
|
||||
/// </summary>
|
||||
public override void RegisterHandler(object obj)
|
||||
{
|
||||
Action<T, Channel> handler = (Action<T, Channel>)obj;
|
||||
_handlers.AddUnique(handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a handler from this type.
|
||||
/// </summary>
|
||||
/// <param name = "handler"></param>
|
||||
public override void UnregisterHandler(object obj)
|
||||
{
|
||||
Action<T, Channel> handler = (Action<T, Channel>)obj;
|
||||
int indexOf = _handlers.IndexOf(handler);
|
||||
// Not registered.
|
||||
if (indexOf == -1)
|
||||
return;
|
||||
|
||||
/* Has already been iterated over, need to subtract
|
||||
* 1 from iteratingIndex to accomodate
|
||||
* for the entry about to be removed. */
|
||||
if (IteratingIndex >= 0 && indexOf <= IteratingIndex)
|
||||
IteratingIndex--;
|
||||
|
||||
// Remove entry.
|
||||
_handlers.RemoveAt(indexOf);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True to require authentication for the broadcast type.
|
||||
/// </summary>
|
||||
public override bool RequireAuthentication => false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0636e29429649a24795091f80edbd892
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Serializing/Helping/Broadcasts.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace FishNet.Serializing.Helping
|
||||
{
|
||||
public class PublicPropertyComparer<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare if T is default.
|
||||
/// </summary>
|
||||
public static Func<T, bool> IsDefault { get; set; }
|
||||
/// <summary>
|
||||
/// Compare if T is the same as T2.
|
||||
/// </summary>
|
||||
public static Func<T, T, bool> Compare { get; set; }
|
||||
}
|
||||
|
||||
public class Comparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns if A equals B using EqualityCompare.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T"></typeparam>
|
||||
/// <param name = "a"></param>
|
||||
/// <param name = "b"></param>
|
||||
/// <returns></returns>
|
||||
public static bool EqualityCompare<T>(T a, T b)
|
||||
{
|
||||
return EqualityComparer<T>.Default.Equals(a, b);
|
||||
}
|
||||
|
||||
public static bool IsDefault<T>(T t)
|
||||
{
|
||||
return t.Equals(default(T));
|
||||
}
|
||||
|
||||
public static bool IsEqualityCompareDefault<T>(T a)
|
||||
{
|
||||
return EqualityComparer<T>.Default.Equals(a, default);
|
||||
}
|
||||
}
|
||||
|
||||
internal class SceneComparer : IEqualityComparer<Scene>
|
||||
{
|
||||
public bool Equals(Scene a, Scene b)
|
||||
{
|
||||
if (!a.IsValid() || !b.IsValid())
|
||||
return false;
|
||||
|
||||
if (a.handle != 0 || b.handle != 0)
|
||||
return a.handle == b.handle;
|
||||
|
||||
return a.name == b.name;
|
||||
}
|
||||
|
||||
public int GetHashCode(Scene obj)
|
||||
{
|
||||
return obj.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e912d0645f10b2c458cc2f01e24ecc27
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Serializing/Helping/Comparers.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,198 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Serializing.Helping
|
||||
{
|
||||
public static class Quaternion32Compression
|
||||
{
|
||||
private const float Maximum = +1.0f / 1.414214f;
|
||||
private const int BitsPerAxis = 10;
|
||||
private const int LargestComponentShift = BitsPerAxis * 3;
|
||||
private const int AShift = BitsPerAxis * 2;
|
||||
private const int BShift = BitsPerAxis * 1;
|
||||
private const int IntScale = (1 << (BitsPerAxis - 1)) - 1;
|
||||
private const int IntMask = (1 << BitsPerAxis) - 1;
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name = "writer"></param>
|
||||
/// <param name = "quaternion"></param>
|
||||
/// <param name = "axesFlippingEnabled">True to flip the smaller values when the largest axes is negative. Doing this saves a byte but the rotation numeric values will be reversed when decompressed.</param>
|
||||
public static void Compress(Writer writer, Quaternion quaternion, bool axesFlippingEnabled = true)
|
||||
{
|
||||
const float precision = 0.00098f;
|
||||
|
||||
float absX = Mathf.Abs(quaternion.x);
|
||||
float absY = Mathf.Abs(quaternion.y);
|
||||
float absZ = Mathf.Abs(quaternion.z);
|
||||
float absW = Mathf.Abs(quaternion.w);
|
||||
|
||||
ComponentType largestComponent = ComponentType.X;
|
||||
float largestAbs = absX;
|
||||
float largest = quaternion.x;
|
||||
|
||||
if (absY > largestAbs)
|
||||
{
|
||||
largestAbs = absY;
|
||||
largestComponent = ComponentType.Y;
|
||||
largest = quaternion.y;
|
||||
}
|
||||
if (absZ > largestAbs)
|
||||
{
|
||||
largestAbs = absZ;
|
||||
largestComponent = ComponentType.Z;
|
||||
largest = quaternion.z;
|
||||
}
|
||||
if (absW > largestAbs)
|
||||
{
|
||||
largestComponent = ComponentType.W;
|
||||
largest = quaternion.w;
|
||||
}
|
||||
|
||||
bool largestIsNegative = largest < 0;
|
||||
|
||||
// If not flipping axes and any values are less than precision then 0 them out.
|
||||
if (!axesFlippingEnabled)
|
||||
{
|
||||
if (absX < precision)
|
||||
quaternion.x = 0f;
|
||||
if (absY < precision)
|
||||
quaternion.y = 0f;
|
||||
if (absZ < precision)
|
||||
quaternion.z = 0f;
|
||||
if (absW < precision)
|
||||
quaternion.w = 0f;
|
||||
}
|
||||
|
||||
float a = 0;
|
||||
float b = 0;
|
||||
float c = 0;
|
||||
switch (largestComponent)
|
||||
{
|
||||
case ComponentType.X:
|
||||
a = quaternion.y;
|
||||
b = quaternion.z;
|
||||
c = quaternion.w;
|
||||
break;
|
||||
case ComponentType.Y:
|
||||
a = quaternion.x;
|
||||
b = quaternion.z;
|
||||
c = quaternion.w;
|
||||
break;
|
||||
case ComponentType.Z:
|
||||
a = quaternion.x;
|
||||
b = quaternion.y;
|
||||
c = quaternion.w;
|
||||
break;
|
||||
case ComponentType.W:
|
||||
a = quaternion.x;
|
||||
b = quaternion.y;
|
||||
c = quaternion.z;
|
||||
break;
|
||||
}
|
||||
|
||||
// If it's okay to flip when largest is negative.
|
||||
if (largestIsNegative && axesFlippingEnabled)
|
||||
{
|
||||
a = -a;
|
||||
b = -b;
|
||||
c = -c;
|
||||
}
|
||||
|
||||
uint integerA = ScaleToUint(a);
|
||||
uint integerB = ScaleToUint(b);
|
||||
uint integerC = ScaleToUint(c);
|
||||
|
||||
if (!axesFlippingEnabled)
|
||||
writer.WriteBoolean(largest < 0f);
|
||||
|
||||
uint result = ((uint)largestComponent << LargestComponentShift) | (integerA << AShift) | (integerB << BShift) | integerC;
|
||||
writer.WriteUInt32Unpacked(result);
|
||||
}
|
||||
|
||||
private static uint ScaleToUint(float v)
|
||||
{
|
||||
float normalized = v / Maximum;
|
||||
return (uint)Mathf.RoundToInt(normalized * IntScale) & IntMask;
|
||||
}
|
||||
|
||||
private static float ScaleToFloat(uint v)
|
||||
{
|
||||
float unscaled = v * Maximum / IntScale;
|
||||
|
||||
if (unscaled > Maximum)
|
||||
unscaled -= Maximum * 2;
|
||||
return unscaled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name = "reader"></param>
|
||||
/// <param name = "axesFlippingEnabled">True if the smaller values were flipped during compression when the largest axes was negative.</param>
|
||||
/// <returns></returns>
|
||||
public static Quaternion Decompress(Reader reader, bool axesFlippingEnabled = true)
|
||||
{
|
||||
bool largestIsNegative = axesFlippingEnabled ? false : reader.ReadBoolean();
|
||||
uint compressed = reader.ReadUInt32Unpacked();
|
||||
|
||||
ComponentType largestComponentType = (ComponentType)(compressed >> LargestComponentShift);
|
||||
uint integerA = (compressed >> AShift) & IntMask;
|
||||
uint integerB = (compressed >> BShift) & IntMask;
|
||||
uint integerC = compressed & IntMask;
|
||||
|
||||
float a = ScaleToFloat(integerA);
|
||||
float b = ScaleToFloat(integerB);
|
||||
float c = ScaleToFloat(integerC);
|
||||
|
||||
Quaternion rotation;
|
||||
switch (largestComponentType)
|
||||
{
|
||||
case ComponentType.X:
|
||||
// (?) y z w
|
||||
rotation.y = a;
|
||||
rotation.z = b;
|
||||
rotation.w = c;
|
||||
rotation.x = Mathf.Sqrt(1 - rotation.y * rotation.y - rotation.z * rotation.z - rotation.w * rotation.w);
|
||||
|
||||
if (largestIsNegative)
|
||||
rotation.x *= -1f;
|
||||
break;
|
||||
case ComponentType.Y:
|
||||
// x (?) z w
|
||||
rotation.x = a;
|
||||
rotation.z = b;
|
||||
rotation.w = c;
|
||||
rotation.y = Mathf.Sqrt(1 - rotation.x * rotation.x - rotation.z * rotation.z - rotation.w * rotation.w);
|
||||
|
||||
if (largestIsNegative)
|
||||
rotation.y *= -1f;
|
||||
break;
|
||||
case ComponentType.Z:
|
||||
// x y (?) w
|
||||
rotation.x = a;
|
||||
rotation.y = b;
|
||||
rotation.w = c;
|
||||
rotation.z = Mathf.Sqrt(1 - rotation.x * rotation.x - rotation.y * rotation.y - rotation.w * rotation.w);
|
||||
|
||||
if (largestIsNegative)
|
||||
rotation.z *= -1f;
|
||||
break;
|
||||
case ComponentType.W:
|
||||
// x y z (?)
|
||||
rotation.x = a;
|
||||
rotation.y = b;
|
||||
rotation.z = c;
|
||||
rotation.w = Mathf.Sqrt(1 - rotation.x * rotation.x - rotation.y * rotation.y - rotation.z * rotation.z);
|
||||
|
||||
if (largestIsNegative)
|
||||
rotation.w *= -1f;
|
||||
break;
|
||||
default:
|
||||
// Should never happen!
|
||||
throw new ArgumentOutOfRangeException("Unknown rotation component type: " + largestComponentType);
|
||||
}
|
||||
|
||||
return rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f71e61ed84064a0429577ec462a8fa79
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Serializing/Helping/Quaternion32Compression.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,180 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Serializing.Helping
|
||||
{
|
||||
/// <summary>
|
||||
/// Credit to https://github.com/viliwonka
|
||||
/// https://github.com/FirstGearGames/FishNet/pull/23
|
||||
/// </summary>
|
||||
public static class Quaternion64Compression
|
||||
{
|
||||
// 64 bit quaternion compression
|
||||
// [4 bits] largest component
|
||||
// [21 bits] higher res
|
||||
// [21 bits] higher res
|
||||
// [20 bits] higher res
|
||||
// sum is 64 bits
|
||||
private const float Maximum = +1.0f / 1.414214f;
|
||||
private const int BitsPerAxis_H = 21; // higher res, 21 bits
|
||||
private const int BitsPerAxis_L = 20; // lower res, 20 bits
|
||||
private const int LargestComponentShift = BitsPerAxis_H * 2 + BitsPerAxis_L * 1;
|
||||
private const int AShift = BitsPerAxis_H + BitsPerAxis_L;
|
||||
private const int BShift = BitsPerAxis_L;
|
||||
private const int IntScale_H = (1 << (BitsPerAxis_H - 1)) - 1;
|
||||
private const int IntMask_H = (1 << BitsPerAxis_H) - 1;
|
||||
private const int IntScale_L = (1 << (BitsPerAxis_L - 1)) - 1;
|
||||
private const int IntMask_L = (1 << BitsPerAxis_L) - 1;
|
||||
|
||||
public static ulong Compress(Quaternion quaternion)
|
||||
{
|
||||
float absX = Mathf.Abs(quaternion.x);
|
||||
float absY = Mathf.Abs(quaternion.y);
|
||||
float absZ = Mathf.Abs(quaternion.z);
|
||||
float absW = Mathf.Abs(quaternion.w);
|
||||
|
||||
ComponentType largestComponent = ComponentType.X;
|
||||
float largestAbs = absX;
|
||||
float largest = quaternion.x;
|
||||
|
||||
if (absY > largestAbs)
|
||||
{
|
||||
largestAbs = absY;
|
||||
largestComponent = ComponentType.Y;
|
||||
largest = quaternion.y;
|
||||
}
|
||||
if (absZ > largestAbs)
|
||||
{
|
||||
largestAbs = absZ;
|
||||
largestComponent = ComponentType.Z;
|
||||
largest = quaternion.z;
|
||||
}
|
||||
if (absW > largestAbs)
|
||||
{
|
||||
largestComponent = ComponentType.W;
|
||||
largest = quaternion.w;
|
||||
}
|
||||
|
||||
float a = 0;
|
||||
float b = 0;
|
||||
float c = 0;
|
||||
|
||||
switch (largestComponent)
|
||||
{
|
||||
case ComponentType.X:
|
||||
a = quaternion.y;
|
||||
b = quaternion.z;
|
||||
c = quaternion.w;
|
||||
break;
|
||||
case ComponentType.Y:
|
||||
a = quaternion.x;
|
||||
b = quaternion.z;
|
||||
c = quaternion.w;
|
||||
break;
|
||||
case ComponentType.Z:
|
||||
a = quaternion.x;
|
||||
b = quaternion.y;
|
||||
c = quaternion.w;
|
||||
break;
|
||||
case ComponentType.W:
|
||||
a = quaternion.x;
|
||||
b = quaternion.y;
|
||||
c = quaternion.z;
|
||||
break;
|
||||
}
|
||||
|
||||
if (largest < 0)
|
||||
{
|
||||
a = -a;
|
||||
b = -b;
|
||||
c = -c;
|
||||
}
|
||||
|
||||
ulong integerA = ScaleToUint_H(a);
|
||||
ulong integerB = ScaleToUint_H(b);
|
||||
ulong integerC = ScaleToUint_L(c);
|
||||
|
||||
return ((ulong)largestComponent << LargestComponentShift) | (integerA << AShift) | (integerB << BShift) | integerC;
|
||||
}
|
||||
|
||||
private static ulong ScaleToUint_H(float v)
|
||||
{
|
||||
float normalized = v / Maximum;
|
||||
return (ulong)Mathf.RoundToInt(normalized * IntScale_H) & IntMask_H;
|
||||
}
|
||||
|
||||
private static ulong ScaleToUint_L(float v)
|
||||
{
|
||||
float normalized = v / Maximum;
|
||||
return (ulong)Mathf.RoundToInt(normalized * IntScale_L) & IntMask_L;
|
||||
}
|
||||
|
||||
private static float ScaleToFloat_H(ulong v)
|
||||
{
|
||||
float unscaled = v * Maximum / IntScale_H;
|
||||
|
||||
if (unscaled > Maximum)
|
||||
unscaled -= Maximum * 2;
|
||||
return unscaled;
|
||||
}
|
||||
|
||||
private static float ScaleToFloat_L(ulong v)
|
||||
{
|
||||
float unscaled = v * Maximum / IntScale_L;
|
||||
|
||||
if (unscaled > Maximum)
|
||||
unscaled -= Maximum * 2;
|
||||
return unscaled;
|
||||
}
|
||||
|
||||
public static Quaternion Decompress(ulong compressed)
|
||||
{
|
||||
ComponentType largestComponentType = (ComponentType)(compressed >> LargestComponentShift);
|
||||
ulong integerA = (compressed >> AShift) & IntMask_H;
|
||||
ulong integerB = (compressed >> BShift) & IntMask_H;
|
||||
ulong integerC = compressed & IntMask_L;
|
||||
|
||||
float a = ScaleToFloat_H(integerA);
|
||||
float b = ScaleToFloat_H(integerB);
|
||||
float c = ScaleToFloat_L(integerC);
|
||||
|
||||
Quaternion rotation;
|
||||
switch (largestComponentType)
|
||||
{
|
||||
case ComponentType.X:
|
||||
// (?) y z w
|
||||
rotation.y = a;
|
||||
rotation.z = b;
|
||||
rotation.w = c;
|
||||
rotation.x = Mathf.Sqrt(1 - rotation.y * rotation.y - rotation.z * rotation.z - rotation.w * rotation.w);
|
||||
break;
|
||||
case ComponentType.Y:
|
||||
// x (?) z w
|
||||
rotation.x = a;
|
||||
rotation.z = b;
|
||||
rotation.w = c;
|
||||
rotation.y = Mathf.Sqrt(1 - rotation.x * rotation.x - rotation.z * rotation.z - rotation.w * rotation.w);
|
||||
break;
|
||||
case ComponentType.Z:
|
||||
// x y (?) w
|
||||
rotation.x = a;
|
||||
rotation.y = b;
|
||||
rotation.w = c;
|
||||
rotation.z = Mathf.Sqrt(1 - rotation.x * rotation.x - rotation.y * rotation.y - rotation.w * rotation.w);
|
||||
break;
|
||||
case ComponentType.W:
|
||||
// x y z (?)
|
||||
rotation.x = a;
|
||||
rotation.y = b;
|
||||
rotation.z = c;
|
||||
rotation.w = Mathf.Sqrt(1 - rotation.x * rotation.x - rotation.y * rotation.y - rotation.z * rotation.z);
|
||||
break;
|
||||
default:
|
||||
// Should never happen!
|
||||
throw new ArgumentOutOfRangeException("Unknown rotation component type: " + largestComponentType);
|
||||
}
|
||||
|
||||
return rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7afd33d2ca5433f4f831dfaf0169423c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Serializing/Helping/Quaternion64Compression.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,40 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Serializing.Helping
|
||||
{
|
||||
public struct QuaternionAutoPack
|
||||
{
|
||||
public Quaternion Value;
|
||||
public AutoPackType PackType;
|
||||
|
||||
public QuaternionAutoPack(Quaternion value)
|
||||
{
|
||||
Value = value;
|
||||
PackType = AutoPackType.Packed;
|
||||
}
|
||||
|
||||
public QuaternionAutoPack(Quaternion value, AutoPackType autoPackType)
|
||||
{
|
||||
Value = value;
|
||||
PackType = autoPackType;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class QuaternionAutoPackExtensions
|
||||
{
|
||||
public static void WriteQuaternionAutoPack(this Writer w, QuaternionAutoPack value)
|
||||
{
|
||||
w.WriteUInt8Unpacked((byte)value.PackType);
|
||||
w.WriteQuaternion(value.Value, value.PackType);
|
||||
}
|
||||
|
||||
public static QuaternionAutoPack ReadUnpackedQuaternion(this Reader reader)
|
||||
{
|
||||
AutoPackType packType = (AutoPackType)reader.ReadUInt8Unpacked();
|
||||
Quaternion q = reader.ReadQuaternion(packType);
|
||||
|
||||
return new(q, packType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae3125324bcaa3347ab27b1ebcd1270e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Serializing/Helping/QuaternionAutoPack.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace FishNet.Serializing.Helping
|
||||
{
|
||||
public enum ComponentType : uint
|
||||
{
|
||||
X = 0,
|
||||
Y = 1,
|
||||
Z = 2,
|
||||
W = 3
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b7ac59ce12259104fa28fc837fb17ccf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Serializing/Helping/QuaternionConverter.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,278 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FishNet.Managing;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Serializing.Helping
|
||||
{
|
||||
[Flags]
|
||||
internal enum QuaternionDeltaPrecisionFlag : byte
|
||||
{
|
||||
Unset = 0,
|
||||
/* Its probably safe to discard '-IsNegative'
|
||||
* and replace with a single 'largest is negative'.
|
||||
* Doing this would still use the same amount of bytes
|
||||
* though, and would require a refactor on this and the delta
|
||||
* compression class. */
|
||||
NextAIsLarger = 1 << 0,
|
||||
NextBIsLarger = 1 << 1,
|
||||
NextCIsLarger = 1 << 2,
|
||||
NextDIsNegative = 1 << 3,
|
||||
LargestIsX = 1 << 4,
|
||||
LargestIsY = 1 << 5,
|
||||
LargestIsZ = 1 << 6,
|
||||
// This flag can be discarded via refactor if we need it later.
|
||||
LargestIsW = 1 << 7
|
||||
}
|
||||
|
||||
internal static class QuaternionDeltaPrecisionFlagExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns if whole contains part.
|
||||
/// </summary>
|
||||
internal static bool FastContains(this QuaternionDeltaPrecisionFlag whole, QuaternionDeltaPrecisionFlag part) => (whole & part) == part;
|
||||
}
|
||||
|
||||
public static class QuaternionDeltaPrecisionCompression
|
||||
{
|
||||
/// <summary>
|
||||
/// Write a compressed a delta Quaternion using a variable precision.
|
||||
/// </summary>
|
||||
public static void Compress(Writer writer, Quaternion valueA, Quaternion valueB, float precision = 0.001f)
|
||||
{
|
||||
uint multiplier = (uint)Mathf.RoundToInt(1f / precision);
|
||||
|
||||
// Position where the next byte is to be written.
|
||||
int startPosition = writer.Position;
|
||||
// Skip one byte so the flags can be inserted after everything else is writteh.
|
||||
writer.Skip(1);
|
||||
|
||||
QuaternionDeltaPrecisionFlag flags = QuaternionDeltaPrecisionFlag.Unset;
|
||||
long largestUValue = -1;
|
||||
|
||||
/* This becomes true if the largest difference is negative on valueB.
|
||||
* EG: if Y is the largest and value.Y is < 0f then largestIsNegative becomes true. */
|
||||
bool largestIsNegative = false;
|
||||
|
||||
/* Set next is larger values, and output differneces. */
|
||||
bool xIsLarger = GetNextIsLarger(valueA.x, valueB.x, multiplier, out uint xDifference);
|
||||
UpdateLargestValues(xDifference, valueB.x, QuaternionDeltaPrecisionFlag.LargestIsX);
|
||||
|
||||
bool yIsLarger = GetNextIsLarger(valueA.y, valueB.y, multiplier, out uint yDifference);
|
||||
UpdateLargestValues(yDifference, valueB.y, QuaternionDeltaPrecisionFlag.LargestIsY);
|
||||
|
||||
bool zIsLarger = GetNextIsLarger(valueA.z, valueB.z, multiplier, out uint zDifference);
|
||||
UpdateLargestValues(zDifference, valueB.z, QuaternionDeltaPrecisionFlag.LargestIsZ);
|
||||
|
||||
bool wIsLarger = GetNextIsLarger(valueA.w, valueB.w, multiplier, out uint wDifference);
|
||||
UpdateLargestValues(wDifference, valueB.w, QuaternionDeltaPrecisionFlag.LargestIsW);
|
||||
|
||||
// If flags are unset something went wrong. This should never be possible.
|
||||
if (flags == QuaternionDeltaPrecisionFlag.Unset)
|
||||
{
|
||||
// Write that flags are unset and error.
|
||||
writer.InsertUInt8Unpacked((byte)flags, startPosition);
|
||||
writer.NetworkManager.LogError($"Flags should not be unset.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Updates largest values and flags.
|
||||
void UpdateLargestValues(uint checkedValue, float fValue, QuaternionDeltaPrecisionFlag newFlag)
|
||||
{
|
||||
if (checkedValue > largestUValue)
|
||||
{
|
||||
largestUValue = checkedValue;
|
||||
flags = newFlag;
|
||||
largestIsNegative = fValue < 0f;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write all but largest. */
|
||||
|
||||
// X is largest.
|
||||
if (flags == QuaternionDeltaPrecisionFlag.LargestIsX)
|
||||
WriteValues(yDifference, yIsLarger, zDifference, zIsLarger, wDifference, wIsLarger);
|
||||
// Y is largest.
|
||||
else if (flags == QuaternionDeltaPrecisionFlag.LargestIsY)
|
||||
WriteValues(xDifference, xIsLarger, zDifference, zIsLarger, wDifference, wIsLarger);
|
||||
// Z is largest.
|
||||
else if (flags == QuaternionDeltaPrecisionFlag.LargestIsZ)
|
||||
WriteValues(xDifference, xIsLarger, yDifference, yIsLarger, wDifference, wIsLarger);
|
||||
// W is largest.
|
||||
else if (flags == QuaternionDeltaPrecisionFlag.LargestIsW)
|
||||
WriteValues(xDifference, xIsLarger, yDifference, yIsLarger, zDifference, zIsLarger);
|
||||
|
||||
/* This must be set after values are written since the enum
|
||||
* checks above use ==, rather than a bit comparer. */
|
||||
if (largestIsNegative)
|
||||
flags |= QuaternionDeltaPrecisionFlag.NextDIsNegative;
|
||||
|
||||
void WriteValues(uint aValue, bool aIsLarger, uint bValue, bool bIsLarger, uint cValue, bool cIsLarger)
|
||||
{
|
||||
writer.WriteUnsignedPackedWhole(aValue);
|
||||
if (aIsLarger)
|
||||
flags |= QuaternionDeltaPrecisionFlag.NextAIsLarger;
|
||||
|
||||
writer.WriteUnsignedPackedWhole(bValue);
|
||||
if (bIsLarger)
|
||||
flags |= QuaternionDeltaPrecisionFlag.NextBIsLarger;
|
||||
|
||||
writer.WriteUnsignedPackedWhole(cValue);
|
||||
if (cIsLarger)
|
||||
flags |= QuaternionDeltaPrecisionFlag.NextCIsLarger;
|
||||
}
|
||||
|
||||
// Insert flags.
|
||||
writer.InsertUInt8Unpacked((byte)flags, startPosition);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a compressed a delta Quaternion using a variable precision.
|
||||
/// </summary>
|
||||
public static Quaternion Decompress(Reader reader, Quaternion valueA, float precision = 0.001f)
|
||||
{
|
||||
uint multiplier = (uint)Mathf.RoundToInt(1f / precision);
|
||||
|
||||
QuaternionDeltaPrecisionFlag flags = (QuaternionDeltaPrecisionFlag)reader.ReadUInt8Unpacked();
|
||||
|
||||
// Unset flags mean something went wrong in writing.
|
||||
if (flags == QuaternionDeltaPrecisionFlag.Unset)
|
||||
{
|
||||
reader.NetworkManager.LogError($"Unset flags were returned.");
|
||||
return default;
|
||||
}
|
||||
|
||||
/* These values will be in order of X Y Z W.
|
||||
* Whichever value is the highest will be left out.
|
||||
*
|
||||
* EG: if Y was the highest then the following will be true...
|
||||
* a = X
|
||||
* b = Z
|
||||
* c = W */
|
||||
uint aWholeDifference = (uint)reader.ReadUnsignedPackedWhole();
|
||||
uint bWholeDifference = (uint)reader.ReadUnsignedPackedWhole();
|
||||
uint cWholeDifference = (uint)reader.ReadUnsignedPackedWhole();
|
||||
|
||||
// Debug.Log($"Read {aWholeDifference}, {bWholeDifference}, {cWholeDifference}. ValueA {valueA}");
|
||||
|
||||
float aFloatDifference = (float)aWholeDifference / multiplier;
|
||||
float bFloatDifference = (float)bWholeDifference / multiplier;
|
||||
float cFloatDifference = (float)cWholeDifference / multiplier;
|
||||
|
||||
// Invert differences as needed so they can all be added onto the previous value as negative or positive.
|
||||
if (!flags.FastContains(QuaternionDeltaPrecisionFlag.NextAIsLarger))
|
||||
aFloatDifference *= -1f;
|
||||
if (!flags.FastContains(QuaternionDeltaPrecisionFlag.NextBIsLarger))
|
||||
bFloatDifference *= -1f;
|
||||
if (!flags.FastContains(QuaternionDeltaPrecisionFlag.NextCIsLarger))
|
||||
cFloatDifference *= -1f;
|
||||
|
||||
float nextA;
|
||||
float nextB;
|
||||
float nextC;
|
||||
|
||||
/* Add onto the previous value. */
|
||||
if (flags.FastContains(QuaternionDeltaPrecisionFlag.LargestIsX))
|
||||
{
|
||||
nextA = valueA.y + aFloatDifference;
|
||||
nextB = valueA.z + bFloatDifference;
|
||||
nextC = valueA.w + cFloatDifference;
|
||||
}
|
||||
else if (flags.FastContains(QuaternionDeltaPrecisionFlag.LargestIsY))
|
||||
{
|
||||
nextA = valueA.x + aFloatDifference;
|
||||
nextB = valueA.z + bFloatDifference;
|
||||
nextC = valueA.w + cFloatDifference;
|
||||
}
|
||||
else if (flags.FastContains(QuaternionDeltaPrecisionFlag.LargestIsZ))
|
||||
{
|
||||
nextA = valueA.x + aFloatDifference;
|
||||
nextB = valueA.y + bFloatDifference;
|
||||
nextC = valueA.w + cFloatDifference;
|
||||
}
|
||||
/* We do not really need the 'largest is W' since we know if
|
||||
* the other 3 are not the largest, then the remaining must be.
|
||||
* We have the available packing to use though, so use them
|
||||
* for now. */
|
||||
else if (flags.FastContains(QuaternionDeltaPrecisionFlag.LargestIsW))
|
||||
{
|
||||
nextA = valueA.x + aFloatDifference;
|
||||
nextB = valueA.y + bFloatDifference;
|
||||
nextC = valueA.z + cFloatDifference;
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.NetworkManager.LogError($"Largest axes was not handled. Flags {flags}.");
|
||||
return default;
|
||||
}
|
||||
|
||||
float abcMagnitude = GetMagnitude(nextA, nextB, nextC);
|
||||
|
||||
float nextD = 1f - abcMagnitude;
|
||||
/* NextD should always be positive. But depending on precision
|
||||
* the calculated result could be negative due to missing decimals.
|
||||
* When negative make positive so nextD will normalize properly. */
|
||||
if (nextD < 0f)
|
||||
nextD *= -1f;
|
||||
|
||||
nextD = (float)Math.Sqrt(nextD);
|
||||
|
||||
// Get magnitude of all values.
|
||||
static float GetMagnitude(float a, float b, float c, float d = 0f) => a * a + b * b + c * c + d * d;
|
||||
|
||||
if (nextD >= 0f && flags.FastContains(QuaternionDeltaPrecisionFlag.NextDIsNegative))
|
||||
nextD *= -1f;
|
||||
|
||||
if (!TryNormalize())
|
||||
return default;
|
||||
|
||||
// Normalizes next values.
|
||||
bool TryNormalize()
|
||||
{
|
||||
float magnitude = (float)Math.Sqrt(GetMagnitude(nextA, nextB, nextC, nextD));
|
||||
if (magnitude < float.Epsilon)
|
||||
{
|
||||
reader.NetworkManager.LogError($"Magnitude cannot be normalized.");
|
||||
return false;
|
||||
}
|
||||
|
||||
nextA /= magnitude;
|
||||
nextB /= magnitude;
|
||||
nextC /= magnitude;
|
||||
nextD /= magnitude;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Add onto the previous value. */
|
||||
if (flags.FastContains(QuaternionDeltaPrecisionFlag.LargestIsX))
|
||||
return new(nextD, nextA, nextB, nextC);
|
||||
if (flags.FastContains(QuaternionDeltaPrecisionFlag.LargestIsY))
|
||||
return new(nextA, nextD, nextB, nextC);
|
||||
if (flags.FastContains(QuaternionDeltaPrecisionFlag.LargestIsZ))
|
||||
return new(nextA, nextB, nextD, nextC);
|
||||
if (flags.FastContains(QuaternionDeltaPrecisionFlag.LargestIsW))
|
||||
return new(nextA, nextB, nextC, nextD);
|
||||
|
||||
reader.NetworkManager.LogError($"Unhandled Largest flag. Received flags are {flags}.");
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if the next value is larger than the previous, and returns unsigned result with multiplier applied.
|
||||
/// </summary>
|
||||
private static bool GetNextIsLarger(float a, float b, uint lMultiplier, out uint multipliedUResult)
|
||||
{
|
||||
// Set is b is larger.
|
||||
bool bIsLarger = b > a;
|
||||
|
||||
// Get multiplied u value.
|
||||
float value = bIsLarger ? b - a : a - b;
|
||||
multipliedUResult = (uint)Mathf.RoundToInt(value * lMultiplier);
|
||||
|
||||
return bIsLarger;
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 37e43ec9c17f2dc43ac255695bd1a71f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Serializing/Helping/QuaternionDeltaPrecisionCompression.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,210 @@
|
||||
using System;
|
||||
using FishNet.Managing;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Serializing.Helping
|
||||
{
|
||||
[Flags]
|
||||
internal enum QuaternionPrecisionFlag : byte
|
||||
{
|
||||
Unset = 0,
|
||||
/* Its probably safe to discard '-IsNegative'
|
||||
* and replace with a single 'largest is negative'.
|
||||
* Doing this would still use the same amount of bytes
|
||||
* though, and would require a refactor on this and the delta
|
||||
* compression class. */
|
||||
AIsNegative = 1 << 0,
|
||||
BIsNegative = 1 << 1,
|
||||
CIsNegative = 1 << 2,
|
||||
DIsNegative = 1 << 3,
|
||||
LargestIsX = 1 << 4,
|
||||
LargestIsY = 1 << 5,
|
||||
LargestIsZ = 1 << 6,
|
||||
// This flag can be discarded via refactor if we need it later.
|
||||
LargestIsW = 1 << 7
|
||||
}
|
||||
|
||||
internal static class QuaternionPrecisionFlagExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns if whole contains part.
|
||||
/// </summary>
|
||||
internal static bool FastContains(this QuaternionPrecisionFlag whole, QuaternionPrecisionFlag part) => (whole & part) == part;
|
||||
}
|
||||
|
||||
public static class QuaternionPrecisionCompression
|
||||
{
|
||||
/// <summary>
|
||||
/// Write a compressed a delta Quaternion using a variable precision.
|
||||
/// </summary>
|
||||
public static void Compress(Writer writer, Quaternion value, float precision = 0.001f)
|
||||
{
|
||||
/* When using 0.001f or less accurate precision use the classic
|
||||
* compression. This saves about a byte by send. */
|
||||
if (precision >= 0.001f)
|
||||
{
|
||||
Quaternion32Compression.Compress(writer, value, axesFlippingEnabled: false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Position where the next byte is to be written.
|
||||
int startPosition = writer.Position;
|
||||
|
||||
// Skip one byte so the flags can be inserted after everything else is writteh.
|
||||
writer.Skip(1);
|
||||
|
||||
QuaternionPrecisionFlag flags = QuaternionPrecisionFlag.Unset;
|
||||
float largestAxesValue = float.MinValue;
|
||||
|
||||
// Find out which value is the largest.
|
||||
UpdateLargestValues(Math.Abs(value.x), QuaternionPrecisionFlag.LargestIsX);
|
||||
UpdateLargestValues(Math.Abs(value.y), QuaternionPrecisionFlag.LargestIsY);
|
||||
UpdateLargestValues(Math.Abs(value.z), QuaternionPrecisionFlag.LargestIsZ);
|
||||
UpdateLargestValues(Math.Abs(value.w), QuaternionPrecisionFlag.LargestIsW);
|
||||
|
||||
// Updates largest values and flags.
|
||||
void UpdateLargestValues(float checkedValue, QuaternionPrecisionFlag newFlag)
|
||||
{
|
||||
if (checkedValue > largestAxesValue)
|
||||
{
|
||||
largestAxesValue = checkedValue;
|
||||
flags = newFlag;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write all but largest. */
|
||||
|
||||
// X is largest.
|
||||
if (flags == QuaternionPrecisionFlag.LargestIsX)
|
||||
WriteValuesAndSetPositives(value.y, value.z, value.w, value.x);
|
||||
// Y is largest.
|
||||
else if (flags == QuaternionPrecisionFlag.LargestIsY)
|
||||
WriteValuesAndSetPositives(value.x, value.z, value.w, value.y);
|
||||
// Z is largest.
|
||||
else if (flags == QuaternionPrecisionFlag.LargestIsZ)
|
||||
WriteValuesAndSetPositives(value.x, value.y, value.w, value.z);
|
||||
// W is largest.
|
||||
else if (flags == QuaternionPrecisionFlag.LargestIsW)
|
||||
WriteValuesAndSetPositives(value.x, value.y, value.z, value.w);
|
||||
|
||||
void WriteValuesAndSetPositives(float aValue, float bValue, float cValue, float largestAxes)
|
||||
{
|
||||
uint multiplier = (uint)Mathf.RoundToInt(1f / precision);
|
||||
|
||||
uint aUint = (uint)Mathf.RoundToInt(Math.Abs(aValue) * multiplier);
|
||||
uint bUint = (uint)Mathf.RoundToInt(Math.Abs(bValue) * multiplier);
|
||||
uint cUint = (uint)Mathf.RoundToInt(Math.Abs(cValue) * multiplier);
|
||||
|
||||
writer.WriteUnsignedPackedWhole(aUint);
|
||||
writer.WriteUnsignedPackedWhole(bUint);
|
||||
writer.WriteUnsignedPackedWhole(cUint);
|
||||
|
||||
/* Update sign on values. */
|
||||
if (aValue < 0f)
|
||||
flags |= QuaternionPrecisionFlag.AIsNegative;
|
||||
if (bValue < 0f)
|
||||
flags |= QuaternionPrecisionFlag.BIsNegative;
|
||||
if (cValue <= 0f)
|
||||
flags |= QuaternionPrecisionFlag.CIsNegative;
|
||||
if (largestAxes < 0f)
|
||||
flags |= QuaternionPrecisionFlag.DIsNegative;
|
||||
}
|
||||
|
||||
// Insert flags.
|
||||
writer.InsertUInt8Unpacked((byte)flags, startPosition);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a compressed a delta Quaternion using a variable precision.
|
||||
/// </summary>
|
||||
public static Quaternion Decompress(Reader reader, float precision = 0.001f)
|
||||
{
|
||||
/* When using 0.001f or less accurate precision use the classic
|
||||
* compression. This saves about a byte by send. */
|
||||
if (precision >= 0.001f)
|
||||
return Quaternion32Compression.Decompress(reader, axesFlippingEnabled: false);
|
||||
|
||||
uint multiplier = (uint)Mathf.RoundToInt(1f / precision);
|
||||
|
||||
QuaternionPrecisionFlag flags = (QuaternionPrecisionFlag)reader.ReadUInt8Unpacked();
|
||||
|
||||
// Unset flags mean something went wrong in writing.
|
||||
if (flags == QuaternionPrecisionFlag.Unset)
|
||||
{
|
||||
reader.NetworkManager.LogError($"Unset flags were returned.");
|
||||
return default;
|
||||
}
|
||||
|
||||
/* These values will be in order of X Y Z W.
|
||||
* Whichever value is the highest will be left out.
|
||||
*
|
||||
* EG: if Y was the highest then the following will be true...
|
||||
* a = X
|
||||
* b = Z
|
||||
* c = W */
|
||||
float aValue = (float)reader.ReadUnsignedPackedWhole() / (float)multiplier;
|
||||
float bValue = (float)reader.ReadUnsignedPackedWhole() / (float)multiplier;
|
||||
float cValue = (float)reader.ReadUnsignedPackedWhole() / (float)multiplier;
|
||||
|
||||
// Make values negative if needed.
|
||||
if (flags.FastContains(QuaternionPrecisionFlag.AIsNegative))
|
||||
aValue *= -1f;
|
||||
if (flags.FastContains(QuaternionPrecisionFlag.BIsNegative))
|
||||
bValue *= -1f;
|
||||
if (flags.FastContains(QuaternionPrecisionFlag.CIsNegative))
|
||||
cValue *= -1f;
|
||||
|
||||
float abcMagnitude = GetMagnitude(aValue, bValue, cValue);
|
||||
|
||||
float dValue = 1f - abcMagnitude;
|
||||
/* NextD should always be positive. But depending on precision
|
||||
* the calculated result could be negative due to missing decimals.
|
||||
* When negative make positive so dValue will normalize properly. */
|
||||
if (dValue < 0f)
|
||||
dValue *= -1f;
|
||||
|
||||
dValue = (float)Math.Sqrt(dValue);
|
||||
|
||||
// Get magnitude of all values.
|
||||
static float GetMagnitude(float a, float b, float c, float d = 0f) => a * a + b * b + c * c + d * d;
|
||||
|
||||
if (dValue >= 0f && flags.FastContains(QuaternionPrecisionFlag.DIsNegative))
|
||||
dValue *= -1f;
|
||||
|
||||
if (!TryNormalize())
|
||||
return default;
|
||||
|
||||
// Normalizes next values.
|
||||
bool TryNormalize()
|
||||
{
|
||||
float magnitude = (float)Math.Sqrt(GetMagnitude(aValue, bValue, cValue, dValue));
|
||||
if (magnitude < float.Epsilon)
|
||||
{
|
||||
reader.NetworkManager.LogError($"Magnitude cannot be normalized.");
|
||||
return false;
|
||||
}
|
||||
|
||||
aValue /= magnitude;
|
||||
bValue /= magnitude;
|
||||
cValue /= magnitude;
|
||||
dValue /= magnitude;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Add onto the previous value. */
|
||||
if (flags.FastContains(QuaternionPrecisionFlag.LargestIsX))
|
||||
return new(dValue, aValue, bValue, cValue);
|
||||
if (flags.FastContains(QuaternionPrecisionFlag.LargestIsY))
|
||||
return new(aValue, dValue, bValue, cValue);
|
||||
if (flags.FastContains(QuaternionPrecisionFlag.LargestIsZ))
|
||||
return new(aValue, bValue, dValue, cValue);
|
||||
if (flags.FastContains(QuaternionPrecisionFlag.LargestIsW))
|
||||
return new(aValue, bValue, cValue, dValue);
|
||||
|
||||
reader.NetworkManager.LogError($"Unhandled Largest flag. Received flags are {flags}.");
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e3df94f7449d53c4b8b5a167b51a93dd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Serializing/Helping/QuaternionPrecisionCompression.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,159 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using FishNet.Managing;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Serializing.Helping
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to reserve bytes in a writer for length, then inserts length after data has been written.
|
||||
/// Reserved values are always written as unsigned.
|
||||
/// </summary>
|
||||
internal class ReservedLengthWriter : IResettable
|
||||
{
|
||||
private Writer _writer;
|
||||
private int _startPosition;
|
||||
private byte _reservedBytes;
|
||||
/// <summary>
|
||||
/// Number of bytes currently written.
|
||||
/// </summary>
|
||||
public int Length
|
||||
{
|
||||
get { return _writer == null ? 0 : _writer.Position - _startPosition; }
|
||||
}
|
||||
|
||||
public void Initialize(Writer writer, byte reservedBytes)
|
||||
{
|
||||
_writer = writer;
|
||||
_reservedBytes = reservedBytes;
|
||||
writer.Skip(reservedBytes);
|
||||
_startPosition = writer.Position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the amount of data written to the reserved space.
|
||||
/// This also resets the state of this object.
|
||||
/// </summary>
|
||||
public void WriteLength()
|
||||
{
|
||||
WriteLength((uint)Length);
|
||||
|
||||
ResetState();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the amount of data written to the reserved space. If no data was written the reserved amount is removed.
|
||||
/// This also resets the state of this object.
|
||||
/// Returns if length was written.
|
||||
/// </summary>
|
||||
public bool WriteLengthOrRemove(uint written)
|
||||
{
|
||||
if (written == 0)
|
||||
_writer.Remove(_reservedBytes);
|
||||
else
|
||||
WriteLength(written);
|
||||
|
||||
ResetState();
|
||||
|
||||
return written > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the amount of data written to the reserved space. This overrides Length normally written.
|
||||
/// This also resets the state of this object.
|
||||
/// </summary>
|
||||
public void WriteLength(uint written)
|
||||
{
|
||||
switch (_reservedBytes)
|
||||
{
|
||||
case 1:
|
||||
_writer.InsertUInt8Unpacked((byte)written, _startPosition - _reservedBytes);
|
||||
break;
|
||||
case 2:
|
||||
_writer.InsertUInt16Unpacked((ushort)written, _startPosition - _reservedBytes);
|
||||
break;
|
||||
case 4:
|
||||
_writer.InsertUInt32Unpacked((uint)written, _startPosition - _reservedBytes);
|
||||
break;
|
||||
default:
|
||||
NetworkManager nm = _writer == null ? null : _writer.NetworkManager;
|
||||
nm.LogError($"Reserved bytes value of {_reservedBytes} is unhandled.");
|
||||
break;
|
||||
}
|
||||
|
||||
ResetState();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the amount of data written to the reserved space. If no data was written the reserved amount is removed.
|
||||
/// This also resets the state of this object.
|
||||
/// </summary>
|
||||
public bool WriteLengthOrRemove()
|
||||
{
|
||||
// Insert written amount.
|
||||
int written = _writer.Position - _startPosition;
|
||||
|
||||
if (written == 0)
|
||||
_writer.Remove(_reservedBytes);
|
||||
else
|
||||
WriteLength((uint)written);
|
||||
|
||||
ResetState();
|
||||
|
||||
return written > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a length read based on a reserved byte count.
|
||||
/// </summary>
|
||||
/// <param name = "resetPosition">True to reset to position before read.</param>
|
||||
public static uint ReadLength(PooledReader reader, byte reservedBytes, bool resetPosition = false)
|
||||
{
|
||||
uint result;
|
||||
switch (reservedBytes)
|
||||
{
|
||||
case 1:
|
||||
result = reader.ReadUInt8Unpacked();
|
||||
break;
|
||||
case 2:
|
||||
result = reader.ReadUInt16Unpacked();
|
||||
break;
|
||||
case 4:
|
||||
result = reader.ReadUInt32Unpacked();
|
||||
break;
|
||||
default:
|
||||
NetworkManager nm = reader == null ? null : reader.NetworkManager;
|
||||
nm.LogError($"Reserved bytes value of {reservedBytes} is unhandled.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (resetPosition)
|
||||
reader.Position -= (int)result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
_writer = null;
|
||||
_startPosition = 0;
|
||||
_reservedBytes = 0;
|
||||
}
|
||||
|
||||
public void InitializeState() { }
|
||||
}
|
||||
|
||||
internal static class ReservedWritersExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores to a cache.
|
||||
/// </summary>
|
||||
public static void Store(this ReservedLengthWriter rlw) => ResettableObjectCaches<ReservedLengthWriter>.Store(rlw);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves from a cache.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static ReservedLengthWriter Retrieve() => ResettableObjectCaches<ReservedLengthWriter>.Retrieve();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b9fd1f1770c15f54da31f3903a8714ab
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Serializing/Helping/ReservedWriters.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,33 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace FishNet.Serializing.Helping
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
internal struct UIntFloat
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public float FloatValue;
|
||||
[FieldOffset(0)]
|
||||
public uint UIntValue;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
internal struct UIntDouble
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public double DoubleValue;
|
||||
[FieldOffset(0)]
|
||||
public ulong LongValue;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
internal struct UIntDecimal
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public ulong LongValue1;
|
||||
[FieldOffset(8)]
|
||||
public ulong LongValue2;
|
||||
[FieldOffset(0)]
|
||||
public decimal DecimalValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 008e79d0f22a2674189acc7eff64408f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Serializing/Helping/ValueConversions.cs
|
||||
uploadId: 866910
|
||||
Reference in New Issue
Block a user