From 9fed811995fbfa296d6ed0a0efc26dd1c0b22c6c Mon Sep 17 00:00:00 2001 From: belplaton Date: Wed, 17 Dec 2025 20:40:00 +0300 Subject: [PATCH 1/5] fix asmdefs --- Assets/FishNet/CodeGenerating/Unity.FishNet.CodeGen.asmdef | 5 ++++- Assets/FishNet/Runtime/FishNet.Runtime.asmdef | 4 +++- .../Plugins/GameKit/Dependencies/GameKit.Dependencies.asmdef | 5 ++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Assets/FishNet/CodeGenerating/Unity.FishNet.CodeGen.asmdef b/Assets/FishNet/CodeGenerating/Unity.FishNet.CodeGen.asmdef index 9aa9b97ad..22a5b611c 100644 --- a/Assets/FishNet/CodeGenerating/Unity.FishNet.CodeGen.asmdef +++ b/Assets/FishNet/CodeGenerating/Unity.FishNet.CodeGen.asmdef @@ -4,7 +4,10 @@ "references": [ "FishNet.Runtime", "FishNet.Codegen.Cecil", - "GameKit.Dependencies" + "GameKit.Dependencies", + "Unity.Burst", + "Unity.Mathematics", + "Unity.Collections" ], "includePlatforms": [ "Editor" diff --git a/Assets/FishNet/Runtime/FishNet.Runtime.asmdef b/Assets/FishNet/Runtime/FishNet.Runtime.asmdef index dc41020c1..b2c5f6a90 100644 --- a/Assets/FishNet/Runtime/FishNet.Runtime.asmdef +++ b/Assets/FishNet/Runtime/FishNet.Runtime.asmdef @@ -4,7 +4,9 @@ "references": [ "GUID:894a6cc6ed5cd2645bb542978cbed6a9", "GUID:1d82bdf40e2465b44b34adf79595e74c", - "GUID:d8b63aba1907145bea998dd612889d6b" + "GUID:d8b63aba1907145bea998dd612889d6b", + "GUID:2665a8d13d1b3f18800f46e256720795", + "GUID:e0cd26848372d4e5c891c569017e11f1" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/GameKit.Dependencies.asmdef b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/GameKit.Dependencies.asmdef index 438b416f0..751eaca63 100644 --- a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/GameKit.Dependencies.asmdef +++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/GameKit.Dependencies.asmdef @@ -2,7 +2,10 @@ "name": "GameKit.Dependencies", "rootNamespace": "", "references": [ - "GUID:6055be8ebefd69e48b49212b09b47b2f" + "GUID:6055be8ebefd69e48b49212b09b47b2f", + "GUID:d8b63aba1907145bea998dd612889d6b", + "GUID:2665a8d13d1b3f18800f46e256720795", + "GUID:e0cd26848372d4e5c891c569017e11f1" ], "includePlatforms": [], "excludePlatforms": [], From a1082f1b2f3c24d5be83e21f2441583fd6701cb3 Mon Sep 17 00:00:00 2001 From: belplaton Date: Wed, 17 Dec 2025 20:40:22 +0300 Subject: [PATCH 2/5] feat: add QOL --- .../Dependencies/Utilities/Dictionaries.cs | 10 +- .../GameKit/Dependencies/Utilities/Floats.cs | 9 + .../Dependencies/Utilities/Quaternions.cs | 47 ++- .../Utilities/Types/StripedRingQueue.cs | 370 ++++++++++++++++++ .../GameKit/Dependencies/Utilities/Vectors.cs | 28 ++ .../Runtime/Utility/Extension/Transforms.cs | 332 ++++++++++++++-- 6 files changed, 757 insertions(+), 39 deletions(-) create mode 100644 Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/StripedRingQueue.cs diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Dictionaries.cs b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Dictionaries.cs index b6a56652a..d7d65070b 100644 --- a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Dictionaries.cs +++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Dictionaries.cs @@ -8,7 +8,7 @@ public static class DictionaryFN /// Uses a hacky way to TryGetValue on a dictionary when using IL2CPP and on mobile. /// This is to support older devices that don't properly handle IL2CPP builds. /// - public static bool TryGetValueIL2CPP(this IDictionary dict, TKey key, out TValue value) + public static bool TryGetValueIL2CPP(this IReadOnlyDictionary dict, TKey key, out TValue value) { #if ENABLE_IL2CPP && UNITY_IOS || UNITY_ANDROID if (dict.ContainsKey(key)) @@ -30,7 +30,7 @@ public static bool TryGetValueIL2CPP(this IDictionary /// - public static List ValuesToList(this IDictionary dict, bool useCache) + public static List ValuesToList(this IReadOnlyDictionary dict, bool useCache) { List result = useCache ? CollectionCaches.RetrieveList() : new(dict.Count); @@ -43,7 +43,7 @@ public static List ValuesToList(this IDictionary /// Adds values to a list. /// - public static void ValuesToList(this IDictionary dict, ref List result, bool clearLst) + public static void ValuesToList(this IReadOnlyDictionary dict, ref List result, bool clearLst) { if (clearLst) result.Clear(); @@ -55,7 +55,7 @@ public static void ValuesToList(this IDictionary dic /// /// Returns keys as a list. /// - public static List KeysToList(this IDictionary dict, bool useCache) + public static List KeysToList(this IReadOnlyDictionary dict, bool useCache) { List result = useCache ? CollectionCaches.RetrieveList() : new(dict.Count); @@ -68,7 +68,7 @@ public static List KeysToList(this IDictionary /// /// Adds keys to a list. /// - public static void KeysToList(this IDictionary dict, ref List result, bool clearLst) + public static void KeysToList(this IReadOnlyDictionary dict, ref List result, bool clearLst) { result.Clear(); diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Floats.cs b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Floats.cs index c082ac258..5b7a908f5 100644 --- a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Floats.cs +++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Floats.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; using UnityEngine; namespace GameKit.Dependencies.Utilities @@ -16,6 +17,7 @@ public static class Floats /// Float to check against tolerance. /// Tolerance float must be equal to or greater than to change to value. /// Value source is set to when breaking tolerance. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float SetIfOverTolerance(this float source, float tolerance, float value) { if (source >= tolerance) @@ -30,6 +32,7 @@ public static float SetIfOverTolerance(this float source, float tolerance, float /// Float to check against tolerance. /// Tolerance float must be equal to or less than to change to value. /// Value source is set to when breaking tolerance. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float SetIfUnderTolerance(this float source, float tolerance, float value) { if (source <= tolerance) @@ -42,6 +45,7 @@ public static float SetIfUnderTolerance(this float source, float tolerance, floa /// Returns how much time is left on an endTime. Returns -1 if no time is left. /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float TimeRemainingValue(this float endTime) { float remaining = endTime - Time.time; @@ -56,6 +60,7 @@ public static float TimeRemainingValue(this float endTime) /// Returns how much time is left on an endTime. Returns -1 if no time is left. /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int TimeRemainingValue(this float endTime, bool useFloor = true) { float remaining = endTime - Time.time; @@ -136,6 +141,7 @@ public static float Random01() /// /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Near(this float a, float b, float tolerance = 0.01f) { return Mathf.Abs(a - b) <= tolerance; @@ -149,6 +155,7 @@ public static bool Near(this float a, float b, float tolerance = 0.01f) /// /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Clamp(float value, float min, float max, ref bool clamped) { clamped = value < min; @@ -192,6 +199,7 @@ public static void Variance(this float source, float variance, ref float result) /// /// Value to sign. /// Precise sign. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float PreciseSign(float value) { if (value == 0f) @@ -207,6 +215,7 @@ public static float PreciseSign(float value) /// Minimum of range. /// Maximum of range. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool InRange(this float source, float rangeMin, float rangeMax) { return source >= rangeMin && source <= rangeMax; diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Quaternions.cs b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Quaternions.cs index d302deb42..bfac65d16 100644 --- a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Quaternions.cs +++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Quaternions.cs @@ -1,4 +1,5 @@ -using UnityEngine; +using Unity.Mathematics; +using UnityEngine; namespace GameKit.Dependencies.Utilities { @@ -16,16 +17,39 @@ public static float GetRate(this Quaternion a, Quaternion goal, float duration, angle = a.Angle(goal, true); return angle / (duration * interval); } + + /// + /// Returns how fast an object must rotate over duration to reach goal. + /// + /// Quaternion to measure distance against. + /// How long it should take to move to goal. + /// A multiplier applied towards interval. Typically this is used for ticks passed. + /// + public static float GetRate(this quaternion a, quaternion goal, float duration, out float angle, uint interval = 1, float tolerance = 0f) + { + angle = a.Angle(goal, true); + return angle / (duration * interval); + } /// /// Subtracts b quaternion from a. /// public static Quaternion Subtract(this Quaternion a, Quaternion b) => Quaternion.Inverse(b) * a; + + /// + /// Subtracts b quaternion from a. + /// + public static quaternion Subtract(this quaternion a, quaternion b) => math.mul(math.inverse(b), a); /// /// Adds quaternion b onto quaternion a. /// public static Quaternion Add(this Quaternion a, Quaternion b) => a * b; + + /// + /// Adds quaternion b onto quaternion a. + /// + public static quaternion Add(this quaternion a, quaternion b) => math.mul(a, b); /// /// Returns if two quaternions match. @@ -58,5 +82,26 @@ public static float Angle(this Quaternion a, Quaternion b, bool precise = false) return Quaternion.Angle(a, b); } } + + /// + /// Returns the angle between two quaterions. + /// + /// True to use a custom implementation with no error tolerance. False to use Unity's implementation which may return 0f due to error tolerance, even while there is a difference. + /// + public static float Angle(this quaternion a, quaternion b, bool precise = false) + { + if (!precise) + { + float d = math.dot(a.value, b.value); + float c = math.saturate(math.abs(d)); + return math.degrees(2f * math.acos(c)); + } + + quaternion an = math.normalize(a); + quaternion bn = math.normalize(b); + float dn = math.dot(an.value, bn.value); + float cn = math.saturate(math.abs(dn)); + return math.degrees(2f * math.acos(cn)); + } } } \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/StripedRingQueue.cs b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/StripedRingQueue.cs new file mode 100644 index 000000000..72b87937c --- /dev/null +++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/StripedRingQueue.cs @@ -0,0 +1,370 @@ +using System; +using System.Runtime.CompilerServices; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine; + +namespace GameKit.Dependencies.Utilities +{ + /// + /// A striped ring-buffer that stores N independent queues addressed by i = 0..N-1. + /// Backing storage uses NativeList-based stripes with a fixed per-queue capacity. + /// Designed for job-friendly, per-index parallel work without cross contention. + /// + public struct StripedRingQueue : IDisposable + where T : unmanaged + { + /// + /// Backing storage for all stripes; length equals _queueCount * _capacity. + /// + [NativeDisableParallelForRestriction] private NativeList _data; + /// + /// Per-queue head (read index) + /// Advances on dequeue operations modulo _capacity. + /// + [NativeDisableParallelForRestriction] private NativeList _head; + /// + /// Per-queue item count. + /// Always clamped to the range [0.._capacity]. + /// + [NativeDisableParallelForRestriction] private NativeList _count; + /// + /// Compact metadata buffer stored in native memory: + /// [0] = fixed per-queue capacity, [1] = current queue count. + /// + [NativeDisableParallelForRestriction] private NativeArray _meta; + + /// + /// True when internal lists are allocated and usable. + /// + public bool IsCreated + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data.IsCreated && _head.IsCreated && _count.IsCreated && _meta.IsCreated; + } + /// + /// Fixed capacity per queue (ring size). + /// + public int Capacity + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _meta[0]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private set => _meta[0] = value; + } + /// + /// Number of independent queues (stripes). + /// + public int QueueCount + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _meta[1]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private set => _meta[1] = value; + } + /// + /// Total addressable storage, equal to QueueCount * Capacity. + /// + public int TotalCapacity + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => QueueCount * Capacity; + } + + /// + /// Indexer for direct access by queue index and raw ring index (0..Capacity-1). + /// Does not account for head/count; use GetCount/Clear/Enqueue/Dequeue for logical queue semantics. + /// + public T this[int queueIndex, int simulatedIndex] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + int offset = GetRealOffset(queueIndex, simulatedIndex); + return _data[offset]; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + int offset = GetRealOffset(queueIndex, simulatedIndex); + _data[offset] = value; + } + } + + /// + /// Constructs the striped ring with an initial queue count and per-queue capacity. + /// Allocates NativeList storage and zeroes head/count for all stripes. + /// + public StripedRingQueue(int initialQueueCount, int capacity, Allocator allocator) + { + if (initialQueueCount < 0) throw new ArgumentOutOfRangeException(nameof(initialQueueCount)); + if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity)); + + _meta = new NativeArray(2, allocator, NativeArrayOptions.UninitializedMemory); + _meta[0] = capacity; + _meta[1] = initialQueueCount; + + _data = new NativeList(math.max(1, initialQueueCount * capacity), allocator); + _head = new NativeList(math.max(1, initialQueueCount), allocator); + _count = new NativeList(math.max(1, initialQueueCount), allocator); + + _data.ResizeUninitialized(initialQueueCount * capacity); + _head.ResizeUninitialized(initialQueueCount); + _count.ResizeUninitialized(initialQueueCount); + + for (int i = 0; i < initialQueueCount; i++) + { + _head[i] = 0; + _count[i] = 0; + } + } + + /// + /// Disposes all internal lists synchronously. + /// Ensure that no jobs are accessing this storage when disposing. + /// + public void Dispose() + { + if (_data.IsCreated) _data.Dispose(); + if (_head.IsCreated) _head.Dispose(); + if (_count.IsCreated) _count.Dispose(); + if (_meta.IsCreated) _meta.Dispose(); + } + + /// + /// Schedules disposal of internal lists and returns a combined JobHandle. + /// Use this to free storage once dependent jobs have completed. + /// + public JobHandle Dispose(JobHandle inputDeps) + { + JobHandle h = inputDeps; + if (_data.IsCreated) h = _data.Dispose(h); + if (_head.IsCreated) h = _head.Dispose(h); + if (_count.IsCreated) h = _count.Dispose(h); + if (_meta.IsCreated) h = _meta.Dispose(h); + return h; + } + + /// + /// Adds a new empty queue (stripe) and returns its index. + /// Grows the data buffer by Capacity and zeroes the stripe's head/count. + /// + public int AddQueue() + { + int capacity = Capacity; + int queueCount = QueueCount; + + int newIndex = queueCount; + + int newDataLen = (newIndex + 1) * capacity; + if (_data.Capacity < newDataLen) _data.Capacity = newDataLen; + _data.ResizeUninitialized(newDataLen); + + _head.Add(0); + _count.Add(0); + + QueueCount = newIndex + 1; + return newIndex; + } + + /// + /// Removes the queue at the given index by swapping with the last stripe, + /// then shrinking storage by one stripe. Data swap is O(Capacity). + /// + public void RemoveQueueAtSwapBack(int index) + { + int queueCount = QueueCount; + int capacity = Capacity; + + int last = queueCount - 1; + if ((uint)index >= (uint)queueCount) + throw new ArgumentOutOfRangeException(nameof(index)); + if (last < 0) + return; + + if (index != last) + { + int a = index * capacity; + int b = last * capacity; + + for (int k = 0; k < capacity; k++) + { + (_data[a + k], _data[b + k]) = (_data[b + k], _data[a + k]); + } + } + + _data.ResizeUninitialized(_data.Length - capacity); + _head.RemoveAtSwapBack(index); + _count.RemoveAtSwapBack(index); + + QueueCount = last; + } + + /// + /// Returns the current number of items in queue i. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetCount(int i) => _count[i]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int BaseOffset(int i) => i * Capacity; + + /// + /// Returns the real index of the collection using a simulated index. + /// + /// + /// + /// True to allow an index be returned from an unused portion of the buffer so long as it is within bounds. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetRealOffset(int queueIndex, int simulatedIndex, bool allowUnusedBuffer = false) + { + int capacity = Capacity; + int queueCount = QueueCount; + + if ((uint)queueIndex >= (uint)queueCount) + throw new ArgumentOutOfRangeException(nameof(queueIndex)); + if ((uint)simulatedIndex >= (uint)capacity) + throw new ArgumentOutOfRangeException(nameof(simulatedIndex)); + + int count = _count[queueIndex]; + if (simulatedIndex >= count && !allowUnusedBuffer) + throw new ArgumentOutOfRangeException( + nameof(simulatedIndex), + $"Index {simulatedIndex} >= item count {count} in queue {queueIndex}"); + + int head = _head[queueIndex]; + int offset = (head + simulatedIndex) % capacity; + return BaseOffset(queueIndex) + offset; + } + + /// + /// Clears queue i by resetting head and count to zero. + /// Stored values remain but are considered invalid. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear(int i) + { + _head[i] = 0; + _count[i] = 0; + } + + /// + /// Enqueues 'value' into queue i; overwrites the oldest item when full. + /// Main-thread only unless no concurrent access to the same i is guaranteed. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Enqueue(int i, in T value) + { + int capacity = Capacity; + + int h = _head[i]; + int c = _count[i]; + int baseOff = BaseOffset(i); + int tail = (h + c) % capacity; + + _data[baseOff + tail] = value; + + if (c < capacity) + { + _count[i] = c + 1; + } + else + { + _head[i] = (h + 1) % capacity; // overwrite oldest + } + } + + /// + /// Tries to dequeue one item from queue i into 'value'. + /// Returns false when the queue is empty. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryDequeue(int i, out T value) + { + int c = _count[i]; + if (c == 0) + { + value = default; + return false; + } + + int capacity = Capacity; + + int h = _head[i]; + int baseOff = BaseOffset(i); + value = _data[baseOff + h]; + + _head[i] = (h + 1) % capacity; + _count[i] = c - 1; + return true; + } + + /// + /// Dequeues up to 'n' items from queue i and returns how many were removed. + /// The last removed item (if any) is written to 'last'. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int DequeueUpTo(int i, int n, out T last) + { + int c = _count[i]; + int drop = math.clamp(n, 0, c); + if (drop == 0) + { + last = default; + return 0; + } + + int capacity = Capacity; + + int h = _head[i]; + int baseOff = BaseOffset(i); + int lastIdx = (h + drop - 1) % capacity; + + last = _data[baseOff + lastIdx]; + + _head[i] = (h + drop) % capacity; + _count[i] = c - drop; + return drop; + } + + /// + /// Peeks the next entry from i queue. + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Peek(int i) + { + int c = _count[i]; + if (c == 0) + throw new InvalidOperationException($"{nameof(StripedRingQueue)} of type {typeof(T).Name} is empty."); + + int h = _head[i]; + int baseOff = BaseOffset(i); + return _data[baseOff + h]; + } + + /// + /// Tries to peek the next entry from queue i. + /// + /// + /// Peeked entry. + /// True if an entry existed to peek. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryPeek(int i, out T result) + { + int c = _count[i]; + if (c == 0) + { + result = default; + return false; + } + + int h = _head[i]; + int baseOff = BaseOffset(i); + result = _data[baseOff + h]; + return true; + } + } +} \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Vectors.cs b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Vectors.cs index 0fa226457..6102fc003 100644 --- a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Vectors.cs +++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Vectors.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.CompilerServices; +using Unity.Mathematics; using UnityEngine; namespace GameKit.Dependencies.Utilities @@ -23,15 +24,31 @@ public static class Vectors /// How long it should take to move to goal. /// A multiplier applied towards interval. Typically this is used for ticks passed. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float GetRate(this Vector3 a, Vector3 b, float duration, out float distance, uint interval = 1) { distance = Vector3.Distance(a, b); return distance / (duration * interval); } + + /// + /// Returns how fast an object must move over duration to reach goal. + /// + /// Vector3 to measure distance against. + /// How long it should take to move to goal. + /// A multiplier applied towards interval. Typically this is used for ticks passed. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float GetRate(this float3 a, float3 b, float duration, out float distance, uint interval = 1) + { + distance = math.distance(a, b); + return distance / (duration * interval); + } /// /// Adds a Vector2 X/Y onto a Vector3. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector3 Add(this Vector3 v3, Vector2 v2) { return v3 + new Vector3(v2.x, v2.y, 0f); @@ -40,6 +57,7 @@ public static Vector3 Add(this Vector3 v3, Vector2 v2) /// /// Subtracts a Vector2 X/Y from a Vector3. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector3 Subtract(this Vector3 v3, Vector2 v2) { return v3 - new Vector3(v2.x, v2.y, 0f); @@ -52,6 +70,7 @@ public static Vector3 Subtract(this Vector3 v3, Vector2 v2) /// /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float InverseLerp(Vector3 a, Vector3 b, Vector3 value) { Vector3 ab = b - a; @@ -66,6 +85,7 @@ public static float InverseLerp(Vector3 a, Vector3 b, Vector3 value) /// Target vector. /// How close the target vector must be to be considered close. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Near(this Vector3 a, Vector3 b, float tolerance = 0.01f) { return Vector3.Distance(a, b) <= tolerance; @@ -76,6 +96,7 @@ public static bool Near(this Vector3 a, Vector3 b, float tolerance = 0.01f) /// /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsNan(this Vector3 source) { return float.IsNaN(source.x) || float.IsNaN(source.y) || float.IsNaN(source.z); @@ -85,6 +106,7 @@ public static bool IsNan(this Vector3 source) /// Lerp between three Vector3 values. /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector3 Lerp3(Vector3 a, Vector3 b, Vector3 c, float percent) { Vector3 r0 = Vector3.Lerp(a, b, percent); @@ -98,6 +120,7 @@ public static Vector3 Lerp3(Vector3 a, Vector3 b, Vector3 c, float percent) /// /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector3 Lerp3(Vector3[] vectors, float percent) { if (vectors.Length < 3) @@ -115,6 +138,7 @@ public static Vector3 Lerp3(Vector3[] vectors, float percent) /// /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector3 Multiply(this Vector3 src, Vector3 multiplier) { return new(src.x * multiplier.x, src.y * multiplier.y, src.z * multiplier.z); @@ -201,6 +225,7 @@ public static Vector3 FastNormalize(Vector3 value) /// How long it should take to move to goal. /// A multiplier applied towards interval. Typically this is used for ticks passed. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float GetRate(this Vector2 a, Vector2 goal, float duration, out float distance, uint interval = 1) { distance = Vector2.Distance(a, goal); @@ -215,6 +240,7 @@ public static float GetRate(this Vector2 a, Vector2 goal, float duration, out fl /// /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 Lerp3(Vector2 a, Vector2 b, Vector2 c, float percent) { Vector2 r0 = Vector2.Lerp(a, b, percent); @@ -228,6 +254,7 @@ public static Vector2 Lerp3(Vector2 a, Vector2 b, Vector2 c, float percent) /// /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 Lerp2(Vector2[] vectors, float percent) { if (vectors.Length < 3) @@ -245,6 +272,7 @@ public static Vector2 Lerp2(Vector2[] vectors, float percent) /// /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 Multiply(this Vector2 src, Vector2 multiplier) { return new(src.x * multiplier.x, src.y * multiplier.y); diff --git a/Assets/FishNet/Runtime/Utility/Extension/Transforms.cs b/Assets/FishNet/Runtime/Utility/Extension/Transforms.cs index 301b71e29..b92f9bd96 100644 --- a/Assets/FishNet/Runtime/Utility/Extension/Transforms.cs +++ b/Assets/FishNet/Runtime/Utility/Extension/Transforms.cs @@ -3,6 +3,7 @@ using FishNet.Object; using System.Runtime.CompilerServices; using UnityEngine; +using UnityEngine.Jobs; namespace FishNet.Utility.Extension { @@ -10,48 +11,124 @@ namespace FishNet.Utility.Extension public static class TransformFN { /// - /// Sets values of TransformProperties to a transforms world properties. + /// Gets correct values of Vector3 pos and Quaternion rot + /// + public static void GetCorrectLocalPositionAndRotation(this TransformAccess t, out Vector3 pos, out Quaternion rot) + { + // https://issuetracker.unity3d.com/issues/wrong-position-and-rotation-values-are-returned-when-using-transformaccess-dot-getlocalpositionandrotation + pos = t.localPosition; + rot = t.localRotation; + } + + /// + /// Sets correct values of Vector3 pos and Quaternion rot + /// + public static void SetCorrectLocalPositionAndRotation(this TransformAccess t, Vector3 pos, Quaternion rot) + { + // https://issuetracker.unity3d.com/issues/wrong-position-and-rotation-values-are-returned-when-using-transformaccess-dot-getlocalpositionandrotation + t.localPosition = pos; + t.localRotation = rot; + } + + /// + /// Gets values of TransformProperties from the transforms world properties. /// public static TransformProperties GetWorldProperties(this Transform t) { - TransformProperties tp = new(t.position, t.rotation, t.localScale); + t.GetPositionAndRotation(out var pos, out var rot); + TransformProperties tp = new(pos, rot, t.localScale); + return tp; + } + + /// + /// Gets values of TransformProperties from the transforms world properties. + /// + public static TransformProperties GetWorldProperties(this TransformAccess t) + { + t.GetPositionAndRotation(out var pos, out var rot); + TransformProperties tp = new(pos, rot, t.localScale); return tp; } /// - /// Sets values of TransformProperties to a transforms world properties. + /// Gets values of TransformProperties from the transforms world properties. /// public static TransformProperties GetWorldProperties(this Transform t, TransformProperties offset) { - TransformProperties tp = new(t.position, t.rotation, t.localScale); + t.GetPositionAndRotation(out var pos, out var rot); + TransformProperties tp = new(pos, rot, t.localScale); + tp.Add(offset); + return tp; + } + + /// + /// Gets values of TransformProperties from the transforms world properties. + /// + public static TransformProperties GetWorldProperties(this TransformAccess t, TransformProperties offset) + { + t.GetPositionAndRotation(out var pos, out var rot); + TransformProperties tp = new(pos, rot, t.localScale); tp.Add(offset); return tp; } /// - /// Sets values of TransformProperties to a transforms world properties. + /// Gets values of TransformProperties from the transforms world properties. /// public static TransformPropertiesCls GetWorldPropertiesCls(this Transform t) { - TransformPropertiesCls tp = new(t.position, t.rotation, t.localScale); + t.GetPositionAndRotation(out var pos, out var rot); + TransformPropertiesCls tp = new(pos, rot, t.localScale); + return tp; + } + + /// + /// Gets values of TransformProperties from the transforms world properties. + /// + public static TransformPropertiesCls GetWorldPropertiesCls(this TransformAccess t) + { + t.GetPositionAndRotation(out var pos, out var rot); + TransformPropertiesCls tp = new(pos, rot, t.localScale); return tp; } /// - /// Sets values of TransformProperties to a transforms world properties. + /// Gets values of TransformProperties from the transforms world properties. /// public static TransformProperties GetLocalProperties(this Transform t) { - TransformProperties tp = new(t.localPosition, t.localRotation, t.localScale); + t.GetLocalPositionAndRotation(out var pos, out var rot); + TransformProperties tp = new(pos, rot, t.localScale); + return tp; + } + + /// + /// Gets values of TransformProperties from the transforms world properties. + /// + public static TransformProperties GetLocalProperties(this TransformAccess t) + { + t.GetCorrectLocalPositionAndRotation(out var pos, out var rot); + TransformProperties tp = new(pos, rot, t.localScale); return tp; } /// - /// Sets values of TransformProperties to a transforms world properties. + /// Gets values of TransformProperties from the transforms world properties. /// public static TransformPropertiesCls GetLocalPropertiesCls(this Transform t) { - TransformPropertiesCls tp = new(t.localPosition, t.localRotation, t.localScale); + t.GetLocalPositionAndRotation(out var pos, out var rot); + TransformPropertiesCls tp = new(pos, rot, t.localScale); + return tp; + } + + /// + /// Gets values of TransformProperties from the transforms world properties. + /// + public static TransformPropertiesCls GetLocalPropertiesCls(this TransformAccess t) + { + t.GetCorrectLocalPositionAndRotation(out var pos, out var rot); + TransformPropertiesCls tp = new(pos, rot, t.localScale); return tp; } @@ -70,8 +147,10 @@ public static void SetTransformOffsets(this Transform t, Transform target, ref V { if (target == null) return; - pos = target.position - t.position; - rot = target.rotation * Quaternion.Inverse(t.rotation); + t.GetPositionAndRotation(out var tPos, out var tRot); + target.GetPositionAndRotation(out var targetPos, out var targetRot); + pos = targetPos - tPos; + rot = targetRot * Quaternion.Inverse(tRot); } /// @@ -83,7 +162,9 @@ public static TransformProperties GetTransformOffsets(this Transform t, Transfor if (target == null) return default; - return new(target.position - t.position, target.rotation * Quaternion.Inverse(t.rotation), target.localScale - t.localScale); + t.GetPositionAndRotation(out var tPos, out var tRot); + target.GetPositionAndRotation(out var targetPos, out var targetRot); + return new(targetPos - tPos, targetRot * Quaternion.Inverse(tRot), target.localScale - t.localScale); } /// @@ -91,8 +172,16 @@ public static TransformProperties GetTransformOffsets(this Transform t, Transfor /// public static void SetLocalProperties(this Transform t, TransformPropertiesCls tp) { - t.localPosition = tp.Position; - t.localRotation = tp.Rotation; + t.SetLocalPositionAndRotation(tp.Position, tp.Rotation); + t.localScale = tp.LocalScale; + } + + /// + /// Sets a transform to local properties. + /// + public static void SetLocalProperties(this TransformAccess t, TransformPropertiesCls tp) + { + t.SetCorrectLocalPositionAndRotation(tp.Position, tp.Rotation); t.localScale = tp.LocalScale; } @@ -101,8 +190,16 @@ public static void SetLocalProperties(this Transform t, TransformPropertiesCls t /// public static void SetLocalProperties(this Transform t, TransformProperties tp) { - t.localPosition = tp.Position; - t.localRotation = tp.Rotation; + t.SetLocalPositionAndRotation(tp.Position, tp.Rotation); + t.localScale = tp.Scale; + } + + /// + /// Sets a transform to local properties. + /// + public static void SetLocalProperties(this TransformAccess t, TransformProperties tp) + { + t.SetCorrectLocalPositionAndRotation(tp.Position, tp.Rotation); t.localScale = tp.Scale; } @@ -111,8 +208,16 @@ public static void SetLocalProperties(this Transform t, TransformProperties tp) /// public static void SetWorldProperties(this Transform t, TransformPropertiesCls tp) { - t.position = tp.Position; - t.rotation = tp.Rotation; + t.SetPositionAndRotation(tp.Position, tp.Rotation); + t.localScale = tp.LocalScale; + } + + /// + /// Sets a transform to world properties. + /// + public static void SetWorldProperties(this TransformAccess t, TransformPropertiesCls tp) + { + t.SetPositionAndRotation(tp.Position, tp.Rotation); t.localScale = tp.LocalScale; } @@ -121,8 +226,16 @@ public static void SetWorldProperties(this Transform t, TransformPropertiesCls t /// public static void SetWorldProperties(this Transform t, TransformProperties tp) { - t.position = tp.Position; - t.rotation = tp.Rotation; + t.SetPositionAndRotation(tp.Position, tp.Rotation); + t.localScale = tp.Scale; + } + + /// + /// Sets a transform to world properties. + /// + public static void SetWorldProperties(this TransformAccess t, TransformProperties tp) + { + t.SetPositionAndRotation(tp.Position, tp.Rotation); t.localScale = tp.Scale; } @@ -131,8 +244,15 @@ public static void SetWorldProperties(this Transform t, TransformProperties tp) /// public static void SetLocalPositionAndRotation(this Transform t, Vector3 pos, Quaternion rot) { - t.localPosition = pos; - t.localRotation = rot; + t.SetLocalPositionAndRotation(pos, rot); + } + + /// + /// Sets local position and rotation for a transform. + /// + public static void SetLocalPositionAndRotation(this TransformAccess t, Vector3 pos, Quaternion rot) + { + t.SetCorrectLocalPositionAndRotation(pos, rot); } /// @@ -140,8 +260,16 @@ public static void SetLocalPositionAndRotation(this Transform t, Vector3 pos, Qu /// public static void SetLocalPositionRotationAndScale(this Transform t, Vector3 pos, Quaternion rot, Vector3 scale) { - t.localPosition = pos; - t.localRotation = rot; + t.SetLocalPositionAndRotation(pos, rot); + t.localScale = scale; + } + + /// + /// Sets local position, rotation, and scale for a transform. + /// + public static void SetLocalPositionRotationAndScale(this TransformAccess t, Vector3 pos, Quaternion rot, Vector3 scale) + { + t.SetCorrectLocalPositionAndRotation(pos, rot); t.localScale = scale; } @@ -151,8 +279,29 @@ public static void SetLocalPositionRotationAndScale(this Transform t, Vector3 po public static void SetLocalPositionRotationAndScale(this Transform t, Vector3? nullablePos, Quaternion? nullableRot, Vector3? nullableScale) { if (nullablePos.HasValue) - t.localPosition = nullablePos.Value; - if (nullableRot.HasValue) + { + if (nullableRot.HasValue) + t.SetLocalPositionAndRotation(nullablePos.Value, nullableRot.Value); + else t.localPosition = nullablePos.Value; + } + else if (nullableRot.HasValue) + t.localRotation = nullableRot.Value; + if (nullableScale.HasValue) + t.localScale = nullableScale.Value; + } + + /// + /// Sets local position, rotation, and scale using nullables for a transform. If a value is null then that property is skipped. + /// + public static void SetLocalPositionRotationAndScale(this TransformAccess t, Vector3? nullablePos, Quaternion? nullableRot, Vector3? nullableScale) + { + if (nullablePos.HasValue) + { + if (nullableRot.HasValue) + t.SetCorrectLocalPositionAndRotation(nullablePos.Value, nullableRot.Value); + else t.localPosition = nullablePos.Value; + } + else if (nullableRot.HasValue) t.localRotation = nullableRot.Value; if (nullableScale.HasValue) t.localScale = nullableScale.Value; @@ -164,8 +313,29 @@ public static void SetLocalPositionRotationAndScale(this Transform t, Vector3? n public static void SetWorldPositionRotationAndScale(this Transform t, Vector3? nullablePos, Quaternion? nullableRot, Vector3? nullableScale) { if (nullablePos.HasValue) - t.position = nullablePos.Value; - if (nullableRot.HasValue) + { + if (nullableRot.HasValue) + t.SetPositionAndRotation(nullablePos.Value, nullableRot.Value); + else t.position = nullablePos.Value; + } + else if (nullableRot.HasValue) + t.rotation = nullableRot.Value; + if (nullableScale.HasValue) + t.localScale = nullableScale.Value; + } + + /// + /// Sets world position, rotation, and scale using nullables for a transform. If a value is null then that property is skipped. + /// + public static void SetWorldPositionRotationAndScale(this TransformAccess t, Vector3? nullablePos, Quaternion? nullableRot, Vector3? nullableScale) + { + if (nullablePos.HasValue) + { + if (nullableRot.HasValue) + t.SetPositionAndRotation(nullablePos.Value, nullableRot.Value); + else t.position = nullablePos.Value; + } + else if (nullableRot.HasValue) t.rotation = nullableRot.Value; if (nullableScale.HasValue) t.localScale = nullableScale.Value; @@ -176,8 +346,56 @@ public static void SetWorldPositionRotationAndScale(this Transform t, Vector3? n /// public static void OutLocalPropertyValues(this Transform t, Vector3? nullablePos, Quaternion? nullableRot, Vector3? nullableScale, out Vector3 pos, out Quaternion rot, out Vector3 scale) { - pos = nullablePos == null ? t.localPosition : nullablePos.Value; - rot = nullableRot == null ? t.localRotation : nullableRot.Value; + if (!nullablePos.HasValue) + { + if (!nullableRot.HasValue) + t.GetLocalPositionAndRotation(out pos, out rot); + else + { + pos = t.localPosition; + rot = nullableRot.Value; + } + } + else if (!nullableRot.HasValue) + { + pos = nullablePos.Value; + rot = t.localRotation; + } + else + { + pos = nullablePos.Value; + rot = nullableRot.Value; + } + + scale = nullableScale == null ? t.localScale : nullableScale.Value; + } + + /// + /// Oututs properties to use for a transform. When a nullable property has value that value is used, otherwise the transforms current property is used. + /// + public static void OutLocalPropertyValues(this TransformAccess t, Vector3? nullablePos, Quaternion? nullableRot, Vector3? nullableScale, out Vector3 pos, out Quaternion rot, out Vector3 scale) + { + if (!nullablePos.HasValue) + { + if (!nullableRot.HasValue) + t.GetCorrectLocalPositionAndRotation(out pos, out rot); + else + { + pos = t.localPosition; + rot = nullableRot.Value; + } + } + else if (!nullableRot.HasValue) + { + pos = nullablePos.Value; + rot = t.localRotation; + } + else + { + pos = nullablePos.Value; + rot = nullableRot.Value; + } + scale = nullableScale == null ? t.localScale : nullableScale.Value; } @@ -186,8 +404,56 @@ public static void OutLocalPropertyValues(this Transform t, Vector3? nullablePos /// public static void OutWorldPropertyValues(this Transform t, Vector3? nullablePos, Quaternion? nullableRot, Vector3? nullableScale, out Vector3 pos, out Quaternion rot, out Vector3 scale) { - pos = nullablePos == null ? t.position : nullablePos.Value; - rot = nullableRot == null ? t.rotation : nullableRot.Value; + if (!nullablePos.HasValue) + { + if (!nullableRot.HasValue) + t.GetPositionAndRotation(out pos, out rot); + else + { + pos = t.position; + rot = nullableRot.Value; + } + } + else if (!nullableRot.HasValue) + { + pos = nullablePos.Value; + rot = t.rotation; + } + else + { + pos = nullablePos.Value; + rot = nullableRot.Value; + } + + scale = nullableScale == null ? t.localScale : nullableScale.Value; + } + + /// + /// Oututs properties to use for a transform. When a nullable property has value that value is used, otherwise the transforms current property is used. + /// + public static void OutWorldPropertyValues(this TransformAccess t, Vector3? nullablePos, Quaternion? nullableRot, Vector3? nullableScale, out Vector3 pos, out Quaternion rot, out Vector3 scale) + { + if (!nullablePos.HasValue) + { + if (!nullableRot.HasValue) + t.GetPositionAndRotation(out pos, out rot); + else + { + pos = t.position; + rot = nullableRot.Value; + } + } + else if (!nullableRot.HasValue) + { + pos = nullablePos.Value; + rot = t.rotation; + } + else + { + pos = nullablePos.Value; + rot = nullableRot.Value; + } + scale = nullableScale == null ? t.localScale : nullableScale.Value; } } From 707c3eb4d475daf1aebd221b30bde8c4943c2dfc Mon Sep 17 00:00:00 2001 From: belplaton Date: Wed, 17 Dec 2025 20:46:37 +0300 Subject: [PATCH 3/5] feat: add jobified tick smoother --- .../TickSmoothing/MovementSettings.cs | 8 +- .../TickSmoothing/TickSmootherController.cs | 281 ++-- .../TickSmoothingManager.Types.cs | 1315 ++++++++++++++++ .../TickSmoothing/TickSmoothingManager.cs | 1336 +++++++++++++++++ .../TickSmoothing/UniversalTickSmoother.cs | 105 +- .../Runtime/Managing/NetworkManager.cs | 7 + .../Runtime/Managing/Timing/TimeManager.cs | 184 ++- .../Runtime/Object/Prediction/MoveRates.cs | 320 +++- .../Runtime/Object/TransformProperties.cs | 100 +- 9 files changed, 3268 insertions(+), 388 deletions(-) create mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs create mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.cs index f543473fc..135913c8f 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.cs @@ -34,6 +34,11 @@ public struct MovementSettings [Tooltip("Properties to smooth. Any value not selected will become offset with every movement.")] public TransformPropertiesFlag SmoothedProperties; /// + /// True to apply smoothing in local space for position and rotation. False to use world space. + /// + [Tooltip("True to apply smoothing in local space for position and rotation. False to use world space.")] + public bool UseLocalSpace; + /// /// True to keep non-smoothed properties at their original localspace every tick. A false value will keep the properties in the same world space as they were before each tick. /// [Tooltip("True to keep non-smoothed properties at their original localspace every tick. A false value will keep the properties in the same world space as they were before each tick.")] @@ -46,7 +51,8 @@ public MovementSettings(bool unityReallyNeedsToSupportParameterlessInitializersO AdaptiveInterpolationValue = AdaptiveInterpolationType.Off; InterpolationValue = 2; SmoothedProperties = TransformPropertiesFlag.Everything; + UseLocalSpace = false; SnapNonSmoothedProperties = false; } } -} \ No newline at end of file +} diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.cs index 8c49766b9..4bf63b15c 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.cs @@ -1,9 +1,8 @@ -using FishNet.Managing.Predicting; -using FishNet.Managing.Timing; +using FishNet.Managing.Timing; using FishNet.Object; using GameKit.Dependencies.Utilities; -using Unity.Profiling; using UnityEngine; +using Unity.Profiling; namespace FishNet.Component.Transforming.Beta { @@ -14,13 +13,22 @@ namespace FishNet.Component.Transforming.Beta public class TickSmootherController : IResettable { #region Public. - /// - /// Logic for owner smoothing. - /// - public UniversalTickSmoother UniversalSmoother { get; private set; } + // /// + // /// Logic for owner smoothing. + // /// + // public UniversalTickSmoother UniversalSmoother { get; private set; } #endregion - + #region Private. + + #region Private Profiler Markers + + private static readonly ProfilerMarker _pm_OnUpdate = new ProfilerMarker("TickSmootherController.TimeManager_OnUpdate()"); + private static readonly ProfilerMarker _pm_OnPreTick = new ProfilerMarker("TickSmootherController.TimeManager_OnPreTick()"); + private static readonly ProfilerMarker _pm_OnPostTick = new ProfilerMarker("TickSmootherController.TimeManager_OnPostTick()"); + + #endregion + /// /// private InitializationSettings _initializationSettings = new(); @@ -43,6 +51,10 @@ public class TickSmootherController : IResettable /// private NetworkBehaviour _initializingNetworkBehaviour; /// + /// TickSmoothingManager. + /// + private TickSmoothingManager _tickSmoothingManager; + /// /// Transform which initialized this object. /// private Transform _graphicalTransform; @@ -62,13 +74,13 @@ public class TickSmootherController : IResettable /// True if initialized. /// private bool _isInitialized; - private static readonly ProfilerMarker _pm_OnUpdate = new("TickSmootherController.TimeManager_OnUpdate()"); - private static readonly ProfilerMarker _pm_OnPreTick = new("TickSmootherController.TimeManager_OnPreTick()"); - private static readonly ProfilerMarker _pm_OnPostTick = new("TickSmootherController.TimeManager_OnPostTick()"); #endregion public void Initialize(InitializationSettings initializationSettings, MovementSettings ownerSettings, MovementSettings spectatorSettings) { + _tickSmoothingManager = + initializationSettings.InitializingNetworkBehaviour?.NetworkManager.TickSmoothingManager ?? + InstanceFinder.NetworkManager.TickSmoothingManager; _initializingNetworkBehaviour = initializationSettings.InitializingNetworkBehaviour; _graphicalTransform = initializationSettings.GraphicalTransform; @@ -83,8 +95,12 @@ public void Initialize(InitializationSettings initializationSettings, MovementSe public void OnDestroy() { - ChangeSubscriptions(false); - StoreSmoother(); + var tsm = _tickSmoothingManager; + if (tsm != null) + tsm.Unregister(this); + + // ChangeSubscriptions(false); + // StoreSmoother(); _destroyed = true; _isInitialized = false; } @@ -99,11 +115,15 @@ public void StartSmoother() if (!canStart) return; - RetrieveSmoothers(); - - UniversalSmoother.Initialize(_initializationSettings, _ownerMovementSettings, _spectatorMovementSettings); + // RetrieveSmoothers(); + // + // UniversalSmoother.Initialize(_initializationSettings, _ownerMovementSettings, _spectatorMovementSettings); + // + // UniversalSmoother.StartSmoother(); - UniversalSmoother.StartSmoother(); + var tsm = _tickSmoothingManager; + if (tsm != null) + tsm.Register(this, _initializationSettings, _ownerMovementSettings, _spectatorMovementSettings); bool StartOnline() { @@ -125,13 +145,15 @@ bool StartOffline() public void StopSmoother() { - ChangeSubscriptions(subscribe: false); - + var tsm = _tickSmoothingManager; + if (tsm != null) + tsm.Unregister(this); + if (!_initializedOffline) StopOnline(); - - if (UniversalSmoother != null) - UniversalSmoother.StopSmoother(); + + // if (UniversalSmoother != null) + // UniversalSmoother.StopSmoother(); void StopOnline() { @@ -142,64 +164,64 @@ void StopOnline() // void StopOffline() { } } - public void TimeManager_OnUpdate() - { - using (_pm_OnUpdate.Auto()) - { - UniversalSmoother.OnUpdate(Time.deltaTime); - } - } - - public void TimeManager_OnPreTick() - { - using (_pm_OnPreTick.Auto()) - { - UniversalSmoother.OnPreTick(); - } - } - - /// - /// Called after a tick completes. - /// - public void TimeManager_OnPostTick() - { - using (_pm_OnPostTick.Auto()) - { - if (_timeManager != null) - UniversalSmoother.OnPostTick(_timeManager.LocalTick); - } - } - - private void PredictionManager_OnPostReplicateReplay(uint clientTick, uint serverTick) - { - UniversalSmoother.OnPostReplicateReplay(clientTick); - } - - private void TimeManager_OnRoundTripTimeUpdated(long rttMs) - { - UniversalSmoother.UpdateRealtimeInterpolation(); - } - - /// - /// Stores smoothers if they have value. - /// - private void StoreSmoother() - { - if (UniversalSmoother == null) - return; - - ResettableObjectCaches.Store(UniversalSmoother); - UniversalSmoother = null; - } - - /// - /// Stores current smoothers and retrieves new ones. - /// - private void RetrieveSmoothers() - { - StoreSmoother(); - UniversalSmoother = ResettableObjectCaches.Retrieve(); - } + // public void TimeManager_OnUpdate() + // { + // using (_pm_OnUpdate.Auto()) + // { + // UniversalSmoother.OnUpdate(Time.deltaTime); + // } + // } + // + // public void TimeManager_OnPreTick() + // { + // using (_pm_OnPreTick.Auto()) + // { + // UniversalSmoother.OnPreTick(); + // } + // } + // + // /// + // /// Called after a tick completes. + // /// + // public void TimeManager_OnPostTick() + // { + // using (_pm_OnPostTick.Auto()) + // { + // if (_timeManager != null) + // UniversalSmoother.OnPostTick(_timeManager.LocalTick); + // } + // } + // + // private void PredictionManager_OnPostReplicateReplay(uint clientTick, uint serverTick) + // { + // UniversalSmoother.OnPostReplicateReplay(clientTick); + // } + // + // private void TimeManager_OnRoundTripTimeUpdated(long rttMs) + // { + // UniversalSmoother.UpdateRealtimeInterpolation(); + // } + // + // /// + // /// Stores smoothers if they have value. + // /// + // private void StoreSmoother() + // { + // if (UniversalSmoother == null) + // return; + // + // ResettableObjectCaches.Store(UniversalSmoother); + // UniversalSmoother = null; + // } + // + // /// + // /// Stores current smoothers and retrieves new ones. + // /// + // private void RetrieveSmoothers() + // { + // StoreSmoother(); + // UniversalSmoother = ResettableObjectCaches.Retrieve(); + // } // /// // /// Sets a target transform to follow. @@ -233,58 +255,59 @@ public void SetTimeManager(TimeManager tm) return; // Unsub from current. - ChangeSubscriptions(false); + // ChangeSubscriptions(false); //Sub to newest. _timeManager = tm; - ChangeSubscriptions(true); - } - - /// - /// Changes the subscription to the TimeManager. - /// - private void ChangeSubscriptions(bool subscribe) - { - if (_destroyed) - return; - TimeManager tm = _timeManager; - if (tm == null) - return; - - if (subscribe == _subscribed) - return; - _subscribed = subscribe; - - bool adaptiveIsOff = _ownerMovementSettings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off && _spectatorMovementSettings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off; - - if (subscribe) - { - tm.OnUpdate += TimeManager_OnUpdate; - tm.OnPreTick += TimeManager_OnPreTick; - tm.OnPostTick += TimeManager_OnPostTick; - - if (!adaptiveIsOff) - { - tm.OnRoundTripTimeUpdated += TimeManager_OnRoundTripTimeUpdated; - PredictionManager pm = tm.NetworkManager.PredictionManager; - pm.OnPostReplicateReplay += PredictionManager_OnPostReplicateReplay; - _subscribedToAdaptiveEvents = true; - } - } - else - { - tm.OnUpdate -= TimeManager_OnUpdate; - tm.OnPreTick -= TimeManager_OnPreTick; - tm.OnPostTick -= TimeManager_OnPostTick; - - if (_subscribedToAdaptiveEvents) - { - tm.OnRoundTripTimeUpdated -= TimeManager_OnRoundTripTimeUpdated; - PredictionManager pm = tm.NetworkManager.PredictionManager; - pm.OnPostReplicateReplay -= PredictionManager_OnPostReplicateReplay; - } - } + // ChangeSubscriptions(true); } - + + + // /// + // /// Changes the subscription to the TimeManager. + // /// + // private void ChangeSubscriptions(bool subscribe) + // { + // if (_destroyed) + // return; + // TimeManager tm = _timeManager; + // if (tm == null) + // return; + // + // if (subscribe == _subscribed) + // return; + // _subscribed = subscribe; + // + // bool adaptiveIsOff = _ownerMovementSettings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off && _spectatorMovementSettings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off; + // + // if (subscribe) + // { + // tm.OnUpdate += TimeManager_OnUpdate; + // tm.OnPreTick += TimeManager_OnPreTick; + // tm.OnPostTick += TimeManager_OnPostTick; + // + // if (!adaptiveIsOff) + // { + // tm.OnRoundTripTimeUpdated += TimeManager_OnRoundTripTimeUpdated; + // PredictionManager pm = tm.NetworkManager.PredictionManager; + // pm.OnPostReplicateReplay += PredictionManager_OnPostReplicateReplay; + // _subscribedToAdaptiveEvents = true; + // } + // } + // else + // { + // tm.OnUpdate -= TimeManager_OnUpdate; + // tm.OnPreTick -= TimeManager_OnPreTick; + // tm.OnPostTick -= TimeManager_OnPostTick; + // + // if (_subscribedToAdaptiveEvents) + // { + // tm.OnRoundTripTimeUpdated -= TimeManager_OnRoundTripTimeUpdated; + // PredictionManager pm = tm.NetworkManager.PredictionManager; + // pm.OnPostReplicateReplay -= PredictionManager_OnPostReplicateReplay; + // } + // } + // } + public void ResetState() { _initializationSettings = default; diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs new file mode 100644 index 000000000..c73f0890e --- /dev/null +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs @@ -0,0 +1,1315 @@ +using System; +using System.Runtime.CompilerServices; +using FishNet.Managing.Timing; +using FishNet.Object; +using FishNet.Object.Prediction; +using FishNet.Utility.Extension; +using GameKit.Dependencies.Utilities; +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine; +using UnityEngine.Jobs; +using UnityEngine.Scripting; + +namespace FishNet.Component.Transforming.Beta +{ + public partial class TickSmoothingManager + { + #region Types. + [Preserve] + public struct TickTransformProperties + { + public readonly uint Tick; + public readonly TransformProperties Properties; + + public TickTransformProperties(uint tick, TransformProperties properties) + { + Tick = tick; + Properties = properties; + } + } + [Preserve] + private struct NullableTransformProperties + { + public readonly byte IsExist; + public readonly TransformProperties Properties; + + public NullableTransformProperties(bool isExist, TransformProperties properties) + { + IsExist = (byte)(isExist ? 1 : 0); + Properties = properties; + } + } + [Preserve] + private struct MoveToTargetPayload + { + public byte executeMask; + public float delta; + + public MoveToTargetPayload(byte executeMask, float delta) + { + this.executeMask = executeMask; + this.delta = delta; + } + } + [Preserve] + private struct UpdateRealtimeInterpolationPayload + { + public byte executeMask; + + public UpdateRealtimeInterpolationPayload(byte executeMask) + { + this.executeMask = executeMask; + } + } + [Preserve] + private struct DiscardExcessiveTransformPropertiesQueuePayload + { + public byte executeMask; + + public DiscardExcessiveTransformPropertiesQueuePayload(byte executeMask) + { + this.executeMask = executeMask; + } + } + [Preserve] + private struct SetMoveRatesPayload + { + public byte executeMask; + public TransformProperties prevValues; + + public SetMoveRatesPayload(byte executeMask, TransformProperties prevValues) + { + this.executeMask = executeMask; + this.prevValues = prevValues; + } + } + [Preserve] + private struct SetMovementMultiplierPayload + { + public byte executeMask; + + public SetMovementMultiplierPayload(byte executeMask) + { + this.executeMask = executeMask; + } + } + [Preserve] + private struct AddTransformPropertiesPayload + { + public byte executeMask; + public TickTransformProperties tickTransformProperties; + + public AddTransformPropertiesPayload(byte executeMask, TickTransformProperties tickTransformProperties) + { + this.executeMask = executeMask; + this.tickTransformProperties = tickTransformProperties; + } + } + [Preserve] + private struct ClearTransformPropertiesQueuePayload + { + public byte executeMask; + + public ClearTransformPropertiesQueuePayload(byte executeMask) + { + this.executeMask = executeMask; + } + } + [Preserve] + private struct ModifyTransformPropertiesPayload + { + public byte executeMask; + public uint clientTick; + public uint firstTick; + + public ModifyTransformPropertiesPayload(byte executeMask, uint clientTick, uint firstTick) + { + this.executeMask = executeMask; + this.clientTick = clientTick; + this.firstTick = firstTick; + } + } + [Preserve] + private struct SnapNonSmoothedPropertiesPayload + { + public byte executeMask; + public TransformProperties goalValues; + + public SnapNonSmoothedPropertiesPayload(byte executeMask, TransformProperties goalValues) + { + this.executeMask = executeMask; + this.goalValues = goalValues; + } + } + [Preserve] + private struct TeleportPayload + { + public byte executeMask; + + public TeleportPayload(byte executeMask) + { + this.executeMask = executeMask; + } + } + #endregion + + #region PreTick. + [BurstCompile] + private struct PreTickMarkJob : IJobParallelFor + { + [ReadOnly] public NativeArray canSmoothMask; + [WriteOnly] public NativeArray preTickedMask; + + [WriteOnly] public NativeArray discardExcessivePayloads; + + public void Execute(int index) + { + discardExcessivePayloads[index] = new DiscardExcessiveTransformPropertiesQueuePayload(0); + + if (canSmoothMask[index] == 0) + return; + + preTickedMask[index] = 1; + discardExcessivePayloads[index] = new DiscardExcessiveTransformPropertiesQueuePayload(1); + } + } + + [BurstCompile] + private struct PreTickCaptureGraphicalJob : IJobParallelForTransform + { + [ReadOnly] public NativeArray canSmoothMask; + [ReadOnly] public NativeArray useOwnerSettingsMask; + [ReadOnly] public NativeArray ownerSettings; + [ReadOnly] public NativeArray spectatorSettings; + [WriteOnly] public NativeArray graphicSnapshot; + + public void Execute(int index, TransformAccess graphicalTransform) + { + if (canSmoothMask[index] == 0) + return; + + byte isOwner = useOwnerSettingsMask[index]; + MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; + + bool useLocalSpace = settings.UseLocalSpace; + graphicSnapshot[index] = GetTransformProperties(graphicalTransform, useLocalSpace); + } + } + #endregion + + #region PostTick + + [BurstCompile] + private struct PostTickCaptureTrackerJob : IJobParallelForTransform + { + [ReadOnly] public NativeArray canSmoothMask; + [ReadOnly] public NativeArray detachOnStartMask; + [ReadOnly] public NativeArray useOwnerSettingsMask; + [ReadOnly] public NativeArray ownerSettings; + [ReadOnly] public NativeArray spectatorSettings; + [ReadOnly] public NativeArray targetSnapshot; + [WriteOnly] public NativeArray trackerSnapshot; + + public void Execute(int index, TransformAccess trackerTransform) + { + if (canSmoothMask[index] == 0) + return; + + bool isDetach = detachOnStartMask[index] != 0; + byte isOwner = useOwnerSettingsMask[index]; + MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; + + bool useLocalSpace = settings.UseLocalSpace; + TransformProperties trackerProperties = GetTrackerTransformProperties(trackerTransform, isDetach, useLocalSpace); + if (useLocalSpace) trackerProperties += targetSnapshot[index]; + trackerSnapshot[index] = trackerProperties; + } + } + [BurstCompile] + private struct PostTickJob : IJobParallelForTransform + { + [ReadOnly] public uint clientTick; + [ReadOnly] public NativeArray canSmoothMask; + [ReadOnly] public NativeArray teleportedTick; + [ReadOnly] public NativeArray preTickedMask; + [ReadOnly] public NativeArray detachOnStartMask; + [ReadOnly] public NativeArray postTickTrackerSnapshot; + [ReadOnly] public NativeArray preTickGraphicSnapshot; + [ReadOnly] public NativeArray useOwnerSettingsMask; + [ReadOnly] public NativeArray ownerSettings; + [ReadOnly] public NativeArray spectatorSettings; + + [WriteOnly] public NativeArray discardExcessivePayloads; + [WriteOnly] public NativeArray snapNonSmoothedPropertiesPayloads; + [WriteOnly] public NativeArray addTransformPropertiesPayloads; + + public void Execute(int index, TransformAccess graphicalTransform) + { + discardExcessivePayloads[index] = new DiscardExcessiveTransformPropertiesQueuePayload(0); + addTransformPropertiesPayloads[index] = new AddTransformPropertiesPayload(0, default); + + if (canSmoothMask[index] == 0) + return; + + if (clientTick <= teleportedTick[index]) + return; + + byte isOwner = useOwnerSettingsMask[index]; + MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; + + bool useLocalSpace = settings.UseLocalSpace; + TransformProperties trackerProps = postTickTrackerSnapshot[index]; + //If preticked then previous transform values are known. + if (preTickedMask[index] != 0) + { + //Only needs to be put to pretick position if not detached. + if (detachOnStartMask[index] == 0) + { + var graphicProps = preTickGraphicSnapshot[index]; + SetTransformProperties(graphicalTransform, graphicProps, useLocalSpace); + } + + TickTransformProperties tickTrackerProps = new TickTransformProperties(clientTick, trackerProps); + discardExcessivePayloads[index] = new DiscardExcessiveTransformPropertiesQueuePayload(1); + snapNonSmoothedPropertiesPayloads[index] = new SnapNonSmoothedPropertiesPayload(0, trackerProps); + addTransformPropertiesPayloads[index] = new AddTransformPropertiesPayload(1, tickTrackerProps); + } + //If did not pretick then the only thing we can do is snap to instantiated values. + else + { + //Only set to position if not to detach. + if (detachOnStartMask[index] == 0) + { + SetTransformProperties(graphicalTransform, trackerProps, useLocalSpace); + } + } + } + } + #endregion + + #region PostReplicateReplay + [BurstCompile] + private struct PostReplicateReplayJob : IJobParallelFor + { + [ReadOnly] public uint clientTick; + [ReadOnly] public NativeArray teleportedTick; + [ReadOnly] public NativeArray objectReconcilingMask; + + public StripedRingQueue transformProperties; + [WriteOnly] public NativeArray modifyTransformPropertiesPayloads; + + public void Execute(int index) + { + modifyTransformPropertiesPayloads[index] = new ModifyTransformPropertiesPayload(0, default, default); + if (objectReconcilingMask[index] == 0) + return; + + if (transformProperties.GetCount(index) == 0) + return; + if (clientTick <= teleportedTick[index]) + return; + uint firstTick = transformProperties.Peek(index).Tick; + //Already in motion to first entry, or first entry passed tick. + if (clientTick <= firstTick) + return; + + modifyTransformPropertiesPayloads[index] = new ModifyTransformPropertiesPayload(1, clientTick, firstTick); + } + } + #endregion + + #region RoundTripTimeUpdated + [BurstCompile] + private struct RoundTripTimeUpdatedJob : IJobParallelFor + { + [ReadOnly] public NativeArray useOwnerSettingsMask; + [ReadOnly] public NativeArray ownerSettings; + [ReadOnly] public NativeArray spectatorSettings; + + [WriteOnly] public NativeArray updateRealtimeInterpolationPayloads; + + public void Execute(int index) + { + updateRealtimeInterpolationPayloads[index] = new UpdateRealtimeInterpolationPayload(0); + + byte isOwner = useOwnerSettingsMask[index]; + MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; + + // Update RTT only if Adaptive Interpolation is not Off + if (GetUseAdaptiveInterpolation(settings)) + updateRealtimeInterpolationPayloads[index] = new UpdateRealtimeInterpolationPayload(1); + } + + private static bool GetUseAdaptiveInterpolation(in MovementSettings settings) + { + if (settings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off) + return false; + + return true; + } + } + #endregion + + #region Update + [BurstCompile] + private struct UpdateJob : IJobParallelFor + { + [ReadOnly] public NativeArray canSmoothMask; + [ReadOnly] public float deltaTime; + + [WriteOnly] public NativeArray moveToTargetPayloads; + + public void Execute(int index) + { + moveToTargetPayloads[index] = new MoveToTargetPayload(0, default); + + if (canSmoothMask[index] == 0) + return; + + moveToTargetPayloads[index] = new MoveToTargetPayload(1, deltaTime); + } + } + #endregion + + #region Methods. + [BurstCompile] + private struct CaptureLocalTargetJob : IJobParallelForTransform + { + [ReadOnly] public NativeArray canSmoothMask; + [ReadOnly] public NativeArray useOwnerSettingsMask; + [ReadOnly] public NativeArray ownerSettings; + [ReadOnly] public NativeArray spectatorSettings; + [WriteOnly] public NativeArray targetSnapshot; + + public void Execute(int index, TransformAccess targetTransform) + { + if (canSmoothMask[index] == 0) + return; + + byte isOwner = useOwnerSettingsMask[index]; + MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; + + bool useLocalSpace = settings.UseLocalSpace; + if (!useLocalSpace) return; + + targetSnapshot[index] = GetTransformProperties(targetTransform, true); + } + } + + [BurstCompile] + private struct MoveToTargetJob : IJobParallelForTransform + { + public NativeArray jobPayloads; + + [ReadOnly] public NativeArray useOwnerSettingsMask; + [ReadOnly] public NativeArray ownerSettings; + [ReadOnly] public NativeArray spectatorSettings; + + [ReadOnly] public NativeArray realTimeInterpolations; + [ReadOnly] public NativeArray moveImmediatelyMask; + [ReadOnly] public float tickDelta; + + public NativeArray isMoving; + public NativeArray movementMultipliers; + + public StripedRingQueue transformProperties; + public NativeArray moveRates; + + public NativeArray setMoveRatesPayloads; + public NativeArray setMovementMultiplierPayloads; + public NativeArray clearTransformPropertiesQueuePayloads; + + public void Execute(int index, TransformAccess graphicalTransform) + { + MoveToTarget( + index, + graphicalTransform, + ref jobPayloads, + ref useOwnerSettingsMask, + ref ownerSettings, + ref spectatorSettings, + ref realTimeInterpolations, + ref moveImmediatelyMask, + tickDelta, + ref isMoving, + ref movementMultipliers, + ref transformProperties, + ref moveRates, + ref setMoveRatesPayloads, + ref setMovementMultiplierPayloads, + ref clearTransformPropertiesQueuePayloads); + } + + /// + /// Moves transform to target values. + /// + public static void MoveToTarget( + int index, + TransformAccess graphicalTransform, + ref NativeArray jobPayloads, + ref NativeArray useOwnerSettingsMask, + ref NativeArray ownerSettings, + ref NativeArray spectatorSettings, + ref NativeArray realTimeInterpolations, + ref NativeArray moveImmediatelyMask, + float tickDelta, + ref NativeArray isMoving, + ref NativeArray movementMultipliers, + ref StripedRingQueue transformProperties, + ref NativeArray moveRates, + ref NativeArray setMoveRatesPayloads, + ref NativeArray setMovementMultiplierPayloads, + ref NativeArray clearTransformPropertiesQueuePayloads) + { + MoveToTargetPayload jobPayload = jobPayloads[index]; + if (jobPayload.executeMask == 0) + return; + jobPayloads[index] = new MoveToTargetPayload(0, default); + + // We only need the delta once, then clear payload for this frame. + float remainingDelta = jobPayload.delta; + + byte isOwner = useOwnerSettingsMask[index]; + MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; + bool useLocalSpace = settings.UseLocalSpace; + + while (remainingDelta > 0f) + { + int tpCount = transformProperties.GetCount(index); + + // No data in queue. + if (tpCount == 0) + return; + + byte realtimeInterpolation = realTimeInterpolations[index]; + if (moveImmediatelyMask[index] != 0) + { + isMoving[index] = 1; + } + else + { + //Enough in buffer to move. + if (tpCount >= realtimeInterpolation) + { + isMoving[index] = 1; + } + else if (isMoving[index] == 0) + { + return; + } + /* If buffer is considerably under goal then halt + * movement. This will allow the buffer to grow. */ + else if (tpCount - realtimeInterpolation < -4) + { + isMoving[index] = 0; + return; + } + } + + TickTransformProperties ttp = transformProperties.Peek(index); + TransformPropertiesFlag smoothedProperties = settings.SmoothedProperties; + + MoveRates moveRatesValue = moveRates[index]; + float movementMultiplier = movementMultipliers[index]; + + moveRatesValue.Move( + graphicalTransform, + ttp.Properties, + smoothedProperties, + remainingDelta * movementMultiplier, + useWorldSpace: !useLocalSpace); + moveRates[index] = moveRatesValue; + + float tRemaining = moveRatesValue.TimeRemaining; + + //if TimeRemaining is <= 0f then transform is at goal. Grab a new goal if possible. + if (tRemaining <= 0f) + { + //Dequeue current entry and if there's another call a move on it. + transformProperties.TryDequeue(index, out _); + + //If there are entries left then setup for the next. + if (transformProperties.GetCount(index) > 0) + { + setMoveRatesPayloads[index] = new SetMoveRatesPayload(1, ttp.Properties); + SetMoveRatesJob.SetMoveRates( + index, + ref setMoveRatesPayloads, + ref useOwnerSettingsMask, + ref ownerSettings, + ref spectatorSettings, + ref transformProperties, + tickDelta, + ref moveRates, + ref setMovementMultiplierPayloads); + + SetMovementMultiplierJob.SetMovementMultiplier( + index, + ref setMovementMultiplierPayloads, + ref transformProperties, + ref realTimeInterpolations, + ref moveImmediatelyMask, + ref movementMultipliers); + + // If there is leftover time, apply it to the next segment in this loop. + if (tRemaining < 0f) + { + remainingDelta = Mathf.Abs(tRemaining); + continue; + } + } + //No remaining, set to snap. + else + { + ClearTransformPropertiesQueueJob.ClearTransformPropertiesQueue( + index, + ref clearTransformPropertiesQueuePayloads, + ref transformProperties, + ref moveRates); + } + } + + // Either we did not finish, or there is no leftover time to consume. + break; + } + } + } + + [BurstCompile] + private struct UpdateRealtimeInterpolationJob : IJobParallelFor + { + public NativeArray jobPayloads; + + [ReadOnly] public NativeArray useOwnerSettingsMask; + [ReadOnly] public NativeArray ownerSettings; + [ReadOnly] public NativeArray spectatorSettings; + + [ReadOnly] public float tickDelta; + [ReadOnly] public ushort tickRate; + [ReadOnly] public uint localTick; + [ReadOnly] public long rtt; + [ReadOnly] public bool isServerOnlyStarted; + + public NativeArray realTimeInterpolations; + + public void Execute(int index) + { + UpdateRealtimeInterpolation( + index, + ref jobPayloads, + ref useOwnerSettingsMask, + ref ownerSettings, + ref spectatorSettings, + tickDelta, + tickRate, + localTick, + rtt, + isServerOnlyStarted, + ref realTimeInterpolations); + } + + /// + /// Updates interpolation based on localClient latency when using adaptive interpolation, or uses set value when adaptive interpolation is off. + /// + public static void UpdateRealtimeInterpolation( + int index, + ref NativeArray jobPayloads, + ref NativeArray useOwnerSettingsMask, + ref NativeArray ownerSettings, + ref NativeArray spectatorSettings, + float tickDelta, + ushort tickRate, + uint localTick, + long rtt, + bool isServerOnlyStarted, + ref NativeArray realTimeInterpolations + ) + { + UpdateRealtimeInterpolationPayload jobPayload = jobPayloads[index]; + if (jobPayload.executeMask == 0) + return; + jobPayloads[index] = new UpdateRealtimeInterpolationPayload(0); + + byte isOwner = useOwnerSettingsMask[index]; + MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; + + /* If not networked, server is started, or if not + * using adaptive interpolation then use + * flat interpolation.*/ + if (!GetUseAdaptiveInterpolation(settings, isServerOnlyStarted)) + { + realTimeInterpolations[index] = settings.InterpolationValue; + return; + } + + /* If here then adaptive interpolation is being calculated. */ + + //Calculate roughly what client state tick would be. + //This should never be the case; this is a precautionary against underflow. + if (localTick == TimeManager.UNSET_TICK) + return; + + //Ensure at least 1 tick. + uint rttTicks = TimeManager.TimeToTicks(rtt, tickDelta) + 1; + + uint clientStateTick = localTick - rttTicks; + float interpolation = localTick - clientStateTick; + + //Minimum interpolation is that of adaptive interpolation level. + interpolation += (byte)settings.AdaptiveInterpolationValue; + + //Ensure interpolation is not more than a second. + if (interpolation > tickRate) + interpolation = tickRate; + else if (interpolation > byte.MaxValue) + interpolation = byte.MaxValue; + + /* Only update realtime interpolation if it changed more than 1 + * tick. This is to prevent excessive changing of interpolation value, which + * could result in noticeable speed ups/slow downs given movement multiplier + * may change when buffer is too full or short. */ + float realtimeInterpolation = realTimeInterpolations[index]; + if (realtimeInterpolation == 0 || math.abs(realtimeInterpolation - interpolation) > 1) + realTimeInterpolations[index] = (byte)math.ceil(interpolation); + } + + private static bool GetUseAdaptiveInterpolation(in MovementSettings settings, in bool isServerOnlyStarted) + { + if (settings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off || isServerOnlyStarted) + return false; + + return true; + } + } + + [BurstCompile] + private struct DiscardExcessiveTransformPropertiesQueueJob : IJobParallelFor + { + public NativeArray jobPayloads; + + [ReadOnly] public NativeArray realTimeInterpolations; + [ReadOnly] public int requiredQueuedOverInterpolation; + + public StripedRingQueue transformProperties; + [WriteOnly] public NativeArray setMoveRatesPayloads; + + public void Execute(int index) + { + DiscardExcessiveTransformPropertiesQueue( + index, + ref jobPayloads, + ref realTimeInterpolations, + requiredQueuedOverInterpolation, + ref transformProperties, + ref setMoveRatesPayloads); + } + + /// + /// Discards datas over interpolation limit from movement queue. + /// + public static void DiscardExcessiveTransformPropertiesQueue( + int index, + ref NativeArray jobPayloads, + ref NativeArray realTimeInterpolations, + int requiredQueuedOverInterpolation, + ref StripedRingQueue transformProperties, + ref NativeArray setMoveRatesPayloads) + { + setMoveRatesPayloads[index] = new SetMoveRatesPayload(0, default); + + DiscardExcessiveTransformPropertiesQueuePayload jobPayload = jobPayloads[index]; + if (jobPayload.executeMask == 0) + return; + jobPayloads[index] = new DiscardExcessiveTransformPropertiesQueuePayload(0); + + int propertiesCount = transformProperties.GetCount(index); + int realtimeInterpolationValue = realTimeInterpolations[index]; + int dequeueCount = propertiesCount - (realtimeInterpolationValue + requiredQueuedOverInterpolation); + + // If there are entries to dequeue. + if (dequeueCount > 0) + { + TickTransformProperties ttp; + transformProperties.DequeueUpTo(index, dequeueCount, out ttp); + + var nextValues = ttp.Properties; + setMoveRatesPayloads[index] = new SetMoveRatesPayload(1, nextValues); + } + } + } + + [BurstCompile] + private struct SetMoveRatesJob : IJobParallelFor + { + public NativeArray jobPayloads; + + [ReadOnly] public NativeArray useOwnerSettingsMask; + [ReadOnly] public NativeArray ownerSettings; + [ReadOnly] public NativeArray spectatorSettings; + + public StripedRingQueue transformProperties; + [ReadOnly] public float tickDelta; + + [WriteOnly] public NativeArray moveRates; + [WriteOnly] public NativeArray setMovementMultiplierPayloads; + + public void Execute(int index) + { + SetMoveRates( + index, + ref jobPayloads, + ref useOwnerSettingsMask, + ref ownerSettings, + ref spectatorSettings, + ref transformProperties, + tickDelta, + ref moveRates, + ref setMovementMultiplierPayloads); + } + + /// + /// Sets new rates based on next entries in transformProperties queue, against a supplied TransformProperties. + /// + public static void SetMoveRates( + int index, + ref NativeArray jobPayloads, + ref NativeArray useOwnerSettingsMask, + ref NativeArray ownerSettings, + ref NativeArray spectatorSettings, + ref StripedRingQueue transformProperties, + float tickDelta, + ref NativeArray moveRates, + ref NativeArray setMovementMultiplierPayloads) + { + moveRates[index] = new(MoveRates.UNSET_VALUE); + setMovementMultiplierPayloads[index] = new SetMovementMultiplierPayload(0); + + SetMoveRatesPayload jobPayload = jobPayloads[index]; + if (jobPayload.executeMask == 0 || transformProperties.GetCount(index) == 0) + return; + jobPayloads[index] = new SetMoveRatesPayload(0, default); + + TransformProperties prevValues = jobPayload.prevValues; + TransformProperties nextValues = transformProperties.Peek(index).Properties; + float duration = tickDelta; + + byte isOwner = useOwnerSettingsMask[index]; + MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; + float teleportThreshold = settings.EnableTeleport + ? settings.TeleportThreshold * settings.TeleportThreshold + : MoveRates.UNSET_VALUE; + + MoveRates moveRatesValue = MoveRates.GetMoveRates(prevValues, nextValues, duration, teleportThreshold); + moveRatesValue.TimeRemaining = duration; + moveRates[index] = moveRatesValue; + setMovementMultiplierPayloads[index] = new SetMovementMultiplierPayload(1); + } + } + + [BurstCompile] + private struct SetMovementMultiplierJob : IJobParallelFor + { + public NativeArray jobPayloads; + + public StripedRingQueue transformProperties; + [ReadOnly] public NativeArray realTimeInterpolations; + [ReadOnly] public NativeArray moveImmediatelyMask; + + public NativeArray movementMultipliers; + + public void Execute(int index) + { + SetMovementMultiplier( + index, + ref jobPayloads, + ref transformProperties, + ref realTimeInterpolations, + ref moveImmediatelyMask, + ref movementMultipliers); + } + + public static void SetMovementMultiplier( + int index, + ref NativeArray jobPayloads, + ref StripedRingQueue transformProperties, + ref NativeArray realTimeInterpolations, + ref NativeArray moveImmediatelyMask, + ref NativeArray movementMultipliers) + { + SetMovementMultiplierPayload jobPayload = jobPayloads[index]; + if (jobPayload.executeMask == 0) + return; + jobPayloads[index] = new SetMovementMultiplierPayload(0); + + byte moveImmediately = moveImmediatelyMask[index]; + byte realTimeInterpolation = realTimeInterpolations[index]; + float movementMultiplier = movementMultipliers[index]; + int propertiesCount = transformProperties.GetCount(index); + if (moveImmediately != 0) + { + float percent = math.unlerp(0, realTimeInterpolation, propertiesCount); + movementMultiplier = percent; + + movementMultiplier = math.clamp(movementMultiplier, 0.5f, 1.05f); + } + // For the time being, not moving immediately uses these multiplier calculations. + else + { + /* If there's more in queue than interpolation then begin to move faster based on overage. + * Move 5% faster for every overage. */ + int overInterpolation = propertiesCount - realTimeInterpolation; + // If needs to be adjusted. + if (overInterpolation != 0) + { + movementMultiplier += 0.015f * overInterpolation; + } + // If does not need to be adjusted. + else + { + // If interpolation is 1 then slow down just barely to accomodate for frame delta variance. + if (realTimeInterpolation == 1) + movementMultiplier = 1f; + } + + movementMultiplier = math.clamp(movementMultiplier, 0.95f, 1.05f); + } + + movementMultipliers[index] = movementMultiplier; + } + } + + [BurstCompile] + private struct AddTransformPropertiesJob : IJobParallelForTransform + { + public NativeArray jobPayloads; + + [ReadOnly] public NativeArray useOwnerSettingsMask; + [ReadOnly] public NativeArray ownerSettings; + [ReadOnly] public NativeArray spectatorSettings; + public StripedRingQueue transformProperties; + [WriteOnly] public NativeArray setMoveRatesPayloads; + + public void Execute(int index, TransformAccess graphicalTransform) + { + AddTransformProperties( + index, + graphicalTransform, + ref jobPayloads, + ref useOwnerSettingsMask, + ref ownerSettings, + ref spectatorSettings, + ref transformProperties, + ref setMoveRatesPayloads); + } + + /// + /// Adds a new transform properties and sets move rates if needed. + /// + public static void AddTransformProperties( + int index, + TransformAccess graphicalTransform, + ref NativeArray jobPayloads, + ref NativeArray useOwnerSettingsMask, + ref NativeArray ownerSettings, + ref NativeArray spectatorSettings, + ref StripedRingQueue transformProperties, + ref NativeArray setMoveRatesPayloads) + { + setMoveRatesPayloads[index] = new SetMoveRatesPayload(0, default); + + AddTransformPropertiesPayload jobPayload = jobPayloads[index]; + if (jobPayload.executeMask == 0) + return; + jobPayloads[index] = new AddTransformPropertiesPayload(0, default); + + transformProperties.Enqueue(index, jobPayload.tickTransformProperties); + + //If first entry then set move rates. + if (transformProperties.GetCount(index) == 1) + { + byte isOwner = useOwnerSettingsMask[index]; + MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; + + bool useLocalSpace = settings.UseLocalSpace; + TransformProperties gfxProperties = GetTransformProperties(graphicalTransform, useLocalSpace); + setMoveRatesPayloads[index] = new SetMoveRatesPayload(1, gfxProperties); + } + } + } + + [BurstCompile] + private struct ClearTransformPropertiesQueueJob : IJobParallelFor + { + public NativeArray jobPayloads; + public StripedRingQueue transformProperties; + [WriteOnly] public NativeArray moveRates; + + public void Execute(int index) + { + ClearTransformPropertiesQueue( + index, + ref jobPayloads, + ref transformProperties, + ref moveRates); + } + + /// + /// Clears the pending movement queue. + /// + public static void ClearTransformPropertiesQueue( + int index, + ref NativeArray jobPayloads, + ref StripedRingQueue transformProperties, + ref NativeArray moveRates) + { + ClearTransformPropertiesQueuePayload jobPayload = jobPayloads[index]; + if (jobPayload.executeMask == 0) + return; + jobPayloads[index] = new ClearTransformPropertiesQueuePayload(0); + + transformProperties.Clear(index); + //Also unset move rates since there is no more queue. + moveRates[index] = new MoveRates(MoveRates.UNSET_VALUE); + } + } + + [BurstCompile] + private struct ModifyTransformPropertiesJob : IJobParallelForTransform + { + public NativeArray jobPayloads; + [ReadOnly] public NativeArray detachOnStartMask; + [ReadOnly] public NativeArray useOwnerSettingsMask; + [ReadOnly] public NativeArray ownerSettings; + [ReadOnly] public NativeArray spectatorSettings; + [ReadOnly] public NativeArray targetSnapshot; + + public StripedRingQueue transformProperties; + + public void Execute(int index, TransformAccess trackerTransform) + { + ModifyTransformProperties( + index, + trackerTransform, + ref jobPayloads, + ref detachOnStartMask, + ref useOwnerSettingsMask, + ref ownerSettings, + ref spectatorSettings, + ref targetSnapshot, + ref transformProperties); + } + + /// + /// Modifies a transform property for a tick. This does not error check for empty collections. + /// firstTick - First tick in the queue. If 0 this will be looked up. + /// + public static void ModifyTransformProperties( + int index, + TransformAccess trackerTransform, + ref NativeArray jobPayloads, + ref NativeArray detachOnStartMask, + ref NativeArray useOwnerSettingsMask, + ref NativeArray ownerSettings, + ref NativeArray spectatorSettings, + ref NativeArray targetSnapshot, + ref StripedRingQueue transformProperties) + { + ModifyTransformPropertiesPayload jobPayload = jobPayloads[index]; + if (jobPayload.executeMask == 0) + return; + jobPayloads[index] = new ModifyTransformPropertiesPayload(0, default, default); + + int queueCount = transformProperties.GetCount(index); + uint tick = jobPayload.clientTick; + /*Ticks will always be added incremental by 1 so it's safe to jump ahead the difference + * of tick and firstTick. */ + int tickIndex = (int)(tick - jobPayload.firstTick); + //Replace with new data. + if (tickIndex < queueCount) + { + TickTransformProperties tickTransformProperties = transformProperties[index, tickIndex]; + if (tick != tickTransformProperties.Tick) + { + //Should not be possible. + } + else + { + bool isDetach = detachOnStartMask[index] != 0; + byte isOwner = useOwnerSettingsMask[index]; + MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; + + bool useLocalSpace = settings.UseLocalSpace; + TransformProperties newProperties = GetTrackerTransformProperties(trackerTransform, isDetach, useLocalSpace); + if (useLocalSpace) newProperties += targetSnapshot[index]; + /* Adjust transformProperties to ease into any corrections. + * The corrected value is used the more the index is to the end + * of the queue. */ + /* We want to be fully eased in by the last entry of the queue. */ + + int lastPossibleIndex = queueCount - 1; + int adjustedQueueCount = lastPossibleIndex - 1; + if (adjustedQueueCount < 1) + adjustedQueueCount = 1; + float easePercent = (float)tickIndex / adjustedQueueCount; + + //If easing. + if (easePercent < 1f) + { + if (easePercent < 1f) + easePercent = (float)Math.Pow(easePercent, adjustedQueueCount - tickIndex); + + TransformProperties oldProperties = tickTransformProperties.Properties; + newProperties.Position = Vector3.Lerp(oldProperties.Position, newProperties.Position, easePercent); + newProperties.Rotation = Quaternion.Lerp(oldProperties.Rotation, newProperties.Rotation, easePercent); + newProperties.Scale = Vector3.Lerp(oldProperties.Scale, newProperties.Scale, easePercent); + } + + transformProperties[index, tickIndex] = new TickTransformProperties(tick, newProperties); + } + } + else + { + //This should never happen. + } + } + } + + [BurstCompile] + private struct SnapNonSmoothedPropertiesJob : IJobParallelForTransform + { + public NativeArray jobPayloads; + + [ReadOnly] public NativeArray useOwnerSettingsMask; + [ReadOnly] public NativeArray ownerSettings; + [ReadOnly] public NativeArray spectatorSettings; + + public void Execute(int index, TransformAccess graphicalTransform) + { + SnapNonSmoothedProperties( + index, + graphicalTransform, + ref jobPayloads, + ref useOwnerSettingsMask, + ref ownerSettings, + ref spectatorSettings); + } + + /// + /// Snaps non-smoothed properties to original positoin if setting is enabled. + /// + public static void SnapNonSmoothedProperties( + int index, + TransformAccess graphicalTransform, + ref NativeArray jobPayloads, + ref NativeArray useOwnerSettingsMask, + ref NativeArray ownerSettings, + ref NativeArray spectatorSettings) + { + SnapNonSmoothedPropertiesPayload jobPayload = jobPayloads[index]; + if (jobPayload.executeMask == 0) + return; + jobPayloads[index] = new SnapNonSmoothedPropertiesPayload(0, default); + + byte isOwner = useOwnerSettingsMask[index]; + MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; + bool useLocalSpace = settings.UseLocalSpace; + + bool snapNonSmoothedProperties = settings.SnapNonSmoothedProperties; + //Feature is not enabled. + if (!snapNonSmoothedProperties) + return; + + TransformPropertiesFlag smoothedProperties = settings.SmoothedProperties; + + //Everything is smoothed. + if (smoothedProperties == TransformPropertiesFlag.Everything) + return; + + TransformProperties goalValues = jobPayload.goalValues; + + if (!smoothedProperties.FastContains(TransformPropertiesFlag.Position)) + { + if (useLocalSpace) + graphicalTransform.localPosition = goalValues.Position; + else + graphicalTransform.position = goalValues.Position; + } + + if (!smoothedProperties.FastContains(TransformPropertiesFlag.Rotation)) + { + if (useLocalSpace) + graphicalTransform.localRotation = goalValues.Rotation; + else + graphicalTransform.rotation = goalValues.Rotation; + } + + if (!smoothedProperties.FastContains(TransformPropertiesFlag.Scale)) + graphicalTransform.localScale = goalValues.Scale; + } + } + + [BurstCompile] + private struct TeleportJob : IJobParallelForTransform + { + public NativeArray jobPayloads; + + [ReadOnly] public NativeArray useOwnerSettingsMask; + [ReadOnly] public NativeArray ownerSettings; + [ReadOnly] public NativeArray spectatorSettings; + [ReadOnly] public NativeArray preTickTrackerSnapshot; + [ReadOnly] public uint localTick; + + public StripedRingQueue transformProperties; + public NativeArray clearTransformPropertiesQueuePayloads; + [WriteOnly] public NativeArray moveRates; + [WriteOnly] public NativeArray teleportedTick; + + public void Execute(int index, TransformAccess graphicalTransform) + { + Teleport( + index, + graphicalTransform, + ref jobPayloads, + ref useOwnerSettingsMask, + ref ownerSettings, + ref spectatorSettings, + ref preTickTrackerSnapshot, + localTick, + ref transformProperties, + ref clearTransformPropertiesQueuePayloads, + ref moveRates, + ref teleportedTick); + } + + /// + /// Snaps non-smoothed properties to original positoin if setting is enabled. + /// + public static void Teleport( + int index, + TransformAccess graphicalTransform, + ref NativeArray jobPayloads, + ref NativeArray useOwnerSettingsMask, + ref NativeArray ownerSettings, + ref NativeArray spectatorSettings, + ref NativeArray preTickTrackerSnapshot, + uint localTick, + ref StripedRingQueue transformProperties, + ref NativeArray clearTransformPropertiesQueuePayloads, + ref NativeArray moveRates, + ref NativeArray teleportedTick) + { + TeleportPayload jobPayload = jobPayloads[index]; + if (jobPayload.executeMask == 0) + return; + jobPayloads[index] = new TeleportPayload(0); + + byte isOwner = useOwnerSettingsMask[index]; + MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; + bool useLocalSpace = settings.UseLocalSpace; + + AdaptiveInterpolationType adaptiveInterpolationValue = settings.AdaptiveInterpolationValue; + + //If using adaptive interpolation then set the tick which was teleported. + if (adaptiveInterpolationValue != AdaptiveInterpolationType.Off) + teleportedTick[index] = localTick; + + clearTransformPropertiesQueuePayloads[index] = new ClearTransformPropertiesQueuePayload(1); + ClearTransformPropertiesQueueJob.ClearTransformPropertiesQueue( + index, + ref clearTransformPropertiesQueuePayloads, + ref transformProperties, + ref moveRates); + + TransformProperties trackerProps = preTickTrackerSnapshot[index]; + SetTransformProperties(graphicalTransform, trackerProps, useLocalSpace); + } + } + + /// + /// Gets properties for the graphical transform in the desired space. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static TransformProperties GetTransformProperties(TransformAccess transform, bool useLocalSpace) + { + if (useLocalSpace) + return transform.GetLocalProperties(); + else + return transform.GetWorldProperties(); + } + + /// + /// Gets properties for the tracker transform in the desired space, accounting for detach. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static TransformProperties GetTrackerTransformProperties(TransformAccess trackerTransform, bool isDetach, bool useLocalSpace) + { + /* Return lossyScale if graphical is not attached. Otherwise, + * graphical should retain the tracker localScale so it changes + * with root. */ + + Vector3 scale = isDetach ? ExtractLossyScale(trackerTransform) : trackerTransform.localScale; + Vector3 pos; + Quaternion rot; + + if (useLocalSpace) + trackerTransform.GetCorrectLocalPositionAndRotation(out pos, out rot); + else + trackerTransform.GetPositionAndRotation(out pos, out rot); + + return new TransformProperties(pos, rot, scale); + } + + /// + /// Applies properties to a transform using the desired space for position/rotation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void SetTransformProperties(TransformAccess transform, in TransformProperties properties, bool useLocalSpace) + { + if (useLocalSpace) + transform.SetLocalProperties(properties); + else + transform.SetWorldProperties(properties); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector3 ExtractLossyScale(TransformAccess transform) + { + var m = transform.localToWorldMatrix; + var c0 = new float3(m.m00, m.m10, m.m20); + var c1 = new float3(m.m01, m.m11, m.m21); + var c2 = new float3(m.m02, m.m12, m.m22); + return new Vector3(math.length(c0), math.length(c1), math.length(c2)); + } + + #endregion + + + public static TransformProperties GetGraphicalWorldProperties(TransformAccess graphicalTransform) + { + return graphicalTransform.GetWorldProperties(); + } + + public static TransformProperties GetTrackerWorldProperties(TransformAccess trackerTransform, bool isDetach) + { + /* Return lossyScale if graphical is not attached. Otherwise, + * graphical should retain the tracker localScale so it changes + * with root. */ + + trackerTransform.GetPositionAndRotation(out var pos, out var rot); + Vector3 scl; + if (isDetach) + { + var m = trackerTransform.localToWorldMatrix; + var c0 = new float3(m.m00, m.m10, m.m20); + var c1 = new float3(m.m01, m.m11, m.m21); + var c2 = new float3(m.m02, m.m12, m.m22); + scl = new Vector3(math.length(c0), math.length(c1), math.length(c2)); + } + else scl = trackerTransform.localScale; + return new TransformProperties(pos, rot, scl); + } + } +} \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs new file mode 100644 index 000000000..92603ce96 --- /dev/null +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs @@ -0,0 +1,1336 @@ +using System.Collections.Generic; +using FishNet.Managing; +using FishNet.Managing.Predicting; +using FishNet.Managing.Timing; +using FishNet.Object; +using FishNet.Object.Prediction; +using FishNet.Transporting; +using FishNet.Utility.Extension; +using GameKit.Dependencies.Utilities; +using Unity.Collections; +using Unity.Jobs; +using Unity.Jobs.LowLevel.Unsafe; +using Unity.Profiling; +using UnityEngine; +using UnityEngine.Jobs; + +namespace FishNet.Component.Transforming.Beta +{ + public partial class TickSmoothingManager : MonoBehaviour + { + #region Private. + + #region Private Profiler Markers + private static readonly ProfilerMarker _pm_ClientManager_OnClientConnectionState = new("TickSmoothingManager.Client_OnClientConnectionState"); + private static readonly ProfilerMarker _pm_OnUpdate = new("TickSmoothingManager.TimeManager_OnUpdate()"); + private static readonly ProfilerMarker _pm_OnPreTick = new("TickSmoothingManager.TimeManager_OnPreTick()"); + private static readonly ProfilerMarker _pm_OnPostTick = new("TickSmoothingManager.TimeManager_OnPostTick()"); + private static readonly ProfilerMarker _pm_Prediction_OnPostReplicateReplay = new("TickSmoothingManager.Prediction_OnPostReplicateReplay()"); + private static readonly ProfilerMarker _pm_TimeManager_OnRoundTripTimeUpdated = new("TickSmoothingManager.TimeManager_OnRoundTripTimeUpdated()"); + private static readonly ProfilerMarker _pm_MoveToTarget = new("TickSmoothingManager.MoveToTarget()"); + private static readonly ProfilerMarker _pm_ScheduleUpdateRealtimeInterpolation = new("TickSmoothingManager.UpdateRealtimeInterpolation()"); + private static readonly ProfilerMarker _pm_ScheduleDiscardExcessiveTransformPropertiesQueue = new("TickSmoothingManager.DiscardExcessiveTransformPropertiesQueue()"); + private static readonly ProfilerMarker _pm_ScheduleSetMoveRates = new("TickSmoothingManager.SetMoveRates()"); + private static readonly ProfilerMarker _pm_ScheduleSetMovementMultiplier = new("TickSmoothingManager.SetMovementMultiplier()"); + private static readonly ProfilerMarker _pm_ScheduleAddTransformProperties = new("TickSmoothingManager.ScheduleAddTransformProperties()"); + private static readonly ProfilerMarker _pm_ScheduleClearTransformPropertiesQueue = new("TickSmoothingManager.ScheduleClearTransformPropertiesQueue()"); + private static readonly ProfilerMarker _pm_ScheduleModifyTransformProperties = new("TickSmoothingManager.ScheduleModifyTransformProperties()"); + private static readonly ProfilerMarker _pm_ScheduleSnapNonSmoothedProperties = new("TickSmoothingManager.ScheduleSnapNonSmoothedProperties()"); + private static readonly ProfilerMarker _pm_ScheduleTeleport = new("TickSmoothingManager.ScheduleTeleport()"); + private static readonly ProfilerMarker _pm_Register = new("TickSmoothingManager.Register()"); + private static readonly ProfilerMarker _pm_Unregister = new("TickSmoothingManager.Unregister()"); + #endregion + + #region Const. + /// + /// Maximum allowed entries. + /// + private const int MAXIMUM_QUEUED = 256; + /// + /// Maximum allowed entries to be queued over the interpolation amount. + /// + private const int REQUIRED_QUEUED_OVER_INTERPOLATION = 3; + #endregion + + /// + /// NetworkManager on the same object as this script. + /// + private NetworkManager _networkManager; + /// + /// TimeManager on the same object as this script. + /// + private TimeManager _timeManager; + /// + /// PredictionManager on the same object as this script. + /// + private PredictionManager _predictionManager; + + /// + /// TrackerTransformsPool. + /// + private readonly Stack _trackerTransformsPool = new(); + /// + /// TrackerTransformsPoolHolder. + /// + private Transform _trackerTransformsPoolHolder; + + /// + /// TickSmootherController to index lookup. + /// + private readonly Dictionary _lookup = new(); + /// + /// Index to TickSmootherController and InitializationSettings lookup. + /// + private readonly List _indexToSmoother = new(); + /// + /// Index to TickSmootherController and NetworkBehaviours lookup. + /// + private readonly List _indexToNetworkBehaviour = new(); + + /// + /// Index to MoveRate lookup. + /// How quickly to move towards goal values. + /// + private NativeList _moveRates; + /// + /// Index to Owner MovementSettings lookup. + /// Settings to use for owners. + /// + private NativeList _ownerSettings; + /// + /// Index to Spectator MovementSettings lookup. + /// Settings to use for spectators. + /// + private NativeList _spectatorSettings; + + /// + /// Index to PreTickedMask lookup. + /// True if a pretick occurred since last postTick. + /// + private NativeList _preTickedMask; + /// + /// Index to MoveImmediatelyMask lookup. + /// True to begin moving soon as movement data becomes available. Movement will ease in until at interpolation value. False to prevent movement until movement data count meet interpolation. + /// + private NativeList _moveImmediatelyMask; + /// + /// Index to CanSmoothMask lookup. + /// Returns if prediction can be used on this rigidbody. + /// + private NativeList _canSmoothMask; + /// + /// Index to UseOwnerSettingsMask lookup. + /// True if to smooth using owner settings, false for spectator settings. + /// This is only used for performance gains. + /// + private NativeList _useOwnerSettingsMask; + /// + /// Index to ObjectReconcilingMask lookup. + /// + private NativeList _objectReconcilingMask; + /// + /// Index to DetachOnStartMask lookup. + /// True if to detach on smoothing start. + /// + private NativeList _detachOnStartMask; + /// + /// Index to AttachOnStopMask lookup. + /// True if to attach on smoothing stop. + /// + private NativeList _attachOnStopMask; + /// + /// Index to IsMoving lookup. + /// True if moving has started and has not been stopped. + /// + private NativeList _isMoving; + /// + /// Index to TeleportedTick lookup. + /// Last tick this was teleported on. + /// + private NativeList _teleportedTick; + /// + /// Index to RealTimeInterpolation lookup. + /// Current interpolation value, be it a flat value or adaptive. + /// + private NativeList _realTimeInterpolations; + /// + /// Index to MovementMultiplier lookup. + /// Value to multiply movement by. This is used to reduce or increase the rate the movement buffer is consumed. + /// + private NativeList _movementMultipliers; + + /// + /// Index to TransformProperties lookup. + /// TransformProperties to move towards + /// + private StripedRingQueue _transformProperties; + /// + /// Index to PreTick Graphic TransformProperties Snapshot lookup. + /// World values of the graphical after it's been aligned to initialized values in PreTick. + /// + private NativeList _preTickGraphicSnapshot; + /// + /// Index to PreTick Tracker TransformProperties Snapshot lookup. + /// World values of the graphical after it's been aligned to initialized values in PreTick. + /// + private NativeList _postTickTrackerSnapshot; + /// + /// Index to Temp Target TransformProperties Snapshot lookup. + /// + private NativeList _tempTargetSnapshot; + /// + /// Index to OutSnapGraphicWorld lookup. + /// + private NativeList _outSnapGraphicWorld; + /// + /// Index to OutEnqueueTrackerWorld lookup. + /// + private NativeList _outEnqueueTrackerWorld; + /// + /// Index to QueuedTrackerProperties lookup. + /// Properties for the tracker which are queued to be set when the tracker is setup. + /// + private NativeList _queuedTrackerProperties; + + /// + /// Index to MoveToTargetPayloads lookup. + /// + private NativeList _moveToTargetPayloads; + /// + /// Index to UpdateRealtimeInterpolationPayloads lookup. + /// + private NativeList _updateRealtimeInterpolationPayloads; + /// + /// Index to DiscardExcessiveTransformPropertiesQueuePayloads lookup. + /// + private NativeList _discardExcessivePayloads; + /// + /// Index to SetMoveRatesPayloads lookup. + /// + private NativeList _setMoveRatesPayloads; + /// + /// Index to SetMovementMultiplierPayloads lookup. + /// + private NativeList _setMovementMultiplierPayloads; + /// + /// Index to AddTransformPropertiesPayloads lookup. + /// + private NativeList _addTransformPropertiesPayloads; + /// + /// Index to ClearTransformPropertiesQueuePayloads lookup. + /// + private NativeList _clearTransformPropertiesQueuePayloads; + /// + /// Index to ModifyTransformPropertiesPayloads lookup. + /// + private NativeList _modifyTransformPropertiesPayloads; + /// + /// Index to SnapNonSmoothedPropertiesPayloads lookup. + /// + private NativeList _snapNonSmoothedPropertiesPayloads; + /// + /// Index to TeleportPayloads lookup. + /// + private NativeList _teleportPayloads; + + /// + /// Target objects TransformAccessArray. + /// Transform the graphics should follow. + /// + private TransformAccessArray _targetTaa; + /// + /// Graphical objects TransformAccessArray. + /// Cached value of the object to smooth. + /// + private TransformAccessArray _graphicalTaa; + /// + /// Tracker objects TransformAccessArray. + /// Empty gameObject containing a transform which has properties checked after each simulation. + /// If the graphical starts off as nested of targetTransform then this object is created where the graphical object is. + /// Otherwise, this object is placed directly beneath targetTransform. + /// + private TransformAccessArray _trackerTaa; + + /// + /// Subscription to callbacks state. + /// + private bool _subscribed; + #endregion + + /// + /// Initialize once from NetworkManager (pattern mirrors RollbackManager). + /// + internal void InitializeOnce_Internal(NetworkManager manager) + { + _networkManager = manager; + _timeManager = manager.TimeManager; + _predictionManager = manager.PredictionManager; + + if (!_trackerTransformsPoolHolder) + { + _trackerTransformsPoolHolder = new GameObject("Tracker Transforms Pool Holder").transform; + DontDestroyOnLoad(_trackerTransformsPoolHolder.gameObject); + } + + if (!_moveRates.IsCreated) _moveRates = new NativeList(64, Allocator.Persistent); + if (!_ownerSettings.IsCreated) _ownerSettings = new NativeList(64, Allocator.Persistent); + if (!_spectatorSettings.IsCreated) _spectatorSettings = new NativeList(64, Allocator.Persistent); + + if (!_preTickedMask.IsCreated) _preTickedMask = new NativeList(64, Allocator.Persistent); + if (!_canSmoothMask.IsCreated) _canSmoothMask = new NativeList(64, Allocator.Persistent); + if (!_useOwnerSettingsMask.IsCreated) _useOwnerSettingsMask = new NativeList(64, Allocator.Persistent); + if (!_objectReconcilingMask.IsCreated) _objectReconcilingMask = new NativeList(64, Allocator.Persistent); + if (!_detachOnStartMask.IsCreated) _detachOnStartMask = new NativeList(64, Allocator.Persistent); + if (!_attachOnStopMask.IsCreated) _attachOnStopMask = new NativeList(64, Allocator.Persistent); + if (!_moveImmediatelyMask.IsCreated) _moveImmediatelyMask = new NativeList(64, Allocator.Persistent); + if (!_isMoving.IsCreated) _isMoving = new NativeList(64, Allocator.Persistent); + if (!_teleportedTick.IsCreated) _teleportedTick = new NativeList(64, Allocator.Persistent); + if (!_realTimeInterpolations.IsCreated) _realTimeInterpolations = new NativeList(64, Allocator.Persistent); + if (!_movementMultipliers.IsCreated) _movementMultipliers = new NativeList(64, Allocator.Persistent); + + if (!_transformProperties.IsCreated) _transformProperties = new StripedRingQueue(64, MAXIMUM_QUEUED, Allocator.Persistent); + if (!_preTickGraphicSnapshot.IsCreated) _preTickGraphicSnapshot = new NativeList(64, Allocator.Persistent); + if (!_postTickTrackerSnapshot.IsCreated) _postTickTrackerSnapshot = new NativeList(64, Allocator.Persistent); + if (!_tempTargetSnapshot.IsCreated) _tempTargetSnapshot = new NativeList(64, Allocator.Persistent); + if (!_outSnapGraphicWorld.IsCreated) _outSnapGraphicWorld = new NativeList(64, Allocator.Persistent); + if (!_outEnqueueTrackerWorld.IsCreated) _outEnqueueTrackerWorld = new NativeList(64, Allocator.Persistent); + if (!_queuedTrackerProperties.IsCreated) _queuedTrackerProperties = new NativeList(64, Allocator.Persistent); + + if (!_moveToTargetPayloads.IsCreated) _moveToTargetPayloads = new NativeList(64, Allocator.Persistent); + if (!_updateRealtimeInterpolationPayloads.IsCreated) _updateRealtimeInterpolationPayloads = new NativeList(64, Allocator.Persistent); + if (!_discardExcessivePayloads.IsCreated) _discardExcessivePayloads = new NativeList(64, Allocator.Persistent); + if (!_setMoveRatesPayloads.IsCreated) _setMoveRatesPayloads = new NativeList(64, Allocator.Persistent); + if (!_setMovementMultiplierPayloads.IsCreated) _setMovementMultiplierPayloads = new NativeList(64, Allocator.Persistent); + if (!_addTransformPropertiesPayloads.IsCreated) _addTransformPropertiesPayloads = new NativeList(64, Allocator.Persistent); + if (!_clearTransformPropertiesQueuePayloads.IsCreated) _clearTransformPropertiesQueuePayloads = new NativeList(64, Allocator.Persistent); + if (!_modifyTransformPropertiesPayloads.IsCreated) _modifyTransformPropertiesPayloads = new NativeList(64, Allocator.Persistent); + if (!_snapNonSmoothedPropertiesPayloads.IsCreated) _snapNonSmoothedPropertiesPayloads = new NativeList(64, Allocator.Persistent); + if (!_teleportPayloads.IsCreated) _teleportPayloads = new NativeList(64, Allocator.Persistent); + + if (!_targetTaa.isCreated) _targetTaa = new TransformAccessArray(64); + if (!_graphicalTaa.isCreated) _graphicalTaa = new TransformAccessArray(64); + if (!_trackerTaa.isCreated) _trackerTaa = new TransformAccessArray(64); + + // Subscribe to client connection state to (un)hook timing/prediction. + _networkManager.ClientManager.OnClientConnectionState += ClientManager_OnClientConnectionState; + if (_networkManager.ClientManager.Started) ChangeSubscriptions(true); + } + + private void OnDestroy() + { + ChangeSubscriptions(false); + + if (_networkManager != null) + { + _networkManager.ClientManager.OnClientConnectionState -= ClientManager_OnClientConnectionState; + } + + while (_trackerTransformsPool.TryPop(out Transform trackerTransform)) + if (trackerTransform.gameObject) Destroy(trackerTransform.gameObject); + + for (int i = 0; i < _indexToSmoother.Count; i++) + { + Transform trackerTransform = _trackerTaa[i]; + if (trackerTransform.gameObject) Destroy(trackerTransform.gameObject); + } + + if (_moveRates.IsCreated) _moveRates.Dispose(); + if (_ownerSettings.IsCreated) _ownerSettings.Dispose(); + if (_spectatorSettings.IsCreated) _spectatorSettings.Dispose(); + + if (_preTickedMask.IsCreated) _preTickedMask.Dispose(); + if (_canSmoothMask.IsCreated) _canSmoothMask.Dispose(); + if (_useOwnerSettingsMask.IsCreated) _useOwnerSettingsMask.Dispose(); + if (_objectReconcilingMask.IsCreated) _objectReconcilingMask.Dispose(); + if (_detachOnStartMask.IsCreated) _detachOnStartMask.Dispose(); + if (_attachOnStopMask.IsCreated) _attachOnStopMask.Dispose(); + if (_moveImmediatelyMask.IsCreated) _moveImmediatelyMask.Dispose(); + if (_isMoving.IsCreated) _isMoving.Dispose(); + if (_teleportedTick.IsCreated) _teleportedTick.Dispose(); + if (_realTimeInterpolations.IsCreated) _realTimeInterpolations.Dispose(); + if (_movementMultipliers.IsCreated) _movementMultipliers.Dispose(); + + if (_transformProperties.IsCreated) _transformProperties.Dispose(); + if (_preTickGraphicSnapshot.IsCreated) _preTickGraphicSnapshot.Dispose(); + if (_postTickTrackerSnapshot.IsCreated) _postTickTrackerSnapshot.Dispose(); + if (_tempTargetSnapshot.IsCreated) _tempTargetSnapshot.Dispose(); + if (_outSnapGraphicWorld.IsCreated) _outSnapGraphicWorld.Dispose(); + if (_outEnqueueTrackerWorld.IsCreated) _outEnqueueTrackerWorld.Dispose(); + if (_queuedTrackerProperties.IsCreated) _queuedTrackerProperties.Dispose(); + + if (_moveToTargetPayloads.IsCreated) _moveToTargetPayloads.Dispose(); + if (_updateRealtimeInterpolationPayloads.IsCreated) _updateRealtimeInterpolationPayloads.Dispose(); + if (_discardExcessivePayloads.IsCreated) _discardExcessivePayloads.Dispose(); + if (_setMoveRatesPayloads.IsCreated) _setMoveRatesPayloads.Dispose(); + if (_setMovementMultiplierPayloads.IsCreated) _setMovementMultiplierPayloads.Dispose(); + if (_addTransformPropertiesPayloads.IsCreated) _addTransformPropertiesPayloads.Dispose(); + if (_clearTransformPropertiesQueuePayloads.IsCreated) _clearTransformPropertiesQueuePayloads.Dispose(); + if (_modifyTransformPropertiesPayloads.IsCreated) _modifyTransformPropertiesPayloads.Dispose(); + if (_snapNonSmoothedPropertiesPayloads.IsCreated) _snapNonSmoothedPropertiesPayloads.Dispose(); + if (_teleportPayloads.IsCreated) _teleportPayloads.Dispose(); + + if (_targetTaa.isCreated) _targetTaa.Dispose(); + if (_graphicalTaa.isCreated) _graphicalTaa.Dispose(); + if (_trackerTaa.isCreated) _trackerTaa.Dispose(); + + _indexToNetworkBehaviour.Clear(); + _indexToSmoother.Clear(); + _lookup.Clear(); + + _networkManager = null; + _timeManager = null; + _predictionManager = null; + } + + /// + /// Register a TickSmootherController with associated settings. + /// + public void Register(TickSmootherController smoother, InitializationSettings initializationSettings, + MovementSettings ownerSettings, MovementSettings spectatorSettings) + { + using (_pm_Register.Auto()) + { + if (smoother == null) + return; + + if (!TransformsAreValid(initializationSettings.GraphicalTransform, initializationSettings.TargetTransform)) + return; + + /* Unset scale smoothing if not detaching. This is to prevent + * the scale from changing with the parent if nested, as that + * would result in the scale being modified twice, once on the parent + * and once on the graphical. Thanks deo_wh for find! */ + if (!initializationSettings.DetachOnStart) + { + ownerSettings.SmoothedProperties &= ~TransformPropertiesFlag.Scale; + spectatorSettings.SmoothedProperties &= ~TransformPropertiesFlag.Scale; + } + + if (_lookup.TryGetValue(smoother, out int index)) + { + _ownerSettings[index] = ownerSettings; + _spectatorSettings[index] = spectatorSettings; + return; + } + index = _indexToSmoother.Count; + + _lookup[smoother] = index; + _indexToSmoother.Add(smoother); + _indexToNetworkBehaviour.Add(initializationSettings.InitializingNetworkBehaviour); + + _moveRates.Add(new MoveRates(MoveRates.UNSET_VALUE)); + _ownerSettings.Add(ownerSettings); + _spectatorSettings.Add(spectatorSettings); + + _preTickedMask.Add(0); + _canSmoothMask.Add( + (byte)(initializationSettings.GraphicalTransform != null && + _networkManager.IsClientStarted ? 1 : 0)); + _useOwnerSettingsMask.Add( + (byte)(initializationSettings.InitializingNetworkBehaviour == null || + initializationSettings.InitializingNetworkBehaviour.IsOwner || + !initializationSettings.InitializingNetworkBehaviour.Owner.IsValid ? 1 : 0)); + _objectReconcilingMask.Add( + (byte)(initializationSettings.InitializingNetworkBehaviour == null || + initializationSettings.InitializingNetworkBehaviour.NetworkObject == null || + initializationSettings.InitializingNetworkBehaviour.NetworkObject.IsObjectReconciling ? 1 : 0)); + _detachOnStartMask.Add( + (byte)(initializationSettings.DetachOnStart ? 1 : 0)); + _attachOnStopMask.Add( + (byte)(initializationSettings.AttachOnStop ? 1 : 0)); + _moveImmediatelyMask.Add( + (byte)(initializationSettings.MoveImmediately ? 1 : 0)); + + _isMoving.Add(default); + _teleportedTick.Add(TimeManager.UNSET_TICK); + _realTimeInterpolations.Add(default); + _movementMultipliers.Add(default); + + _transformProperties.AddQueue(); + _preTickGraphicSnapshot.Add(default); + _postTickTrackerSnapshot.Add(default); + _tempTargetSnapshot.Add(default); + _outSnapGraphicWorld.Add(default); + _outEnqueueTrackerWorld.Add(default); + _queuedTrackerProperties.Add(new NullableTransformProperties(false, default)); + + _moveToTargetPayloads.Add(new MoveToTargetPayload(0, default)); + _updateRealtimeInterpolationPayloads.Add(new UpdateRealtimeInterpolationPayload(0)); + _discardExcessivePayloads.Add(new DiscardExcessiveTransformPropertiesQueuePayload(0)); + _setMoveRatesPayloads.Add(new SetMoveRatesPayload(0, default)); + _setMovementMultiplierPayloads.Add(new SetMovementMultiplierPayload(0)); + _addTransformPropertiesPayloads.Add(new AddTransformPropertiesPayload(0, default)); + _clearTransformPropertiesQueuePayloads.Add(new ClearTransformPropertiesQueuePayload(0)); + _modifyTransformPropertiesPayloads.Add(new ModifyTransformPropertiesPayload(0, default, default)); + _snapNonSmoothedPropertiesPayloads.Add(new SnapNonSmoothedPropertiesPayload(0, default)); + _teleportPayloads.Add(new TeleportPayload(0)); + + Transform targetTransform = initializationSettings.TargetTransform; + Transform graphicalTransform = initializationSettings.GraphicalTransform; + if (!_trackerTransformsPool.TryPop(out Transform trackerTransform)) + trackerTransform = new GameObject().transform; + ProcessTransformsOnStart(trackerTransform, targetTransform, graphicalTransform, initializationSettings.DetachOnStart); + + _targetTaa.Add(targetTransform); + _graphicalTaa.Add(graphicalTransform); + _trackerTaa.Add(trackerTransform); + + //Use set method as it has sanity checks. + SetInterpolationValue(smoother, ownerSettings.InterpolationValue, forOwnerOrOfflineSmoother: true, unsetAdaptiveInterpolation: false); + SetInterpolationValue(smoother, spectatorSettings.InterpolationValue, forOwnerOrOfflineSmoother: false, unsetAdaptiveInterpolation: false); + + SetAdaptiveInterpolation(smoother, ownerSettings.AdaptiveInterpolationValue, forOwnerOrOfflineSmoother: true); + SetAdaptiveInterpolation(smoother, spectatorSettings.AdaptiveInterpolationValue, forOwnerOrOfflineSmoother: false); + } + } + + /// + /// Unregister a TickSmootherController. + /// + public void Unregister(TickSmootherController smoother) + { + using (_pm_Unregister.Auto()) + { + if (smoother == null || !_lookup.TryGetValue(smoother, out int index)) + return; + + bool isDetachOnStart = _detachOnStartMask[index] != 0; + bool isAttachOnStop = _attachOnStopMask[index] != 0; + Transform targetTransform = _targetTaa[index]; + Transform graphicalTransform = _graphicalTaa[index]; + Transform trackerTransform = _trackerTaa[index]; + ProcessTransformsOnStop(trackerTransform, targetTransform, graphicalTransform, isDetachOnStart, isAttachOnStop); + if (trackerTransform) + { + _trackerTransformsPool.Push(trackerTransform); + trackerTransform.SetParent(_trackerTransformsPoolHolder); + } + + int last = _indexToSmoother.Count - 1; + if (index != last) + { + var movedSmoother = _indexToSmoother[last]; + _indexToSmoother[index] = movedSmoother; + _lookup[movedSmoother] = index; + var movedNetworkBehaviour = _indexToNetworkBehaviour[last]; + _indexToNetworkBehaviour[index] = movedNetworkBehaviour; + } + + _indexToNetworkBehaviour.RemoveAt(last); + _indexToSmoother.RemoveAt(last); + _lookup.Remove(smoother); + + _moveRates.RemoveAtSwapBack(index); + _ownerSettings.RemoveAtSwapBack(index); + _spectatorSettings.RemoveAtSwapBack(index); + + _preTickedMask.RemoveAtSwapBack(index); + _canSmoothMask.RemoveAtSwapBack(index); + _useOwnerSettingsMask.RemoveAtSwapBack(index); + _objectReconcilingMask.RemoveAtSwapBack(index); + _detachOnStartMask.RemoveAtSwapBack(index); + _attachOnStopMask.RemoveAtSwapBack(index); + _moveImmediatelyMask.RemoveAtSwapBack(index); + + _isMoving.RemoveAtSwapBack(index); + _teleportedTick.RemoveAtSwapBack(index); + _realTimeInterpolations.RemoveAtSwapBack(index); + _movementMultipliers.RemoveAtSwapBack(index); + + _transformProperties.RemoveQueueAtSwapBack(index); + _preTickGraphicSnapshot.RemoveAtSwapBack(index); + _postTickTrackerSnapshot.RemoveAtSwapBack(index); + _tempTargetSnapshot.RemoveAtSwapBack(index); + _outSnapGraphicWorld.RemoveAtSwapBack(index); + _outEnqueueTrackerWorld.RemoveAtSwapBack(index); + _queuedTrackerProperties.RemoveAtSwapBack(index); + + _targetTaa.RemoveAtSwapBack(index); + _graphicalTaa.RemoveAtSwapBack(index); + _trackerTaa.RemoveAtSwapBack(index); + + _moveToTargetPayloads.RemoveAtSwapBack(index); + _updateRealtimeInterpolationPayloads.RemoveAtSwapBack(index); + _discardExcessivePayloads.RemoveAtSwapBack(index); + _setMoveRatesPayloads.RemoveAtSwapBack(index); + _setMovementMultiplierPayloads.RemoveAtSwapBack(index); + _addTransformPropertiesPayloads.RemoveAtSwapBack(index); + _clearTransformPropertiesQueuePayloads.RemoveAtSwapBack(index); + _modifyTransformPropertiesPayloads.RemoveAtSwapBack(index); + _snapNonSmoothedPropertiesPayloads.RemoveAtSwapBack(index); + _teleportPayloads.RemoveAtSwapBack(index); + } + } + + /// + /// Returns if configured transforms are valid. + /// + /// + private static bool TransformsAreValid(Transform graphicalTransform, Transform targetTransform) + { + if (graphicalTransform == null) + { + NetworkManagerExtensions.LogError($"Graphical transform cannot be null."); + return false; + } + if (targetTransform == null) + { + NetworkManagerExtensions.LogError($"Target transform on {graphicalTransform} cannot be null."); + return false; + } + if (targetTransform == graphicalTransform) + { + NetworkManagerExtensions.LogError($"Target transform cannot be the same as graphical transform on {graphicalTransform}."); + return false; + } + + return true; + } + + private static void ProcessTransformsOnStart(Transform trackerTransform, Transform targetTransform, Transform graphicalTransform, bool isDetachOnStart) + { + if (isDetachOnStart) + { + trackerTransform.SetParent(targetTransform); + + TransformProperties gfxWorldProperties = graphicalTransform.GetWorldProperties(); + graphicalTransform.SetParent(null); + graphicalTransform.SetWorldProperties(gfxWorldProperties); + } + else + { + Transform trackerParent = graphicalTransform.IsChildOf(targetTransform) ? graphicalTransform.parent : targetTransform; + trackerTransform.SetParent(trackerParent); + } + + targetTransform.GetPositionAndRotation(out var pos, out var rot); + trackerTransform.SetWorldPositionRotationAndScale(pos, rot, graphicalTransform.localScale); + trackerTransform.gameObject.name = $"{graphicalTransform.name}_Tracker"; + } + + private static void ProcessTransformsOnStop(Transform trackerTransform, Transform targetTransform, Transform graphicalTransform, bool isDetachOnStart, bool isAttachOnStop) + { + if (trackerTransform == null || targetTransform == null || graphicalTransform == null) + return; + if (ApplicationState.IsQuitting()) + return; + + trackerTransform.SetParent(null); + if (isDetachOnStart && isAttachOnStop) + { + graphicalTransform.SetParent(targetTransform.parent); + graphicalTransform.SetLocalProperties(trackerTransform.GetLocalProperties()); + } + } + + /// + /// Updates movement settings for a registered smoother. + /// Both owner and spectator settings are applied atomically. + /// + public void SetSettings(TickSmootherController smoother, in MovementSettings owner, in MovementSettings spectator) + { + if (smoother == null) return; + if (!_lookup.TryGetValue(smoother, out int index)) return; + + _ownerSettings[index] = owner; + _spectatorSettings[index] = spectator; + } + + /// + /// Sets transforms for a registered smoother (target, graphical, tracker). + /// + public void SetTransforms(TickSmootherController smoother, Transform target, Transform graphical) + { + if (smoother == null) return; + if (!_lookup.TryGetValue(smoother, out int index)) return; + + bool isDetachOnStart = _detachOnStartMask[index] != 0; + bool isAttachOnStop = _attachOnStopMask[index] != 0; + + Transform tracker = _trackerTaa[index]; + Transform prevTarget = _targetTaa[index]; + Transform prevGraphical = _graphicalTaa[index]; + ProcessTransformsOnStop(tracker, prevTarget, prevGraphical, isDetachOnStart, isAttachOnStop); + + _targetTaa[index] = target; + _graphicalTaa[index] = graphical; + ProcessTransformsOnStart(tracker, target, graphical, isDetachOnStart); + } + + /// + /// Updates the smoothedProperties value. + /// + /// TickSmootherController. + /// New value. + /// True if updating owner smoothing settings, or updating settings on an offline smoother. False to update spectator settings + public void SetSmoothedProperties(TickSmootherController smoother, TransformPropertiesFlag value, bool forOwnerOrOfflineSmoother) + { + if (smoother == null) return; + if (!_lookup.TryGetValue(smoother, out int index)) return; + + MovementSettings settings = forOwnerOrOfflineSmoother ? _ownerSettings[index] : _spectatorSettings[index]; + settings.SmoothedProperties = value; + + if (forOwnerOrOfflineSmoother) + _ownerSettings[index] = settings; + else + _spectatorSettings[index] = settings; + } + + /// + /// Updates the interpolationValue when not using adaptive interpolation. Calling this method will also disable adaptive interpolation. + /// + /// TickSmootherController. + /// New value. + /// True if updating owner smoothing settings, or updating settings on an offline smoother. False to update spectator settings + public void SetInterpolationValue(TickSmootherController smoother, byte value, bool forOwnerOrOfflineSmoother) => SetInterpolationValue(smoother, value, forOwnerOrOfflineSmoother, unsetAdaptiveInterpolation: true); + + /// + /// Updates the interpolationValue when not using adaptive interpolation. Calling this method will also disable adaptive interpolation. + /// + /// TickSmootherController. + /// New value. + /// True if updating owner smoothing settings, or updating settings on an offline smoother. False to update spectator settings + /// + private void SetInterpolationValue(TickSmootherController smoother, byte value, bool forOwnerOrOfflineSmoother, bool unsetAdaptiveInterpolation) + { + if (smoother == null) return; + if (!_lookup.TryGetValue(smoother, out int index)) return; + + if (value < 1) + value = 1; + + MovementSettings settings = forOwnerOrOfflineSmoother ? _ownerSettings[index] : _spectatorSettings[index]; + settings.InterpolationValue = value; + + if (forOwnerOrOfflineSmoother) + _ownerSettings[index] = settings; + else + _spectatorSettings[index] = settings; + + if (unsetAdaptiveInterpolation) + SetAdaptiveInterpolation(smoother, AdaptiveInterpolationType.Off, forOwnerOrOfflineSmoother); + } + + /// + /// Updates the adaptiveInterpolation value. + /// + /// TickSmootherController. + /// New value. + /// True if updating owner smoothing settings, or updating settings on an offline smoother. False to update spectator settings + public void SetAdaptiveInterpolation(TickSmootherController smoother, AdaptiveInterpolationType value, bool forOwnerOrOfflineSmoother) + { + if (smoother == null) return; + if (!_lookup.TryGetValue(smoother, out int index)) return; + + MovementSettings settings = forOwnerOrOfflineSmoother ? _ownerSettings[index] : _spectatorSettings[index]; + settings.AdaptiveInterpolationValue = value; + + if (forOwnerOrOfflineSmoother) + _ownerSettings[index] = settings; + else + _spectatorSettings[index] = settings; + + _updateRealtimeInterpolationPayloads[index] = new UpdateRealtimeInterpolationPayload(1); + } + + /// + /// Tries to set local properties for the graphical tracker transform. + /// + /// New values. + /// Returns true if the tracker has been setup and values have been applied to teh tracker transform. + /// When false is returned the values are cached and will be set when tracker is created. A cached value will be used every time the tracker is setup; to disable this behavior call this method with null value. + public bool TrySetGraphicalTrackerLocalProperties(TickSmootherController smoother, TransformProperties? localValues) + { + if (smoother == null) return false; + if (!_lookup.TryGetValue(smoother, out int index)) return false; + + if (_trackerTaa[index] == null || localValues == null) + { + _queuedTrackerProperties[index] = new NullableTransformProperties(localValues != null, localValues ?? default); + return false; + } + + _trackerTaa[index].SetLocalProperties(localValues.Value); + return true; + } + + public bool TryGetGraphicalTrackerLocalProperties(TickSmootherController smoother, out TransformProperties localValues) + { + localValues = default; + if (smoother == null) return false; + if (!_lookup.TryGetValue(smoother, out int index)) return false; + + Transform trackerTransform = _trackerTaa[index]; + if (trackerTransform != null) + { + localValues = new(trackerTransform.localPosition, trackerTransform.localRotation, trackerTransform.localScale); + return true; + } + + NullableTransformProperties queuedTrackerProperties = _queuedTrackerProperties[index]; + if (queuedTrackerProperties.IsExist != 0) + { + localValues = queuedTrackerProperties.Properties; + return true; + } + + // Fall through. + return false; + } + + /// + /// Marks to teleports the graphical to it's starting position and clears the internal movement queue at the PreTick. + /// + public void Teleport(TickSmootherController smoother) + { + if (smoother == null) return; + if (!_lookup.TryGetValue(smoother, out int index)) return; + + _teleportPayloads[index] = new TeleportPayload(1); + } + + private void ClientManager_OnClientConnectionState(ClientConnectionStateArgs args) + { + using (_pm_ClientManager_OnClientConnectionState.Auto()) + { + while (_trackerTransformsPool.TryPop(out Transform trackerTransform)) + if (trackerTransform.gameObject) Destroy(trackerTransform.gameObject); + + if (args.ConnectionState == LocalConnectionState.Started) + ChangeSubscriptions(true); + else + ChangeSubscriptions(false); + } + } + + private void ChangeSubscriptions(bool subscribe) + { + if (_timeManager == null) + return; + + if (_subscribed == subscribe) + return; + + _subscribed = subscribe; + + if (subscribe) + { + _timeManager.OnUpdate += TimeManager_OnUpdate; + _timeManager.OnPreTick += TimeManager_OnPreTick; + _timeManager.OnPostTick += TimeManager_OnPostTick; + _timeManager.OnRoundTripTimeUpdated += TimeManager_OnRoundTripTimeUpdated; + + if (_predictionManager != null) + _predictionManager.OnPostReplicateReplay += PredictionManager_OnPostReplicateReplay; + } + else + { + _timeManager.OnUpdate -= TimeManager_OnUpdate; + _timeManager.OnPreTick -= TimeManager_OnPreTick; + _timeManager.OnPostTick -= TimeManager_OnPostTick; + _timeManager.OnRoundTripTimeUpdated -= TimeManager_OnRoundTripTimeUpdated; + + if (_predictionManager != null) + _predictionManager.OnPostReplicateReplay -= PredictionManager_OnPostReplicateReplay; + } + } + + /// + /// Called every frame. + /// + private void TimeManager_OnUpdate() + { + using (_pm_OnUpdate.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return; + int batchSize = ComputeBatchSize(count); + + var job = new UpdateJob + { + canSmoothMask = _canSmoothMask.AsArray(), + deltaTime = Time.deltaTime, + + moveToTargetPayloads = _moveToTargetPayloads.AsArray() + }; + + JobHandle innerHandle = job.Schedule(count, batchSize); + JobHandle moveToTargetHandle = ScheduleMoveToTarget(innerHandle); + moveToTargetHandle.Complete(); + } + } + + /// + /// Called when the TimeManager invokes OnPreTick. + /// + private void TimeManager_OnPreTick() + { + using (_pm_OnPreTick.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return; + int batchSize = ComputeBatchSize(count); + + for (int i = 0; i < count; i++) + { + Transform graphicalTransform = _graphicalTaa[i]; + NetworkBehaviour networkBehaviour = _indexToNetworkBehaviour[i]; + _canSmoothMask[i] = + (byte)(graphicalTransform != null && + _networkManager.IsClientStarted ? 1 : 0); + + _useOwnerSettingsMask[i] = + (byte)(networkBehaviour == null || + networkBehaviour.IsOwner || + !networkBehaviour.Owner.IsValid ? 1 : 0); + + _objectReconcilingMask[i] = + (byte)(networkBehaviour == null || + networkBehaviour.NetworkObject == null || + networkBehaviour.NetworkObject.IsObjectReconciling ? 1 : 0); + } + + JobHandle preTickMarkHandle = new PreTickMarkJob + { + canSmoothMask = _canSmoothMask.AsArray(), + preTickedMask = _preTickedMask.AsArray(), + discardExcessivePayloads = _discardExcessivePayloads.AsArray() + }.Schedule(count, batchSize); + + JobHandle discardExcessiveHandle = ScheduleDiscardExcessiveTransformPropertiesQueue(preTickMarkHandle); + + JobHandle teleportHandle = ScheduleTeleport(discardExcessiveHandle); + + JobHandle preTickCaptureGraphicalHandle = new PreTickCaptureGraphicalJob + { + canSmoothMask = _canSmoothMask.AsArray(), + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray(), + graphicSnapshot = _preTickGraphicSnapshot.AsArray() + }.Schedule(_graphicalTaa, teleportHandle); + + preTickCaptureGraphicalHandle.Complete(); + } + } + + /// + /// Called when the TimeManager invokes OnPostReplay. + /// + /// Replay tick for the local client. + /// + /// This is dependent on the initializing NetworkBehaviour being set. + private void PredictionManager_OnPostReplicateReplay(uint clientTick, uint serverTick) + { + using (_pm_Prediction_OnPostReplicateReplay.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return; + int batchSize = ComputeBatchSize(count); + + var job = new PostReplicateReplayJob + { + clientTick = clientTick, + teleportedTick = _teleportedTick.AsArray(), + objectReconcilingMask = _objectReconcilingMask.AsArray(), + + modifyTransformPropertiesPayloads = _modifyTransformPropertiesPayloads.AsArray() + }; + + JobHandle innerHandle = job.Schedule(count, batchSize); + JobHandle modifyTransformPropertiesHandle = ScheduleModifyTransformProperties(innerHandle); + modifyTransformPropertiesHandle.Complete(); + } + } + + /// + /// Called when TimeManager invokes OnPostTick. + /// + private void TimeManager_OnPostTick() + { + using (_pm_OnPostTick.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return; + + JobHandle captureLocalTargetHandle = new CaptureLocalTargetJob + { + canSmoothMask = _canSmoothMask.AsArray(), + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray(), + targetSnapshot = _tempTargetSnapshot.AsArray() + }.Schedule(_targetTaa); + + JobHandle postTickCaptureTrackerHandle = new PostTickCaptureTrackerJob + { + canSmoothMask = _canSmoothMask.AsArray(), + detachOnStartMask = _detachOnStartMask.AsArray(), + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray(), + targetSnapshot = _tempTargetSnapshot.AsArray(), + trackerSnapshot = _postTickTrackerSnapshot.AsArray() + }.Schedule(_trackerTaa, captureLocalTargetHandle); + + JobHandle postTickHandle = new PostTickJob + { + clientTick = _timeManager.LocalTick, + canSmoothMask = _canSmoothMask.AsArray(), + teleportedTick = _teleportedTick.AsArray(), + preTickedMask = _preTickedMask.AsArray(), + detachOnStartMask = _detachOnStartMask.AsArray(), + postTickTrackerSnapshot = _postTickTrackerSnapshot.AsArray(), + preTickGraphicSnapshot = _preTickGraphicSnapshot.AsArray(), + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray(), + + discardExcessivePayloads = _discardExcessivePayloads.AsArray(), + snapNonSmoothedPropertiesPayloads = _snapNonSmoothedPropertiesPayloads.AsArray(), + addTransformPropertiesPayloads = _addTransformPropertiesPayloads.AsArray() + }.Schedule(_graphicalTaa, postTickCaptureTrackerHandle); + + JobHandle discardExcessiveHandle = ScheduleDiscardExcessiveTransformPropertiesQueue(postTickHandle); + JobHandle snapNonSmoothedPropertiesHandle = ScheduleSnapNonSmoothedProperties(discardExcessiveHandle); + JobHandle addTransformPropertiesHandle = ScheduleAddTransformProperties(snapNonSmoothedPropertiesHandle); + addTransformPropertiesHandle.Complete(); + } + } + + private void TimeManager_OnRoundTripTimeUpdated(long rttMs) + { + using (_pm_TimeManager_OnRoundTripTimeUpdated.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return; + int batchSize = ComputeBatchSize(count); + + var job = new RoundTripTimeUpdatedJob + { + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray(), + + updateRealtimeInterpolationPayloads = _updateRealtimeInterpolationPayloads.AsArray() + }; + + JobHandle innerHandle = job.Schedule(count, batchSize); + JobHandle updateRealtimeInterpolationHandle = ScheduleUpdateRealtimeInterpolation(innerHandle); + updateRealtimeInterpolationHandle.Complete(); + } + } + + /// + /// Moves transform to target values. + /// + public JobHandle ScheduleMoveToTarget(in JobHandle outerHandle = default) + { + using (_pm_MoveToTarget.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return outerHandle; + + var job = new MoveToTargetJob + { + jobPayloads = _moveToTargetPayloads.AsArray(), + + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray(), + + realTimeInterpolations = _realTimeInterpolations.AsArray(), + moveImmediatelyMask = _moveImmediatelyMask.AsArray(), + tickDelta = (float)_timeManager.TickDelta, + + isMoving = _isMoving.AsArray(), + movementMultipliers = _movementMultipliers.AsArray(), + + transformProperties = _transformProperties, + moveRates = _moveRates.AsArray(), + + setMoveRatesPayloads = _setMoveRatesPayloads.AsArray(), + setMovementMultiplierPayloads = _setMovementMultiplierPayloads.AsArray(), + clearTransformPropertiesQueuePayloads = _clearTransformPropertiesQueuePayloads.AsArray() + }; + + JobHandle innerHandle = job.Schedule(_graphicalTaa, outerHandle); + return innerHandle; + } + } + + /// + /// Updates interpolation based on localClient latency when using adaptive interpolation, or uses set value when adaptive interpolation is off. + /// + public JobHandle ScheduleUpdateRealtimeInterpolation(in JobHandle outerHandle = default) + { + using (_pm_ScheduleUpdateRealtimeInterpolation.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return outerHandle; + int batchSize = ComputeBatchSize(count); + + var job = new UpdateRealtimeInterpolationJob + { + jobPayloads = _updateRealtimeInterpolationPayloads.AsArray(), + + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray(), + + tickDelta = (float)_timeManager.TickDelta, + tickRate = _timeManager.TickRate, + rtt = _timeManager.RoundTripTime, + localTick = _timeManager.LocalTick, + isServerOnlyStarted = _networkManager.IsServerOnlyStarted, + + realTimeInterpolations = _realTimeInterpolations.AsArray() + }; + + JobHandle innerHandle = job.Schedule(count, batchSize, outerHandle); + return innerHandle; + } + } + + /// + /// Discards datas over interpolation limit from movement queue. + /// + private JobHandle ScheduleDiscardExcessiveTransformPropertiesQueue(in JobHandle outerHandle = default) + { + using (_pm_ScheduleDiscardExcessiveTransformPropertiesQueue.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return outerHandle; + int batchSize = ComputeBatchSize(count); + + var job = new DiscardExcessiveTransformPropertiesQueueJob + { + jobPayloads = _discardExcessivePayloads.AsArray(), + + realTimeInterpolations = _realTimeInterpolations.AsArray(), + requiredQueuedOverInterpolation = REQUIRED_QUEUED_OVER_INTERPOLATION, + + transformProperties = _transformProperties, + setMoveRatesPayloads = _setMoveRatesPayloads.AsArray() + }; + + JobHandle innerHandle = job.Schedule(count, batchSize, outerHandle); + JobHandle setMoveRatesHandle = ScheduleSetMoveRates(innerHandle); + return setMoveRatesHandle; + } + } + + /// + /// Sets new rates based on next entries in transformProperties queue, against a supplied TransformProperties. + /// + private JobHandle ScheduleSetMoveRates(in JobHandle outerHandle = default) + { + using (_pm_ScheduleSetMoveRates.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return outerHandle; + int batchSize = ComputeBatchSize(count); + + var job = new SetMoveRatesJob + { + jobPayloads = _setMoveRatesPayloads.AsArray(), + + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray(), + + transformProperties = _transformProperties, + tickDelta = (float)_timeManager.TickDelta, + + moveRates = _moveRates.AsArray(), + setMovementMultiplierPayloads = _setMovementMultiplierPayloads.AsArray(), + }; + JobHandle innerHandle = job.Schedule(count, batchSize, outerHandle); + JobHandle setMovementMultiplierHandle = ScheduleSetMovementMultiplier(innerHandle); + return setMovementMultiplierHandle; + } + } + + private JobHandle ScheduleSetMovementMultiplier(in JobHandle outerHandle = default) + { + using (_pm_ScheduleSetMovementMultiplier.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return outerHandle; + int batchSize = ComputeBatchSize(count); + + var job = new SetMovementMultiplierJob + { + jobPayloads = _setMovementMultiplierPayloads.AsArray(), + + transformProperties = _transformProperties, + realTimeInterpolations = _realTimeInterpolations.AsArray(), + moveImmediatelyMask = _moveImmediatelyMask.AsArray(), + + movementMultipliers = _movementMultipliers.AsArray() + }; + JobHandle innerHandle = job.Schedule(count, batchSize, outerHandle); + return innerHandle; + } + } + + /// + /// Adds a new transform properties and sets move rates if needed. + /// + private JobHandle ScheduleAddTransformProperties(JobHandle outerHandle) + { + using (_pm_ScheduleAddTransformProperties.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return outerHandle; + + var job = new AddTransformPropertiesJob + { + jobPayloads = _addTransformPropertiesPayloads.AsArray(), + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray(), + + transformProperties = _transformProperties, + setMoveRatesPayloads = _setMoveRatesPayloads.AsArray() + }; + + JobHandle innerHandle = job.Schedule(_graphicalTaa, outerHandle); + JobHandle setMoveRatesHandle = ScheduleSetMoveRates(innerHandle); + return setMoveRatesHandle; + } + } + + /// + /// Clears the pending movement queue. + /// + private JobHandle ScheduleClearTransformPropertiesQueue(JobHandle outerHandle) + { + using (_pm_ScheduleClearTransformPropertiesQueue.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return outerHandle; + int batchSize = ComputeBatchSize(count); + + var job = new ClearTransformPropertiesQueueJob + { + jobPayloads = _clearTransformPropertiesQueuePayloads.AsArray(), + + transformProperties = _transformProperties, + moveRates = _moveRates.AsArray() + }; + + JobHandle innerHandle = job.Schedule(count, batchSize, outerHandle); + return innerHandle; + } + } + + /// + /// Modifies a transform property for a tick. This does not error check for empty collections. + /// firstTick - First tick in the queue. If 0 this will be looked up. + /// + private JobHandle ScheduleModifyTransformProperties(JobHandle outerHandle) + { + using (_pm_ScheduleModifyTransformProperties.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return outerHandle; + + JobHandle captureLocalTargetHandle = new CaptureLocalTargetJob + { + canSmoothMask = _canSmoothMask.AsArray(), + targetSnapshot = _tempTargetSnapshot.AsArray(), + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray() + }.Schedule(_targetTaa, outerHandle); + + JobHandle modifyTransformPropertiesHandle = new ModifyTransformPropertiesJob + { + jobPayloads = _modifyTransformPropertiesPayloads.AsArray(), + detachOnStartMask = _detachOnStartMask.AsArray(), + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray(), + targetSnapshot = _tempTargetSnapshot.AsArray(), + transformProperties = _transformProperties, + }.Schedule(_trackerTaa, captureLocalTargetHandle); + + return modifyTransformPropertiesHandle; + } + } + + /// + /// Snaps non-smoothed properties to original positoin if setting is enabled. + /// + private JobHandle ScheduleSnapNonSmoothedProperties(JobHandle outerHandle) + { + using (_pm_ScheduleSnapNonSmoothedProperties.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return outerHandle; + + var job = new SnapNonSmoothedPropertiesJob + { + jobPayloads = _snapNonSmoothedPropertiesPayloads.AsArray(), + + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray(), + }; + + JobHandle innerHandle = job.Schedule(_graphicalTaa, outerHandle); + return innerHandle; + } + } + + /// + /// Teleports the graphical to it's starting position and clears the internal movement queue. + /// + private JobHandle ScheduleTeleport(JobHandle outerHandle) + { + using (_pm_ScheduleTeleport.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return outerHandle; + + var job = new TeleportJob + { + jobPayloads = _teleportPayloads.AsArray(), + + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray(), + preTickTrackerSnapshot = _postTickTrackerSnapshot.AsArray(), + localTick = _timeManager.LocalTick, + + transformProperties = _transformProperties, + clearTransformPropertiesQueuePayloads = _clearTransformPropertiesQueuePayloads.AsArray(), + moveRates = _moveRates.AsArray(), + teleportedTick = _teleportedTick.AsArray() + }; + + JobHandle innerHandle = job.Schedule(_graphicalTaa, outerHandle); + JobHandle clearTransformPropertiesQueueHandle = ScheduleClearTransformPropertiesQueue(innerHandle); + return clearTransformPropertiesQueueHandle; + } + } + + private static int ComputeBatchSize(int length, int minBatch = 1, int maxBatch = 128) + { + if (length <= 0) return 1; + + // +1: main thread + worker threads + int workers = JobsUtility.JobWorkerCount + 1; + + // Aim for ~4 waves of batches across all workers. + int targetBatches = Mathf.Max(1, workers * 4); + + // CeilDiv to get iterations per batch + int batch = (length + targetBatches - 1) / targetBatches; + + return Mathf.Clamp(batch, minBatch, maxBatch); + } + } +} diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.cs index 5f621a341..76c83f7c0 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.cs @@ -5,8 +5,9 @@ using FishNet.Object.Prediction; using FishNet.Utility.Extension; using GameKit.Dependencies.Utilities; -using Unity.Profiling; using UnityEngine; +using UnityEngine.Profiling; +using Unity.Profiling; using UnityEngine.Scripting; namespace FishNet.Component.Transforming.Beta @@ -16,41 +17,7 @@ namespace FishNet.Component.Transforming.Beta /// public sealed class UniversalTickSmoother : IResettable { - #region Types. - [Preserve] - private struct TickTransformProperties - { - public readonly uint Tick; - public readonly TransformProperties Properties; - - public TickTransformProperties(uint tick, Transform t) - { - Tick = tick; - Properties = new(t.localPosition, t.localRotation, t.localScale); - } - - public TickTransformProperties(uint tick, Transform t, Vector3 localScale) - { - Tick = tick; - Properties = new(t.localPosition, t.localRotation, localScale); - } - - public TickTransformProperties(uint tick, TransformProperties tp) - { - Tick = tick; - Properties = tp; - } - - public TickTransformProperties(uint tick, TransformProperties tp, Vector3 localScale) - { - Tick = tick; - tp.Scale = localScale; - Properties = tp; - } - } - #endregion - - #region public. + #region Public. /// /// True if currently initialized. /// @@ -58,6 +25,23 @@ public TickTransformProperties(uint tick, TransformProperties tp, Vector3 localS #endregion #region Private. + + #region Private Profiler Markers + + private static readonly ProfilerMarker _pm_UpdateRealtimeInterpolation = new ProfilerMarker("UniversalTickSmoother.UpdateRealtimeInterpolation()"); + private static readonly ProfilerMarker _pm_OnUpdate = new ProfilerMarker("UniversalTickSmoother.OnUpdate(float)"); + private static readonly ProfilerMarker _pm_OnPreTick = new ProfilerMarker("UniversalTickSmoother.OnPreTick()"); + private static readonly ProfilerMarker _pm_OnPostReplicateReplay = new ProfilerMarker("UniversalTickSmoother.OnPostReplicateReplay(uint)"); + private static readonly ProfilerMarker _pm_OnPostTick = new ProfilerMarker("UniversalTickSmoother.OnPostTick(uint)"); + private static readonly ProfilerMarker _pm_ClearTPQ = new ProfilerMarker("UniversalTickSmoother.ClearTransformPropertiesQueue()"); + private static readonly ProfilerMarker _pm_DiscardTPQ = new ProfilerMarker("UniversalTickSmoother.DiscardExcessiveTransformPropertiesQueue()"); + private static readonly ProfilerMarker _pm_AddTP = new ProfilerMarker("UniversalTickSmoother.AddTransformProperties(uint, TransformProperties, TransformProperties)"); + private static readonly ProfilerMarker _pm_ModifyTP = new ProfilerMarker("UniversalTickSmoother.ModifyTransformProperties(uint, uint)"); + private static readonly ProfilerMarker _pm_SetMoveRates = new ProfilerMarker("UniversalTickSmoother.SetMoveRates(in TransformProperties)"); + private static readonly ProfilerMarker _pm_MoveToTarget = new ProfilerMarker("UniversalTickSmoother.MoveToTarget(float)"); + + #endregion + /// /// How quickly to move towards goal values. /// @@ -139,7 +123,7 @@ public TickTransformProperties(uint tick, TransformProperties tp, Vector3 localS /// /// TransformProperties to move towards. /// - private BasicQueue _transformProperties; + private BasicQueue _transformProperties; /// /// True if to smooth using owner settings, false for spectator settings. /// This is only used for performance gains. @@ -167,22 +151,6 @@ public TickTransformProperties(uint tick, TransformProperties tp, Vector3 localS private bool _isMoving; #endregion - #region Private Profiler Markers - // private static readonly ProfilerMarker _pm_ConsumeFixedOffset = new("UniversalTickSmoother.ConsumeFixedOffset(uint)"); - // private static readonly ProfilerMarker _pm_AxiswiseClamp = new("UniversalTickSmoother.AxiswiseClamp(TransformProperties, TransformProperties)"); - private static readonly ProfilerMarker _pm_UpdateRealtimeInterpolation = new("UniversalTickSmoother.UpdateRealtimeInterpolation()"); - private static readonly ProfilerMarker _pm_OnUpdate = new("UniversalTickSmoother.OnUpdate(float)"); - private static readonly ProfilerMarker _pm_OnPreTick = new("UniversalTickSmoother.OnPreTick()"); - private static readonly ProfilerMarker _pm_OnPostReplicateReplay = new("UniversalTickSmoother.OnPostReplicateReplay(uint)"); - private static readonly ProfilerMarker _pm_OnPostTick = new("UniversalTickSmoother.OnPostTick(uint)"); - private static readonly ProfilerMarker _pm_ClearTPQ = new("UniversalTickSmoother.ClearTransformPropertiesQueue()"); - private static readonly ProfilerMarker _pm_DiscardTPQ = new("UniversalTickSmoother.DiscardExcessiveTransformPropertiesQueue()"); - private static readonly ProfilerMarker _pm_AddTP = new("UniversalTickSmoother.AddTransformProperties(uint, TransformProperties, TransformProperties)"); - private static readonly ProfilerMarker _pm_ModifyTP = new("UniversalTickSmoother.ModifyTransformProperties(uint, uint)"); - private static readonly ProfilerMarker _pm_SetMoveRates = new("UniversalTickSmoother.SetMoveRates(in TransformProperties)"); - private static readonly ProfilerMarker _pm_MoveToTarget = new("UniversalTickSmoother.MoveToTarget(float)"); - #endregion - #region Const. /// /// Maximum allowed entries to be queued over the interpolation amount. @@ -305,8 +273,8 @@ public void Initialize(InitializationSettings initializationSettings, MovementSe if (!TransformsAreValid(graphicalTransform, targetTransform)) return; - - _transformProperties = CollectionCaches.RetrieveBasicQueue(); + + _transformProperties = CollectionCaches.RetrieveBasicQueue(); _controllerMovementSettings = ownerSettings; _spectatorMovementSettings = spectatorSettings; @@ -358,7 +326,7 @@ void SetupTrackerTransform() _trackerTransform.SetParent(trackerParent); } - _trackerTransform.SetLocalPositionRotationAndScale(_graphicalTransform.localPosition, graphicalTransform.localRotation, graphicalTransform.localScale); + _trackerTransform.SetWorldPositionRotationAndScale(_targetTransform.position, _targetTransform.rotation, graphicalTransform.localScale); } IsInitialized = true; @@ -588,14 +556,14 @@ public void OnPostTick(uint clientTick) //If preticked then previous transform values are known. if (_preTicked) { - DiscardExcessiveTransformPropertiesQueue(); - + var trackerProps = GetTrackerWorldProperties(); //Only needs to be put to pretick position if not detached. if (!_detachOnStart) _graphicalTransform.SetWorldProperties(_graphicsPreTickWorldValues); + DiscardExcessiveTransformPropertiesQueue(); //SnapNonSmoothedProperties(); - AddTransformProperties(clientTick); + AddTransformProperties(clientTick, trackerProps); } //If did not pretick then the only thing we can do is snap to instantiated values. else @@ -684,11 +652,14 @@ private void DiscardExcessiveTransformPropertiesQueue() //If there are entries to dequeue. if (dequeueCount > 0) { - TickTransformProperties tpp = default; + TickSmoothingManager.TickTransformProperties ttp = default; for (int i = 0; i < dequeueCount; i++) - tpp = _transformProperties.Dequeue(); + { + ttp = _transformProperties.Dequeue(); + } - SetMoveRates(tpp.Properties); + var nextValues = ttp.Properties; + SetMoveRates(nextValues); } } } @@ -696,12 +667,12 @@ private void DiscardExcessiveTransformPropertiesQueue() /// /// Adds a new transform properties and sets move rates if needed. /// - private void AddTransformProperties(uint tick) + private void AddTransformProperties(uint tick, TransformProperties properties) { using (_pm_AddTP.Auto()) { - TickTransformProperties tpp = new(tick, GetTrackerWorldProperties()); - _transformProperties.Enqueue(tpp); + TickSmoothingManager.TickTransformProperties ttp = new(tick, properties); + _transformProperties.Enqueue(ttp); //If first entry then set move rates. if (_transformProperties.Count == 1) @@ -887,7 +858,7 @@ private void MoveToTarget(float delta) } } - TickTransformProperties ttp = _transformProperties.Peek(); + TickSmoothingManager.TickTransformProperties ttp = _transformProperties.Peek(); TransformPropertiesFlag smoothedProperties = _cachedSmoothedProperties; @@ -969,7 +940,7 @@ public void ResetState() _teleportedTick = TimeManager.UNSET_TICK; _movementMultiplier = 1f; - CollectionCaches.StoreAndDefault(ref _transformProperties); + CollectionCaches.StoreAndDefault(ref _transformProperties); _moveRates = default; _preTicked = default; _queuedTrackerProperties = null; diff --git a/Assets/FishNet/Runtime/Managing/NetworkManager.cs b/Assets/FishNet/Runtime/Managing/NetworkManager.cs index 6f0736d5c..216aed6a1 100644 --- a/Assets/FishNet/Runtime/Managing/NetworkManager.cs +++ b/Assets/FishNet/Runtime/Managing/NetworkManager.cs @@ -25,6 +25,7 @@ using FishNet.Managing.Statistic; using FishNet.Utility.Performance; using FishNet.Component.ColliderRollback; +using FishNet.Component.Transforming.Beta; using FishNet.Configuring; using FishNet.Configuring.EditorCloning; using FishNet.Managing.Predicting; @@ -121,6 +122,10 @@ public static IReadOnlyList Instances /// public ObserverManager ObserverManager { get; private set; } /// + /// TickSmoothingManager for this NetworkManager. + /// + public TickSmoothingManager TickSmoothingManager { get; private set; } + /// /// DebugManager for this NetworkManager. /// public DebugManager DebugManager { get; private set; } @@ -318,6 +323,7 @@ private void Awake() TimeManager = GetOrCreateComponent(); SceneManager = GetOrCreateComponent(); ObserverManager = GetOrCreateComponent(); + TickSmoothingManager = GetOrCreateComponent(); RollbackManager = GetOrCreateComponent(); PredictionManager = GetOrCreateComponent(); StatisticsManager = GetOrCreateComponent(); @@ -362,6 +368,7 @@ private void InitializeComponents() SceneManager.InitializeOnce_Internal(this); ObserverManager.InitializeOnce_Internal(this); + TickSmoothingManager.InitializeOnce_Internal(this); RollbackManager.InitializeOnce_Internal(this); PredictionManager.InitializeOnce(this); StatisticsManager.InitializeOnce_Internal(this); diff --git a/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs b/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs index c81ec7d15..58c37480e 100644 --- a/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs +++ b/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs @@ -8,6 +8,7 @@ using System; using System.Runtime.CompilerServices; using FishNet.Managing.Statistic; +using Unity.Mathematics; using Unity.Profiling; using UnityEngine; using SystemStopwatch = System.Diagnostics.Stopwatch; @@ -284,10 +285,11 @@ public void SetPhysicsTimeScale(float value) /// /// - private NetworkTrafficStatistics _networkTrafficStatistics; + [NonSerialized] private NetworkTrafficStatistics _networkTrafficStatistics; #endregion #region Private Profiler Markers + private static readonly ProfilerMarker _pm_IncreaseTick = new("TimeManager.IncreaseTick()"); private static readonly ProfilerMarker _pm_OnFixedUpdate = new("TimeManager.OnFixedUpdate()"); private static readonly ProfilerMarker _pm_OnPostPhysicsSimulation = new("TimeManager.OnPostPhysicsSimulation(float)"); private static readonly ProfilerMarker _pm_OnPrePhysicsSimulation = new("TimeManager.OnPrePhysicsSimulation(float)"); @@ -683,97 +685,100 @@ internal void SendPong(NetworkConnection conn, uint clientTick) /// private void IncreaseTick() { - bool isClient = NetworkManager.IsClientStarted; - bool isServer = NetworkManager.IsServerStarted; - - double timePerSimulation = isServer ? TickDelta : _adjustedTickDelta; - if (timePerSimulation == 0d) + using (_pm_IncreaseTick.Auto()) { - NetworkManager.LogWarning($"Simulation delta cannot be 0. Network timing will not continue."); - return; - } - - double time = Time.unscaledDeltaTime; + bool isClient = NetworkManager.IsClientStarted; + bool isServer = NetworkManager.IsServerStarted; - _elapsedTickTime += time; - FrameTicked = _elapsedTickTime >= timePerSimulation; + double timePerSimulation = isServer ? TickDelta : _adjustedTickDelta; + if (timePerSimulation == 0d) + { + NetworkManager.LogWarning($"Simulation delta cannot be 0. Network timing will not continue."); + return; + } - // Number of ticks to occur this frame. - int ticksCount = Mathf.FloorToInt((float)(_elapsedTickTime / timePerSimulation)); - if (ticksCount > 1) - _lastMultipleTicksTime = Time.unscaledTime; + double time = Time.unscaledDeltaTime; - if (_allowTickDropping) - { - // If ticks require dropping. Set exactly to maximum ticks. - if (ticksCount > _maximumFrameTicks) - _elapsedTickTime = timePerSimulation * (double)_maximumFrameTicks; - } + _elapsedTickTime += time; + FrameTicked = _elapsedTickTime >= timePerSimulation; - bool variableTiming = _timingType == TimingType.Variable; - bool frameTicked = FrameTicked; - float tickDelta = (float)TickDelta * GetPhysicsTimeScale(); + // Number of ticks to occur this frame. + int ticksCount = Mathf.FloorToInt((float)(_elapsedTickTime / timePerSimulation)); + if (ticksCount > 1) + _lastMultipleTicksTime = Time.unscaledTime; - do - { - if (frameTicked) + if (_allowTickDropping) { - using (_pm_OnPreTick.Auto()) - OnPreTick?.Invoke(); + // If ticks require dropping. Set exactly to maximum ticks. + if (ticksCount > _maximumFrameTicks) + _elapsedTickTime = timePerSimulation * (double)_maximumFrameTicks; } - /* This has to be called inside the loop because - * OnPreTick promises data hasn't been read yet. - * Therefor iterate must occur after OnPreTick. - * Iteration will only run once per frame. */ - if (frameTicked || variableTiming) - TryIterateData(true); + bool variableTiming = _timingType == TimingType.Variable; + bool frameTicked = FrameTicked; + float tickDelta = (float)TickDelta * GetPhysicsTimeScale(); - if (frameTicked) + do { - // Tell predicted objecs to reconcile before OnTick. - NetworkManager.PredictionManager.ReconcileToStates(); + if (frameTicked) + { + using (_pm_OnPreTick.Auto()) + OnPreTick?.Invoke(); + } - using (_pm_OnTick.Auto()) - OnTick?.Invoke(); + /* This has to be called inside the loop because + * OnPreTick promises data hasn't been read yet. + * Therefor iterate must occur after OnPreTick. + * Iteration will only run once per frame. */ + if (frameTicked || variableTiming) + TryIterateData(true); - if (PhysicsMode == PhysicsMode.TimeManager && tickDelta > 0f) + if (frameTicked) { - using (_pm_OnPrePhysicsSimulation.Auto()) - OnPrePhysicsSimulation?.Invoke(tickDelta); - using (_pm_PhysicsSimulate.Auto()) - Physics.Simulate(tickDelta); - using (_pm_Physics2DSimulate.Auto()) - Physics2D.Simulate(tickDelta); - using (_pm_OnPostPhysicsSimulation.Auto()) - OnPostPhysicsSimulation?.Invoke(tickDelta); + // Tell predicted objecs to reconcile before OnTick. + NetworkManager.PredictionManager.ReconcileToStates(); + + using (_pm_OnTick.Auto()) + OnTick?.Invoke(); + + if (PhysicsMode == PhysicsMode.TimeManager && tickDelta > 0f) + { + using (_pm_OnPrePhysicsSimulation.Auto()) + OnPrePhysicsSimulation?.Invoke(tickDelta); + using (_pm_PhysicsSimulate.Auto()) + Physics.Simulate(tickDelta); + using (_pm_Physics2DSimulate.Auto()) + Physics2D.Simulate(tickDelta); + using (_pm_OnPostPhysicsSimulation.Auto()) + OnPostPhysicsSimulation?.Invoke(tickDelta); + } + + using (_pm_OnPostTick.Auto()) + OnPostTick?.Invoke(); + // After post tick send states. + NetworkManager.PredictionManager.SendStateUpdate(); + + /* If isClient this is the + * last tick during this loop. */ + bool lastTick = _elapsedTickTime < timePerSimulation * 2d; + if (isClient && lastTick) + TrySendPing(LocalTick + 1); + if (NetworkManager.IsServerStarted) + SendTimingAdjustment(); } - using (_pm_OnPostTick.Auto()) - OnPostTick?.Invoke(); - // After post tick send states. - NetworkManager.PredictionManager.SendStateUpdate(); - - /* If isClient this is the - * last tick during this loop. */ - bool lastTick = _elapsedTickTime < timePerSimulation * 2d; - if (isClient && lastTick) - TrySendPing(LocalTick + 1); - if (NetworkManager.IsServerStarted) - SendTimingAdjustment(); - } - - // Send out data. - if (frameTicked || variableTiming) - TryIterateData(false); + // Send out data. + if (frameTicked || variableTiming) + TryIterateData(false); - if (frameTicked) - { - _elapsedTickTime -= timePerSimulation; - Tick++; - LocalTick++; - } - } while (_elapsedTickTime >= timePerSimulation); + if (frameTicked) + { + _elapsedTickTime -= timePerSimulation; + Tick++; + LocalTick++; + } + } while (_elapsedTickTime >= timePerSimulation); + } } #region Tick conversions. @@ -795,12 +800,14 @@ public double GetTickPercentAsDouble() /// Returns the current elapsed amount for the next tick. /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public double GetTickElapsedAsDouble() => _elapsedTickTime; /// /// Returns the percentage of how far the TimeManager is into the next tick. /// Value will return between 0 and 100. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public byte GetTickPercentAsByte() { double result = GetTickPercentAsDouble(); @@ -811,6 +818,7 @@ public byte GetTickPercentAsByte() /// Converts a 0 to 100 byte value to a 0d to 1d percent value. /// This does not check for excessive byte values, such as anything over 100. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double GetTickPercentAsDouble(byte value) { return value / 100d; @@ -892,6 +900,7 @@ public double TicksToTime(TickType tickType = TickType.LocalTick) /// /// PreciseTick to convert. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public double TicksToTime(PreciseTick pt) { double tickTime = TicksToTime(pt.Tick); @@ -904,6 +913,7 @@ public double TicksToTime(PreciseTick pt) /// /// Ticks to convert. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public double TicksToTime(uint ticks) { return TickDelta * (double)ticks; @@ -993,16 +1003,28 @@ public double TimePassed(uint previousTick, bool allowNegative = false) /// /// Time to convert as decimal. /// - public uint TimeToTicks(double time, TickRounding rounding = TickRounding.RoundNearest) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint TimeToTicks(double time, double tickDelta, TickRounding rounding = TickRounding.RoundNearest) { - double result = time / TickDelta; + double result = time / tickDelta; if (rounding == TickRounding.RoundNearest) - return (uint)Math.Round(result); + return (uint)math.round(result); else if (rounding == TickRounding.RoundDown) - return (uint)Math.Floor(result); + return (uint)math.floor(result); else - return (uint)Math.Ceiling(result); + return (uint)math.ceil(result); + } + + /// + /// Converts time to ticks. + /// + /// Time to convert as decimal. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint TimeToTicks(double time, TickRounding rounding = TickRounding.RoundNearest) + { + return TimeToTicks(time, TickDelta, rounding); } /// @@ -1010,6 +1032,7 @@ public uint TimeToTicks(double time, TickRounding rounding = TickRounding.RoundN /// /// Time to convert as whole (milliseconds) /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public uint TimeToTicks(long time, TickRounding rounding = TickRounding.RoundNearest) { double dTime = (double)time / 1000d; @@ -1021,6 +1044,7 @@ public uint TimeToTicks(long time, TickRounding rounding = TickRounding.RoundNea /// /// Time to convert. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public PreciseTick TimeToPreciseTick(double time) => time.AsPreciseTick(TickDelta); /// diff --git a/Assets/FishNet/Runtime/Object/Prediction/MoveRates.cs b/Assets/FishNet/Runtime/Object/Prediction/MoveRates.cs index e647b7efc..befe7acb6 100644 --- a/Assets/FishNet/Runtime/Object/Prediction/MoveRates.cs +++ b/Assets/FishNet/Runtime/Object/Prediction/MoveRates.cs @@ -1,6 +1,10 @@ -using GameKit.Dependencies.Utilities; -using Unity.Profiling; +using System.Runtime.CompilerServices; +using FishNet.Utility.Extension; +using GameKit.Dependencies.Utilities; +using Unity.Mathematics; using UnityEngine; +using Unity.Profiling; +using UnityEngine.Jobs; using UnityEngine.Scripting; namespace FishNet.Object.Prediction @@ -11,6 +15,14 @@ namespace FishNet.Object.Prediction [Preserve] public struct MoveRates { + #region Private Profiler Markers + + private static readonly ProfilerMarker _pm_GetMoveRatesFull = new ProfilerMarker("MoveRates.GetMoveRates(float3, float3, quaternion, quaternion, float3, float3, float, float)"); + private static readonly ProfilerMarker _pm_GetMoveRatesVec = new ProfilerMarker("MoveRates.GetMoveRates(float3, float3, float, float)"); + private static readonly ProfilerMarker _pm_Move = new ProfilerMarker("MoveRates.Move(TransformAccess, TransformPropertiesFlag, float3, float, quaternion, float, float3, float, float, bool)"); + + #endregion + /// /// Rate at which to move Position. /// @@ -102,25 +114,22 @@ public MoveRates(float position, float rotation, float scale, float timeRemainin /// public bool IsScaleInstantValue => Scale == INSTANT_VALUE; - #region Private Profiler Markers - private static readonly ProfilerMarker _pm_GetMoveRatesFull = new("MoveRates.GetMoveRates(Vector3, Vector3, Quaternion, Quaternion, Vector3, Vector3, float, float)"); - private static readonly ProfilerMarker _pm_GetMoveRatesVec = new("MoveRates.GetMoveRates(Vector3, Vector3, float, float)"); - private static readonly ProfilerMarker _pm_Move = new("MoveRates.Move(Transform, TransformPropertiesFlag, Vector3, float, Quaternion, float, Vector3, float, float, bool)"); - #endregion - /// /// Sets all rates to instant. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetInstantRates() => Update(INSTANT_VALUE); /// /// Sets all rates to the same value. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(float value) => Update(value, value, value); /// /// Sets rates for each property. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(float position, float rotation, float scale) { Position = position; @@ -133,6 +142,7 @@ public void Update(float position, float rotation, float scale) /// /// Sets rates for each property. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(float position, float rotation, float scale, float timeRemaining) { Position = position; @@ -146,11 +156,13 @@ public void Update(float position, float rotation, float scale, float timeRemain /// /// Updates to new values. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(MoveRates moveRates) => Update(moveRates.Position, moveRates.Rotation, moveRates.Scale, moveRates.TimeRemaining); /// /// Updates to new values. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(MoveRatesCls moveRates) => Update(moveRates.Position, moveRates.Rotation, moveRates.Scale, moveRates.TimeRemaining); /// @@ -166,38 +178,91 @@ public void ResetState() /// /// Returns a new MoveRates based on previous values, and a transforms current position. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MoveRates GetWorldMoveRates(Transform from, Transform to, float duration, float teleportThreshold) { - return GetMoveRates(from.position, to.position, from.rotation, to.rotation, from.localScale, to.localScale, duration, teleportThreshold); + from.GetPositionAndRotation(out var fromPos, out var fromRot); + to.GetPositionAndRotation(out var toPos, out var toRot); + return GetMoveRates(fromPos, toPos, fromRot, toRot, from.localScale, to.localScale, duration, teleportThreshold); + } + + /// + /// Returns a new MoveRates based on previous values, and a transforms current position. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MoveRates GetWorldMoveRates(TransformAccess from, TransformAccess to, float duration, float teleportThreshold) + { + from.GetPositionAndRotation(out var fromPos, out var fromRot); + to.GetPositionAndRotation(out var toPos, out var toRot); + return GetMoveRates(fromPos, toPos, fromRot, toRot, from.localScale, to.localScale, duration, teleportThreshold); } /// /// Returns a new MoveRates based on previous values, and a transforms current position. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MoveRates GetLocalMoveRates(Transform from, Transform to, float duration, float teleportThreshold) { - return GetMoveRates(from.localPosition, to.localPosition, from.localRotation, to.localRotation, from.localScale, to.localScale, duration, teleportThreshold); + from.GetPositionAndRotation(out var fromPos, out var fromRot); + to.GetPositionAndRotation(out var toPos, out var toRot); + return GetMoveRates(fromPos, toPos, fromRot, toRot, from.localScale, to.localScale, duration, teleportThreshold); } - + + /// + /// Returns a new MoveRates based on previous values, and a transforms current position. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MoveRates GetLocalMoveRates(TransformAccess from, TransformAccess to, float duration, float teleportThreshold) + { + from.GetPositionAndRotation(out var fromPos, out var fromRot); + to.GetPositionAndRotation(out var toPos, out var toRot); + return GetMoveRates(fromPos, toPos, fromRot, toRot, from.localScale, to.localScale, duration, teleportThreshold); + } + /// /// Returns a new MoveRates based on previous values, and a transforms current position. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MoveRates GetWorldMoveRates(TransformProperties prevValues, Transform t, float duration, float teleportThreshold) { - return GetMoveRates(prevValues.Position, t.position, prevValues.Rotation, t.rotation, prevValues.Scale, t.localScale, duration, teleportThreshold); + t.GetPositionAndRotation(out var pos, out var rot); + return GetMoveRates(prevValues.Position, pos, prevValues.Rotation, rot, prevValues.Scale, t.localScale, duration, teleportThreshold); + } + + /// + /// Returns a new MoveRates based on previous values, and a transforms current position. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MoveRates GetWorldMoveRates(TransformProperties prevValues, TransformAccess t, float duration, float teleportThreshold) + { + t.GetPositionAndRotation(out var pos, out var rot); + return GetMoveRates(prevValues.Position, pos, prevValues.Rotation, rot, prevValues.Scale, t.localScale, duration, teleportThreshold); } /// /// Returns a new MoveRates based on previous values, and a transforms current position. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MoveRates GetLocalMoveRates(TransformProperties prevValues, Transform t, float duration, float teleportThreshold) { - return GetMoveRates(prevValues.Position, t.localPosition, prevValues.Rotation, t.localRotation, prevValues.Scale, t.localScale, duration, teleportThreshold); + t.GetLocalPositionAndRotation(out var pos, out var rot); + return GetMoveRates(prevValues.Position, pos, prevValues.Rotation, rot, prevValues.Scale, t.localScale, duration, teleportThreshold); + } + + /// + /// Returns a new MoveRates based on previous values, and a transforms current position. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MoveRates GetLocalMoveRates(TransformProperties prevValues, TransformAccess t, float duration, float teleportThreshold) + { + t.GetCorrectLocalPositionAndRotation(out var pos, out var rot); + return GetMoveRates(prevValues.Position, pos, prevValues.Rotation, rot, prevValues.Scale, t.localScale, duration, teleportThreshold); } /// /// Returns a new MoveRates based on previous values, and a transforms current position. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MoveRates GetMoveRates(TransformProperties prevValues, TransformProperties nextValues, float duration, float teleportThreshold) { return GetMoveRates(prevValues.Position, nextValues.Position, prevValues.Rotation, nextValues.Rotation, prevValues.Scale, nextValues.Scale, duration, teleportThreshold); @@ -206,7 +271,8 @@ public static MoveRates GetMoveRates(TransformProperties prevValues, TransformPr /// /// Returns a new MoveRates based on previous values, and a transforms current position. /// - public static MoveRates GetMoveRates(Vector3 fromPosition, Vector3 toPosition, Quaternion fromRotation, Quaternion toRotation, Vector3 fromScale, Vector3 toScale, float duration, float teleportThreshold) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MoveRates GetMoveRates(float3 fromPosition, float3 toPosition, quaternion fromRotation, quaternion toRotation, float3 fromScale, float3 toScale, float duration, float teleportThreshold) { using (_pm_GetMoveRatesFull.Auto()) { @@ -232,7 +298,8 @@ public static MoveRates GetMoveRates(Vector3 fromPosition, Vector3 toPosition, Q /// /// Gets a move rate for two Vector3s. /// - public static float GetMoveRate(Vector3 fromPosition, Vector3 toPosition, float duration, float teleportThreshold) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float GetMoveRate(float3 fromPosition, float3 toPosition, float duration, float teleportThreshold) { using (_pm_GetMoveRatesVec.Auto()) { @@ -258,7 +325,8 @@ public static float GetMoveRate(Vector3 fromPosition, Vector3 toPosition, float /// /// Gets a move rate for two Quaternions. /// - public static float GetMoveRate(Quaternion fromRotation, Quaternion toRotation, float duration) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float GetMoveRate(quaternion fromRotation, quaternion toRotation, float duration) { float rate = toRotation.GetRate(fromRotation, duration, out _); float rotationRate = rate.SetIfUnderTolerance(0.2f, INSTANT_VALUE); @@ -268,6 +336,7 @@ public static float GetMoveRate(Quaternion fromRotation, Quaternion toRotation, /// /// Moves transform to target values. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Move(Transform movingTransform, TransformProperties goalProperties, float delta, bool useWorldSpace) { if (!IsValid) @@ -276,10 +345,24 @@ public void Move(Transform movingTransform, TransformProperties goalProperties, Move(movingTransform, TransformPropertiesFlag.Everything, goalProperties.Position, Position, goalProperties.Rotation, Rotation, goalProperties.Scale, Scale, delta, useWorldSpace); TimeRemaining -= delta; } + + /// + /// Moves transform to target values. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Move(TransformAccess movingTransform, TransformProperties goalProperties, float delta, bool useWorldSpace) + { + if (!IsValid) + return; + + Move(movingTransform, TransformPropertiesFlag.Everything, goalProperties.Position, Position, goalProperties.Rotation, Rotation, goalProperties.Scale, Scale, delta, useWorldSpace); + TimeRemaining -= delta; + } /// /// Moves transform to target values. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Move(Transform movingTransform, TransformProperties goalProperties, TransformPropertiesFlag movedProperties, float delta, bool useWorldSpace) { if (!IsValid) @@ -288,11 +371,24 @@ public void Move(Transform movingTransform, TransformProperties goalProperties, Move(movingTransform, movedProperties, goalProperties.Position, Position, goalProperties.Rotation, Rotation, goalProperties.Scale, Scale, delta, useWorldSpace); TimeRemaining -= delta; } + + /// + /// Moves transform to target values. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Move(TransformAccess movingTransform, TransformProperties goalProperties, TransformPropertiesFlag movedProperties, float delta, bool useWorldSpace) + { + if (!IsValid) + return; + + Move(movingTransform, movedProperties, goalProperties.Position, Position, goalProperties.Rotation, Rotation, goalProperties.Scale, Scale, delta, useWorldSpace); + TimeRemaining -= delta; + } /// /// Moves transform to target values. /// - public static void Move(Transform movingTransform, TransformPropertiesFlag movedProperties, Vector3 posGoal, float posRate, Quaternion rotGoal, float rotRate, Vector3 scaleGoal, float scaleRate, float delta, bool useWorldSpace) + public static void Move(Transform movingTransform, TransformPropertiesFlag movedProperties, float3 posGoal, float posRate, quaternion rotGoal, float rotRate, float3 scaleGoal, float scaleRate, float delta, bool useWorldSpace) { using (_pm_Move.Auto()) { @@ -302,80 +398,117 @@ public static void Move(Transform movingTransform, TransformPropertiesFlag moved bool containsRotation = movedProperties.FastContains(TransformPropertiesFlag.Rotation); bool containsScale = movedProperties.FastContains(TransformPropertiesFlag.Scale); - //World space. + Vector3 pos; + Quaternion rot; if (useWorldSpace) + t.GetPositionAndRotation(out pos, out rot); + else t.GetLocalPositionAndRotation(out pos, out rot); + + if (containsPosition) + pos = MoveTowardsFast(pos, posGoal, posRate, delta); + + if (containsRotation) + rot = RotateTowardsFast(rot, rotGoal, rotRate, delta); + + if (containsPosition || containsRotation) + ApplyPosRot(t, useWorldSpace, pos, rot); + + if (containsScale) { - if (containsPosition) - { - if (posRate == INSTANT_VALUE) - { - t.position = posGoal; - } - else if (posRate == UNSET_VALUE) { } - else - { - t.position = Vector3.MoveTowards(t.position, posGoal, posRate * delta); - } - } - - if (containsRotation) - { - if (rotRate == INSTANT_VALUE) - { - t.rotation = rotGoal; - } - else if (rotRate == UNSET_VALUE) { } - else - { - t.rotation = Quaternion.RotateTowards(t.rotation, rotGoal, rotRate * delta); - } - } - } - //Local space. - else - { - if (containsPosition) - { - if (posRate == INSTANT_VALUE) - { - t.localPosition = posGoal; - } - else if (posRate == UNSET_VALUE) { } - else - { - t.localPosition = Vector3.MoveTowards(t.localPosition, posGoal, posRate * delta); - } - } - - if (containsRotation) - { - if (rotRate == INSTANT_VALUE) - { - t.localRotation = rotGoal; - } - else if (rotRate == UNSET_VALUE) { } - else - { - t.localRotation = Quaternion.RotateTowards(t.localRotation, rotGoal, rotRate * delta); - } - } + var scale = t.localScale; + t.localScale = MoveTowardsFast(scale, scaleGoal, scaleRate, delta); } + } + } + + /// + /// Moves transform to target values. + /// + public static void Move(TransformAccess movingTransform, TransformPropertiesFlag movedProperties, float3 posGoal, float posRate, quaternion rotGoal, float rotRate, float3 scaleGoal, float scaleRate, float delta, bool useWorldSpace) + { + using (_pm_Move.Auto()) + { + TransformAccess t = movingTransform; - //Scale always uses local. + bool containsPosition = movedProperties.FastContains(TransformPropertiesFlag.Position); + bool containsRotation = movedProperties.FastContains(TransformPropertiesFlag.Rotation); + bool containsScale = movedProperties.FastContains(TransformPropertiesFlag.Scale); + + Vector3 pos; + Quaternion rot; + if (useWorldSpace) + t.GetPositionAndRotation(out pos, out rot); + else t.GetCorrectLocalPositionAndRotation(out pos, out rot); + + if (containsPosition) + pos = MoveTowardsFast(pos, posGoal, posRate, delta); + + if (containsRotation) + rot = RotateTowardsFast(rot, rotGoal, rotRate, delta); + + if (containsPosition || containsRotation) + ApplyPosRot(t, useWorldSpace, pos, rot); + if (containsScale) { - if (scaleRate == INSTANT_VALUE) - { - t.localScale = scaleGoal; - } - else if (scaleRate == UNSET_VALUE) { } - else - { - t.localScale = Vector3.MoveTowards(t.localScale, scaleGoal, scaleRate * delta); - } + var scale = t.localScale; + t.localScale = MoveTowardsFast(scale, scaleGoal, scaleRate, delta); } } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static float3 MoveTowardsFast(float3 current, float3 goal, float rate, float delta) + { + if (rate == INSTANT_VALUE) return goal; + if (rate == UNSET_VALUE) return current; + + float3 diff = goal - current; + float maxDelta = math.max(0f, rate * delta); + + float lenSq = math.lengthsq(diff); + if (lenSq <= maxDelta * maxDelta) return goal; + + float invLen = math.rsqrt(lenSq); // 1 / sqrt(lenSq) + float t = math.min(maxDelta * invLen, 1f); + return current + diff * t; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static quaternion RotateTowardsFast(quaternion current, quaternion goal, float rate, float delta) + { + if (rate == INSTANT_VALUE) return goal; + if (rate == UNSET_VALUE) return current; + + float maxDelta = math.max(0f, rate * delta); + + float dot = math.dot(current.value, goal.value); + float c = math.saturate(math.abs(dot)); // min(|dot|, 1) + + float angle = math.degrees(2f * math.acos(c)); + if (angle <= maxDelta) return goal; + + float t = math.min(1f, maxDelta / angle); + return math.slerp(current, goal, t); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void ApplyPosRot(Transform t, bool worldSpace, float3 pos, quaternion rot) + { + if (worldSpace) + t.SetPositionAndRotation(pos, rot); + else + t.SetLocalPositionAndRotation(pos, rot); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void ApplyPosRot(TransformAccess t, bool worldSpace, float3 pos, quaternion rot) + { + if (worldSpace) + t.SetPositionAndRotation(pos, rot); + else + t.SetLocalPositionAndRotation(pos, rot); + } } /// @@ -429,39 +562,60 @@ public class MoveRatesCls : IResettable /// /// Sets all rates to instant. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetInstantRates() => _moveRates.SetInstantRates(); /// /// Sets all rates to the same value. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(float value) => _moveRates.Update(value); /// /// Updates values. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(float position, float rotation, float scale) => _moveRates.Update(position, rotation, scale); /// /// Updates values. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(float position, float rotation, float scale, float timeRemaining) => _moveRates.Update(position, rotation, scale, timeRemaining); /// /// Updaes values. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(MoveRatesCls mr) => _moveRates.Update(mr.Position, mr.Rotation, mr.Scale); - + /// /// Moves transform to target values. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Move(Transform movingTransform, TransformProperties goalProperties, float delta, bool useWorldSpace) => _moveRates.Move(movingTransform, goalProperties, delta, useWorldSpace); /// /// Moves transform to target values. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Move(TransformAccess movingTransform, TransformProperties goalProperties, float delta, bool useWorldSpace) => _moveRates.Move(movingTransform, goalProperties, delta, useWorldSpace); + + /// + /// Moves transform to target values. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Move(Transform movingTransform, TransformProperties goalProperties, TransformPropertiesFlag movedProperties, float delta, bool useWorldSpace) => _moveRates.Move(movingTransform, goalProperties, movedProperties, delta, useWorldSpace); + + /// + /// Moves transform to target values. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Move(TransformAccess movingTransform, TransformProperties goalProperties, TransformPropertiesFlag movedProperties, float delta, bool useWorldSpace) => _moveRates.Move(movingTransform, goalProperties, movedProperties, delta, useWorldSpace); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ResetState() => _moveRates.ResetState(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void InitializeState() { } } } \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Object/TransformProperties.cs b/Assets/FishNet/Runtime/Object/TransformProperties.cs index 7ee1d7ca7..34f26d0c5 100644 --- a/Assets/FishNet/Runtime/Object/TransformProperties.cs +++ b/Assets/FishNet/Runtime/Object/TransformProperties.cs @@ -1,6 +1,8 @@ using System; using GameKit.Dependencies.Utilities; +using Unity.Mathematics; using UnityEngine; +using UnityEngine.Jobs; namespace FishNet.Object { @@ -13,17 +15,17 @@ public static class TransformPropertiesExtensions /// public static TransformProperties CreateDirections(this TransformProperties prevProperties, TransformProperties nextProperties, uint divisor = 1) { - Vector3 position = (nextProperties.Position - prevProperties.Position) / divisor; + float3 position = (nextProperties.Position - prevProperties.Position) / divisor; - Quaternion rotation = nextProperties.Rotation.Subtract(prevProperties.Rotation); + quaternion rotation = nextProperties.Rotation.Subtract(prevProperties.Rotation); //If more than 1 tick span then get a portion of the rotation. if (divisor > 1) { float percent = 1f / (float)divisor; - rotation = Quaternion.Lerp(Quaternion.identity, nextProperties.Rotation, percent); + rotation = math.nlerp(quaternion.identity, nextProperties.Rotation, percent); } - Vector3 scale = (nextProperties.Scale - prevProperties.Scale) / divisor; + float3 scale = (nextProperties.Scale - prevProperties.Scale) / divisor; return new(position, rotation, scale); } @@ -52,12 +54,12 @@ public static void SetWorldProperties(this TransformProperties tp, Transform t) [Serializable] public class TransformPropertiesCls : IResettable { - public Vector3 Position; - public Quaternion Rotation; - public Vector3 LocalScale; + public float3 Position; + public quaternion Rotation; + public float3 LocalScale; public TransformPropertiesCls() { } - public TransformPropertiesCls(Vector3 position, Quaternion rotation, Vector3 localScale) + public TransformPropertiesCls(float3 position, quaternion rotation, float3 localScale) { Position = position; Rotation = rotation; @@ -68,7 +70,7 @@ public void InitializeState() { } public void ResetState() { - Update(Vector3.zero, Quaternion.identity, Vector3.zero); + Update(float3.zero, quaternion.identity, float3.zero); } public void Update(Transform t) @@ -86,12 +88,12 @@ public void Update(TransformProperties tp) Update(tp.Position, tp.Rotation, tp.Scale); } - public void Update(Vector3 position, Quaternion rotation) + public void Update(float3 position, quaternion rotation) { Update(position, rotation, LocalScale); } - public void Update(Vector3 position, Quaternion rotation, Vector3 localScale) + public void Update(float3 position, quaternion rotation, float3 localScale) { Position = position; Rotation = rotation; @@ -103,7 +105,7 @@ public void Update(Vector3 position, Quaternion rotation, Vector3 localScale) /// public bool ValuesEquals(TransformPropertiesCls properties) { - return Position == properties.Position && Rotation == properties.Rotation && LocalScale == properties.LocalScale; + return Position.Equals(properties.Position) && Rotation.Equals(properties.Rotation) && LocalScale.Equals(properties.LocalScale); } /// @@ -120,29 +122,64 @@ public TransformProperties ToStruct() [Serializable] public struct TransformProperties { - public Vector3 Position; - public Quaternion Rotation; + public float3 Position; + public quaternion Rotation; [Obsolete("Use Scale.")] //Remove V5 - public Vector3 LocalScale => Scale; - public Vector3 Scale; + public float3 LocalScale => Scale; + public float3 Scale; + public byte IsValidByte; + /// /// Becomes true when values are set through update or constructor. /// - public bool IsValid; + public bool IsValid + { + get => IsValidByte != 0; + set => IsValidByte = (byte)(value ? 1 : 0); + } - public TransformProperties(Vector3 position, Quaternion rotation, Vector3 localScale) + public TransformProperties(float3 position, quaternion rotation, float3 localScale) { Position = position; Rotation = rotation; Scale = localScale; - IsValid = true; + IsValidByte = 1; } /// - /// Creates a TransformProperties with default position and rotation, with Vector3.one scale. + /// Creates a TransformProperties with default position and rotation, with float3.one scale. /// - public static TransformProperties GetTransformDefault() => new(Vector3.zero, Quaternion.identity, Vector3.one); + public static TransformProperties GetTransformDefault() => new(float3.zero, quaternion.identity, new float3(1f, 1f, 1f)); + public static TransformProperties GetOffsetDefault() => new(float3.zero, quaternion.identity, float3.zero); + public static TransformProperties operator +(TransformProperties a, TransformProperties b) + { + if (!a.IsValid) return b; + if (!b.IsValid) return a; + return new TransformProperties( + a.Position + b.Position, + math.mul(a.Rotation, b.Rotation), + a.Scale * b.Scale); + } + + public static TransformProperties operator -(TransformProperties a, TransformProperties b) + { + if (!a.IsValid) return -b; + if (!b.IsValid) return a; + return new TransformProperties( + a.Position - b.Position, + math.mul(a.Rotation, math.inverse(b.Rotation)), + a.Scale / b.Scale); + } + + public static TransformProperties operator -(TransformProperties a) + { + return new TransformProperties( + -a.Position, + math.inverse(a.Rotation), + 1f / a.Scale); + } + public override string ToString() { return $"Position: {Position.ToString()}, Rotation {Rotation.ToString()}, Scale {Scale.ToString()}"; @@ -155,13 +192,20 @@ public TransformProperties(Transform t) : this(t.position, t.rotation, t.localSc public void ResetState() { - Update(Vector3.zero, Quaternion.identity, Vector3.zero); + Update(float3.zero, quaternion.identity, float3.zero); IsValid = false; } public void Update(Transform t) { - Update(t.position, t.rotation, t.localScale); + t.GetPositionAndRotation(out var pos, out var rot); + Update(pos, rot, t.localScale); + } + + public void Update(TransformAccess t) + { + t.GetPositionAndRotation(out var pos, out var rot); + Update(pos, rot, t.localScale); } public void Update(TransformProperties tp) @@ -169,12 +213,12 @@ public void Update(TransformProperties tp) Update(tp.Position, tp.Rotation, tp.Scale); } - public void Update(Vector3 position, Quaternion rotation) + public void Update(float3 position, quaternion rotation) { Update(position, rotation, Scale); } - public void Update(Vector3 position, Quaternion rotation, Vector3 localScale) + public void Update(float3 position, quaternion rotation, float3 localScale) { Position = position; Rotation = rotation; @@ -189,7 +233,7 @@ public void Update(Vector3 position, Quaternion rotation, Vector3 localScale) public void Add(TransformProperties tp) { Position += tp.Position; - Rotation *= tp.Rotation; + Rotation = math.mul(Rotation, tp.Rotation); Scale += tp.Scale; } @@ -200,7 +244,7 @@ public void Add(TransformProperties tp) public void Subtract(TransformProperties tp) { Position -= tp.Position; - Rotation *= Quaternion.Inverse(tp.Rotation); + Rotation = math.mul(Rotation, math.inverse(tp.Rotation)); Scale -= tp.Scale; } @@ -209,7 +253,7 @@ public void Subtract(TransformProperties tp) /// public bool ValuesEquals(TransformProperties properties) { - return Position == properties.Position && Rotation == properties.Rotation && Scale == properties.Scale; + return Position.Equals(properties.Position) && Rotation.Equals(properties.Rotation) && Scale.Equals(properties.Scale); } } } \ No newline at end of file From f38fe3aef0f824cd24dcd25eb72478fb9fad0ac5 Mon Sep 17 00:00:00 2001 From: belplaton Date: Wed, 17 Dec 2025 21:09:52 +0300 Subject: [PATCH 4/5] fix --- .../Component/TickSmoothing/TickSmoothingManager.Types.cs | 2 +- .../Component/TickSmoothing/TickSmoothingManager.cs | 7 ++++--- .../Dependencies/Utilities/Types/StripedRingQueue.cs | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs index c73f0890e..5b400d53d 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs @@ -4,7 +4,7 @@ using FishNet.Object; using FishNet.Object.Prediction; using FishNet.Utility.Extension; -using GameKit.Dependencies.Utilities; +using GameKit.Dependencies.Utilities.Types; using Unity.Burst; using Unity.Collections; using Unity.Jobs; diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs index 92603ce96..4d21bd490 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs @@ -7,6 +7,7 @@ using FishNet.Transporting; using FishNet.Utility.Extension; using GameKit.Dependencies.Utilities; +using GameKit.Dependencies.Utilities.Types; using Unity.Collections; using Unity.Jobs; using Unity.Jobs.LowLevel.Unsafe; @@ -326,12 +327,12 @@ private void OnDestroy() } while (_trackerTransformsPool.TryPop(out Transform trackerTransform)) - if (trackerTransform.gameObject) Destroy(trackerTransform.gameObject); + if (trackerTransform && trackerTransform.gameObject) Destroy(trackerTransform.gameObject); for (int i = 0; i < _indexToSmoother.Count; i++) { Transform trackerTransform = _trackerTaa[i]; - if (trackerTransform.gameObject) Destroy(trackerTransform.gameObject); + if (trackerTransform && trackerTransform.gameObject) Destroy(trackerTransform.gameObject); } if (_moveRates.IsCreated) _moveRates.Dispose(); @@ -795,7 +796,7 @@ private void ClientManager_OnClientConnectionState(ClientConnectionStateArgs arg using (_pm_ClientManager_OnClientConnectionState.Auto()) { while (_trackerTransformsPool.TryPop(out Transform trackerTransform)) - if (trackerTransform.gameObject) Destroy(trackerTransform.gameObject); + if (trackerTransform && trackerTransform.gameObject) Destroy(trackerTransform.gameObject); if (args.ConnectionState == LocalConnectionState.Started) ChangeSubscriptions(true); diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/StripedRingQueue.cs b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/StripedRingQueue.cs index 72b87937c..eccd23875 100644 --- a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/StripedRingQueue.cs +++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/StripedRingQueue.cs @@ -5,7 +5,7 @@ using Unity.Mathematics; using UnityEngine; -namespace GameKit.Dependencies.Utilities +namespace GameKit.Dependencies.Utilities.Types { /// /// A striped ring-buffer that stores N independent queues addressed by i = 0..N-1. From 786c9a0e9491ebccfe9c7261e63fea3061dc1414 Mon Sep 17 00:00:00 2001 From: belplaton Date: Wed, 17 Dec 2025 21:16:58 +0300 Subject: [PATCH 5/5] Update MovementSettingsDrawer.cs --- .../Component/TickSmoothing/Editor/MovementSettingsDrawer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.cs index d6bcf4df6..71bca8c17 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.cs @@ -26,6 +26,7 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten SerializedProperty adaptiveInterpolationValue = property.FindPropertyRelative("AdaptiveInterpolationValue"); SerializedProperty interpolationValue = property.FindPropertyRelative("InterpolationValue"); SerializedProperty smoothedProperties = property.FindPropertyRelative("SmoothedProperties"); + SerializedProperty useLocalSpace = property.FindPropertyRelative("UseLocalSpace"); SerializedProperty snapNonSmoothedProperties = property.FindPropertyRelative("SnapNonSmoothedProperties"); _propertyDrawer.DrawProperty(enableTeleport, "Enable Teleport"); @@ -37,6 +38,7 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten _propertyDrawer.DrawProperty(interpolationValue, "Interpolation Value", indent: 1); _propertyDrawer.DrawProperty(smoothedProperties, "Smoothed Properties"); + _propertyDrawer.DrawProperty(useLocalSpace, "Use Local Space"); if ((uint)smoothedProperties.intValue != (uint)TransformPropertiesFlag.Everything) _propertyDrawer.DrawProperty(snapNonSmoothedProperties, "Snap Non-Smoothed Properties", indent: 1);