From 9fed811995fbfa296d6ed0a0efc26dd1c0b22c6c Mon Sep 17 00:00:00 2001 From: belplaton Date: Wed, 17 Dec 2025 20:40:00 +0300 Subject: [PATCH 1/9] 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/9] 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/9] 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/9] 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/9] 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); From 912bd0ea8f9347aa60bb29d29fe609fb28fb1937 Mon Sep 17 00:00:00 2001 From: waterb <143908720+belplaton@users.noreply.github.com> Date: Sun, 28 Dec 2025 03:06:10 +0300 Subject: [PATCH 6/9] Rename profiler markers for clarity --- .../Component/TickSmoothing/TickSmoothingManager.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs index 4d21bd490..e315616e4 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs @@ -29,10 +29,10 @@ public partial class TickSmoothingManager : MonoBehaviour 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_ScheduleUpdateRealtimeInterpolation = new("TickSmoothingManager.ScheduleUpdateRealtimeInterpolation()"); + private static readonly ProfilerMarker _pm_ScheduleDiscardExcessiveTransformPropertiesQueue = new("TickSmoothingManager.ScheduleDiscardExcessiveTransformPropertiesQueue()"); + private static readonly ProfilerMarker _pm_ScheduleSetMoveRates = new("TickSmoothingManager.ScheduleSetMoveRates()"); + private static readonly ProfilerMarker _pm_ScheduleSetMovementMultiplier = new("TickSmoothingManager.ScheduleSetMovementMultiplier()"); 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()"); From e44b772a7cc357c39734fc4bf2409528438d8f5b Mon Sep 17 00:00:00 2001 From: waterb <143908720+belplaton@users.noreply.github.com> Date: Wed, 7 Jan 2026 04:29:40 +0300 Subject: [PATCH 7/9] Replace SetLocalPositionAndRotation with SetCorrectLocalPositionAndRotation --- Assets/FishNet/Runtime/Object/Prediction/MoveRates.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/FishNet/Runtime/Object/Prediction/MoveRates.cs b/Assets/FishNet/Runtime/Object/Prediction/MoveRates.cs index befe7acb6..c4a889705 100644 --- a/Assets/FishNet/Runtime/Object/Prediction/MoveRates.cs +++ b/Assets/FishNet/Runtime/Object/Prediction/MoveRates.cs @@ -507,7 +507,7 @@ static void ApplyPosRot(TransformAccess t, bool worldSpace, float3 pos, quaterni if (worldSpace) t.SetPositionAndRotation(pos, rot); else - t.SetLocalPositionAndRotation(pos, rot); + t.SetCorrectLocalPositionAndRotation(pos, rot); } } @@ -618,4 +618,4 @@ public class MoveRatesCls : IResettable [MethodImpl(MethodImplOptions.AggressiveInlining)] public void InitializeState() { } } -} \ No newline at end of file +} From 36b80cee9a6fd72bf92bc9e2b664945c5bd53705 Mon Sep 17 00:00:00 2001 From: belplaton Date: Wed, 21 Jan 2026 14:00:43 +0300 Subject: [PATCH 8/9] feat: add preprocessor markers "THREADED_TICKSMOOTHERS" --- .../Editor/MovementSettingsDrawer.Threaded.cs | 53 + .../MovementSettingsDrawer.Threaded.cs.meta | 11 + .../Editor/MovementSettingsDrawer.cs | 2 + .../Editor/NetworkTickSmootherEditor.cs | 3 +- .../MovementSettings.Threaded.cs | 60 ++ .../MovementSettings.Threaded.cs.meta | 11 + .../TickSmoothing/MovementSettings.cs | 10 +- .../TickSmoothing/NetworkTickSmoother.cs | 3 +- .../TickSmootherController.Threaded.cs | 332 ++++++ .../TickSmootherController.Threaded.cs.meta | 11 + .../TickSmoothing/TickSmootherController.cs | 291 +++--- .../TickSmoothingManager.Types.cs | 2 + .../TickSmoothingManager.Types.cs.meta | 11 + .../TickSmoothing/TickSmoothingManager.cs | 21 + .../TickSmoothingManager.cs.meta | 11 + .../UniversalTickSmoother.Threaded.cs | 983 ++++++++++++++++++ .../UniversalTickSmoother.Threaded.cs.meta | 11 + .../TickSmoothing/UniversalTickSmoother.cs | 170 +-- .../Runtime/Managing/NetworkManager.cs | 6 + .../Runtime/Managing/Timing/TimeManager.cs | 84 +- 20 files changed, 1828 insertions(+), 258 deletions(-) create mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs create mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs.meta create mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.Threaded.cs create mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.Threaded.cs.meta create mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.Threaded.cs create mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.Threaded.cs.meta create mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs.meta create mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs.meta create mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.Threaded.cs create mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.Threaded.cs.meta diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs new file mode 100644 index 000000000..bac3154cd --- /dev/null +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs @@ -0,0 +1,53 @@ +#if UNITY_EDITOR +using FishNet.Object; +using GameKit.Dependencies.Utilities; +using UnityEditor; +using UnityEngine; + +namespace FishNet.Component.Transforming.Beta.Editing +{ + #if THREADED_TICKSMOOTHERS + [CustomPropertyDrawer(typeof(MovementSettings))] + public class MovementSettingsDrawer : PropertyDrawer + { + private PropertyDrawerTool _propertyDrawer; + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + EditorGUI.BeginProperty(position, label, property); + + _propertyDrawer = new(position); + + // _propertyDrawer.DrawLabel(label, FontStyle.Bold); + + EditorGUI.indentLevel++; + + SerializedProperty enableTeleport = property.FindPropertyRelative("EnableTeleport"); + SerializedProperty teleportThreshold = property.FindPropertyRelative("TeleportThreshold"); + SerializedProperty adaptiveInterpolationValue = property.FindPropertyRelative("AdaptiveInterpolationValue"); + SerializedProperty interpolationValue = property.FindPropertyRelative("InterpolationValue"); + SerializedProperty smoothedProperties = property.FindPropertyRelative("SmoothedProperties"); + SerializedProperty snapNonSmoothedProperties = property.FindPropertyRelative("SnapNonSmoothedProperties"); + + _propertyDrawer.DrawProperty(enableTeleport, "Enable Teleport"); + if (enableTeleport.boolValue == true) + _propertyDrawer.DrawProperty(teleportThreshold, "Teleport Threshold", indent: 1); + + _propertyDrawer.DrawProperty(adaptiveInterpolationValue, "Adaptive Interpolation"); + if ((AdaptiveInterpolationType)adaptiveInterpolationValue.intValue == AdaptiveInterpolationType.Off) + _propertyDrawer.DrawProperty(interpolationValue, "Interpolation Value", indent: 1); + + _propertyDrawer.DrawProperty(smoothedProperties, "Smoothed Properties"); + if ((uint)smoothedProperties.intValue != (uint)TransformPropertiesFlag.Everything) + _propertyDrawer.DrawProperty(snapNonSmoothedProperties, "Snap Non-Smoothed Properties", indent: 1); + + _propertyDrawer.SetIndentToStarting(); + + EditorGUI.EndProperty(); + } + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) => _propertyDrawer.GetPropertyHeight(); + } + #endif +} +#endif \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs.meta b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs.meta new file mode 100644 index 000000000..cb9b31bd5 --- /dev/null +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8d3d0f9278ca7424f964926dc8a70515 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.cs index 71bca8c17..0298f005e 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.cs @@ -6,6 +6,7 @@ namespace FishNet.Component.Transforming.Beta.Editing { + #if !THREADED_TICKSMOOTHERS [CustomPropertyDrawer(typeof(MovementSettings))] public class MovementSettingsDrawer : PropertyDrawer { @@ -49,5 +50,6 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten public override float GetPropertyHeight(SerializedProperty property, GUIContent label) => _propertyDrawer.GetPropertyHeight(); } + #endif } #endif \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/NetworkTickSmootherEditor.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/NetworkTickSmootherEditor.cs index 533e6ae8f..74aa282af 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/NetworkTickSmootherEditor.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/NetworkTickSmootherEditor.cs @@ -31,7 +31,7 @@ public override void OnInspectorGUI() EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour((NetworkTickSmoother)target), typeof(NetworkTickSmoother), false); GUI.enabled = true; - EditorGUILayout.PropertyField(_favorPredictionNetworkTransform); + EditorGUILayout.LabelField("Initialization Settings", EditorStyles.boldLabel); EditorGUILayout.PropertyField(_initializationSettings); @@ -45,7 +45,6 @@ public override void OnInspectorGUI() EditorGUILayout.PropertyField(_spectatorMovementSettings); } - // EditorGUI.indentLevel--; serializedObject.ApplyModifiedProperties(); diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.Threaded.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.Threaded.cs new file mode 100644 index 000000000..c3abffd76 --- /dev/null +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.Threaded.cs @@ -0,0 +1,60 @@ +using FishNet.Object; +using UnityEngine; + +namespace FishNet.Component.Transforming.Beta +{ + #if THREADED_TICKSMOOTHERS + [System.Serializable] + public struct MovementSettings + { + /// + /// True to enable teleport threshold. + /// + [Tooltip("True to enable teleport threshold.")] + public bool EnableTeleport; + /// + /// How far the object must move between ticks to teleport rather than smooth. + /// + [Tooltip("How far the object must move between ticks to teleport rather than smooth.")] + [Range(0f, ushort.MaxValue)] + public float TeleportThreshold; + /// + /// Amount of adaptive interpolation to use. Adaptive interpolation increases interpolation with the local client's latency. Lower values of adaptive interpolation results in smaller interpolation increases. + /// In most cases adaptive interpolation is only used with prediction where objects might be affected by other moving objects. + /// + [Tooltip("Amount of adaptive interpolation to use. Adaptive interpolation increases interpolation with the local client's latency. Lower values of adaptive interpolation results in smaller interpolation increases. In most cases adaptive interpolation is only used with prediction where objects might be affected by other moving objects.")] + public AdaptiveInterpolationType AdaptiveInterpolationValue; + /// + /// Number of ticks to smooth over when not using adaptive interpolation. + /// + [Tooltip("Number of ticks to smooth over when not using adaptive interpolation.")] + public byte InterpolationValue; + /// + /// Properties to smooth. Any value not selected will become offset with every movement. + /// + [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.")] + public bool SnapNonSmoothedProperties; + + public MovementSettings(bool unityReallyNeedsToSupportParameterlessInitializersOnStructsAlready) + { + EnableTeleport = false; + TeleportThreshold = 0f; + AdaptiveInterpolationValue = AdaptiveInterpolationType.Off; + InterpolationValue = 2; + SmoothedProperties = TransformPropertiesFlag.Everything; + UseLocalSpace = false; + SnapNonSmoothedProperties = false; + } + } + #endif +} diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.Threaded.cs.meta b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.Threaded.cs.meta new file mode 100644 index 000000000..24da146ea --- /dev/null +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.Threaded.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 99cd0db221a6bd94288e6cc02aabca8f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.cs index 135913c8f..e033d7cb8 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.cs @@ -3,6 +3,7 @@ namespace FishNet.Component.Transforming.Beta { + #if !THREADED_TICKSMOOTHERS [System.Serializable] public struct MovementSettings { @@ -34,11 +35,6 @@ 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.")] @@ -51,8 +47,8 @@ public MovementSettings(bool unityReallyNeedsToSupportParameterlessInitializersO AdaptiveInterpolationValue = AdaptiveInterpolationType.Off; InterpolationValue = 2; SmoothedProperties = TransformPropertiesFlag.Everything; - UseLocalSpace = false; SnapNonSmoothedProperties = false; } } -} + #endif +} \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/NetworkTickSmoother.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/NetworkTickSmoother.cs index 1f6cb705d..2e1991130 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/NetworkTickSmoother.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/NetworkTickSmoother.cs @@ -1,7 +1,6 @@ using FishNet.Object; using GameKit.Dependencies.Utilities; using UnityEngine; -using UnityEngine.Serialization; namespace FishNet.Component.Transforming.Beta { @@ -53,7 +52,7 @@ private void OnDestroy() public override void OnStartClient() { RetrieveControllers(); - + _initializationSettings.SetNetworkedRuntimeValues(initializingNetworkBehaviour: this, graphicalTransform: transform, _favorPredictionNetworkTransform); SmootherController.Initialize(_initializationSettings, _controllerMovementSettings, _spectatorMovementSettings); diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.Threaded.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.Threaded.cs new file mode 100644 index 000000000..9acc4914b --- /dev/null +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.Threaded.cs @@ -0,0 +1,332 @@ +using FishNet.Managing.Timing; +using FishNet.Object; +using GameKit.Dependencies.Utilities; +using UnityEngine; +using Unity.Profiling; + +namespace FishNet.Component.Transforming.Beta +{ + #if THREADED_TICKSMOOTHERS + /// + /// Smoothes this object between ticks. + /// + /// This can be configured to smooth over a set interval of time, or to smooth adaptively and make path corrections for prediction. + public class TickSmootherController : IResettable + { + #region Public. + // /// + // /// 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(); + /// + /// + private MovementSettings _ownerMovementSettings = new(); + /// + /// + private MovementSettings _spectatorMovementSettings = new(); + /// + /// True if OnDestroy has been called. + /// + private bool _destroyed; + /// + /// Cached timeManager reference. + /// + private TimeManager _timeManager; + /// + /// NetworkBehaviour which initialized this object. Value may be null when initialized for an Offline smoother. + /// + private NetworkBehaviour _initializingNetworkBehaviour; + /// + /// TickSmoothingManager. + /// + private TickSmoothingManager _tickSmoothingManager; + /// + /// Transform which initialized this object. + /// + private Transform _graphicalTransform; + /// + /// True if initialized with a null NetworkBehaviour. + /// + private bool _initializedOffline; + /// + /// True if subscribed to events used for adaptiveInterpolation. + /// + private bool _subscribedToAdaptiveEvents; + /// + /// True if currently subscribed to events. + /// + private bool _subscribed; + /// + /// True if initialized. + /// + private bool _isInitialized; + #endregion + + public void Initialize(InitializationSettings initializationSettings, MovementSettings ownerSettings, MovementSettings spectatorSettings) + { + _tickSmoothingManager = + initializationSettings.InitializingNetworkBehaviour?.NetworkManager.TickSmoothingManager ?? + InstanceFinder.NetworkManager.TickSmoothingManager; + _initializingNetworkBehaviour = initializationSettings.InitializingNetworkBehaviour; + _graphicalTransform = initializationSettings.GraphicalTransform; + + _initializationSettings = initializationSettings; + _ownerMovementSettings = ownerSettings; + _spectatorMovementSettings = spectatorSettings; + + _initializedOffline = initializationSettings.InitializingNetworkBehaviour == null; + + _isInitialized = true; + } + + public void OnDestroy() + { + var tsm = _tickSmoothingManager; + if (tsm != null) + tsm.Unregister(this); + + // ChangeSubscriptions(false); + // StoreSmoother(); + _destroyed = true; + _isInitialized = false; + } + + public void StartSmoother() + { + if (!_isInitialized) + return; + + bool canStart = _initializedOffline ? StartOffline() : StartOnline(); + + if (!canStart) + return; + + // RetrieveSmoothers(); + // + // UniversalSmoother.Initialize(_initializationSettings, _ownerMovementSettings, _spectatorMovementSettings); + // + // UniversalSmoother.StartSmoother(); + + var tsm = _tickSmoothingManager; + if (tsm != null) + tsm.Register(this, _initializationSettings, _ownerMovementSettings, _spectatorMovementSettings); + + bool StartOnline() + { + NetworkBehaviour nb = _initializingNetworkBehaviour; + + SetTimeManager(nb.TimeManager); + + return true; + } + + bool StartOffline() + { + if (_timeManager == null) + return false; + + return true; + } + } + + public void StopSmoother() + { + var tsm = _tickSmoothingManager; + if (tsm != null) + tsm.Unregister(this); + + if (!_initializedOffline) + StopOnline(); + + // if (UniversalSmoother != null) + // UniversalSmoother.StopSmoother(); + + void StopOnline() + { + SetTimeManager(tm: null); + } + + // Intentionally left blank. + // 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(); + // } + + // /// + // /// Sets a target transform to follow. + // /// + // public void SetTargetTransform(Transform value) + // { + // Transform currentTargetTransform = _initializationSettings.TargetTransform; + // + // if (value == currentTargetTransform) + // return; + // + // bool clientStartCalled = (_initializedOffline && _timeManager != null) || (_initializingNetworkBehaviour != null && _initializingNetworkBehaviour.OnStartClientCalled); + // + // bool previousTargetTransformIsValid = (currentTargetTransform != null); + // + // // If target is different and old is not null then reset. + // if (previousTargetTransformIsValid && clientStartCalled) + // OnStopClient(); + // + // _initializationSettings.TargetTransform = value; + // if (previousTargetTransformIsValid && clientStartCalled) + // OnStartClient(); + // } + + /// + /// Sets a new PredictionManager to use. + /// + public void SetTimeManager(TimeManager tm) + { + if (tm == _timeManager) + return; + + // Unsub from current. + // 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; + // } + // } + // } + + public void ResetState() + { + _initializationSettings = default; + _ownerMovementSettings = default; + _spectatorMovementSettings = default; + + _destroyed = false; + _timeManager = null; + _initializingNetworkBehaviour = null; + _graphicalTransform = null; + + _subscribed = false; + _subscribedToAdaptiveEvents = false; + + _isInitialized = false; + } + + public void InitializeState() { } + } + #endif +} \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.Threaded.cs.meta b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.Threaded.cs.meta new file mode 100644 index 000000000..d6c4d59d0 --- /dev/null +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.Threaded.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c757af1b1b164aa4c9d239dd72e15b93 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.cs index 4bf63b15c..2544587da 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.cs @@ -1,11 +1,13 @@ -using FishNet.Managing.Timing; +using FishNet.Managing.Predicting; +using FishNet.Managing.Timing; using FishNet.Object; using GameKit.Dependencies.Utilities; -using UnityEngine; using Unity.Profiling; +using UnityEngine; namespace FishNet.Component.Transforming.Beta { + #if !THREADED_TICKSMOOTHERS /// /// Smoothes this object between ticks. /// @@ -13,28 +15,19 @@ 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(); /// /// - private MovementSettings _ownerMovementSettings = new(); + private MovementSettings _controllerMovementSettings = new(); /// /// private MovementSettings _spectatorMovementSettings = new(); @@ -51,10 +44,6 @@ public class TickSmootherController : IResettable /// private NetworkBehaviour _initializingNetworkBehaviour; /// - /// TickSmoothingManager. - /// - private TickSmoothingManager _tickSmoothingManager; - /// /// Transform which initialized this object. /// private Transform _graphicalTransform; @@ -74,18 +63,18 @@ 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) + public void Initialize(InitializationSettings initializationSettings, MovementSettings controllerSettings, MovementSettings spectatorSettings) { - _tickSmoothingManager = - initializationSettings.InitializingNetworkBehaviour?.NetworkManager.TickSmoothingManager ?? - InstanceFinder.NetworkManager.TickSmoothingManager; _initializingNetworkBehaviour = initializationSettings.InitializingNetworkBehaviour; _graphicalTransform = initializationSettings.GraphicalTransform; _initializationSettings = initializationSettings; - _ownerMovementSettings = ownerSettings; + _controllerMovementSettings = controllerSettings; _spectatorMovementSettings = spectatorSettings; _initializedOffline = initializationSettings.InitializingNetworkBehaviour == null; @@ -95,12 +84,8 @@ public void Initialize(InitializationSettings initializationSettings, MovementSe public void OnDestroy() { - var tsm = _tickSmoothingManager; - if (tsm != null) - tsm.Unregister(this); - - // ChangeSubscriptions(false); - // StoreSmoother(); + ChangeSubscriptions(false); + StoreSmoother(); _destroyed = true; _isInitialized = false; } @@ -115,15 +100,11 @@ public void StartSmoother() if (!canStart) return; - // RetrieveSmoothers(); - // - // UniversalSmoother.Initialize(_initializationSettings, _ownerMovementSettings, _spectatorMovementSettings); - // - // UniversalSmoother.StartSmoother(); + RetrieveSmoothers(); + + UniversalSmoother.Initialize(_initializationSettings, _controllerMovementSettings, _spectatorMovementSettings); - var tsm = _tickSmoothingManager; - if (tsm != null) - tsm.Register(this, _initializationSettings, _ownerMovementSettings, _spectatorMovementSettings); + UniversalSmoother.StartSmoother(); bool StartOnline() { @@ -145,15 +126,13 @@ bool StartOffline() public void StopSmoother() { - var tsm = _tickSmoothingManager; - if (tsm != null) - tsm.Unregister(this); - + ChangeSubscriptions(subscribe: false); + if (!_initializedOffline) StopOnline(); - - // if (UniversalSmoother != null) - // UniversalSmoother.StopSmoother(); + + if (UniversalSmoother != null) + UniversalSmoother.StopSmoother(); void StopOnline() { @@ -164,64 +143,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. @@ -255,63 +234,62 @@ public void SetTimeManager(TimeManager tm) return; // Unsub from current. - // ChangeSubscriptions(false); + ChangeSubscriptions(false); //Sub to newest. _timeManager = tm; - // ChangeSubscriptions(true); + 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; - // } - // } - // } - + + /// + /// 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 = _controllerMovementSettings.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; - _ownerMovementSettings = default; + _controllerMovementSettings = default; _spectatorMovementSettings = default; _destroyed = false; @@ -327,4 +305,5 @@ public void ResetState() public void InitializeState() { } } + #endif } \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs index 5b400d53d..3be6e893f 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs @@ -15,6 +15,7 @@ namespace FishNet.Component.Transforming.Beta { + #if THREADED_TICKSMOOTHERS public partial class TickSmoothingManager { #region Types. @@ -1312,4 +1313,5 @@ public static TransformProperties GetTrackerWorldProperties(TransformAccess trac return new TransformProperties(pos, rot, scl); } } + #endif } \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs.meta b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs.meta new file mode 100644 index 000000000..2f6f484ad --- /dev/null +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2687216adf6641b3a037f7cfcdad52f1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs index e315616e4..6a4523ae3 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs @@ -17,6 +17,7 @@ namespace FishNet.Component.Transforming.Beta { + #if THREADED_TICKSMOOTHERS public partial class TickSmoothingManager : MonoBehaviour { #region Private. @@ -87,6 +88,10 @@ public partial class TickSmoothingManager : MonoBehaviour /// Index to TickSmootherController and NetworkBehaviours lookup. /// private readonly List _indexToNetworkBehaviour = new(); + /// + /// Index to TickSmootherController and redictionNetworkTransform lookup. + /// + private readonly List _indexToPredictionNetworkTransform = new(); /// /// Index to MoveRate lookup. @@ -375,6 +380,7 @@ private void OnDestroy() if (_trackerTaa.isCreated) _trackerTaa.Dispose(); _indexToNetworkBehaviour.Clear(); + _indexToPredictionNetworkTransform.Clear(); _indexToSmoother.Clear(); _lookup.Clear(); @@ -418,6 +424,14 @@ public void Register(TickSmootherController smoother, InitializationSettings ini _lookup[smoother] = index; _indexToSmoother.Add(smoother); _indexToNetworkBehaviour.Add(initializationSettings.InitializingNetworkBehaviour); + _indexToPredictionNetworkTransform.Add( + initializationSettings.FavorPredictionNetworkTransform && + initializationSettings.InitializingNetworkBehaviour != null && + initializationSettings.InitializingNetworkBehaviour.NetworkObject != null && + initializationSettings.InitializingNetworkBehaviour.NetworkObject.IsRigidbodyPredictionType + ? initializationSettings.InitializingNetworkBehaviour.NetworkObject.PredictionNetworkTransform + : null + ); _moveRates.Add(new MoveRates(MoveRates.UNSET_VALUE)); _ownerSettings.Add(ownerSettings); @@ -515,9 +529,12 @@ public void Unregister(TickSmootherController smoother) _lookup[movedSmoother] = index; var movedNetworkBehaviour = _indexToNetworkBehaviour[last]; _indexToNetworkBehaviour[index] = movedNetworkBehaviour; + var movedPredictionNetworkTransform = _indexToPredictionNetworkTransform[last]; + _indexToPredictionNetworkTransform[index] = movedPredictionNetworkTransform; } _indexToNetworkBehaviour.RemoveAt(last); + _indexToPredictionNetworkTransform.RemoveAt(last); _indexToSmoother.RemoveAt(last); _lookup.Remove(smoother); @@ -877,8 +894,11 @@ private void TimeManager_OnPreTick() { Transform graphicalTransform = _graphicalTaa[i]; NetworkBehaviour networkBehaviour = _indexToNetworkBehaviour[i]; + NetworkTransform predictionNetworkTransform = _indexToPredictionNetworkTransform[i]; _canSmoothMask[i] = (byte)(graphicalTransform != null && + (predictionNetworkTransform == null || + !predictionNetworkTransform.DoSettingsAllowSmoothing()) && _networkManager.IsClientStarted ? 1 : 0); _useOwnerSettingsMask[i] = @@ -1334,4 +1354,5 @@ private static int ComputeBatchSize(int length, int minBatch = 1, int maxBatch = return Mathf.Clamp(batch, minBatch, maxBatch); } } + #endif } diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs.meta b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs.meta new file mode 100644 index 000000000..80b9c5832 --- /dev/null +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bed04a80458a35443b1a283848e3d1c9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.Threaded.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.Threaded.cs new file mode 100644 index 000000000..b6b3d47f5 --- /dev/null +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.Threaded.cs @@ -0,0 +1,983 @@ +using System; +using FishNet.Managing; +using FishNet.Managing.Timing; +using FishNet.Object; +using FishNet.Object.Prediction; +using FishNet.Utility.Extension; +using GameKit.Dependencies.Utilities; +using UnityEngine; +using UnityEngine.Profiling; +using Unity.Profiling; +using UnityEngine.Scripting; + +namespace FishNet.Component.Transforming.Beta +{ + #if THREADED_TICKSMOOTHERS + /// + /// This class is under regular development and it's API may change at any time. + /// + public sealed class UniversalTickSmoother : IResettable + { + #region Public. + /// + /// True if currently initialized. + /// + public bool IsInitialized { get; private set; } + #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. + /// + private MoveRates _moveRates = new(); + /// + /// True if a pretick occurred since last postTick. + /// + private bool _preTicked; + /// + /// World values of the graphical after it's been aligned to initialized values in PreTick. + /// + private TransformProperties _trackerPreTickWorldValues; + /// + /// World values of the graphical after it's been aligned to initialized values in PreTick. + /// + private TransformProperties _graphicsPreTickWorldValues; + /// + /// Cached value of adaptive interpolation value. + /// + private AdaptiveInterpolationType _cachedAdaptiveInterpolationValue; + /// + /// Cached value of flat interpolation value. + /// + private byte _cachedInterpolationValue; + /// + /// Cached properties to smooth of the graphical. + /// + private TransformPropertiesFlag _cachedSmoothedProperties; + /// + /// Cached value of snapping non-smoothed properties. + /// + private bool _cachedSnapNonSmoothedProperties; + /// + /// Squared distance target must travel to cause a teleport. + /// + private float _cachedTeleportThreshold; + /// + /// True if to detach on network start. + /// + private bool _detachOnStart; + /// + /// True to re-attach on network stop. + /// + private bool _attachOnStop; + /// + /// 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 bool _moveImmediately; + /// + /// Transform the graphics should follow. + /// + private Transform _targetTransform; + /// + /// Cached value of the object to smooth. + /// + private Transform _graphicalTransform; + /// + /// 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 Transform _trackerTransform; + /// + /// TimeManager tickDelta. + /// + private float _tickDelta; + /// + /// NetworkBehaviour this is initialized for. Value may be null. + /// + private NetworkBehaviour _initializingNetworkBehaviour; + /// + /// TimeManager this is initialized for. + /// + private TimeManager _initializingTimeManager; + /// + /// Value to multiply movement by. This is used to reduce or increase the rate the movement buffer is consumed. + /// + private float _movementMultiplier = 1f; + /// + /// TransformProperties to move towards. + /// + private BasicQueue _transformProperties; + /// + /// True if to smooth using owner settings, false for spectator settings. + /// This is only used for performance gains. + /// + private bool _useOwnerSettings; + /// + /// Last tick this was teleported on. + /// + private uint _teleportedTick = TimeManager.UNSET_TICK; + /// + /// Current interpolation value, be it a flat value or adaptive. + /// + private byte _realtimeInterpolation; + /// + /// Settings to use for owners. + /// + private MovementSettings _controllerMovementSettings; + /// + /// Settings to use for spectators. + /// + private MovementSettings _spectatorMovementSettings; + /// + /// True if moving has started and has not been stopped. + /// + private bool _isMoving; + /// + /// NetworkTransform used when prediction type is set to other. + /// + private NetworkTransform _predictionNetworkTransform; + #endregion + + #region Const. + /// + /// Maximum allowed entries to be queued over the interpolation amount. + /// + private const int MAXIMUM_QUEUED_OVER_INTERPOLATION = 3; + #endregion + + [Preserve] + public UniversalTickSmoother() { } + + ~UniversalTickSmoother() + { + // This is a last resort for if something didnt deinitialize right. + ResetState(); + } + + [Obsolete("This method is no longer used. Use TrySetGraphicalTrackerLocalProperties(TransformProperties).")] // Remove V5 + public void SetGraphicalInitializedOffsetValues(TransformProperties value) { } + + [Obsolete("This method is no longer used. Use GetGraphicalTrackerLocalProperties.")] // Remove V5 + public TransformProperties GetGraphicalInitializedOffsetValues() => default; + + /// + /// 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(TransformProperties? localValues) + { + if (_trackerTransform == null || localValues == null) + { + _queuedTrackerProperties = localValues; + return false; + } + + + _trackerTransform.SetLocalProperties(localValues.Value); + return true; + } + + [Obsolete("This method is no longer used. Use TrySetGraphicalTrackerLocalProperties(TransformProperties).")] // Remove V5 + public void SetAdditionalGraphicalOffsetValues(TransformProperties localValues) { } + + [Obsolete("This method is no longer used. Use GetGraphicalTrackerLocalProperties.")] // Remove V5 + public TransformProperties GetAdditionalGraphicalOffsetValues() => default; + + public TransformProperties GetGraphicalTrackerLocalProperties() + { + if (_trackerTransform != null) + return new(_trackerTransform.localPosition, _trackerTransform.localRotation, _trackerTransform.localScale); + if (_queuedTrackerProperties != null) + return _queuedTrackerProperties.Value; + + // Fall through. + NetworkManager manager = _initializingNetworkBehaviour == null ? null : _initializingNetworkBehaviour.NetworkManager; + manager.LogWarning($"Graphical tracker properties cannot be returned because tracker is not setup yet, and no setup properties have been specified. Use TrySetGraphicalTrackerProperties to set setup properties or call this method after IsInitialized is true."); + return default; + } + + /// + /// Properties for the tracker which are queued to be set when the tracker is setup. + /// + private TransformProperties? _queuedTrackerProperties; + + /// + /// Updates the smoothedProperties value. + /// + /// New value. + /// True if updating owner smoothing settings, or updating settings on an offline smoother. False to update spectator settings + public void SetSmoothedProperties(TransformPropertiesFlag value, bool forController) + { + _controllerMovementSettings.SmoothedProperties = value; + SetCaches(forController); + } + + /// + /// Updates the interpolationValue when not using adaptive interpolation. Calling this method will also disable adaptive interpolation. + /// + /// + public void SetInterpolationValue(byte value, bool forOwnerOrOfflineSmoother) => SetInterpolationValue(value, forOwnerOrOfflineSmoother, unsetAdaptiveInterpolation: true); + + /// + /// Updates the interpolationValue when not using adaptive interpolation. Calling this method will also disable adaptive interpolation. + /// + private void SetInterpolationValue(byte value, bool forOwnerOrOfflineSmoother, bool unsetAdaptiveInterpolation) + { + if (value < 1) + value = 1; + + if (forOwnerOrOfflineSmoother) + _controllerMovementSettings.InterpolationValue = value; + else + _spectatorMovementSettings.InterpolationValue = value; + + if (unsetAdaptiveInterpolation) + SetAdaptiveInterpolation(AdaptiveInterpolationType.Off, forOwnerOrOfflineSmoother); + } + + /// + /// Updates the adaptiveInterpolation value. + /// + /// New value. + public void SetAdaptiveInterpolation(AdaptiveInterpolationType value, bool forOwnerOrOfflineSmoother) + { + if (forOwnerOrOfflineSmoother) + _controllerMovementSettings.AdaptiveInterpolationValue = value; + else + _spectatorMovementSettings.AdaptiveInterpolationValue = value; + + UpdateRealtimeInterpolation(); + } + + public void Initialize(InitializationSettings initializationSettings, MovementSettings ownerSettings, MovementSettings spectatorSettings) + { + ResetState(); + + Transform graphicalTransform = initializationSettings.GraphicalTransform; + Transform targetTransform = initializationSettings.TargetTransform; + + if (!TransformsAreValid(graphicalTransform, targetTransform)) + return; + + _transformProperties = CollectionCaches.RetrieveBasicQueue(); + _controllerMovementSettings = ownerSettings; + _spectatorMovementSettings = spectatorSettings; + + /* 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) + { + _controllerMovementSettings.SmoothedProperties &= ~TransformPropertiesFlag.Scale; + _spectatorMovementSettings.SmoothedProperties &= ~TransformPropertiesFlag.Scale; + } + + _initializingNetworkBehaviour = initializationSettings.InitializingNetworkBehaviour; + _initializingTimeManager = initializationSettings.InitializingTimeManager; + _targetTransform = targetTransform; + _graphicalTransform = graphicalTransform; + _tickDelta = (float)initializationSettings.InitializingTimeManager.TickDelta; + _detachOnStart = initializationSettings.DetachOnStart; + _attachOnStop = initializationSettings.AttachOnStop; + _moveImmediately = initializationSettings.MoveImmediately; + + if (initializationSettings.FavorPredictionNetworkTransform && _initializingNetworkBehaviour != null) + { + NetworkObject networkObject = _initializingNetworkBehaviour.NetworkObject; + if (!networkObject.IsRigidbodyPredictionType) + _predictionNetworkTransform = networkObject.PredictionNetworkTransform; + else + _predictionNetworkTransform = null; + } + else + { + _predictionNetworkTransform = null; + } + + SetCaches(GetUseOwnerSettings()); + + //Use set method as it has sanity checks. + SetInterpolationValue(_controllerMovementSettings.InterpolationValue, forOwnerOrOfflineSmoother: true, unsetAdaptiveInterpolation: false); + SetInterpolationValue(_spectatorMovementSettings.InterpolationValue, forOwnerOrOfflineSmoother: false, unsetAdaptiveInterpolation: false); + + SetAdaptiveInterpolation(_controllerMovementSettings.AdaptiveInterpolationValue, forOwnerOrOfflineSmoother: true); + SetAdaptiveInterpolation(_spectatorMovementSettings.AdaptiveInterpolationValue, forOwnerOrOfflineSmoother: false); + + SetupTrackerTransform(); + /* This is called after setting up the tracker transform in the scenario + * the user set additional offsets before this was initialized. */ + if (_queuedTrackerProperties != null) + TrySetGraphicalTrackerLocalProperties(_queuedTrackerProperties.Value); + + void SetupTrackerTransform() + { + _trackerTransform = new GameObject($"{_graphicalTransform.name}_Tracker").transform; + + if (_detachOnStart) + { + _trackerTransform.SetParent(_targetTransform); + } + else + { + Transform trackerParent = _graphicalTransform.IsChildOf(targetTransform) ? _graphicalTransform.parent : targetTransform; + _trackerTransform.SetParent(trackerParent); + } + + _trackerTransform.SetWorldPositionRotationAndScale(_targetTransform.position, _targetTransform.rotation, graphicalTransform.localScale); + } + + IsInitialized = true; + } + + /// + /// Returns if configured transforms are valid. + /// + /// + private 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; + } + + /// + /// Returns true if to use adaptive interpolation. + /// + /// + private bool GetUseAdaptiveInterpolation() + { + if (_cachedAdaptiveInterpolationValue == AdaptiveInterpolationType.Off || _initializingTimeManager.NetworkManager.IsServerOnlyStarted) + return false; + + return true; + } + + /// + /// Gets if to use owner values. + /// + /// OwnerSettings can be used to read determine this as both owner and spectator settings will have the name InitializingNetworkBehaviour. + /// + private bool GetUseOwnerSettings() + { + /* No networkBehaviour indicates an offline smoother. + * The offline smoothers use owner settings. */ + if (_initializingNetworkBehaviour == null) + return true; + + if (_initializingNetworkBehaviour.IsController) + return true; + + return false; + // return _initializingNetworkBehaviour.IsOwner || !_initializingNetworkBehaviour.Owner.IsValid; + } + + /// + /// Updates OwnerDuringPreTick value and caches if needed. + /// + private void SetCaches(bool useOwnerSettings) + { + MovementSettings movementSettings = useOwnerSettings ? _controllerMovementSettings : _spectatorMovementSettings; + + _cachedSmoothedProperties = movementSettings.SmoothedProperties; + _cachedSnapNonSmoothedProperties = movementSettings.SnapNonSmoothedProperties; + _cachedAdaptiveInterpolationValue = movementSettings.AdaptiveInterpolationValue; + _cachedInterpolationValue = movementSettings.InterpolationValue; + + _cachedTeleportThreshold = movementSettings.EnableTeleport ? movementSettings.TeleportThreshold * movementSettings.TeleportThreshold : MoveRates.UNSET_VALUE; + } + + /// + /// Deinitializes this smoother resetting values. + /// + public void Deinitialize() + { + ResetState(); + IsInitialized = false; + } + + /// + /// Updates interpolation based on localClient latency when using adaptive interpolation, or uses set value when adaptive interpolation is off. + /// + public void UpdateRealtimeInterpolation() + { + using (_pm_UpdateRealtimeInterpolation.Auto()) + { + /* If not networked, server is started, or if not + * using adaptive interpolation then use + * flat interpolation.*/ + if (!GetUseAdaptiveInterpolation()) + { + _realtimeInterpolation = _cachedInterpolationValue; + return; + } + + /* If here then adaptive interpolation is being calculated. */ + + TimeManager tm = _initializingTimeManager; + + //Calculate roughly what client state tick would be. + uint localTick = tm.LocalTick; + //This should never be the case; this is a precautionary against underflow. + if (localTick == TimeManager.UNSET_TICK) + return; + + //Ensure at least 1 tick. + long rttTime = tm.RoundTripTime; + uint rttTicks = tm.TimeToTicks(rttTime) + 1; + + uint clientStateTick = localTick - rttTicks; + float interpolation = localTick - clientStateTick; + + //Minimum interpolation is that of adaptive interpolation level. + interpolation += (byte)_cachedAdaptiveInterpolationValue; + + //Ensure interpolation is not more than a second. + if (interpolation > tm.TickRate) + interpolation = tm.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. */ + if (_realtimeInterpolation == 0 || Math.Abs(_realtimeInterpolation - interpolation) > 1) + _realtimeInterpolation = (byte)Math.Ceiling(interpolation); + } + } + + /// + /// This should be called when OnStartClient is invoked on the initializing NetworkBehaviour. + /// + /// This does not need to be called if there is no initializing NetworkBehaviour. + public void StartSmoother() + { + DetachOnStart(); + } + + /// + /// This should be called when OnStopClient is invoked on the initializing NetworkBehaviour. + /// + /// This does not need to be called if there is no initializing NetworkBehaviour. + internal void StopSmoother() + { + AttachOnStop(); + } + + /// + /// Called every frame. + /// + public void OnUpdate(float delta) + { + using (_pm_OnUpdate.Auto()) + { + if (!CanSmooth()) + return; + + MoveToTarget(delta); + } + } + + /// + /// Called when the TimeManager invokes OnPreTick. + /// + public void OnPreTick() + { + using (_pm_OnPreTick.Auto()) + { + if (!CanSmooth()) + return; + + SetCaches(GetUseOwnerSettings()); + + _preTicked = true; + DiscardExcessiveTransformPropertiesQueue(); + _graphicsPreTickWorldValues = _graphicalTransform.GetWorldProperties(); + _trackerPreTickWorldValues = GetTrackerWorldProperties(); + } + } + + /// + /// Called when the TimeManager invokes OnPostReplay. + /// + /// Replay tick for the local client. + /// This is dependent on the initializing NetworkBehaviour being set. + public void OnPostReplicateReplay(uint clientTick) + { + using (_pm_OnPostReplicateReplay.Auto()) + { + if (!NetworkObjectIsReconciling()) + return; + + if (_transformProperties.Count == 0) + return; + if (clientTick <= _teleportedTick) + return; + uint firstTick = _transformProperties.Peek().Tick; + //Already in motion to first entry, or first entry passed tick. + if (clientTick <= firstTick) + return; + + ModifyTransformProperties(clientTick, firstTick); + } + } + + /// + /// Called when TimeManager invokes OnPostTick. + /// + /// Local tick of the client. + public void OnPostTick(uint clientTick) + { + using (_pm_OnPostTick.Auto()) + { + if (!CanSmooth()) + return; + if (clientTick <= _teleportedTick) + return; + + //If preticked then previous transform values are known. + if (_preTicked) + { + var trackerProps = GetTrackerWorldProperties(); + //Only needs to be put to pretick position if not detached. + if (!_detachOnStart) + _graphicalTransform.SetWorldProperties(_graphicsPreTickWorldValues); + + DiscardExcessiveTransformPropertiesQueue(); + //SnapNonSmoothedProperties(); + AddTransformProperties(clientTick, trackerProps); + } + //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 (!_detachOnStart) + _graphicalTransform.SetWorldProperties(GetTrackerWorldProperties()); + } + } + } + + /// + /// Snaps non-smoothed properties to original positoin if setting is enabled. + /// + private void SnapNonSmoothedProperties() + { + //Feature is not enabled. + if (!_cachedSnapNonSmoothedProperties) + return; + + TransformPropertiesFlag smoothedProperties = _cachedSmoothedProperties; + + //Everything is smoothed. + if (smoothedProperties == TransformPropertiesFlag.Everything) + return; + + TransformProperties goalValeus = GetTrackerWorldProperties(); + + if (!smoothedProperties.FastContains(TransformPropertiesFlag.Position)) + _graphicalTransform.position = goalValeus.Position; + if (!smoothedProperties.FastContains(TransformPropertiesFlag.Rotation)) + _graphicalTransform.rotation = goalValeus.Rotation; + if (!smoothedProperties.FastContains(TransformPropertiesFlag.Scale)) + _graphicalTransform.localScale = goalValeus.Scale; + } + + /// + /// Returns if the initialized NetworkBehaviour's NetworkObject is reconcilling. + /// + private bool NetworkObjectIsReconciling() => _initializingNetworkBehaviour == null || _initializingNetworkBehaviour.NetworkObject.IsObjectReconciling; + + /// + /// Teleports the graphical to it's starting position and clears the internal movement queue. + /// + public void Teleport() + { + if (_initializingTimeManager == null) + return; + + //If using adaptive interpolation then set the tick which was teleported. + if (_controllerMovementSettings.AdaptiveInterpolationValue != AdaptiveInterpolationType.Off) + { + TimeManager tm = _initializingTimeManager == null ? InstanceFinder.TimeManager : _initializingTimeManager; + if (tm != null) + _teleportedTick = tm.LocalTick; + } + + ClearTransformPropertiesQueue(); + + _graphicalTransform.SetWorldProperties(_trackerTransform.GetWorldProperties()); + } + + /// + /// Clears the pending movement queue. + /// + private void ClearTransformPropertiesQueue() + { + using (_pm_ClearTPQ.Auto()) + { + _transformProperties.Clear(); + //Also unset move rates since there is no more queue. + _moveRates = new(MoveRates.UNSET_VALUE); + } + } + + /// + /// Discards datas over interpolation limit from movement queue. + /// + private void DiscardExcessiveTransformPropertiesQueue() + { + using (_pm_DiscardTPQ.Auto()) + { + int propertiesCount = _transformProperties.Count; + int dequeueCount = propertiesCount - (_realtimeInterpolation + MAXIMUM_QUEUED_OVER_INTERPOLATION); + + //If there are entries to dequeue. + if (dequeueCount > 0) + { + TickSmoothingManager.TickTransformProperties ttp = default; + for (int i = 0; i < dequeueCount; i++) + { + ttp = _transformProperties.Dequeue(); + } + + var nextValues = ttp.Properties; + SetMoveRates(nextValues); + } + } + } + + /// + /// Adds a new transform properties and sets move rates if needed. + /// + private void AddTransformProperties(uint tick, TransformProperties properties) + { + using (_pm_AddTP.Auto()) + { + TickSmoothingManager.TickTransformProperties ttp = new(tick, properties); + _transformProperties.Enqueue(ttp); + + //If first entry then set move rates. + if (_transformProperties.Count == 1) + { + TransformProperties gfxWorldProperties = _graphicalTransform.GetWorldProperties(); + SetMoveRates(gfxWorldProperties); + } + } + } + + /// + /// Modifies a transform property for a tick. This does not error check for empty collections. + /// + /// First tick in the queue. If 0 this will be looked up. + private void ModifyTransformProperties(uint clientTick, uint firstTick) + { + using (_pm_ModifyTP.Auto()) + { + int queueCount = _transformProperties.Count; + uint tick = clientTick; + /*Ticks will always be added incremental by 1 so it's safe to jump ahead the difference + * of tick and firstTick. */ + int index = (int)(tick - firstTick); + //Replace with new data. + if (index < queueCount) + { + if (tick != _transformProperties[index].Tick) + { + //Should not be possible. + } + else + { + TransformProperties newProperties = GetTrackerWorldProperties(); + /* 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)index / adjustedQueueCount; + + //If easing. + if (easePercent < 1f) + { + if (easePercent < 1f) + easePercent = (float)Math.Pow(easePercent, adjustedQueueCount - index); + + TransformProperties oldProperties = _transformProperties[index].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] = new(tick, newProperties); + } + } + else + { + //This should never happen. + } + } + } + + /// + /// Gets properties of the tracker. + /// + private TransformProperties GetTrackerWorldProperties() + { + /* Return lossyScale if graphical is not attached. Otherwise, + * graphical should retain the tracker localScale so it changes + * with root. */ + + Vector3 scale = _detachOnStart ? _trackerTransform.lossyScale : _trackerTransform.localScale; + return new(_trackerTransform.position, _trackerTransform.rotation, scale); + } + + /// + /// Returns if prediction can be used on this rigidbody. + /// + /// + private bool CanSmooth() + { + //No graphical object is set. + if (_graphicalTransform == null) + return false; + + /* When this is the case the prediction networkTransform exist and is + * configured in a way to smooth the object, therefor this component should not be smoothing. */ + if (_predictionNetworkTransform != null && _predictionNetworkTransform.DoSettingsAllowSmoothing()) + return false; + + return _initializingTimeManager.NetworkManager.IsClientStarted; + } + + /// + /// Sets new rates based on next entries in transformProperties queue, against a supplied TransformProperties. + /// + private void SetMoveRates(in TransformProperties prevValues) + { + using (_pm_SetMoveRates.Auto()) + { + if (_transformProperties.Count == 0) + { + _moveRates = new(MoveRates.UNSET_VALUE); + return; + } + + TransformProperties nextValues = _transformProperties.Peek().Properties; + + float duration = _tickDelta; + + _moveRates = MoveRates.GetMoveRates(prevValues, nextValues, duration, _cachedTeleportThreshold); + _moveRates.TimeRemaining = duration; + + SetMovementMultiplier(); + } + } + + private void SetMovementMultiplier() + { + if (_moveImmediately) + { + float percent = Mathf.InverseLerp(0, _realtimeInterpolation, _transformProperties.Count); + _movementMultiplier = percent; + + _movementMultiplier = Mathf.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 = _transformProperties.Count - _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 = Mathf.Clamp(_movementMultiplier, 0.95f, 1.05f); + } + } + + /// + /// Moves transform to target values. + /// + private void MoveToTarget(float delta) + { + using (_pm_MoveToTarget.Auto()) + { + int tpCount = _transformProperties.Count; + + //No data. + if (tpCount == 0) + return; + + if (_moveImmediately) + { + _isMoving = true; + } + else + { + //Enough in buffer to move. + if (tpCount >= _realtimeInterpolation) + { + _isMoving = true; + } + else if (!_isMoving) + { + return; + } + /* If buffer is considerably under goal then halt + * movement. This will allow the buffer to grow. */ + else if (tpCount - _realtimeInterpolation < -4) + { + _isMoving = false; + return; + } + } + + TickSmoothingManager.TickTransformProperties ttp = _transformProperties.Peek(); + + TransformPropertiesFlag smoothedProperties = _cachedSmoothedProperties; + + _moveRates.Move(_graphicalTransform, ttp.Properties, smoothedProperties, delta * _movementMultiplier, useWorldSpace: true); + + float tRemaining = _moveRates.TimeRemaining; + //if TimeLeft 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.Dequeue(); + + //If there are entries left then setup for the next. + if (_transformProperties.Count > 0) + { + SetMoveRates(ttp.Properties); + //If delta is negative then call move again with abs. + if (tRemaining < 0f) + MoveToTarget(Mathf.Abs(tRemaining)); + } + //No remaining, set to snap. + else + { + ClearTransformPropertiesQueue(); + } + } + } + } + + private void DetachOnStart() + { + if (!_detachOnStart) + return; + + TransformProperties gfxWorldProperties = _graphicalTransform.GetWorldProperties(); + _graphicalTransform.SetParent(null); + _graphicalTransform.SetWorldProperties(gfxWorldProperties); + } + + /// + /// Attachs to Target transform is possible. + /// + private void AttachOnStop() + { + //Never detached. + if (!_detachOnStart) + return; + //Graphical is null, nothing can be moved. + if (_graphicalTransform == null) + return; + if (ApplicationState.IsQuitting()) + return; + + /* If not to re-attach or if there's no target to reference + * then the graphical must be destroyed. */ + bool destroy = !_attachOnStop || _targetTransform == null; + //If not to re-attach then destroy graphical if needed. + if (destroy) + { + UnityEngine.Object.Destroy(_graphicalTransform.gameObject); + return; + } + + _graphicalTransform.SetParent(_targetTransform.parent); + _graphicalTransform.SetLocalProperties(_trackerTransform.GetLocalProperties()); + } + + public void ResetState() + { + if (!IsInitialized) + return; + + AttachOnStop(); + + _initializingNetworkBehaviour = null; + _initializingTimeManager = null; + _graphicalTransform = null; + _targetTransform = null; + + _teleportedTick = TimeManager.UNSET_TICK; + _movementMultiplier = 1f; + CollectionCaches.StoreAndDefault(ref _transformProperties); + _moveRates = default; + _preTicked = default; + _queuedTrackerProperties = null; + _trackerPreTickWorldValues = default; + _graphicsPreTickWorldValues = default; + _realtimeInterpolation = default; + _isMoving = default; + + _predictionNetworkTransform = null; + + if (_trackerTransform != null) + UnityEngine.Object.Destroy(_trackerTransform.gameObject); + } + + public void InitializeState() { } + } + #endif +} \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.Threaded.cs.meta b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.Threaded.cs.meta new file mode 100644 index 000000000..9180bfaf1 --- /dev/null +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.Threaded.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2fea8ea4886baae46875b4349f9389e3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.cs index 76c83f7c0..c7dda8613 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.cs @@ -5,19 +5,53 @@ using FishNet.Object.Prediction; using FishNet.Utility.Extension; using GameKit.Dependencies.Utilities; -using UnityEngine; -using UnityEngine.Profiling; using Unity.Profiling; +using UnityEngine; using UnityEngine.Scripting; namespace FishNet.Component.Transforming.Beta { + #if !THREADED_TICKSMOOTHERS /// /// This class is under regular development and it's API may change at any time. /// public sealed class UniversalTickSmoother : IResettable { - #region Public. + #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. /// /// True if currently initialized. /// @@ -25,23 +59,6 @@ public sealed class UniversalTickSmoother : IResettable #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. /// @@ -123,12 +140,7 @@ public sealed class UniversalTickSmoother : IResettable /// /// TransformProperties to move towards. /// - private BasicQueue _transformProperties; - /// - /// True if to smooth using owner settings, false for spectator settings. - /// This is only used for performance gains. - /// - private bool _useOwnerSettings; + private BasicQueue _transformProperties; /// /// Last tick this was teleported on. /// @@ -149,6 +161,26 @@ public sealed class UniversalTickSmoother : IResettable /// True if moving has started and has not been stopped. /// private bool _isMoving; + /// + /// NetworkTransform used when prediction type is set to other. + /// + private NetworkTransform _predictionNetworkTransform; + #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. @@ -220,11 +252,11 @@ public TransformProperties GetGraphicalTrackerLocalProperties() /// Updates the smoothedProperties value. /// /// New value. - /// True if updating owner smoothing settings, or updating settings on an offline smoother. False to update spectator settings - public void SetSmoothedProperties(TransformPropertiesFlag value, bool forOwnerOrOfflineSmoother) + /// True if updating owner smoothing settings, or updating settings on an offline smoother. False to update spectator settings + public void SetSmoothedProperties(TransformPropertiesFlag value, bool forController) { _controllerMovementSettings.SmoothedProperties = value; - SetCaches(forOwnerOrOfflineSmoother); + SetCaches(forController); } /// @@ -264,7 +296,7 @@ public void SetAdaptiveInterpolation(AdaptiveInterpolationType value, bool forOw UpdateRealtimeInterpolation(); } - public void Initialize(InitializationSettings initializationSettings, MovementSettings ownerSettings, MovementSettings spectatorSettings) + public void Initialize(InitializationSettings initializationSettings, MovementSettings controllerSettings, MovementSettings spectatorSettings) { ResetState(); @@ -273,9 +305,9 @@ public void Initialize(InitializationSettings initializationSettings, MovementSe if (!TransformsAreValid(graphicalTransform, targetTransform)) return; - - _transformProperties = CollectionCaches.RetrieveBasicQueue(); - _controllerMovementSettings = ownerSettings; + + _transformProperties = CollectionCaches.RetrieveBasicQueue(); + _controllerMovementSettings = controllerSettings; _spectatorMovementSettings = spectatorSettings; /* Unset scale smoothing if not detaching. This is to prevent @@ -296,7 +328,20 @@ public void Initialize(InitializationSettings initializationSettings, MovementSe _detachOnStart = initializationSettings.DetachOnStart; _attachOnStop = initializationSettings.AttachOnStop; _moveImmediately = initializationSettings.MoveImmediately; - + + if (initializationSettings.FavorPredictionNetworkTransform && _initializingNetworkBehaviour != null) + { + NetworkObject networkObject = _initializingNetworkBehaviour.NetworkObject; + if (!networkObject.IsRigidbodyPredictionType) + _predictionNetworkTransform = networkObject.PredictionNetworkTransform; + else + _predictionNetworkTransform = null; + } + else + { + _predictionNetworkTransform = null; + } + SetCaches(GetUseOwnerSettings()); //Use set method as it has sanity checks. @@ -326,7 +371,7 @@ void SetupTrackerTransform() _trackerTransform.SetParent(trackerParent); } - _trackerTransform.SetWorldPositionRotationAndScale(_targetTransform.position, _targetTransform.rotation, graphicalTransform.localScale); + _trackerTransform.SetLocalPositionRotationAndScale(_graphicalTransform.localPosition, graphicalTransform.localRotation, graphicalTransform.localScale); } IsInitialized = true; @@ -374,19 +419,18 @@ private bool GetUseAdaptiveInterpolation() /// /// OwnerSettings can be used to read determine this as both owner and spectator settings will have the name InitializingNetworkBehaviour. /// - private bool GetUseOwnerSettings() => _initializingNetworkBehaviour == null || _initializingNetworkBehaviour.IsOwner || !_initializingNetworkBehaviour.Owner.IsValid; - - /// - /// Updates OwnerDuringPreTick value and caches if needed. - /// - private void SetUseOwnerSettings(bool value, bool force = false) + private bool GetUseOwnerSettings() { - if (value == _useOwnerSettings && !force) - return; + /* No networkBehaviour indicates an offline smoother. + * The offline smoothers use owner settings. */ + if (_initializingNetworkBehaviour == null) + return true; - _useOwnerSettings = value; + if (_initializingNetworkBehaviour.IsController) + return true; - SetCaches(value); + return false; + // return _initializingNetworkBehaviour.IsOwner || !_initializingNetworkBehaviour.Owner.IsValid; } /// @@ -506,7 +550,7 @@ public void OnPreTick() if (!CanSmooth()) return; - SetUseOwnerSettings(GetUseOwnerSettings()); + SetCaches(GetUseOwnerSettings()); _preTicked = true; DiscardExcessiveTransformPropertiesQueue(); @@ -556,14 +600,14 @@ public void OnPostTick(uint clientTick) //If preticked then previous transform values are known. if (_preTicked) { - var trackerProps = GetTrackerWorldProperties(); + DiscardExcessiveTransformPropertiesQueue(); + //Only needs to be put to pretick position if not detached. if (!_detachOnStart) _graphicalTransform.SetWorldProperties(_graphicsPreTickWorldValues); - DiscardExcessiveTransformPropertiesQueue(); //SnapNonSmoothedProperties(); - AddTransformProperties(clientTick, trackerProps); + AddTransformProperties(clientTick); } //If did not pretick then the only thing we can do is snap to instantiated values. else @@ -652,14 +696,11 @@ private void DiscardExcessiveTransformPropertiesQueue() //If there are entries to dequeue. if (dequeueCount > 0) { - TickSmoothingManager.TickTransformProperties ttp = default; + TickTransformProperties tpp = default; for (int i = 0; i < dequeueCount; i++) - { - ttp = _transformProperties.Dequeue(); - } + tpp = _transformProperties.Dequeue(); - var nextValues = ttp.Properties; - SetMoveRates(nextValues); + SetMoveRates(tpp.Properties); } } } @@ -667,12 +708,12 @@ private void DiscardExcessiveTransformPropertiesQueue() /// /// Adds a new transform properties and sets move rates if needed. /// - private void AddTransformProperties(uint tick, TransformProperties properties) + private void AddTransformProperties(uint tick) { using (_pm_AddTP.Auto()) { - TickSmoothingManager.TickTransformProperties ttp = new(tick, properties); - _transformProperties.Enqueue(ttp); + TickTransformProperties tpp = new(tick, GetTrackerWorldProperties()); + _transformProperties.Enqueue(tpp); //If first entry then set move rates. if (_transformProperties.Count == 1) @@ -762,6 +803,11 @@ private bool CanSmooth() if (_graphicalTransform == null) return false; + /* When this is the case the prediction networkTransform exist and is + * configured in a way to smooth the object, therefor this component should not be smoothing. */ + if (_predictionNetworkTransform != null && _predictionNetworkTransform.DoSettingsAllowSmoothing()) + return false; + return _initializingTimeManager.NetworkManager.IsClientStarted; } @@ -858,7 +904,7 @@ private void MoveToTarget(float delta) } } - TickSmoothingManager.TickTransformProperties ttp = _transformProperties.Peek(); + TickTransformProperties ttp = _transformProperties.Peek(); TransformPropertiesFlag smoothedProperties = _cachedSmoothedProperties; @@ -940,7 +986,7 @@ public void ResetState() _teleportedTick = TimeManager.UNSET_TICK; _movementMultiplier = 1f; - CollectionCaches.StoreAndDefault(ref _transformProperties); + CollectionCaches.StoreAndDefault(ref _transformProperties); _moveRates = default; _preTicked = default; _queuedTrackerProperties = null; @@ -948,11 +994,13 @@ public void ResetState() _graphicsPreTickWorldValues = default; _realtimeInterpolation = default; _isMoving = default; - + _predictionNetworkTransform = null; + if (_trackerTransform != null) UnityEngine.Object.Destroy(_trackerTransform.gameObject); } public void InitializeState() { } } + #endif } \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Managing/NetworkManager.cs b/Assets/FishNet/Runtime/Managing/NetworkManager.cs index e31e27679..6bac0da00 100644 --- a/Assets/FishNet/Runtime/Managing/NetworkManager.cs +++ b/Assets/FishNet/Runtime/Managing/NetworkManager.cs @@ -121,10 +121,12 @@ public static IReadOnlyList Instances /// ObserverManager for this NetworkManager. /// public ObserverManager ObserverManager { get; private set; } + #if THREADED_TICKSMOOTHERS /// /// TickSmoothingManager for this NetworkManager. /// public TickSmoothingManager TickSmoothingManager { get; private set; } + #endif /// /// DebugManager for this NetworkManager. /// @@ -323,7 +325,9 @@ private void Awake() TimeManager = GetOrCreateComponent(); SceneManager = GetOrCreateComponent(); ObserverManager = GetOrCreateComponent(); + #if THREADED_TICKSMOOTHERS TickSmoothingManager = GetOrCreateComponent(); + #endif RollbackManager = GetOrCreateComponent(); PredictionManager = GetOrCreateComponent(); StatisticsManager = GetOrCreateComponent(); @@ -368,7 +372,9 @@ private void InitializeComponents() SceneManager.InitializeOnce_Internal(this); ObserverManager.InitializeOnce_Internal(this); + #if THREADED_TICKSMOOTHERS TickSmoothingManager.InitializeOnce_Internal(this); + #endif 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 58c37480e..bd4f623fd 100644 --- a/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs +++ b/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs @@ -62,7 +62,7 @@ private enum UpdateOrder : byte /// public event Action OnPreTick; /// - /// Called when a tick occurs. + /// Called after PreTick and before OnPostTick, most similar to FixedUpdate. This is commonly where you run replicate or other network. /// public event Action OnTick; /// @@ -290,6 +290,7 @@ public void SetPhysicsTimeScale(float value) #region Private Profiler Markers private static readonly ProfilerMarker _pm_IncreaseTick = new("TimeManager.IncreaseTick()"); + private static readonly ProfilerMarker _pm_TryIterateData = new("TimeManager.TryIterateData(bool)"); 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)"); @@ -743,14 +744,9 @@ private void IncreaseTick() 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); + InvokeOnSimulation(preSimulation: true, tickDelta); + SimulatePhysics(tickDelta); + InvokeOnSimulation(preSimulation: false, tickDelta); } using (_pm_OnPostTick.Auto()) @@ -1089,33 +1085,61 @@ public uint LocalTickToTick(uint localTick) #endregion /// - /// Tries to iterate incoming or outgoing data. + /// Invokes OnPreSimulation or OnPostSimulation. /// - /// True to iterate incoming. - private void TryIterateData(bool incoming) + internal void InvokeOnSimulation(bool preSimulation, float delta) { - if (incoming) + if (preSimulation) { - /* It's not possible for data to come in - * more than once per frame but there could - * be new data going out each tick, since - * movement is often based off the tick system. - * Because of this don't iterate incoming if - * it's the same frame, but the outgoing - * may iterate multiple times per frame due to - * there possibly being multiple ticks per frame. */ - int frameCount = Time.frameCount; - if (frameCount == _lastIncomingIterationFrame) - return; - _lastIncomingIterationFrame = frameCount; - - NetworkManager.TransportManager.IterateIncoming(asServer: true); - NetworkManager.TransportManager.IterateIncoming(asServer: false); + using (_pm_OnPrePhysicsSimulation.Auto()) + OnPrePhysicsSimulation?.Invoke(delta); } else { - NetworkManager.TransportManager.IterateOutgoing(asServer: true); - NetworkManager.TransportManager.IterateOutgoing(asServer: false); + using (_pm_OnPostPhysicsSimulation.Auto()) + OnPostPhysicsSimulation?.Invoke(delta); + } + } + + internal void SimulatePhysics(float delta) + { + using (_pm_PhysicsSimulate.Auto()) + Physics.Simulate(delta); + using (_pm_Physics2DSimulate.Auto()) + Physics2D.Simulate(delta); + } + + /// + /// Tries to iterate incoming or outgoing data. + /// + /// True to iterate incoming. + private void TryIterateData(bool incoming) + { + using (_pm_TryIterateData.Auto()) + { + if (incoming) + { + /* It's not possible for data to come in + * more than once per frame but there could + * be new data going out each tick, since + * movement is often based off the tick system. + * Because of this don't iterate incoming if + * it's the same frame, but the outgoing + * may iterate multiple times per frame due to + * there possibly being multiple ticks per frame. */ + int frameCount = Time.frameCount; + if (frameCount == _lastIncomingIterationFrame) + return; + _lastIncomingIterationFrame = frameCount; + + NetworkManager.TransportManager.IterateIncoming(asServer: true); + NetworkManager.TransportManager.IterateIncoming(asServer: false); + } + else + { + NetworkManager.TransportManager.IterateOutgoing(asServer: true); + NetworkManager.TransportManager.IterateOutgoing(asServer: false); + } } } From af2cc3506881e5b5003ec1e73ed21a2ab2c876a1 Mon Sep 17 00:00:00 2001 From: waterb <143908720+belplaton@users.noreply.github.com> Date: Wed, 21 Jan 2026 17:09:22 +0300 Subject: [PATCH 9/9] fix --- .../Generated/Component/TickSmoothing/TickSmoothingManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs index 6a4523ae3..67e544038 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs @@ -955,7 +955,8 @@ private void PredictionManager_OnPostReplicateReplay(uint clientTick, uint serve clientTick = clientTick, teleportedTick = _teleportedTick.AsArray(), objectReconcilingMask = _objectReconcilingMask.AsArray(), - + + transformProperties = _transformProperties, modifyTransformPropertiesPayloads = _modifyTransformPropertiesPayloads.AsArray() };