From cb6b811002f6354c6ef0f443291670755a562fb7 Mon Sep 17 00:00:00 2001 From: _nebula <41904486+misternebula@users.noreply.github.com> Date: Wed, 14 Feb 2024 21:40:11 +0000 Subject: [PATCH] move determinstic stuff to separate folder, remove WO dumps --- .../AlarmTotemSync/AlarmTotemManager.cs | 2 +- QSB/EchoesOfTheEye/Ghosts/GhostManager.cs | 2 +- .../LightSensorSync/LightSensorManager.cs | 2 +- QSB/ItemSync/ItemManager.cs | 1 + QSB/PoolSync/PoolManager.cs | 2 +- QSB/QSBCore.cs | 1 + QSB/Syncs/Occasional/OccasionalManager.cs | 1 + .../ProbeLauncherTool/ProbeLauncherManager.cs | 2 +- QSB/Utility/DebugSettings.cs | 3 - .../Deterministic/DeterministicManager.cs | 80 +++++ .../Deterministic/OWRigidbodyPatches.cs | 217 +++++++++++++ QSB/Utility/DeterministicManager.cs | 305 ------------------ QSB/WorldSync/MiscManager.cs | 2 +- QSB/WorldSync/QSBWorldSync.cs | 1 + 14 files changed, 307 insertions(+), 314 deletions(-) create mode 100644 QSB/Utility/Deterministic/DeterministicManager.cs create mode 100644 QSB/Utility/Deterministic/OWRigidbodyPatches.cs delete mode 100644 QSB/Utility/DeterministicManager.cs diff --git a/QSB/EchoesOfTheEye/AlarmTotemSync/AlarmTotemManager.cs b/QSB/EchoesOfTheEye/AlarmTotemSync/AlarmTotemManager.cs index 992f79ee1..c59956598 100644 --- a/QSB/EchoesOfTheEye/AlarmTotemSync/AlarmTotemManager.cs +++ b/QSB/EchoesOfTheEye/AlarmTotemSync/AlarmTotemManager.cs @@ -1,6 +1,6 @@ using Cysharp.Threading.Tasks; using QSB.EchoesOfTheEye.AlarmTotemSync.WorldObjects; -using QSB.Utility; +using QSB.Utility.Deterministic; using QSB.WorldSync; using System.Linq; using System.Threading; diff --git a/QSB/EchoesOfTheEye/Ghosts/GhostManager.cs b/QSB/EchoesOfTheEye/Ghosts/GhostManager.cs index 64f5dbcf6..3cfdec966 100644 --- a/QSB/EchoesOfTheEye/Ghosts/GhostManager.cs +++ b/QSB/EchoesOfTheEye/Ghosts/GhostManager.cs @@ -1,6 +1,6 @@ using Cysharp.Threading.Tasks; using QSB.EchoesOfTheEye.Ghosts.WorldObjects; -using QSB.Utility; +using QSB.Utility.Deterministic; using QSB.WorldSync; using System.Collections.Generic; using System.Linq; diff --git a/QSB/EchoesOfTheEye/LightSensorSync/LightSensorManager.cs b/QSB/EchoesOfTheEye/LightSensorSync/LightSensorManager.cs index 4fa61dc63..f65565ca6 100644 --- a/QSB/EchoesOfTheEye/LightSensorSync/LightSensorManager.cs +++ b/QSB/EchoesOfTheEye/LightSensorSync/LightSensorManager.cs @@ -1,6 +1,6 @@ using Cysharp.Threading.Tasks; using QSB.EchoesOfTheEye.LightSensorSync.WorldObjects; -using QSB.Utility; +using QSB.Utility.Deterministic; using QSB.WorldSync; using System.Linq; using System.Threading; diff --git a/QSB/ItemSync/ItemManager.cs b/QSB/ItemSync/ItemManager.cs index afa1a8eea..f429acaa4 100644 --- a/QSB/ItemSync/ItemManager.cs +++ b/QSB/ItemSync/ItemManager.cs @@ -4,6 +4,7 @@ using QSB.ItemSync.WorldObjects.Items; using QSB.ItemSync.WorldObjects.Sockets; using QSB.Utility; +using QSB.Utility.Deterministic; using QSB.WorldSync; using System.Linq; using System.Threading; diff --git a/QSB/PoolSync/PoolManager.cs b/QSB/PoolSync/PoolManager.cs index 39a48f586..7b5c1c7ee 100644 --- a/QSB/PoolSync/PoolManager.cs +++ b/QSB/PoolSync/PoolManager.cs @@ -1,5 +1,5 @@ using Cysharp.Threading.Tasks; -using QSB.Utility; +using QSB.Utility.Deterministic; using QSB.WorldSync; using System.Threading; diff --git a/QSB/QSBCore.cs b/QSB/QSBCore.cs index 486e004b5..e033123f8 100644 --- a/QSB/QSBCore.cs +++ b/QSB/QSBCore.cs @@ -25,6 +25,7 @@ using UnityEngine; using UnityEngine.InputSystem; using Random = System.Random; +using QSB.Utility.Deterministic; /* Copyright (C) 2020 - 2023 diff --git a/QSB/Syncs/Occasional/OccasionalManager.cs b/QSB/Syncs/Occasional/OccasionalManager.cs index 1b0ffb9d7..b60fdda3f 100644 --- a/QSB/Syncs/Occasional/OccasionalManager.cs +++ b/QSB/Syncs/Occasional/OccasionalManager.cs @@ -1,6 +1,7 @@ using Cysharp.Threading.Tasks; using Mirror; using QSB.Utility; +using QSB.Utility.Deterministic; using QSB.WorldSync; using System.Collections.Generic; using System.Linq; diff --git a/QSB/Tools/ProbeLauncherTool/ProbeLauncherManager.cs b/QSB/Tools/ProbeLauncherTool/ProbeLauncherManager.cs index 3902e0a3c..8f98a005e 100644 --- a/QSB/Tools/ProbeLauncherTool/ProbeLauncherManager.cs +++ b/QSB/Tools/ProbeLauncherTool/ProbeLauncherManager.cs @@ -1,6 +1,6 @@ using Cysharp.Threading.Tasks; using QSB.Tools.ProbeLauncherTool.WorldObjects; -using QSB.Utility; +using QSB.Utility.Deterministic; using QSB.WorldSync; using System.Linq; using System.Threading; diff --git a/QSB/Utility/DebugSettings.cs b/QSB/Utility/DebugSettings.cs index 0d0fdb802..ee4b4569c 100644 --- a/QSB/Utility/DebugSettings.cs +++ b/QSB/Utility/DebugSettings.cs @@ -5,9 +5,6 @@ namespace QSB.Utility; [JsonObject(MemberSerialization.OptIn)] public class DebugSettings { - [JsonProperty("dumpWorldObjects")] - public bool DumpWorldObjects; - [JsonProperty("logQSBMessages")] public bool LogQSBMessages; diff --git a/QSB/Utility/Deterministic/DeterministicManager.cs b/QSB/Utility/Deterministic/DeterministicManager.cs new file mode 100644 index 000000000..d4bd4b015 --- /dev/null +++ b/QSB/Utility/Deterministic/DeterministicManager.cs @@ -0,0 +1,80 @@ +using HarmonyLib; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; + +namespace QSB.Utility.Deterministic; + +/// +/// TODO make this only do cache clearing on pre scene load when HOSTING instead of just all the time +/// +public static class DeterministicManager +{ + private static readonly Harmony _harmony = new(typeof(DeterministicManager).FullName); + private static bool _patched; + + public static readonly Dictionary Cache = new(); + + public static void Init() => + QSBSceneManager.OnPreSceneLoad += (_, _) => + { + DebugLog.DebugWrite("cleared cache"); + Cache.Clear(); + + if (!_patched) + { + _harmony.PatchAll(typeof(OWRigidbodyPatches)); + _patched = true; + } + }; + + public static void OnWorldObjectsAdded() + { + //DebugLog.DebugWrite($"cleared cache of {_cache.Count} entries"); + //_cache.Clear(); + + if (_patched) + { + _harmony.UnpatchSelf(); + _patched = false; + } + } + + /// + /// only call this before world objects added + /// + public static string DeterministicPath(this Component component) + { + var sb = new StringBuilder(); + var transform = component.transform; + while (true) + { + if (!Cache.TryGetValue(transform, out var data)) + { + data = (transform.GetSiblingIndex(), transform.parent); + Cache.Add(transform, data); + } + + if (!data.Parent) + { + break; + } + + sb.Append(transform.name); + sb.Append(' '); + sb.Append(data.SiblingIndex); + sb.Append(' '); + transform = data.Parent; + } + + sb.Append(transform.name); + return sb.ToString(); + } + + /// + /// only call this before world objects added + /// + public static IEnumerable SortDeterministic(this IEnumerable components) where T : Component + => components.OrderBy(DeterministicPath); +} \ No newline at end of file diff --git a/QSB/Utility/Deterministic/OWRigidbodyPatches.cs b/QSB/Utility/Deterministic/OWRigidbodyPatches.cs new file mode 100644 index 000000000..3005321e3 --- /dev/null +++ b/QSB/Utility/Deterministic/OWRigidbodyPatches.cs @@ -0,0 +1,217 @@ +using HarmonyLib; +using System.Collections.Generic; +using UnityEngine; + +namespace QSB.Utility.Deterministic; + +[HarmonyPatch(typeof(OWRigidbody))] +public static class OWRigidbodyPatches +{ + private static readonly Dictionary _setParentQueue = new(); + + [HarmonyPrefix] + [HarmonyPatch(nameof(OWRigidbody.Awake))] + private static bool Awake(OWRigidbody __instance) + { + __instance._transform = __instance.transform; + + // ADDED + DeterministicManager.Cache.Add(__instance._transform, (__instance._transform.GetSiblingIndex(), __instance._transform.parent)); + + if (!__instance._scaleRoot) + { + __instance._scaleRoot = __instance._transform; + } + + CenterOfTheUniverse.TrackRigidbody(__instance); + __instance._offsetApplier = __instance.gameObject.GetAddComponent(); + __instance._offsetApplier.Init(__instance); + if (__instance._simulateInSector) + { + __instance._simulateInSector.OnSectorOccupantsUpdated += __instance.OnSectorOccupantsUpdated; + } + + __instance._origParent = __instance._transform.parent; + __instance._origParentBody = __instance._origParent ? __instance._origParent.GetAttachedOWRigidbody() : null; + + // ADDED + if (__instance._transform.parent) + { + _setParentQueue[__instance] = null; + } + + // REMOVED + /*if (__instance._transform.parent != null) + { + __instance._transform.parent = null; + }*/ + + __instance._rigidbody = __instance.GetRequiredComponent(); + __instance._rigidbody.interpolation = RigidbodyInterpolation.None; + + if (!__instance._autoGenerateCenterOfMass) + { + __instance._rigidbody.centerOfMass = __instance._centerOfMass; + } + + if (__instance.IsSimulatedKinematic()) + { + __instance.EnableKinematicSimulation(); + } + + __instance._origCenterOfMass = __instance.RunningKinematicSimulation() ? __instance._kinematicRigidbody.centerOfMass : __instance._rigidbody.centerOfMass; + __instance._referenceFrame = new ReferenceFrame(__instance); + + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(OWRigidbody.Start))] + private static void Start(OWRigidbody __instance) + { + if (_setParentQueue.TryGetValue(__instance, out var parent)) + { + __instance._transform.parent = parent; + _setParentQueue.Remove(__instance); + } + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(OWRigidbody.OnDestroy))] + private static void OnDestroy(OWRigidbody __instance) + { + DeterministicManager.Cache.Remove(__instance._transform); + _setParentQueue.Remove(__instance); + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(OWRigidbody.Suspend), typeof(Transform), typeof(OWRigidbody))] + private static bool Suspend(OWRigidbody __instance, Transform suspensionParent, OWRigidbody suspensionBody) + { + if (!__instance._suspended || __instance._unsuspendNextUpdate) + { + __instance._suspensionBody = suspensionBody; + var direction = __instance.GetVelocity() - suspensionBody.GetPointVelocity(__instance._transform.position); + __instance._cachedRelativeVelocity = suspensionBody.transform.InverseTransformDirection(direction); + __instance._cachedAngularVelocity = __instance.RunningKinematicSimulation() ? __instance._kinematicRigidbody.angularVelocity : __instance._rigidbody.angularVelocity; + __instance.enabled = false; + __instance._offsetApplier.enabled = false; + + if (__instance.RunningKinematicSimulation()) + { + __instance._kinematicRigidbody.enabled = false; + } + else + { + __instance.MakeKinematic(); + } + + // ADDED + if (_setParentQueue.ContainsKey(__instance)) + { + _setParentQueue[__instance] = suspensionParent; + } + else + { + __instance._transform.parent = suspensionParent; + } + + // REMOVED + // __instance._transform.parent = suspensionParent; + + __instance._suspended = true; + __instance._unsuspendNextUpdate = false; + if (!Physics.autoSyncTransforms) + { + Physics.SyncTransforms(); + } + + if (__instance._childColliders == null) + { + __instance._childColliders = __instance.GetComponentsInChildren(); + + // CLEANED + foreach (var childCollider in __instance._childColliders) + { + childCollider.gameObject.GetAddComponent().ListenForParentBodySuspension(); + } + } + + __instance.RaiseEvent(nameof(__instance.OnSuspendOWRigidbody), __instance); + } + + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(OWRigidbody.ChangeSuspensionBody))] + private static bool ChangeSuspensionBody(OWRigidbody __instance, OWRigidbody newSuspensionBody) + { + if (__instance._suspended) + { + __instance._cachedRelativeVelocity = Vector3.zero; + __instance._suspensionBody = newSuspensionBody; + + // ADDED + if (_setParentQueue.ContainsKey(__instance)) + { + _setParentQueue[__instance] = newSuspensionBody.transform; + } + else + { + __instance._transform.parent = newSuspensionBody.transform; + } + + // REMOVED + // __instance._transform.parent = newSuspensionBody.transform; + } + + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(OWRigidbody.UnsuspendImmediate))] + private static bool UnsuspendImmediate(OWRigidbody __instance, bool restoreCachedVelocity) + { + if (__instance._suspended) + { + if (__instance.RunningKinematicSimulation()) + { + __instance._kinematicRigidbody.enabled = true; + } + else + { + __instance.MakeNonKinematic(); + } + + __instance.enabled = true; + + // ADDED + if (_setParentQueue.ContainsKey(__instance)) + { + _setParentQueue[__instance] = null; + } + else + { + __instance._transform.parent = null; + } + + // REMOVED + // __instance._transform.parent = null; + + if (!Physics.autoSyncTransforms) + { + Physics.SyncTransforms(); + } + + var cachedVelocity = restoreCachedVelocity ? __instance._suspensionBody.transform.TransformDirection(__instance._cachedRelativeVelocity) : Vector3.zero; + __instance.SetVelocity(__instance._suspensionBody.GetPointVelocity(__instance._transform.position) + cachedVelocity); + __instance.SetAngularVelocity(restoreCachedVelocity ? __instance._cachedAngularVelocity : Vector3.zero); + __instance._suspended = false; + __instance._suspensionBody = null; + __instance.RaiseEvent(nameof(__instance.OnUnsuspendOWRigidbody), __instance); + } + + return false; + } +} diff --git a/QSB/Utility/DeterministicManager.cs b/QSB/Utility/DeterministicManager.cs deleted file mode 100644 index d6a71aef7..000000000 --- a/QSB/Utility/DeterministicManager.cs +++ /dev/null @@ -1,305 +0,0 @@ -using HarmonyLib; -using QSB.WorldSync; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using UnityEngine; - -namespace QSB.Utility; - -/// -/// TODO make this only do cache clearing on pre scene load when HOSTING instead of just all the time -/// -public static class DeterministicManager -{ - private static readonly Harmony _harmony = new(typeof(DeterministicManager).FullName); - private static bool _patched; - - private static readonly Dictionary _cache = new(); - - public static void Init() => - QSBSceneManager.OnPreSceneLoad += (_, _) => - { - DebugLog.DebugWrite("cleared cache"); - _cache.Clear(); - - if (!_patched) - { - _harmony.PatchAll(typeof(OWRigidbodyPatches)); - _patched = true; - } - }; - - public static void OnWorldObjectsAdded() - { - if (QSBCore.DebugSettings.DumpWorldObjects) - { - using (var file = File.CreateText(Path.Combine(QSBCore.Helper.Manifest.ModFolderPath, $"[{DebugLog.ProcessInstanceId}] world objects.csv"))) - { - file.WriteLine("world object,deterministic path"); - foreach (var worldObject in QSBWorldSync.GetWorldObjects()) - { - file.Write('"'); - file.Write(worldObject); - file.Write('"'); - file.Write(','); - file.Write('"'); - file.Write(worldObject.AttachedObject.DeterministicPath().Replace("\"", "\"\"")); - file.Write('"'); - file.WriteLine(); - } - } - - using (var file = File.CreateText(Path.Combine(QSBCore.Helper.Manifest.ModFolderPath, $"[{DebugLog.ProcessInstanceId}] cache.csv"))) - { - file.WriteLine("name,instance id,sibling index,parent,parent instance id"); - foreach (var (transform, (siblingIndex, parent)) in _cache) - { - file.Write('"'); - file.Write(transform.name.Replace("\"", "\"\"")); - file.Write('"'); - file.Write(','); - file.Write(transform.GetInstanceID()); - file.Write(','); - file.Write(siblingIndex); - file.Write(','); - file.Write('"'); - file.Write(parent ? parent.name.Replace("\"", "\"\"") : default); - file.Write('"'); - file.Write(','); - file.Write(parent ? parent.GetInstanceID() : default); - file.WriteLine(); - } - } - } - - DebugLog.DebugWrite($"cleared cache of {_cache.Count} entries"); - _cache.Clear(); - - if (_patched) - { - _harmony.UnpatchSelf(); - _patched = false; - } - } - - [HarmonyPatch(typeof(OWRigidbody))] - private static class OWRigidbodyPatches - { - private static readonly Dictionary _setParentQueue = new(); - - [HarmonyPrefix] - [HarmonyPatch(nameof(OWRigidbody.Awake))] - private static bool Awake(OWRigidbody __instance) - { - __instance._transform = __instance.transform; - _cache.Add(__instance._transform, (__instance._transform.GetSiblingIndex(), __instance._transform.parent)); - if (!__instance._scaleRoot) - { - __instance._scaleRoot = __instance._transform; - } - - CenterOfTheUniverse.TrackRigidbody(__instance); - __instance._offsetApplier = __instance.gameObject.GetAddComponent(); - __instance._offsetApplier.Init(__instance); - if (__instance._simulateInSector) - { - __instance._simulateInSector.OnSectorOccupantsUpdated += __instance.OnSectorOccupantsUpdated; - } - - __instance._origParent = __instance._transform.parent; - __instance._origParentBody = __instance._origParent ? __instance._origParent.GetAttachedOWRigidbody() : null; - if (__instance._transform.parent) - { - _setParentQueue[__instance] = null; - } - - __instance._rigidbody = __instance.GetRequiredComponent(); - __instance._rigidbody.interpolation = RigidbodyInterpolation.None; - if (!__instance._autoGenerateCenterOfMass) - { - __instance._rigidbody.centerOfMass = __instance._centerOfMass; - } - - if (__instance.IsSimulatedKinematic()) - { - __instance.EnableKinematicSimulation(); - } - - __instance._origCenterOfMass = __instance.RunningKinematicSimulation() ? __instance._kinematicRigidbody.centerOfMass : __instance._rigidbody.centerOfMass; - __instance._referenceFrame = new ReferenceFrame(__instance); - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(nameof(OWRigidbody.Start))] - private static void Start(OWRigidbody __instance) - { - if (_setParentQueue.TryGetValue(__instance, out var parent)) - { - __instance._transform.parent = parent; - _setParentQueue.Remove(__instance); - } - } - - [HarmonyPrefix] - [HarmonyPatch(nameof(OWRigidbody.OnDestroy))] - private static void OnDestroy(OWRigidbody __instance) - { - _cache.Remove(__instance._transform); - _setParentQueue.Remove(__instance); - } - - [HarmonyPrefix] - [HarmonyPatch(nameof(OWRigidbody.Suspend), typeof(Transform), typeof(OWRigidbody))] - private static bool Suspend(OWRigidbody __instance, Transform suspensionParent, OWRigidbody suspensionBody) - { - if (!__instance._suspended || __instance._unsuspendNextUpdate) - { - __instance._suspensionBody = suspensionBody; - var direction = __instance.GetVelocity() - suspensionBody.GetPointVelocity(__instance._transform.position); - __instance._cachedRelativeVelocity = suspensionBody.transform.InverseTransformDirection(direction); - __instance._cachedAngularVelocity = __instance.RunningKinematicSimulation() ? __instance._kinematicRigidbody.angularVelocity : __instance._rigidbody.angularVelocity; - __instance.enabled = false; - __instance._offsetApplier.enabled = false; - if (__instance.RunningKinematicSimulation()) - { - __instance._kinematicRigidbody.enabled = false; - } - else - { - __instance.MakeKinematic(); - } - - if (_setParentQueue.ContainsKey(__instance)) - { - _setParentQueue[__instance] = suspensionParent; - } - else - { - __instance._transform.parent = suspensionParent; - } - - __instance._suspended = true; - __instance._unsuspendNextUpdate = false; - if (!Physics.autoSyncTransforms) - { - Physics.SyncTransforms(); - } - - if (__instance._childColliders == null) - { - __instance._childColliders = __instance.GetComponentsInChildren(); - foreach (var childCollider in __instance._childColliders) - { - childCollider.gameObject.GetAddComponent().ListenForParentBodySuspension(); - } - } - - __instance.RaiseEvent(nameof(__instance.OnSuspendOWRigidbody), __instance); - } - - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(nameof(OWRigidbody.ChangeSuspensionBody))] - private static bool ChangeSuspensionBody(OWRigidbody __instance, OWRigidbody newSuspensionBody) - { - if (__instance._suspended) - { - __instance._cachedRelativeVelocity = Vector3.zero; - __instance._suspensionBody = newSuspensionBody; - if (_setParentQueue.ContainsKey(__instance)) - { - _setParentQueue[__instance] = newSuspensionBody.transform; - } - else - { - __instance._transform.parent = newSuspensionBody.transform; - } - } - - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(nameof(OWRigidbody.UnsuspendImmediate))] - private static bool UnsuspendImmediate(OWRigidbody __instance, bool restoreCachedVelocity) - { - if (__instance._suspended) - { - if (__instance.RunningKinematicSimulation()) - { - __instance._kinematicRigidbody.enabled = true; - } - else - { - __instance.MakeNonKinematic(); - } - - __instance.enabled = true; - if (_setParentQueue.ContainsKey(__instance)) - { - _setParentQueue[__instance] = null; - } - else - { - __instance._transform.parent = null; - } - - if (!Physics.autoSyncTransforms) - { - Physics.SyncTransforms(); - } - - var cachedVelocity = restoreCachedVelocity ? __instance._suspensionBody.transform.TransformDirection(__instance._cachedRelativeVelocity) : Vector3.zero; - __instance.SetVelocity(__instance._suspensionBody.GetPointVelocity(__instance._transform.position) + cachedVelocity); - __instance.SetAngularVelocity(restoreCachedVelocity ? __instance._cachedAngularVelocity : Vector3.zero); - __instance._suspended = false; - __instance._suspensionBody = null; - __instance.RaiseEvent(nameof(__instance.OnUnsuspendOWRigidbody), __instance); - } - - return false; - } - } - - /// - /// only call this before world objects added - /// - public static string DeterministicPath(this Component component) - { - var sb = new StringBuilder(); - var transform = component.transform; - while (true) - { - if (!_cache.TryGetValue(transform, out var data)) - { - data = (transform.GetSiblingIndex(), transform.parent); - _cache.Add(transform, data); - } - - if (!data.Parent) - { - break; - } - - sb.Append(transform.name); - sb.Append(' '); - sb.Append(data.SiblingIndex); - sb.Append(' '); - transform = data.Parent; - } - - sb.Append(transform.name); - return sb.ToString(); - } - - /// - /// only call this before world objects added - /// - public static IEnumerable SortDeterministic(this IEnumerable components) where T : Component - => components.OrderBy(DeterministicPath); -} \ No newline at end of file diff --git a/QSB/WorldSync/MiscManager.cs b/QSB/WorldSync/MiscManager.cs index 5d69b7b9e..800101ff9 100644 --- a/QSB/WorldSync/MiscManager.cs +++ b/QSB/WorldSync/MiscManager.cs @@ -1,5 +1,5 @@ using Cysharp.Threading.Tasks; -using QSB.Utility; +using QSB.Utility.Deterministic; using System.Linq; using System.Threading; diff --git a/QSB/WorldSync/QSBWorldSync.cs b/QSB/WorldSync/QSBWorldSync.cs index ef87cfb9d..38702de5b 100644 --- a/QSB/WorldSync/QSBWorldSync.cs +++ b/QSB/WorldSync/QSBWorldSync.cs @@ -8,6 +8,7 @@ using QSB.Player.TransformSync; using QSB.TriggerSync.WorldObjects; using QSB.Utility; +using QSB.Utility.Deterministic; using QSB.Utility.LinkedWorldObject; using QSB.WorldSync.Messages; using System;