From 35fb0a2cddb80231715d3e498d9f5d00b67e141b Mon Sep 17 00:00:00 2001 From: Jakob Ruckel Date: Wed, 29 Jun 2022 20:36:03 +0200 Subject: [PATCH 001/138] NetworkManager: Add OnInitialized event --- .../Runtime/Core/NetworkManager.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index b2403fde71..21ba854eee 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -388,6 +388,11 @@ public IReadOnlyList ConnectedClientsIds /// public event Action OnServerStarted = null; + /// + /// The callback to invoke once the NetworkManager is initiazlied, i.e. things like SceneManager are available. + /// + public event Action OnInitialized = null; + /// /// The callback to invoke if the fails. /// @@ -988,6 +993,8 @@ private void Initialize(bool server) NetworkConfig.NetworkTransport.OnTransportEvent += HandleRawTransportPoll; NetworkConfig.NetworkTransport.Initialize(this); + + OnInitialized?.Invoke(); } private void ClearClients() From 343fb11a48382fc400544197b372f0f67b7780e9 Mon Sep 17 00:00:00 2001 From: Jakob Ruckel Date: Mon, 11 Jul 2022 09:54:34 +0200 Subject: [PATCH 002/138] Some Debug output to investigate a crash --- com.unity.netcode.gameobjects/Components/NetworkTransform.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index 82c1ab8b2c..c82f9f874d 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -467,7 +467,12 @@ private void CommitLocallyAndReplicate(NetworkTransformState networkState) private void ResetInterpolatedStateToCurrentAuthoritativeState() { var serverTime = NetworkManager.ServerTime.Time; + try { m_PositionXInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionX, serverTime); + } catch(Exception e) { + Debug.LogError(gameObject.name + " will crash :("); + throw e; + } m_PositionYInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionY, serverTime); m_PositionZInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionZ, serverTime); From 1d34889d0f31c07870bd0228c9e4ca5edf0ddae4 Mon Sep 17 00:00:00 2001 From: Frank Luong <100299641+fluong6@users.noreply.github.com> Date: Mon, 11 Jul 2022 18:24:02 +0000 Subject: [PATCH 003/138] chore: pointing tools package to release 1.0.0 branch (#2057) --- testproject-tools-integration/Packages/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testproject-tools-integration/Packages/manifest.json b/testproject-tools-integration/Packages/manifest.json index 1d3fcfb11c..98dff627b8 100644 --- a/testproject-tools-integration/Packages/manifest.json +++ b/testproject-tools-integration/Packages/manifest.json @@ -2,7 +2,7 @@ "registry": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates", "dependencies": { "com.unity.ide.rider": "3.0.7", - "com.unity.multiplayer.tools": "https://github.com/Unity-Technologies/com.unity.multiplayer.tools.git#release/1.0.0-pre.8", + "com.unity.multiplayer.tools": "https://github.com/Unity-Technologies/com.unity.multiplayer.tools.git#release/1.0.0", "com.unity.netcode.gameobjects": "file:../../com.unity.netcode.gameobjects", "com.unity.test-framework": "1.1.31", "com.unity.test-framework.performance": "2.8.0-preview", From 010d991cc14090682da44b074a09eafc40124a41 Mon Sep 17 00:00:00 2001 From: zain-mecklai <64809707+zain-mecklai@users.noreply.github.com> Date: Mon, 11 Jul 2022 20:49:09 -0400 Subject: [PATCH 004/138] test: Allow for the detection of whether to run locally or remotely. (#2054) * Set up for launching remotely * Set up for launching remotely * Report on WorkerCount and LaunchRemotely state * Store the Process as it will complete later * add a little more logging * Simplified name * Update GetWorkerCount to no longer set internal state * Update BaseMultiprocessTests.cs respond to PR feedback Co-authored-by: ashwini <36935028+ashwinimurt@users.noreply.github.com> --- .../BaseMultiprocessTests.cs | 50 +++++++--- .../Helpers/MultiprocessLogger.cs | 2 +- .../Helpers/MultiprocessOrchestration.cs | 91 +++++++++++++++++++ 3 files changed, 128 insertions(+), 15 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs index a318469316..b3579f21d4 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs @@ -1,5 +1,7 @@ using System; using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; using NUnit.Framework; using UnityEngine; using UnityEngine.SceneManagement; @@ -22,6 +24,14 @@ public MultiprocessTestsAttribute() : base(MultiprocessCategoryName) { } [MultiprocessTests] public abstract class BaseMultiprocessTests { + protected string[] platformList { get; set; } + + protected int GetWorkerCount() + { + platformList = MultiprocessOrchestration.GetRemotePlatformList(); + return platformList == null ? WorkerCount : platformList.Length; + } + protected bool m_LaunchRemotely; private bool m_HasSceneLoaded = false; // TODO: Remove UTR check once we have Multiprocess tests fully working protected bool IgnoreMultiprocessTests => MultiprocessOrchestration.ShouldIgnoreUTRTests(); @@ -30,7 +40,7 @@ public abstract class BaseMultiprocessTests /// /// Implement this to specify the amount of workers to spawn from your main test runner - /// TODO there's a good chance this will be re-factored with something fancier once we start integrating with bokken + /// Note: If using remote workers, the woorker count will come from the environment variable /// protected abstract int WorkerCount { get; } @@ -131,16 +141,16 @@ private bool VerifySceneIsValidForClientsToLoad(int sceneIndex, string sceneName public virtual IEnumerator Setup() { yield return new WaitUntil(() => NetworkManager.Singleton != null); - MultiprocessLogger.Log("NetworkManager.Singleton != null"); yield return new WaitUntil(() => NetworkManager.Singleton.IsServer); - MultiprocessLogger.Log("NetworkManager.Singleton.IsServer"); yield return new WaitUntil(() => NetworkManager.Singleton.IsListening); - MultiprocessLogger.Log("NetworkManager.Singleton.IsListening"); yield return new WaitUntil(() => m_HasSceneLoaded == true); - MultiprocessLogger.Log("m_HasSceneLoaded"); var startTime = Time.time; + m_LaunchRemotely = MultiprocessOrchestration.IsRemoteOperationEnabled(); - MultiprocessLogger.Log($"Active Worker Count is {MultiprocessOrchestration.ActiveWorkerCount()} and connected client count is {NetworkManager.Singleton.ConnectedClients.Count}"); + MultiprocessLogger.Log($"Active Worker Count is {MultiprocessOrchestration.ActiveWorkerCount()}" + + $" and connected client count is {NetworkManager.Singleton.ConnectedClients.Count} " + + $" and WorkerCount is {GetWorkerCount()} " + + $" and LaunchRemotely is {m_LaunchRemotely}"); if (MultiprocessOrchestration.ActiveWorkerCount() + 1 < NetworkManager.Singleton.ConnectedClients.Count) { MultiprocessLogger.Log("Is this a bad state?"); @@ -149,20 +159,32 @@ public virtual IEnumerator Setup() // Moved this out of OnSceneLoaded as OnSceneLoaded is a callback from the SceneManager and just wanted to avoid creating // processes from within the same callstack/context as the SceneManager. This will instantiate up to the WorkerCount and // then any subsequent calls to Setup if there are already workers it will skip this step - if (NetworkManager.Singleton.ConnectedClients.Count - 1 < WorkerCount) + if (!m_LaunchRemotely) { - var numProcessesToCreate = WorkerCount - (NetworkManager.Singleton.ConnectedClients.Count - 1); - for (int i = 1; i <= numProcessesToCreate; i++) + if (NetworkManager.Singleton.ConnectedClients.Count - 1 < WorkerCount) { - MultiprocessLogger.Log($"Spawning testplayer {i} since connected client count is {NetworkManager.Singleton.ConnectedClients.Count} is less than {WorkerCount} and Number of spawned external players is {MultiprocessOrchestration.ActiveWorkerCount()} "); - string logPath = MultiprocessOrchestration.StartWorkerNode(); // will automatically start built player as clients - MultiprocessLogger.Log($"logPath to new process is {logPath}"); - MultiprocessLogger.Log($"Active Worker Count {MultiprocessOrchestration.ActiveWorkerCount()} and connected client count is {NetworkManager.Singleton.ConnectedClients.Count}"); + var numProcessesToCreate = WorkerCount - (NetworkManager.Singleton.ConnectedClients.Count - 1); + for (int i = 1; i <= numProcessesToCreate; i++) + { + MultiprocessLogger.Log($"Spawning testplayer {i} since connected client count is {NetworkManager.Singleton.ConnectedClients.Count} is less than {WorkerCount} and Number of spawned external players is {MultiprocessOrchestration.ActiveWorkerCount()} "); + string logPath = MultiprocessOrchestration.StartWorkerNode(); // will automatically start built player as clients + MultiprocessLogger.Log($"logPath to new process is {logPath}"); + MultiprocessLogger.Log($"Active Worker Count {MultiprocessOrchestration.ActiveWorkerCount()} and connected client count is {NetworkManager.Singleton.ConnectedClients.Count}"); + } } } else { - MultiprocessLogger.Log($"No need to spawn a new test player as there are already existing processes {MultiprocessOrchestration.ActiveWorkerCount()} and connected clients {NetworkManager.Singleton.ConnectedClients.Count}"); + var launchProcessList = new List(); + if (NetworkManager.Singleton.ConnectedClients.Count - 1 < GetWorkerCount()) + { + var machines = MultiprocessOrchestration.GetRemoteMachineList(); + foreach (var machine in machines) + { + MultiprocessLogger.Log($"Would launch on {machine.Name} too get worker count to {GetWorkerCount()} from {NetworkManager.Singleton.ConnectedClients.Count - 1}"); + launchProcessList.Add(MultiprocessOrchestration.StartWorkersOnRemoteNodes(machine)); + } + } } var timeOutTime = Time.realtimeSinceStartup + TestCoordinator.MaxWaitTimeoutSec; diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessLogger.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessLogger.cs index c0cdd71162..bb156a2ac1 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessLogger.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessLogger.cs @@ -62,7 +62,7 @@ public void LogFormat(LogType logType, UnityEngine.Object context, string format testName = "unknown"; } - Debug.unityLogger.logHandler.LogFormat(logType, context, $"MPLOG({DateTime.Now:T}) : {testName} : {format}", args); + Debug.LogFormat(logType, LogOption.NoStacktrace, context, $"MPLOG({DateTime.Now:T}) : {testName} : {format}", args); } } } diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs index dd4c9af266..0f9398d52f 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs @@ -21,6 +21,9 @@ public static DirectoryInfo MultiprocessDirInfo } private static List s_Processes = new List(); private static int s_TotalProcessCounter = 0; + public static string PathToDll { get; private set; } + public static List ProcessList = new List(); + private static FileInfo s_Localip_fileinfo; private static DirectoryInfo initMultiprocessDirinfo() { @@ -39,12 +42,27 @@ private static DirectoryInfo initMultiprocessDirinfo() { MultiprocessDirInfo.Create(); } + s_Localip_fileinfo = new FileInfo(Path.Combine(s_MultiprocessDirInfo.FullName, "localip")); + return s_MultiprocessDirInfo; } static MultiprocessOrchestration() { initMultiprocessDirinfo(); + MultiprocessLogger.Log($" userprofile: {s_MultiprocessDirInfo.FullName} localipfile: {s_Localip_fileinfo}"); + var rootdir_FileInfo = new FileInfo(Path.Combine(MultiprocessDirInfo.FullName, "rootdir")); + MultiprocessLogger.Log($"Checking for the existence of {rootdir_FileInfo.FullName}"); + if (rootdir_FileInfo.Exists) + { + var rootDirText = (File.ReadAllText(rootdir_FileInfo.FullName)).Trim(); + PathToDll = Path.Combine(rootDirText, "multiplayer-multiprocess-test-tools/BokkenForNetcode/ProvisionBokkenMachines/bin/Debug/netcoreapp3.1/osx-x64", "ProvisionBokkenMachines.dll"); + } + else + { + MultiprocessLogger.Log("PathToDll cannot be set as rootDir doesn't exist"); + PathToDll = "unknown"; + } } /// @@ -185,4 +203,77 @@ public static void ShutdownAllProcesses() s_Processes.Clear(); } + + public static bool IsRemoteOperationEnabled() + { + string encodedPlatformList = Environment.GetEnvironmentVariable("MP_PLATFORM_LIST"); + if (encodedPlatformList != null && encodedPlatformList.Split(',').Length > 1) + { + return true; + } + return false; + } + + public static string[] GetRemotePlatformList() + { + // "default-win:test-win,default-mac:test-mac" + if (!IsRemoteOperationEnabled()) + { + return null; + } + string encodedPlatformList = Environment.GetEnvironmentVariable("MP_PLATFORM_LIST"); + string[] separated = encodedPlatformList.Split(','); + return separated; + } + + public static List GetRemoteMachineList() + { + var machineJson = new List(); + foreach (var f in MultiprocessDirInfo.GetFiles("*.json")) + { + if (f.Name.Equals("remoteConfig.json")) + { + continue; + } + else + { + machineJson.Add(f); + } + } + return machineJson; + } + + public static Process StartWorkersOnRemoteNodes(FileInfo machine) + { + string command = $" --command launch " + + $"--input-path {machine.FullName} "; + + var workerProcess = new Process(); + + workerProcess.StartInfo.FileName = Path.Combine("dotnet"); + workerProcess.StartInfo.UseShellExecute = false; + workerProcess.StartInfo.RedirectStandardError = true; + workerProcess.StartInfo.RedirectStandardOutput = true; + workerProcess.StartInfo.Arguments = $"{PathToDll} {command} "; + try + { + var newProcessStarted = workerProcess.Start(); + + if (!newProcessStarted) + { + throw new Exception("Failed to start worker process!"); + } + } + catch (Win32Exception e) + { + MultiprocessLogger.LogError($"Error starting bokken process, {e.Message} {e.Data} {e.ErrorCode}"); + throw; + } + + + ProcessList.Add(workerProcess); + + MultiprocessLogger.Log($"Execute Command: {PathToDll} {command} End"); + return workerProcess; + } } From eba211e30cb91872fbf10ea67964234dee41df7d Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Mon, 18 Jul 2022 14:30:40 -0400 Subject: [PATCH 005/138] fix: m_ListAtLastReset and dynamically spawned objects (#2065) --- com.unity.netcode.gameobjects/CHANGELOG.md | 8 +++++++ .../Collections/NetworkList.cs | 22 ++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 765dfed989..d4c23ef60a 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). +## [Unreleased] + +### Changed + +### Fixed + +- Fixed NetworkLists not populating on client. NetworkList now uses the most recent list as opposed to the list at the end of previous frame, when sending full updates to dynamically spawned NetworkObject. The difference in behaviour is required as scene management spawns those objects at a different time in the frame, relative to updates. (#2062) + ## [1.0.0] - 2022-06-27 ### Changed diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index 7fa8628c02..0d909fb0f2 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -122,10 +122,26 @@ public override void WriteDelta(FastBufferWriter writer) /// public override void WriteField(FastBufferWriter writer) { - writer.WriteValueSafe((ushort)m_ListAtLastReset.Length); - for (int i = 0; i < m_ListAtLastReset.Length; i++) + // The listAtLastReset mechanism was put in place to deal with duplicate adds + // upon initial spawn. However, it causes issues with in-scene placed objects + // due to difference in spawn order. In order to address this, we pick the right + // list based on the type of object. + bool isSceneObject = m_NetworkBehaviour.NetworkObject.IsSceneObject != false; + if (isSceneObject) { - NetworkVariableSerialization.Write(writer, ref m_ListAtLastReset.ElementAt(i)); + writer.WriteValueSafe((ushort)m_ListAtLastReset.Length); + for (int i = 0; i < m_ListAtLastReset.Length; i++) + { + NetworkVariableSerialization.Write(writer, ref m_ListAtLastReset.ElementAt(i)); + } + } + else + { + writer.WriteValueSafe((ushort)m_List.Length); + for (int i = 0; i < m_List.Length; i++) + { + NetworkVariableSerialization.Write(writer, ref m_List.ElementAt(i)); + } } } From 4f7b7160fe3ad857b0fc0414d9fe9999a3276b85 Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Tue, 19 Jul 2022 13:44:35 -0400 Subject: [PATCH 006/138] fix: correctly setting PreviousValue when changing the element of NetworkList (#2069) --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 + .../Collections/NetworkList.cs | 4 +- .../Tests/Runtime/ListChangedTest.cs | 82 +++++++++++++++++++ .../Tests/Runtime/ListChangedTest.cs.meta | 11 +++ 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/ListChangedTest.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/ListChangedTest.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index d4c23ef60a..dd9b951ed3 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -14,6 +14,8 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed NetworkLists not populating on client. NetworkList now uses the most recent list as opposed to the list at the end of previous frame, when sending full updates to dynamically spawned NetworkObject. The difference in behaviour is required as scene management spawns those objects at a different time in the frame, relative to updates. (#2062) +- Fixed NetworkList Value event on the server. PreviousValue is now set correctly when a new value is set through property setter. (#2067) + ## [1.0.0] - 2022-06-27 ### Changed diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index 0d909fb0f2..30f8459d1f 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -452,13 +452,15 @@ public T this[int index] get => m_List[index]; set { + var previousValue = m_List[index]; m_List[index] = value; var listEvent = new NetworkListEvent() { Type = NetworkListEvent.EventType.Value, Index = index, - Value = value + Value = value, + PreviousValue = previousValue }; HandleAddListEvent(listEvent); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ListChangedTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/ListChangedTest.cs new file mode 100644 index 0000000000..8c9c3be384 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ListChangedTest.cs @@ -0,0 +1,82 @@ +using System.Collections; +using UnityEngine; +using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; + +namespace Unity.Netcode.RuntimeTests +{ + public class NetworkListChangedTestComponent : NetworkBehaviour + { + + } + + public class ListChangedObject : NetworkBehaviour + { + public int ExpectedPreviousValue = 0; + public int ExpectedValue = 0; + public bool AddDone = false; + + public NetworkList MyNetworkList = new NetworkList(); + + public override void OnNetworkSpawn() + { + MyNetworkList.OnListChanged += Changed; + base.OnNetworkSpawn(); + } + + public void Changed(NetworkListEvent listEvent) + { + if (listEvent.Type == NetworkListEvent.EventType.Value) + { + if (listEvent.PreviousValue != ExpectedPreviousValue) + { + Debug.Log($"Expected previous value mismatch {listEvent.PreviousValue} versus {ExpectedPreviousValue}"); + Debug.Assert(listEvent.PreviousValue == ExpectedPreviousValue); + } + + if (listEvent.Value != ExpectedValue) + { + Debug.Log($"Expected value mismatch {listEvent.Value} versus {ExpectedValue}"); + Debug.Assert(listEvent.Value == ExpectedValue); + } + + AddDone = true; + } + } + } + + public class NetworkListChangedTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 2; + + private ulong m_ClientId0; + private GameObject m_PrefabToSpawn; + + private NetworkObject m_NetSpawnedObject1; + + protected override void OnServerAndClientsCreated() + { + m_PrefabToSpawn = CreateNetworkObjectPrefab("ListChangedObject"); + m_PrefabToSpawn.AddComponent(); + } + + [UnityTest] + public IEnumerator NetworkListChangedTest() + { + m_ClientId0 = m_ClientNetworkManagers[0].LocalClientId; + + // create 3 objects + var spawnedObject1 = SpawnObject(m_PrefabToSpawn, m_ServerNetworkManager); + m_NetSpawnedObject1 = spawnedObject1.GetComponent(); + + m_NetSpawnedObject1.GetComponent().MyNetworkList.Add(42); + m_NetSpawnedObject1.GetComponent().ExpectedPreviousValue = 42; + m_NetSpawnedObject1.GetComponent().ExpectedValue = 44; + m_NetSpawnedObject1.GetComponent().MyNetworkList[0] = 44; + + Debug.Assert(m_NetSpawnedObject1.GetComponent().AddDone); + + return null; + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ListChangedTest.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/ListChangedTest.cs.meta new file mode 100644 index 0000000000..1cc34f6f91 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ListChangedTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b269e2a059f814075a737691bc02afa4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From fe6eee674419524e7fc618b45841f5e5393b4f8a Mon Sep 17 00:00:00 2001 From: ashwini <36935028+ashwinimurt@users.noreply.github.com> Date: Wed, 20 Jul 2022 09:15:08 -0700 Subject: [PATCH 007/138] =?UTF-8?q?chore:=20Disable=20unstable=20test=20Wh?= =?UTF-8?q?enAMessageIsDeferredForMoreThanTheConf=E2=80=A6=20(#2070)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: Disable unstable test WhenAMessageIsDeferredForMoreThanTheConfiguredTime_ItIsRemoved * Update com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs Co-authored-by: Fatih Mar Co-authored-by: Fatih Mar --- .../Tests/Runtime/DeferredMessagingTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs index 05826ac266..28ed3793df 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs @@ -813,6 +813,7 @@ public IEnumerator WhenSpawnTriggeredMessagesAreDeferredBeforeThePrefabIsAdded_A } [UnityTest] + [Ignore("This test is unstable (MTT-4146)")] public IEnumerator WhenAMessageIsDeferredForMoreThanTheConfiguredTime_ItIsRemoved([Values(1, 2, 3)] int timeout) { RegisterClientPrefabs(); From 2c492623cbbe6c4d52b71dd4e1fbb51bfed089ee Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 20 Jul 2022 18:22:34 -0500 Subject: [PATCH 008/138] chore: Remove legacy NetworkAnimator tests from the sdk runtime tests folder [MTT-4024] (#2073) --- .../NetworkAnimator/NetworkAnimatorTests.cs | 239 ---------- .../NetworkAnimatorTests.cs.meta | 11 - .../Runtime/NetworkAnimator/Resources.meta | 8 - .../Resources/AlphaAnimation.anim | 53 --- .../Resources/AlphaAnimation.anim.meta | 8 - .../Resources/DefaultAnimation.anim | 53 --- .../Resources/DefaultAnimation.anim.meta | 8 - .../Resources/Layer2Animation.anim | 53 --- .../Resources/Layer2Animation.anim.meta | 8 - .../Resources/OverrideAlphaAnimation.anim | 53 --- .../OverrideAlphaAnimation.anim.meta | 8 - .../Resources/OverrideDefaultAnimation.anim | 53 --- .../OverrideDefaultAnimation.anim.meta | 8 - .../TestAnimatorController.controller | 449 ------------------ .../TestAnimatorController.controller.meta | 8 - ...matorOverrideController.overrideController | 15 - ...OverrideController.overrideController.meta | 8 - 17 files changed, 1043 deletions(-) delete mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/NetworkAnimatorTests.cs delete mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/NetworkAnimatorTests.cs.meta delete mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources.meta delete mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/AlphaAnimation.anim delete mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/AlphaAnimation.anim.meta delete mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/DefaultAnimation.anim delete mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/DefaultAnimation.anim.meta delete mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/Layer2Animation.anim delete mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/Layer2Animation.anim.meta delete mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/OverrideAlphaAnimation.anim delete mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/OverrideAlphaAnimation.anim.meta delete mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/OverrideDefaultAnimation.anim delete mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/OverrideDefaultAnimation.anim.meta delete mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/TestAnimatorController.controller delete mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/TestAnimatorController.controller.meta delete mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/TestAnimatorOverrideController.overrideController delete mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/TestAnimatorOverrideController.overrideController.meta diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/NetworkAnimatorTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/NetworkAnimatorTests.cs deleted file mode 100644 index 108e37e55a..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/NetworkAnimatorTests.cs +++ /dev/null @@ -1,239 +0,0 @@ -#if COM_UNITY_MODULES_ANIMATION -using System.Collections; -using System.Collections.Generic; -using NUnit.Framework; -using Unity.Netcode.Components; -using UnityEngine; -using UnityEngine.TestTools; -using Unity.Netcode.TestHelpers.Runtime; - -namespace Unity.Netcode.RuntimeTests -{ - [TestFixture(HostOrServer.Host)] - [TestFixture(HostOrServer.Server)] - public class NetworkAnimatorTests : NetcodeIntegrationTest - { - protected override int NumberOfClients => 1; - - private GameObject m_PlayerOnServer; - private GameObject m_PlayerOnClient; - - private Animator m_PlayerOnServerAnimator; - private Animator m_PlayerOnClientAnimator; - - public NetworkAnimatorTests(HostOrServer hostOrServer) : base(hostOrServer) { } - - protected override void OnCreatePlayerPrefab() - { - // ideally, we would build up the AnimatorController entirely in code and not need an asset, - // but after some attempts this doesn't seem readily doable. Instead, we load a controller - var controller = Resources.Load("TestAnimatorController") as RuntimeAnimatorController; - var animator = m_PlayerPrefab.AddComponent(); - animator.runtimeAnimatorController = controller; - - var networkAnimator = m_PlayerPrefab.AddComponent(); - networkAnimator.Animator = animator; - } - - protected override IEnumerator OnServerAndClientsConnected() - { - m_PlayerOnServer = m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][m_ClientNetworkManagers[0].LocalClientId].gameObject; - m_PlayerOnServerAnimator = m_PlayerOnServerAnimator = m_PlayerOnServer.GetComponent(); - - m_PlayerOnClient = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][m_ClientNetworkManagers[0].LocalClientId].gameObject; - m_PlayerOnClientAnimator = m_PlayerOnClient.GetComponent(); - - return base.OnServerAndClientsConnected(); - } - - // helper function to scan an animator and verify a given clip is present - private bool HasClip(Animator animator, string clipName) - { - var clips = new List(); - animator.GetCurrentAnimatorClipInfo(0, clips); - foreach (var clip in clips) - { - if (clip.clip.name == clipName) - { - return true; - } - } - return false; - } - - [UnityTest] - public IEnumerator AnimationTriggerReset([Values(true, false)] bool asHash) - { - // We have "UnboundTrigger" purposely not bound to any animations so we can test resetting. - // If we used a trigger that was bound to a transition, then the trigger would reset as soon as the - // transition happens. This way it will stay stuck on - string triggerString = "UnboundTrigger"; - int triggerHash = Animator.StringToHash(triggerString); - - // Verify trigger is off - Assert.True(m_PlayerOnServerAnimator.GetBool(triggerString) == false); - Assert.True(m_PlayerOnClientAnimator.GetBool(triggerString) == false); - - // trigger. - if (asHash) - { - m_PlayerOnServer.GetComponent().SetTrigger(triggerHash); - } - else - { - m_PlayerOnServer.GetComponent().SetTrigger(triggerString); - } - - // verify trigger is set for client and server - yield return WaitForConditionOrTimeOut(() => asHash ? m_PlayerOnServerAnimator.GetBool(triggerHash) : m_PlayerOnServerAnimator.GetBool(triggerString)); - Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out on server trigger set check"); - - yield return WaitForConditionOrTimeOut(() => asHash ? m_PlayerOnClientAnimator.GetBool(triggerHash) : m_PlayerOnClientAnimator.GetBool(triggerString)); - Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out on client trigger set check"); - - // reset the trigger - if (asHash) - { - m_PlayerOnServer.GetComponent().ResetTrigger(triggerHash); - } - else - { - m_PlayerOnServer.GetComponent().ResetTrigger(triggerString); - } - - // verify trigger is reset for client and server - yield return WaitForConditionOrTimeOut(() => asHash ? m_PlayerOnServerAnimator.GetBool(triggerHash) == false : m_PlayerOnServerAnimator.GetBool(triggerString) == false); - Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out on server reset check"); - - yield return WaitForConditionOrTimeOut(() => asHash ? m_PlayerOnClientAnimator.GetBool(triggerHash) == false : m_PlayerOnClientAnimator.GetBool(triggerString) == false); - Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out on client reset check"); - } - - - [UnityTest] - public IEnumerator AnimationStateSyncTest() - { - // check that we have started in the default state - Assert.True(m_PlayerOnServerAnimator.GetCurrentAnimatorStateInfo(0).IsName("DefaultState")); - Assert.True(m_PlayerOnClientAnimator.GetCurrentAnimatorStateInfo(0).IsName("DefaultState")); - - // cause a change to the AlphaState state by setting AlphaParameter, which is - // the variable bound to the transition from default to AlphaState (see the TestAnimatorController asset) - m_PlayerOnServerAnimator.SetBool("AlphaParameter", true); - - // ...and now we should be in the AlphaState having triggered the AlphaParameter - yield return WaitForConditionOrTimeOut(() => m_PlayerOnServerAnimator.GetCurrentAnimatorStateInfo(0).IsName("AlphaState")); - Assert.False(s_GlobalTimeoutHelper.TimedOut, "Server failed to reach its animation state"); - - // ...and now the client should also have sync'd and arrived at the correct state - yield return WaitForConditionOrTimeOut(() => m_PlayerOnClientAnimator.GetCurrentAnimatorStateInfo(0).IsName("AlphaState")); - Assert.False(s_GlobalTimeoutHelper.TimedOut, "Client failed to sync its animation state from the server"); - } - - [UnityTest] - public IEnumerator AnimationLayerStateSyncTest() - { - int layer = 1; - // check that we have started in the default state - Assert.True(m_PlayerOnServerAnimator.GetCurrentAnimatorStateInfo(layer).IsName("DefaultStateLayer2")); - Assert.True(m_PlayerOnClientAnimator.GetCurrentAnimatorStateInfo(layer).IsName("DefaultStateLayer2")); - - // cause a change to the AlphaState state by setting AlphaParameter, which is - // the variable bound to the transition from default to AlphaState (see the TestAnimatorController asset) - m_PlayerOnServerAnimator.SetBool("Layer2AlphaParameter", true); - - // ...and now we should be in the AlphaState having triggered the AlphaParameter - yield return WaitForConditionOrTimeOut(() => m_PlayerOnServerAnimator.GetCurrentAnimatorStateInfo(layer).IsName("Layer2AlphaState")); - Assert.False(s_GlobalTimeoutHelper.TimedOut, "Server failed to reach its animation state"); - - // ...and now the client should also have sync'd and arrived at the correct state - yield return WaitForConditionOrTimeOut(() => m_PlayerOnClientAnimator.GetCurrentAnimatorStateInfo(layer).IsName("Layer2AlphaState")); - Assert.False(s_GlobalTimeoutHelper.TimedOut, "Client failed to sync its animation state from the server"); - } - - [UnityTest] - public IEnumerator AnimationLayerWeightTest() - { - int layer = 1; - float targetWeight = 0.333f; - - // check that we have started in the default state - Assert.True(Mathf.Approximately(m_PlayerOnServerAnimator.GetLayerWeight(layer), 1f)); - Assert.True(Mathf.Approximately(m_PlayerOnClientAnimator.GetLayerWeight(layer), 1f)); - - m_PlayerOnServerAnimator.SetLayerWeight(layer, targetWeight); - - // ...and now we should be in the AlphaState having triggered the AlphaParameter - yield return WaitForConditionOrTimeOut(() => - Mathf.Approximately(m_PlayerOnServerAnimator.GetLayerWeight(layer), targetWeight) - ); - Assert.False(s_GlobalTimeoutHelper.TimedOut, "Server failed to reach its animation state"); - - // ...and now the client should also have sync'd and arrived at the correct state - yield return WaitForConditionOrTimeOut(() => - Mathf.Approximately(m_PlayerOnClientAnimator.GetLayerWeight(layer), targetWeight) - ); - Assert.False(s_GlobalTimeoutHelper.TimedOut, "Server failed to reach its animation state"); - } - - - [UnityTest] - public IEnumerator AnimationStateSyncTriggerTest([Values(true, false)] bool asHash) - { - string triggerString = "TestTrigger"; - int triggerHash = Animator.StringToHash(triggerString); - - // check that we have started in the default state - Assert.True(m_PlayerOnServerAnimator.GetCurrentAnimatorStateInfo(0).IsName("DefaultState")); - Assert.True(m_PlayerOnClientAnimator.GetCurrentAnimatorStateInfo(0).IsName("DefaultState")); - - // cause a change to the AlphaState state by setting TestTrigger - // note, we have a special test for triggers because activating triggers via the - // NetworkAnimator is special; for other parameters you set them on the Animator and NetworkAnimator - // listens. But because triggers are super short and transitory, we require users to call - // NetworkAnimator.SetTrigger so we don't miss it - if (asHash) - { - m_PlayerOnServer.GetComponent().SetTrigger(triggerHash); - } - else - { - m_PlayerOnServer.GetComponent().SetTrigger(triggerString); - } - - // ...and now we should be in the AlphaState having triggered the AlphaParameter - yield return WaitForConditionOrTimeOut(() => m_PlayerOnServerAnimator.GetCurrentAnimatorStateInfo(0).IsName("TriggeredState")); - Assert.False(s_GlobalTimeoutHelper.TimedOut, "Server failed to reach its animation state via trigger"); - - // ...and now the client should also have sync'd and arrived at the correct state - yield return WaitForConditionOrTimeOut(() => m_PlayerOnClientAnimator.GetCurrentAnimatorStateInfo(0).IsName("TriggeredState")); - Assert.False(s_GlobalTimeoutHelper.TimedOut, "Client failed to sync its animation state from the server via trigger"); - } - - [UnityTest] - public IEnumerator AnimationStateSyncTestWithOverride() - { - // set up the animation override controller - var overrideController = Resources.Load("TestAnimatorOverrideController") as AnimatorOverrideController; - m_PlayerOnServer.GetComponent().runtimeAnimatorController = overrideController; - m_PlayerOnClient.GetComponent().runtimeAnimatorController = overrideController; - - // in our default state, we should see the OverrideDefaultAnimation clip - Assert.True(HasClip(m_PlayerOnServerAnimator, "OverrideDefaultAnimation")); - Assert.True(HasClip(m_PlayerOnClientAnimator, "OverrideDefaultAnimation")); - - // cause a change to the AlphaState state by setting AlphaParameter, which is - // the variable bound to the transition from default to AlphaState (see the TestAnimatorController asset) - m_PlayerOnServerAnimator.SetBool("AlphaParameter", true); - - // ...and now we should be in the AlphaState having set the AlphaParameter - yield return WaitForConditionOrTimeOut(() => HasClip(m_PlayerOnServerAnimator, "OverrideAlphaAnimation")); - Assert.False(s_GlobalTimeoutHelper.TimedOut, "Server failed to reach its overriden animation state"); - - // ...and now the client should also have sync'd and arrived at the correct state - yield return WaitForConditionOrTimeOut(() => HasClip(m_PlayerOnServerAnimator, "OverrideAlphaAnimation")); - Assert.False(s_GlobalTimeoutHelper.TimedOut, "Client failed to reach its overriden animation state"); - } - } -} -#endif // COM_UNITY_MODULES_ANIMATION diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/NetworkAnimatorTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/NetworkAnimatorTests.cs.meta deleted file mode 100644 index 19205b5511..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/NetworkAnimatorTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: c2e5a740c1abd4315801e3f26ecf8adb -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources.meta deleted file mode 100644 index d3faa16d04..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: c3a8707ef624947a7ae8843ca6c70c0a -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/AlphaAnimation.anim b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/AlphaAnimation.anim deleted file mode 100644 index cb964eee55..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/AlphaAnimation.anim +++ /dev/null @@ -1,53 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!74 &7400000 -AnimationClip: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: AlphaAnimation - serializedVersion: 6 - m_Legacy: 0 - m_Compressed: 0 - m_UseHighQualityCurve: 1 - m_RotationCurves: [] - m_CompressedRotationCurves: [] - m_EulerCurves: [] - m_PositionCurves: [] - m_ScaleCurves: [] - m_FloatCurves: [] - m_PPtrCurves: [] - m_SampleRate: 60 - m_WrapMode: 0 - m_Bounds: - m_Center: {x: 0, y: 0, z: 0} - m_Extent: {x: 0, y: 0, z: 0} - m_ClipBindingConstant: - genericBindings: [] - pptrCurveMapping: [] - m_AnimationClipSettings: - serializedVersion: 2 - m_AdditiveReferencePoseClip: {fileID: 0} - m_AdditiveReferencePoseTime: 0 - m_StartTime: 0 - m_StopTime: 1 - m_OrientationOffsetY: 0 - m_Level: 0 - m_CycleOffset: 0 - m_HasAdditiveReferencePose: 0 - m_LoopTime: 0 - m_LoopBlend: 0 - m_LoopBlendOrientation: 0 - m_LoopBlendPositionY: 0 - m_LoopBlendPositionXZ: 0 - m_KeepOriginalOrientation: 0 - m_KeepOriginalPositionY: 1 - m_KeepOriginalPositionXZ: 0 - m_HeightFromFeet: 0 - m_Mirror: 0 - m_EditorCurves: [] - m_EulerEditorCurves: [] - m_HasGenericRootTransform: 0 - m_HasMotionFloatCurves: 0 - m_Events: [] diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/AlphaAnimation.anim.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/AlphaAnimation.anim.meta deleted file mode 100644 index 46edd4d329..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/AlphaAnimation.anim.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: db8faf64ca46248abb6624513ac1fb1b -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 7400000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/DefaultAnimation.anim b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/DefaultAnimation.anim deleted file mode 100644 index f19491b84e..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/DefaultAnimation.anim +++ /dev/null @@ -1,53 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!74 &7400000 -AnimationClip: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: DefaultAnimation - serializedVersion: 6 - m_Legacy: 0 - m_Compressed: 0 - m_UseHighQualityCurve: 1 - m_RotationCurves: [] - m_CompressedRotationCurves: [] - m_EulerCurves: [] - m_PositionCurves: [] - m_ScaleCurves: [] - m_FloatCurves: [] - m_PPtrCurves: [] - m_SampleRate: 60 - m_WrapMode: 0 - m_Bounds: - m_Center: {x: 0, y: 0, z: 0} - m_Extent: {x: 0, y: 0, z: 0} - m_ClipBindingConstant: - genericBindings: [] - pptrCurveMapping: [] - m_AnimationClipSettings: - serializedVersion: 2 - m_AdditiveReferencePoseClip: {fileID: 0} - m_AdditiveReferencePoseTime: 0 - m_StartTime: 0 - m_StopTime: 1 - m_OrientationOffsetY: 0 - m_Level: 0 - m_CycleOffset: 0 - m_HasAdditiveReferencePose: 0 - m_LoopTime: 0 - m_LoopBlend: 0 - m_LoopBlendOrientation: 0 - m_LoopBlendPositionY: 0 - m_LoopBlendPositionXZ: 0 - m_KeepOriginalOrientation: 0 - m_KeepOriginalPositionY: 1 - m_KeepOriginalPositionXZ: 0 - m_HeightFromFeet: 0 - m_Mirror: 0 - m_EditorCurves: [] - m_EulerEditorCurves: [] - m_HasGenericRootTransform: 0 - m_HasMotionFloatCurves: 0 - m_Events: [] diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/DefaultAnimation.anim.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/DefaultAnimation.anim.meta deleted file mode 100644 index e79be12bb4..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/DefaultAnimation.anim.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 1f6191147839943ab93e2171cc15c5e9 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 7400000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/Layer2Animation.anim b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/Layer2Animation.anim deleted file mode 100644 index e0bbbe796f..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/Layer2Animation.anim +++ /dev/null @@ -1,53 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!74 &7400000 -AnimationClip: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: Layer2Animation - serializedVersion: 6 - m_Legacy: 0 - m_Compressed: 0 - m_UseHighQualityCurve: 1 - m_RotationCurves: [] - m_CompressedRotationCurves: [] - m_EulerCurves: [] - m_PositionCurves: [] - m_ScaleCurves: [] - m_FloatCurves: [] - m_PPtrCurves: [] - m_SampleRate: 60 - m_WrapMode: 0 - m_Bounds: - m_Center: {x: 0, y: 0, z: 0} - m_Extent: {x: 0, y: 0, z: 0} - m_ClipBindingConstant: - genericBindings: [] - pptrCurveMapping: [] - m_AnimationClipSettings: - serializedVersion: 2 - m_AdditiveReferencePoseClip: {fileID: 0} - m_AdditiveReferencePoseTime: 0 - m_StartTime: 0 - m_StopTime: 1 - m_OrientationOffsetY: 0 - m_Level: 0 - m_CycleOffset: 0 - m_HasAdditiveReferencePose: 0 - m_LoopTime: 0 - m_LoopBlend: 0 - m_LoopBlendOrientation: 0 - m_LoopBlendPositionY: 0 - m_LoopBlendPositionXZ: 0 - m_KeepOriginalOrientation: 0 - m_KeepOriginalPositionY: 1 - m_KeepOriginalPositionXZ: 0 - m_HeightFromFeet: 0 - m_Mirror: 0 - m_EditorCurves: [] - m_EulerEditorCurves: [] - m_HasGenericRootTransform: 0 - m_HasMotionFloatCurves: 0 - m_Events: [] diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/Layer2Animation.anim.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/Layer2Animation.anim.meta deleted file mode 100644 index 2d1fbd70b4..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/Layer2Animation.anim.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: d31c84f6372c54d7eb8decb27010d005 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 7400000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/OverrideAlphaAnimation.anim b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/OverrideAlphaAnimation.anim deleted file mode 100644 index 612342096c..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/OverrideAlphaAnimation.anim +++ /dev/null @@ -1,53 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!74 &7400000 -AnimationClip: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: OverrideAlphaAnimation - serializedVersion: 6 - m_Legacy: 0 - m_Compressed: 0 - m_UseHighQualityCurve: 1 - m_RotationCurves: [] - m_CompressedRotationCurves: [] - m_EulerCurves: [] - m_PositionCurves: [] - m_ScaleCurves: [] - m_FloatCurves: [] - m_PPtrCurves: [] - m_SampleRate: 60 - m_WrapMode: 0 - m_Bounds: - m_Center: {x: 0, y: 0, z: 0} - m_Extent: {x: 0, y: 0, z: 0} - m_ClipBindingConstant: - genericBindings: [] - pptrCurveMapping: [] - m_AnimationClipSettings: - serializedVersion: 2 - m_AdditiveReferencePoseClip: {fileID: 0} - m_AdditiveReferencePoseTime: 0 - m_StartTime: 0 - m_StopTime: 1 - m_OrientationOffsetY: 0 - m_Level: 0 - m_CycleOffset: 0 - m_HasAdditiveReferencePose: 0 - m_LoopTime: 0 - m_LoopBlend: 0 - m_LoopBlendOrientation: 0 - m_LoopBlendPositionY: 0 - m_LoopBlendPositionXZ: 0 - m_KeepOriginalOrientation: 0 - m_KeepOriginalPositionY: 1 - m_KeepOriginalPositionXZ: 0 - m_HeightFromFeet: 0 - m_Mirror: 0 - m_EditorCurves: [] - m_EulerEditorCurves: [] - m_HasGenericRootTransform: 0 - m_HasMotionFloatCurves: 0 - m_Events: [] diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/OverrideAlphaAnimation.anim.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/OverrideAlphaAnimation.anim.meta deleted file mode 100644 index bf0e868d65..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/OverrideAlphaAnimation.anim.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 05a2afc2ff8884d32afc64ed6765880a -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 7400000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/OverrideDefaultAnimation.anim b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/OverrideDefaultAnimation.anim deleted file mode 100644 index 9ac691703d..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/OverrideDefaultAnimation.anim +++ /dev/null @@ -1,53 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!74 &7400000 -AnimationClip: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: OverrideDefaultAnimation - serializedVersion: 6 - m_Legacy: 0 - m_Compressed: 0 - m_UseHighQualityCurve: 1 - m_RotationCurves: [] - m_CompressedRotationCurves: [] - m_EulerCurves: [] - m_PositionCurves: [] - m_ScaleCurves: [] - m_FloatCurves: [] - m_PPtrCurves: [] - m_SampleRate: 60 - m_WrapMode: 0 - m_Bounds: - m_Center: {x: 0, y: 0, z: 0} - m_Extent: {x: 0, y: 0, z: 0} - m_ClipBindingConstant: - genericBindings: [] - pptrCurveMapping: [] - m_AnimationClipSettings: - serializedVersion: 2 - m_AdditiveReferencePoseClip: {fileID: 0} - m_AdditiveReferencePoseTime: 0 - m_StartTime: 0 - m_StopTime: 1 - m_OrientationOffsetY: 0 - m_Level: 0 - m_CycleOffset: 0 - m_HasAdditiveReferencePose: 0 - m_LoopTime: 0 - m_LoopBlend: 0 - m_LoopBlendOrientation: 0 - m_LoopBlendPositionY: 0 - m_LoopBlendPositionXZ: 0 - m_KeepOriginalOrientation: 0 - m_KeepOriginalPositionY: 1 - m_KeepOriginalPositionXZ: 0 - m_HeightFromFeet: 0 - m_Mirror: 0 - m_EditorCurves: [] - m_EulerEditorCurves: [] - m_HasGenericRootTransform: 0 - m_HasMotionFloatCurves: 0 - m_Events: [] diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/OverrideDefaultAnimation.anim.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/OverrideDefaultAnimation.anim.meta deleted file mode 100644 index bcfba8fb79..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/OverrideDefaultAnimation.anim.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: cf503a5569d0b4df4910a26d09ce4530 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 7400000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/TestAnimatorController.controller b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/TestAnimatorController.controller deleted file mode 100644 index 247a8e4896..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/TestAnimatorController.controller +++ /dev/null @@ -1,449 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!1102 &-8144973961595650150 -AnimatorState: - serializedVersion: 6 - m_ObjectHideFlags: 1 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: New State - m_Speed: 1 - m_CycleOffset: 0 - m_Transitions: [] - m_StateMachineBehaviours: [] - m_Position: {x: 50, y: 50, z: 0} - m_IKOnFeet: 0 - m_WriteDefaultValues: 1 - m_Mirror: 0 - m_SpeedParameterActive: 0 - m_MirrorParameterActive: 0 - m_CycleOffsetParameterActive: 0 - m_TimeParameterActive: 0 - m_Motion: {fileID: 0} - m_Tag: - m_SpeedParameter: - m_MirrorParameter: - m_CycleOffsetParameter: - m_TimeParameter: ---- !u!1102 &-7257898091357968356 -AnimatorState: - serializedVersion: 6 - m_ObjectHideFlags: 1 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: New State - m_Speed: 1 - m_CycleOffset: 0 - m_Transitions: [] - m_StateMachineBehaviours: [] - m_Position: {x: 50, y: 50, z: 0} - m_IKOnFeet: 0 - m_WriteDefaultValues: 1 - m_Mirror: 0 - m_SpeedParameterActive: 0 - m_MirrorParameterActive: 0 - m_CycleOffsetParameterActive: 0 - m_TimeParameterActive: 0 - m_Motion: {fileID: 0} - m_Tag: - m_SpeedParameter: - m_MirrorParameter: - m_CycleOffsetParameter: - m_TimeParameter: ---- !u!1101 &-7235917949335567458 -AnimatorStateTransition: - m_ObjectHideFlags: 1 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: - m_Conditions: - - m_ConditionMode: 2 - m_ConditionEvent: Layer2AlphaParameter - m_EventTreshold: 0 - m_DstStateMachine: {fileID: 0} - m_DstState: {fileID: 6016706997111698284} - m_Solo: 0 - m_Mute: 0 - m_IsExit: 0 - serializedVersion: 3 - m_TransitionDuration: 0.25 - m_TransitionOffset: 0 - m_ExitTime: 0.75 - m_HasExitTime: 1 - m_HasFixedDuration: 1 - m_InterruptionSource: 2 - m_OrderedInterruption: 1 - m_CanTransitionToSelf: 1 ---- !u!1101 &-6097014330458455406 -AnimatorStateTransition: - m_ObjectHideFlags: 1 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: - m_Conditions: - - m_ConditionMode: 2 - m_ConditionEvent: AlphaParameter - m_EventTreshold: 0 - m_DstStateMachine: {fileID: 0} - m_DstState: {fileID: -1198466922477486815} - m_Solo: 0 - m_Mute: 0 - m_IsExit: 0 - serializedVersion: 3 - m_TransitionDuration: 0.25 - m_TransitionOffset: 0 - m_ExitTime: 0.75 - m_HasExitTime: 1 - m_HasFixedDuration: 1 - m_InterruptionSource: 0 - m_OrderedInterruption: 1 - m_CanTransitionToSelf: 1 ---- !u!1107 &-1914299053840757887 -AnimatorStateMachine: - serializedVersion: 6 - m_ObjectHideFlags: 1 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: Base Layer - m_ChildStates: - - serializedVersion: 1 - m_State: {fileID: -1198466922477486815} - m_Position: {x: 70, y: 290, z: 0} - - serializedVersion: 1 - m_State: {fileID: 320527679719022362} - m_Position: {x: 110, y: 490, z: 0} - - serializedVersion: 1 - m_State: {fileID: 3942933370568001311} - m_Position: {x: 380, y: 280, z: 0} - m_ChildStateMachines: [] - m_AnyStateTransitions: [] - m_EntryTransitions: [] - m_StateMachineTransitions: {} - m_StateMachineBehaviours: [] - m_AnyStatePosition: {x: 50, y: 20, z: 0} - m_EntryPosition: {x: 30, y: 180, z: 0} - m_ExitPosition: {x: 800, y: 120, z: 0} - m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} - m_DefaultState: {fileID: -1198466922477486815} ---- !u!1102 &-1198466922477486815 -AnimatorState: - serializedVersion: 6 - m_ObjectHideFlags: 1 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: DefaultState - m_Speed: 1 - m_CycleOffset: 0 - m_Transitions: - - {fileID: 232953446134799302} - - {fileID: 8340347106517238820} - m_StateMachineBehaviours: [] - m_Position: {x: 50, y: 50, z: 0} - m_IKOnFeet: 0 - m_WriteDefaultValues: 1 - m_Mirror: 0 - m_SpeedParameterActive: 0 - m_MirrorParameterActive: 0 - m_CycleOffsetParameterActive: 0 - m_TimeParameterActive: 0 - m_Motion: {fileID: 7400000, guid: 1f6191147839943ab93e2171cc15c5e9, type: 2} - m_Tag: - m_SpeedParameter: - m_MirrorParameter: - m_CycleOffsetParameter: - m_TimeParameter: ---- !u!91 &9100000 -AnimatorController: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: TestAnimatorController - serializedVersion: 5 - m_AnimatorParameters: - - m_Name: AlphaParameter - m_Type: 4 - m_DefaultFloat: 0 - m_DefaultInt: 0 - m_DefaultBool: 0 - m_Controller: {fileID: 9100000} - - m_Name: TestTrigger - m_Type: 9 - m_DefaultFloat: 0 - m_DefaultInt: 0 - m_DefaultBool: 0 - m_Controller: {fileID: 9100000} - - m_Name: UnboundTrigger - m_Type: 9 - m_DefaultFloat: 0 - m_DefaultInt: 0 - m_DefaultBool: 0 - m_Controller: {fileID: 9100000} - - m_Name: Layer2AlphaParameter - m_Type: 4 - m_DefaultFloat: 0 - m_DefaultInt: 0 - m_DefaultBool: 0 - m_Controller: {fileID: 9100000} - m_AnimatorLayers: - - serializedVersion: 5 - m_Name: Base Layer - m_StateMachine: {fileID: -1914299053840757887} - m_Mask: {fileID: 0} - m_Motions: [] - m_Behaviours: [] - m_BlendingMode: 0 - m_SyncedLayerIndex: -1 - m_DefaultWeight: 0 - m_IKPass: 0 - m_SyncedLayerAffectsTiming: 0 - m_Controller: {fileID: 9100000} - - serializedVersion: 5 - m_Name: Layer2 - m_StateMachine: {fileID: 1433017894673297828} - m_Mask: {fileID: 0} - m_Motions: [] - m_Behaviours: [] - m_BlendingMode: 0 - m_SyncedLayerIndex: -1 - m_DefaultWeight: 1 - m_IKPass: 0 - m_SyncedLayerAffectsTiming: 0 - m_Controller: {fileID: 9100000} ---- !u!1101 &232953446134799302 -AnimatorStateTransition: - m_ObjectHideFlags: 1 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: - m_Conditions: - - m_ConditionMode: 1 - m_ConditionEvent: AlphaParameter - m_EventTreshold: 0 - m_DstStateMachine: {fileID: 0} - m_DstState: {fileID: 320527679719022362} - m_Solo: 0 - m_Mute: 0 - m_IsExit: 0 - serializedVersion: 3 - m_TransitionDuration: 0.25 - m_TransitionOffset: 0 - m_ExitTime: 0.75 - m_HasExitTime: 1 - m_HasFixedDuration: 1 - m_InterruptionSource: 0 - m_OrderedInterruption: 1 - m_CanTransitionToSelf: 1 ---- !u!1102 &320527679719022362 -AnimatorState: - serializedVersion: 6 - m_ObjectHideFlags: 1 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: AlphaState - m_Speed: 1 - m_CycleOffset: 0 - m_Transitions: [] - m_StateMachineBehaviours: [] - m_Position: {x: 50, y: 50, z: 0} - m_IKOnFeet: 0 - m_WriteDefaultValues: 1 - m_Mirror: 0 - m_SpeedParameterActive: 0 - m_MirrorParameterActive: 0 - m_CycleOffsetParameterActive: 0 - m_TimeParameterActive: 0 - m_Motion: {fileID: 7400000, guid: db8faf64ca46248abb6624513ac1fb1b, type: 2} - m_Tag: - m_SpeedParameter: - m_MirrorParameter: - m_CycleOffsetParameter: - m_TimeParameter: ---- !u!1102 &927597079590233140 -AnimatorState: - serializedVersion: 6 - m_ObjectHideFlags: 1 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: Layer2AlphaState - m_Speed: 1 - m_CycleOffset: 0 - m_Transitions: - - {fileID: -7235917949335567458} - m_StateMachineBehaviours: [] - m_Position: {x: 50, y: 50, z: 0} - m_IKOnFeet: 0 - m_WriteDefaultValues: 1 - m_Mirror: 0 - m_SpeedParameterActive: 0 - m_MirrorParameterActive: 0 - m_CycleOffsetParameterActive: 0 - m_TimeParameterActive: 0 - m_Motion: {fileID: 7400000, guid: d31c84f6372c54d7eb8decb27010d005, type: 2} - m_Tag: - m_SpeedParameter: - m_MirrorParameter: - m_CycleOffsetParameter: - m_TimeParameter: ---- !u!1107 &1433017894673297828 -AnimatorStateMachine: - serializedVersion: 6 - m_ObjectHideFlags: 1 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: Layer2 - m_ChildStates: - - serializedVersion: 1 - m_State: {fileID: 6016706997111698284} - m_Position: {x: 160, y: 250, z: 0} - - serializedVersion: 1 - m_State: {fileID: 927597079590233140} - m_Position: {x: 270, y: 370, z: 0} - m_ChildStateMachines: [] - m_AnyStateTransitions: [] - m_EntryTransitions: [] - m_StateMachineTransitions: {} - m_StateMachineBehaviours: [] - m_AnyStatePosition: {x: 50, y: 20, z: 0} - m_EntryPosition: {x: 50, y: 120, z: 0} - m_ExitPosition: {x: 800, y: 120, z: 0} - m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} - m_DefaultState: {fileID: 6016706997111698284} ---- !u!1102 &3942933370568001311 -AnimatorState: - serializedVersion: 6 - m_ObjectHideFlags: 1 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: TriggeredState - m_Speed: 1 - m_CycleOffset: 0 - m_Transitions: [] - m_StateMachineBehaviours: [] - m_Position: {x: 50, y: 50, z: 0} - m_IKOnFeet: 0 - m_WriteDefaultValues: 1 - m_Mirror: 0 - m_SpeedParameterActive: 0 - m_MirrorParameterActive: 0 - m_CycleOffsetParameterActive: 0 - m_TimeParameterActive: 0 - m_Motion: {fileID: 7400000, guid: db8faf64ca46248abb6624513ac1fb1b, type: 2} - m_Tag: - m_SpeedParameter: - m_MirrorParameter: - m_CycleOffsetParameter: - m_TimeParameter: ---- !u!1101 &5326371122012901575 -AnimatorStateTransition: - m_ObjectHideFlags: 1 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: - m_Conditions: - - m_ConditionMode: 2 - m_ConditionEvent: AlphaParameter - m_EventTreshold: 0 - m_DstStateMachine: {fileID: 0} - m_DstState: {fileID: -1198466922477486815} - m_Solo: 0 - m_Mute: 0 - m_IsExit: 0 - serializedVersion: 3 - m_TransitionDuration: 0.25 - m_TransitionOffset: 0 - m_ExitTime: 0.75 - m_HasExitTime: 1 - m_HasFixedDuration: 1 - m_InterruptionSource: 0 - m_OrderedInterruption: 1 - m_CanTransitionToSelf: 1 ---- !u!1102 &6016706997111698284 -AnimatorState: - serializedVersion: 6 - m_ObjectHideFlags: 1 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: DefaultStateLayer2 - m_Speed: 1 - m_CycleOffset: 0 - m_Transitions: - - {fileID: 6324505406226331058} - m_StateMachineBehaviours: [] - m_Position: {x: 50, y: 50, z: 0} - m_IKOnFeet: 0 - m_WriteDefaultValues: 1 - m_Mirror: 0 - m_SpeedParameterActive: 0 - m_MirrorParameterActive: 0 - m_CycleOffsetParameterActive: 0 - m_TimeParameterActive: 0 - m_Motion: {fileID: 0} - m_Tag: - m_SpeedParameter: - m_MirrorParameter: - m_CycleOffsetParameter: - m_TimeParameter: ---- !u!1101 &6324505406226331058 -AnimatorStateTransition: - m_ObjectHideFlags: 1 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: - m_Conditions: - - m_ConditionMode: 1 - m_ConditionEvent: Layer2AlphaParameter - m_EventTreshold: 0 - m_DstStateMachine: {fileID: 0} - m_DstState: {fileID: 927597079590233140} - m_Solo: 0 - m_Mute: 0 - m_IsExit: 0 - serializedVersion: 3 - m_TransitionDuration: 0.25 - m_TransitionOffset: 0 - m_ExitTime: 0.75 - m_HasExitTime: 1 - m_HasFixedDuration: 1 - m_InterruptionSource: 2 - m_OrderedInterruption: 1 - m_CanTransitionToSelf: 1 ---- !u!1101 &8340347106517238820 -AnimatorStateTransition: - m_ObjectHideFlags: 1 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: - m_Conditions: - - m_ConditionMode: 1 - m_ConditionEvent: TestTrigger - m_EventTreshold: 0 - m_DstStateMachine: {fileID: 0} - m_DstState: {fileID: 3942933370568001311} - m_Solo: 0 - m_Mute: 0 - m_IsExit: 0 - serializedVersion: 3 - m_TransitionDuration: 0.25 - m_TransitionOffset: 0 - m_ExitTime: 0.75 - m_HasExitTime: 1 - m_HasFixedDuration: 1 - m_InterruptionSource: 0 - m_OrderedInterruption: 1 - m_CanTransitionToSelf: 1 diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/TestAnimatorController.controller.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/TestAnimatorController.controller.meta deleted file mode 100644 index 9c9d7156a1..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/TestAnimatorController.controller.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: a0b8ebecb362240989d16159bdfa067c -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 9100000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/TestAnimatorOverrideController.overrideController b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/TestAnimatorOverrideController.overrideController deleted file mode 100644 index 3d7080904b..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/TestAnimatorOverrideController.overrideController +++ /dev/null @@ -1,15 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!221 &22100000 -AnimatorOverrideController: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: TestAnimatorOverrideController - m_Controller: {fileID: 9100000, guid: a0b8ebecb362240989d16159bdfa067c, type: 2} - m_Clips: - - m_OriginalClip: {fileID: 7400000, guid: 1f6191147839943ab93e2171cc15c5e9, type: 2} - m_OverrideClip: {fileID: 7400000, guid: cf503a5569d0b4df4910a26d09ce4530, type: 2} - - m_OriginalClip: {fileID: 7400000, guid: db8faf64ca46248abb6624513ac1fb1b, type: 2} - m_OverrideClip: {fileID: 7400000, guid: 05a2afc2ff8884d32afc64ed6765880a, type: 2} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/TestAnimatorOverrideController.overrideController.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/TestAnimatorOverrideController.overrideController.meta deleted file mode 100644 index 96a91f01e1..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator/Resources/TestAnimatorOverrideController.overrideController.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 16a21d7fd66d54fd8afc0e6925d10c64 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 22100000 - userData: - assetBundleName: - assetBundleVariant: From 23931244de7fa4661b5f84ba5fc9f7b2d08ee073 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 22 Jul 2022 10:12:05 -0500 Subject: [PATCH 009/138] fix: NetworkAnimator does not unsubscribe to OnClientConnectedCallback [MTT-4080] (#2074) --- com.unity.netcode.gameobjects/CHANGELOG.md | 4 +- .../Runtime/Core/NetworkManager.cs | 21 ++-- .../Runtime/Animation/AnimatorTestHelper.cs | 7 ++ .../Runtime/Animation/NetworkAnimatorTests.cs | 107 ++++++++++++++++++ 4 files changed, 129 insertions(+), 10 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index dd9b951ed3..05c29c03d7 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -5,15 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). - ## [Unreleased] ### Changed ### Fixed +- Fixed issue where NetworkAnimator was not removing its subscription from OnClientConnectedCallback when despawned during the shutdown sequence. (#2074) +- Fixed IsServer and IsClient being set to false before object despawn during the shutdown sequence. (#2074) - Fixed NetworkLists not populating on client. NetworkList now uses the most recent list as opposed to the list at the end of previous frame, when sending full updates to dynamically spawned NetworkObject. The difference in behaviour is required as scene management spawns those objects at a different time in the frame, relative to updates. (#2062) - - Fixed NetworkList Value event on the server. PreviousValue is now set correctly when a new value is set through property setter. (#2067) ## [1.0.0] - 2022-06-27 diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 21ba854eee..dcf8e0c114 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1391,6 +1391,19 @@ internal void ShutdownInternal() } IsConnectedClient = false; + + // We need to clean up NetworkObjects before we reset the IsServer + // and IsClient properties. This provides consistency of these two + // property values for NetworkObjects that are still spawned when + // the shutdown cycle begins. + if (SpawnManager != null) + { + SpawnManager.DespawnAndDestroyNetworkObjects(); + SpawnManager.ServerResetShudownStateForSceneObjects(); + + SpawnManager = null; + } + IsServer = false; IsClient = false; @@ -1413,14 +1426,6 @@ internal void ShutdownInternal() NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll; } - if (SpawnManager != null) - { - SpawnManager.DespawnAndDestroyNetworkObjects(); - SpawnManager.ServerResetShudownStateForSceneObjects(); - - SpawnManager = null; - } - if (DeferredMessageManager != null) { DeferredMessageManager.CleanupAllTriggers(); diff --git a/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs b/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs index 6a6ee212d6..48f1db583f 100644 --- a/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs +++ b/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using UnityEngine; using Unity.Netcode; @@ -49,8 +50,14 @@ public override void OnNetworkSpawn() base.OnNetworkSpawn(); } + public Action OnCheckIsServerIsClient; + public override void OnNetworkDespawn() { + // This verifies the issue where IsServer and IsClient were + // being reset prior to NetworkObjects being despawned during + // the shutdown period. + OnCheckIsServerIsClient?.Invoke(IsServer, IsClient); if (ClientSideInstances.ContainsKey(NetworkManager.LocalClientId)) { ClientSideInstances.Remove(NetworkManager.LocalClientId); diff --git a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs index c53f7f102b..c1f1cf2f39 100644 --- a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs +++ b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs @@ -1,5 +1,6 @@ using System.Collections; +using System.Linq; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; @@ -383,5 +384,111 @@ public IEnumerator LateJoinSynchronizationTest([Values] OwnerShipMode ownerShipM yield return StopOneClient(newlyJoinedClient); VerboseDebug($" ------------------ Late-Join Test [{TriggerTest.Iteration}][{ownerShipMode}] Stopping ------------------ "); } + + private bool m_ClientDisconnected; + /// + /// This validates that NetworkAnimator properly removes its subscription to the + /// OnClientConnectedCallback when it is despawned and destroyed during the + /// shutdown sequence on both the server and the client. + /// + [UnityTest] + public IEnumerator ShutdownWhileSpawnedAndStartBackUpTest() + { + VerboseDebug($" ++++++++++++++++++ Disconnect-Reconnect Server Test Starting ++++++++++++++++++ "); + // Spawn our test animator object + var objectInstance = SpawnPrefab(false, AuthoritativeMode.ServerAuth); + var networkObjectInstance = objectInstance.GetComponent(); + var serverAnimatorTestHelper = objectInstance.GetComponent(); + m_ServerNetworkManager.OnClientDisconnectCallback += ServerNetworkManager_OnClientDisconnectCallback; + // Wait for it to spawn server-side + yield return WaitForConditionOrTimeOut(() => AnimatorTestHelper.ServerSideInstance != null); + AssertOnTimeout($"Timed out waiting for the server-side instance of {GetNetworkAnimatorName(AuthoritativeMode.ServerAuth)} to be spawned!"); + + // Wait for it to spawn client-side + yield return WaitForConditionOrTimeOut(WaitForClientsToInitialize); + AssertOnTimeout($"Timed out waiting for the client-side instance of {GetNetworkAnimatorName(AuthoritativeMode.ServerAuth)} to be spawned!"); + + var clientAnimatorTestHelper = s_GlobalNetworkObjects[m_ClientNetworkManagers[0].LocalClientId].Values.Where((c) => c.GetComponent() != null).First().GetComponent(); + Assert.IsNotNull(clientAnimatorTestHelper, $"Could not find the client side {nameof(AnimatorTestHelper)}!"); + VerboseDebug($" ++++++++++++++++++ Disconnect-Reconnect Shutting Down Client and Server ++++++++++++++++++ "); + clientAnimatorTestHelper.OnCheckIsServerIsClient += Client_OnCheckIsServerIsClient; + + // Now shutdown the client-side to verify this fix. + // The client-side spawned NetworkObject should get despawned + // and invoke the Client_OnCheckIsServerIsClient action. + m_ClientNetworkManagers[0].Shutdown(true); + + // Wait for the server to receive the client disconnection notification + yield return WaitForConditionOrTimeOut(() => m_ClientDisconnected); + AssertOnTimeout($"Timed out waiting for the client to disconnect!"); + + Assert.IsTrue(m_ClientTestHelperDespawned, $"Client-Side {nameof(AnimatorTestHelper)} did not have a valid IsClient setting!"); + + serverAnimatorTestHelper.OnCheckIsServerIsClient += Server_OnCheckIsServerIsClient; + m_ServerNetworkManager.OnClientDisconnectCallback -= ServerNetworkManager_OnClientDisconnectCallback; + + // Now shutdown the server-side to verify this fix. + // The server-side spawned NetworkObject should get despawned + // and invoke the Server_OnCheckIsServerIsClient action. + m_ServerNetworkManager.Shutdown(); + + yield return s_DefaultWaitForTick; + + yield return WaitForConditionOrTimeOut(() => !m_ServerNetworkManager.ShutdownInProgress); + + Assert.IsTrue(m_ServerTestHelperDespawned, $"Server-Side {nameof(AnimatorTestHelper)} did not have a valid IsServer setting!"); + AssertOnTimeout($"Timed out waiting for the server to shutdown!"); + + VerboseDebug($" ++++++++++++++++++ Disconnect-Reconnect Restarting Server and Client ++++++++++++++++++ "); + // Since the dynamically generated PlayerPrefab is destroyed when the server shuts down, + // we need to create a new one and assign it to NetworkPrefab index 0 + m_PlayerPrefab = new GameObject("Player"); + NetworkObject networkObject = m_PlayerPrefab.AddComponent(); + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); + m_ServerNetworkManager.NetworkConfig.NetworkPrefabs[0].Prefab = m_PlayerPrefab; + m_ClientNetworkManagers[0].NetworkConfig.NetworkPrefabs[0].Prefab = m_PlayerPrefab; + OnCreatePlayerPrefab(); + + // Now, restart the server and the client + m_ServerNetworkManager.StartHost(); + m_ClientNetworkManagers[0].StartClient(); + + // Wait for the server and client to start and connect + yield return WaitForClientsConnectedOrTimeOut(); + + VerboseDebug($" ++++++++++++++++++ Disconnect-Reconnect Server Test Stopping ++++++++++++++++++ "); + } + + private bool m_ServerTestHelperDespawned; + /// + /// Server-Side + /// This callback will be invoked as the spawned prefab is destroyed during shutdown + /// + private void Server_OnCheckIsServerIsClient(bool isServer, bool isClient) + { + // Validates this is still set when the NetworkObject is despawned during shutdown + Assert.IsTrue(isServer); + m_ServerTestHelperDespawned = true; + } + + private bool m_ClientTestHelperDespawned; + /// + /// Client-Side + /// This callback will be invoked as the spawned prefab is destroyed during shutdown + /// + private void Client_OnCheckIsServerIsClient(bool isServer, bool isClient) + { + // Validates this is still set when the NetworkObject is despawned during shutdown + Assert.IsTrue(isClient); + m_ClientTestHelperDespawned = true; + } + + /// + /// Verifies the client has disconnected + /// + private void ServerNetworkManager_OnClientDisconnectCallback(ulong obj) + { + m_ClientDisconnected = true; + } } } From 8771f3d4a48eea1935822bb019e8e9acab7e29e1 Mon Sep 17 00:00:00 2001 From: Sam Bellomo <71790295+SamuelBellomo@users.noreply.github.com> Date: Mon, 25 Jul 2022 21:41:52 -0400 Subject: [PATCH 010/138] docs: Quick fix for clearer instructions in integration tests [skip ci] (#2077) --- .../TestHelpers/Runtime/NetcodeIntegrationTest.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index 4dbcf1a15d..8ab56179ee 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -869,12 +869,14 @@ public NetcodeIntegrationTest() /// Constructor that allows you To break tests up as a host /// and a server. /// Example: Decorate your child derived class with TestFixture - /// and then create a constructor at the child level + /// and then create a constructor at the child level. + /// Don't forget to set your constructor public, else Unity will + /// give you a hard to decipher error /// [TestFixture(HostOrServer.Host)] /// [TestFixture(HostOrServer.Server)] /// public class MyChildClass : NetcodeIntegrationTest /// { - /// MyChildClass(HostOrServer hostOrServer) : base(hostOrServer) { } + /// public MyChildClass(HostOrServer hostOrServer) : base(hostOrServer) { } /// } /// /// From 464c675c420b7876169b844ba0b14fab63d70f59 Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Tue, 26 Jul 2022 13:22:06 -0400 Subject: [PATCH 011/138] feat: Allow reliable sends to go over maximum payload size (#2081) * feat: Allow reliable sends to go over maximum payload size --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 ++ .../Runtime/Transports/UTP/UnityTransport.cs | 12 +++++------ .../Runtime/Transports/UnityTransportTests.cs | 21 +++++++++++++++++++ 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 05c29c03d7..48c00b5624 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -9,6 +9,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- When using `UnityTransport`, _reliable_ payloads are now allowed to exceed the configured 'Max Payload Size'. Unreliable payloads remain bounded by this setting. (#2081) + ### Fixed - Fixed issue where NetworkAnimator was not removing its subscription from OnClientConnectedCallback when despawned during the shutdown sequence. (#2074) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index ad2787fee5..3728280066 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -158,11 +158,11 @@ public int MaxPacketQueueSize set => m_MaxPacketQueueSize = value; } - [Tooltip("The maximum size of a payload that can be handled by the transport.")] + [Tooltip("The maximum size of an unreliable payload that can be handled by the transport.")] [SerializeField] private int m_MaxPayloadSize = InitialMaxPayloadSize; - /// The maximum size of a payload that can be handled by the transport. + /// The maximum size of an unreliable payload that can be handled by the transport. public int MaxPayloadSize { get => m_MaxPayloadSize; @@ -1148,14 +1148,14 @@ public override NetcodeNetworkEvent PollEvent(out ulong clientId, out ArraySegme /// The delivery type (QoS) to send data with public override void Send(ulong clientId, ArraySegment payload, NetworkDelivery networkDelivery) { - if (payload.Count > m_MaxPayloadSize) + var pipeline = SelectSendPipeline(networkDelivery); + + if (pipeline != m_ReliableSequencedPipeline && payload.Count > m_MaxPayloadSize) { - Debug.LogError($"Payload of size {payload.Count} larger than configured 'Max Payload Size' ({m_MaxPayloadSize})."); + Debug.LogError($"Unreliable payload of size {payload.Count} larger than configured 'Max Payload Size' ({m_MaxPayloadSize})."); return; } - var pipeline = SelectSendPipeline(networkDelivery); - var sendTarget = new SendTarget(clientId, pipeline); if (!m_SendQueue.TryGetValue(sendTarget, out var queue)) { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs index 5dec849759..481366e216 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs @@ -457,5 +457,26 @@ public IEnumerator SendQueuesFlushedOnRemoteClientDisconnect([ValueSource("k_Del yield return null; } + + [UnityTest] + public IEnumerator ReliablePayloadsCanBeLargerThanMaximum() + { + InitializeTransport(out m_Server, out m_ServerEvents); + InitializeTransport(out m_Client1, out m_Client1Events); + + m_Server.StartServer(); + m_Client1.StartClient(); + + yield return WaitForNetworkEvent(NetworkEvent.Connect, m_Client1Events); + + var payloadSize = UnityTransport.InitialMaxPayloadSize + 1; + var data = new ArraySegment(new byte[payloadSize]); + + m_Server.Send(m_Client1.ServerClientId, data, NetworkDelivery.Reliable); + + yield return WaitForNetworkEvent(NetworkEvent.Data, m_Client1Events); + + yield return null; + } } } From 32976587c9db0f9d681066d6111751856212d164 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 27 Jul 2022 03:01:13 -0500 Subject: [PATCH 012/138] fix: NetworkAnimator fails to synchronize end of animation loop for late joining clients [MTT-4060] (#2076) This resolves the issue where animations at the end of their loop would not be synchronized with late joining clients. --- com.unity.netcode.gameobjects/CHANGELOG.md | 4 +- .../Components/NetworkAnimator.cs | 5 - .../CubeAnimatorController.controller | 255 +++++++++++++++++- .../Cube_Contolled_Rotation.anim | 199 ++++++++++++++ .../Cube_Contolled_Rotation.anim.meta | 8 + .../Runtime/Animation/AnimatorTestHelper.cs | 13 + .../Runtime/Animation/NetworkAnimatorTests.cs | 143 +++++++++- .../Tests/Runtime/Animation/StateSyncTest.cs | 46 ++++ .../Runtime/Animation/StateSyncTest.cs.meta | 11 + .../Tests/Runtime/Animation/TriggerTest.cs | 2 +- 10 files changed, 661 insertions(+), 25 deletions(-) create mode 100644 testproject/Assets/Tests/Manual/NetworkAnimatorTests/Cube_Contolled_Rotation.anim create mode 100644 testproject/Assets/Tests/Manual/NetworkAnimatorTests/Cube_Contolled_Rotation.anim.meta create mode 100644 testproject/Assets/Tests/Runtime/Animation/StateSyncTest.cs create mode 100644 testproject/Assets/Tests/Runtime/Animation/StateSyncTest.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 48c00b5624..08dd5cb3f0 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -12,8 +12,8 @@ Additional documentation and release notes are available at [Multiplayer Documen - When using `UnityTransport`, _reliable_ payloads are now allowed to exceed the configured 'Max Payload Size'. Unreliable payloads remain bounded by this setting. (#2081) ### Fixed - -- Fixed issue where NetworkAnimator was not removing its subscription from OnClientConnectedCallback when despawned during the shutdown sequence. (#2074) +- Fixed issue where `NetworkAnimator` would not synchronize a looping animation for late joining clients if it was at the very end of its loop. (#2076) +- Fixed issue where `NetworkAnimator` was not removing its subscription from `OnClientConnectedCallback` when despawned during the shutdown sequence. (#2074) - Fixed IsServer and IsClient being set to false before object despawn during the shutdown sequence. (#2074) - Fixed NetworkLists not populating on client. NetworkList now uses the most recent list as opposed to the list at the end of previous frame, when sending full updates to dynamically spawned NetworkObject. The difference in behaviour is required as scene management spawns those objects at a different time in the frame, relative to updates. (#2062) - Fixed NetworkList Value event on the server. PreviousValue is now set correctly when a new value is set through property setter. (#2067) diff --git a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs index 0b138ede8a..5e8179bf70 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs @@ -424,11 +424,6 @@ internal void ServerSynchronizeNewPlayer(ulong playerId) stateHash = nextState.fullPathHash; } - else - if (st.normalizedTime >= adjustedNormalizedMaxTime) - { - continue; - } var animMsg = new AnimationMessage { diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CubeAnimatorController.controller b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CubeAnimatorController.controller index a873aee24d..d01348ed86 100644 --- a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CubeAnimatorController.controller +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CubeAnimatorController.controller @@ -1,5 +1,83 @@ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: +--- !u!1101 &-9076771464115044414 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 1 + m_ConditionEvent: LateJoinTest + m_EventTreshold: 0 + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: -8870587575130706737} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.25 + m_TransitionOffset: 0 + m_ExitTime: 0.75 + m_HasExitTime: 1 + m_HasFixedDuration: 1 + m_InterruptionSource: 0 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 +--- !u!1102 &-8870587575130706737 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: LateJoinSync + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: + - {fileID: 2097057606729455931} + m_StateMachineBehaviours: + - {fileID: 4108346256824837128} + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 7400000, guid: 0dbf24ad942a3ef4e8045f46b378d431, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!1101 &-7881886588344925529 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 2 + m_ConditionEvent: LateJoinTest + m_EventTreshold: 0 + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: 0} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.25 + m_TransitionOffset: 0 + m_ExitTime: 0.75 + m_HasExitTime: 1 + m_HasFixedDuration: 1 + m_InterruptionSource: 0 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 --- !u!1101 &-6396453490711135124 AnimatorStateTransition: m_ObjectHideFlags: 1 @@ -150,6 +228,18 @@ AnimatorStateTransition: m_InterruptionSource: 0 m_OrderedInterruption: 1 m_CanTransitionToSelf: 1 +--- !u!114 &-1766406323300068053 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 768f2c827b02cc240a588a902e6bbdba, type: 3} + m_Name: + m_EditorClassIdentifier: --- !u!1102 &-1676030328622575462 AnimatorState: serializedVersion: 6 @@ -163,7 +253,8 @@ AnimatorState: m_Transitions: - {fileID: -4282378417640754704} - {fileID: 1678733063235620591} - m_StateMachineBehaviours: [] + m_StateMachineBehaviours: + - {fileID: 6706570197837314945} m_Position: {x: 50, y: 50, z: 0} m_IKOnFeet: 0 m_WriteDefaultValues: 1 @@ -190,7 +281,8 @@ AnimatorState: m_CycleOffset: 0 m_Transitions: - {fileID: -6396453490711135124} - m_StateMachineBehaviours: [] + m_StateMachineBehaviours: + - {fileID: -1261272140589342921} m_Position: {x: 50, y: 50, z: 0} m_IKOnFeet: 0 m_WriteDefaultValues: 1 @@ -205,6 +297,18 @@ AnimatorState: m_MirrorParameter: m_CycleOffsetParameter: m_TimeParameter: +--- !u!114 &-1261272140589342921 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 768f2c827b02cc240a588a902e6bbdba, type: 3} + m_Name: + m_EditorClassIdentifier: --- !u!1102 &-338501773749300249 AnimatorState: serializedVersion: 6 @@ -244,21 +348,21 @@ AnimatorStateMachine: m_ChildStates: - serializedVersion: 1 m_State: {fileID: -1676030328622575462} - m_Position: {x: 290, y: 10, z: 0} + m_Position: {x: 290, y: 0, z: 0} - serializedVersion: 1 m_State: {fileID: 6353332194479835970} - m_Position: {x: 350, y: -150, z: 0} + m_Position: {x: 290, y: -150, z: 0} - serializedVersion: 1 m_State: {fileID: 2733578584814225138} - m_Position: {x: 180, y: 190, z: 0} + m_Position: {x: 290, y: 180, z: 0} m_ChildStateMachines: [] m_AnyStateTransitions: [] m_EntryTransitions: [] m_StateMachineTransitions: {} m_StateMachineBehaviours: [] - m_AnyStatePosition: {x: 50, y: 20, z: 0} - m_EntryPosition: {x: 420, y: 160, z: 0} - m_ExitPosition: {x: 800, y: 120, z: 0} + m_AnyStatePosition: {x: 540, y: 0, z: 0} + m_EntryPosition: {x: 20, y: 10, z: 0} + m_ExitPosition: {x: 540, y: 70, z: 0} m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} m_DefaultState: {fileID: -1676030328622575462} --- !u!91 &9100000 @@ -306,6 +410,12 @@ AnimatorController: m_DefaultInt: 0 m_DefaultBool: 0 m_Controller: {fileID: 9100000} + - m_Name: LateJoinTest + m_Type: 4 + m_DefaultFloat: 0 + m_DefaultInt: 0 + m_DefaultBool: 0 + m_Controller: {fileID: 9100000} m_AnimatorLayers: - serializedVersion: 5 m_Name: Base Layer @@ -331,6 +441,18 @@ AnimatorController: m_IKPass: 0 m_SyncedLayerAffectsTiming: 0 m_Controller: {fileID: 9100000} + - serializedVersion: 5 + m_Name: LateJoingSyncLayer + m_StateMachine: {fileID: 7987549579817906310} + m_Mask: {fileID: 0} + m_Motions: [] + m_Behaviours: [] + m_BlendingMode: 0 + m_SyncedLayerIndex: -1 + m_DefaultWeight: 1 + m_IKPass: 0 + m_SyncedLayerAffectsTiming: 0 + m_Controller: {fileID: 9100000} --- !u!1101 &1138737138882309440 AnimatorStateTransition: m_ObjectHideFlags: 1 @@ -400,6 +522,31 @@ AnimatorStateTransition: m_InterruptionSource: 0 m_OrderedInterruption: 1 m_CanTransitionToSelf: 1 +--- !u!1101 &2097057606729455931 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 2 + m_ConditionEvent: LateJoinTest + m_EventTreshold: 0 + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: 5205197960406981613} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.25 + m_TransitionOffset: 0 + m_ExitTime: 0.5 + m_HasExitTime: 1 + m_HasFixedDuration: 1 + m_InterruptionSource: 0 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 --- !u!1102 &2733578584814225138 AnimatorState: serializedVersion: 6 @@ -440,6 +587,30 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: dec8cf4a19a8ef745ae0440ec04911ae, type: 3} m_Name: m_EditorClassIdentifier: +--- !u!114 &3735381422868909868 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 768f2c827b02cc240a588a902e6bbdba, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &4108346256824837128 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 768f2c827b02cc240a588a902e6bbdba, type: 3} + m_Name: + m_EditorClassIdentifier: --- !u!1102 &4311160535506449488 AnimatorState: serializedVersion: 6 @@ -452,7 +623,8 @@ AnimatorState: m_CycleOffset: 0 m_Transitions: - {fileID: 1830534497079063084} - m_StateMachineBehaviours: [] + m_StateMachineBehaviours: + - {fileID: 3735381422868909868} m_Position: {x: 50, y: 50, z: 0} m_IKOnFeet: 0 m_WriteDefaultValues: 0 @@ -467,6 +639,34 @@ AnimatorState: m_MirrorParameter: m_CycleOffsetParameter: m_TimeParameter: +--- !u!1102 &5205197960406981613 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: New State + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: + - {fileID: -9076771464115044414} + m_StateMachineBehaviours: + - {fileID: -1766406323300068053} + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 0} + m_Tag: + m_SpeedParameter: TestFloat + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: --- !u!114 &5559889042091692034 MonoBehaviour: m_ObjectHideFlags: 1 @@ -506,6 +706,18 @@ AnimatorState: m_MirrorParameter: m_CycleOffsetParameter: m_TimeParameter: +--- !u!114 &6706570197837314945 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 768f2c827b02cc240a588a902e6bbdba, type: 3} + m_Name: + m_EditorClassIdentifier: --- !u!1107 &7599406000257451018 AnimatorStateMachine: serializedVersion: 6 @@ -531,3 +743,28 @@ AnimatorStateMachine: m_ExitPosition: {x: 800, y: 120, z: 0} m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} m_DefaultState: {fileID: -1607953088454488810} +--- !u!1107 &7987549579817906310 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: LateJoingSyncLayer + m_ChildStates: + - serializedVersion: 1 + m_State: {fileID: 5205197960406981613} + m_Position: {x: 450, y: 110, z: 0} + - serializedVersion: 1 + m_State: {fileID: -8870587575130706737} + m_Position: {x: 450, y: -20, z: 0} + m_ChildStateMachines: [] + m_AnyStateTransitions: [] + m_EntryTransitions: [] + m_StateMachineTransitions: {} + m_StateMachineBehaviours: [] + m_AnyStatePosition: {x: 50, y: 20, z: 0} + m_EntryPosition: {x: 50, y: 120, z: 0} + m_ExitPosition: {x: 800, y: 120, z: 0} + m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} + m_DefaultState: {fileID: 5205197960406981613} diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/Cube_Contolled_Rotation.anim b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/Cube_Contolled_Rotation.anim new file mode 100644 index 0000000000..9114297d33 --- /dev/null +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/Cube_Contolled_Rotation.anim @@ -0,0 +1,199 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!74 &7400000 +AnimationClip: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Cube_Contolled_Rotation + serializedVersion: 6 + m_Legacy: 0 + m_Compressed: 0 + m_UseHighQualityCurve: 1 + m_RotationCurves: [] + m_CompressedRotationCurves: [] + m_EulerCurves: + - curve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: {x: 0, y: 0, z: 0} + inSlope: {x: 0, y: 0, z: 0} + outSlope: {x: 0, y: 0, z: 0} + tangentMode: 0 + weightedMode: 0 + inWeight: {x: 0.33333334, y: 0.33333334, z: 0.33333334} + outWeight: {x: 0.33333334, y: 0.33333334, z: 0.33333334} + - serializedVersion: 3 + time: 0.5 + value: {x: 0, y: 180, z: 0} + inSlope: {x: 0, y: 0, z: 0} + outSlope: {x: 0, y: 0, z: 0} + tangentMode: 0 + weightedMode: 0 + inWeight: {x: 0.33333334, y: 0.33333334, z: 0.33333334} + outWeight: {x: 0.33333334, y: 0.33333334, z: 0.33333334} + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + path: + m_PositionCurves: [] + m_ScaleCurves: [] + m_FloatCurves: [] + m_PPtrCurves: [] + m_SampleRate: 60 + m_WrapMode: 0 + m_Bounds: + m_Center: {x: 0, y: 0, z: 0} + m_Extent: {x: 0, y: 0, z: 0} + m_ClipBindingConstant: + genericBindings: + - serializedVersion: 2 + path: 0 + attribute: 4 + script: {fileID: 0} + typeID: 4 + customType: 4 + isPPtrCurve: 0 + pptrCurveMapping: [] + m_AnimationClipSettings: + serializedVersion: 2 + m_AdditiveReferencePoseClip: {fileID: 0} + m_AdditiveReferencePoseTime: 0 + m_StartTime: 0 + m_StopTime: 0.5 + m_OrientationOffsetY: 0 + m_Level: 0 + m_CycleOffset: 0 + m_HasAdditiveReferencePose: 0 + m_LoopTime: 0 + m_LoopBlend: 0 + m_LoopBlendOrientation: 0 + m_LoopBlendPositionY: 0 + m_LoopBlendPositionXZ: 0 + m_KeepOriginalOrientation: 0 + m_KeepOriginalPositionY: 1 + m_KeepOriginalPositionXZ: 0 + m_HeightFromFeet: 0 + m_Mirror: 0 + m_EditorCurves: + - curve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 136 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + - serializedVersion: 3 + time: 0.5 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 136 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + attribute: localEulerAnglesRaw.x + path: + classID: 4 + script: {fileID: 0} + - curve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 136 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + - serializedVersion: 3 + time: 0.5 + value: 180 + inSlope: 0 + outSlope: 0 + tangentMode: 136 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + attribute: localEulerAnglesRaw.y + path: + classID: 4 + script: {fileID: 0} + - curve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 136 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + - serializedVersion: 3 + time: 0.5 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 136 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + attribute: localEulerAnglesRaw.z + path: + classID: 4 + script: {fileID: 0} + m_EulerEditorCurves: + - curve: + serializedVersion: 2 + m_Curve: [] + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + attribute: m_LocalEulerAngles.x + path: + classID: 4 + script: {fileID: 0} + - curve: + serializedVersion: 2 + m_Curve: [] + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + attribute: m_LocalEulerAngles.y + path: + classID: 4 + script: {fileID: 0} + - curve: + serializedVersion: 2 + m_Curve: [] + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + attribute: m_LocalEulerAngles.z + path: + classID: 4 + script: {fileID: 0} + m_HasGenericRootTransform: 1 + m_HasMotionFloatCurves: 0 + m_Events: [] diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/Cube_Contolled_Rotation.anim.meta b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/Cube_Contolled_Rotation.anim.meta new file mode 100644 index 0000000000..7f6803b9cc --- /dev/null +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/Cube_Contolled_Rotation.anim.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0dbf24ad942a3ef4e8045f46b378d431 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 7400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs b/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs index 48f1db583f..4d09240e9b 100644 --- a/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs +++ b/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs @@ -35,6 +35,9 @@ public override void OnNetworkSpawn() { Debug.Log($"[AnimatorTestHelper][{IsServer}] {NetworkManager.name}"); } + + m_Animator = GetComponent(); + m_NetworkAnimator = GetComponent(); if (IsServer) { ServerSideInstance = this; @@ -109,5 +112,15 @@ public void SetTrigger(string name = "TestTrigger") { m_NetworkAnimator.SetTrigger(name); } + + public void SetLateJoinParam(bool isEnabled) + { + m_Animator.SetBool("LateJoinTest", isEnabled); + } + + public Animator GetAnimator() + { + return m_Animator; + } } } diff --git a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs index c1f1cf2f39..878f7d78f1 100644 --- a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs +++ b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs @@ -1,4 +1,3 @@ - using System.Collections; using System.Linq; using NUnit.Framework; @@ -38,14 +37,14 @@ protected override void OnOneTimeSetup() Assert.NotNull(m_AnimatorObjectPrefab, $"Failed to load resource {k_AnimatorObjectName}"); m_OwnerAnimatorObjectPrefab = Resources.Load(k_OwnerAnimatorObjectName); Assert.NotNull(m_OwnerAnimatorObjectPrefab, $"Failed to load resource {k_OwnerAnimatorObjectName}"); - base.OnOneTimeSetup(); } protected override IEnumerator OnSetup() { AnimatorTestHelper.Initialize(); - TriggerTest.Reset(); + TriggerTest.ResetTest(); + StateSyncTest.ResetTest(); yield return base.OnSetup(); } @@ -293,13 +292,13 @@ protected override void OnNewClientCreated(NetworkManager networkManager) /// /// Verifies that late joining clients are synchronized to an - /// animator's state. + /// animator's trigger state. /// /// Server or Owner authoritative [UnityTest] - public IEnumerator LateJoinSynchronizationTest([Values] OwnerShipMode ownerShipMode, [Values] AuthoritativeMode authoritativeMode) + public IEnumerator LateJoinTriggerSynchronizationTest([Values] OwnerShipMode ownerShipMode, [Values] AuthoritativeMode authoritativeMode) { - VerboseDebug($" ++++++++++++++++++ Late-Join Test [{TriggerTest.Iteration}][{ownerShipMode}] Starting ++++++++++++++++++ "); + VerboseDebug($" ++++++++++++++++++ Late Join Trigger Test [{TriggerTest.Iteration}][{ownerShipMode}] Starting ++++++++++++++++++ "); TriggerTest.IsVerboseDebug = m_EnableVerboseDebug; AnimatorTestHelper.IsTriggerTest = m_EnableVerboseDebug; bool isClientOwner = ownerShipMode == OwnerShipMode.ClientOwner; @@ -307,7 +306,6 @@ public IEnumerator LateJoinSynchronizationTest([Values] OwnerShipMode ownerShipM // Spawn our test animator object var objectInstance = SpawnPrefab(ownerShipMode == OwnerShipMode.ClientOwner, authoritativeMode); - // Wait for it to spawn server-side yield return WaitForConditionOrTimeOut(() => AnimatorTestHelper.ServerSideInstance != null); AssertOnTimeout($"Timed out waiting for the server-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!"); @@ -382,7 +380,136 @@ public IEnumerator LateJoinSynchronizationTest([Values] OwnerShipMode ownerShipM var newlyJoinedClient = m_ClientNetworkManagers[1]; yield return StopOneClient(newlyJoinedClient); - VerboseDebug($" ------------------ Late-Join Test [{TriggerTest.Iteration}][{ownerShipMode}] Stopping ------------------ "); + VerboseDebug($" ------------------ Late Join Trigger Test [{TriggerTest.Iteration}][{ownerShipMode}] Stopping ------------------ "); + } + + /// + /// Verifies that late joining clients are synchronized to all of the + /// states of an animator. + /// + /// Server or Owner authoritative + [UnityTest] + public IEnumerator LateJoinSynchronizationTest([Values] OwnerShipMode ownerShipMode, [Values] AuthoritativeMode authoritativeMode) + { + VerboseDebug($" ++++++++++++++++++ Late Join Synchronization Test [{TriggerTest.Iteration}][{ownerShipMode}] Starting ++++++++++++++++++ "); + + StateSyncTest.IsVerboseDebug = m_EnableVerboseDebug; + TriggerTest.IsVerboseDebug = m_EnableVerboseDebug; + AnimatorTestHelper.IsTriggerTest = m_EnableVerboseDebug; + bool isClientOwner = ownerShipMode == OwnerShipMode.ClientOwner; + + // Spawn our test animator object + var objectInstance = SpawnPrefab(ownerShipMode == OwnerShipMode.ClientOwner, authoritativeMode); + + // Wait for it to spawn server-side + yield return WaitForConditionOrTimeOut(() => AnimatorTestHelper.ServerSideInstance != null); + AssertOnTimeout($"Timed out waiting for the server-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!"); + + // Wait for it to spawn client-side + yield return WaitForConditionOrTimeOut(WaitForClientsToInitialize); + AssertOnTimeout($"Timed out waiting for the client-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!"); + + // Set the late join parameter based on the type of test + if (authoritativeMode == AuthoritativeMode.OwnerAuth) + { + var objectToUpdate = ownerShipMode == OwnerShipMode.ClientOwner ? AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[0].LocalClientId] : AnimatorTestHelper.ServerSideInstance; + // Set the late join parameter via the owner + objectToUpdate.SetLateJoinParam(true); + } + else + { + // Set the late join parameter to kick off the late join synchronization state + // (it rotates to 180 degrees and then stops animating until the value is reset) + AnimatorTestHelper.ServerSideInstance.SetLateJoinParam(true); + } + + var firstClientAnimatorTestHelper = AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[0].LocalClientId]; + + // Wait for the 1st client to rotate to the 180.0f degree point + yield return WaitForConditionOrTimeOut(() => Mathf.Approximately(firstClientAnimatorTestHelper.transform.rotation.eulerAngles.y, 180.0f)); + AssertOnTimeout($"Timed out waiting for client-side cube to reach 180.0f!"); + + m_ServerNetworkManager.OnClientConnectedCallback += Server_OnClientConnectedCallback; + // Create and join a new client (late joining client) + yield return CreateAndStartNewClient(); + + Assert.IsTrue(m_ClientNetworkManagers.Length == 2, $"Newly created and connected client was not added to {nameof(m_ClientNetworkManagers)}!"); + + // Wait for the client to have spawned and the spawned prefab to be instantiated + yield return WaitForConditionOrTimeOut(WaitForClientsToInitialize); + AssertOnTimeout($"Timed out waiting for the late joining client-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!"); + + // Make sure the AnimatorTestHelper client side instances (plus host) is the same as the TotalClients + Assert.True((AnimatorTestHelper.ClientSideInstances.Count + 1) == TotalClients); + + var lateJoinObjectInstance = AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[1].LocalClientId]; + yield return WaitForConditionOrTimeOut(() => Mathf.Approximately(lateJoinObjectInstance.transform.rotation.eulerAngles.y, 180.0f)); + AssertOnTimeout($"[Late Join] Timed out waiting for cube to reach 180.0f!"); + + // Validate the fix by making sure the late joining client was synchronized to the server's Animator's states + yield return WaitForConditionOrTimeOut(LateJoinClientSynchronized); + AssertOnTimeout("[Late Join] Timed out waiting for newly joined client to have expected state synchronized!"); + + var newlyJoinedClient = m_ClientNetworkManagers[1]; + yield return StopOneClient(newlyJoinedClient); + VerboseDebug($" ------------------ Late Join Synchronization Test [{TriggerTest.Iteration}][{ownerShipMode}] Stopping ------------------ "); + } + + /// + /// Update Server Side Animator Layer's AnimationStateInfo when late joining + /// client connects to get the values being sent to the late joining client + /// during NetworkAnimator synchronization. + /// + private void Server_OnClientConnectedCallback(ulong obj) + { + m_ServerNetworkManager.OnClientConnectedCallback -= Server_OnClientConnectedCallback; + var serverAnimator = AnimatorTestHelper.ServerSideInstance.GetAnimator(); + for (int i = 0; i < serverAnimator.layerCount; i++) + { + Assert.True(StateSyncTest.StatesEntered.ContainsKey(m_ServerNetworkManager.LocalClientId), $"Server does not have an entry for layer {i}!"); + var animationStateInfo = serverAnimator.GetCurrentAnimatorStateInfo(i); + StateSyncTest.StatesEntered[m_ServerNetworkManager.LocalClientId][i] = animationStateInfo; + VerboseDebug($"[{i}][STATE-REFRESH][{m_ServerNetworkManager.name}] updated state normalized time ({animationStateInfo.normalizedTime}) to compare with late joined client."); + } + } + + /// + /// Used by: LateJoinSynchronizationTest + /// Wait condition method that compares the states of the late joined client + /// and the server. + /// + private bool LateJoinClientSynchronized() + { + if (!StateSyncTest.StatesEntered.ContainsKey(m_ClientNetworkManagers[1].LocalClientId)) + { + return false; + } + + var serverStates = StateSyncTest.StatesEntered[m_ServerNetworkManager.LocalClientId]; + var clientStates = StateSyncTest.StatesEntered[m_ClientNetworkManagers[1].LocalClientId]; + + if (serverStates.Count() != clientStates.Count()) + { + VerboseDebug($"[Count][Server] {serverStates.Count} | [Client-{m_ClientNetworkManagers[1].LocalClientId}]{clientStates.Count}"); + return false; + } + + // Basically a sanity check to make sure they all match + for (int i = 0; i < serverStates.Count; i++) + { + var serverAnimState = serverStates[i]; + if (clientStates[i].shortNameHash != serverAnimState.shortNameHash) + { + VerboseDebug($"[Hash Fail] Server({serverAnimState.shortNameHash}) | Client({clientStates[i].shortNameHash}) "); + return false; + } + if (clientStates[i].normalizedTime != serverAnimState.normalizedTime) + { + VerboseDebug($"[NormalizedTime Fail] Server({serverAnimState.normalizedTime}) | Client({clientStates[i].normalizedTime})"); + return false; + } + } + return true; } private bool m_ClientDisconnected; diff --git a/testproject/Assets/Tests/Runtime/Animation/StateSyncTest.cs b/testproject/Assets/Tests/Runtime/Animation/StateSyncTest.cs new file mode 100644 index 0000000000..777f5d0342 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/Animation/StateSyncTest.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using UnityEngine; +using Unity.Netcode; + +public class StateSyncTest : StateMachineBehaviour +{ + public static Dictionary> StatesEntered = new Dictionary>(); + public static bool IsVerboseDebug; + public static void ResetTest() + { + StatesEntered.Clear(); + IsVerboseDebug = false; + } + + // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state + // This provides us with the AnimatorStateInfo when a SendAnimStateClientRpc is received by the client + // (the server and the connected client are updated elsewhere) + override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) + { + var networkObject = animator.GetComponent(); + if (networkObject == null || networkObject.NetworkManager == null || !networkObject.IsSpawned) + { + return; + } + + var clientId = networkObject.NetworkManager.LocalClientId; + if (!StatesEntered.ContainsKey(clientId)) + { + StatesEntered.Add(clientId, new Dictionary()); + } + + if (!StatesEntered[clientId].ContainsKey(layerIndex)) + { + StatesEntered[clientId].Add(layerIndex, stateInfo); + } + else + { + StatesEntered[clientId][layerIndex] = stateInfo; + } + + if (IsVerboseDebug) + { + Debug.Log($"[{layerIndex}][STATE-ENTER][{clientId}] {networkObject.NetworkManager.name} entered state {stateInfo.normalizedTime}!"); + } + } +} diff --git a/testproject/Assets/Tests/Runtime/Animation/StateSyncTest.cs.meta b/testproject/Assets/Tests/Runtime/Animation/StateSyncTest.cs.meta new file mode 100644 index 0000000000..c7eac9daaf --- /dev/null +++ b/testproject/Assets/Tests/Runtime/Animation/StateSyncTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 768f2c827b02cc240a588a902e6bbdba +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/Animation/TriggerTest.cs b/testproject/Assets/Tests/Runtime/Animation/TriggerTest.cs index 1ef82510ea..69e7c94bdd 100644 --- a/testproject/Assets/Tests/Runtime/Animation/TriggerTest.cs +++ b/testproject/Assets/Tests/Runtime/Animation/TriggerTest.cs @@ -6,7 +6,7 @@ public class TriggerTest : StateMachineBehaviour { public static List ClientsThatTriggered = new List(); public static bool IsVerboseDebug; - public static void Reset() + public static void ResetTest() { ClientsThatTriggered.Clear(); ClientsThatResetTrigger.Clear(); From 4ef8b3f15eaf07081fe2c42b5d475124c79ccb43 Mon Sep 17 00:00:00 2001 From: zain-mecklai <64809707+zain-mecklai@users.noreply.github.com> Date: Wed, 27 Jul 2022 12:22:11 -0400 Subject: [PATCH 013/138] fix: ILPP filter out tests (#2024) * update timeout * remote UNET * fix: Stop populating ILPPMessageProvider with messages from outside the main runtime assembly * reset the changes to the scene * Initialiize the Rawgithash variable for the local execution case * Add a little more logging and see if we can find why allactons hasn't been populated at the time the test runs * Update the test to reduce the number of log messages as well as make the messages more informative * Lot when initlializatioon is complete * More logging clean up and adding information about registered test methods * Move init all steps to Awake instead of Start, eventually need to figure out how to block initial connection until init all steps is done * Move init to Awake * Fix missing log message in TestCoordinator Start method * Use GPU for Ubuntu image * Remove the nographics requirement and see if we can stabilize 2022.2 * Use -nographics and don't request a GPU for Linux * try to use ubuntu-20 and see if this is more stable * Use GPU for Linux and use Ubuntu 18 * Remove nographics again as it clearly makes an improvement * Adding a GPU and removing nographics just made things slower but still failing so no point making this change, reverse it Co-authored-by: Kitty Draper Co-authored-by: ashwini <36935028+ashwinimurt@users.noreply.github.com> --- .yamato/multiprocess-project-tests.yml | 2 +- .yamato/project.metafile | 2 +- .../Editor/CodeGen/INetworkMessageILPP.cs | 3 +- .../BaseMultiprocessTests.cs | 2 -- .../ExecuteStepInContext.cs | 5 ++++ .../Helpers/MultiprocessOrchestration.cs | 1 - .../MultiprocessRuntime/TestCoordinator.cs | 28 ++++++++++++------- 7 files changed, 26 insertions(+), 17 deletions(-) diff --git a/.yamato/multiprocess-project-tests.yml b/.yamato/multiprocess-project-tests.yml index 04d365176c..3505d72c52 100644 --- a/.yamato/multiprocess-project-tests.yml +++ b/.yamato/multiprocess-project-tests.yml @@ -16,7 +16,7 @@ singlenode_multiprocess_test_testproject_{{ editor }}_{{ platform.name }}: {% if editor != "trunk" %} - unity-downloader-cli -u {{ editor }} -c editor -w --fast - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr{% if platform.name == "win" %}.bat --output utr.bat{% endif %}{% if platform.name != "win" %} --output utr && chmod +x ./utr{% endif %} - - {{ platform.editorpath }} -projectpath testproject -batchmode -nographics -quit -logfile BuildMultiprocessTestPlayer.log -executeMethod Unity.Netcode.MultiprocessRuntimeTests.BuildMultiprocessTestPlayer.BuildRelease + - {{ platform.editorpath }} -projectpath testproject -batchmode -quit -nographics -logfile BuildMultiprocessTestPlayer.log -executeMethod Unity.Netcode.MultiprocessRuntimeTests.BuildMultiprocessTestPlayer.BuildRelease {% if platform.name == "mac" %} - sudo codesign --force --deep --sign - ./testproject/Builds/MultiprocessTests/MultiprocessTestPlayer.app{% endif %} - {{ platform.utr }} --suite=playmode --testproject=testproject --editor-location=.Editor --testfilter=Unity.Netcode.MultiprocessRuntimeTests --extra-editor-arg=-bypassIgnoreUTR {% endif %} diff --git a/.yamato/project.metafile b/.yamato/project.metafile index 4cc5a0efe2..dd8704b1f1 100644 --- a/.yamato/project.metafile +++ b/.yamato/project.metafile @@ -72,4 +72,4 @@ scripting_backends: - il2cpp # Images with build-tools installed required for Standalone Tests - IL2CPP -win_il2cpp_test_image: dots-player/windows10:latest \ No newline at end of file +win_il2cpp_test_image: dots-player/windows10:latest diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs index a78a7e65ad..309e268ca9 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs @@ -18,8 +18,7 @@ internal sealed class INetworkMessageILPP : ILPPInterface public override ILPPInterface GetInstance() => this; public override bool WillProcess(ICompiledAssembly compiledAssembly) => - compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName || - compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName); + compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName; private readonly List m_Diagnostics = new List(); diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs index b3579f21d4..c02fb55965 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs @@ -85,7 +85,6 @@ public virtual void SetupTestSuite() private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { - MultiprocessLogger.Log($"OnSceneLoaded {scene.name}"); SceneManager.sceneLoaded -= OnSceneLoaded; if (scene.name == BuildMultiprocessTestPlayer.MainSceneName) { @@ -93,7 +92,6 @@ private void OnSceneLoaded(Scene scene, LoadSceneMode mode) } var transport = NetworkManager.Singleton.NetworkConfig.NetworkTransport; - MultiprocessLogger.Log($"transport is {transport}"); switch (transport) { #if UNITY_UNET_PRESENT diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs index dcde9fdb50..589e17a4f5 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs @@ -10,6 +10,7 @@ using UnityEngine; using UnityEngine.TestTools; using Debug = UnityEngine.Debug; +using Unity.Netcode.MultiprocessRuntimeTests; /// /// Allows for context based delegate execution. @@ -83,6 +84,7 @@ private static string GetMethodIdentifier(MethodBase callerMethod) internal static void InitializeAllSteps() { + MultiprocessLogger.Log("InitializeAllSteps - Start"); // registering magically all context based steps IsRegistering = true; var registeredMethods = typeof(TestCoordinator).Assembly.GetTypes().SelectMany(t => t.GetMethods()) @@ -147,6 +149,7 @@ object[] GetParameterValuesToPassFunc(ParameterInfo[] parameterInfo) IsRegistering = false; HasRegistered = true; + MultiprocessLogger.Log("InitializeAllSteps - Done"); } /// @@ -196,9 +199,11 @@ public ExecuteStepInContext(StepExecutionContext actionContext, Action s { Assert.That(AllActions, Does.Not.Contain(currentActionId)); // sanity check AllActions[currentActionId] = this; + MultiprocessLogger.Log($"InitializeAllSteps - Registering {currentActionId}"); } else { + MultiprocessLogger.Log($"InitializeAllSteps - Not Registering {currentActionId}"); if (shouldExecuteLocally) { m_StepToExecute.Invoke(paramToPass); diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs index 0f9398d52f..240ee28ea4 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs @@ -84,7 +84,6 @@ public static int ActiveWorkerCount() if (s_Processes.Count > 0) { - MultiprocessLogger.Log($"s_Processes.Count is {s_Processes.Count}"); foreach (var p in s_Processes) { if ((p != null) && (!p.HasExited)) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs index 5de5010e6c..499e011aae 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs @@ -27,7 +27,7 @@ public class TestCoordinator : NetworkBehaviour { public const int PerTestTimeoutSec = 5 * 60; // seconds - public const float MaxWaitTimeoutSec = 20; + public const float MaxWaitTimeoutSec = 60; private const char k_MethodFullNameSplitChar = '@'; private bool m_ShouldShutdown; @@ -75,8 +75,10 @@ public void Awake() enabled = false; NetworkManager.OnClientConnectedCallback += OnClientConnectedCallback; + MultiprocessLogger.Log("Awake - Initialize All Steps"); + ExecuteStepInContext.InitializeAllSteps(); + s_ProcessId = Process.GetCurrentProcess().Id; - MultiprocessLogger.Log($"Awake {s_ProcessId}"); ReadGitHashFile(); // Configuration via command line (supported for many but not all platforms) @@ -165,6 +167,7 @@ private async void ConfigureViaWebApi() private void ReadGitHashFile() { + Rawgithash = "uninitialized"; try { var githash_resource = Resources.Load("Text/githash"); @@ -216,12 +219,7 @@ private void SetAddressAndPort() public void Start() { - MultiprocessLogger.Log("Start"); - MultiprocessLogger.Log("Initialize All Steps"); - ExecuteStepInContext.InitializeAllSteps(); - MultiprocessLogger.Log($"Initialize All Steps... done"); - MultiprocessLogger.Log($"IsInvoking: {NetworkManager.Singleton.IsInvoking()}"); - MultiprocessLogger.Log($"IsActiveAndEnabled: {NetworkManager.Singleton.isActiveAndEnabled}"); + MultiprocessLogger.Log($"TestCoordinator - Start"); } public void Update() @@ -282,8 +280,11 @@ public void OnDisable() // Once we are connected, we can run the update method public void OnClientConnectedCallback(ulong clientId) { - MultiprocessLogger.Log("Client start callback, enabling behavior"); - enabled = true; + if (enabled == false) + { + MultiprocessLogger.Log($"OnClientConnectedCallback enabling behavior clientId: {clientId} {NetworkManager.Singleton.IsHost}/{NetworkManager.Singleton.IsClient} IsRegistering:{ExecuteStepInContext.IsRegistering}"); + enabled = true; + } } private static void OnClientDisconnectCallback(ulong clientId) @@ -424,6 +425,7 @@ public void InvokeFromMethodActionRpc(Action methodInfo) public void TriggerActionIdClientRpc(string actionId, byte[] args, bool ignoreException, ClientRpcParams clientRpcParams = default) { MultiprocessLogger.Log($"received RPC from server, client side triggering action ID {actionId}"); + WriteLogServerRpc($"received RPC from server, client side triggering action ID {actionId} {ExecuteStepInContext.AllActions.Count}"); try { ExecuteStepInContext.AllActions[actionId].Invoke(args); @@ -519,5 +521,11 @@ public void WriteErrorServerRpc(string errorMessage, ServerRpcParams receivePara { MultiprocessLogger.LogError($"[Netcode-Server Sender={receiveParams.Receive.SenderClientId}] {errorMessage}"); } + + [ServerRpc(RequireOwnership = false)] + public void WriteLogServerRpc(string logMessage, ServerRpcParams receiveParams = default) + { + MultiprocessLogger.Log($"[Netcode-Server Sender={receiveParams.Receive.SenderClientId}] {logMessage}"); + } } From de8f1f4bd85ee674ee803fc2850ec81a64c8ad8f Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 29 Jul 2022 14:43:13 -0500 Subject: [PATCH 014/138] fix: NetworkObject.NetworkHide destroys client-side in-scene placed NetworkObjects [MTT-4211] (#2086) * fix MTT-4211 This fixes the issue where in-scene placed NetworkObjects were being destroyed when NetworkHide is invoked. * test MTT-4211 This adds an additional check for NetworkHide and NetworkShow towards the end of the in-scene placed NetworkObject synchronization test. * style whitespace fix * style Adjusting comments and adding additional API XML Documentation for readability and clarification purposes. * update MTT-4211 Updating the changelog. * update improving the logic used for readability purposes since we know IsSceneObject will be valid. * style migrating the majority of the comments into remarks. * style Adding space within all added
tags so they look like this:
This is to see if the version of docusaurus we are using will not crash when it runs across this markdown syntax. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Core/NetworkObject.cs | 69 +++++++++++++++---- .../InScenePlacedNetworkObjectTests.cs | 14 ++++ 3 files changed, 70 insertions(+), 14 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 08dd5cb3f0..bf8bcaf446 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -12,6 +12,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - When using `UnityTransport`, _reliable_ payloads are now allowed to exceed the configured 'Max Payload Size'. Unreliable payloads remain bounded by this setting. (#2081) ### Fixed +- Fixed issue where `NetworkObject.NetworkHide` was despawning and destroying, as opposed to only despawning, in-scene placed `NetworkObject`s. (#2086) - Fixed issue where `NetworkAnimator` would not synchronize a looping animation for late joining clients if it was at the very end of its loop. (#2076) - Fixed issue where `NetworkAnimator` was not removing its subscription from `OnClientConnectedCallback` when despawned during the shutdown sequence. (#2074) - Fixed IsServer and IsClient being set to false before object despawn during the shutdown sequence. (#2074) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 4b201301f9..21c5d5d3cc 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -235,9 +235,19 @@ private void Awake() } /// - /// Shows a previously hidden to a client + /// Makes the previously hidden "netcode visible" to the targeted client. /// - /// The client to show the to + /// + /// Usage: Use to start sending updates for a previously hidden to the targeted client.
+ ///
+ /// Dynamically Spawned: s will be instantiated and spawned on the targeted client side.
+ /// In-Scene Placed: The instantiated but despawned s will be spawned on the targeted client side.
+ ///
+ /// See Also:
+ ///
+ /// or
+ ///
+ /// The targeted client public void NetworkShow(ulong clientId) { if (!IsSpawned) @@ -260,11 +270,22 @@ public void NetworkShow(ulong clientId) NetworkManager.SpawnManager.SendSpawnCallForObject(clientId, this); } + /// - /// Shows a list of previously hidden s to a client + /// Makes a list of previously hidden s "netcode visible" for the client specified. /// - /// The s to show - /// The client to show the objects to + /// + /// Usage: Use to start sending updates for previously hidden s to the targeted client.
+ ///
+ /// Dynamically Spawned: s will be instantiated and spawned on the targeted client's side.
+ /// In-Scene Placed: Already instantiated but despawned s will be spawned on the targeted client's side.
+ ///
+ /// See Also:
+ ///
+ /// or
+ ///
+ /// The objects to become "netcode visible" to the targeted client + /// The targeted client public static void NetworkShow(List networkObjects, ulong clientId) { if (networkObjects == null || networkObjects.Count == 0) @@ -305,9 +326,19 @@ public static void NetworkShow(List networkObjects, ulong clientI } /// - /// Hides a object from a specific client + /// Hides the from the targeted client. /// - /// The client to hide the object for + /// + /// Usage: Use to stop sending updates to the targeted client, "netcode invisible", for a currently visible .
+ ///
+ /// Dynamically Spawned: s will be despawned and destroyed on the targeted client's side.
+ /// In-Scene Placed: s will only be despawned on the targeted client's side.
+ ///
+ /// See Also:
+ ///
+ /// or
+ ///
+ /// The targeted client public void NetworkHide(ulong clientId) { if (!IsSpawned) @@ -335,7 +366,7 @@ public void NetworkHide(ulong clientId) var message = new DestroyObjectMessage { NetworkObjectId = NetworkObjectId, - DestroyGameObject = true + DestroyGameObject = !IsSceneObject.Value }; // Send destroy call var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId); @@ -343,10 +374,20 @@ public void NetworkHide(ulong clientId) } /// - /// Hides a list of objects from a client + /// Hides a list of s from the targeted client. /// - /// The objects to hide - /// The client to hide the objects from + /// + /// Usage: Use to stop sending updates to the targeted client, "netcode invisible", for the currently visible s.
+ ///
+ /// Dynamically Spawned: s will be despawned and destroyed on the targeted client's side.
+ /// In-Scene Placed: s will only be despawned on the targeted client's side.
+ ///
+ /// See Also:
+ ///
+ /// or
+ ///
+ /// The s that will become "netcode invisible" to the targeted client + /// The targeted client public static void NetworkHide(List networkObjects, ulong clientId) { if (networkObjects == null || networkObjects.Count == 0) @@ -455,8 +496,8 @@ public void SpawnWithOwnership(ulong clientId, bool destroyWithScene = false) /// /// Spawns a across the network and makes it the player object for the given client /// - /// The clientId whos player object this is - /// Should the object be destroyd when the scene is changed + /// The clientId who's player object this is + /// Should the object be destroyed when the scene is changed public void SpawnAsPlayerObject(ulong clientId, bool destroyWithScene = false) { SpawnInternal(destroyWithScene, clientId, true); @@ -697,7 +738,7 @@ private void OnTransformParentChanged() // For instance, if we're spawning NetworkObject 5 and its parent is 10, what should happen if we do not have 10 yet? // let's say 10 is on the way to be replicated in a few frames and we could fix that parent-child relationship later. // - // If you couldn't find your parent, we put you into OrphanChildren set and everytime we spawn another NetworkObject locally due to replication, + // If you couldn't find your parent, we put you into OrphanChildren set and every time we spawn another NetworkObject locally due to replication, // we call CheckOrphanChildren() method and quickly iterate over OrphanChildren set and see if we can reparent/adopt one. internal static HashSet OrphanChildren = new HashSet(); diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs index d00e34a92e..3d02ff0689 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs @@ -102,6 +102,20 @@ public IEnumerator InSceneNetworkObjectSynchAndSpawn() yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == TotalClients); AssertOnTimeout($"Timed out waiting for all in-scene instances to be spawned! Current spawned count: {NetworkObjectTestComponent.SpawnedInstances.Count()} | Expected spawn count: {TotalClients}"); + + // Test NetworkHide on the first client + var firstClientId = m_ClientNetworkManagers[0].LocalClientId; + + serverObject.NetworkHide(firstClientId); + + yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == TotalClients - 1); + AssertOnTimeout($"[NetworkHide] Timed out waiting for Client-{firstClientId} to despawn the in-scene placed NetworkObject! Current spawned count: {NetworkObjectTestComponent.SpawnedInstances.Count()} | Expected spawn count: {TotalClients - 1}"); + + // Validate that the first client can spawn the "netcode hidden" in-scene placed NetworkObject + serverObject.NetworkShow(firstClientId); + yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == TotalClients); + AssertOnTimeout($"[NetworkShow] Timed out waiting for Client-{firstClientId} to spawn the in-scene placed NetworkObject! Current spawned count: {NetworkObjectTestComponent.SpawnedInstances.Count()} | Expected spawn count: {TotalClients}"); + CleanUpLoadedScene(); } From 185fa3e29417487f6abf2ee70240921964a8aeba Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 29 Jul 2022 17:07:18 -0500 Subject: [PATCH 015/138] fix: updating testproject NetworkAnimator test (#2075) * fix This resolves the issue with NetworkAnimator not de-registering itself from OnClientConnected. This resolves some issues with the manual NetworkAnimator test. * update MTT-4080 (this branch includes fixes for the OnClientConnectedCallback issue) Renamed NetworkAnimatorEnhancement scene to NetworkAnimatorServerOwnerTest. Adding the manual NetworkAnimatorServerOwnerTest scene to the scenes in build list. * style fixing whitespace issue. * update Fixing how AnimatedCubeController determines the type of NetworkAnimator and whether it is server or owner authoritative. * update Simplifying the conditional check requirements down to IsSpawned and IsOwner for the AnimationUserInput component since the only input checked should always be by the owner. * update removing an artifact from cherry picking a previous branch's work that included the manual test fixes. * style removing unused property. --- ...t => NetworkAnimatorServerOwnerTest.asset} | 2 +- ...NetworkAnimatorServerOwnerTest.asset.meta} | 0 .../AnimatedCubeController.cs | 24 +++++++++---------- .../AnimationUserInput.cs | 2 +- ...y => NetworkAnimatorServerOwnerTest.unity} | 0 ...NetworkAnimatorServerOwnerTest.unity.meta} | 0 .../NetworkAnimatorTestPrefab.prefab | 8 +++---- .../ProjectSettings/EditorBuildSettings.asset | 3 ++- 8 files changed, 18 insertions(+), 21 deletions(-) rename testproject/Assets/References/Scene/{NetworkAnimatorEnhancements.asset => NetworkAnimatorServerOwnerTest.asset} (93%) rename testproject/Assets/References/Scene/{NetworkAnimatorEnhancements.asset.meta => NetworkAnimatorServerOwnerTest.asset.meta} (100%) rename testproject/Assets/Tests/Manual/NetworkAnimatorTests/{NetworkAnimatorEnhancement.unity => NetworkAnimatorServerOwnerTest.unity} (100%) rename testproject/Assets/Tests/Manual/NetworkAnimatorTests/{NetworkAnimatorEnhancement.unity.meta => NetworkAnimatorServerOwnerTest.unity.meta} (100%) diff --git a/testproject/Assets/References/Scene/NetworkAnimatorEnhancements.asset b/testproject/Assets/References/Scene/NetworkAnimatorServerOwnerTest.asset similarity index 93% rename from testproject/Assets/References/Scene/NetworkAnimatorEnhancements.asset rename to testproject/Assets/References/Scene/NetworkAnimatorServerOwnerTest.asset index 801cf57694..26abed5b9f 100644 --- a/testproject/Assets/References/Scene/NetworkAnimatorEnhancements.asset +++ b/testproject/Assets/References/Scene/NetworkAnimatorServerOwnerTest.asset @@ -10,7 +10,7 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 39a16938ffb5cd846a9f6df7a686a9c4, type: 3} - m_Name: NetworkAnimatorEnhancements + m_Name: NetworkAnimatorServerOwnerTest m_EditorClassIdentifier: SceneToReference: {fileID: 102900000, guid: f88da8bb8d07e11418eaad6524d5cc12, type: 3} m_IncludedScenes: [] diff --git a/testproject/Assets/References/Scene/NetworkAnimatorEnhancements.asset.meta b/testproject/Assets/References/Scene/NetworkAnimatorServerOwnerTest.asset.meta similarity index 100% rename from testproject/Assets/References/Scene/NetworkAnimatorEnhancements.asset.meta rename to testproject/Assets/References/Scene/NetworkAnimatorServerOwnerTest.asset.meta diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimatedCubeController.cs b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimatedCubeController.cs index b51b5b3bb7..2863598699 100644 --- a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimatedCubeController.cs +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimatedCubeController.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; using Unity.Netcode; using Unity.Netcode.Components; @@ -14,23 +15,17 @@ public class AnimatedCubeController : NetworkBehaviour private NetworkAnimator m_NetworkAnimator; private bool m_IsServerAuthoritative = true; - private void Awake() + private void DetermineNetworkAnimatorComponentType() { - m_Animator = GetComponent(); m_NetworkAnimator = GetComponent(); - if (m_NetworkAnimator == null) + if (m_NetworkAnimator != null) { - m_NetworkAnimator = GetComponent(); - if (m_NetworkAnimator != null) - { - m_IsServerAuthoritative = false; - } - else - { - throw new System.Exception($"{nameof(AnimatedCubeController)} requires that it is paired with either a {nameof(NetworkAnimator)} or {nameof(OwnerNetworkAnimator)}. Neither of the two components were found!"); - } + m_IsServerAuthoritative = m_NetworkAnimator.GetType() != typeof(OwnerNetworkAnimator); + } + else + { + throw new Exception($"{nameof(AnimatedCubeController)} requires that it is paired with either a {nameof(NetworkAnimator)} or {nameof(OwnerNetworkAnimator)}. Neither of the two components were found!"); } - m_Rotate = m_Animator.GetBool("Rotate"); } public override void OnNetworkSpawn() @@ -39,6 +34,9 @@ public override void OnNetworkSpawn() { enabled = false; } + m_Animator = GetComponent(); + DetermineNetworkAnimatorComponentType(); + m_Rotate = m_Animator.GetBool("Rotate"); } private bool HasAuthority() diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimationUserInput.cs b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimationUserInput.cs index 8008fdc3f6..4d7eed6421 100644 --- a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimationUserInput.cs +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimationUserInput.cs @@ -17,7 +17,7 @@ private void Start() // Update is called once per frame private void LateUpdate() { - if (!IsSpawned || !IsOwner && !IsServer) + if (!IsSpawned || !IsOwner) { return; } diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorEnhancement.unity b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorServerOwnerTest.unity similarity index 100% rename from testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorEnhancement.unity rename to testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorServerOwnerTest.unity diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorEnhancement.unity.meta b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorServerOwnerTest.unity.meta similarity index 100% rename from testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorEnhancement.unity.meta rename to testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorServerOwnerTest.unity.meta diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorTestPrefab.prefab b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorTestPrefab.prefab index a9c73c2a03..4686709f30 100644 --- a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorTestPrefab.prefab +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorTestPrefab.prefab @@ -287,7 +287,7 @@ GameObject: - component: {fileID: 3078684837582866037} - component: {fileID: 3078684837575715027} - component: {fileID: 5295167409644547614} - - component: {fileID: 1923420540885290134} + - component: {fileID: 3632991713004262889} m_Layer: 0 m_Name: NetworkAnimatedCube-Owner m_TagString: Untagged @@ -344,7 +344,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: TestIterations: 20 ---- !u!114 &1923420540885290134 +--- !u!114 &3632991713004262889 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -353,11 +353,10 @@ MonoBehaviour: m_GameObject: {fileID: 3078684837583100501} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: e8d0727d5ae3244e3b569694d3912374, type: 3} + m_Script: {fileID: 11500000, guid: f6a3556fb5f3bee4e8a0fb88acff87ff, type: 3} m_Name: m_EditorClassIdentifier: m_Animator: {fileID: 3078684837575715027} - m_IsServerAuthoritative: 0 --- !u!1 &3214090169133903718 GameObject: m_ObjectHideFlags: 0 @@ -649,7 +648,6 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_Animator: {fileID: 6047057957339659638} - m_IsServerAuthoritative: 1 --- !u!1 &4033769488171516769 GameObject: m_ObjectHideFlags: 0 diff --git a/testproject/ProjectSettings/EditorBuildSettings.asset b/testproject/ProjectSettings/EditorBuildSettings.asset index 0e9401da7c..54f861c188 100644 --- a/testproject/ProjectSettings/EditorBuildSettings.asset +++ b/testproject/ProjectSettings/EditorBuildSettings.asset @@ -104,7 +104,8 @@ EditorBuildSettings: - enabled: 1 path: Assets/Tests/Manual/InSceneObjectParentingTests/InSceneNetworkObjectToLoad.unity guid: f06a8525a4b5200459f62905c29ce09e - path: Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorEnhancement.unity + - enabled: 1 + path: Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorServerOwnerTest.unity guid: f88da8bb8d07e11418eaad6524d5cc12 m_configObjects: com.unity.addressableassets: {fileID: 11400000, guid: 5a3d5c53c25349c48912726ae850f3b0, From cb80991a3b586b4f1d36d238bcf7462f430194df Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 2 Aug 2022 08:44:41 -0500 Subject: [PATCH 016/138] fix: deactivated child NetworkBehaviour exceptions [NCCBUG-138] (#2096) --- com.unity.netcode.gameobjects/CHANGELOG.md | 3 ++ .../Runtime/Core/NetworkObject.cs | 18 +++++++- .../Runtime/NetworkBehaviourGenericTests.cs | 41 +++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index bf8bcaf446..78c5aab462 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -11,7 +11,10 @@ Additional documentation and release notes are available at [Multiplayer Documen - When using `UnityTransport`, _reliable_ payloads are now allowed to exceed the configured 'Max Payload Size'. Unreliable payloads remain bounded by this setting. (#2081) + ### Fixed + +- Fixed issue when attempting to spawn a parent `GameObject`, with `NetworkObject` component attached, that has one or more child `GameObject`s, that are inactive in the hierarchy, with `NetworkBehaviour` components it will no longer attempt to spawn the associated NetworkBehaviour(s) or invoke ownership changed notifications but will log a warning message. (#2096) - Fixed issue where `NetworkObject.NetworkHide` was despawning and destroying, as opposed to only despawning, in-scene placed `NetworkObject`s. (#2086) - Fixed issue where `NetworkAnimator` would not synchronize a looping animation for late joining clients if it was at the very end of its loop. (#2076) - Fixed issue where `NetworkAnimator` was not removing its subscription from `OnClientConnectedCallback` when despawned during the shutdown sequence. (#2074) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 21c5d5d3cc..49416c00e4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -553,7 +553,14 @@ internal void InvokeBehaviourOnGainedOwnership() for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { - ChildNetworkBehaviours[i].InternalOnGainedOwnership(); + if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy) + { + ChildNetworkBehaviours[i].InternalOnGainedOwnership(); + } + else + { + Debug.LogWarning($"{ChildNetworkBehaviours[i].gameObject.name} is disabled! Netcode for GameObjects does not support disabled NetworkBehaviours! The {ChildNetworkBehaviours[i].GetType().Name} component was skipped during ownership assignment!"); + } } } @@ -805,7 +812,14 @@ internal void InvokeBehaviourNetworkSpawn() for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { - ChildNetworkBehaviours[i].InternalOnNetworkSpawn(); + if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy) + { + ChildNetworkBehaviours[i].InternalOnNetworkSpawn(); + } + else + { + Debug.LogWarning($"{ChildNetworkBehaviours[i].gameObject.name} is disabled! Netcode for GameObjects does not support spawning disabled NetworkBehaviours! The {ChildNetworkBehaviours[i].GetType().Name} component was skipped during spawn!"); + } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs index 7d66770b9a..785e0fed9d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs @@ -2,6 +2,7 @@ using UnityEngine; using UnityEngine.TestTools; using Unity.Netcode.TestHelpers.Runtime; +using Unity.Netcode.Components; namespace Unity.Netcode.RuntimeTests { @@ -23,6 +24,46 @@ public class SimpleNetworkBehaviour : NetworkBehaviour { } + protected override IEnumerator OnSetup() + { + m_AllowServerToStart = false; + return base.OnSetup(); + } + + /// + /// This validates the fix for when a child GameObject with a NetworkBehaviour + /// is deleted while the parent GameObject with a NetworkObject is spawned and + /// is not deleted until a later time would cause an exception due to the + /// NetworkBehaviour not being removed from the NetworkObject.ChildNetworkBehaviours + /// list. + /// + [UnityTest] + public IEnumerator ValidatedDisableddNetworkBehaviourWarning() + { + m_AllowServerToStart = true; + + yield return s_DefaultWaitForTick; + + // Now just start the Host + yield return StartServerAndClients(); + + var parentObject = new GameObject(); + var childObject = new GameObject(); + childObject.name = "ChildObject"; + childObject.transform.parent = parentObject.transform; + var parentNetworkObject = parentObject.AddComponent(); + var childBehaviour = childObject.AddComponent(); + + // Set the child object to be inactive in the hierarchy + childObject.SetActive(false); + + LogAssert.Expect(LogType.Warning, $"{childObject.name} is disabled! Netcode for GameObjects does not support disabled NetworkBehaviours! The {childBehaviour.GetType().Name} component was skipped during ownership assignment!"); + LogAssert.Expect(LogType.Warning, $"{childObject.name} is disabled! Netcode for GameObjects does not support spawning disabled NetworkBehaviours! The {childBehaviour.GetType().Name} component was skipped during spawn!"); + + parentNetworkObject.Spawn(); + yield return s_DefaultWaitForTick; + } + /// /// This test validates a fix to NetworkBehaviour.NetworkObject when /// the NetworkManager.LogLevel is set to Developer From 705ae1d29b397ce57ac91c2511cbad12a6cf5623 Mon Sep 17 00:00:00 2001 From: Fatih Mar Date: Wed, 3 Aug 2022 22:08:47 +0100 Subject: [PATCH 017/138] chore: update `CODEOWNERS` --- .github/CODEOWNERS | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 39b92b5321..1b2fbcc8c8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -7,10 +7,10 @@ Metrics/ @Unity-Technologies/multiplayer-tools /com.unity.netcode.gameobjects/Runtime/Transports/ @Unity-Technologies/multiplayer-server /com.unity.netcode.gameobjects/Tests/Editor/Transports/ @Unity-Technologies/multiplayer-server /com.unity.netcode.gameobjects/Tests/Runtime/Transports/ @Unity-Technologies/multiplayer-server -*.asmdef @ThusWroteNomad -package.json @ThusWroteNomad -AssemblyInfo.cs @ThusWroteNomad -.editorconfig @ThusWroteNomad -.gitignore @ThusWroteNomad -.github/ @ThusWroteNomad @lpmaurice @ashwinimurt -.yamato/ @ThusWroteNomad @lpmaurice @ashwinimurt +*.asmdef @ThusSpokeNomad +package.json @ThusSpokeNomad +AssemblyInfo.cs @ThusSpokeNomad +.editorconfig @ThusSpokeNomad +.gitignore @ThusSpokeNomad +.github/ @ThusSpokeNomad @lpmaurice @ashwinimurt +.yamato/ @ThusSpokeNomad @lpmaurice @ashwinimurt From b861894150cd7d7edc99d8b69d9f7d4c53a73ceb Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Wed, 3 Aug 2022 19:01:50 -0400 Subject: [PATCH 018/138] fix: NetworkList "insert at end" bug (#2099) --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Collections/NetworkList.cs | 23 +++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 78c5aab462..a2e2ce3448 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -21,6 +21,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed IsServer and IsClient being set to false before object despawn during the shutdown sequence. (#2074) - Fixed NetworkLists not populating on client. NetworkList now uses the most recent list as opposed to the list at the end of previous frame, when sending full updates to dynamically spawned NetworkObject. The difference in behaviour is required as scene management spawns those objects at a different time in the frame, relative to updates. (#2062) - Fixed NetworkList Value event on the server. PreviousValue is now set correctly when a new value is set through property setter. (#2067) +- Fixed NetworkList issue that showed when inserting at the very end of a NetworkList (#2099) ## [1.0.0] - 2022-06-27 diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index 30f8459d1f..e8c939eb57 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -196,8 +196,16 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) { reader.ReadValueSafe(out int index); NetworkVariableSerialization.Read(reader, out T value); - m_List.InsertRangeWithBeginEnd(index, index + 1); - m_List[index] = value; + + if (index < m_List.Length) + { + m_List.InsertRangeWithBeginEnd(index, index + 1); + m_List[index] = value; + } + else + { + m_List.Add(value); + } if (OnListChanged != null) { @@ -419,8 +427,15 @@ public int IndexOf(T item) /// public void Insert(int index, T item) { - m_List.InsertRangeWithBeginEnd(index, index + 1); - m_List[index] = item; + if (index < m_List.Length) + { + m_List.InsertRangeWithBeginEnd(index, index + 1); + m_List[index] = item; + } + else + { + m_List.Add(item); + } var listEvent = new NetworkListEvent() { From fb63ed3b0ab790ec4502c7affdec054f0dfa9e32 Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Mon, 8 Aug 2022 15:43:39 -0400 Subject: [PATCH 019/138] feat: host tells client which IDs it uses for INetworkMessages (#2103) Server sends client reordering messages when host and client have different sets to ensure proper handler mappings. --- .../Runtime/Core/NetworkManager.cs | 14 ++ .../Messaging/Messages/OrderingMessage.cs | 50 +++++++ .../Messages/OrderingMessage.cs.meta | 11 ++ .../Runtime/Messaging/MessagingSystem.cs | 141 ++++++++++++++++-- .../Messaging/MessageRegistrationTests.cs | 118 +++++++++++++++ .../Editor/Messaging/MessageSendingTests.cs | 47 +++++- 6 files changed, 369 insertions(+), 12 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/Messages/OrderingMessage.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/Messages/OrderingMessage.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index dcf8e0c114..b35b6ad315 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -2072,6 +2072,20 @@ internal void HandleConnectionApproval(ulong ownerClientId, ConnectionApprovalRe SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId); + for (int index = 0; index < MessagingSystem.MessageHandlers.Length; index++) + { + if (MessagingSystem.MessageTypes[index] != null) + { + var orderingMessage = new OrderingMessage + { + Order = index, + Hash = XXHash.Hash32(MessagingSystem.MessageTypes[index].FullName) + }; + + SendMessage(ref orderingMessage, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId); + } + } + // If scene management is enabled, then let NetworkSceneManager handle the initial scene and NetworkObject synchronization if (!NetworkConfig.EnableSceneManagement) { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/OrderingMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/OrderingMessage.cs new file mode 100644 index 0000000000..6651e0e533 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/OrderingMessage.cs @@ -0,0 +1,50 @@ +using System; + +namespace Unity.Netcode +{ + /// + /// Upon connecting, the host sends a series of OrderingMessage to the client so that it can make sure both sides + /// have the same message types in the same positions in + /// - MessagingSystem.m_MessageHandlers + /// - MessagingSystem.m_ReverseTypeMap + /// even if one side has extra messages (compilation, version, patch, or platform differences, etc...) + /// + /// The ConnectionRequestedMessage, ConnectionApprovedMessage and OrderingMessage are prioritized at the beginning + /// of the mapping, to guarantee they can be exchanged before the two sides share their ordering + /// The sorting used in also stable so that even if MessageType names share hashes, it will work most of the time + /// + internal struct OrderingMessage : INetworkMessage + { + public int Order; + public uint Hash; + + public void Serialize(FastBufferWriter writer) + { + if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(Order) + FastBufferWriter.GetWriteSize(Hash))) + { + throw new OverflowException($"Not enough space in the buffer to write {nameof(OrderingMessage)}"); + } + + writer.WriteValue(Order); + writer.WriteValue(Hash); + } + + public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + { + if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(Order) + FastBufferWriter.GetWriteSize(Hash))) + { + throw new OverflowException($"Not enough data in the buffer to read {nameof(OrderingMessage)}"); + } + + reader.ReadValue(out Order); + reader.ReadValue(out Hash); + + return true; + } + + public void Handle(ref NetworkContext context) + { + ((NetworkManager)context.SystemOwner).MessagingSystem.ReorderMessage(Order, Hash); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/OrderingMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/OrderingMessage.cs.meta new file mode 100644 index 0000000000..3a8bc039c5 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/OrderingMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3ada9e8fd5bf94b1f9a6a21531c8a3ee +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/MessagingSystem.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/MessagingSystem.cs index 4bec42e6fb..82917a1fe9 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/MessagingSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/MessagingSystem.cs @@ -8,6 +8,11 @@ namespace Unity.Netcode { + internal class HandlerNotRegisteredException : SystemException + { + public HandlerNotRegisteredException() { } + public HandlerNotRegisteredException(string issue) : base(issue) { } + } internal class InvalidMessageStructureException : SystemException { @@ -44,8 +49,9 @@ public SendQueueItem(NetworkDelivery delivery, int writerSize, Allocator writerA private NativeList m_IncomingMessageQueue = new NativeList(16, Allocator.Persistent); - private MessageHandler[] m_MessageHandlers = new MessageHandler[255]; - private Type[] m_ReverseTypeMap = new Type[255]; + // These array will grow as we need more message handlers. 4 is just a starting size. + private MessageHandler[] m_MessageHandlers = new MessageHandler[4]; + private Type[] m_ReverseTypeMap = new Type[4]; private Dictionary m_MessageTypes = new Dictionary(); private Dictionary> m_SendQueues = new Dictionary>(); @@ -59,6 +65,7 @@ public SendQueueItem(NetworkDelivery delivery, int writerSize, Allocator writerA internal Type[] MessageTypes => m_ReverseTypeMap; internal MessageHandler[] MessageHandlers => m_MessageHandlers; + internal uint MessageHandlerCount => m_HighMessageType; internal uint GetMessageType(Type t) @@ -75,6 +82,35 @@ internal struct MessageWithHandler public MessageHandler Handler; } + internal List PrioritizeMessageOrder(List allowedTypes) + { + var prioritizedTypes = new List(); + + // first pass puts the priority message in the first indices + // Those are the messages that must be delivered in order to allow re-ordering the others later + foreach (var t in allowedTypes) + { + if (t.MessageType.FullName == "Unity.Netcode.ConnectionRequestMessage" || + t.MessageType.FullName == "Unity.Netcode.ConnectionApprovedMessage" || + t.MessageType.FullName == "Unity.Netcode.OrderingMessage") + { + prioritizedTypes.Add(t); + } + } + + foreach (var t in allowedTypes) + { + if (t.MessageType.FullName != "Unity.Netcode.ConnectionRequestMessage" && + t.MessageType.FullName != "Unity.Netcode.ConnectionApprovedMessage" && + t.MessageType.FullName != "Unity.Netcode.OrderingMessage") + { + prioritizedTypes.Add(t); + } + } + + return prioritizedTypes; + } + public MessagingSystem(IMessageSender messageSender, object owner, IMessageProvider provider = null) { try @@ -89,6 +125,7 @@ public MessagingSystem(IMessageSender messageSender, object owner, IMessageProvi var allowedTypes = provider.GetMessages(); allowedTypes.Sort((a, b) => string.CompareOrdinal(a.MessageType.FullName, b.MessageType.FullName)); + allowedTypes = PrioritizeMessageOrder(allowedTypes); foreach (var type in allowedTypes) { RegisterMessageType(type); @@ -143,6 +180,13 @@ public void Unhook(INetworkHooks hooks) private void RegisterMessageType(MessageWithHandler messageWithHandler) { + // if we are out of space, perform amortized linear growth + if (m_HighMessageType == m_MessageHandlers.Length) + { + Array.Resize(ref m_MessageHandlers, 2 * m_MessageHandlers.Length); + Array.Resize(ref m_ReverseTypeMap, 2 * m_ReverseTypeMap.Length); + } + m_MessageHandlers[m_HighMessageType] = messageWithHandler.Handler; m_ReverseTypeMap[m_HighMessageType] = messageWithHandler.MessageType; m_MessageTypes[messageWithHandler.MessageType] = m_HighMessageType++; @@ -226,6 +270,70 @@ private bool CanReceive(ulong clientId, Type messageType, FastBufferReader messa return true; } + // Moves the handler for the type having hash `targetHash` to the `desiredOrder` position, in the handler list + // This allows the server to tell the client which id it is using for which message and make sure the right + // message is used when deserializing. + internal void ReorderMessage(int desiredOrder, uint targetHash) + { + if (desiredOrder < 0) + { + throw new ArgumentException("ReorderMessage desiredOrder must be positive"); + } + + if (desiredOrder < m_ReverseTypeMap.Length && + XXHash.Hash32(m_ReverseTypeMap[desiredOrder].FullName) == targetHash) + { + // matching positions and hashes. All good. + return; + } + + Debug.Log($"Unexpected hash for {desiredOrder}"); + + // Since the message at `desiredOrder` is not the expected one, + // insert an empty placeholder and move the messages down + var typesAsList = new List(m_ReverseTypeMap); + + typesAsList.Insert(desiredOrder, null); + var handlersAsList = new List(m_MessageHandlers); + handlersAsList.Insert(desiredOrder, null); + + // we added a dummy message, bump the end up + m_HighMessageType++; + + // Here, we rely on the server telling us about all messages, in order. + // So, we know the handlers before desiredOrder are correct. + // We start at desiredOrder to not shift them when we insert. + int position = desiredOrder; + bool found = false; + while (position < typesAsList.Count) + { + if (typesAsList[position] != null && + XXHash.Hash32(typesAsList[position].FullName) == targetHash) + { + found = true; + break; + } + + position++; + } + + if (found) + { + // Copy the handler and type to the right index + + typesAsList[desiredOrder] = typesAsList[position]; + handlersAsList[desiredOrder] = handlersAsList[position]; + typesAsList.RemoveAt(position); + handlersAsList.RemoveAt(position); + + // we removed a copy after moving a message, reduce the high message index + m_HighMessageType--; + } + + m_ReverseTypeMap = typesAsList.ToArray(); + m_MessageHandlers = handlersAsList.ToArray(); + } + public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulong senderId, float timestamp, int serializedHeaderSize) { if (header.MessageType >= m_HighMessageType) @@ -259,18 +367,29 @@ public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulon var handler = m_MessageHandlers[header.MessageType]; using (reader) { - // No user-land message handler exceptions should escape the receive loop. - // If an exception is throw, the message is ignored. - // Example use case: A bad message is received that can't be deserialized and throws - // an OverflowException because it specifies a length greater than the number of bytes in it - // for some dynamic-length value. - try + // This will also log an exception is if the server knows about a message type the client doesn't know + // about. In this case the handler will be null. It is still an issue the user must deal with: If the + // two connecting builds know about different messages, the server should not send a message to a client + // that doesn't know about it + if (handler == null) { - handler.Invoke(reader, ref context, this); + Debug.LogException(new HandlerNotRegisteredException(header.MessageType.ToString())); } - catch (Exception e) + else { - Debug.LogException(e); + // No user-land message handler exceptions should escape the receive loop. + // If an exception is throw, the message is ignored. + // Example use case: A bad message is received that can't be deserialized and throws + // an OverflowException because it specifies a length greater than the number of bytes in it + // for some dynamic-length value. + try + { + handler.Invoke(reader, ref context, this); + } + catch (Exception e) + { + Debug.LogException(e); + } } } for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs index b606fd5c86..1228712c2e 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using NUnit.Framework; @@ -179,5 +180,122 @@ public void WhenCreatingMessageSystem_BoundTypeMessageHandlersAreRegistered() Assert.AreEqual(handlerFour, systemThree.MessageHandlers[systemThree.GetMessageType(typeof(TestMessageFour))]); } } + + internal class AAAEarlyLexicographicNetworkMessage : INetworkMessage + { + public void Serialize(FastBufferWriter writer) + { + } + + public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + { + return true; + } + + public void Handle(ref NetworkContext context) + { + } + } + +#pragma warning disable IDE1006 + internal class zzzLateLexicographicNetworkMessage : AAAEarlyLexicographicNetworkMessage + { + } +#pragma warning restore IDE1006 + + internal class OrderingMessageProvider : IMessageProvider + { + public List GetMessages() + { + var listMessages = new List(); + + var messageWithHandler = new MessagingSystem.MessageWithHandler(); + + messageWithHandler.MessageType = typeof(zzzLateLexicographicNetworkMessage); + listMessages.Add(messageWithHandler); + + messageWithHandler.MessageType = typeof(ConnectionRequestMessage); + listMessages.Add(messageWithHandler); + + messageWithHandler.MessageType = typeof(ConnectionApprovedMessage); + listMessages.Add(messageWithHandler); + + messageWithHandler.MessageType = typeof(OrderingMessage); + listMessages.Add(messageWithHandler); + + messageWithHandler.MessageType = typeof(AAAEarlyLexicographicNetworkMessage); + listMessages.Add(messageWithHandler); + + return listMessages; + } + } + + [Test] + public void MessagesGetPrioritizedCorrectly() + { + var sender = new NopMessageSender(); + var provider = new OrderingMessageProvider(); + var messagingSystem = new MessagingSystem(sender, null, provider); + + // the 3 priority messages should appear first, in lexicographic order + Assert.AreEqual(messagingSystem.MessageTypes[0], typeof(ConnectionApprovedMessage)); + Assert.AreEqual(messagingSystem.MessageTypes[1], typeof(ConnectionRequestMessage)); + Assert.AreEqual(messagingSystem.MessageTypes[2], typeof(OrderingMessage)); + + // the other should follow after + Assert.AreEqual(messagingSystem.MessageTypes[3], typeof(AAAEarlyLexicographicNetworkMessage)); + Assert.AreEqual(messagingSystem.MessageTypes[4], typeof(zzzLateLexicographicNetworkMessage)); + + // there should not be any extras + Assert.AreEqual(messagingSystem.MessageHandlerCount, 5); + + // reorder the zzz one to position 3 + messagingSystem.ReorderMessage(3, XXHash.Hash32(typeof(zzzLateLexicographicNetworkMessage).FullName)); + + // the 3 priority messages should still appear first, in lexicographic order + Assert.AreEqual(messagingSystem.MessageTypes[0], typeof(ConnectionApprovedMessage)); + Assert.AreEqual(messagingSystem.MessageTypes[1], typeof(ConnectionRequestMessage)); + Assert.AreEqual(messagingSystem.MessageTypes[2], typeof(OrderingMessage)); + + // the other should follow after, but reordered + Assert.AreEqual(messagingSystem.MessageTypes[3], typeof(zzzLateLexicographicNetworkMessage)); + Assert.AreEqual(messagingSystem.MessageTypes[4], typeof(AAAEarlyLexicographicNetworkMessage)); + + // there should still not be any extras + Assert.AreEqual(messagingSystem.MessageHandlerCount, 5); + + // verify we get an exception when asking for an invalid position + try + { + messagingSystem.ReorderMessage(-1, XXHash.Hash32(typeof(zzzLateLexicographicNetworkMessage).FullName)); + Assert.Fail(); + } + catch (ArgumentException) + { + } + + // reorder the zzz one to position 3, again, to check nothing bad happens + messagingSystem.ReorderMessage(3, XXHash.Hash32(typeof(zzzLateLexicographicNetworkMessage).FullName)); + + // the two non-priority should not have moved + Assert.AreEqual(messagingSystem.MessageTypes[3], typeof(zzzLateLexicographicNetworkMessage)); + Assert.AreEqual(messagingSystem.MessageTypes[4], typeof(AAAEarlyLexicographicNetworkMessage)); + + // there should still not be any extras + Assert.AreEqual(messagingSystem.MessageHandlerCount, 5); + + // 4242 is a random hash that should not match anything + messagingSystem.ReorderMessage(3, 4242); + + // that should result in an extra entry + Assert.AreEqual(messagingSystem.MessageHandlerCount, 6); + + // with a null handler + Assert.AreEqual(messagingSystem.MessageHandlers[3], null); + + // and it should have bumped the previous messages down + Assert.AreEqual(messagingSystem.MessageTypes[4], typeof(zzzLateLexicographicNetworkMessage)); + Assert.AreEqual(messagingSystem.MessageTypes[5], typeof(AAAEarlyLexicographicNetworkMessage)); + } } } diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs index dd1d3eb7e4..0cb09a48bb 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs @@ -1,8 +1,11 @@ -using System; using System.Collections.Generic; +using System.Text.RegularExpressions; using NUnit.Framework; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; +using UnityEngine; +using UnityEngine.TestTools; +using Random = System.Random; namespace Unity.Netcode.EditorTests { @@ -224,5 +227,47 @@ public void WhenSendingMessaged_SentDataIsCorrect() Assert.AreEqual(message2, receivedMessage2); } } + + private class TestNoHandlerMessageProvider : IMessageProvider + { + public List GetMessages() + { + return new List + { + new MessagingSystem.MessageWithHandler + { + MessageType = typeof(TestMessage), + Handler = null + } + }; + } + } + + [Test] + public void WhenReceivingAMessageWithoutAHandler_ExceptionIsLogged() + { + m_MessagingSystem = new MessagingSystem(new NopMessageSender(), this, new TestNoHandlerMessageProvider()); + + var messageHeader = new MessageHeader + { + MessageSize = (ushort)UnsafeUtility.SizeOf(), + MessageType = m_MessagingSystem.GetMessageType(typeof(TestMessage)), + }; + var message = GetMessage(); + + var writer = new FastBufferWriter(1300, Allocator.Temp); + using (writer) + { + writer.TryBeginWrite(FastBufferWriter.GetWriteSize(message)); + writer.WriteValue(message); + + var reader = new FastBufferReader(writer, Allocator.Temp); + using (reader) + { + m_MessagingSystem.HandleMessage(messageHeader, reader, 0, 0, 0); + LogAssert.Expect(LogType.Exception, new Regex(".*HandlerNotRegisteredException.*")); + } + } + } } } From ae4204ebb2df990a655e11a82bb44cd2b686696d Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 9 Aug 2022 07:56:02 -0500 Subject: [PATCH 020/138] fix: client owner with both owner read and write permissions on a NetworkVariable does not update the server when changed (#2097) * fix MTT-4266 This fixes the issue where a NetworkVariable that is set to owner read and write permissions and the owner is a client, then the server would not be updated with any changes to the NetworkVariable. * test This includes an additional test to check that a client owner with both Owner read and write permissions will only update changes to the NetworkVariable on the server. * update Adding the changelog entry for this fix. * update adding PR number. --- com.unity.netcode.gameobjects/CHANGELOG.md | 3 +- .../NetworkVariable/NetworkVariableBase.cs | 2 +- .../Tests/Runtime/NetworkVariableTests.cs | 75 +++++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index a2e2ce3448..9862d1104a 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -14,7 +14,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed -- Fixed issue when attempting to spawn a parent `GameObject`, with `NetworkObject` component attached, that has one or more child `GameObject`s, that are inactive in the hierarchy, with `NetworkBehaviour` components it will no longer attempt to spawn the associated NetworkBehaviour(s) or invoke ownership changed notifications but will log a warning message. (#2096) +- Fixed issue where a client owner of a `NetworkVariable` with both owner read and write permissions would not update the server side when changed. (#2097) +- Fixed issue when attempting to spawn a parent `GameObject`, with `NetworkObject` component attached, that has one or more child `GameObject`s, that are inactive in the hierarchy, with `NetworkBehaviour` components it will no longer attempt to spawn the associated `NetworkBehaviour`(s) or invoke ownership changed notifications but will log a warning message. (#2096) - Fixed issue where `NetworkObject.NetworkHide` was despawning and destroying, as opposed to only despawning, in-scene placed `NetworkObject`s. (#2086) - Fixed issue where `NetworkAnimator` would not synchronize a looping animation for late joining clients if it was at the very end of its loop. (#2076) - Fixed issue where `NetworkAnimator` was not removing its subscription from `OnClientConnectedCallback` when despawned during the shutdown sequence. (#2074) diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs index a35e31eac8..fa29bd1274 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -111,7 +111,7 @@ public bool CanClientRead(ulong clientId) case NetworkVariableReadPermission.Everyone: return true; case NetworkVariableReadPermission.Owner: - return clientId == m_NetworkBehaviour.NetworkObject.OwnerClientId; + return clientId == m_NetworkBehaviour.NetworkObject.OwnerClientId || NetworkManager.ServerClientId == clientId; } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs index fee8922612..f8b733985c 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs @@ -14,6 +14,7 @@ public class NetVarPermTestComp : NetworkBehaviour { public NetworkVariable OwnerWritable_Position = new NetworkVariable(Vector3.one, NetworkVariableBase.DefaultReadPerm, NetworkVariableWritePermission.Owner); public NetworkVariable ServerWritable_Position = new NetworkVariable(Vector3.one, NetworkVariableBase.DefaultReadPerm, NetworkVariableWritePermission.Server); + public NetworkVariable OwnerReadWrite_Position = new NetworkVariable(Vector3.one, NetworkVariableReadPermission.Owner, NetworkVariableWritePermission.Owner); } [TestFixtureSource(nameof(TestDataSource))] @@ -104,6 +105,42 @@ private bool CheckServerWritableAreEqualOnAll() return true; } + private bool CheckOwnerReadWriteAreEqualOnOwnerAndServer() + { + var testObjServer = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; + var testCompServer = testObjServer.GetComponent(); + foreach (var clientNetworkManager in m_ClientNetworkManagers) + { + var testObjClient = clientNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; + var testCompClient = testObjClient.GetComponent(); + if (testObjServer.OwnerClientId == testObjClient.OwnerClientId && + testCompServer.OwnerReadWrite_Position.Value == testCompClient.ServerWritable_Position.Value && + testCompServer.OwnerReadWrite_Position.ReadPerm == testCompClient.ServerWritable_Position.ReadPerm && + testCompServer.OwnerReadWrite_Position.WritePerm == testCompClient.ServerWritable_Position.WritePerm) + { + return true; + } + } + return false; + } + + private bool CheckOwnerReadWriteAreNotEqualOnNonOwnerClients(NetVarPermTestComp ownerReadWriteObject) + { + foreach (var clientNetworkManager in m_ClientNetworkManagers) + { + var testObjClient = clientNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; + var testCompClient = testObjClient.GetComponent(); + if (testObjClient.OwnerClientId != ownerReadWriteObject.OwnerClientId || + ownerReadWriteObject.OwnerReadWrite_Position.Value == testCompClient.ServerWritable_Position.Value || + ownerReadWriteObject.OwnerReadWrite_Position.ReadPerm != testCompClient.ServerWritable_Position.ReadPerm || + ownerReadWriteObject.OwnerReadWrite_Position.WritePerm != testCompClient.ServerWritable_Position.WritePerm) + { + return false; + } + } + return true; + } + [UnityTest] public IEnumerator ServerChangesOwnerWritableNetVar() { @@ -164,6 +201,44 @@ public IEnumerator ClientChangesOwnerWritableNetVar() yield return WaitForOwnerWritableAreEqualOnAll(); } + /// + /// This tests the scenario where a client owner has both read and write + /// permissions set. The server should be the only instance that can read + /// the NetworkVariable. ServerCannotChangeOwnerWritableNetVar performs + /// the same check to make sure the server cannot write to a client owner + /// NetworkVariable with owner write permissions. + /// + [UnityTest] + public IEnumerator ClientOwnerWithReadWriteChangesNetVar() + { + yield return WaitForOwnerWritableAreEqualOnAll(); + + var testObjServer = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId]; + + int clientManagerIndex = m_ClientNetworkManagers.Length - 1; + var newOwnerClientId = m_ClientNetworkManagers[clientManagerIndex].LocalClientId; + testObjServer.ChangeOwnership(newOwnerClientId); + yield return NetcodeIntegrationTestHelpers.WaitForTicks(m_ServerNetworkManager, 2); + + yield return WaitForOwnerWritableAreEqualOnAll(); + + var testObjClient = m_ClientNetworkManagers[clientManagerIndex].SpawnManager.SpawnedObjects[m_TestObjId]; + var testCompClient = testObjClient.GetComponent(); + + var oldValue = testCompClient.OwnerReadWrite_Position.Value; + var newValue = oldValue + new Vector3(Random.Range(0, 100.0f), Random.Range(0, 100.0f), Random.Range(0, 100.0f)); + + testCompClient.OwnerWritable_Position.Value = newValue; + yield return WaitForPositionsAreEqual(testCompClient.OwnerWritable_Position, newValue); + + // Verify the client owner and server match + yield return CheckOwnerReadWriteAreEqualOnOwnerAndServer(); + + // Verify the non-owner clients do not have the same Value but do have the same permissions + yield return CheckOwnerReadWriteAreNotEqualOnNonOwnerClients(testCompClient); + } + + [UnityTest] public IEnumerator ClientCannotChangeServerWritableNetVar() { From dd053268f1024dcab23a9bed3184a4bf40a172b3 Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Wed, 10 Aug 2022 12:51:34 -0400 Subject: [PATCH 021/138] fix: disconnect event not being generated (#2113) Co-authored-by: Simon Lemay --- .../Runtime/Transports/UTP/UnityTransport.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index 3728280066..0a4bbf1702 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -1285,10 +1285,10 @@ public override void Shutdown() SendBatchedMessages(kvp.Key, kvp.Value); } - // The above flush only puts the message in UTP internal buffers, need the flush send - // job to execute to actually get things out on the wire. This will also ensure any - // disconnect messages are sent out. - m_Driver.ScheduleFlushSend(default).Complete(); + // The above flush only puts the message in UTP internal buffers, need an update to + // actually get the messages on the wire. (Normally a flush send would be sufficient, + // but there might be disconnect messages and those require an update call.) + m_Driver.ScheduleUpdate().Complete(); DisposeInternals(); From bf9e7e8fc9fcc280fa3d4f60371f0efa5b09198a Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 11 Aug 2022 09:49:28 -0500 Subject: [PATCH 022/138] fix: Nested NetworkBehaviours don't de-register or Invoke OnNetworkDespawn if destroyed while the parent NetworkObject remains spawned [NCCBUG-137] (#2091) * fix NCCBUG-137 This resolves the issue where destroying a GameObject with one or more NetworkBehaviour components that is a child or any child generation of the NetworkObject/GameObject that theNetworkBehaviour(s) are assigned to would still try to access the destroyed NetworkBehaviour since it was still an entry within the NetworkObject.ChildNetworkBehaviours list. It also resolves the issue where under this scenario the NetworkBehaviours would not have their OnNetworkDespawn method invoked. * update NCCBUG-137 updating the changelog * fix We need to check IsSpawned when logging a warning about a NetworkBehaviour not having an assigned NetworkObject (this is only valid if NetworkBehaviour.IsSpawned == true) * fix Needed to adjust to account for the condition when it is valid is when you have a NetworkBehaviour that is spawned but there is no assigned NetworkObject. * test NCCBUG-137 This is the integration validation test for this PR fix. * update Adding PR number to the changelog entry * test adding check for OnNetworkDespawn being invoked when a child NetworkBehaviour is destroyed. but the parent NetworkObject persists and remains spawned. * style MTT-4260 was created for this technical debt. removing reminder to create a ticket. * fix fixing merge issue. * fix Removing the invocation of NetworkBehaviour's OnNetworkDespawn when a NetworkBehaviour is destroyed while a NetworkObject is still spawned. * style removing unused namespace. * Update com.unity.netcode.gameobjects/CHANGELOG.md Co-authored-by: Jesse Olmer Co-authored-by: Jesse Olmer --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Core/NetworkBehaviour.cs | 12 ++++- .../Runtime/Core/NetworkObject.cs | 16 ++++++ .../Runtime/NetworkBehaviourGenericTests.cs | 49 +++++++++++++++++++ 4 files changed, 77 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 9862d1104a..9def797f94 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -16,6 +16,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed issue where a client owner of a `NetworkVariable` with both owner read and write permissions would not update the server side when changed. (#2097) - Fixed issue when attempting to spawn a parent `GameObject`, with `NetworkObject` component attached, that has one or more child `GameObject`s, that are inactive in the hierarchy, with `NetworkBehaviour` components it will no longer attempt to spawn the associated `NetworkBehaviour`(s) or invoke ownership changed notifications but will log a warning message. (#2096) +- Fixed an issue where destroying a NetworkBehaviour would not deregister it from the parent NetworkObject, leading to exceptions when the parent was later destroyed. (#2091) - Fixed issue where `NetworkObject.NetworkHide` was despawning and destroying, as opposed to only despawning, in-scene placed `NetworkObject`s. (#2086) - Fixed issue where `NetworkAnimator` would not synchronize a looping animation for late joining clients if it was at the very end of its loop. (#2076) - Fixed issue where `NetworkAnimator` was not removing its subscription from `OnClientConnectedCallback` when despawned during the shutdown sequence. (#2074) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index d2ec920252..37bb31ec42 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -331,7 +331,8 @@ public NetworkObject NetworkObject // in Update and/or in FixedUpdate could still be checking NetworkBehaviour.NetworkObject directly (i.e. does it exist?) // or NetworkBehaviour.IsSpawned (i.e. to early exit if not spawned) which, in turn, could generate several Warning messages // per spawned NetworkObject. Checking for ShutdownInProgress prevents these unnecessary LogWarning messages. - if (m_NetworkObject == null && (NetworkManager.Singleton == null || !NetworkManager.Singleton.ShutdownInProgress)) + // We must check IsSpawned, otherwise a warning will be logged under certain valid conditions (see OnDestroy) + if (IsSpawned && m_NetworkObject == null && (NetworkManager.Singleton == null || !NetworkManager.Singleton.ShutdownInProgress)) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { @@ -759,6 +760,14 @@ protected NetworkObject GetNetworkObject(ulong networkId) /// public virtual void OnDestroy() { + if (NetworkObject != null && NetworkObject.IsSpawned && IsSpawned) + { + // If the associated NetworkObject is still spawned then this + // NetworkBehaviour will be removed from the NetworkObject's + // ChildNetworkBehaviours list. + NetworkObject.OnNetworkBehaviourDestroyed(this); + } + // this seems odd to do here, but in fact especially in tests we can find ourselves // here without having called InitializedVariables, which causes problems if any // of those variables use native containers (e.g. NetworkList) as they won't be @@ -770,6 +779,7 @@ public virtual void OnDestroy() InitializeVariables(); } + for (int i = 0; i < NetworkVariableFields.Count; i++) { NetworkVariableFields[i].Dispose(); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 49416c00e4..cae10420c6 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1219,5 +1219,21 @@ internal uint HostCheckForGlobalObjectIdHashOverride() return GlobalObjectIdHash; } + + /// + /// Removes a NetworkBehaviour from the ChildNetworkBehaviours list when destroyed + /// while the NetworkObject is still spawned. + /// + internal void OnNetworkBehaviourDestroyed(NetworkBehaviour networkBehaviour) + { + if (networkBehaviour.IsSpawned && IsSpawned) + { + if (NetworkManager.LogLevel == LogLevel.Developer) + { + NetworkLog.LogWarning($"{nameof(NetworkBehaviour)}-{networkBehaviour.name} is being destroyed while {nameof(NetworkObject)}-{name} is still spawned! (could break state synchronization)"); + } + ChildNetworkBehaviours.Remove(networkBehaviour); + } + } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs index 785e0fed9d..c51527a591 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs @@ -22,6 +22,13 @@ protected override bool CanStartServerAndClients() public class SimpleNetworkBehaviour : NetworkBehaviour { + public bool OnNetworkDespawnCalled; + + public override void OnNetworkDespawn() + { + OnNetworkDespawnCalled = true; + base.OnNetworkDespawn(); + } } protected override IEnumerator OnSetup() @@ -83,6 +90,9 @@ public IEnumerator ValidateNoSpam() // set the log level to developer m_ServerNetworkManager.LogLevel = LogLevel.Developer; + // The only valid condition for this would be if the NetworkBehaviour is spawned. + simpleNetworkBehaviour.IsSpawned = true; + // Verify the warning gets logged under normal conditions var isNull = simpleNetworkBehaviour.NetworkObject == null; LogAssert.Expect(LogType.Warning, $"[Netcode] Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?"); @@ -98,5 +108,44 @@ public IEnumerator ValidateNoSpam() networkObjectToTest.Despawn(); Object.Destroy(networkObjectToTest); } + + /// + /// This validates the fix for when a child GameObject with a NetworkBehaviour + /// is deleted while the parent GameObject with a NetworkObject is spawned and + /// is not deleted until a later time would cause an exception due to the + /// NetworkBehaviour not being removed from the NetworkObject.ChildNetworkBehaviours + /// list. + /// + [UnityTest] + public IEnumerator ValidateDeleteChildNetworkBehaviour() + { + m_AllowServerToStart = true; + + yield return s_DefaultWaitForTick; + + // Now just start the Host + yield return StartServerAndClients(); + + var parentObject = new GameObject(); + var childObject = new GameObject(); + childObject.transform.parent = parentObject.transform; + var parentNetworkObject = parentObject.AddComponent(); + childObject.AddComponent(); + + parentNetworkObject.Spawn(); + yield return s_DefaultWaitForTick; + + // Destroy the child object with child NetworkBehaviour + Object.Destroy(childObject); + + yield return s_DefaultWaitForTick; + + // Assure no log messages are logged when they should not be logged + LogAssert.NoUnexpectedReceived(); + + // Destroy the parent object which should not cause any exceptions + // (validating the fix) + Object.Destroy(parentObject); + } } } From 6bfae1b1c3261ab4e082407b637ea1bb91b5a33a Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 16 Aug 2022 09:59:57 -0500 Subject: [PATCH 023/138] fix: MessageSendingTests causing memory leak during editor tests. (#2125) * fix Seeing if this fixes the issue with our memory leaks. * fix minor fix to my fix. Need to reconnect the client after creating the new MessagingSystem. adjusted a comment. * Update com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs Adding Fatih's LF/CR suggestion for readability Co-authored-by: Fatih Mar Co-authored-by: Fatih Mar --- .../Editor/Messaging/MessageSendingTests.cs | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs index 0cb09a48bb..5fce803b07 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Text.RegularExpressions; using NUnit.Framework; @@ -43,11 +44,24 @@ public void Send(ulong clientId, NetworkDelivery delivery, FastBufferWriter batc } } - private class TestMessageProvider : IMessageProvider + private class TestMessageProvider : IMessageProvider, IDisposable { + // Keep track of what we sent + private List> m_CachedMessages = new List>(); + + public void Dispose() + { + foreach (var cachedItem in m_CachedMessages) + { + // Clear out any references to MessagingSystem.MessageWithHandlers + cachedItem.Clear(); + } + m_CachedMessages.Clear(); + } + public List GetMessages() { - return new List + var messageList = new List { new MessagingSystem.MessageWithHandler { @@ -55,9 +69,13 @@ private class TestMessageProvider : IMessageProvider Handler = MessagingSystem.ReceiveMessage } }; + // Track messages sent + m_CachedMessages.Add(messageList); + return messageList; } } + private TestMessageProvider m_TestMessageProvider; private TestMessageSender m_MessageSender; private MessagingSystem m_MessagingSystem; private ulong[] m_Clients = { 0 }; @@ -66,15 +84,16 @@ private class TestMessageProvider : IMessageProvider public void SetUp() { TestMessage.Serialized = false; - m_MessageSender = new TestMessageSender(); - m_MessagingSystem = new MessagingSystem(m_MessageSender, this, new TestMessageProvider()); + m_TestMessageProvider = new TestMessageProvider(); + m_MessagingSystem = new MessagingSystem(m_MessageSender, this, m_TestMessageProvider); m_MessagingSystem.ClientConnected(0); } [TearDown] public void TearDown() { + m_TestMessageProvider.Dispose(); m_MessagingSystem.Dispose(); } @@ -246,7 +265,16 @@ private class TestNoHandlerMessageProvider : IMessageProvider [Test] public void WhenReceivingAMessageWithoutAHandler_ExceptionIsLogged() { + // If a MessagingSystem already exists then dispose of it before creating a new MessagingSystem (otherwise memory leak) + if (m_MessagingSystem != null) + { + m_MessagingSystem.Dispose(); + m_MessagingSystem = null; + } + + // Since m_MessagingSystem is disposed during teardown we don't need to worry about that here. m_MessagingSystem = new MessagingSystem(new NopMessageSender(), this, new TestNoHandlerMessageProvider()); + m_MessagingSystem.ClientConnected(0); var messageHeader = new MessageHeader { From 2bce9726e11bef5f45dc5f2097c275b6b5ca3b00 Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Tue, 16 Aug 2022 14:27:32 -0400 Subject: [PATCH 024/138] perf: improved runtime performance, dirty objects (#2116) * Performance fix for large number of NetworkObjects * docs: style: changelog and coding convention * fixes tests (but not NetworkList ones) * Fixes NetworkList functionality * disabling incompatible test * test adjustment, following improvement in NetworkVariable sending * Merging two loops to scan over objects one less time (performance). Re-enabling test, making it use SetDirty() properly * improved naming, fixed tests * addressing 'This seems like it should be handled by PostNetworkVariableWrite' PR review comment * refactoring common part to duplicated code paths into a single instance * limiting m_IsDirty visibility, to encourage using SetDirty() Co-authored-by: starchitectus --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Core/NetworkBehaviour.cs | 6 +- .../Runtime/Core/NetworkBehaviourUpdater.cs | 52 ++++++++--------- .../Runtime/Core/NetworkManager.cs | 7 ++- .../Runtime/Core/NetworkObject.cs | 4 +- .../Collections/NetworkList.cs | 12 ++++ .../NetworkVariable/NetworkVariable.cs | 4 +- .../NetworkVariable/NetworkVariableBase.cs | 6 +- .../Runtime/Spawning/NetworkSpawnManager.cs | 2 +- .../Editor/NetworkVar/NetworkVarTests.cs | 48 +++++++++------ .../NetworkVariableTestComponent.cs | 58 +++++++++---------- .../Metrics/NetworkVariableMetricsTests.cs | 15 +++-- .../Tests/Runtime/NetworkShowHideTests.cs | 2 +- .../Tests/Runtime/NetworkVarBufferCopyTest.cs | 15 +---- 14 files changed, 130 insertions(+), 102 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 9def797f94..7b332abc82 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -11,6 +11,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - When using `UnityTransport`, _reliable_ payloads are now allowed to exceed the configured 'Max Payload Size'. Unreliable payloads remain bounded by this setting. (#2081) +- Preformance improvements for cases with large number of NetworkObjects, by not iterating over all unchanged NetworkObjects ### Fixed diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 37bb31ec42..302ea168ee 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -583,6 +583,8 @@ internal void PostNetworkVariableWrite() { NetworkVariableFields[NetworkVariableIndexesToReset[i]].ResetDirty(); } + + MarkVariablesDirty(false); } internal void VariableUpdate(ulong targetClientId) @@ -664,11 +666,11 @@ private bool CouldHaveDirtyNetworkVariables() return false; } - internal void MarkVariablesDirty() + internal void MarkVariablesDirty(bool dirty) { for (int j = 0; j < NetworkVariableFields.Count; j++) { - NetworkVariableFields[j].SetDirty(true); + NetworkVariableFields[j].SetDirty(dirty); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs index 56bdfdca35..135cefb1ac 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs @@ -8,12 +8,17 @@ namespace Unity.Netcode ///
public class NetworkBehaviourUpdater { - private HashSet m_Touched = new HashSet(); + private HashSet m_DirtyNetworkObjects = new HashSet(); #if DEVELOPMENT_BUILD || UNITY_EDITOR private ProfilerMarker m_NetworkBehaviourUpdate = new ProfilerMarker($"{nameof(NetworkBehaviour)}.{nameof(NetworkBehaviourUpdate)}"); #endif + internal void AddForUpdate(NetworkObject networkObject) + { + m_DirtyNetworkObjects.Add(networkObject); + } + internal void NetworkBehaviourUpdate(NetworkManager networkManager) { #if DEVELOPMENT_BUILD || UNITY_EDITOR @@ -23,38 +28,31 @@ internal void NetworkBehaviourUpdate(NetworkManager networkManager) { if (networkManager.IsServer) { - m_Touched.Clear(); - for (int i = 0; i < networkManager.ConnectedClientsList.Count; i++) + foreach (var dirtyObj in m_DirtyNetworkObjects) { - var client = networkManager.ConnectedClientsList[i]; - var spawnedObjs = networkManager.SpawnManager.SpawnedObjectsList; - m_Touched.UnionWith(spawnedObjs); - foreach (var sobj in spawnedObjs) + for (int i = 0; i < networkManager.ConnectedClientsList.Count; i++) { - if (sobj.IsNetworkVisibleTo(client.ClientId)) + var client = networkManager.ConnectedClientsList[i]; + if (networkManager.IsHost && client.ClientId == networkManager.LocalClientId) + { + continue; + } + + if (dirtyObj.IsNetworkVisibleTo(client.ClientId)) { // Sync just the variables for just the objects this client sees - for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++) + for (int k = 0; k < dirtyObj.ChildNetworkBehaviours.Count; k++) { - sobj.ChildNetworkBehaviours[k].VariableUpdate(client.ClientId); + dirtyObj.ChildNetworkBehaviours[k].VariableUpdate(client.ClientId); } } } } - - // Now, reset all the no-longer-dirty variables - foreach (var sobj in m_Touched) - { - for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++) - { - sobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite(); - } - } } else { // when client updates the server, it tells it about all its objects - foreach (var sobj in networkManager.SpawnManager.SpawnedObjectsList) + foreach (var sobj in m_DirtyNetworkObjects) { if (sobj.IsOwner) { @@ -64,16 +62,16 @@ internal void NetworkBehaviourUpdate(NetworkManager networkManager) } } } - - // Now, reset all the no-longer-dirty variables - foreach (var sobj in networkManager.SpawnManager.SpawnedObjectsList) + } + // Now, reset all the no-longer-dirty variables + foreach (var dirtyobj in m_DirtyNetworkObjects) + { + for (int k = 0; k < dirtyobj.ChildNetworkBehaviours.Count; k++) { - for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++) - { - sobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite(); - } + dirtyobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite(); } } + m_DirtyNetworkObjects.Clear(); } finally { diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index b35b6ad315..28c35b65a1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -54,7 +54,12 @@ internal static string PrefabDebugHelper(NetworkPrefab networkPrefab) return $"{nameof(NetworkPrefab)} \"{networkPrefab.Prefab.gameObject.name}\""; } - internal NetworkBehaviourUpdater BehaviourUpdater { get; private set; } + internal NetworkBehaviourUpdater BehaviourUpdater { get; set; } + + internal void MarkNetworkObjectDirty(NetworkObject networkObject) + { + BehaviourUpdater.AddForUpdate(networkObject); + } internal MessagingSystem MessagingSystem { get; private set; } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index cae10420c6..0d889cb1e4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -868,12 +868,12 @@ internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClie } } - internal void MarkVariablesDirty() + internal void MarkVariablesDirty(bool dirty) { for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { var behavior = ChildNetworkBehaviours[i]; - behavior.MarkVariablesDirty(); + behavior.MarkVariablesDirty(dirty); } } diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index e8c939eb57..8f69b770f7 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -63,6 +63,11 @@ public override bool IsDirty() return base.IsDirty() || m_DirtyEvents.Length > 0; } + internal void MarkNetworkObjectDirty() + { + m_NetworkBehaviour.NetworkManager.MarkNetworkObjectDirty(m_NetworkBehaviour.NetworkObject); + } + /// public override void WriteDelta(FastBufferWriter writer) { @@ -189,6 +194,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) Index = m_List.Length - 1, Value = m_List[m_List.Length - 1] }); + MarkNetworkObjectDirty(); } } break; @@ -225,6 +231,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) Index = index, Value = m_List[index] }); + MarkNetworkObjectDirty(); } } break; @@ -257,6 +264,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) Index = index, Value = value }); + MarkNetworkObjectDirty(); } } break; @@ -284,6 +292,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) Index = index, Value = value }); + MarkNetworkObjectDirty(); } } break; @@ -319,6 +328,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) Value = value, PreviousValue = previousValue }); + MarkNetworkObjectDirty(); } } break; @@ -341,6 +351,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) { Type = eventType }); + MarkNetworkObjectDirty(); } } break; @@ -485,6 +496,7 @@ public T this[int index] private void HandleAddListEvent(NetworkListEvent listEvent) { m_DirtyEvents.Add(listEvent); + MarkNetworkObjectDirty(); OnListChanged?.Invoke(listEvent); } diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs index d32a80f127..698fe01c05 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs @@ -87,7 +87,7 @@ private static unsafe bool ValueEquals(ref T a, ref T b) /// the new value of type `T` to be set/> private protected void Set(T value) { - m_IsDirty = true; + SetDirty(true); T previousValue = m_InternalValue; m_InternalValue = value; OnValueChanged?.Invoke(previousValue, m_InternalValue); @@ -119,7 +119,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) if (keepDirtyDelta) { - m_IsDirty = true; + SetDirty(true); } OnValueChanged?.Invoke(previousValue, m_InternalValue); diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs index fa29bd1274..46b6ab84e6 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -54,7 +54,7 @@ protected NetworkVariableBase( /// The property is used to determine if the /// value of the `NetworkVariable` has changed. ///
- private protected bool m_IsDirty; + private bool m_IsDirty; /// /// Gets or sets the name of the network variable's instance @@ -79,6 +79,10 @@ protected NetworkVariableBase( public virtual void SetDirty(bool isDirty) { m_IsDirty = isDirty; + if (m_IsDirty) + { + m_NetworkBehaviour.NetworkManager.MarkNetworkObjectDirty(m_NetworkBehaviour.NetworkObject); + } } /// diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 0a241692a1..3300af9c50 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -576,7 +576,7 @@ internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientId); NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size); - networkObject.MarkVariablesDirty(); + networkObject.MarkVariablesDirty(true); } internal ulong? GetSpawnParentId(NetworkObject networkObject) diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkVar/NetworkVarTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/NetworkVar/NetworkVarTests.cs index 61bc1474d4..e871b23315 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/NetworkVar/NetworkVarTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/NetworkVar/NetworkVarTests.cs @@ -1,41 +1,55 @@ using NUnit.Framework; +using UnityEngine; namespace Unity.Netcode.EditorTests.NetworkVar { public class NetworkVarTests { + public class NetworkVarComponent : NetworkBehaviour + { + public NetworkVariable NetworkVariable = new NetworkVariable(); + } [Test] public void TestAssignmentUnchanged() { - var intVar = new NetworkVariable(); - - intVar.Value = 314159265; - - intVar.OnValueChanged += (value, newValue) => + var gameObjectMan = new GameObject(); + var networkManager = gameObjectMan.AddComponent(); + networkManager.BehaviourUpdater = new NetworkBehaviourUpdater(); + var gameObject = new GameObject(); + var networkObject = gameObject.AddComponent(); + networkObject.NetworkManagerOwner = networkManager; + var networkVarComponent = gameObject.AddComponent(); + networkVarComponent.NetworkVariable.Initialize(networkVarComponent); + networkVarComponent.NetworkVariable.Value = 314159265; + networkVarComponent.NetworkVariable.OnValueChanged += (value, newValue) => { Assert.Fail("OnValueChanged was invoked when setting the same value"); }; - - intVar.Value = 314159265; + networkVarComponent.NetworkVariable.Value = 314159265; + Object.DestroyImmediate(gameObject); + Object.DestroyImmediate(gameObjectMan); } - [Test] public void TestAssignmentChanged() { - var intVar = new NetworkVariable(); - - intVar.Value = 314159265; - + var gameObjectMan = new GameObject(); + var networkManager = gameObjectMan.AddComponent(); + networkManager.BehaviourUpdater = new NetworkBehaviourUpdater(); + var gameObject = new GameObject(); + var networkObject = gameObject.AddComponent(); + var networkVarComponent = gameObject.AddComponent(); + networkObject.NetworkManagerOwner = networkManager; + networkVarComponent.NetworkVariable.Initialize(networkVarComponent); + networkVarComponent.NetworkVariable.Value = 314159265; var changed = false; - - intVar.OnValueChanged += (value, newValue) => + networkVarComponent.NetworkVariable.OnValueChanged += (value, newValue) => { changed = true; }; - - intVar.Value = 314159266; - + networkVarComponent.NetworkVariable.Value = 314159266; Assert.True(changed); + Object.DestroyImmediate(gameObject); + Object.DestroyImmediate(gameObjectMan); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs index b887067633..6b24f18a73 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs @@ -53,7 +53,6 @@ internal class NetworkVariableTestComponent : NetworkBehaviour public bool EnableTesting; - private bool m_Initialized; private bool m_FinishedTests; private bool m_ChangesAppliedToNetworkVariables; @@ -148,6 +147,11 @@ public bool IsTestComplete() return m_FinishedTests; } + public void Awake() + { + InitializeTest(); + } + // Update is called once per frame private void Update() { @@ -164,37 +168,29 @@ private void Update() { if (NetworkManager != null && NetworkManager.IsListening) { - if (!m_Initialized) - { - InitializeTest(); - m_Initialized = true; - } - else - { - //Now change all of the values to make sure we are at least testing the local callback - m_NetworkVariableBool.Value = false; - m_NetworkVariableByte.Value = 255; - m_NetworkVariableColor.Value = new Color(100, 100, 100); - m_NetworkVariableColor32.Value = new Color32(100, 100, 100, 100); - m_NetworkVariableDouble.Value = 1000; - m_NetworkVariableFloat.Value = 1000.0f; - m_NetworkVariableInt.Value = 1000; - m_NetworkVariableLong.Value = 100000; - m_NetworkVariableSByte.Value = -127; - m_NetworkVariableQuaternion.Value = new Quaternion(100, 100, 100, 100); - m_NetworkVariableShort.Value = short.MaxValue; - m_NetworkVariableVector4.Value = new Vector4(1000, 1000, 1000, 1000); - m_NetworkVariableVector3.Value = new Vector3(1000, 1000, 1000); - m_NetworkVariableVector2.Value = new Vector2(1000, 1000); - m_NetworkVariableRay.Value = new Ray(Vector3.one, Vector3.right); - m_NetworkVariableULong.Value = ulong.MaxValue; - m_NetworkVariableUInt.Value = uint.MaxValue; - m_NetworkVariableUShort.Value = ushort.MaxValue; + //Now change all of the values to make sure we are at least testing the local callback + m_NetworkVariableBool.Value = false; + m_NetworkVariableByte.Value = 255; + m_NetworkVariableColor.Value = new Color(100, 100, 100); + m_NetworkVariableColor32.Value = new Color32(100, 100, 100, 100); + m_NetworkVariableDouble.Value = 1000; + m_NetworkVariableFloat.Value = 1000.0f; + m_NetworkVariableInt.Value = 1000; + m_NetworkVariableLong.Value = 100000; + m_NetworkVariableSByte.Value = -127; + m_NetworkVariableQuaternion.Value = new Quaternion(100, 100, 100, 100); + m_NetworkVariableShort.Value = short.MaxValue; + m_NetworkVariableVector4.Value = new Vector4(1000, 1000, 1000, 1000); + m_NetworkVariableVector3.Value = new Vector3(1000, 1000, 1000); + m_NetworkVariableVector2.Value = new Vector2(1000, 1000); + m_NetworkVariableRay.Value = new Ray(Vector3.one, Vector3.right); + m_NetworkVariableULong.Value = ulong.MaxValue; + m_NetworkVariableUInt.Value = uint.MaxValue; + m_NetworkVariableUShort.Value = ushort.MaxValue; - //Set the timeout (i.e. how long we will wait for all NetworkVariables to have registered their changes) - m_WaitForChangesTimeout = Time.realtimeSinceStartup + 0.50f; - m_ChangesAppliedToNetworkVariables = true; - } + //Set the timeout (i.e. how long we will wait for all NetworkVariables to have registered their changes) + m_WaitForChangesTimeout = Time.realtimeSinceStartup + 0.50f; + m_ChangesAppliedToNetworkVariables = true; } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/NetworkVariableMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/NetworkVariableMetricsTests.cs index 2ab85fa857..640cee4615 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/NetworkVariableMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/NetworkVariableMetricsTests.cs @@ -25,10 +25,17 @@ public IEnumerator TrackNetworkVariableDeltaSentMetric() var metricValues = waitForMetricValues.AssertMetricValuesHaveBeenFound(); - var networkVariableDeltaSent = metricValues.First(); - Assert.AreEqual(nameof(NetworkVariableComponent.MyNetworkVariable), networkVariableDeltaSent.Name); - Assert.AreEqual(Server.LocalClientId, networkVariableDeltaSent.Connection.Id); - Assert.AreNotEqual(0, networkVariableDeltaSent.BytesCount); + bool found = false; + foreach (var networkVariableDeltaSent in metricValues) + { + if (nameof(NetworkVariableComponent.MyNetworkVariable) == networkVariableDeltaSent.Name && + Client.LocalClientId == networkVariableDeltaSent.Connection.Id && + 0 != networkVariableDeltaSent.BytesCount) + { + found = true; + } + } + Assert.IsTrue(found); } [UnityTest] diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs index 15f9ec7fcd..bd95fa4f41 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs @@ -50,7 +50,7 @@ public override void OnNetworkDespawn() public NetworkVariable MyNetworkVariable; - private void Start() + private void Awake() { MyNetworkVariable = new NetworkVariable(); MyNetworkVariable.OnValueChanged += Changed; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVarBufferCopyTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVarBufferCopyTest.cs index b927cb19b7..228b2300d7 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVarBufferCopyTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVarBufferCopyTest.cs @@ -15,17 +15,6 @@ public class DummyNetVar : NetworkVariableBase public bool FieldWritten; public bool DeltaRead; public bool FieldRead; - public bool Dirty = false; - - public override void ResetDirty() - { - Dirty = false; - } - - public override bool IsDirty() - { - return Dirty; - } public override void WriteDelta(FastBufferWriter writer) { @@ -138,12 +127,12 @@ public IEnumerator TestEntireBufferIsCopiedOnNetworkVariableDelta() Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for client side DummyNetBehaviour to register it was spawned!"); // Check that FieldWritten is written when dirty - serverComponent.NetVar.Dirty = true; + serverComponent.NetVar.SetDirty(true); yield return s_DefaultWaitForTick; Assert.True(serverComponent.NetVar.FieldWritten); // Check that DeltaWritten is written when dirty - serverComponent.NetVar.Dirty = true; + serverComponent.NetVar.SetDirty(true); yield return s_DefaultWaitForTick; Assert.True(serverComponent.NetVar.DeltaWritten); From 65f289387c2a7bcd7b499a73bf21ba84b88925cf Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Wed, 17 Aug 2022 14:34:25 -0400 Subject: [PATCH 025/138] chore: Update UTP dependency to 1.2.0 (#2129) * chore: Update UTP dependency to 1.2.0 * Add PR number to CHANGELOG entry --- com.unity.netcode.gameobjects/CHANGELOG.md | 3 ++- com.unity.netcode.gameobjects/package.json | 2 +- testproject/Packages/manifest.json | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 7b332abc82..3536e97977 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -5,12 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). + ## [Unreleased] ### Changed +- Updated dependency on `com.unity.transport` to 1.2.0. (#2129) - When using `UnityTransport`, _reliable_ payloads are now allowed to exceed the configured 'Max Payload Size'. Unreliable payloads remain bounded by this setting. (#2081) - - Preformance improvements for cases with large number of NetworkObjects, by not iterating over all unchanged NetworkObjects ### Fixed diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index 29c3ea870f..ae7985362d 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -6,6 +6,6 @@ "unity": "2020.3", "dependencies": { "com.unity.nuget.mono-cecil": "1.10.1", - "com.unity.transport": "1.1.0" + "com.unity.transport": "1.2.0" } } \ No newline at end of file diff --git a/testproject/Packages/manifest.json b/testproject/Packages/manifest.json index efe9e50b10..ca669e2802 100644 --- a/testproject/Packages/manifest.json +++ b/testproject/Packages/manifest.json @@ -15,7 +15,6 @@ "com.unity.test-framework.performance": "2.8.0-preview", "com.unity.textmeshpro": "3.0.6", "com.unity.timeline": "1.6.2", - "com.unity.transport": "1.0.0-pre.4", "com.unity.ugui": "1.0.0", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", From 523b221543a8e6990b1a0783f53b5f4734c372f0 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 17 Aug 2022 17:00:45 -0500 Subject: [PATCH 026/138] fix: NetworkAnimator Synchronizing transition twice [MTT-3564] (#2084) * fix This fixes the issue where transitions were being both triggered and the change in animation state (i.e. transition state) was being synchronized and applied to clients already transitioning which would cause any StateMachineBehaviours to have their OnStateEnter method invoked twice. * fix Making adjustments to account for changes to the Transition state update vs trigger update fix. * test Adding the additional StateMachineBehaviour (CheckStateEnter) to validate this fix. Adjusting two tests for this fix. Includes the animation related assets needed for the test. * style adding additional comments for some changes that were not obvious to the fix. * update removing code used to debug * style MTT-3564 updating changelog. * style correcting grammar in comment * style slight adjustment to the comment * style * update removing more debug related code. * style minor integration test variable rename. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Components/NetworkAnimator.cs | 39 +++++-- .../CheckStateEnterCount.cs | 101 ++++++++++++++++++ .../CheckStateEnterCount.cs.meta | 11 ++ .../CubeAnimatorController.controller | 26 +++++ .../NetworkAnimatorTestPrefab.prefab | 4 +- .../Runtime/Animation/NetworkAnimatorTests.cs | 60 +++++++---- 7 files changed, 210 insertions(+), 32 deletions(-) create mode 100644 testproject/Assets/Tests/Manual/NetworkAnimatorTests/CheckStateEnterCount.cs create mode 100644 testproject/Assets/Tests/Manual/NetworkAnimatorTests/CheckStateEnterCount.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 3536e97977..64136bcbf4 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -20,6 +20,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed issue when attempting to spawn a parent `GameObject`, with `NetworkObject` component attached, that has one or more child `GameObject`s, that are inactive in the hierarchy, with `NetworkBehaviour` components it will no longer attempt to spawn the associated `NetworkBehaviour`(s) or invoke ownership changed notifications but will log a warning message. (#2096) - Fixed an issue where destroying a NetworkBehaviour would not deregister it from the parent NetworkObject, leading to exceptions when the parent was later destroyed. (#2091) - Fixed issue where `NetworkObject.NetworkHide` was despawning and destroying, as opposed to only despawning, in-scene placed `NetworkObject`s. (#2086) +- Fixed `NetworkAnimator` synchronizing transitions twice due to it detecting the change in animation state once a transition is started by a trigger. (#2084) - Fixed issue where `NetworkAnimator` would not synchronize a looping animation for late joining clients if it was at the very end of its loop. (#2076) - Fixed issue where `NetworkAnimator` was not removing its subscription from `OnClientConnectedCallback` when despawned during the shutdown sequence. (#2074) - Fixed IsServer and IsClient being set to false before object despawn during the shutdown sequence. (#2074) diff --git a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs index 5e8179bf70..41ab79d855 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs @@ -163,6 +163,7 @@ public class NetworkAnimator : NetworkBehaviour internal struct AnimationMessage : INetworkSerializable { // state hash per layer. if non-zero, then Play() this animation, skipping transitions + internal bool Transition; internal int StateHash; internal float NormalizedTime; internal int Layer; @@ -427,6 +428,7 @@ internal void ServerSynchronizeNewPlayer(ulong playerId) var animMsg = new AnimationMessage { + Transition = m_Animator.IsInTransition(layer), StateHash = stateHash, NormalizedTime = normalizedTime, Layer = layer, @@ -442,6 +444,9 @@ private void OnClientConnectedCallback(ulong playerId) m_NetworkAnimatorStateChangeHandler.SynchronizeClient(playerId); } + /// + /// Checks for changes in both Animator parameters and state. + /// internal void CheckForAnimatorChanges() { if (!IsOwner && !IsServerAuthoritative() || IsServerAuthoritative() && !IsServer) @@ -482,6 +487,7 @@ internal void CheckForAnimatorChanges() var animMsg = new AnimationMessage { + Transition = m_Animator.IsInTransition(layer), StateHash = stateHash, NormalizedTime = normalizedTime, Layer = layer, @@ -744,7 +750,13 @@ internal unsafe void UpdateParameters(ParametersUpdateMessage parametersUpdate) /// private unsafe void UpdateAnimationState(AnimationMessage animationState) { - if (animationState.StateHash != 0) + if (animationState.StateHash == 0) + { + return; + } + + var currentState = m_Animator.GetCurrentAnimatorStateInfo(animationState.Layer); + if (currentState.fullPathHash != animationState.StateHash || m_Animator.IsInTransition(animationState.Layer) != animationState.Transition) { m_Animator.Play(animationState.StateHash, animationState.Layer, animationState.NormalizedTime); } @@ -830,6 +842,10 @@ private unsafe void SendAnimStateServerRpc(AnimationMessage animSnapshot, Server [ClientRpc] private unsafe void SendAnimStateClientRpc(AnimationMessage animSnapshot, ClientRpcParams clientRpcParams = default) { + if (IsServer) + { + return; + } var isServerAuthoritative = IsServerAuthoritative(); if (!isServerAuthoritative && !IsOwner || isServerAuthoritative) { @@ -879,11 +895,7 @@ private void SendAnimTriggerServerRpc(AnimationTriggerMessage animationTriggerMe [ClientRpc] internal void SendAnimTriggerClientRpc(AnimationTriggerMessage animationTriggerMessage, ClientRpcParams clientRpcParams = default) { - var isServerAuthoritative = IsServerAuthoritative(); - if (!isServerAuthoritative && !IsOwner || isServerAuthoritative) - { - m_Animator.SetBool(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet); - } + m_Animator.SetBool(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet); } /// @@ -900,8 +912,13 @@ public void SetTrigger(string triggerName) /// sets (true) or resets (false) the trigger. The default is to set it (true). public void SetTrigger(int hash, bool setTrigger = true) { - var isServerAuthoritative = IsServerAuthoritative(); - if (IsOwner && !isServerAuthoritative || IsServer && isServerAuthoritative) + // MTT-3564: + // After fixing the issue with trigger controlled Transitions being synchronized twice, + // it exposed additional issues with this logic. Now, either the owner or the server can + // update triggers. Since server-side RPCs are immediately invoked, for a host a trigger + // will happen when SendAnimTriggerClientRpc is called. For a client owner, we call the + // SendAnimTriggerServerRpc and then trigger locally when running in owner authority mode. + if (IsOwner || IsServer) { var animTriggerMessage = new AnimationTriggerMessage() { Hash = hash, IsTriggerSet = setTrigger }; if (IsServer) @@ -911,9 +928,11 @@ public void SetTrigger(int hash, bool setTrigger = true) else { SendAnimTriggerServerRpc(animTriggerMessage); + if (!IsServerAuthoritative()) + { + m_Animator.SetTrigger(hash); + } } - // trigger the animation locally on the server... - m_Animator.SetBool(hash, setTrigger); } } diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CheckStateEnterCount.cs b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CheckStateEnterCount.cs new file mode 100644 index 0000000000..a95842402e --- /dev/null +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CheckStateEnterCount.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; +using UnityEngine; +using Unity.Netcode; + +namespace TestProject.RuntimeTests +{ + public class CheckStateEnterCount : StateMachineBehaviour + { + public static Dictionary>> OnStateEnterCounter = new Dictionary>>(); + public static bool IsIntegrationTest; + public static bool IsManualTestEnabled = true; + + public static void ResetTest(bool isIntegrationTest = true) + { + IsIntegrationTest = isIntegrationTest; + IsManualTestEnabled = !isIntegrationTest; + OnStateEnterCounter.Clear(); + } + + public static bool AllStatesEnteredMatch(List clientIdsToCheck) + { + if (clientIdsToCheck.Contains(NetworkManager.ServerClientId)) + { + clientIdsToCheck.Remove(NetworkManager.ServerClientId); + } + + if (!OnStateEnterCounter.ContainsKey(NetworkManager.ServerClientId)) + { + Debug.Log($"Server has not entered into any states! OnStateEntered Entry Count ({OnStateEnterCounter.Count})"); + return false; + } + + var serverStates = OnStateEnterCounter[NetworkManager.ServerClientId]; + + foreach (var layerEntries in serverStates) + { + var layerIndex = layerEntries.Key; + var layerStates = layerEntries.Value; + if (layerStates.Count > 1) + { + Debug.Log($"Server layer ({layerIndex}) state was entered ({layerStates.Count}) times!"); + return false; + } + + foreach (var clientId in clientIdsToCheck) + { + if (!OnStateEnterCounter.ContainsKey(clientId)) + { + Debug.Log($"Client-{clientId} never entered into any state for layer index ({layerIndex})!"); + return false; + } + var clientStates = OnStateEnterCounter[clientId]; + if (!clientStates.ContainsKey(layerIndex)) + { + Debug.Log($"Client-{clientId} never layer ({layerIndex}) state!"); + return false; + } + var clientLayerStateEntries = clientStates[layerIndex]; + if (clientLayerStateEntries.Count > 1) + { + Debug.Log($"Client-{clientId} layer ({layerIndex}) state was entered ({layerStates.Count}) times!"); + return false; + } + // We should have only entered into the state once on the server + // and all connected clients + var serverAnimStateInfo = layerStates[0]; + var clientAnimStateInfo = clientLayerStateEntries[0]; + // We just need to make sure we are looking at the same state + if (clientAnimStateInfo.fullPathHash != serverAnimStateInfo.fullPathHash) + { + Debug.Log($"Client-{clientId} full path hash ({clientAnimStateInfo.fullPathHash}) for layer ({layerIndex}) was not the same as the Server full path hash ({serverAnimStateInfo.fullPathHash})!"); + return false; + } + } + } + return true; + } + + public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) + { + if (IsIntegrationTest) + { + var networkObject = animator.GetComponent(); + var localClientId = networkObject.NetworkManager.IsServer ? NetworkManager.ServerClientId : networkObject.NetworkManager.LocalClientId; + if (!OnStateEnterCounter.ContainsKey(localClientId)) + { + OnStateEnterCounter.Add(localClientId, new Dictionary>()); + } + if (!OnStateEnterCounter[localClientId].ContainsKey(layerIndex)) + { + OnStateEnterCounter[localClientId].Add(layerIndex, new List()); + } + OnStateEnterCounter[localClientId][layerIndex].Add(stateInfo); + } + else if (IsManualTestEnabled) + { + Debug.Log($"[{layerIndex}][{stateInfo.shortNameHash}][{stateInfo.normalizedTime}][{animator.IsInTransition(layerIndex)}]"); + } + } + } +} diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CheckStateEnterCount.cs.meta b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CheckStateEnterCount.cs.meta new file mode 100644 index 0000000000..bd0738029e --- /dev/null +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CheckStateEnterCount.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: beb3702bd4c8d274e9dffe0c6467eafb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CubeAnimatorController.controller b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CubeAnimatorController.controller index d01348ed86..af3d9502eb 100644 --- a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CubeAnimatorController.controller +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CubeAnimatorController.controller @@ -337,6 +337,18 @@ AnimatorState: m_MirrorParameter: m_CycleOffsetParameter: m_TimeParameter: +--- !u!114 &-268161786856038416 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: beb3702bd4c8d274e9dffe0c6467eafb, type: 3} + m_Name: + m_EditorClassIdentifier: --- !u!1107 &-108110249979471251 AnimatorStateMachine: serializedVersion: 6 @@ -453,6 +465,18 @@ AnimatorController: m_IKPass: 0 m_SyncedLayerAffectsTiming: 0 m_Controller: {fileID: 9100000} +--- !u!114 &252787195921886346 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: beb3702bd4c8d274e9dffe0c6467eafb, type: 3} + m_Name: + m_EditorClassIdentifier: --- !u!1101 &1138737138882309440 AnimatorStateTransition: m_ObjectHideFlags: 1 @@ -561,6 +585,7 @@ AnimatorState: - {fileID: 1138737138882309440} m_StateMachineBehaviours: - {fileID: 5559889042091692034} + - {fileID: -268161786856038416} m_Position: {x: 50, y: 50, z: 0} m_IKOnFeet: 0 m_WriteDefaultValues: 1 @@ -625,6 +650,7 @@ AnimatorState: - {fileID: 1830534497079063084} m_StateMachineBehaviours: - {fileID: 3735381422868909868} + - {fileID: 252787195921886346} m_Position: {x: 50, y: 50, z: 0} m_IKOnFeet: 0 m_WriteDefaultValues: 0 diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorTestPrefab.prefab b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorTestPrefab.prefab index 4686709f30..c1b99a54f2 100644 --- a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorTestPrefab.prefab +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorTestPrefab.prefab @@ -578,7 +578,7 @@ GameObject: - component: {fileID: 3604323723684300772} - component: {fileID: 6047057957339659638} - component: {fileID: 8104648428997222210} - - component: {fileID: 5157980021080854601} + - component: {fileID: 1245349943772079751} m_Layer: 0 m_Name: NetworkAnimatedCube-Server m_TagString: Untagged @@ -635,7 +635,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: TestIterations: 20 ---- !u!114 &5157980021080854601 +--- !u!114 &1245349943772079751 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} diff --git a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs index 878f7d78f1..4e28df0472 100644 --- a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs +++ b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using UnityEngine; @@ -43,6 +44,7 @@ protected override void OnOneTimeSetup() protected override IEnumerator OnSetup() { AnimatorTestHelper.Initialize(); + CheckStateEnterCount.ResetTest(); TriggerTest.ResetTest(); StateSyncTest.ResetTest(); yield return base.OnSetup(); @@ -229,6 +231,7 @@ private bool WaitForClientsToInitialize() [UnityTest] public IEnumerator TriggerUpdateTests([Values] OwnerShipMode ownerShipMode, [Values] AuthoritativeMode authoritativeMode) { + CheckStateEnterCount.ResetTest(); VerboseDebug($" ++++++++++++++++++ Trigger Test [{TriggerTest.Iteration}][{ownerShipMode}] Starting ++++++++++++++++++ "); TriggerTest.IsVerboseDebug = m_EnableVerboseDebug; AnimatorTestHelper.IsTriggerTest = m_EnableVerboseDebug; @@ -278,6 +281,18 @@ public IEnumerator TriggerUpdateTests([Values] OwnerShipMode ownerShipMode, [Val AssertOnTimeout($"Timed out waiting for all triggers to match!"); } + yield return s_DefaultWaitForTick; + + var clientIdList = new List(); + foreach (var client in m_ClientNetworkManagers) + { + clientIdList.Add(client.LocalClientId); + } + + // Verify we only entered each state once + yield return WaitForConditionOrTimeOut(() => CheckStateEnterCount.AllStatesEnteredMatch(clientIdList)); + AssertOnTimeout($"Timed out waiting for all states entered to match!"); + AnimatorTestHelper.IsTriggerTest = false; VerboseDebug($" ------------------ Trigger Test [{TriggerTest.Iteration}][{ownerShipMode}] Stopping ------------------ "); } @@ -464,13 +479,14 @@ private void Server_OnClientConnectedCallback(ulong obj) { m_ServerNetworkManager.OnClientConnectedCallback -= Server_OnClientConnectedCallback; var serverAnimator = AnimatorTestHelper.ServerSideInstance.GetAnimator(); - for (int i = 0; i < serverAnimator.layerCount; i++) - { - Assert.True(StateSyncTest.StatesEntered.ContainsKey(m_ServerNetworkManager.LocalClientId), $"Server does not have an entry for layer {i}!"); - var animationStateInfo = serverAnimator.GetCurrentAnimatorStateInfo(i); - StateSyncTest.StatesEntered[m_ServerNetworkManager.LocalClientId][i] = animationStateInfo; - VerboseDebug($"[{i}][STATE-REFRESH][{m_ServerNetworkManager.name}] updated state normalized time ({animationStateInfo.normalizedTime}) to compare with late joined client."); - } + + // Only update the 3rd layer since this is where we want to assure all values are synchronized to the + // same values upon the client connecting. + var index = 2; + Assert.True(StateSyncTest.StatesEntered.ContainsKey(m_ServerNetworkManager.LocalClientId), $"Server does not have an entry for layer {index}!"); + var animationStateInfo = serverAnimator.GetCurrentAnimatorStateInfo(index); + StateSyncTest.StatesEntered[m_ServerNetworkManager.LocalClientId][index] = animationStateInfo; + VerboseDebug($"[{index}][STATE-REFRESH][{m_ServerNetworkManager.name}] updated state normalized time ({animationStateInfo.normalizedTime}) to compare with late joined client."); } /// @@ -482,6 +498,7 @@ private bool LateJoinClientSynchronized() { if (!StateSyncTest.StatesEntered.ContainsKey(m_ClientNetworkManagers[1].LocalClientId)) { + VerboseDebug($"Late join client has not had any states synchronized yet!"); return false; } @@ -494,21 +511,24 @@ private bool LateJoinClientSynchronized() return false; } - // Basically a sanity check to make sure they all match - for (int i = 0; i < serverStates.Count; i++) + // We only check the last layer for this test as the other layers will have their normalized time slightly out of sync + var index = 2; + var serverAnimState = serverStates[index]; + if (clientStates[index].shortNameHash != serverAnimState.shortNameHash) { - var serverAnimState = serverStates[i]; - if (clientStates[i].shortNameHash != serverAnimState.shortNameHash) - { - VerboseDebug($"[Hash Fail] Server({serverAnimState.shortNameHash}) | Client({clientStates[i].shortNameHash}) "); - return false; - } - if (clientStates[i].normalizedTime != serverAnimState.normalizedTime) - { - VerboseDebug($"[NormalizedTime Fail] Server({serverAnimState.normalizedTime}) | Client({clientStates[i].normalizedTime})"); - return false; - } + VerboseDebug($"[Hash Fail] Server({serverAnimState.shortNameHash}) | Client({clientStates[index].shortNameHash}) "); + return false; } + + var clientNormalizedTime = clientStates[index].normalizedTime; + var serverNormalizedTime = serverAnimState.normalizedTime; + if (!Mathf.Approximately(clientNormalizedTime, serverNormalizedTime)) + { + VerboseDebug($"[NormalizedTime Fail][{index}][{serverStates.Count}:{clientStates.Count}] Server({serverNormalizedTime}) | Client-{m_ClientNetworkManagers[1].LocalClientId}({clientNormalizedTime})"); + return false; + } + VerboseDebug($"[NormalizedTime][{index}][{serverStates.Count}:{clientStates.Count}] Server({serverNormalizedTime}) | Client-{m_ClientNetworkManagers[1].LocalClientId}({clientNormalizedTime})"); + return true; } From cd8bbd3be1419a87db3b7eeb97ab4c3e1837ffdc Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Wed, 17 Aug 2022 18:34:11 -0400 Subject: [PATCH 027/138] fix: Fixed Owner-written NetworkVariable infinitely write themselves (#2128) * fix: Fixed Owner-written NetworkVariable infinitely write themselves (#2109) Fixed by doing the pre-write as a separate step * nit PR review :-) * Adding test --- com.unity.netcode.gameobjects/CHANGELOG.md | 5 +- .../Runtime/Core/NetworkBehaviour.cs | 6 +- .../Runtime/Core/NetworkBehaviourUpdater.cs | 9 ++ .../Tests/Runtime/OwnerModifiedTests.cs | 103 ++++++++++++++++++ .../Tests/Runtime/OwnerModifiedTests.cs.meta | 11 ++ 5 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/OwnerModifiedTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/OwnerModifiedTests.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 64136bcbf4..8096f17299 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -16,6 +16,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed Owner-written NetworkVariable infinitely write themselves (#2109) +- Fixed NetworkList issue that showed when inserting at the very end of a NetworkList (#2099) - Fixed issue where a client owner of a `NetworkVariable` with both owner read and write permissions would not update the server side when changed. (#2097) - Fixed issue when attempting to spawn a parent `GameObject`, with `NetworkObject` component attached, that has one or more child `GameObject`s, that are inactive in the hierarchy, with `NetworkBehaviour` components it will no longer attempt to spawn the associated `NetworkBehaviour`(s) or invoke ownership changed notifications but will log a warning message. (#2096) - Fixed an issue where destroying a NetworkBehaviour would not deregister it from the parent NetworkObject, leading to exceptions when the parent was later destroyed. (#2091) @@ -24,9 +26,8 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed issue where `NetworkAnimator` would not synchronize a looping animation for late joining clients if it was at the very end of its loop. (#2076) - Fixed issue where `NetworkAnimator` was not removing its subscription from `OnClientConnectedCallback` when despawned during the shutdown sequence. (#2074) - Fixed IsServer and IsClient being set to false before object despawn during the shutdown sequence. (#2074) -- Fixed NetworkLists not populating on client. NetworkList now uses the most recent list as opposed to the list at the end of previous frame, when sending full updates to dynamically spawned NetworkObject. The difference in behaviour is required as scene management spawns those objects at a different time in the frame, relative to updates. (#2062) - Fixed NetworkList Value event on the server. PreviousValue is now set correctly when a new value is set through property setter. (#2067) -- Fixed NetworkList issue that showed when inserting at the very end of a NetworkList (#2099) +- Fixed NetworkLists not populating on client. NetworkList now uses the most recent list as opposed to the list at the end of previous frame, when sending full updates to dynamically spawned NetworkObject. The difference in behaviour is required as scene management spawns those objects at a different time in the frame, relative to updates. (#2062) ## [1.0.0] - 2022-06-27 diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 302ea168ee..6f96f03697 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -587,7 +587,7 @@ internal void PostNetworkVariableWrite() MarkVariablesDirty(false); } - internal void VariableUpdate(ulong targetClientId) + internal void PreVariableUpdate() { if (!m_VarInit) { @@ -595,6 +595,10 @@ internal void VariableUpdate(ulong targetClientId) } PreNetworkVariableWrite(); + } + + internal void VariableUpdate(ulong targetClientId) + { NetworkVariableUpdate(targetClientId, NetworkBehaviourId); } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs index 135cefb1ac..7e9176efbf 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs @@ -30,6 +30,11 @@ internal void NetworkBehaviourUpdate(NetworkManager networkManager) { foreach (var dirtyObj in m_DirtyNetworkObjects) { + for (int k = 0; k < dirtyObj.ChildNetworkBehaviours.Count; k++) + { + dirtyObj.ChildNetworkBehaviours[k].PreVariableUpdate(); + } + for (int i = 0; i < networkManager.ConnectedClientsList.Count; i++) { var client = networkManager.ConnectedClientsList[i]; @@ -56,6 +61,10 @@ internal void NetworkBehaviourUpdate(NetworkManager networkManager) { if (sobj.IsOwner) { + for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++) + { + sobj.ChildNetworkBehaviours[k].PreVariableUpdate(); + } for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++) { sobj.ChildNetworkBehaviours[k].VariableUpdate(NetworkManager.ServerClientId); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/OwnerModifiedTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/OwnerModifiedTests.cs new file mode 100644 index 0000000000..52ddb2b575 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/OwnerModifiedTests.cs @@ -0,0 +1,103 @@ +using System.Collections; +using System.Collections.Generic; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; + +namespace Unity.Netcode.RuntimeTests +{ + // This is a bit of a quirky test. + // Addresses MTT-4386 #2109 + // Where the NetworkVariable updates would be repeated on some clients. + // The twist comes fom the updates needing to happens very specifically for the issue to repro in tests + + public class OwnerModifiedObject : NetworkBehaviour, INetworkUpdateSystem + { + public NetworkList MyNetworkList; + + static internal int Updates = 0; + + private void Awake() + { + MyNetworkList = new NetworkList(new List(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); + MyNetworkList.OnListChanged += Changed; + } + + public void Changed(NetworkListEvent listEvent) + { + var expected = 0; + var listString = ""; + foreach (var i in MyNetworkList) + { + Assert.AreEqual(i, expected); + expected++; + listString += i.ToString(); + } + Debug.Log($"[{NetworkManager.LocalClientId}] Value changed to {listString}"); + Updates++; + } + + public bool AddValues; + + public NetworkUpdateStage NetworkUpdateStageToCheck; + + private int m_ValueToUpdate; + + public void NetworkUpdate(NetworkUpdateStage updateStage) + { + if (updateStage == NetworkUpdateStageToCheck) + { + if (AddValues) + { + MyNetworkList.Add(m_ValueToUpdate++); + AddValues = false; + } + } + } + + public override void OnDestroy() + { + NetworkUpdateLoop.UnregisterAllNetworkUpdates(this); + base.OnDestroy(); + } + + public void InitializeLastCient() + { + NetworkUpdateLoop.RegisterAllNetworkUpdates(this); + } + } + + public class OwnerModifiedTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 2; + + protected override void OnCreatePlayerPrefab() + { + m_PlayerPrefab.AddComponent(); + } + + [UnityTest] + public IEnumerator OwnerModifiedTest() + { + // We use this to assure we are the "last client" connected. + yield return CreateAndStartNewClient(); + var ownerModLastClient = m_ClientNetworkManagers[2].LocalClient.PlayerObject.GetComponent(); + ownerModLastClient.InitializeLastCient(); + + // Run through all update loops setting the value once every 5 frames + foreach (var updateLoopType in System.Enum.GetValues(typeof(NetworkUpdateStage))) + { + ownerModLastClient.NetworkUpdateStageToCheck = (NetworkUpdateStage)updateLoopType; + Debug.Log($"Testing Update Stage: {ownerModLastClient.NetworkUpdateStageToCheck}"); + ownerModLastClient.AddValues = true; + yield return NetcodeIntegrationTestHelpers.WaitForTicks(m_ServerNetworkManager, 5); + } + + yield return NetcodeIntegrationTestHelpers.WaitForTicks(m_ServerNetworkManager, 5); + + // We'll have at least one update per stage per client, if all goes well. + Assert.True(OwnerModifiedObject.Updates > 20); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/OwnerModifiedTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/OwnerModifiedTests.cs.meta new file mode 100644 index 0000000000..700bc202e6 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/OwnerModifiedTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 419d83ebac7544ea9b0a9d5c3eab2c71 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From fd7dc407433d815528996b0c497670bb92a2e56e Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Wed, 17 Aug 2022 19:03:18 -0400 Subject: [PATCH 028/138] fix: Don't set the maximum frame time of UTP (#2088) --- .../Runtime/Transports/UTP/UnityTransport.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index 0a4bbf1702..5a06191c36 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -1325,10 +1325,8 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, #if MULTIPLAYER_TOOLS_1_0_0_PRE_7 NetworkPipelineStageCollection.RegisterPipelineStage(new NetworkMetricsPipelineStage()); #endif - var maxFrameTimeMS = 0; #if UNITY_EDITOR || DEVELOPMENT_BUILD - maxFrameTimeMS = 100; ConfigureSimulator(); #endif @@ -1336,8 +1334,7 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, maxConnectAttempts: transport.m_MaxConnectAttempts, connectTimeoutMS: transport.m_ConnectTimeoutMS, disconnectTimeoutMS: transport.m_DisconnectTimeoutMS, - heartbeatTimeoutMS: transport.m_HeartbeatTimeoutMS, - maxFrameTimeMS: maxFrameTimeMS); + heartbeatTimeoutMS: transport.m_HeartbeatTimeoutMS); driver = NetworkDriver.Create(m_NetworkSettings); From ea019dffb3d3bb039ee01a1483a270613c312314 Mon Sep 17 00:00:00 2001 From: Kitty Draper <284434+ShadauxCat@users.noreply.github.com> Date: Wed, 17 Aug 2022 18:47:52 -0500 Subject: [PATCH 029/138] fix: NCCBUG-175: BitReader/BitWriter Reading/writing more than 8 bits was producing the wrong result (#2130) * fix: NCCBUG-175: BitReader/BitWriter Reading/writing more than 8 bits was producing the wrong result * Standards + changelog Co-authored-by: Noel Stephens --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 + .../Runtime/Serialization/BitReader.cs | 11 +-- .../Runtime/Serialization/BitWriter.cs | 6 +- ...serBitReaderAndBitWriterTests_NCCBUG175.cs | 95 +++++++++++++++++++ ...tReaderAndBitWriterTests_NCCBUG175.cs.meta | 3 + dotnet-tools/netcode.standards/Program.cs | 8 +- 6 files changed, 112 insertions(+), 13 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Editor/Serialization/UserBitReaderAndBitWriterTests_NCCBUG175.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Editor/Serialization/UserBitReaderAndBitWriterTests_NCCBUG175.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 8096f17299..9c78b5065f 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -1,3 +1,4 @@ + # Changelog All notable changes to this project will be documented in this file. @@ -16,6 +17,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed an issue where reading/writing more than 8 bits at a time with BitReader/BitWriter would write/read from the wrong place, returning and incorrect result. (#2130) - Fixed Owner-written NetworkVariable infinitely write themselves (#2109) - Fixed NetworkList issue that showed when inserting at the very end of a NetworkList (#2099) - Fixed issue where a client owner of a `NetworkVariable` with both owner read and write permissions would not update the server side when changed. (#2097) diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BitReader.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BitReader.cs index 313e0c4cfb..dcb30a92dd 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/BitReader.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BitReader.cs @@ -21,6 +21,8 @@ public ref struct BitReader private const int k_BitsPerByte = 8; + private int BytePosition => m_BitPosition >> 3; + /// /// Whether or not the current BitPosition is evenly divisible by 8. I.e. whether or not the BitPosition is at a byte boundary. /// @@ -98,11 +100,6 @@ public unsafe void ReadBits(out ulong value, uint bitCount) throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read more than 64 bits from a 64-bit value!"); } - if (bitCount < 0) - { - throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read fewer than 0 bits!"); - } - int checkPos = (int)(m_BitPosition + bitCount); if (checkPos > m_AllowedBitwiseReadMark) { @@ -165,7 +162,7 @@ public unsafe void ReadBit(out bool bit) #endif int offset = m_BitPosition & 7; - int pos = m_BitPosition >> 3; + int pos = BytePosition; bit = (m_BufferPointer[pos] & (1 << offset)) != 0; ++m_BitPosition; } @@ -175,7 +172,7 @@ private unsafe void ReadPartialValue(out T value, int bytesToRead, int offset { var val = new T(); byte* ptr = ((byte*)&val) + offsetBytes; - byte* bufferPointer = m_BufferPointer + m_Position; + byte* bufferPointer = m_BufferPointer + BytePosition; UnsafeUtility.MemCpy(ptr, bufferPointer, bytesToRead); m_BitPosition += bytesToRead * k_BitsPerByte; diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs index 842522a1b9..9eacd5601f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs @@ -29,6 +29,8 @@ public bool BitAligned get => (m_BitPosition & 7) == 0; } + private int BytePosition => m_BitPosition >> 3; + internal unsafe BitWriter(FastBufferWriter writer) { m_Writer = writer; @@ -181,7 +183,7 @@ public unsafe void WriteBit(bool bit) #endif int offset = m_BitPosition & 7; - int pos = m_BitPosition >> 3; + int pos = BytePosition; ++m_BitPosition; m_BufferPointer[pos] = (byte)(bit ? (m_BufferPointer[pos] & ~(1 << offset)) | (1 << offset) : (m_BufferPointer[pos] & ~(1 << offset))); } @@ -190,7 +192,7 @@ public unsafe void WriteBit(bool bit) private unsafe void WritePartialValue(T value, int bytesToWrite, int offsetBytes = 0) where T : unmanaged { byte* ptr = ((byte*)&value) + offsetBytes; - byte* bufferPointer = m_BufferPointer + m_Position; + byte* bufferPointer = m_BufferPointer + BytePosition; UnsafeUtility.MemCpy(bufferPointer, ptr, bytesToWrite); m_BitPosition += bytesToWrite * k_BitsPerByte; diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/UserBitReaderAndBitWriterTests_NCCBUG175.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/UserBitReaderAndBitWriterTests_NCCBUG175.cs new file mode 100644 index 0000000000..5db3db1639 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/UserBitReaderAndBitWriterTests_NCCBUG175.cs @@ -0,0 +1,95 @@ +using NUnit.Framework; +using Unity.Collections; + +namespace Unity.Netcode.EditorTests +{ + public class UserBitReaderAndBitWriterTests_NCCBUG175 + { + + [Test] + public void WhenBitwiseWritingMoreThan8Bits_ValuesAreCorrect() + { + using var writer = new FastBufferWriter(1024, Allocator.Temp); + ulong inVal = 123456789; + + for (int i = 0; i < 100; ++i) + { + writer.WriteValueSafe(i); + } + + using (var bitWriter = writer.EnterBitwiseContext()) + { + for (int i = 0; i < 16; ++i) + { + Assert.IsTrue((bitWriter.TryBeginWriteBits(32))); + bitWriter.WriteBits(inVal, 31); + bitWriter.WriteBit(true); + } + } + + using var reader = new FastBufferReader(writer, Allocator.Temp); + + for (int i = 0; i < 100; ++i) + { + reader.ReadValueSafe(out int outVal); + Assert.AreEqual(i, outVal); + } + + using var bitReader = reader.EnterBitwiseContext(); + for (int i = 0; i < 16; ++i) + { + Assert.IsTrue(bitReader.TryBeginReadBits(32)); + bitReader.ReadBits(out ulong outVal, 31); + bitReader.ReadBit(out bool bit); + Assert.AreEqual(inVal, outVal); + Assert.AreEqual(true, bit); + } + } + + [Test] + public void WhenBitwiseReadingMoreThan8Bits_ValuesAreCorrect() + { + using var writer = new FastBufferWriter(1024, Allocator.Temp); + ulong inVal = 123456789; + + for (int i = 0; i < 100; ++i) + { + writer.WriteValueSafe(i); + } + + uint combined = (uint)inVal | (1u << 31); + writer.WriteValueSafe(combined); + writer.WriteValueSafe(combined); + writer.WriteValueSafe(combined); + + using var reader = new FastBufferReader(writer, Allocator.Temp); + + for (int i = 0; i < 100; ++i) + { + reader.ReadValueSafe(out int outVal); + Assert.AreEqual(i, outVal); + } + + using (var bitReader = reader.EnterBitwiseContext()) + { + Assert.IsTrue(bitReader.TryBeginReadBits(32)); + bitReader.ReadBits(out ulong outVal, 31); + bitReader.ReadBit(out bool bit); + Assert.AreEqual(inVal, outVal); + Assert.AreEqual(true, bit); + + Assert.IsTrue(bitReader.TryBeginReadBits(32)); + bitReader.ReadBits(out outVal, 31); + bitReader.ReadBit(out bit); + Assert.AreEqual(inVal, outVal); + Assert.AreEqual(true, bit); + + Assert.IsTrue(bitReader.TryBeginReadBits(32)); + bitReader.ReadBits(out outVal, 31); + bitReader.ReadBit(out bit); + Assert.AreEqual(inVal, outVal); + Assert.AreEqual(true, bit); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/UserBitReaderAndBitWriterTests_NCCBUG175.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/UserBitReaderAndBitWriterTests_NCCBUG175.cs.meta new file mode 100644 index 0000000000..b1a34e7192 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/UserBitReaderAndBitWriterTests_NCCBUG175.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: adfa622d42824b70a39a30b6aa22c9c5 +timeCreated: 1660758428 \ No newline at end of file diff --git a/dotnet-tools/netcode.standards/Program.cs b/dotnet-tools/netcode.standards/Program.cs index f9190a5c09..3c84bc18e8 100644 --- a/dotnet-tools/netcode.standards/Program.cs +++ b/dotnet-tools/netcode.standards/Program.cs @@ -35,8 +35,8 @@ private static int Main( var procInfo = new ProcessStartInfo("dotnet"); procInfo.Arguments = check - ? $"format {file} whitespace --no-restore --verify-no-changes --verbosity {verbosity}" - : $"format {file} whitespace --no-restore --verbosity {verbosity}"; + ? $"format whitespace {file} --no-restore --verify-no-changes --verbosity {verbosity}" + : $"format whitespace {file} --no-restore --verbosity {verbosity}"; Console.WriteLine($"######## START -> {(check ? "check" : "fix")} whitespace issues"); var whitespace = Process.Start(procInfo); whitespace.WaitForExit(); @@ -48,8 +48,8 @@ private static int Main( Console.WriteLine("######## SUCCEEDED -> no whitespace issues"); procInfo.Arguments = check - ? $"format {file} style --severity error --no-restore --verify-no-changes --verbosity {verbosity}" - : $"format {file} style --severity error --no-restore --verbosity {verbosity}"; + ? $"format style {file} --severity error --no-restore --verify-no-changes --verbosity {verbosity}" + : $"format style {file} --severity error --no-restore --verbosity {verbosity}"; Console.WriteLine($"######## START -> {(check ? "check" : "fix")} style/naming issues"); var style = Process.Start(procInfo); style.WaitForExit(); From 6928ce0ae5ee111b5c77c0bfef6982f72c25fbf0 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 18 Aug 2022 13:00:30 -0500 Subject: [PATCH 030/138] fix: NetworkTransform teleport interpolating from last position on non-authoritative side [NCCBUG-166] (#2110) * fix NetworkTransform was setting the value of m_Bitset but not resetting the individual bits if there was no deltas. This would result in a gradually increasing message size over time when updating the NetworkTransform. * update migrating the assignment back down below the CanCommitToTransform * fix This resolves the issue where clients were applying every value marked to be synchronized even if the update didn't include specific axis values. * test This includes some fixes to prefabs that hadn't been updated to the more recent changes in NetworkTransform that limited the minimum threshold (i.e. they were set to 0 which causes issues) * fix Since we are now no longer sending the entire transform values, this addition prevents the non-authoritative side from making changes to the transform. * style removing whitespace * update Adding some test adjustments and including rotation to interpolation measurements. * style removing an unused namespace. * style fixing issue with reference to UnityEngine's Object after I removed the system namespace. * fix This includes the adjustments for client authoritative mode to work without sending RPCs. * fix This update fixes the issue where the authoritative side was interpolating when interpolate is enabled. Now, the authoritative side no longer interpolates. * manual test updates These changes make the player client authoritative within the Samples Menu->Additive Scene Loading manual test/sample. * update reverting the change of m_Bitset to an internal. * test update updating test to account for reverting back to the private m_Bitset. * update Migrated the code that updates interpolators with the current value if the axis is not set into the AddInterpolatedState method as opposed to it being in the ApplyInterpolatedNetworkStateToTransform * fix First pass fix for teleporting. * test manual Manual test adjustments (still working on player motion controller from client side) * manual test Adjustments for the teleporting sample/manual test. Still working on the new player cube motion model. * test manual Improved motion model (still could use some more work for client to server side). Now packing data sent from client to host/server. Added UI text that displays whether the instance is a server, host, or client. * update removing debug code * fix This fixes the last issues with teleporting and the updated player motion model. * fix Cleaning up how we handle transitioning to and from global and local space. Minor adjustment to the validation test to assure the range of error being tested on the client side is the same as the delta position threshold range. * update Removing the NetworkTransformState Position, Rotation, and Scale since we are only sending the values that changed (i.e. if the x-axis of position was the only thing that was set and NetworkTransformState.Position was used, then the Y and Z axial values would be invalid). * fix removing code block no longer needed. * style removing a comment that was no longer needed. * update applying suggested changes * update replacing the GetReplicatedNetworkState() method with a getter ReplicatedNetworkState property. * update This is a new approach: - Make in-between state updates additive - Clear our state's Bitset value and prepare to capture any deltas for the next network tick period. * update Realized since we are back to additive (per network tick), we can now remove the non-authoritative interpolation work around for not having all values that changed within the associated NetworkTransformState's network tick period. * update Disposing the NetworkVariables when the NetworkTransform is destroyed. * update Suggestion updates and comment fixes. * style adding Sam's comment. * style updated comment to include what we are preserving. * update Removing the ApplyTransformValues and SetTransformValues as they are no longer needed. Also, removing the associated properties. * Test Manual Updates to manual test related assets and scripts. Removed the "make a pooled object invisible to hide the teleport interpolation issue" hack that would disable the render mesh when despawned and enable it shortly after it is spawned. Increased tick rate to 32 in the additive scene loading test. Fixing white space issues. * update Removing the unused serverTime addition as well. * update Renamed ApplyInterpolatedNetworkStateToTransform to ApplyAuthoritativeState. (It didn't make sense if interpolation is disabled) * tests update This includes an updated NetworkTransformTests that tests both authority types while running in Server and Host modes. This includes some updates to naming for easier identification of what each test does (in test runner and in code). * Update Minor adjustment where we don't need to send a local properties over the stack via parameters. * test Added interpolate enable/disable values to all tests. Now, all NetworkTranaformTests are done with interpolation enabled and disabled. * fix and update Fixed another issue that was exposed while cleaning up the code base. Was able to remove even more of the code added previously. Some clean up as well as making one additional method private as opposed to protected to prevent us from having to release a minor version vs patch. Also, ApplyTransformToNetworkState, and ApplyTransformToNetworkStateWithInfo only return if the state is dirty (the rest can be determined from within the state itself). ApplyLocalNetworkState, used for integration tests, now just returns the NetworkTransformState. * fix This fixes the issue with the memory leak message in the editor NetworkTimeTests (very odd this fixed the issue). * test manual Switched over to an owner authoritative NetworkTransform that also includes the playermovement. Adjusted the PlayerCube NetworkPrefab and minor adjustment to the CollideTeleporter. * update updating the changelog * fix Found a minor logic issue in ApplyTransformToNetworkStateWithInfo where the state dirty flag could be reset before end of the current network tick. This could result in state being carried forward into the next network tick. This resolves that issue. * update Minor adjustment to how we handle applying a rotation measurement on the non-authoritative side. Removed a property not being used outside of a single method and added additional comments to the PlayerMovement component. * update This update includes the final changes required in order to support the previous NetworkTransform model where it was always server authoritative and client owners could direct/apply state via internal RPCs. This also includes the ability for a server to apply/dictate state when running in client owner authority mode. This includes updates to the NetworkTransform tests that validate all of these updates and increase the amount of coverage for NetworkTransform. * style moving new private m_ClientRpcParams and m_ClientIds up to the rest of the property declarations. Adjusting comment * test manual Minor adjustments for the random player movers. Applying the server authoritative player prefab back to be the default for the additive scene sample (this makes it easier to compare to main branch). * style removing a no longer needed comment. adding note about future clean up for NetworkTransform. * style fixed spelling issue * style removing left over comment and spelling * style removing legacy/unused namespaces * update realized our CommitLocallyAndReplicate method was serving no purpose at this point. added the assignment of NetworkTransformState to the ReplicatedNetworkState within TryCommit and removed CommitLocallyAndReplicate. * update removing legacy check for TryCommitTransformToServer that is no longer needed. * update removing m_Transform (no purpose for it) Fixing minor grammar issue with a comment * test manual update removing additional IsOwner check. * revert reverting the changes I made to the editor test NetworkTimeTests as I just had Yamato crash again in a different area in NetworkTimeTests with the same memory leak error. * style replacing LF/CR * Style Improving comments --- com.unity.netcode.gameobjects/CHANGELOG.md | 3 + .../Components/NetworkTransform.cs | 711 ++-- .../NetworkTransform/NetworkTransformTests.cs | 588 +++- .../Runtime/TransformInterpolationTests.cs | 15 +- ...bjectToOverride-VariantBlueCylinder.prefab | 5 + testproject/Assets/Prefabs/Player.prefab | 13 +- testproject/Assets/Prefabs/PlayerCube.prefab | 81 +- .../Assets/References/Scene/Teleporting.asset | 19 + .../References/Scene/Teleporting.asset.meta | 8 + testproject/Assets/Samples/SamplesMenu.unity | 3 +- testproject/Assets/Samples/Teleport.meta | 8 + .../Samples/Teleport/CollideTeleporter.cs | 57 + .../Teleport/CollideTeleporter.cs.meta | 11 + .../Samples/Teleport/TeleportSample.unity | 2881 +++++++++++++++++ .../Teleport/TeleportSample.unity.meta | 7 + testproject/Assets/Scripts/PlayerMovement.cs | 123 +- .../Assets/Scripts/ServerHostClientText.cs | 46 + .../Scripts/ServerHostClientText.cs.meta | 11 + .../AdditiveSceneMultiInstance.unity | 65 +- .../ClientNetworkTransform.cs | 12 + .../ClientNetworkTransform.cs.meta | 11 + .../PlayerClientAuthoritative.prefab | 297 ++ .../PlayerClientAuthoritative.prefab.meta | 7 + .../SceneTransitioningBase1.unity | 309 +- .../Scripts/GenericNetworkObjectBehaviour.cs | 57 +- .../Manual/Scripts/PlayerMovementManager.cs | 35 +- .../Tests/Manual/Scripts/RandomMovement.cs | 90 +- .../ProjectSettings/EditorBuildSettings.asset | 3 + 28 files changed, 4712 insertions(+), 764 deletions(-) create mode 100644 testproject/Assets/References/Scene/Teleporting.asset create mode 100644 testproject/Assets/References/Scene/Teleporting.asset.meta create mode 100644 testproject/Assets/Samples/Teleport.meta create mode 100644 testproject/Assets/Samples/Teleport/CollideTeleporter.cs create mode 100644 testproject/Assets/Samples/Teleport/CollideTeleporter.cs.meta create mode 100644 testproject/Assets/Samples/Teleport/TeleportSample.unity create mode 100644 testproject/Assets/Samples/Teleport/TeleportSample.unity.meta create mode 100644 testproject/Assets/Scripts/ServerHostClientText.cs create mode 100644 testproject/Assets/Scripts/ServerHostClientText.cs.meta create mode 100644 testproject/Assets/Tests/Manual/SceneTransitioningAdditive/ClientNetworkTransform.cs create mode 100644 testproject/Assets/Tests/Manual/SceneTransitioningAdditive/ClientNetworkTransform.cs.meta create mode 100644 testproject/Assets/Tests/Manual/SceneTransitioningAdditive/PlayerClientAuthoritative.prefab create mode 100644 testproject/Assets/Tests/Manual/SceneTransitioningAdditive/PlayerClientAuthoritative.prefab.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 9c78b5065f..ba4318ba02 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -18,6 +18,9 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed - Fixed an issue where reading/writing more than 8 bits at a time with BitReader/BitWriter would write/read from the wrong place, returning and incorrect result. (#2130) +- Fixed issue with the internal `NetworkTransformState.m_Bitset` flag not getting cleared upon the next tick advancement. (#2110) +- Fixed interpolation issue with `NetworkTransform.Teleport`. (#2110) +- Fixed issue where the authoritative side was interpolating its transform. (#2110) - Fixed Owner-written NetworkVariable infinitely write themselves (#2109) - Fixed NetworkList issue that showed when inserting at the very end of a NetworkList (#2099) - Fixed issue where a client owner of a `NetworkVariable` with both owner read and write permissions would not update the server side when changed. (#2097) diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index c82f9f874d..f54d08dee3 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -60,8 +60,8 @@ internal struct NetworkTransformState : INetworkSerializable private const int k_ScaleYBit = 8; private const int k_ScaleZBit = 9; private const int k_TeleportingBit = 10; - // 11-15: + private ushort m_Bitset; internal bool InLocalSpace @@ -105,6 +105,14 @@ internal bool HasPositionZ } } + internal bool HasPositionChange + { + get + { + return HasPositionX | HasPositionY | HasPositionZ; + } + } + // RotAngles internal bool HasRotAngleX { @@ -136,6 +144,15 @@ internal bool HasRotAngleZ } } + internal bool HasRotAngleChange + { + get + { + return HasRotAngleX | HasRotAngleY | HasRotAngleZ; + } + } + + // Scale internal bool HasScaleX { @@ -167,6 +184,14 @@ internal bool HasScaleZ } } + internal bool HasScaleChange + { + get + { + return HasScaleX | HasScaleY | HasScaleZ; + } + } + internal bool IsTeleportingNextFrame { get => (m_Bitset & (1 << k_TeleportingBit)) != 0; @@ -182,37 +207,16 @@ internal bool IsTeleportingNextFrame internal float ScaleX, ScaleY, ScaleZ; internal double SentTime; - internal Vector3 Position - { - get { return new Vector3(PositionX, PositionY, PositionZ); } - set - { - PositionX = value.x; - PositionY = value.y; - PositionZ = value.z; - } - } - - internal Vector3 Rotation - { - get { return new Vector3(RotAngleX, RotAngleY, RotAngleZ); } - set - { - RotAngleX = value.x; - RotAngleY = value.y; - RotAngleZ = value.z; - } - } + internal bool IsDirty; - internal Vector3 Scale + /// + /// This will reset the NetworkTransform BitSet + /// + internal void ClearBitSetForNextTick() { - get { return new Vector3(ScaleX, ScaleY, ScaleZ); } - set - { - ScaleX = value.x; - ScaleY = value.y; - ScaleZ = value.z; - } + // We need to preserve the local space settings for the current state + m_Bitset &= (ushort)(m_Bitset & (1 << k_InLocalSpaceBit)); + IsDirty = false; } public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter @@ -267,6 +271,21 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade { serializer.SerializeValue(ref ScaleZ); } + + // Only if we are receiving state + if (serializer.IsReader) + { + // Go ahead and mark the local state dirty or not dirty as well + /// + if (HasPositionChange || HasRotAngleChange || HasScaleChange) + { + IsDirty = true; + } + else + { + IsDirty = false; + } + } } } @@ -328,22 +347,21 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade /// public float ScaleThreshold = ScaleThresholdDefault; + /// - /// Sets whether this transform should sync in local space or in world space. - /// This is important to set since reparenting this transform could have issues, - /// if using world position (depending on who gets synced first: the parent or the child) - /// Having a child always at position 0,0,0 for example will have less possibilities of desync than when using world positions + /// Sets whether the transform should be treated as local (true) or world (false) space. /// + /// + /// This should only be changed by the authoritative side during runtime. Non-authoritative + /// changes will be overridden upon the next state update. + /// [Tooltip("Sets whether this transform should sync in local space or in world space")] public bool InLocalSpace = false; - private bool m_LastInterpolateLocal = false; // was the last frame local /// /// When enabled (default) interpolation is applied and when disabled no interpolation is applied - /// Note: can be changed during runtime. /// public bool Interpolate = true; - private bool m_LastInterpolate = true; // was the last frame interpolated /// /// Used to determine who can write to this transform. Server only for this transform. @@ -351,7 +369,6 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade /// in the package samples for how to implement a NetworkTransform with client write support. /// If using different values, please use RPCs to write to the server. Netcode doesn't support client side network variable writing /// - // This is public to make sure that users don't depend on this IsClient && IsOwner check in their code. If this logic changes in the future, we can make it invisible here public bool CanCommitToTransform { get; protected set; } /// @@ -366,62 +383,107 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade /// protected NetworkManager m_CachedNetworkManager; - private readonly NetworkVariable m_ReplicatedNetworkState = new NetworkVariable(new NetworkTransformState()); + /// + /// We have two internal NetworkVariables. + /// One for server authoritative and one for "client/owner" authoritative. + /// + private readonly NetworkVariable m_ReplicatedNetworkStateServer = new NetworkVariable(new NetworkTransformState(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server); + private readonly NetworkVariable m_ReplicatedNetworkStateOwner = new NetworkVariable(new NetworkTransformState(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); + - private NetworkTransformState m_LocalAuthoritativeNetworkState; + internal NetworkVariable ReplicatedNetworkState + { + get + { + if (!IsServerAuthoritative()) + { + return m_ReplicatedNetworkStateOwner; + } + + return m_ReplicatedNetworkStateServer; + } + } - private const int k_DebugDrawLineTime = 10; + private NetworkTransformState m_LocalAuthoritativeNetworkState; private bool m_HasSentLastValue = false; // used to send one last value, so clients can make the difference between lost replication data (clients extrapolate) and no more data to send. + private ClientRpcParams m_ClientRpcParams = new ClientRpcParams() { Send = new ClientRpcSendParams() }; + private List m_ClientIds = new List() { 0 }; - private BufferedLinearInterpolator m_PositionXInterpolator; // = new BufferedLinearInterpolatorFloat(); - private BufferedLinearInterpolator m_PositionYInterpolator; // = new BufferedLinearInterpolatorFloat(); - private BufferedLinearInterpolator m_PositionZInterpolator; // = new BufferedLinearInterpolatorFloat(); - private BufferedLinearInterpolator m_RotationInterpolator; // = new BufferedLinearInterpolatorQuaternion(); // rotation is a single Quaternion since each euler axis will affect the quaternion's final value - private BufferedLinearInterpolator m_ScaleXInterpolator; // = new BufferedLinearInterpolatorFloat(); - private BufferedLinearInterpolator m_ScaleYInterpolator; // = new BufferedLinearInterpolatorFloat(); - private BufferedLinearInterpolator m_ScaleZInterpolator; // = new BufferedLinearInterpolatorFloat(); + private BufferedLinearInterpolator m_PositionXInterpolator; + private BufferedLinearInterpolator m_PositionYInterpolator; + private BufferedLinearInterpolator m_PositionZInterpolator; + private BufferedLinearInterpolator m_RotationInterpolator; // rotation is a single Quaternion since each Euler axis will affect the quaternion's final value + private BufferedLinearInterpolator m_ScaleXInterpolator; + private BufferedLinearInterpolator m_ScaleYInterpolator; + private BufferedLinearInterpolator m_ScaleZInterpolator; private readonly List> m_AllFloatInterpolators = new List>(6); - private Transform m_Transform; // cache the transform component to reduce unnecessary bounce between managed and native private int m_LastSentTick; private NetworkTransformState m_LastSentState; - /// - /// Tries updating the server authoritative transform, only if allowed. - /// If this called server side, this will commit directly. - /// If no update is needed, nothing will be sent. This method should still be called every update, it'll self manage when it should and shouldn't send - /// - /// - /// - protected void TryCommitTransformToServer(Transform transformToCommit, double dirtyTime) + internal NetworkTransformState GetLastSentState() { - var isDirty = ApplyTransformToNetworkState(ref m_LocalAuthoritativeNetworkState, dirtyTime, transformToCommit); - TryCommit(isDirty); + return m_LastSentState; } - private void TryCommitValuesToServer(Vector3 position, Vector3 rotation, Vector3 scale, double dirtyTime) + /// + /// This will try to send/commit the current transform delta states (if any) + /// + /// + /// Only client owners or the server should invoke this method + /// + /// the transform to be committed + /// time it was marked dirty + protected void TryCommitTransformToServer(Transform transformToCommit, double dirtyTime) { - var isDirty = ApplyTransformToNetworkStateWithInfo(ref m_LocalAuthoritativeNetworkState, dirtyTime, position, rotation, scale); + // Only client owners or the server should invoke this method + if (!IsOwner && !m_CachedIsServer) + { + NetworkLog.LogError($"Non-owner instance, {name}, is trying to commit a transform!"); + return; + } - TryCommit(isDirty.isDirty); - } + /// If authority is invoking this, then treat it like we do with + if (CanCommitToTransform) + { + // If our replicated state is not dirty and our local authority state is dirty, clear it. + if (!ReplicatedNetworkState.IsDirty() && m_LocalAuthoritativeNetworkState.IsDirty) + { + // Now clear our bitset and prepare for next network tick state update + m_LocalAuthoritativeNetworkState.ClearBitSetForNextTick(); + } - private void TryCommit(bool isDirty) - { - void Send(NetworkTransformState stateToSend) + TryCommitTransform(transformToCommit, m_CachedNetworkManager.LocalTime.Time); + } + else { - if (m_CachedIsServer) + // We are an owner requesting to update our state + if (!m_CachedIsServer) { - // server RPC takes a few frames to execute server side, we want this to execute immediately - CommitLocallyAndReplicate(stateToSend); + SetStateServerRpc(transformToCommit.position, transformToCommit.rotation, transformToCommit.localScale, false); } - else + else // Server is always authoritative (including owner authoritative) { - CommitTransformServerRpc(stateToSend); + SetStateClientRpc(transformToCommit.position, transformToCommit.rotation, transformToCommit.localScale, false); } } + } + + /// + /// Authoritative side only + /// If there are any transform delta states, this method will synchronize the + /// state with all non-authority instances. + /// + private void TryCommitTransform(Transform transformToCommit, double dirtyTime) + { + if (!CanCommitToTransform && !IsOwner) + { + NetworkLog.LogError($"[{name}] is trying to commit the transform without authority!"); + return; + } + var isDirty = ApplyTransformToNetworkState(ref m_LocalAuthoritativeNetworkState, dirtyTime, transformToCommit); // if dirty, send // if not dirty anymore, but hasn't sent last value for limiting extrapolation, still set isDirty @@ -432,38 +494,24 @@ void Send(NetworkTransformState stateToSend) // making it immobile. if (isDirty) { - Send(m_LocalAuthoritativeNetworkState); + // Commit the state + ReplicatedNetworkState.Value = m_LocalAuthoritativeNetworkState; m_HasSentLastValue = false; m_LastSentTick = m_CachedNetworkManager.LocalTime.Tick; m_LastSentState = m_LocalAuthoritativeNetworkState; } else if (!m_HasSentLastValue && m_CachedNetworkManager.LocalTime.Tick >= m_LastSentTick + 1) // check for state.IsDirty since update can happen more than once per tick. No need for client, RPCs will just queue up { + // Since the last m_LocalAuthoritativeNetworkState could have included a IsTeleportingNextFrame + // we need to reset this here so only the deltas are applied and interpolation is not reset again. + m_LastSentState.IsTeleportingNextFrame = false; m_LastSentState.SentTime = m_CachedNetworkManager.LocalTime.Time; // time 1+ tick later - Send(m_LastSentState); + // Commit the state + ReplicatedNetworkState.Value = m_LastSentState; m_HasSentLastValue = true; } } - [ServerRpc(RequireOwnership = false)] - private void CommitTransformServerRpc(NetworkTransformState networkState, ServerRpcParams serverParams = default) - { - if (serverParams.Receive.SenderClientId == OwnerClientId) // RPC call when not authorized to write could happen during the RTT interval during which a server's ownership change hasn't reached the client yet - { - CommitLocallyAndReplicate(networkState); - } - } - - private void CommitLocallyAndReplicate(NetworkTransformState networkState) - { - m_ReplicatedNetworkState.Value = networkState; - - if (Interpolate) - { - AddInterpolatedState(networkState); - } - } - private void ResetInterpolatedStateToCurrentAuthoritativeState() { var serverTime = NetworkManager.ServerTime.Time; @@ -476,7 +524,7 @@ private void ResetInterpolatedStateToCurrentAuthoritativeState() m_PositionYInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionY, serverTime); m_PositionZInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionZ, serverTime); - m_RotationInterpolator.ResetTo(Quaternion.Euler(m_LocalAuthoritativeNetworkState.Rotation), serverTime); + m_RotationInterpolator.ResetTo(Quaternion.Euler(m_LocalAuthoritativeNetworkState.RotAngleX, m_LocalAuthoritativeNetworkState.RotAngleY, m_LocalAuthoritativeNetworkState.RotAngleZ), serverTime); m_ScaleXInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleX, serverTime); m_ScaleYInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleY, serverTime); @@ -484,38 +532,46 @@ private void ResetInterpolatedStateToCurrentAuthoritativeState() } /// - /// Will apply the transform to the LocalAuthoritativeNetworkState and get detailed isDirty information returned. + /// Used for integration testing: + /// Will apply the transform to the LocalAuthoritativeNetworkState and get detailed dirty information returned + /// in the returned. /// /// transform to apply - /// bool isDirty, bool isPositionDirty, bool isRotationDirty, bool isScaleDirty - internal (bool isDirty, bool isPositionDirty, bool isRotationDirty, bool isScaleDirty) ApplyLocalNetworkState(Transform transform) + /// NetworkTransformState + internal NetworkTransformState ApplyLocalNetworkState(Transform transform) { - return ApplyTransformToNetworkStateWithInfo(ref m_LocalAuthoritativeNetworkState, m_CachedNetworkManager.LocalTime.Time, transform); - } + // Since we never commit these changes, we need to simulate that any changes were committed previously and the bitset + // value would already be reset prior to having the state applied + m_LocalAuthoritativeNetworkState.ClearBitSetForNextTick(); - // updates `NetworkState` properties if they need to and returns a `bool` indicating whether or not there was any changes made - // returned boolean would be useful to change encapsulating `NetworkVariable`'s dirty state, e.g. ReplNetworkState.SetDirty(isDirty); - internal bool ApplyTransformToNetworkState(ref NetworkTransformState networkState, double dirtyTime, Transform transformToUse) - { - return ApplyTransformToNetworkStateWithInfo(ref networkState, dirtyTime, transformToUse).isDirty; + // Now check the transform for any threshold value changes + ApplyTransformToNetworkStateWithInfo(ref m_LocalAuthoritativeNetworkState, m_CachedNetworkManager.LocalTime.Time, transform); + + // Return the entire state to be used by the integration test + return m_LocalAuthoritativeNetworkState; } - private (bool isDirty, bool isPositionDirty, bool isRotationDirty, bool isScaleDirty) ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState networkState, double dirtyTime, Transform transformToUse) + /// + /// Used for integration testing + /// + internal bool ApplyTransformToNetworkState(ref NetworkTransformState networkState, double dirtyTime, Transform transformToUse) { - var position = InLocalSpace ? transformToUse.localPosition : transformToUse.position; - var rotAngles = InLocalSpace ? transformToUse.localEulerAngles : transformToUse.eulerAngles; - var scale = transformToUse.localScale; - return ApplyTransformToNetworkStateWithInfo(ref networkState, dirtyTime, position, rotAngles, scale); + return ApplyTransformToNetworkStateWithInfo(ref networkState, dirtyTime, transformToUse); } - private (bool isDirty, bool isPositionDirty, bool isRotationDirty, bool isScaleDirty) ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState networkState, double dirtyTime, Vector3 position, Vector3 rotAngles, Vector3 scale) + /// + /// Applies the transform to the specified. + /// + private bool ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState networkState, double dirtyTime, Transform transformToUse) { var isDirty = false; var isPositionDirty = false; var isRotationDirty = false; var isScaleDirty = false; - // hasPositionZ set to false when it should be true? + var position = InLocalSpace ? transformToUse.localPosition : transformToUse.position; + var rotAngles = InLocalSpace ? transformToUse.localEulerAngles : transformToUse.eulerAngles; + var scale = transformToUse.localScale; if (InLocalSpace != networkState.InLocalSpace) { @@ -523,77 +579,63 @@ internal bool ApplyTransformToNetworkState(ref NetworkTransformState networkStat isDirty = true; } - // we assume that if x, y or z are dirty then we'll have to send all 3 anyway, so for efficiency - // we skip doing the (quite expensive) Math.Approximately() and check against PositionThreshold - // this still is overly costly and could use more improvements. - // - // (ditto for scale components) - if (SyncPositionX && - Mathf.Abs(networkState.PositionX - position.x) > PositionThreshold) + if (SyncPositionX && Mathf.Abs(networkState.PositionX - position.x) >= PositionThreshold || networkState.IsTeleportingNextFrame) { networkState.PositionX = position.x; networkState.HasPositionX = true; isPositionDirty = true; } - if (SyncPositionY && - Mathf.Abs(networkState.PositionY - position.y) > PositionThreshold) + if (SyncPositionY && Mathf.Abs(networkState.PositionY - position.y) >= PositionThreshold || networkState.IsTeleportingNextFrame) { networkState.PositionY = position.y; networkState.HasPositionY = true; isPositionDirty = true; } - if (SyncPositionZ && - Mathf.Abs(networkState.PositionZ - position.z) > PositionThreshold) + if (SyncPositionZ && Mathf.Abs(networkState.PositionZ - position.z) >= PositionThreshold || networkState.IsTeleportingNextFrame) { networkState.PositionZ = position.z; networkState.HasPositionZ = true; isPositionDirty = true; } - if (SyncRotAngleX && - Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleX, rotAngles.x)) > RotAngleThreshold) + if (SyncRotAngleX && Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleX, rotAngles.x)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame) { networkState.RotAngleX = rotAngles.x; networkState.HasRotAngleX = true; isRotationDirty = true; } - if (SyncRotAngleY && - Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleY, rotAngles.y)) > RotAngleThreshold) + if (SyncRotAngleY && Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleY, rotAngles.y)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame) { networkState.RotAngleY = rotAngles.y; networkState.HasRotAngleY = true; isRotationDirty = true; } - if (SyncRotAngleZ && - Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleZ, rotAngles.z)) > RotAngleThreshold) + if (SyncRotAngleZ && Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleZ, rotAngles.z)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame) { networkState.RotAngleZ = rotAngles.z; networkState.HasRotAngleZ = true; isRotationDirty = true; } - if (SyncScaleX && - Mathf.Abs(networkState.ScaleX - scale.x) > ScaleThreshold) + if (SyncScaleX && Mathf.Abs(networkState.ScaleX - scale.x) >= ScaleThreshold || networkState.IsTeleportingNextFrame) { networkState.ScaleX = scale.x; networkState.HasScaleX = true; isScaleDirty = true; } - if (SyncScaleY && - Mathf.Abs(networkState.ScaleY - scale.y) > ScaleThreshold) + if (SyncScaleY && Mathf.Abs(networkState.ScaleY - scale.y) >= ScaleThreshold || networkState.IsTeleportingNextFrame) { networkState.ScaleY = scale.y; networkState.HasScaleY = true; isScaleDirty = true; } - if (SyncScaleZ && - Mathf.Abs(networkState.ScaleZ - scale.z) > ScaleThreshold) + if (SyncScaleZ && Mathf.Abs(networkState.ScaleZ - scale.z) >= ScaleThreshold || networkState.IsTeleportingNextFrame) { networkState.ScaleZ = scale.z; networkState.HasScaleZ = true; @@ -607,37 +649,46 @@ internal bool ApplyTransformToNetworkState(ref NetworkTransformState networkStat networkState.SentTime = dirtyTime; } - return (isDirty, isPositionDirty, isRotationDirty, isScaleDirty); + /// We need to set this in order to know when we can reset our local authority state + /// If our state is already dirty or we just found deltas (i.e. isDirty == true) + networkState.IsDirty |= isDirty; + return isDirty; } - private void ApplyInterpolatedNetworkStateToTransform(NetworkTransformState networkState, Transform transformToUpdate) + /// + /// Applies the authoritative state to the transform + /// + private void ApplyAuthoritativeState() { - var interpolatedPosition = InLocalSpace ? transformToUpdate.localPosition : transformToUpdate.position; + var networkState = ReplicatedNetworkState.Value; + var interpolatedPosition = networkState.InLocalSpace ? transform.localPosition : transform.position; // todo: we should store network state w/ quats vs. euler angles - var interpolatedRotAngles = InLocalSpace ? transformToUpdate.localEulerAngles : transformToUpdate.eulerAngles; - var interpolatedScale = transformToUpdate.localScale; + var interpolatedRotAngles = networkState.InLocalSpace ? transform.localEulerAngles : transform.eulerAngles; + var interpolatedScale = transform.localScale; + var isTeleporting = networkState.IsTeleportingNextFrame; - // InLocalSpace Read + // InLocalSpace Read: InLocalSpace = networkState.InLocalSpace; - // Position Read - if (SyncPositionX) + + // Update the position values that were changed in this state update + if (networkState.HasPositionX) { - interpolatedPosition.x = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Position.x : m_PositionXInterpolator.GetInterpolatedValue(); + interpolatedPosition.x = isTeleporting || !Interpolate ? networkState.PositionX : m_PositionXInterpolator.GetInterpolatedValue(); } - if (SyncPositionY) + if (networkState.HasPositionY) { - interpolatedPosition.y = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Position.y : m_PositionYInterpolator.GetInterpolatedValue(); + interpolatedPosition.y = isTeleporting || !Interpolate ? networkState.PositionY : m_PositionYInterpolator.GetInterpolatedValue(); } - if (SyncPositionZ) + if (networkState.HasPositionZ) { - interpolatedPosition.z = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Position.z : m_PositionZInterpolator.GetInterpolatedValue(); + interpolatedPosition.z = isTeleporting || !Interpolate ? networkState.PositionZ : m_PositionZInterpolator.GetInterpolatedValue(); } - // again, we should be using quats here - if (SyncRotAngleX || SyncRotAngleY || SyncRotAngleZ) + // Update the rotation values that were changed in this state update + if (networkState.HasRotAngleChange) { var eulerAngles = new Vector3(); if (Interpolate) @@ -645,111 +696,174 @@ private void ApplyInterpolatedNetworkStateToTransform(NetworkTransformState netw eulerAngles = m_RotationInterpolator.GetInterpolatedValue().eulerAngles; } - if (SyncRotAngleX) + if (networkState.HasRotAngleX) { - interpolatedRotAngles.x = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Rotation.x : eulerAngles.x; + interpolatedRotAngles.x = isTeleporting || !Interpolate ? networkState.RotAngleX : eulerAngles.x; } - if (SyncRotAngleY) + if (networkState.HasRotAngleY) { - interpolatedRotAngles.y = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Rotation.y : eulerAngles.y; + interpolatedRotAngles.y = isTeleporting || !Interpolate ? networkState.RotAngleY : eulerAngles.y; } - if (SyncRotAngleZ) + if (networkState.HasRotAngleZ) { - interpolatedRotAngles.z = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Rotation.z : eulerAngles.z; + interpolatedRotAngles.z = isTeleporting || !Interpolate ? networkState.RotAngleZ : eulerAngles.z; } } - // Scale Read - if (SyncScaleX) + // Update all scale axis that were changed in this state update + if (networkState.HasScaleX) { - interpolatedScale.x = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Scale.x : m_ScaleXInterpolator.GetInterpolatedValue(); + interpolatedScale.x = isTeleporting || !Interpolate ? networkState.ScaleX : m_ScaleXInterpolator.GetInterpolatedValue(); } - if (SyncScaleY) + if (networkState.HasScaleY) { - interpolatedScale.y = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Scale.y : m_ScaleYInterpolator.GetInterpolatedValue(); + interpolatedScale.y = isTeleporting || !Interpolate ? networkState.ScaleY : m_ScaleYInterpolator.GetInterpolatedValue(); } - if (SyncScaleZ) + if (networkState.HasScaleZ) { - interpolatedScale.z = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Scale.z : m_ScaleZInterpolator.GetInterpolatedValue(); + interpolatedScale.z = isTeleporting || !Interpolate ? networkState.ScaleZ : m_ScaleZInterpolator.GetInterpolatedValue(); } - // Position Apply - if (SyncPositionX || SyncPositionY || SyncPositionZ) + // Apply the new position + if (networkState.HasPositionChange) { if (InLocalSpace) { - transformToUpdate.localPosition = interpolatedPosition; + + transform.localPosition = interpolatedPosition; } else { - transformToUpdate.position = interpolatedPosition; + transform.position = interpolatedPosition; } } - // RotAngles Apply - if (SyncRotAngleX || SyncRotAngleY || SyncRotAngleZ) + // Apply the new rotation + if (networkState.HasRotAngleChange) { if (InLocalSpace) { - transformToUpdate.localRotation = Quaternion.Euler(interpolatedRotAngles); + transform.localRotation = Quaternion.Euler(interpolatedRotAngles); } else { - transformToUpdate.rotation = Quaternion.Euler(interpolatedRotAngles); + transform.rotation = Quaternion.Euler(interpolatedRotAngles); } } - // Scale Apply - if (SyncScaleX || SyncScaleY || SyncScaleZ) + // Apply the new scale + if (networkState.HasScaleChange) { - transformToUpdate.localScale = interpolatedScale; + transform.localScale = interpolatedScale; } } - private void AddInterpolatedState(NetworkTransformState newState, bool reset = false) + /// + /// Only non-authoritative instances should invoke this + /// + private void AddInterpolatedState(NetworkTransformState newState) { var sentTime = newState.SentTime; + var currentPosition = newState.InLocalSpace ? transform.localPosition : transform.position; + var currentRotation = newState.InLocalSpace ? transform.localRotation : transform.rotation; + var currentEulerAngles = currentRotation.eulerAngles; - if (reset) + // When there is a change in interpolation or if teleporting, we reset + if ((newState.InLocalSpace != InLocalSpace) || newState.IsTeleportingNextFrame) { + InLocalSpace = newState.InLocalSpace; + var currentScale = transform.localScale; + + // we should clear our float interpolators + foreach (var interpolator in m_AllFloatInterpolators) + { + interpolator.Clear(); + } + + // we should clear our quaternion interpolator + m_RotationInterpolator.Clear(); + + // Adjust based on which axis changed if (newState.HasPositionX) { m_PositionXInterpolator.ResetTo(newState.PositionX, sentTime); + currentPosition.x = newState.PositionX; } if (newState.HasPositionY) { m_PositionYInterpolator.ResetTo(newState.PositionY, sentTime); + currentPosition.y = newState.PositionY; } if (newState.HasPositionZ) { m_PositionZInterpolator.ResetTo(newState.PositionZ, sentTime); + currentPosition.z = newState.PositionZ; } - m_RotationInterpolator.ResetTo(Quaternion.Euler(newState.Rotation), sentTime); + // Apply the position + if (newState.InLocalSpace) + { + transform.localPosition = currentPosition; + } + else + { + transform.position = currentPosition; + } + // Adjust based on which axis changed if (newState.HasScaleX) { m_ScaleXInterpolator.ResetTo(newState.ScaleX, sentTime); + currentScale.x = newState.ScaleX; } if (newState.HasScaleY) { m_ScaleYInterpolator.ResetTo(newState.ScaleY, sentTime); + currentScale.y = newState.ScaleY; } if (newState.HasScaleZ) { m_ScaleZInterpolator.ResetTo(newState.ScaleZ, sentTime); + currentScale.z = newState.ScaleZ; + } + + // Apply the adjusted scale + transform.localScale = currentScale; + + // Adjust based on which axis changed + if (newState.HasRotAngleX) + { + currentEulerAngles.x = newState.RotAngleX; + } + + if (newState.HasRotAngleY) + { + currentEulerAngles.y = newState.RotAngleY; } + if (newState.HasRotAngleZ) + { + currentEulerAngles.z = newState.RotAngleZ; + } + + // Apply the rotation + currentRotation.eulerAngles = currentEulerAngles; + transform.rotation = currentRotation; + + // Reset the rotation interpolator + m_RotationInterpolator.ResetTo(currentRotation, sentTime); return; } + + // Apply axial changes from the new state if (newState.HasPositionX) { m_PositionXInterpolator.AddMeasurement(newState.PositionX, sentTime); @@ -765,8 +879,6 @@ private void AddInterpolatedState(NetworkTransformState newState, bool reset = f m_PositionZInterpolator.AddMeasurement(newState.PositionZ, sentTime); } - m_RotationInterpolator.AddMeasurement(Quaternion.Euler(newState.Rotation), sentTime); - if (newState.HasScaleX) { m_ScaleXInterpolator.AddMeasurement(newState.ScaleX, sentTime); @@ -781,8 +893,35 @@ private void AddInterpolatedState(NetworkTransformState newState, bool reset = f { m_ScaleZInterpolator.AddMeasurement(newState.ScaleZ, sentTime); } + + // With rotation, we check if there are any changes first and + // if so then apply the changes to the current Euler rotation + // values. + if (newState.HasRotAngleChange) + { + if (newState.HasRotAngleX) + { + currentEulerAngles.x = newState.RotAngleX; + } + + if (newState.HasRotAngleY) + { + currentEulerAngles.y = newState.RotAngleY; + } + + if (newState.HasRotAngleZ) + { + currentEulerAngles.z = newState.RotAngleZ; + } + + currentRotation.eulerAngles = currentEulerAngles; + m_RotationInterpolator.AddMeasurement(currentRotation, sentTime); + } } + /// + /// Only non-authoritative instances should invoke this method + /// private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransformState newState) { if (!NetworkObject.IsSpawned) @@ -798,13 +937,7 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf if (Interpolate) { - AddInterpolatedState(newState, (newState.InLocalSpace != m_LastInterpolateLocal)); - } - m_LastInterpolateLocal = newState.InLocalSpace; - - if (m_CachedNetworkManager.LogLevel == LogLevel.Developer) - { - var pos = new Vector3(newState.PositionX, newState.PositionY, newState.PositionZ); + AddInterpolatedState(newState); } } @@ -849,35 +982,39 @@ private void Awake() } } - /// public override void OnNetworkSpawn() { - // must set up m_Transform in OnNetworkSpawn because it's possible an object spawns but is disabled - // and thus awake won't be called. - // TODO: investigate further on not sending data for something that is not enabled - m_Transform = transform; - m_ReplicatedNetworkState.OnValueChanged += OnNetworkStateChanged; - - CanCommitToTransform = IsServer; m_CachedIsServer = IsServer; m_CachedNetworkManager = NetworkManager; + Initialize(); + + // This assures the initial spawning of the object synchronizes all connected clients + // with the current transform values. This should not be placed within Initialize since + // that can be invoked when ownership changes. if (CanCommitToTransform) { - TryCommitTransformToServer(m_Transform, m_CachedNetworkManager.LocalTime.Time); - } - m_LocalAuthoritativeNetworkState = m_ReplicatedNetworkState.Value; + // Teleport to current position + SetStateInternal(transform.position, transform.rotation, transform.localScale, true); - // crucial we do this to reset the interpolators so that recycled objects when using a pool will - // not have leftover interpolator state from the previous object - Initialize(); + // Force the state update to be sent + TryCommitTransform(transform, m_CachedNetworkManager.LocalTime.Time); + } } /// public override void OnNetworkDespawn() { - m_ReplicatedNetworkState.OnValueChanged -= OnNetworkStateChanged; + ReplicatedNetworkState.OnValueChanged -= OnNetworkStateChanged; + } + + /// + public override void OnDestroy() + { + base.OnDestroy(); + m_ReplicatedNetworkStateServer.Dispose(); + m_ReplicatedNetworkStateOwner.Dispose(); } /// @@ -892,22 +1029,36 @@ public override void OnLostOwnership() Initialize(); } + /// + /// Initializes NetworkTransform when spawned and ownership changes. + /// private void Initialize() { - ResetInterpolatedStateToCurrentAuthoritativeState(); // useful for late joining + if (!IsSpawned) + { + return; + } + + CanCommitToTransform = IsServerAuthoritative() ? IsServer : IsOwner; + var replicatedState = ReplicatedNetworkState; + m_LocalAuthoritativeNetworkState = replicatedState.Value; if (CanCommitToTransform) { - m_ReplicatedNetworkState.SetDirty(true); + replicatedState.OnValueChanged -= OnNetworkStateChanged; } - else if (m_Transform != null) + else { - ApplyInterpolatedNetworkStateToTransform(m_ReplicatedNetworkState.Value, m_Transform); + replicatedState.OnValueChanged += OnNetworkStateChanged; + + // In case we are late joining + ResetInterpolatedStateToCurrentAuthoritativeState(); } } /// /// Directly sets a state on the authoritative transform. + /// Owner clients can directly set the state on a server authoritative transform /// This will override any changes made previously to the transform /// This isn't resistant to network jitter. Server side changes due to this method won't be interpolated. /// The parameters are broken up into pos / rot / scale on purpose so that the caller can perturb @@ -921,36 +1072,86 @@ private void Initialize() /// public void SetState(Vector3? posIn = null, Quaternion? rotIn = null, Vector3? scaleIn = null, bool shouldGhostsInterpolate = true) { - if (!IsOwner) + if (!IsSpawned) { - throw new Exception("Trying to set a state on a not owned transform"); + return; } - if (m_CachedNetworkManager && !(m_CachedNetworkManager.IsConnectedClient || m_CachedNetworkManager.IsListening)) + // Only the server or owner can invoke this method + if (!IsOwner && !m_CachedIsServer) { - return; + throw new Exception("Non-owner client instance cannot set the state of the NetworkTransform!"); } - Vector3 pos = posIn == null ? transform.position : (Vector3)posIn; - Quaternion rot = rotIn == null ? transform.rotation : (Quaternion)rotIn; - Vector3 scale = scaleIn == null ? transform.localScale : (Vector3)scaleIn; + Vector3 pos = posIn == null ? InLocalSpace ? transform.localPosition : transform.position : posIn.Value; + Quaternion rot = rotIn == null ? InLocalSpace ? transform.localRotation : transform.rotation : rotIn.Value; + Vector3 scale = scaleIn == null ? transform.localScale : scaleIn.Value; if (!CanCommitToTransform) { - if (!m_CachedIsServer) + // Preserving the ability for owner authoritative mode to accept state changes from server + if (m_CachedIsServer) + { + m_ClientIds[0] = OwnerClientId; + m_ClientRpcParams.Send.TargetClientIds = m_ClientIds; + SetStateClientRpc(pos, rot, scale, !shouldGhostsInterpolate, m_ClientRpcParams); + } + else // Preserving the ability for server authoritative mode to accept state changes from owner { - SetStateServerRpc(pos, rot, scale, shouldGhostsInterpolate); + SetStateServerRpc(pos, rot, scale, !shouldGhostsInterpolate); } + return; + } + + SetStateInternal(pos, rot, scale, !shouldGhostsInterpolate); + } + + /// + /// Authoritative only method + /// Sets the internal state (teleporting or just set state) of the authoritative + /// transform directly. + /// + private void SetStateInternal(Vector3 pos, Quaternion rot, Vector3 scale, bool shouldTeleport) + { + if (InLocalSpace) + { + transform.localPosition = pos; + transform.localRotation = rot; } else { - m_Transform.position = pos; - m_Transform.rotation = rot; - m_Transform.localScale = scale; - m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldGhostsInterpolate; + transform.position = pos; + transform.rotation = rot; } + transform.localScale = scale; + m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldTeleport; + + TryCommitTransform(transform, m_CachedNetworkManager.LocalTime.Time); + } + + /// + /// Invoked by , allows a non-owner server to update the transform state + /// + /// + /// Continued support for client-driven server authority model + /// + [ClientRpc] + private void SetStateClientRpc(Vector3 pos, Quaternion rot, Vector3 scale, bool shouldTeleport, ClientRpcParams clientRpcParams = default) + { + // Server dictated state is always applied + transform.position = pos; + transform.rotation = rot; + transform.localScale = scale; + m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldTeleport; + TryCommitTransform(transform, m_CachedNetworkManager.LocalTime.Time); } + /// + /// Invoked by , allows an owner-client update the transform state + /// + /// + /// Continued support for client-driven server authority model + /// [ServerRpc] private void SetStateServerRpc(Vector3 pos, Quaternion rot, Vector3 scale, bool shouldTeleport) { @@ -959,15 +1160,23 @@ private void SetStateServerRpc(Vector3 pos, Quaternion rot, Vector3 scale, bool { (pos, rot, scale) = OnClientRequestChange(pos, rot, scale); } - m_Transform.position = pos; - m_Transform.rotation = rot; - m_Transform.localScale = scale; + + transform.position = pos; + transform.rotation = rot; + transform.localScale = scale; m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldTeleport; + TryCommitTransform(transform, m_CachedNetworkManager.LocalTime.Time); } // todo: this is currently in update, to be able to catch any transform changes. A FixedUpdate mode could be added to be less intense, but it'd be // conditional to users only making transform update changes in FixedUpdate. /// + /// + /// If you override this method, be sure that: + /// - Non-owners always invoke this base class method when using interpolation. + /// - Authority can opt to use in place of invoking this base class method. + /// - Non-authority owners can use but should still invoke the this base class method when using interpolation. + /// protected virtual void Update() { if (!IsSpawned) @@ -975,36 +1184,25 @@ protected virtual void Update() return; } - if (!Interpolate && m_LastInterpolate) - { - // if we just stopped interpolating, let's clear the interpolators - foreach (var interpolator in m_AllFloatInterpolators) - { - interpolator.Clear(); - } - } - - m_LastInterpolate = Interpolate; - if (CanCommitToTransform) { - if (m_CachedIsServer) + // If our replicated state is not dirty and our local authority state is dirty, clear it. + if (!ReplicatedNetworkState.IsDirty() && m_LocalAuthoritativeNetworkState.IsDirty) { - TryCommitTransformToServer(m_Transform, m_CachedNetworkManager.LocalTime.Time); + // Now clear our bitset and prepare for next network tick state update + m_LocalAuthoritativeNetworkState.ClearBitSetForNextTick(); } + TryCommitTransform(transform, m_CachedNetworkManager.LocalTime.Time); } - - // apply interpolated value - if (m_CachedNetworkManager.IsConnectedClient || m_CachedNetworkManager.IsListening) + else { - // eventually, we could hoist this calculation so that it happens once for all objects, not once per object - var cachedDeltaTime = Time.deltaTime; - var serverTime = NetworkManager.ServerTime; - var cachedServerTime = serverTime.Time; - var cachedRenderTime = serverTime.TimeTicksAgo(1).Time; - if (Interpolate) { + // eventually, we could hoist this calculation so that it happens once for all objects, not once per object + var serverTime = NetworkManager.ServerTime; + var cachedDeltaTime = Time.deltaTime; + var cachedServerTime = serverTime.Time; + var cachedRenderTime = serverTime.TimeTicksAgo(1).Time; foreach (var interpolator in m_AllFloatInterpolators) { interpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime); @@ -1012,18 +1210,13 @@ protected virtual void Update() m_RotationInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime); } - - if (!CanCommitToTransform) - { - // Apply updated interpolated value - ApplyInterpolatedNetworkStateToTransform(m_ReplicatedNetworkState.Value, m_Transform); - } + // Now apply the current authoritative state + ApplyAuthoritativeState(); } - m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = false; } /// - /// Teleports the transform to the given values without interpolating + /// Teleport the transform to the given values without interpolating /// /// new position to move to. /// new rotation to rotate to. @@ -1033,21 +1226,11 @@ public void Teleport(Vector3 newPosition, Quaternion newRotation, Vector3 newSca { if (!CanCommitToTransform) { - throw new Exception("Teleport not allowed"); - } - - var newRotationEuler = newRotation.eulerAngles; - var stateToSend = m_LocalAuthoritativeNetworkState; - stateToSend.IsTeleportingNextFrame = true; - stateToSend.Position = newPosition; - stateToSend.Rotation = newRotationEuler; - stateToSend.Scale = newScale; - ApplyInterpolatedNetworkStateToTransform(stateToSend, transform); - // set teleport flag in state to signal to ghosts not to interpolate - m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = true; - // check server side - TryCommitValuesToServer(newPosition, newRotationEuler, newScale, m_CachedNetworkManager.LocalTime.Time); - m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = false; + throw new Exception("Teleporting on non-authoritative side is not allowed!"); + } + + // Teleporting now is as simple as setting the internal state and passing the teleport flag + SetStateInternal(newPosition, newRotation, newScale, true); } /// diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index 0d6c58ff4e..207b691b75 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -1,11 +1,6 @@ -using System; using System.Collections; -#if NGO_TRANSFORM_DEBUG -using System.Text.RegularExpressions; -#endif using Unity.Netcode.Components; using NUnit.Framework; -// using Unity.Netcode.Samples; using UnityEngine; using UnityEngine.TestTools; using Unity.Netcode.TestHelpers.Runtime; @@ -14,8 +9,15 @@ namespace Unity.Netcode.RuntimeTests { public class NetworkTransformTestComponent : NetworkTransform { + public bool ServerAuthority; public bool ReadyToReceivePositionUpdate = false; + + protected override bool OnIsServerAuthoritative() + { + return ServerAuthority; + } + public override void OnNetworkSpawn() { base.OnNetworkSpawn(); @@ -23,243 +25,531 @@ public override void OnNetworkSpawn() ReadyToReceivePositionUpdate = true; } + public void CommitToTransform() + { + TryCommitTransformToServer(transform, NetworkManager.LocalTime.Time); + } + public (bool isDirty, bool isPositionDirty, bool isRotationDirty, bool isScaleDirty) ApplyState() { - return ApplyLocalNetworkState(transform); + var transformState = ApplyLocalNetworkState(transform); + return (transformState.IsDirty, transformState.HasPositionChange, transformState.HasRotAngleChange, transformState.HasScaleChange); } } - // [TestFixture(true, true)] - [TestFixture(true, false)] - // [TestFixture(false, true)] - [TestFixture(false, false)] + [TestFixture(HostOrServer.Host, Authority.Server)] + [TestFixture(HostOrServer.Host, Authority.Owner)] + [TestFixture(HostOrServer.Server, Authority.Server)] + [TestFixture(HostOrServer.Server, Authority.Owner)] + public class NetworkTransformTests : NetcodeIntegrationTest { - private NetworkObject m_ClientSideClientPlayer; - private NetworkObject m_ServerSideClientPlayer; + private NetworkObject m_AuthoritativePlayer; + private NetworkObject m_NonAuthoritativePlayer; - private readonly bool m_TestWithClientNetworkTransform; + private NetworkTransformTestComponent m_AuthoritativeTransform; + private NetworkTransformTestComponent m_NonAuthoritativeTransform; + private NetworkTransformTestComponent m_OwnerTransform; - public NetworkTransformTests(bool testWithHost, bool testWithClientNetworkTransform) + private readonly Authority m_Authority; + + public enum Authority { - m_UseHost = testWithHost; // from test fixture - m_TestWithClientNetworkTransform = testWithClientNetworkTransform; + Server, + Owner + } + + public enum Interpolation + { + DisableInterpolate, + EnableInterpolate + } + + /// + /// Constructor + /// + /// Value is set by TestFixture + /// Value is set by TestFixture + public NetworkTransformTests(HostOrServer testWithHost, Authority authority) + { + m_UseHost = testWithHost == HostOrServer.Host ? true : false; + m_Authority = authority; } protected override int NumberOfClients => 1; protected override void OnCreatePlayerPrefab() { - if (m_TestWithClientNetworkTransform) - { - // m_PlayerPrefab.AddComponent(); - } - else - { - var networkTransform = m_PlayerPrefab.AddComponent(); - networkTransform.Interpolate = false; - } + var networkTransformTestComponent = m_PlayerPrefab.AddComponent(); + networkTransformTestComponent.ServerAuthority = m_Authority == Authority.Server; } protected override void OnServerAndClientsCreated() { -#if NGO_TRANSFORM_DEBUG - // Log assert for writing without authority is a developer log... - // TODO: This is why monolithic test base classes and test helpers are an anti-pattern - this is part of an individual test case setup but is separated from the code verifying it! - m_ServerNetworkManager.LogLevel = LogLevel.Developer; - m_ClientNetworkManagers[0].LogLevel = LogLevel.Developer; -#endif + if (m_EnableVerboseDebug) + { + m_ServerNetworkManager.LogLevel = LogLevel.Developer; + foreach (var clientNetworkManager in m_ClientNetworkManagers) + { + clientNetworkManager.LogLevel = LogLevel.Developer; + } + } } protected override IEnumerator OnServerAndClientsConnected() { // Get the client player representation on both the server and the client side - m_ServerSideClientPlayer = m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][m_ClientNetworkManagers[0].LocalClientId]; - m_ClientSideClientPlayer = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][m_ClientNetworkManagers[0].LocalClientId]; + var serverSideClientPlayer = m_ServerNetworkManager.ConnectedClients[m_ClientNetworkManagers[0].LocalClientId].PlayerObject; + var clientSideClientPlayer = m_ClientNetworkManagers[0].LocalClient.PlayerObject; + + m_AuthoritativePlayer = m_Authority == Authority.Server ? serverSideClientPlayer : clientSideClientPlayer; + m_NonAuthoritativePlayer = m_Authority == Authority.Server ? clientSideClientPlayer : serverSideClientPlayer; // Get the NetworkTransformTestComponent to make sure the client side is ready before starting test - var otherSideNetworkTransformComponent = m_ClientSideClientPlayer.GetComponent(); + m_AuthoritativeTransform = m_AuthoritativePlayer.GetComponent(); + m_NonAuthoritativeTransform = m_NonAuthoritativePlayer.GetComponent(); + + m_OwnerTransform = m_AuthoritativeTransform.IsOwner ? m_AuthoritativeTransform : m_NonAuthoritativeTransform; // Wait for the client-side to notify it is finished initializing and spawning. - yield return WaitForConditionOrTimeOut(() => otherSideNetworkTransformComponent.ReadyToReceivePositionUpdate == true); + yield return WaitForConditionOrTimeOut(() => m_NonAuthoritativeTransform.ReadyToReceivePositionUpdate == true); + AssertOnTimeout("Timed out waiting for client-side to notify it is ready!"); + + Assert.True(m_AuthoritativeTransform.CanCommitToTransform); + Assert.False(m_NonAuthoritativeTransform.CanCommitToTransform); - Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for client-side to notify it is ready!"); yield return base.OnServerAndClientsConnected(); } - // TODO: rewrite after perms & authority changes - [UnityTest] - public IEnumerator TestAuthoritativeTransformChangeOneAtATime([Values] bool testLocalTransform) + public enum TransformSpace { - // Get the client player's NetworkTransform for both instances - var authoritativeNetworkTransform = m_ServerSideClientPlayer.GetComponent(); - var otherSideNetworkTransform = m_ClientSideClientPlayer.GetComponent(); - - Assert.That(!otherSideNetworkTransform.CanCommitToTransform); - Assert.That(authoritativeNetworkTransform.CanCommitToTransform); + World, + Local + } - if (authoritativeNetworkTransform.CanCommitToTransform) - { - authoritativeNetworkTransform.InLocalSpace = testLocalTransform; - } + public enum OverrideState + { + Update, + CommitToTransform + } - if (otherSideNetworkTransform.CanCommitToTransform) - { - otherSideNetworkTransform.InLocalSpace = testLocalTransform; - } + /// + /// Tests changing all axial values one at a time. + /// These tests are performed: + /// - While in local space and world space + /// - While interpolation is enabled and disabled + /// - Using the TryCommitTransformToServer "override" that can be used + /// from a child derived or external class. + /// + [UnityTest] + public IEnumerator TestAuthoritativeTransformChangeOneAtATime([Values] TransformSpace testLocalTransform, [Values] Interpolation interpolation, [Values] OverrideState overideState) + { + var overrideUpdate = overideState == OverrideState.CommitToTransform; + m_AuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate; + m_NonAuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate; - float approximation = 0.05f; + m_AuthoritativeTransform.InLocalSpace = testLocalTransform == TransformSpace.Local; // test position - var authPlayerTransform = authoritativeNetworkTransform.transform; + var authPlayerTransform = overrideUpdate ? m_OwnerTransform.transform : m_AuthoritativeTransform.transform; - Assert.AreEqual(Vector3.zero, otherSideNetworkTransform.transform.position, "server side pos should be zero at first"); // sanity check + Assert.AreEqual(Vector3.zero, m_NonAuthoritativeTransform.transform.position, "server side pos should be zero at first"); // sanity check authPlayerTransform.position = new Vector3(10, 20, 30); + if (overrideUpdate) + { + m_OwnerTransform.CommitToTransform(); + } - yield return WaitForConditionOrTimeOut(() => otherSideNetworkTransform.transform.position.x > approximation); - - Assert.False(s_GlobalTimeoutHelper.TimedOut, $"timeout while waiting for position change! Otherside value {otherSideNetworkTransform.transform.position.x} vs. Approximation {approximation}"); - - Assert.True(new Vector3(10, 20, 30) == otherSideNetworkTransform.transform.position, $"wrong position on ghost, {otherSideNetworkTransform.transform.position}"); // Vector3 already does float approximation with == + yield return WaitForConditionOrTimeOut(PositionsMatch); + AssertOnTimeout($"Timed out waiting for positions to match"); // test rotation - authPlayerTransform.rotation = Quaternion.Euler(45, 40, 35); // using euler angles instead of quaternions directly to really see issues users might encounter - Assert.AreEqual(Quaternion.identity, otherSideNetworkTransform.transform.rotation, "wrong initial value for rotation"); // sanity check - - yield return WaitForConditionOrTimeOut(() => otherSideNetworkTransform.transform.rotation.eulerAngles.x > approximation); + Assert.AreEqual(Quaternion.identity, m_NonAuthoritativeTransform.transform.rotation, "wrong initial value for rotation"); // sanity check - Assert.False(s_GlobalTimeoutHelper.TimedOut, "timeout while waiting for rotation change"); + authPlayerTransform.rotation = Quaternion.Euler(45, 40, 35); // using euler angles instead of quaternions directly to really see issues users might encounter + if (overrideUpdate) + { + m_OwnerTransform.CommitToTransform(); + } - // approximation needed here since eulerAngles isn't super precise. - Assert.LessOrEqual(Math.Abs(45 - otherSideNetworkTransform.transform.rotation.eulerAngles.x), approximation, $"wrong rotation on ghost on x, got {otherSideNetworkTransform.transform.rotation.eulerAngles.x}"); - Assert.LessOrEqual(Math.Abs(40 - otherSideNetworkTransform.transform.rotation.eulerAngles.y), approximation, $"wrong rotation on ghost on y, got {otherSideNetworkTransform.transform.rotation.eulerAngles.y}"); - Assert.LessOrEqual(Math.Abs(35 - otherSideNetworkTransform.transform.rotation.eulerAngles.z), approximation, $"wrong rotation on ghost on z, got {otherSideNetworkTransform.transform.rotation.eulerAngles.z}"); + yield return WaitForConditionOrTimeOut(RotationsMatch); + AssertOnTimeout($"Timed out waiting for rotations to match"); - // test scale - UnityEngine.Assertions.Assert.AreApproximatelyEqual(1f, otherSideNetworkTransform.transform.lossyScale.x, "wrong initial value for scale"); // sanity check - UnityEngine.Assertions.Assert.AreApproximatelyEqual(1f, otherSideNetworkTransform.transform.lossyScale.y, "wrong initial value for scale"); // sanity check - UnityEngine.Assertions.Assert.AreApproximatelyEqual(1f, otherSideNetworkTransform.transform.lossyScale.z, "wrong initial value for scale"); // sanity check authPlayerTransform.localScale = new Vector3(2, 3, 4); + if (overrideUpdate) + { + m_OwnerTransform.CommitToTransform(); + } - yield return WaitForConditionOrTimeOut(() => otherSideNetworkTransform.transform.lossyScale.x > 1f + approximation); - - Assert.False(s_GlobalTimeoutHelper.TimedOut, "timeout while waiting for scale change"); - - UnityEngine.Assertions.Assert.AreApproximatelyEqual(2f, otherSideNetworkTransform.transform.lossyScale.x, "wrong scale on ghost"); - UnityEngine.Assertions.Assert.AreApproximatelyEqual(3f, otherSideNetworkTransform.transform.lossyScale.y, "wrong scale on ghost"); - UnityEngine.Assertions.Assert.AreApproximatelyEqual(4f, otherSideNetworkTransform.transform.lossyScale.z, "wrong scale on ghost"); - - // todo reparent and test - // todo test all public API + yield return WaitForConditionOrTimeOut(ScaleValuesMatch); + AssertOnTimeout($"Timed out waiting for scale values to match"); } + /// + /// Test to verify nonAuthority cannot change the transform directly + /// [UnityTest] - public IEnumerator TestCantChangeTransformFromOtherSideAuthority([Values] bool testClientAuthority) + public IEnumerator VerifyNonAuthorityCantChangeTransform([Values] Interpolation interpolation) { - // Get the client player's NetworkTransform for both instances - var authoritativeNetworkTransform = m_ServerSideClientPlayer.GetComponent(); - var otherSideNetworkTransform = m_ClientSideClientPlayer.GetComponent(); + m_AuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate; + m_NonAuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate; + Assert.AreEqual(Vector3.zero, m_NonAuthoritativeTransform.transform.position, "other side pos should be zero at first"); // sanity check - Assert.AreEqual(Vector3.zero, otherSideNetworkTransform.transform.position, "other side pos should be zero at first"); // sanity check + m_NonAuthoritativeTransform.transform.position = new Vector3(4, 5, 6); - otherSideNetworkTransform.transform.position = new Vector3(4, 5, 6); + yield return s_DefaultWaitForTick; + Assert.AreEqual(Vector3.zero, m_NonAuthoritativeTransform.transform.position, "[Position] NonAuthority was able to change the position!"); + + var nonAuthorityRotation = m_NonAuthoritativeTransform.transform.rotation; + var originalNonAuthorityEulerRotation = nonAuthorityRotation.eulerAngles; + var nonAuthorityEulerRotation = originalNonAuthorityEulerRotation; + // Verify rotation is not marked dirty when rotated by half of the threshold + nonAuthorityEulerRotation.y += 20.0f; + nonAuthorityRotation.eulerAngles = nonAuthorityEulerRotation; + m_NonAuthoritativeTransform.transform.rotation = nonAuthorityRotation; yield return s_DefaultWaitForTick; + var nonAuthorityCurrentEuler = m_NonAuthoritativeTransform.transform.rotation.eulerAngles; + Assert.True(originalNonAuthorityEulerRotation.Equals(nonAuthorityCurrentEuler), "[Rotation] NonAuthority was able to change the rotation!"); - Assert.AreEqual(Vector3.zero, otherSideNetworkTransform.transform.position, "got authority error, but other side still moved!"); -#if NGO_TRANSFORM_DEBUG - // We are no longer emitting this warning, and we are banishing tests that rely on console output, so - // needs re-implementation - // TODO: This should be a separate test - verify 1 behavior per test - LogAssert.Expect(LogType.Warning, new Regex(".*without authority detected.*")); -#endif - } + var nonAuthorityScale = m_NonAuthoritativeTransform.transform.localScale; + m_NonAuthoritativeTransform.transform.localScale = nonAuthorityScale * 100; + yield return s_DefaultWaitForTick; + + Assert.True(nonAuthorityScale.Equals(m_NonAuthoritativeTransform.transform.localScale), "[Scale] NonAuthority was able to change the scale!"); + } /// /// Validates that rotation checks don't produce false positive /// results when rolling over between 0 and 360 degrees /// [UnityTest] - public IEnumerator TestRotationThresholdDeltaCheck() + public IEnumerator TestRotationThresholdDeltaCheck([Values] Interpolation interpolation) { - // Get the client player's NetworkTransform for both instances - var authoritativeNetworkTransform = m_ServerSideClientPlayer.GetComponent(); - var otherSideNetworkTransform = m_ClientSideClientPlayer.GetComponent(); - otherSideNetworkTransform.RotAngleThreshold = authoritativeNetworkTransform.RotAngleThreshold = 5.0f; + m_AuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate; + m_NonAuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate; + + m_NonAuthoritativeTransform.RotAngleThreshold = m_AuthoritativeTransform.RotAngleThreshold = 5.0f; - var halfThreshold = authoritativeNetworkTransform.RotAngleThreshold * 0.5001f; - var serverRotation = authoritativeNetworkTransform.transform.rotation; - var serverEulerRotation = serverRotation.eulerAngles; + var halfThreshold = m_AuthoritativeTransform.RotAngleThreshold * 0.5001f; + var authorityRotation = m_AuthoritativeTransform.transform.rotation; + var authorityEulerRotation = authorityRotation.eulerAngles; // Verify rotation is not marked dirty when rotated by half of the threshold - serverEulerRotation.y += halfThreshold; - serverRotation.eulerAngles = serverEulerRotation; - authoritativeNetworkTransform.transform.rotation = serverRotation; - var results = authoritativeNetworkTransform.ApplyState(); - Assert.IsFalse(results.isRotationDirty, $"Rotation is dirty when rotation threshold is {authoritativeNetworkTransform.RotAngleThreshold} degrees and only adjusted by {halfThreshold} degrees!"); + authorityEulerRotation.y += halfThreshold; + authorityRotation.eulerAngles = authorityEulerRotation; + m_AuthoritativeTransform.transform.rotation = authorityRotation; + var results = m_AuthoritativeTransform.ApplyState(); + Assert.IsFalse(results.isRotationDirty, $"Rotation is dirty when rotation threshold is {m_AuthoritativeTransform.RotAngleThreshold} degrees and only adjusted by {halfThreshold} degrees!"); yield return s_DefaultWaitForTick; // Verify rotation is marked dirty when rotated by another half threshold value - serverEulerRotation.y += halfThreshold; - serverRotation.eulerAngles = serverEulerRotation; - authoritativeNetworkTransform.transform.rotation = serverRotation; - results = authoritativeNetworkTransform.ApplyState(); - Assert.IsTrue(results.isRotationDirty, $"Rotation was not dirty when rotated by the threshold value: {authoritativeNetworkTransform.RotAngleThreshold} degrees!"); + authorityEulerRotation.y += halfThreshold; + authorityRotation.eulerAngles = authorityEulerRotation; + m_AuthoritativeTransform.transform.rotation = authorityRotation; + results = m_AuthoritativeTransform.ApplyState(); + Assert.IsTrue(results.isRotationDirty, $"Rotation was not dirty when rotated by the threshold value: {m_AuthoritativeTransform.RotAngleThreshold} degrees!"); yield return s_DefaultWaitForTick; //Reset rotation back to zero on all axis - serverRotation.eulerAngles = serverEulerRotation = Vector3.zero; - authoritativeNetworkTransform.transform.rotation = serverRotation; + authorityRotation.eulerAngles = authorityEulerRotation = Vector3.zero; + m_AuthoritativeTransform.transform.rotation = authorityRotation; yield return s_DefaultWaitForTick; // Rotate by 360 minus halfThreshold (which is really just negative halfThreshold) and verify rotation is not marked dirty - serverEulerRotation.y = 360 - halfThreshold; - serverRotation.eulerAngles = serverEulerRotation; - authoritativeNetworkTransform.transform.rotation = serverRotation; - results = authoritativeNetworkTransform.ApplyState(); + authorityEulerRotation.y = 360 - halfThreshold; + authorityRotation.eulerAngles = authorityEulerRotation; + m_AuthoritativeTransform.transform.rotation = authorityRotation; + results = m_AuthoritativeTransform.ApplyState(); - Assert.IsFalse(results.isRotationDirty, $"Rotation is dirty when rotation threshold is {authoritativeNetworkTransform.RotAngleThreshold} degrees and only adjusted by " + - $"{Mathf.DeltaAngle(0, serverEulerRotation.y)} degrees!"); + Assert.IsFalse(results.isRotationDirty, $"Rotation is dirty when rotation threshold is {m_AuthoritativeTransform.RotAngleThreshold} degrees and only adjusted by " + + $"{Mathf.DeltaAngle(0, authorityEulerRotation.y)} degrees!"); - serverEulerRotation.y -= halfThreshold; - serverRotation.eulerAngles = serverEulerRotation; - authoritativeNetworkTransform.transform.rotation = serverRotation; - results = authoritativeNetworkTransform.ApplyState(); + authorityEulerRotation.y -= halfThreshold; + authorityRotation.eulerAngles = authorityEulerRotation; + m_AuthoritativeTransform.transform.rotation = authorityRotation; + results = m_AuthoritativeTransform.ApplyState(); - Assert.IsTrue(results.isRotationDirty, $"Rotation was not dirty when rotated by {Mathf.DeltaAngle(0, serverEulerRotation.y)} degrees!"); + Assert.IsTrue(results.isRotationDirty, $"Rotation was not dirty when rotated by {Mathf.DeltaAngle(0, authorityEulerRotation.y)} degrees!"); //Reset rotation back to zero on all axis - serverRotation.eulerAngles = serverEulerRotation = Vector3.zero; - authoritativeNetworkTransform.transform.rotation = serverRotation; + authorityRotation.eulerAngles = authorityEulerRotation = Vector3.zero; + m_AuthoritativeTransform.transform.rotation = authorityRotation; yield return s_DefaultWaitForTick; - serverEulerRotation.y -= halfThreshold; - serverRotation.eulerAngles = serverEulerRotation; - authoritativeNetworkTransform.transform.rotation = serverRotation; - results = authoritativeNetworkTransform.ApplyState(); - Assert.IsFalse(results.isRotationDirty, $"Rotation is dirty when rotation threshold is {authoritativeNetworkTransform.RotAngleThreshold} degrees and only adjusted by " + - $"{Mathf.DeltaAngle(0, serverEulerRotation.y)} degrees!"); + authorityEulerRotation.y -= halfThreshold; + authorityRotation.eulerAngles = authorityEulerRotation; + m_AuthoritativeTransform.transform.rotation = authorityRotation; + results = m_AuthoritativeTransform.ApplyState(); + Assert.IsFalse(results.isRotationDirty, $"Rotation is dirty when rotation threshold is {m_AuthoritativeTransform.RotAngleThreshold} degrees and only adjusted by " + + $"{Mathf.DeltaAngle(0, authorityEulerRotation.y)} degrees!"); + + authorityEulerRotation.y -= halfThreshold; + authorityRotation.eulerAngles = authorityEulerRotation; + m_AuthoritativeTransform.transform.rotation = authorityRotation; + results = m_AuthoritativeTransform.ApplyState(); + + Assert.IsTrue(results.isRotationDirty, $"Rotation was not dirty when rotated by {Mathf.DeltaAngle(0, authorityEulerRotation.y)} degrees!"); + } - serverEulerRotation.y -= halfThreshold; - serverRotation.eulerAngles = serverEulerRotation; - authoritativeNetworkTransform.transform.rotation = serverRotation; - results = authoritativeNetworkTransform.ApplyState(); + private bool ValidateBitSetValues(NetworkTransform.NetworkTransformState serverState, NetworkTransform.NetworkTransformState clientState) + { + if (serverState.HasPositionX == clientState.HasPositionX && serverState.HasPositionY == clientState.HasPositionY && serverState.HasPositionZ == clientState.HasPositionZ && + serverState.HasRotAngleX == clientState.HasRotAngleX && serverState.HasRotAngleY == clientState.HasRotAngleY && serverState.HasRotAngleZ == clientState.HasRotAngleZ && + serverState.HasScaleX == clientState.HasScaleX && serverState.HasScaleY == clientState.HasScaleY && serverState.HasScaleZ == clientState.HasScaleZ) + { + return true; + } + return false; + } - Assert.IsTrue(results.isRotationDirty, $"Rotation was not dirty when rotated by {Mathf.DeltaAngle(0, serverEulerRotation.y)} degrees!"); + /// + /// Test to make sure that the bitset value is updated properly + /// + [UnityTest] + public IEnumerator TestBitsetValue([Values] Interpolation interpolation) + { + m_AuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate; + m_NonAuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate; + m_NonAuthoritativeTransform.RotAngleThreshold = m_AuthoritativeTransform.RotAngleThreshold = 0.1f; + yield return s_DefaultWaitForTick; + + m_AuthoritativeTransform.transform.rotation = Quaternion.Euler(1, 2, 3); + var serverLastSentState = m_AuthoritativeTransform.GetLastSentState(); + var clientReplicatedState = m_NonAuthoritativeTransform.ReplicatedNetworkState.Value; + yield return WaitForConditionOrTimeOut(() => ValidateBitSetValues(serverLastSentState, clientReplicatedState)); + AssertOnTimeout($"Timed out waiting for Authoritative Bitset state to equal NonAuthoritative replicated Bitset state!"); + + yield return WaitForConditionOrTimeOut(RotationsMatch); + AssertOnTimeout($"[Timed-Out] Authoritative rotation {m_AuthoritativeTransform.transform.rotation.eulerAngles} != Non-Authoritative rotation {m_NonAuthoritativeTransform.transform.rotation.eulerAngles}"); + } + + private float m_DetectedPotentialInterpolatedTeleport; + + /// + /// The tests teleporting with and without interpolation + /// + [UnityTest] + public IEnumerator TeleportTest([Values] Interpolation interpolation) + { + m_AuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate; + m_NonAuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate; + var authTransform = m_AuthoritativeTransform.transform; + var nonAuthPosition = m_NonAuthoritativeTransform.transform.position; + var currentTick = m_AuthoritativeTransform.NetworkManager.ServerTime.Tick; + m_DetectedPotentialInterpolatedTeleport = 0.0f; + var teleportDestination = new Vector3(100.00f, 100.00f, 100.00f); + var targetDistance = Mathf.Abs(Vector3.Distance(nonAuthPosition, teleportDestination)); + m_AuthoritativeTransform.Teleport(new Vector3(100.00f, 100.00f, 100.00f), authTransform.rotation, authTransform.localScale); + yield return WaitForConditionOrTimeOut(() => TeleportPositionMatches(nonAuthPosition)); + + AssertOnTimeout($"[Timed-Out][Teleport] Timed out waiting for NonAuthoritative position to !"); + Assert.IsTrue(m_DetectedPotentialInterpolatedTeleport == 0.0f, $"Detected possible interpolation on non-authority side! NonAuthority distance: {m_DetectedPotentialInterpolatedTeleport} | Target distance: {targetDistance}"); + } + + + /// + /// This test validates the method + /// usage for the non-authoritative side. It will either be the owner or the server making/requesting state changes. + /// This validates that: + /// - The owner authoritative mode can still be controlled by the server (i.e. owner authoritative with server authority override capabilities) + /// - The server authoritative mode can still be directed by the client owner. + /// + /// + /// This also tests that the original server authoritative model with client-owner driven NetworkTransforms is preserved. + /// + [UnityTest] + public IEnumerator NonAuthorityOwnerSettingStateTest([Values] Interpolation interpolation) + { + var interpolate = interpolation == Interpolation.EnableInterpolate; + m_AuthoritativeTransform.Interpolate = interpolate; + m_NonAuthoritativeTransform.Interpolate = interpolate; + m_NonAuthoritativeTransform.RotAngleThreshold = m_AuthoritativeTransform.RotAngleThreshold = 0.1f; + + // Test one parameter at a time first + var newPosition = new Vector3(125f, 35f, 65f); + var newRotation = Quaternion.Euler(1, 2, 3); + var newScale = new Vector3(2.0f, 2.0f, 2.0f); + m_NonAuthoritativeTransform.SetState(newPosition, null, null, interpolate); + yield return WaitForConditionOrTimeOut(() => PositionsMatchesValue(newPosition)); + AssertOnTimeout($"Timed out waiting for non-authoritative position state request to be applied!"); + Assert.True(Aproximately(newPosition, m_AuthoritativeTransform.transform.position), "Authoritative position does not match!"); + Assert.True(Aproximately(newPosition, m_NonAuthoritativeTransform.transform.position), "Non-Authoritative position does not match!"); + + m_NonAuthoritativeTransform.SetState(null, newRotation, null, interpolate); + yield return WaitForConditionOrTimeOut(() => RotationMatchesValue(newRotation.eulerAngles)); + AssertOnTimeout($"Timed out waiting for non-authoritative rotation state request to be applied!"); + Assert.True(Aproximately(newRotation.eulerAngles, m_AuthoritativeTransform.transform.rotation.eulerAngles), "Authoritative rotation does not match!"); + Assert.True(Aproximately(newRotation.eulerAngles, m_NonAuthoritativeTransform.transform.rotation.eulerAngles), "Non-Authoritative rotation does not match!"); + + m_NonAuthoritativeTransform.SetState(null, null, newScale, interpolate); + yield return WaitForConditionOrTimeOut(() => ScaleMatchesValue(newScale)); + AssertOnTimeout($"Timed out waiting for non-authoritative scale state request to be applied!"); + Assert.True(Aproximately(newScale, m_AuthoritativeTransform.transform.localScale), "Authoritative scale does not match!"); + Assert.True(Aproximately(newScale, m_NonAuthoritativeTransform.transform.localScale), "Non-Authoritative scale does not match!"); + + // Test all parameters at once + newPosition = new Vector3(55f, 95f, -25f); + newRotation = Quaternion.Euler(20, 5, 322); + newScale = new Vector3(0.5f, 0.5f, 0.5f); + + m_NonAuthoritativeTransform.SetState(newPosition, newRotation, newScale, interpolate); + yield return WaitForConditionOrTimeOut(() => PositionRotationScaleMatches(newPosition, newRotation.eulerAngles, newScale)); + AssertOnTimeout($"Timed out waiting for non-authoritative position, rotation, and scale state request to be applied!"); + Assert.True(Aproximately(newPosition, m_AuthoritativeTransform.transform.position), "Authoritative position does not match!"); + Assert.True(Aproximately(newPosition, m_NonAuthoritativeTransform.transform.position), "Non-Authoritative position does not match!"); + Assert.True(Aproximately(newRotation.eulerAngles, m_AuthoritativeTransform.transform.rotation.eulerAngles), "Authoritative rotation does not match!"); + Assert.True(Aproximately(newRotation.eulerAngles, m_NonAuthoritativeTransform.transform.rotation.eulerAngles), "Non-Authoritative rotation does not match!"); + Assert.True(Aproximately(newScale, m_AuthoritativeTransform.transform.localScale), "Authoritative scale does not match!"); + Assert.True(Aproximately(newScale, m_NonAuthoritativeTransform.transform.localScale), "Non-Authoritative scale does not match!"); + } + + private bool Aproximately(float x, float y) + { + return Mathf.Abs(x - y) <= k_AproximateDeltaVariance; + } + + private bool Aproximately(Vector3 a, Vector3 b) + { + return Mathf.Abs(a.x - b.x) <= k_AproximateDeltaVariance && + Mathf.Abs(a.y - b.y) <= k_AproximateDeltaVariance && + Mathf.Abs(a.z - b.z) <= k_AproximateDeltaVariance; + } + + private const float k_AproximateDeltaVariance = 0.01f; + private bool PositionsMatchesValue(Vector3 positionToMatch) + { + var authorityPosition = m_AuthoritativeTransform.transform.position; + var nonAuthorityPosition = m_NonAuthoritativeTransform.transform.position; + var auhtorityIsEqual = Aproximately(authorityPosition, positionToMatch); + var nonauthorityIsEqual = Aproximately(nonAuthorityPosition, positionToMatch); + + if (!auhtorityIsEqual) + { + VerboseDebug($"Authority position {authorityPosition} != position to match: {positionToMatch}!"); + } + if (!nonauthorityIsEqual) + { + VerboseDebug($"NonAuthority position {nonAuthorityPosition} != position to match: {positionToMatch}!"); + } + return auhtorityIsEqual && nonauthorityIsEqual; + } + + private bool RotationMatchesValue(Vector3 rotationEulerToMatch) + { + var authorityRotationEuler = m_AuthoritativeTransform.transform.rotation.eulerAngles; + var nonAuthorityRotationEuler = m_NonAuthoritativeTransform.transform.rotation.eulerAngles; + var auhtorityIsEqual = Aproximately(authorityRotationEuler, rotationEulerToMatch); + var nonauthorityIsEqual = Aproximately(nonAuthorityRotationEuler, rotationEulerToMatch); + + if (!auhtorityIsEqual) + { + VerboseDebug($"Authority rotation {authorityRotationEuler} != rotation to match: {rotationEulerToMatch}!"); + } + if (!nonauthorityIsEqual) + { + VerboseDebug($"NonAuthority position {nonAuthorityRotationEuler} != rotation to match: {rotationEulerToMatch}!"); + } + return auhtorityIsEqual && nonauthorityIsEqual; + } + + private bool ScaleMatchesValue(Vector3 scaleToMatch) + { + var authorityScale = m_AuthoritativeTransform.transform.localScale; + var nonAuthorityScale = m_NonAuthoritativeTransform.transform.localScale; + var auhtorityIsEqual = Aproximately(authorityScale, scaleToMatch); + var nonauthorityIsEqual = Aproximately(nonAuthorityScale, scaleToMatch); + + if (!auhtorityIsEqual) + { + VerboseDebug($"Authority scale {authorityScale} != scale to match: {scaleToMatch}!"); + } + if (!nonauthorityIsEqual) + { + VerboseDebug($"NonAuthority scale {nonAuthorityScale} != scale to match: {scaleToMatch}!"); + } + return auhtorityIsEqual && nonauthorityIsEqual; + } + + + private bool TeleportPositionMatches(Vector3 nonAuthorityOriginalPosition) + { + var nonAuthorityPosition = m_NonAuthoritativeTransform.transform.position; + var authorityPosition = m_AuthoritativeTransform.transform.position; + var targetDistance = Mathf.Abs(Vector3.Distance(nonAuthorityOriginalPosition, authorityPosition)); + var nonAuthorityCurrentDistance = Mathf.Abs(Vector3.Distance(nonAuthorityPosition, nonAuthorityOriginalPosition)); + if (!Aproximately(targetDistance, nonAuthorityCurrentDistance)) + { + if (nonAuthorityCurrentDistance >= 0.15f * targetDistance && nonAuthorityCurrentDistance <= 0.75f * targetDistance) + { + m_DetectedPotentialInterpolatedTeleport = nonAuthorityCurrentDistance; + } + return false; + } + var xIsEqual = Aproximately(authorityPosition.x, nonAuthorityPosition.x); + var yIsEqual = Aproximately(authorityPosition.y, nonAuthorityPosition.y); + var zIsEqual = Aproximately(authorityPosition.z, nonAuthorityPosition.z); + if (!xIsEqual || !yIsEqual || !zIsEqual) + { + VerboseDebug($"Authority position {authorityPosition} != NonAuthority position {nonAuthorityPosition}"); + } + return xIsEqual && yIsEqual && zIsEqual; ; + } + + private bool PositionRotationScaleMatches(Vector3 position, Vector3 eulerRotation, Vector3 scale) + { + return PositionsMatchesValue(position) && RotationMatchesValue(eulerRotation) && ScaleMatchesValue(scale); + } + + private bool RotationsMatch() + { + var authorityEulerRotation = m_AuthoritativeTransform.transform.rotation.eulerAngles; + var nonAuthorityEulerRotation = m_NonAuthoritativeTransform.transform.rotation.eulerAngles; + var xIsEqual = Aproximately(authorityEulerRotation.x, nonAuthorityEulerRotation.x); + var yIsEqual = Aproximately(authorityEulerRotation.y, nonAuthorityEulerRotation.y); + var zIsEqual = Aproximately(authorityEulerRotation.z, nonAuthorityEulerRotation.z); + if (!xIsEqual || !yIsEqual || !zIsEqual) + { + VerboseDebug($"Authority rotation {authorityEulerRotation} != NonAuthority rotation {nonAuthorityEulerRotation}"); + } + return xIsEqual && yIsEqual && zIsEqual; + } + + private bool PositionsMatch() + { + var authorityPosition = m_AuthoritativeTransform.transform.position; + var nonAuthorityPosition = m_NonAuthoritativeTransform.transform.position; + var xIsEqual = Aproximately(authorityPosition.x, nonAuthorityPosition.x); + var yIsEqual = Aproximately(authorityPosition.y, nonAuthorityPosition.y); + var zIsEqual = Aproximately(authorityPosition.z, nonAuthorityPosition.z); + if (!xIsEqual || !yIsEqual || !zIsEqual) + { + VerboseDebug($"Authority position {authorityPosition} != NonAuthority position {nonAuthorityPosition}"); + } + return xIsEqual && yIsEqual && zIsEqual; + } + + private bool ScaleValuesMatch() + { + var authorityScale = m_AuthoritativeTransform.transform.localScale; + var nonAuthorityScale = m_NonAuthoritativeTransform.transform.localScale; + var xIsEqual = Aproximately(authorityScale.x, nonAuthorityScale.x); + var yIsEqual = Aproximately(authorityScale.y, nonAuthorityScale.y); + var zIsEqual = Aproximately(authorityScale.z, nonAuthorityScale.z); + if (!xIsEqual || !yIsEqual || !zIsEqual) + { + VerboseDebug($"Authority scale {authorityScale} != NonAuthority scale {nonAuthorityScale}"); + } + return xIsEqual && yIsEqual && zIsEqual; } - /* - * ownership change - * test teleport with interpolation - * test teleport without interpolation - * test dynamic spawning - */ protected override IEnumerator OnTearDown() { - UnityEngine.Object.DestroyImmediate(m_PlayerPrefab); + m_EnableVerboseDebug = false; + Object.DestroyImmediate(m_PlayerPrefab); yield return base.OnTearDown(); } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs index 2fca8ea33f..8ed5b78849 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs @@ -10,21 +10,19 @@ namespace Unity.Netcode.RuntimeTests { public class TransformInterpolationObject : NetworkBehaviour { + // Set the minimum threshold which we will use as our margin of error + public const float MinThreshold = 0.001f; + public bool CheckPosition; public bool IsMoving; public bool IsFixed; private void Update() { - // Since the local position is transformed from local to global and vice-versa on the server and client - // it may accumulate some error. We allow an error of 0.01 over the range of 1000 used in this test. - // This requires precision to 5 digits, so it doesn't weaken the test, while preventing spurious failures - const float maxRoundingError = 0.01f; - // Check the position of the nested object on the client if (CheckPosition) { - if (transform.position.y < -maxRoundingError || transform.position.y > 100.0f + maxRoundingError) + if (transform.position.y < -MinThreshold || transform.position.y > 100.0f + MinThreshold) { Debug.LogError($"Interpolation failure. transform.position.y is {transform.position.y}. Should be between 0.0 and 100.0"); } @@ -65,7 +63,8 @@ public class TransformInterpolationTests : NetcodeIntegrationTest protected override void OnServerAndClientsCreated() { m_PrefabToSpawn = CreateNetworkObjectPrefab("InterpTestObject"); - m_PrefabToSpawn.AddComponent(); + var networkTransform = m_PrefabToSpawn.AddComponent(); + networkTransform.PositionThreshold = TransformInterpolationObject.MinThreshold; m_PrefabToSpawn.AddComponent(); } @@ -85,8 +84,6 @@ private IEnumerator RefreshNetworkObjects() m_SpawnedObjectOnClient = s_GlobalNetworkObjects[clientId][m_SpawnedAsNetworkObject.NetworkObjectId]; // make sure the objects are set with the right network manager m_SpawnedObjectOnClient.NetworkManagerOwner = m_ClientNetworkManagers[0]; - - } [UnityTest] diff --git a/testproject/Assets/Prefabs/ObjectToOverride-VariantBlueCylinder.prefab b/testproject/Assets/Prefabs/ObjectToOverride-VariantBlueCylinder.prefab index d584980843..6bcfbaca76 100644 --- a/testproject/Assets/Prefabs/ObjectToOverride-VariantBlueCylinder.prefab +++ b/testproject/Assets/Prefabs/ObjectToOverride-VariantBlueCylinder.prefab @@ -82,5 +82,10 @@ PrefabInstance: propertyPath: m_LocalEulerAnglesHint.z value: 0 objectReference: {fileID: 0} + - target: {fileID: 4600632750638426092, guid: 29cabf623d47bb345a9bb4140e4397d7, + type: 3} + propertyPath: PositionThreshold + value: 0.01 + objectReference: {fileID: 0} m_RemovedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 29cabf623d47bb345a9bb4140e4397d7, type: 3} diff --git a/testproject/Assets/Prefabs/Player.prefab b/testproject/Assets/Prefabs/Player.prefab index 82e49fd5c8..43c603a70e 100644 --- a/testproject/Assets/Prefabs/Player.prefab +++ b/testproject/Assets/Prefabs/Player.prefab @@ -34,8 +34,9 @@ Transform: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 4079352819444256614} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 5, y: 0.625, z: 5} + m_LocalPosition: {x: 0, y: 0.55, z: 0} m_LocalScale: {x: 1.25, y: 1.25, z: 1.25} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 3519470446676406143} m_Father: {fileID: 0} @@ -62,12 +63,11 @@ MonoBehaviour: SyncScaleX: 1 SyncScaleY: 1 SyncScaleZ: 1 - PositionThreshold: 0 - RotAngleThreshold: 0 - ScaleThreshold: 0 + PositionThreshold: 0.01 + RotAngleThreshold: 0.01 + ScaleThreshold: 0.01 InLocalSpace: 0 Interpolate: 1 - FixedSendsPerSecond: 15 --- !u!114 &-3775814466963834669 MonoBehaviour: m_ObjectHideFlags: 0 @@ -103,6 +103,7 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 @@ -239,6 +240,7 @@ Transform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 1.045, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 4079352819444256611} m_RootOrder: 0 @@ -262,6 +264,7 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 diff --git a/testproject/Assets/Prefabs/PlayerCube.prefab b/testproject/Assets/Prefabs/PlayerCube.prefab index d2cf11586c..a3ae1dbaf8 100644 --- a/testproject/Assets/Prefabs/PlayerCube.prefab +++ b/testproject/Assets/Prefabs/PlayerCube.prefab @@ -18,7 +18,6 @@ GameObject: - component: {fileID: 8685790303553767876} - component: {fileID: 3809075828520557319} - component: {fileID: 7138389085065872747} - - component: {fileID: 8585540885791567915} - component: {fileID: 2744080254494315543} m_Layer: 0 m_Name: PlayerCube @@ -37,6 +36,7 @@ Transform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0.5, z: 0} m_LocalScale: {x: 0.5, y: 0.5, z: 0.5} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} m_RootOrder: 0 @@ -49,14 +49,14 @@ Rigidbody: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 8685790303553767886} serializedVersion: 2 - m_Mass: 1 - m_Drag: 0 - m_AngularDrag: 0.05 + m_Mass: 2 + m_Drag: 0.05 + m_AngularDrag: 0.02 m_UseGravity: 1 m_IsKinematic: 1 - m_Interpolate: 0 + m_Interpolate: 1 m_Constraints: 80 - m_CollisionDetection: 0 + m_CollisionDetection: 2 --- !u!65 &8685790303553767873 BoxCollider: m_ObjectHideFlags: 0 @@ -89,6 +89,7 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 @@ -127,9 +128,10 @@ LineRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 8685790303553767886} m_Enabled: 1 - m_CastShadows: 1 - m_ReceiveShadows: 1 + m_CastShadows: 0 + m_ReceiveShadows: 0 m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 m_MotionVectors: 0 m_LightProbeUsage: 0 m_ReflectionProbeUsage: 0 @@ -138,7 +140,7 @@ LineRenderer: m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: - - {fileID: 0} + - {fileID: 2100000, guid: 712559e4bd05f1942a8fd4bfa4e10c56, type: 2} m_StaticBatchInfo: firstSubMesh: 0 subMeshCount: 0 @@ -182,16 +184,16 @@ LineRenderer: m_RotationOrder: 4 colorGradient: serializedVersion: 2 - key0: {r: 0.73333335, g: 0.7137255, b: 0.6666667, a: 1} - key1: {r: 1, g: 1, b: 1, a: 1} - key2: {r: 1, g: 1, b: 1, a: 0} + key0: {r: 0.30566037, g: 0.90742147, b: 1, a: 1} + key1: {r: 0.28166604, g: 0.8183648, b: 0.8679245, a: 1} + key2: {r: 0.0391598, g: 0.9433962, b: 0.89236206, a: 0} key3: {r: 0, g: 0, b: 0, a: 0} key4: {r: 0, g: 0, b: 0, a: 0} key5: {r: 0, g: 0, b: 0, a: 0} key6: {r: 0, g: 0, b: 0, a: 0} key7: {r: 0, g: 0, b: 0, a: 0} ctime0: 0 - ctime1: 25443 + ctime1: 26021 ctime2: 65535 ctime3: 0 ctime4: 0 @@ -206,15 +208,15 @@ LineRenderer: atime5: 0 atime6: 0 atime7: 0 - m_Mode: 0 + m_Mode: 1 m_NumColorKeys: 3 m_NumAlphaKeys: 2 numCornerVertices: 0 numCapVertices: 0 alignment: 0 textureMode: 0 - shadowBias: 0.5 - generateLightingData: 0 + shadowBias: 1 + generateLightingData: 1 m_UseWorldSpace: 1 m_Loop: 0 --- !u!114 &947981134 @@ -245,8 +247,22 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 82b41b172a31546ffba450f1418f4e69, type: 3} m_Name: m_EditorClassIdentifier: - m_Speed: 10 - m_RotSpeed: 2 + SyncPositionX: 1 + SyncPositionY: 1 + SyncPositionZ: 1 + SyncRotAngleX: 0 + SyncRotAngleY: 1 + SyncRotAngleZ: 0 + SyncScaleX: 0 + SyncScaleY: 0 + SyncScaleZ: 0 + PositionThreshold: 0.001 + RotAngleThreshold: 0.01 + ScaleThreshold: 0.01 + InLocalSpace: 0 + Interpolate: 1 + Speed: 7 + RotSpeed: 2 --- !u!114 &3809075828520557319 MonoBehaviour: m_ObjectHideFlags: 0 @@ -271,34 +287,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 3e34656ebae784afca7d1f7f6dc18580, type: 3} m_Name: m_EditorClassIdentifier: - Range: 10 ---- !u!114 &8585540885791567915 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 8685790303553767886} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 54c9647dc784a46bca664910f182491e, type: 3} - m_Name: - m_EditorClassIdentifier: - SyncPositionX: 1 - SyncPositionY: 1 - SyncPositionZ: 1 - SyncRotAngleX: 1 - SyncRotAngleY: 1 - SyncRotAngleZ: 1 - SyncScaleX: 1 - SyncScaleY: 1 - SyncScaleZ: 1 - PositionThreshold: 0 - RotAngleThreshold: 0 - ScaleThreshold: 0 - InLocalSpace: 0 - Interpolate: 1 - FixedSendsPerSecond: 30 + Range: 3 --- !u!114 &2744080254494315543 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/testproject/Assets/References/Scene/Teleporting.asset b/testproject/Assets/References/Scene/Teleporting.asset new file mode 100644 index 0000000000..d15c07527e --- /dev/null +++ b/testproject/Assets/References/Scene/Teleporting.asset @@ -0,0 +1,19 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 39a16938ffb5cd846a9f6df7a686a9c4, type: 3} + m_Name: Teleporting + m_EditorClassIdentifier: + SceneToReference: {fileID: 102900000, guid: efa247d1f78ca694f8d2dcb5672e8f8b, type: 3} + m_IncludedScenes: [] + m_DisplayName: Teleporting + m_ReferencedScenes: + - TeleportSample diff --git a/testproject/Assets/References/Scene/Teleporting.asset.meta b/testproject/Assets/References/Scene/Teleporting.asset.meta new file mode 100644 index 0000000000..50a749f8c8 --- /dev/null +++ b/testproject/Assets/References/Scene/Teleporting.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0509eb053ce4ef749afd8495f13128f1 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Samples/SamplesMenu.unity b/testproject/Assets/Samples/SamplesMenu.unity index 8eb2fa1afb..3267b0f797 100644 --- a/testproject/Assets/Samples/SamplesMenu.unity +++ b/testproject/Assets/Samples/SamplesMenu.unity @@ -2523,8 +2523,9 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_SceneMenus: - - {fileID: 11400000, guid: 7fdc32fee173cca45a4601ba234954d0, type: 2} + - {fileID: 11400000, guid: 0509eb053ce4ef749afd8495f13128f1, type: 2} - {fileID: 11400000, guid: 9a8d9296fb33f794f95514bf38de3cf9, type: 2} + - {fileID: 11400000, guid: 7fdc32fee173cca45a4601ba234954d0, type: 2} - {fileID: 11400000, guid: 21aae92071ad50448a45b013d8346639, type: 2} - {fileID: 11400000, guid: 660535b6e155b5b4bbede52313fcb32e, type: 2} - {fileID: 11400000, guid: d2e34ed37c087154dbd7f89fd463801b, type: 2} diff --git a/testproject/Assets/Samples/Teleport.meta b/testproject/Assets/Samples/Teleport.meta new file mode 100644 index 0000000000..87764ee3da --- /dev/null +++ b/testproject/Assets/Samples/Teleport.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: cce8b52742a026348a34cae6ea568558 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Samples/Teleport/CollideTeleporter.cs b/testproject/Assets/Samples/Teleport/CollideTeleporter.cs new file mode 100644 index 0000000000..77222a86c3 --- /dev/null +++ b/testproject/Assets/Samples/Teleport/CollideTeleporter.cs @@ -0,0 +1,57 @@ +using UnityEngine; +using Unity.Netcode.Components; +using Unity.Netcode; + +public class CollideTeleporter : MonoBehaviour +{ + [Tooltip("The destination GameObject transform. All rotation and scale will be applied, but position values can be ignored by setting the Preserve properties.")] + public GameObject Destination; + [Tooltip("When checked, the x-axis position value will remain the same during teleporting.")] + public bool Preserve_XAxis; + [Tooltip("When checked, the y-axis position value will remain the same during teleporting.")] + public bool Preserve_YAxis; + [Tooltip("When checked, the z-axis position value will remain the same during teleporting.")] + public bool Preserve_ZAxis; + + private void OnCollisionEnter(Collision collision) + { + if (NetworkManager.Singleton == null || !NetworkManager.Singleton.IsListening) + { + return; + } + var playerMover = collision.gameObject.GetComponent(); + if (playerMover == null || playerMover.IsTeleporting) + { + return; + } + + var networkTransform = collision.gameObject.GetComponent(); + if (networkTransform == null || Destination == null) + { + return; + } + var objectTransform = networkTransform.transform; + var position = Destination.transform.position; + + if (Preserve_XAxis) + { + position.x = objectTransform.position.x; + } + + if (Preserve_YAxis) + { + position.y = objectTransform.position.y; + } + else + { + position.y = transform.position.y + 0.1f; + } + + if (Preserve_ZAxis) + { + position.z = objectTransform.position.z; + } + + playerMover.Telporting(position); + } +} diff --git a/testproject/Assets/Samples/Teleport/CollideTeleporter.cs.meta b/testproject/Assets/Samples/Teleport/CollideTeleporter.cs.meta new file mode 100644 index 0000000000..564469ad09 --- /dev/null +++ b/testproject/Assets/Samples/Teleport/CollideTeleporter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1de461850fd18574584d884cc31ed103 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Samples/Teleport/TeleportSample.unity b/testproject/Assets/Samples/Teleport/TeleportSample.unity new file mode 100644 index 0000000000..8654a3e2a4 --- /dev/null +++ b/testproject/Assets/Samples/Teleport/TeleportSample.unity @@ -0,0 +1,2881 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_IndirectSpecularColor: {r: 0.44657755, g: 0.4964127, b: 0.57481825, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1001 &9495347 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + m_TransformParent: {fileID: 1031177251} + m_Modifications: + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_Pivot.x + value: 0.5 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_Pivot.y + value: 0.5 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_RootOrder + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_AnchorMax.x + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_AnchorMax.y + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_AnchorMin.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_AnchorMin.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_SizeDelta.x + value: -952 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_SizeDelta.y + value: -344 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_AnchoredPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_AnchoredPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6963777608485144162, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_Name + value: ConnectionModeButtons + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: d725b5588e1b956458798319e6541d84, type: 3} +--- !u!224 &9495348 stripped +RectTransform: + m_CorrespondingSourceObject: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + m_PrefabInstance: {fileID: 9495347} + m_PrefabAsset: {fileID: 0} +--- !u!1 &53041272 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 53041273} + m_Layer: 0 + m_Name: DestinationBBottomZ + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &53041273 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 53041272} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: -0.96, y: 0, z: -6.95} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 465045809} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &133022763 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 133022766} + - component: {fileID: 133022765} + - component: {fileID: 133022764} + m_Layer: 0 + m_Name: Stats + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &133022764 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 133022763} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 4260285143 + AlwaysReplicateAsRoot: 0 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 +--- !u!114 &133022765 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 133022763} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: cb5f3e55f5dd247129d8a4979b80ebbb, type: 3} + m_Name: + m_EditorClassIdentifier: + m_ClientServerToggle: {fileID: 0} + m_TrackSceneEvents: 1 +--- !u!4 &133022766 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 133022763} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 318.45444, y: 110.697815, z: 216.79077} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 6 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &277959947 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 277959948} + m_Layer: 0 + m_Name: DestinationBFarX + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &277959948 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 277959947} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -6.88, y: 0, z: -0.66} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 465045809} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &422011491 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 422011493} + - component: {fileID: 422011492} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &422011492 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 422011491} + m_Enabled: 1 + serializedVersion: 10 + m_Type: 1 + m_Shape: 0 + m_Color: {r: 0.990566, g: 0.98932636, b: 0.98589355, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.80208 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: 2 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!4 &422011493 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 422011491} + m_LocalRotation: {x: 0.42261827, y: 0, z: 0, w: 0.9063079} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 50, y: 0, z: 0} +--- !u!1 &465045808 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 465045809} + m_Layer: 0 + m_Name: TeleportDestinationB + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &465045809 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 465045808} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -23.5, y: 0.5, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 277959948} + - {fileID: 2071037935} + - {fileID: 878389898} + - {fileID: 53041273} + - {fileID: 4012615692010092074} + m_Father: {fileID: 0} + m_RootOrder: 5 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &539459709 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 539459712} + - component: {fileID: 539459711} + - component: {fileID: 539459710} + m_Layer: 0 + m_Name: NetworkManager + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &539459710 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 539459709} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 6960e84d07fb87f47956e7a81d71c4e6, type: 3} + m_Name: + m_EditorClassIdentifier: + m_ProtocolType: 0 + m_MaxPacketQueueSize: 128 + m_MaxPayloadSize: 128000 + m_MaxSendQueueSize: 1000000 + m_HeartbeatTimeoutMS: 500 + m_ConnectTimeoutMS: 1000 + m_MaxConnectAttempts: 60 + m_DisconnectTimeoutMS: 30000 + ConnectionData: + Address: 127.0.0.1 + Port: 7777 + ServerListenAddress: + DebugSimulator: + PacketDelayMS: 0 + PacketJitterMS: 0 + PacketDropRate: 0 +--- !u!114 &539459711 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 539459709} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 593a2fe42fa9d37498c96f9a383b6521, type: 3} + m_Name: + m_EditorClassIdentifier: + RunInBackground: 1 + LogLevel: 1 + NetworkConfig: + ProtocolVersion: 0 + NetworkTransport: {fileID: 539459710} + PlayerPrefab: {fileID: 8685790303553767886, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + NetworkPrefabs: [] + TickRate: 60 + ClientConnectionBufferTimeout: 10 + ConnectionApproval: 0 + ConnectionData: + EnableTimeResync: 0 + TimeResyncInterval: 30 + EnsureNetworkVariableLengthSafety: 0 + EnableSceneManagement: 1 + ForceSamePrefabs: 1 + RecycleNetworkIds: 1 + NetworkIdRecycleDelay: 120 + RpcHashSize: 0 + LoadSceneTimeOut: 120 + SpawnTimeout: 1 + EnableNetworkLogs: 1 +--- !u!4 &539459712 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 539459709} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &567879417 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 567879420} + - component: {fileID: 567879419} + - component: {fileID: 567879418} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &567879418 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 567879417} + m_Enabled: 1 +--- !u!20 &567879419 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 567879417} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.06755074, g: 0.28341934, b: 0.6226415, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_FocalLength: 50 + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &567879420 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 567879417} + m_LocalRotation: {x: 0.7071068, y: 0, z: 0, w: 0.7071068} + m_LocalPosition: {x: -11.75, y: 20, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0} +--- !u!1 &778998217 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 778998218} + m_Layer: 0 + m_Name: DestinationANearX + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &778998218 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 778998217} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: -6.88, y: 0, z: -0.66} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1111851143} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &878389897 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 878389898} + m_Layer: 0 + m_Name: DestinationBTopZ + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &878389898 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 878389897} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: -0.75, y: 0, z: 6.89} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 465045809} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1031177247 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1031177251} + - component: {fileID: 1031177250} + - component: {fileID: 1031177249} + - component: {fileID: 1031177248} + m_Layer: 5 + m_Name: Canvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1031177248 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1031177247} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &1031177249 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1031177247} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 0 +--- !u!223 &1031177250 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1031177247} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 0 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_AdditionalShaderChannelsFlag: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &1031177251 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1031177247} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1485784454} + - {fileID: 1068532237} + - {fileID: 9495348} + - {fileID: 1632447478} + m_Father: {fileID: 0} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!1001 &1068532236 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + m_TransformParent: {fileID: 1031177251} + m_Modifications: + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_Pivot.x + value: 0.5 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_Pivot.y + value: 0.5 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_RootOrder + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_AnchorMax.x + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_AnchorMax.y + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_AnchorMin.x + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_AnchorMin.y + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_SizeDelta.x + value: 20 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_SizeDelta.y + value: 25 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalScale.x + value: 0.9806 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalScale.y + value: 0.9806 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalScale.z + value: 0.9806 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalRotation.x + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalRotation.y + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalRotation.z + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_AnchoredPosition.x + value: -22.946533 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_AnchoredPosition.y + value: -23.338623 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247795, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_Name + value: ExitButton + objectReference: {fileID: 0} + - target: {fileID: 5266522511616468950, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_SceneMenuToLoad + value: + objectReference: {fileID: 11400000, guid: c10d995498e0c514a853c3506031d3fb, + type: 2} + m_RemovedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 3200770c16e3b2b4ebe7f604154faac7, type: 3} +--- !u!224 &1068532237 stripped +RectTransform: + m_CorrespondingSourceObject: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + m_PrefabInstance: {fileID: 1068532236} + m_PrefabAsset: {fileID: 0} +--- !u!1 &1111851142 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1111851143} + m_Layer: 0 + m_Name: TeleportDestinationA + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1111851143 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1111851142} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -0, y: 0.5, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1346219376} + - {fileID: 778998218} + - {fileID: 1480985473} + - {fileID: 1774593016} + - {fileID: 4012615690793490293} + m_Father: {fileID: 0} + m_RootOrder: 4 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1346219375 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1346219376} + m_Layer: 0 + m_Name: DestinationAFarX + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1346219376 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1346219375} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 6.55, y: 0, z: -0.66} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1111851143} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1480985472 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1480985473} + m_Layer: 0 + m_Name: DestinationATopZ + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1480985473 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1480985472} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0.34, y: 0, z: 6.89} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1111851143} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1485784453 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1485784454} + - component: {fileID: 1485784458} + - component: {fileID: 1485784457} + - component: {fileID: 1485784456} + - component: {fileID: 1485784455} + m_Layer: 5 + m_Name: ServerHostClientDisplay + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1485784454 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1485784453} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1031177251} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0} + m_AnchorMax: {x: 0.5, y: 0} + m_AnchoredPosition: {x: 0, y: 31} + m_SizeDelta: {x: 200, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1485784455 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1485784453} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 1332868523 + AlwaysReplicateAsRoot: 0 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 +--- !u!114 &1485784456 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1485784453} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 7ea6e834b18e2c840a76ce574eb4b144, type: 3} + m_Name: + m_EditorClassIdentifier: + m_DisplayText: {fileID: 1485784457} +--- !u!114 &1485784457 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1485784453} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 0.5058824, b: 0.003921569, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 24 + m_FontStyle: 1 + m_BestFit: 0 + m_MinSize: 1 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 0 + m_Text: Server +--- !u!222 &1485784458 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1485784453} + m_CullTransparentMesh: 1 +--- !u!1 &1632447477 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1632447478} + - component: {fileID: 1632447480} + - component: {fileID: 1632447479} + m_Layer: 5 + m_Name: EventSystem + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1632447478 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1632447477} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1031177251} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1632447479 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1632447477} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3} + m_Name: + m_EditorClassIdentifier: + m_HorizontalAxis: Horizontal + m_VerticalAxis: Vertical + m_SubmitButton: Submit + m_CancelButton: Cancel + m_InputActionsPerSecond: 10 + m_RepeatDelay: 0.5 + m_ForceModuleActive: 0 +--- !u!114 &1632447480 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1632447477} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_FirstSelected: {fileID: 0} + m_sendNavigationEvents: 1 + m_DragThreshold: 10 +--- !u!1 &1774593015 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1774593016} + m_Layer: 0 + m_Name: DestinationABottomZ + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1774593016 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1774593015} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: -0.19, y: 0, z: -6.95} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1111851143} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &2071037934 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2071037935} + m_Layer: 0 + m_Name: DestinationBNearX + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2071037935 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2071037934} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 6.61, y: 0, z: -0.66} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 465045809} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &910007655381311276 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6283120761446335287} + - component: {fileID: 3739510624132112197} + m_Layer: 0 + m_Name: CornerBumper (1) + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!1 &910007656598437491 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6283120762361012840} + - component: {fileID: 3739510625366015514} + m_Layer: 0 + m_Name: CornerBumper (1) + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!1 &1854705290707056910 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2290144463474879011} + - component: {fileID: 4559046434087525967} + m_Layer: 0 + m_Name: CornerBumper (2) + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!1 &1854705291588622417 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2290144462274530172} + - component: {fileID: 4559046435286335248} + m_Layer: 0 + m_Name: CornerBumper (2) + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2290144462274530172 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1854705291588622417} + m_LocalRotation: {x: -0, y: -0.3771283, z: -0, w: -0.92616105} + m_LocalPosition: {x: -29.53, y: 0.98, z: -29.71} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615690793490293} + m_RootOrder: 7 + m_LocalEulerAnglesHint: {x: 0, y: -315.688, z: 0} +--- !u!4 &2290144463474879011 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1854705290707056910} + m_LocalRotation: {x: -0, y: -0.3771283, z: -0, w: -0.92616105} + m_LocalPosition: {x: -29.53, y: 0.98, z: -29.71} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692010092074} + m_RootOrder: 7 + m_LocalEulerAnglesHint: {x: 0, y: -315.688, z: 0} +--- !u!65 &3136259738196406079 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4674276235128641327} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 4, y: 2, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!65 &3136259739378421344 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4674276234231314032} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 4, y: 2, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!65 &3739510624132112197 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 910007655381311276} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 4, y: 2, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!65 &3739510625366015514 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 910007656598437491} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 4, y: 2, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!4 &3910294715920693371 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4674276234231314032} + m_LocalRotation: {x: 0, y: -0.38268343, z: 0, w: 0.92387956} + m_LocalPosition: {x: 29.7, y: 0.98, z: -29.61} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615690793490293} + m_RootOrder: 5 + m_LocalEulerAnglesHint: {x: 0, y: -45, z: 0} +--- !u!4 &3910294717136820516 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4674276235128641327} + m_LocalRotation: {x: 0, y: -0.38268343, z: 0, w: 0.92387956} + m_LocalPosition: {x: 29.7, y: 0.98, z: -29.61} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692010092074} + m_RootOrder: 5 + m_LocalEulerAnglesHint: {x: 0, y: -45, z: 0} +--- !u!4 &4012615690734689956 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615690734689960} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: -30.5, y: 0.49999994, z: 0} + m_LocalScale: {x: 1, y: 3, z: 62} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692010092074} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &4012615690734689958 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615690734689960} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 00cf8ac777c8c42e8967157f70fbfcbf, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &4012615690734689959 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615690734689960} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!1 &4012615690734689960 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4012615690734689956} + - component: {fileID: 4012615690734689959} + - component: {fileID: 4012615690734689958} + - component: {fileID: 4012615690734689961} + - component: {fileID: 4012615690734689962} + m_Layer: 0 + m_Name: Side + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!65 &4012615690734689961 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615690734689960} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 2, y: 1, z: 1} + m_Center: {x: -0.5, y: 0, z: 0} +--- !u!114 &4012615690734689962 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615690734689960} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1de461850fd18574584d884cc31ed103, type: 3} + m_Name: + m_EditorClassIdentifier: + Destination: {fileID: 1346219375} + Preserve_XAxis: 0 + Preserve_YAxis: 0 + Preserve_ZAxis: 1 +--- !u!1 &4012615690780670402 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4012615690780670406} + - component: {fileID: 4012615690780670407} + - component: {fileID: 4012615690780670404} + - component: {fileID: 4012615690780670405} + - component: {fileID: 4012615690780670408} + m_Layer: 0 + m_Name: Side + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!23 &4012615690780670404 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615690780670402} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 00cf8ac777c8c42e8967157f70fbfcbf, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &4012615690780670405 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615690780670402} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 2, y: 1, z: 1} + m_Center: {x: 0.5, y: 0, z: 0} +--- !u!4 &4012615690780670406 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615690780670402} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 30.5, y: 0.49999994, z: 0} + m_LocalScale: {x: 1, y: 3, z: 62} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615690793490293} + m_RootOrder: 4 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &4012615690780670407 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615690780670402} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!114 &4012615690780670408 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615690780670402} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1de461850fd18574584d884cc31ed103, type: 3} + m_Name: + m_EditorClassIdentifier: + Destination: {fileID: 277959947} + Preserve_XAxis: 0 + Preserve_YAxis: 0 + Preserve_ZAxis: 1 +--- !u!1 &4012615690793490290 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4012615690793490293} + m_Layer: 0 + m_Name: SceneLevelGeometryStart + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4012615690793490293 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615690793490290} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: -0.5, z: 0} + m_LocalScale: {x: 0.25, y: 0.25, z: 0.25} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 4012615692280833000} + - {fileID: 4012615691539789097} + - {fileID: 4012615691302274498} + - {fileID: 4012615691934547963} + - {fileID: 4012615690780670406} + - {fileID: 3910294715920693371} + - {fileID: 6283120762361012840} + - {fileID: 2290144462274530172} + - {fileID: 6959258899469919941} + m_Father: {fileID: 1111851143} + m_RootOrder: 4 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &4012615691046896310 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691046896315} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!4 &4012615691046896311 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691046896315} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: -0.50000006, z: 0} + m_LocalScale: {x: 60, y: 1, z: 60} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692010092074} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!65 &4012615691046896312 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691046896315} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 1, y: 1, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!23 &4012615691046896313 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691046896315} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 00cf8ac777c8c42e8967157f70fbfcbf, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!1 &4012615691046896315 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4012615691046896311} + - component: {fileID: 4012615691046896310} + - component: {fileID: 4012615691046896313} + - component: {fileID: 4012615691046896312} + m_Layer: 0 + m_Name: Floor + m_TagString: Floor + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!23 &4012615691302274496 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691302274558} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 00cf8ac777c8c42e8967157f70fbfcbf, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &4012615691302274497 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691302274558} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 1, y: 1, z: 2} + m_Center: {x: 0, y: 0, z: -0.5} +--- !u!4 &4012615691302274498 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691302274558} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0.49999994, z: -30.5} + m_LocalScale: {x: 60, y: 3, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615690793490293} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &4012615691302274499 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691302274558} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!1 &4012615691302274558 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4012615691302274498} + - component: {fileID: 4012615691302274499} + - component: {fileID: 4012615691302274496} + - component: {fileID: 4012615691302274497} + - component: {fileID: 4012615691302274559} + m_Layer: 0 + m_Name: Side + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &4012615691302274559 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691302274558} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1de461850fd18574584d884cc31ed103, type: 3} + m_Name: + m_EditorClassIdentifier: + Destination: {fileID: 1480985472} + Preserve_XAxis: 1 + Preserve_YAxis: 0 + Preserve_ZAxis: 0 +--- !u!65 &4012615691539789092 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691539789093} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 1, y: 1, z: 2} + m_Center: {x: 0, y: 0, z: 0.5} +--- !u!1 &4012615691539789093 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4012615691539789097} + - component: {fileID: 4012615691539789094} + - component: {fileID: 4012615691539789095} + - component: {fileID: 4012615691539789092} + - component: {fileID: 4012615691539789098} + m_Layer: 0 + m_Name: Side + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!33 &4012615691539789094 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691539789093} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &4012615691539789095 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691539789093} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 00cf8ac777c8c42e8967157f70fbfcbf, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!4 &4012615691539789097 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691539789093} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0.49999994, z: 30.5} + m_LocalScale: {x: 60, y: 3, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615690793490293} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &4012615691539789098 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691539789093} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1de461850fd18574584d884cc31ed103, type: 3} + m_Name: + m_EditorClassIdentifier: + Destination: {fileID: 1774593015} + Preserve_XAxis: 1 + Preserve_YAxis: 0 + Preserve_ZAxis: 0 +--- !u!65 &4012615691934547958 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691934547959} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 2, y: 1, z: 1} + m_Center: {x: -0.5, y: 0, z: 0} +--- !u!1 &4012615691934547959 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4012615691934547963} + - component: {fileID: 4012615691934547960} + - component: {fileID: 4012615691934547961} + - component: {fileID: 4012615691934547958} + - component: {fileID: 4012615691934547964} + m_Layer: 0 + m_Name: Side + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!33 &4012615691934547960 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691934547959} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &4012615691934547961 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691934547959} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 00cf8ac777c8c42e8967157f70fbfcbf, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!4 &4012615691934547963 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691934547959} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: -30.5, y: 0.49999994, z: 0} + m_LocalScale: {x: 1, y: 3, z: 62} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615690793490293} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &4012615691934547964 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691934547959} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1de461850fd18574584d884cc31ed103, type: 3} + m_Name: + m_EditorClassIdentifier: + Destination: {fileID: 2071037934} + Preserve_XAxis: 0 + Preserve_YAxis: 0 + Preserve_ZAxis: 1 +--- !u!4 &4012615692010092074 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692010092077} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: -0.5, z: 0} + m_LocalScale: {x: 0.25, y: 0.25, z: 0.25} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 4012615691046896311} + - {fileID: 4012615692740187254} + - {fileID: 4012615692568749213} + - {fileID: 4012615690734689956} + - {fileID: 4012615692014524569} + - {fileID: 3910294717136820516} + - {fileID: 6283120761446335287} + - {fileID: 2290144463474879011} + - {fileID: 6959258898237589402} + m_Father: {fileID: 465045809} + m_RootOrder: 4 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &4012615692010092077 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4012615692010092074} + m_Layer: 0 + m_Name: SceneLevelGeometryTeleport + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!33 &4012615692014524568 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692014524573} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!4 &4012615692014524569 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692014524573} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 30.5, y: 0.49999994, z: 0} + m_LocalScale: {x: 1, y: 3, z: 62} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692010092074} + m_RootOrder: 4 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!65 &4012615692014524570 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692014524573} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 2, y: 1, z: 1} + m_Center: {x: 0.5, y: 0, z: 0} +--- !u!23 &4012615692014524571 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692014524573} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 00cf8ac777c8c42e8967157f70fbfcbf, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!1 &4012615692014524573 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4012615692014524569} + - component: {fileID: 4012615692014524568} + - component: {fileID: 4012615692014524571} + - component: {fileID: 4012615692014524570} + - component: {fileID: 4012615692014524574} + m_Layer: 0 + m_Name: Side + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &4012615692014524574 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692014524573} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1de461850fd18574584d884cc31ed103, type: 3} + m_Name: + m_EditorClassIdentifier: + Destination: {fileID: 778998217} + Preserve_XAxis: 0 + Preserve_YAxis: 0 + Preserve_ZAxis: 1 +--- !u!1 &4012615692280832996 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4012615692280833000} + - component: {fileID: 4012615692280833001} + - component: {fileID: 4012615692280832998} + - component: {fileID: 4012615692280832999} + m_Layer: 0 + m_Name: Floor + m_TagString: Floor + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!23 &4012615692280832998 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692280832996} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: d2f6bf650dfcc483794cdacf53f9fe2b, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &4012615692280832999 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692280832996} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 1, y: 1, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!4 &4012615692280833000 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692280832996} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: -0.50000006, z: 0} + m_LocalScale: {x: 60, y: 1, z: 60} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615690793490293} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &4012615692280833001 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692280832996} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!33 &4012615692568749212 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692568749217} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!4 &4012615692568749213 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692568749217} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0.49999994, z: -30.5} + m_LocalScale: {x: 60, y: 3, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692010092074} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!65 &4012615692568749214 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692568749217} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 1, y: 1, z: 2} + m_Center: {x: 0, y: 0, z: -0.5} +--- !u!23 &4012615692568749215 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692568749217} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 00cf8ac777c8c42e8967157f70fbfcbf, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!1 &4012615692568749217 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4012615692568749213} + - component: {fileID: 4012615692568749212} + - component: {fileID: 4012615692568749215} + - component: {fileID: 4012615692568749214} + - component: {fileID: 4012615692568749218} + m_Layer: 0 + m_Name: Side + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &4012615692568749218 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692568749217} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1de461850fd18574584d884cc31ed103, type: 3} + m_Name: + m_EditorClassIdentifier: + Destination: {fileID: 878389897} + Preserve_XAxis: 1 + Preserve_YAxis: 0 + Preserve_ZAxis: 0 +--- !u!4 &4012615692740187254 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692740187258} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0.49999994, z: 30.5} + m_LocalScale: {x: 60, y: 3, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692010092074} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &4012615692740187256 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692740187258} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 00cf8ac777c8c42e8967157f70fbfcbf, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &4012615692740187257 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692740187258} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!1 &4012615692740187258 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4012615692740187254} + - component: {fileID: 4012615692740187257} + - component: {fileID: 4012615692740187256} + - component: {fileID: 4012615692740187259} + - component: {fileID: 4012615692740187260} + m_Layer: 0 + m_Name: Side + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!65 &4012615692740187259 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692740187258} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 1, y: 1, z: 2} + m_Center: {x: 0, y: 0, z: 0.5} +--- !u!114 &4012615692740187260 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692740187258} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1de461850fd18574584d884cc31ed103, type: 3} + m_Name: + m_EditorClassIdentifier: + Destination: {fileID: 53041272} + Preserve_XAxis: 1 + Preserve_YAxis: 0 + Preserve_ZAxis: 0 +--- !u!65 &4559046434087525967 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1854705290707056910} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 4, y: 2, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!65 &4559046435286335248 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1854705291588622417} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 4, y: 2, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!1 &4674276234231314032 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3910294715920693371} + - component: {fileID: 3136259739378421344} + m_Layer: 0 + m_Name: CornerBumper + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!1 &4674276235128641327 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3910294717136820516} + - component: {fileID: 3136259738196406079} + m_Layer: 0 + m_Name: CornerBumper + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &6283120761446335287 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 910007655381311276} + m_LocalRotation: {x: -0, y: -0.9244967, z: -0, w: -0.38119} + m_LocalPosition: {x: -29.72, y: 0.98, z: 29.82} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692010092074} + m_RootOrder: 6 + m_LocalEulerAnglesHint: {x: 0, y: -224.815, z: 0} +--- !u!4 &6283120762361012840 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 910007656598437491} + m_LocalRotation: {x: -0, y: -0.9244967, z: -0, w: -0.38119} + m_LocalPosition: {x: -29.72, y: 0.98, z: 29.82} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615690793490293} + m_RootOrder: 6 + m_LocalEulerAnglesHint: {x: 0, y: -224.815, z: 0} +--- !u!4 &6959258898237589402 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7080625901594168492} + m_LocalRotation: {x: -0, y: 0.93588465, z: -0, w: -0.35230666} + m_LocalPosition: {x: 29.26, y: 0.98, z: 29.45} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692010092074} + m_RootOrder: 8 + m_LocalEulerAnglesHint: {x: 0, y: -498.74298, z: 0} +--- !u!4 &6959258899469919941 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7080625902744219123} + m_LocalRotation: {x: -0, y: 0.93588465, z: -0, w: -0.35230666} + m_LocalPosition: {x: 29.26, y: 0.98, z: 29.45} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615690793490293} + m_RootOrder: 8 + m_LocalEulerAnglesHint: {x: 0, y: -498.74298, z: 0} +--- !u!1 &7080625901594168492 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6959258898237589402} + - component: {fileID: 7672408767872724259} + m_Layer: 0 + m_Name: CornerBumper (3) + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!1 &7080625902744219123 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6959258899469919941} + - component: {fileID: 7672408766706977916} + m_Layer: 0 + m_Name: CornerBumper (3) + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!65 &7672408766706977916 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7080625902744219123} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 4, y: 2, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!65 &7672408767872724259 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7080625901594168492} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 4, y: 2, z: 1} + m_Center: {x: 0, y: 0, z: 0} diff --git a/testproject/Assets/Samples/Teleport/TeleportSample.unity.meta b/testproject/Assets/Samples/Teleport/TeleportSample.unity.meta new file mode 100644 index 0000000000..c47d58c6a0 --- /dev/null +++ b/testproject/Assets/Samples/Teleport/TeleportSample.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: efa247d1f78ca694f8d2dcb5672e8f8b +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Scripts/PlayerMovement.cs b/testproject/Assets/Scripts/PlayerMovement.cs index 8e1d771d03..f7b3bc544b 100644 --- a/testproject/Assets/Scripts/PlayerMovement.cs +++ b/testproject/Assets/Scripts/PlayerMovement.cs @@ -1,38 +1,133 @@ using System.Collections.Generic; -using Unity.Netcode; using UnityEngine; +using Unity.Netcode; +using Unity.Netcode.Components; + -public class PlayerMovement : NetworkBehaviour +public class PlayerMovement : NetworkTransform { - [SerializeField] - private float m_Speed = 20.0f; - [SerializeField] - private float m_RotSpeed = 5.0f; + public float Speed = 4.0f; + public float RotSpeed = 1.0f; + + private Rigidbody m_Rigidbody; + + /// Used by public static Dictionary Players = new Dictionary(); - private void Start() + /// + /// Make this PlayerMovement-NetworkTransform component + /// Owner Authoritative + /// + protected override bool OnIsServerAuthoritative() { - if (IsLocalPlayer) + return false; + } + + private bool m_IsTeleporting; + public bool IsTeleporting + { + get + { + return m_IsTeleporting; + } + } + + private float m_TickFrequency; + private float m_DelayInputForTeleport; + private Quaternion m_PreviousRotation; + private RigidbodyInterpolation m_OrginalRigidbodyInterpolation; + + public void Telporting(Vector3 destination) + { + if (IsSpawned && IsOwner && !m_IsTeleporting) + { + m_IsTeleporting = true; + + // With rigid bodies, if you already know you are using standard + // transform interpolation (not NetworkTransform) on the authoritative + // side then you need to set it to Kinematic, preserve the current rigid + // body interpolation value, and then finally set interpolation to none. + m_Rigidbody.isKinematic = true; + m_OrginalRigidbodyInterpolation = m_Rigidbody.interpolation; + m_Rigidbody.interpolation = RigidbodyInterpolation.None; + + // We want to provide a few network ticks to pass in time before restoring + // the rigid body back to its settings prior to being teleported. + m_DelayInputForTeleport = Time.realtimeSinceStartup + (3f * m_TickFrequency); + + // Since the player-cube is a cube, when colliding with something it could + // cause the cube to rotate based on the surface being collided against + // and the facing of the cube. This prevents rotation from being changed + // due to colliding with a side wall (and then teleported) + transform.rotation = m_PreviousRotation; + + // Now teleport + Teleport(destination, transform.rotation, transform.localScale); + } + } + + public override void OnNetworkSpawn() + { + if (IsOwner) { var temp = transform.position; temp.y = 0.5f; transform.position = temp; + m_Rigidbody = GetComponent(); + m_TickFrequency = 1.0f / NetworkManager.NetworkTickSystem.TickRate; } + + /// Used by + Players[OwnerClientId] = this; + + base.OnNetworkSpawn(); } - public override void OnNetworkSpawn() + public override void OnNetworkDespawn() { - base.OnNetworkSpawn(); - Players[OwnerClientId] = this; // todo should really have a NetworkStop for unregistering this... + if (Players.ContainsKey(OwnerClientId)) + { + Players.Remove(OwnerClientId); + } + base.OnNetworkDespawn(); + } + + /// + /// LateUpdate is being used to check for the end of + /// a player's teleporting cycle. + /// + private void LateUpdate() + { + if (!IsSpawned || !IsOwner || !m_IsTeleporting) + { + return; + } + if (Time.realtimeSinceStartup >= m_DelayInputForTeleport) + { + m_IsTeleporting = false; + m_Rigidbody.isKinematic = false; + m_Rigidbody.interpolation = m_OrginalRigidbodyInterpolation; + } } private void FixedUpdate() { - if (IsLocalPlayer) + if (!IsSpawned || !IsOwner) { - transform.position += Input.GetAxis("Vertical") * m_Speed * Time.fixedDeltaTime * transform.forward; - transform.rotation = Quaternion.Euler(0, Input.GetAxis("Horizontal") * 90 * m_RotSpeed * Time.fixedDeltaTime, 0) * transform.rotation; + return; + } + else + { + transform.position = Vector3.Lerp(transform.position, transform.position + Input.GetAxis("Vertical") * Speed * transform.forward, Time.fixedDeltaTime); + var rotation = transform.rotation; + var euler = rotation.eulerAngles; + euler.y += Input.GetAxis("Horizontal") * 90 * RotSpeed * Time.fixedDeltaTime; + rotation.eulerAngles = euler; + transform.rotation = rotation; + + /// We store this to handle a collision rotation issue: + m_PreviousRotation = transform.rotation; } } } diff --git a/testproject/Assets/Scripts/ServerHostClientText.cs b/testproject/Assets/Scripts/ServerHostClientText.cs new file mode 100644 index 0000000000..a394fbf03e --- /dev/null +++ b/testproject/Assets/Scripts/ServerHostClientText.cs @@ -0,0 +1,46 @@ +using UnityEngine; +using UnityEngine.UI; +using Unity.Netcode; + +public class ServerHostClientText : NetworkBehaviour +{ + [SerializeField] + private Text m_DisplayText; + private Color m_Color; + + private void Start() + { + if (m_DisplayText != null) + { + m_DisplayText.text = string.Empty; + m_Color = m_DisplayText.color; + } + } + + public override void OnNetworkSpawn() + { + if (m_DisplayText != null) + { + if (NetworkManager.IsServer) + { + m_DisplayText.text = NetworkManager.IsHost ? "Host" : "Server"; + } + else if (NetworkManager.IsClient) + { + m_DisplayText.text = "Client"; + } + } + } + + private void OnGUI() + { + if (Application.isFocused) + { + m_DisplayText.color = m_Color; + } + else + { + m_DisplayText.color = Color.grey; + } + } +} diff --git a/testproject/Assets/Scripts/ServerHostClientText.cs.meta b/testproject/Assets/Scripts/ServerHostClientText.cs.meta new file mode 100644 index 0000000000..4744094557 --- /dev/null +++ b/testproject/Assets/Scripts/ServerHostClientText.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7ea6e834b18e2c840a76ce574eb4b144 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/AdditiveSceneMultiInstance.unity b/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/AdditiveSceneMultiInstance.unity index 6ea9d5e74b..a762b39af1 100644 --- a/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/AdditiveSceneMultiInstance.unity +++ b/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/AdditiveSceneMultiInstance.unity @@ -195,6 +195,21 @@ PrefabInstance: propertyPath: m_LocalEulerAnglesHint.z value: 0 objectReference: {fileID: 0} + - target: {fileID: 4600632750638426092, guid: ea906834639fa3f4ba65c95db6181d6b, + type: 3} + propertyPath: ScaleThreshold + value: 0.01 + objectReference: {fileID: 0} + - target: {fileID: 4600632750638426092, guid: ea906834639fa3f4ba65c95db6181d6b, + type: 3} + propertyPath: PositionThreshold + value: 0.01 + objectReference: {fileID: 0} + - target: {fileID: 4600632750638426092, guid: ea906834639fa3f4ba65c95db6181d6b, + type: 3} + propertyPath: RotAngleThreshold + value: 0.1 + objectReference: {fileID: 0} m_RemovedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: ea906834639fa3f4ba65c95db6181d6b, type: 3} --- !u!1001 &365996112 @@ -269,6 +284,21 @@ PrefabInstance: propertyPath: m_LocalEulerAnglesHint.z value: 0 objectReference: {fileID: 0} + - target: {fileID: 4600632750638426092, guid: ea906834639fa3f4ba65c95db6181d6b, + type: 3} + propertyPath: ScaleThreshold + value: 0.01 + objectReference: {fileID: 0} + - target: {fileID: 4600632750638426092, guid: ea906834639fa3f4ba65c95db6181d6b, + type: 3} + propertyPath: PositionThreshold + value: 0.01 + objectReference: {fileID: 0} + - target: {fileID: 4600632750638426092, guid: ea906834639fa3f4ba65c95db6181d6b, + type: 3} + propertyPath: RotAngleThreshold + value: 0.1 + objectReference: {fileID: 0} m_RemovedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: ea906834639fa3f4ba65c95db6181d6b, type: 3} --- !u!850595691 &903034822 @@ -278,7 +308,7 @@ LightingSettings: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_Name: - serializedVersion: 3 + serializedVersion: 4 m_GIWorkflowMode: 1 m_EnableBakedLightmaps: 1 m_EnableRealtimeLightmaps: 0 @@ -291,7 +321,7 @@ LightingSettings: m_LightmapMaxSize: 1024 m_BakeResolution: 40 m_Padding: 2 - m_TextureCompression: 1 + m_LightmapCompression: 3 m_AO: 0 m_AOMaxDistance: 1 m_CompAOExponent: 1 @@ -332,6 +362,7 @@ LightingSettings: m_PVRFilteringAtrousPositionSigmaDirect: 0.5 m_PVRFilteringAtrousPositionSigmaIndirect: 2 m_PVRFilteringAtrousPositionSigmaAO: 1 + m_PVRTiledBaking: 0 --- !u!1001 &1152084533 PrefabInstance: m_ObjectHideFlags: 0 @@ -404,6 +435,21 @@ PrefabInstance: propertyPath: m_LocalEulerAnglesHint.z value: 0 objectReference: {fileID: 0} + - target: {fileID: 4600632750638426092, guid: ea906834639fa3f4ba65c95db6181d6b, + type: 3} + propertyPath: ScaleThreshold + value: 0.01 + objectReference: {fileID: 0} + - target: {fileID: 4600632750638426092, guid: ea906834639fa3f4ba65c95db6181d6b, + type: 3} + propertyPath: PositionThreshold + value: 0.01 + objectReference: {fileID: 0} + - target: {fileID: 4600632750638426092, guid: ea906834639fa3f4ba65c95db6181d6b, + type: 3} + propertyPath: RotAngleThreshold + value: 0.1 + objectReference: {fileID: 0} m_RemovedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: ea906834639fa3f4ba65c95db6181d6b, type: 3} --- !u!1001 &1247056486 @@ -478,5 +524,20 @@ PrefabInstance: propertyPath: m_LocalEulerAnglesHint.z value: 0 objectReference: {fileID: 0} + - target: {fileID: 4600632750638426092, guid: ea906834639fa3f4ba65c95db6181d6b, + type: 3} + propertyPath: ScaleThreshold + value: 0.01 + objectReference: {fileID: 0} + - target: {fileID: 4600632750638426092, guid: ea906834639fa3f4ba65c95db6181d6b, + type: 3} + propertyPath: PositionThreshold + value: 0.01 + objectReference: {fileID: 0} + - target: {fileID: 4600632750638426092, guid: ea906834639fa3f4ba65c95db6181d6b, + type: 3} + propertyPath: RotAngleThreshold + value: 0.1 + objectReference: {fileID: 0} m_RemovedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: ea906834639fa3f4ba65c95db6181d6b, type: 3} diff --git a/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/ClientNetworkTransform.cs b/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/ClientNetworkTransform.cs new file mode 100644 index 0000000000..f236eac9bf --- /dev/null +++ b/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/ClientNetworkTransform.cs @@ -0,0 +1,12 @@ +using Unity.Netcode.Components; + +namespace TestProject.ManualTests +{ + public class ClientNetworkTransform : NetworkTransform + { + protected override bool OnIsServerAuthoritative() + { + return false; + } + } +} diff --git a/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/ClientNetworkTransform.cs.meta b/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/ClientNetworkTransform.cs.meta new file mode 100644 index 0000000000..1c86c33058 --- /dev/null +++ b/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/ClientNetworkTransform.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cf01cca54b77c0241ad6d9da5ef6a709 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/PlayerClientAuthoritative.prefab b/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/PlayerClientAuthoritative.prefab new file mode 100644 index 0000000000..5fe8f7e1d0 --- /dev/null +++ b/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/PlayerClientAuthoritative.prefab @@ -0,0 +1,297 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &4079352819444256614 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4079352819444256611} + - component: {fileID: 972770265172480408} + - component: {fileID: -3775814466963834669} + - component: {fileID: 4079352819444256610} + - component: {fileID: 4079352819444256613} + - component: {fileID: 4079352819444256612} + - component: {fileID: 1750810845806302260} + - component: {fileID: 4053684789567975459} + - component: {fileID: 6442518961346739709} + - component: {fileID: 2756533617708860847} + - component: {fileID: 9187250582412873179} + m_Layer: 8 + m_Name: PlayerClientAuthoritative + m_TagString: Player + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4079352819444256611 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4079352819444256614} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 5, y: 0.625, z: 5} + m_LocalScale: {x: 1.25, y: 1.25, z: 1.25} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 3519470446676406143} + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &972770265172480408 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4079352819444256614} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: cf01cca54b77c0241ad6d9da5ef6a709, type: 3} + m_Name: + m_EditorClassIdentifier: + SyncPositionX: 1 + SyncPositionY: 1 + SyncPositionZ: 1 + SyncRotAngleX: 1 + SyncRotAngleY: 1 + SyncRotAngleZ: 1 + SyncScaleX: 1 + SyncScaleY: 1 + SyncScaleZ: 1 + PositionThreshold: 0.01 + RotAngleThreshold: 0.01 + ScaleThreshold: 0.01 + InLocalSpace: 0 + Interpolate: 1 +--- !u!114 &-3775814466963834669 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4079352819444256614} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 951099334 + AlwaysReplicateAsRoot: 0 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 +--- !u!33 &4079352819444256610 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4079352819444256614} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &4079352819444256613 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4079352819444256614} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: a7b755ad8e4fe4bdb8f5518c951abc70, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &4079352819444256612 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4079352819444256614} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 1, y: 1, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!54 &1750810845806302260 +Rigidbody: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4079352819444256614} + serializedVersion: 2 + m_Mass: 100 + m_Drag: 7 + m_AngularDrag: 0.05 + m_UseGravity: 1 + m_IsKinematic: 0 + m_Interpolate: 1 + m_Constraints: 112 + m_CollisionDetection: 1 +--- !u!114 &4053684789567975459 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4079352819444256614} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 24a9235f10bc6456183432fdb3015157, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &6442518961346739709 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4079352819444256614} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e3e4ecffca0894dfa88af779c2a67b9e, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &2756533617708860847 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4079352819444256614} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8e5aa2f75493a4fee8ec635d215f03c2, type: 3} + m_Name: + m_EditorClassIdentifier: + MoveSpeed: 10 +--- !u!114 &9187250582412873179 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4079352819444256614} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f6c0be61502bb534f922ebb746851216, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!1 &6479012615216050269 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3519470446676406143} + - component: {fileID: 6858102498835906389} + - component: {fileID: 2780619303711114328} + m_Layer: 8 + m_Name: PlayerSub + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &3519470446676406143 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6479012615216050269} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1.045, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4079352819444256611} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &6858102498835906389 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6479012615216050269} + m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &2780619303711114328 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6479012615216050269} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} diff --git a/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/PlayerClientAuthoritative.prefab.meta b/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/PlayerClientAuthoritative.prefab.meta new file mode 100644 index 0000000000..3b1a7f37fd --- /dev/null +++ b/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/PlayerClientAuthoritative.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3cc5d500f7b2e1245bfb8652adadb286 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/SceneTransitioningBase1.unity b/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/SceneTransitioningBase1.unity index bf5d0be2c7..3288c65aaf 100644 --- a/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/SceneTransitioningBase1.unity +++ b/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/SceneTransitioningBase1.unity @@ -171,8 +171,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 475de064003ff104fb88b1fbccd0f417, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_ActivateOnLoad: 0 m_SceneToLoad: AdditiveSceneMultiInstance m_SceneAsset: {fileID: 102900000, guid: 0ae94f636016d3b40bfbecad57d99553, type: 3} @@ -224,12 +224,13 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 244f0414ea8419b41ac51adb305d64b0, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_SwitchSceneButtonObject: {fileID: 1347823141} m_SceneToSwitchTo: SceneTransitioningBase2 m_EnableAutoSwitch: 0 m_AutoSwitchTimeOut: 60 + DisconnectClientUponLoadScene: 0 --- !u!1 &37242881 GameObject: m_ObjectHideFlags: 0 @@ -379,8 +380,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 @@ -455,8 +456,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 @@ -632,8 +633,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 475de064003ff104fb88b1fbccd0f417, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_ActivateOnLoad: 0 m_SceneToLoad: AdditiveSceneMultiInstance m_SceneAsset: {fileID: 102900000, guid: 0ae94f636016d3b40bfbecad57d99553, type: 3} @@ -686,8 +687,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 @@ -762,8 +763,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 @@ -823,8 +824,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_IgnoreReversedGraphics: 1 m_BlockingObjects: 0 m_BlockingMask: @@ -840,8 +841,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_UiScaleMode: 0 m_ReferencePixelsPerUnit: 100 m_ScaleFactor: 1 @@ -924,8 +925,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_IgnoreReversedGraphics: 1 m_BlockingObjects: 0 m_BlockingMask: @@ -941,8 +942,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_UiScaleMode: 1 m_ReferencePixelsPerUnit: 100 m_ScaleFactor: 1 @@ -1055,8 +1056,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 475de064003ff104fb88b1fbccd0f417, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_ActivateOnLoad: 0 m_SceneToLoad: AdditiveScene2 m_SceneAsset: {fileID: 102900000, guid: c6a3d883c8253ee43bca4f2b03797d7b, type: 3} @@ -1109,8 +1110,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 9085046f02f69544eb97fd06b6048fe2, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Navigation: m_Mode: 3 m_WrapAround: 0 @@ -1156,7 +1157,7 @@ MonoBehaviour: m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine m_IntArgument: 0 m_FloatArgument: 0 - m_StringArgument: + m_StringArgument: m_BoolArgument: 0 m_CallState: 2 m_IsOn: 0 @@ -1306,8 +1307,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 9085046f02f69544eb97fd06b6048fe2, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Navigation: m_Mode: 3 m_WrapAround: 0 @@ -1353,7 +1354,7 @@ MonoBehaviour: m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine m_IntArgument: 0 m_FloatArgument: 0 - m_StringArgument: + m_StringArgument: m_BoolArgument: 0 m_CallState: 2 m_IsOn: 0 @@ -1406,8 +1407,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 @@ -1482,8 +1483,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 @@ -1558,8 +1559,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 0.9811321, g: 0.9811321, b: 0.9811321, a: 1} m_RaycastTarget: 1 @@ -1722,8 +1723,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 0.5058824, b: 0.003921569, a: 1} m_RaycastTarget: 1 @@ -1802,8 +1803,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 @@ -1878,8 +1879,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 @@ -1954,8 +1955,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 @@ -2030,8 +2031,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 475de064003ff104fb88b1fbccd0f417, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_ActivateOnLoad: 0 m_SceneToLoad: AdditiveScene1 m_SceneAsset: {fileID: 102900000, guid: 41a0239b0c49e2047b7063c822f0df8a, type: 3} @@ -2041,8 +2042,8 @@ LightingSettings: m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_Name: - serializedVersion: 3 + m_Name: + serializedVersion: 4 m_GIWorkflowMode: 1 m_EnableBakedLightmaps: 1 m_EnableRealtimeLightmaps: 0 @@ -2145,8 +2146,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 @@ -2299,8 +2300,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 475de064003ff104fb88b1fbccd0f417, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_ActivateOnLoad: 0 m_SceneToLoad: AdditiveSceneMultiInstance m_SceneAsset: {fileID: 102900000, guid: 0ae94f636016d3b40bfbecad57d99553, type: 3} @@ -2313,10 +2314,10 @@ GameObject: serializedVersion: 6 m_Component: - component: {fileID: 1024114720} - - component: {fileID: 1024114719} - component: {fileID: 1024114718} - component: {fileID: 1024114721} - component: {fileID: 1024114722} + - component: {fileID: 1024114723} m_Layer: 0 m_Name: NetworkManager m_TagString: Untagged @@ -2334,13 +2335,13 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 593a2fe42fa9d37498c96f9a383b6521, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: RunInBackground: 1 LogLevel: 1 NetworkConfig: ProtocolVersion: 0 - NetworkTransport: {fileID: 1024114719} + NetworkTransport: {fileID: 1024114723} PlayerPrefab: {fileID: 4079352819444256614, guid: c16f03336b6104576a565ef79ad643c0, type: 3} NetworkPrefabs: @@ -2394,10 +2395,10 @@ MonoBehaviour: SourcePrefabToOverride: {fileID: 0} SourceHashToOverride: 0 OverridingTargetPrefab: {fileID: 0} - TickRate: 30 + TickRate: 32 ClientConnectionBufferTimeout: 10 ConnectionApproval: 0 - ConnectionData: + ConnectionData: EnableTimeResync: 0 TimeResyncInterval: 30 EnsureNetworkVariableLengthSafety: 0 @@ -2409,25 +2410,6 @@ MonoBehaviour: LoadSceneTimeOut: 120 SpawnTimeout: 1 EnableNetworkLogs: 1 ---- !u!114 &1024114719 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1024114717} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: b84c2d8dfe509a34fb59e2b81f8e1319, type: 3} - m_Name: - m_EditorClassIdentifier: - MessageBufferSize: 65535 - MaxConnections: 100 - MaxSentMessageQueueSize: 512 - ConnectAddress: 127.0.0.1 - ConnectPort: 7777 - ServerListenPort: 7777 - MessageSendMode: 0 --- !u!4 &1024114720 Transform: m_ObjectHideFlags: 0 @@ -2436,7 +2418,7 @@ Transform: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1024114717} m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: 0.000061035156, y: 0.000015258789, z: 0} + m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -2453,8 +2435,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 0e6f8504d891fc44881b2d9703017d03, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: --- !u!114 &1024114722 MonoBehaviour: m_ObjectHideFlags: 0 @@ -2465,10 +2447,38 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 71c52a32bd1c1c84691050b6d045ab4a, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: LogToConsole: 1 TimeToLive: 10 +--- !u!114 &1024114723 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1024114717} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 6960e84d07fb87f47956e7a81d71c4e6, type: 3} + m_Name: + m_EditorClassIdentifier: + m_ProtocolType: 0 + m_MaxPacketQueueSize: 128 + m_MaxPayloadSize: 512000 + m_MaxSendQueueSize: 4096000 + m_HeartbeatTimeoutMS: 500 + m_ConnectTimeoutMS: 1000 + m_MaxConnectAttempts: 60 + m_DisconnectTimeoutMS: 30000 + ConnectionData: + Address: 127.0.0.1 + Port: 7777 + ServerListenAddress: + DebugSimulator: + PacketDelayMS: 0 + PacketJitterMS: 0 + PacketDropRate: 0 --- !u!1 &1113539278 GameObject: m_ObjectHideFlags: 0 @@ -2497,8 +2507,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 8c48ea35c67e64f7fac22a3f6831ca88, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: AutoSpawnEnable: 1 InitialSpawnDelay: 0.2 SpawnsPerSecond: 1 @@ -2538,8 +2548,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: GlobalObjectIdHash: 1983031731 AlwaysReplicateAsRoot: 0 DontDestroyWithOwner: 0 @@ -2592,8 +2602,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 475de064003ff104fb88b1fbccd0f417, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_ActivateOnLoad: 0 m_SceneToLoad: AdditiveSceneMultiInstance m_SceneAsset: {fileID: 102900000, guid: 0ae94f636016d3b40bfbecad57d99553, type: 3} @@ -2645,8 +2655,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 @@ -2726,8 +2736,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 @@ -2935,8 +2945,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 @@ -3017,8 +3027,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Navigation: m_Mode: 3 m_WrapAround: 0 @@ -3060,7 +3070,7 @@ MonoBehaviour: m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine m_IntArgument: 0 m_FloatArgument: 0 - m_StringArgument: + m_StringArgument: m_BoolArgument: 0 m_CallState: 2 --- !u!114 &1347823144 @@ -3073,8 +3083,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 0.1981132, g: 0.1981132, b: 0.1981132, a: 1} m_RaycastTarget: 1 @@ -3150,8 +3160,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 9085046f02f69544eb97fd06b6048fe2, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Navigation: m_Mode: 3 m_WrapAround: 0 @@ -3197,7 +3207,7 @@ MonoBehaviour: m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine m_IntArgument: 0 m_FloatArgument: 0 - m_StringArgument: + m_StringArgument: m_BoolArgument: 0 m_CallState: 2 m_IsOn: 0 @@ -3249,8 +3259,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 0.990566, g: 0.990566, b: 0.990566, a: 1} m_RaycastTarget: 1 @@ -3367,8 +3377,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 @@ -3443,8 +3453,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 @@ -3519,8 +3529,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 @@ -3595,8 +3605,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 @@ -3673,8 +3683,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Navigation: m_Mode: 3 m_WrapAround: 0 @@ -3716,7 +3726,7 @@ MonoBehaviour: m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine m_IntArgument: 0 m_FloatArgument: 0 - m_StringArgument: + m_StringArgument: m_BoolArgument: 0 m_CallState: 2 --- !u!114 &1588117330 @@ -3729,8 +3739,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 0.1981132, g: 0.1981132, b: 0.1981132, a: 1} m_RaycastTarget: 1 @@ -3805,8 +3815,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 @@ -3886,8 +3896,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 9085046f02f69544eb97fd06b6048fe2, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Navigation: m_Mode: 3 m_WrapAround: 0 @@ -3933,7 +3943,7 @@ MonoBehaviour: m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine m_IntArgument: 0 m_FloatArgument: 0 - m_StringArgument: + m_StringArgument: m_BoolArgument: 0 m_CallState: 2 m_IsOn: 0 @@ -3986,8 +3996,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 9085046f02f69544eb97fd06b6048fe2, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Navigation: m_Mode: 3 m_WrapAround: 0 @@ -4033,7 +4043,7 @@ MonoBehaviour: m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine m_IntArgument: 0 m_FloatArgument: 0 - m_StringArgument: + m_StringArgument: m_BoolArgument: 0 m_CallState: 2 m_IsOn: 0 @@ -4086,8 +4096,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 @@ -4162,8 +4172,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 @@ -4218,8 +4228,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_HorizontalAxis: Horizontal m_VerticalAxis: Vertical m_SubmitButton: Submit @@ -4237,8 +4247,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_FirstSelected: {fileID: 0} m_sendNavigationEvents: 1 m_DragThreshold: 10 @@ -4305,8 +4315,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 0.5058824, b: 0.003921569, a: 1} m_RaycastTarget: 1 @@ -4645,8 +4655,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 9085046f02f69544eb97fd06b6048fe2, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Navigation: m_Mode: 3 m_WrapAround: 0 @@ -4692,7 +4702,7 @@ MonoBehaviour: m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine m_IntArgument: 0 m_FloatArgument: 0 - m_StringArgument: + m_StringArgument: m_BoolArgument: 0 m_CallState: 2 m_IsOn: 0 @@ -4744,8 +4754,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 @@ -4920,8 +4930,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 67db9e8f0e2ae9c40bc1e2b64352a6b4, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Navigation: m_Mode: 3 m_WrapAround: 0 @@ -4970,7 +4980,7 @@ MonoBehaviour: m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine m_IntArgument: 0 m_FloatArgument: 0 - m_StringArgument: + m_StringArgument: m_BoolArgument: 0 m_CallState: 2 --- !u!1 &2107482020 @@ -5016,11 +5026,10 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: cb5f3e55f5dd247129d8a4979b80ebbb, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_ClientServerToggle: {fileID: 1588117327} m_TrackSceneEvents: 1 - m_LogSceneEventsToConsole: 1 --- !u!114 &2107482023 MonoBehaviour: m_ObjectHideFlags: 0 @@ -5031,8 +5040,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: GlobalObjectIdHash: 3197939627 AlwaysReplicateAsRoot: 0 DontDestroyWithOwner: 0 @@ -5162,7 +5171,7 @@ PrefabInstance: - target: {fileID: 2848221157716786269, guid: 3200770c16e3b2b4ebe7f604154faac7, type: 3} propertyPath: m_FontData.m_Font - value: + value: objectReference: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} - target: {fileID: 2848221157716786269, guid: 3200770c16e3b2b4ebe7f604154faac7, type: 3} @@ -5197,7 +5206,7 @@ PrefabInstance: - target: {fileID: 5266522511616468950, guid: 3200770c16e3b2b4ebe7f604154faac7, type: 3} propertyPath: m_SceneMenuToLoad - value: + value: objectReference: {fileID: 11400000, guid: c10d995498e0c514a853c3506031d3fb, type: 2} m_RemovedComponents: [] diff --git a/testproject/Assets/Tests/Manual/Scripts/GenericNetworkObjectBehaviour.cs b/testproject/Assets/Tests/Manual/Scripts/GenericNetworkObjectBehaviour.cs index 9536352470..376c182ae8 100644 --- a/testproject/Assets/Tests/Manual/Scripts/GenericNetworkObjectBehaviour.cs +++ b/testproject/Assets/Tests/Manual/Scripts/GenericNetworkObjectBehaviour.cs @@ -52,26 +52,6 @@ private void Start() m_NetworkTransform = GetComponent(); } - /// - /// Handles disabling the MeshRenderer when the client despawns a NetworkObject - /// - public override void OnNetworkDespawn() - { - if (!IsServer) - { - if (m_MeshRenderer == null) - { - m_MeshRenderer = GetComponent(); - } - - if (m_MeshRenderer != null) - { - m_MeshRenderer.enabled = false; - } - } - base.OnNetworkDespawn(); - } - /// /// Makes mesh renderer visible again /// @@ -84,28 +64,6 @@ public void Reset() m_MeshRenderer.enabled = true; } - private float m_VisibilitySpawn; - /// - /// Handles setting a delay before the newly spawned object is visible - /// - public override void OnNetworkSpawn() - { - if (!IsServer) - { - if (m_MeshRenderer == null) - { - m_MeshRenderer = GetComponent(); - } - m_MeshRenderer.enabled = false; - m_VisibilitySpawn = Time.realtimeSinceStartup + 0.12f; - if (NetworkObject.NetworkObjectId == 0) - { - Debug.Log("Spawning NetworkObjectId 0!"); - } - } - base.OnNetworkSpawn(); - } - public void ShouldMoveRandomly(bool shouldMoveRandomly) { m_MoveRandomly = shouldMoveRandomly; @@ -176,19 +134,6 @@ private void Update() NetworkObject.Despawn(); } } - else if (!IsServer) - { - // This is here to handle any short term latency between the time - // an object becomes spawned to the time it takes to update its first - // position. - if (m_MeshRenderer != null && !m_MeshRenderer.enabled) - { - if (m_VisibilitySpawn < Time.realtimeSinceStartup) - { - m_MeshRenderer.enabled = true; - } - } - } } [HideInInspector] @@ -198,7 +143,7 @@ private void Update() private void OnTriggerEnter(Collider other) { - if (IsOwner && !m_ShouldDespawn) + if (IsSpawned && IsOwner && !m_ShouldDespawn) { if (other.CompareTag("GenericObject") || other.CompareTag("Floor")) { diff --git a/testproject/Assets/Tests/Manual/Scripts/PlayerMovementManager.cs b/testproject/Assets/Tests/Manual/Scripts/PlayerMovementManager.cs index 763ef5819e..ae9c96d86c 100644 --- a/testproject/Assets/Tests/Manual/Scripts/PlayerMovementManager.cs +++ b/testproject/Assets/Tests/Manual/Scripts/PlayerMovementManager.cs @@ -20,24 +20,35 @@ private void Start() private void Update() { - if (NetworkObject != null) + if (!IsSpawned) { - if (IsOwner && Input.GetKeyDown(KeyCode.Space)) + return; + } + + if (m_RandomMovement.HasAuthority()) + { + if (Input.GetKeyDown(KeyCode.Space) && IsOwner) { - if (m_RandomMovement) - { - m_RandomMovement.enabled = !m_RandomMovement.enabled; - } + m_RandomMovement.enabled = !m_RandomMovement.enabled; } - - if (NetworkObject != null && NetworkObject.NetworkManager != null && NetworkObject.NetworkManager.IsListening) + if (m_RandomMovement.enabled) { - if (m_RandomMovement.enabled) - { - m_RandomMovement.Move(MoveSpeed); - } + m_RandomMovement.Move(MoveSpeed); } } + else if (IsOwner) + { + if (Input.GetKeyDown(KeyCode.Space)) + { + ToggleEnableDisableServerRpc(); + } + } + } + + [ServerRpc] + private void ToggleEnableDisableServerRpc() + { + m_RandomMovement.enabled = !m_RandomMovement.enabled; } } } diff --git a/testproject/Assets/Tests/Manual/Scripts/RandomMovement.cs b/testproject/Assets/Tests/Manual/Scripts/RandomMovement.cs index 6aa161ceb0..27412684cd 100644 --- a/testproject/Assets/Tests/Manual/Scripts/RandomMovement.cs +++ b/testproject/Assets/Tests/Manual/Scripts/RandomMovement.cs @@ -1,5 +1,6 @@ using UnityEngine; using Unity.Netcode; +using Unity.Netcode.Components; namespace TestProject.ManualTests { @@ -12,54 +13,55 @@ public class RandomMovement : NetworkBehaviour, IPlayerMovement private Vector3 m_Direction; private Rigidbody m_Rigidbody; + private NetworkTransform m_NetworkTransform; + private ClientNetworkTransform m_ClientNetworkTransform; + + public bool HasAuthority() + { + if (m_NetworkTransform != null) + { + if (m_ClientNetworkTransform == null) + { + m_ClientNetworkTransform = m_NetworkTransform as ClientNetworkTransform; + } + if (m_ClientNetworkTransform != null) + { + return IsOwner; + } + } + return IsServer; + } public override void OnNetworkSpawn() { m_Rigidbody = GetComponent(); + m_NetworkTransform = GetComponent(); if (NetworkObject != null && m_Rigidbody != null) { - if (NetworkObject.IsOwner) + if (HasAuthority()) { ChangeDirection(true, true); } } } - /// - /// Notify the server of any client side change in direction or speed - /// - /// - [ServerRpc(RequireOwnership = false)] - private void MovePlayerServerRpc(Vector3 moveTowards) - { - m_MoveTowardsPosition = moveTowards; - } - private Vector3 m_MoveTowardsPosition; + private float m_CurrentSpeed; public void Move(int speed) { - // Server sets this locally - if (IsServer && IsOwner) - { - m_MoveTowardsPosition = (m_Direction * speed); - } - else if (!IsServer && IsOwner) - { - // Client must sent Rpc - MovePlayerServerRpc(m_Direction * speed * 1.05f); - } - else if (IsServer && !IsOwner) - { - m_MoveTowardsPosition = Vector3.Lerp(m_MoveTowardsPosition, Vector3.zero, 0.01f); - } + m_CurrentSpeed = speed; } // We just apply our current direction with magnitude to our current position during fixed update private void FixedUpdate() { - if (IsServer && NetworkObject && NetworkObject.NetworkManager && NetworkObject.NetworkManager.IsListening) + if (!IsSpawned) + { + return; + } + if (HasAuthority()) { if (m_Rigidbody == null) { @@ -67,24 +69,18 @@ private void FixedUpdate() } if (m_Rigidbody != null) { - m_Rigidbody.MovePosition(transform.position + (m_MoveTowardsPosition * Time.fixedDeltaTime)); + var position = m_Rigidbody.position; + var yAxis = position.y; + position += (m_Direction * m_CurrentSpeed); + position.y = yAxis; + m_Rigidbody.position = Vector3.Lerp(m_Rigidbody.position, position, Time.fixedDeltaTime); } } } - /// - /// Handles server notification to client that we need to change direction - /// - /// - [ClientRpc] - private void ChangeDirectionClientRpc(Vector3 direction) - { - m_Direction = direction; - } - private void OnCollisionStay(Collision collision) { - if (IsServer) + if (HasAuthority()) { if (collision.gameObject.CompareTag("Floor") || collision.gameObject.CompareTag("GenericObject")) { @@ -95,24 +91,6 @@ private void OnCollisionStay(Collision collision) bool moveDown = collisionPoint.z > transform.position.z; ChangeDirection(moveRight, moveDown); - - // If we are not the owner then we need to notify the client that their direction - // must change - if (!IsOwner) - { - m_MoveTowardsPosition = m_Direction * m_MoveTowardsPosition.magnitude; - ChangeDirectionClientRpc(m_Direction); - } - } - } - - private static void ChangeDirectionClientRpcInHandler(NetworkBehaviour target, FastBufferReader reader) - { - NetworkManager networkManager = target.NetworkManager; - if (networkManager != null && networkManager.IsListening) - { - reader.ReadValueSafe(out Vector3 value); - ((RandomMovement)target).ChangeDirectionClientRpc(value); } } diff --git a/testproject/ProjectSettings/EditorBuildSettings.asset b/testproject/ProjectSettings/EditorBuildSettings.asset index 54f861c188..d10c3f223c 100644 --- a/testproject/ProjectSettings/EditorBuildSettings.asset +++ b/testproject/ProjectSettings/EditorBuildSettings.asset @@ -107,6 +107,9 @@ EditorBuildSettings: - enabled: 1 path: Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorServerOwnerTest.unity guid: f88da8bb8d07e11418eaad6524d5cc12 + - enabled: 1 + path: Assets/Samples/Teleport/TeleportSample.unity + guid: efa247d1f78ca694f8d2dcb5672e8f8b m_configObjects: com.unity.addressableassets: {fileID: 11400000, guid: 5a3d5c53c25349c48912726ae850f3b0, type: 2} From 96bb2eb962ae626b7361e2cb45510172795469b0 Mon Sep 17 00:00:00 2001 From: ashwini <36935028+ashwinimurt@users.noreply.github.com> Date: Mon, 22 Aug 2022 15:40:04 -0700 Subject: [PATCH 031/138] chore: Fix build test (#2139) --- .../Tests/Editor/Build/BuildTests.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs index 66a2b13124..c8cde89618 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs @@ -4,7 +4,6 @@ using UnityEditor; using UnityEditor.Build.Reporting; using UnityEngine; -using UnityEngine.TestTools; namespace Unity.Netcode.EditorTests { @@ -21,20 +20,20 @@ public void BasicBuildTest() var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(buildTarget); var buildTargetSupported = BuildPipeline.IsBuildTargetSupported(buildTargetGroup, buildTarget); - var buildReport = BuildPipeline.BuildPlayer( - new[] { Path.Combine(packagePath, DefaultBuildScenePath) }, - Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds", nameof(BuildTests)), - buildTarget, - BuildOptions.None - ); - if (buildTargetSupported) { + var buildReport = BuildPipeline.BuildPlayer( + new[] { Path.Combine(packagePath, DefaultBuildScenePath) }, + Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds", nameof(BuildTests)), + buildTarget, + BuildOptions.None + ); + Assert.AreEqual(BuildResult.Succeeded, buildReport.summary.result); } else { - LogAssert.Expect(LogType.Error, "Error building player because build target was unsupported"); + Debug.Log($"Skipped building player due to Unsupported Build Target"); } } } From 6d0de9a48e90da76a53c94687f3b7a292b540705 Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Mon, 22 Aug 2022 19:46:02 -0400 Subject: [PATCH 032/138] fix: prevent exception in Boss Room with NetworkAnimator (#2140) fix: prevent exception in Boss Room with NetworkAnimator due to m_NetworkBehaviour being null --- .../Runtime/NetworkVariable/NetworkVariableBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs index 46b6ab84e6..4b8ea84930 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -79,7 +79,7 @@ protected NetworkVariableBase( public virtual void SetDirty(bool isDirty) { m_IsDirty = isDirty; - if (m_IsDirty) + if (m_IsDirty && m_NetworkBehaviour != null) { m_NetworkBehaviour.NetworkManager.MarkNetworkObjectDirty(m_NetworkBehaviour.NetworkObject); } From ba92bd65132e94418339c1ec2d1e1174bef63747 Mon Sep 17 00:00:00 2001 From: ashwini <36935028+ashwinimurt@users.noreply.github.com> Date: Tue, 23 Aug 2022 13:03:44 -0700 Subject: [PATCH 033/138] chore: Bump NGO version to 1.0.1 (#2131) --- com.unity.netcode.gameobjects/CHANGELOG.md | 3 ++- .../Runtime/com.unity.netcode.testhelpers.runtime.asmdef | 3 +++ .../Tests/Editor/com.unity.netcode.editortests.asmdef | 3 +++ .../Tests/Runtime/com.unity.netcode.runtimetests.asmdef | 3 +++ com.unity.netcode.gameobjects/package.json | 2 +- minimalproject/Packages/packages-lock.json | 4 ++-- testproject-tools-integration/Packages/packages-lock.json | 4 ++-- testproject/Packages/packages-lock.json | 4 ++-- 8 files changed, 18 insertions(+), 8 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index ba4318ba02..627975e120 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -7,10 +7,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). -## [Unreleased] +## [1.0.1] - 2022-08-23 ### Changed +- Changed version to 1.0.1. (#2131) - Updated dependency on `com.unity.transport` to 1.2.0. (#2129) - When using `UnityTransport`, _reliable_ payloads are now allowed to exceed the configured 'Max Payload Size'. Unreliable payloads remain bounded by this setting. (#2081) - Preformance improvements for cases with large number of NetworkObjects, by not iterating over all unchanged NetworkObjects diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/com.unity.netcode.testhelpers.runtime.asmdef b/com.unity.netcode.gameobjects/TestHelpers/Runtime/com.unity.netcode.testhelpers.runtime.asmdef index 03c978bfee..2fb107c79f 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/com.unity.netcode.testhelpers.runtime.asmdef +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/com.unity.netcode.testhelpers.runtime.asmdef @@ -11,6 +11,9 @@ "optionalUnityReferences": [ "TestAssemblies" ], + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], "versionDefines": [ { "name": "com.unity.multiplayer.tools", diff --git a/com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef b/com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef index 36642d5598..8bfcba5bca 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef +++ b/com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef @@ -15,6 +15,9 @@ "optionalUnityReferences": [ "TestAssemblies" ], + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], "includePlatforms": [ "Editor" ], diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/com.unity.netcode.runtimetests.asmdef b/com.unity.netcode.gameobjects/Tests/Runtime/com.unity.netcode.runtimetests.asmdef index 45ae434407..fbb6f71d51 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/com.unity.netcode.runtimetests.asmdef +++ b/com.unity.netcode.gameobjects/Tests/Runtime/com.unity.netcode.runtimetests.asmdef @@ -16,6 +16,9 @@ "optionalUnityReferences": [ "TestAssemblies" ], + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], "versionDefines": [ { "name": "com.unity.multiplayer.tools", diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index ae7985362d..641a768117 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -2,7 +2,7 @@ "name": "com.unity.netcode.gameobjects", "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", - "version": "1.0.0", + "version": "1.0.1", "unity": "2020.3", "dependencies": { "com.unity.nuget.mono-cecil": "1.10.1", diff --git a/minimalproject/Packages/packages-lock.json b/minimalproject/Packages/packages-lock.json index 7de64407d1..c6004bd04b 100644 --- a/minimalproject/Packages/packages-lock.json +++ b/minimalproject/Packages/packages-lock.json @@ -39,7 +39,7 @@ "source": "local", "dependencies": { "com.unity.nuget.mono-cecil": "1.10.1", - "com.unity.transport": "1.0.0" + "com.unity.transport": "1.2.0" } }, "com.unity.nuget.mono-cecil": { @@ -61,7 +61,7 @@ "url": "https://packages.unity.com" }, "com.unity.transport": { - "version": "1.0.0", + "version": "1.2.0", "depth": 1, "source": "registry", "dependencies": { diff --git a/testproject-tools-integration/Packages/packages-lock.json b/testproject-tools-integration/Packages/packages-lock.json index 9a70870dd9..f9ea6d11b7 100644 --- a/testproject-tools-integration/Packages/packages-lock.json +++ b/testproject-tools-integration/Packages/packages-lock.json @@ -61,7 +61,7 @@ "source": "local", "dependencies": { "com.unity.nuget.mono-cecil": "1.10.1", - "com.unity.transport": "1.0.0" + "com.unity.transport": "1.2.0" } }, "com.unity.nuget.mono-cecil": { @@ -107,7 +107,7 @@ "url": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates" }, "com.unity.transport": { - "version": "1.0.0", + "version": "1.2.0", "depth": 1, "source": "registry", "dependencies": { diff --git a/testproject/Packages/packages-lock.json b/testproject/Packages/packages-lock.json index 8ef7ead1da..b6a4d09171 100644 --- a/testproject/Packages/packages-lock.json +++ b/testproject/Packages/packages-lock.json @@ -83,7 +83,7 @@ "source": "local", "dependencies": { "com.unity.nuget.mono-cecil": "1.10.1", - "com.unity.transport": "1.1.0" + "com.unity.transport": "1.2.0" } }, "com.unity.nuget.mono-cecil": { @@ -195,7 +195,7 @@ "url": "https://packages.unity.com" }, "com.unity.transport": { - "version": "1.1.0", + "version": "1.2.0", "depth": 1, "source": "registry", "dependencies": { From 798d866e2d4941fc5c0480fe8f4266fcc0f4e61c Mon Sep 17 00:00:00 2001 From: Kitty Draper <284434+ShadauxCat@users.noreply.github.com> Date: Tue, 23 Aug 2022 18:20:50 -0500 Subject: [PATCH 034/138] fix: Adding FastBuffer extension methods for multiple instantiations of a generic type causes runtime errors [MTT-3063] (#2142) Fixed RPC codegen failing to choose the correct extension methods for FastBufferReader and FastBufferWriter when the parameters were a generic type (i.e., List) and extensions for multiple instantiations of that type have been defined (i.e., List and List) --- com.unity.netcode.gameobjects/CHANGELOG.md | 6 + .../Editor/CodeGen/NetworkBehaviourILPP.cs | 7 +- .../Runtime/RpcUserSerializableTypesTest.cs | 302 +++++++++++++++++- 3 files changed, 295 insertions(+), 20 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 627975e120..9ab71d3968 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -7,6 +7,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). +## [Unreleased] + +### Fixed + +- Fixed RPC codegen failing to choose the correct extension methods for FastBufferReader and FastBufferWriter when the parameters were a generic type (i.e., List) and extensions for multiple instantiations of that type have been defined (i.e., List and List) (#2142) + ## [1.0.1] - 2022-08-23 ### Changed diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index 8f8305c1de..01a2e88d10 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -669,7 +669,7 @@ private bool GetWriteMethodForParameter(TypeReference paramType, out MethodRefer { if (parameters[1].IsIn) { - if (parameters[1].ParameterType.Resolve() == paramType.MakeByReferenceType().Resolve() && + if (((ByReferenceType)parameters[1].ParameterType).ElementType.FullName == paramType.FullName && ((ByReferenceType)parameters[1].ParameterType).ElementType.IsArray == paramType.IsArray) { methodRef = method; @@ -679,8 +679,7 @@ private bool GetWriteMethodForParameter(TypeReference paramType, out MethodRefer } else { - - if (parameters[1].ParameterType.Resolve() == paramType.Resolve() && + if (parameters[1].ParameterType.FullName == paramType.FullName && parameters[1].ParameterType.IsArray == paramType.IsArray) { methodRef = method; @@ -813,7 +812,7 @@ private bool GetReadMethodForParameter(TypeReference paramType, out MethodRefere var parameters = method.Resolve().Parameters; if (method.Name == k_ReadValueMethodName && parameters[1].IsOut && - parameters[1].ParameterType.Resolve() == paramType.MakeByReferenceType().Resolve() && + ((ByReferenceType)parameters[1].ParameterType).ElementType.FullName == paramType.FullName && ((ByReferenceType)parameters[1].ParameterType).ElementType.IsArray == paramType.IsArray) { methodRef = method; diff --git a/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs b/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs index b39bf0f675..01deda38b4 100644 --- a/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs +++ b/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs @@ -225,19 +225,24 @@ public IEnumerator ExtensionMethodRpcTest() var obj = new MyObject(256); var obj2 = new MySharedObjectReferencedById(256); var obj3 = new MyObjectPassedWithThisRef(256); + var intList = new List { 5, 10, 15, 5, 1 }; + var strList = new List { "foo", "bar", "baz", "qux" }; bool clientMyObjCalled = false; bool clientMyObjPassedWithThisRefCalled = false; - bool clientMySharedObjCalled = true; + bool clientMySharedObjCalled = false; bool serverMyObjCalled = false; bool serverMyObjPassedWithThisRefCalled = false; - bool serverMySharedObjCalled = true; + bool serverMySharedObjCalled = false; + bool serverIntListCalled = false; + bool serverStrListCalled = false; clientSideNetworkBehaviourClass.OnMyObjectUpdated = (receivedObj) => { Assert.AreEqual(obj.I, receivedObj.I); Assert.AreNotSame(obj, receivedObj); clientMyObjCalled = true; m_FinishedTest = clientMyObjCalled && clientMySharedObjCalled && clientMyObjPassedWithThisRefCalled && - serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled; + serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled && + serverIntListCalled && serverStrListCalled; }; serverSideNetworkBehaviourClass.OnMyObjectUpdated = (receivedObj) => { @@ -245,7 +250,8 @@ public IEnumerator ExtensionMethodRpcTest() Assert.AreNotSame(obj, receivedObj); serverMyObjCalled = true; m_FinishedTest = clientMyObjCalled && clientMySharedObjCalled && clientMyObjPassedWithThisRefCalled && - serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled; + serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled && + serverIntListCalled && serverStrListCalled; }; clientSideNetworkBehaviourClass.OnMyObjectPassedWithThisRefUpdated = (receivedObj) => { @@ -253,7 +259,8 @@ public IEnumerator ExtensionMethodRpcTest() Assert.AreNotSame(obj, receivedObj); clientMyObjPassedWithThisRefCalled = true; m_FinishedTest = clientMyObjCalled && clientMySharedObjCalled && clientMyObjPassedWithThisRefCalled && - serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled; + serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled && + serverIntListCalled && serverStrListCalled; }; serverSideNetworkBehaviourClass.OnMyObjectPassedWithThisRefUpdated = (receivedObj) => { @@ -261,26 +268,55 @@ public IEnumerator ExtensionMethodRpcTest() Assert.AreNotSame(obj, receivedObj); serverMyObjPassedWithThisRefCalled = true; m_FinishedTest = clientMyObjCalled && clientMySharedObjCalled && clientMyObjPassedWithThisRefCalled && - serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled; + serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled && + serverIntListCalled && serverStrListCalled; }; clientSideNetworkBehaviourClass.OnMySharedObjectReferencedByIdUpdated = (receivedObj) => { Assert.AreSame(obj2, receivedObj); clientMySharedObjCalled = true; m_FinishedTest = clientMyObjCalled && clientMySharedObjCalled && clientMyObjPassedWithThisRefCalled && - serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled; + serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled && + serverIntListCalled && serverStrListCalled; }; serverSideNetworkBehaviourClass.OnMySharedObjectReferencedByIdUpdated = (receivedObj) => { Assert.AreSame(obj2, receivedObj); serverMySharedObjCalled = true; m_FinishedTest = clientMyObjCalled && clientMySharedObjCalled && clientMyObjPassedWithThisRefCalled && - serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled; + serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled && + serverIntListCalled && serverStrListCalled; + }; + serverSideNetworkBehaviourClass.OnIntListUpdated = (receivedList) => + { + Assert.AreEqual(intList.Count, receivedList.Count); + for (var i = 0; i < receivedList.Count; ++i) + { + Assert.AreEqual(intList[i], receivedList[i]); + } + serverIntListCalled = true; + m_FinishedTest = clientMyObjCalled && clientMySharedObjCalled && clientMyObjPassedWithThisRefCalled && + serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled && + serverIntListCalled && serverStrListCalled; + }; + serverSideNetworkBehaviourClass.OnStringListUpdated = (receivedList) => + { + Assert.AreEqual(strList.Count, receivedList.Count); + for (var i = 0; i < receivedList.Count; ++i) + { + Assert.AreEqual(strList[i], receivedList[i]); + } + serverStrListCalled = true; + m_FinishedTest = clientMyObjCalled && clientMySharedObjCalled && clientMyObjPassedWithThisRefCalled && + serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled && + serverIntListCalled && serverStrListCalled; }; clientSideNetworkBehaviourClass.SendMyObjectServerRpc(obj); clientSideNetworkBehaviourClass.SendMySharedObjectReferencedByIdServerRpc(obj2); clientSideNetworkBehaviourClass.SendMyObjectPassedWithThisRefServerRpc(obj3); + clientSideNetworkBehaviourClass.SendIntListServerRpc(intList); + clientSideNetworkBehaviourClass.SendStringListServerRpc(strList); // Wait until the test has finished or we time out var timeOutPeriod = Time.realtimeSinceStartup + 5; @@ -329,12 +365,16 @@ public IEnumerator ExtensionMethodArrayRpcTest() var objs = new[] { new MyObject(256), new MyObject(512) }; var objs2 = new[] { new MySharedObjectReferencedById(256), new MySharedObjectReferencedById(512) }; var objs3 = new[] { new MyObjectPassedWithThisRef(256), new MyObjectPassedWithThisRef(512) }; + var intList = new[] { new List { 5, 10, 15 }, new List { 5, 1 } }; + var strList = new[] { new List { "foo", "bar" }, new List { "baz", "qux" }, new List { "quuz" } }; bool clientMyObjCalled = false; bool clientMyObjPassedWithThisRefCalled = false; - bool clientMySharedObjCalled = true; + bool clientMySharedObjCalled = false; bool serverMyObjCalled = false; bool serverMyObjPassedWithThisRefCalled = false; - bool serverMySharedObjCalled = true; + bool serverMySharedObjCalled = false; + bool serverIntListCalled = false; + bool serverStrListCalled = false; clientSideNetworkBehaviourClass.OnMyObjectUpdated = (receivedObjs) => { Assert.AreEqual(receivedObjs.Length, objs2.Length); @@ -345,7 +385,8 @@ public IEnumerator ExtensionMethodArrayRpcTest() } clientMyObjCalled = true; m_FinishedTest = clientMyObjCalled && clientMySharedObjCalled && clientMyObjPassedWithThisRefCalled && - serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled; + serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled && + serverIntListCalled && serverStrListCalled; }; serverSideNetworkBehaviourClass.OnMyObjectUpdated = (receivedObjs) => { @@ -357,7 +398,8 @@ public IEnumerator ExtensionMethodArrayRpcTest() } serverMyObjCalled = true; m_FinishedTest = clientMyObjCalled && clientMySharedObjCalled && clientMyObjPassedWithThisRefCalled && - serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled; + serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled && + serverIntListCalled && serverStrListCalled; }; clientSideNetworkBehaviourClass.OnMyObjectPassedWithThisRefUpdated = (receivedObjs) => { @@ -369,7 +411,8 @@ public IEnumerator ExtensionMethodArrayRpcTest() } clientMyObjPassedWithThisRefCalled = true; m_FinishedTest = clientMyObjCalled && clientMySharedObjCalled && clientMyObjPassedWithThisRefCalled && - serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled; + serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled && + serverIntListCalled && serverStrListCalled; }; serverSideNetworkBehaviourClass.OnMyObjectPassedWithThisRefUpdated = (receivedObjs) => { @@ -381,7 +424,8 @@ public IEnumerator ExtensionMethodArrayRpcTest() } serverMyObjPassedWithThisRefCalled = true; m_FinishedTest = clientMyObjCalled && clientMySharedObjCalled && clientMyObjPassedWithThisRefCalled && - serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled; + serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled && + serverIntListCalled && serverStrListCalled; }; clientSideNetworkBehaviourClass.OnMySharedObjectReferencedByIdUpdated = (receivedObjs) => { @@ -392,7 +436,8 @@ public IEnumerator ExtensionMethodArrayRpcTest() } clientMySharedObjCalled = true; m_FinishedTest = clientMyObjCalled && clientMySharedObjCalled && clientMyObjPassedWithThisRefCalled && - serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled; + serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled && + serverIntListCalled && serverStrListCalled; }; serverSideNetworkBehaviourClass.OnMySharedObjectReferencedByIdUpdated = (receivedObjs) => { @@ -403,12 +448,47 @@ public IEnumerator ExtensionMethodArrayRpcTest() } serverMySharedObjCalled = true; m_FinishedTest = clientMyObjCalled && clientMySharedObjCalled && clientMyObjPassedWithThisRefCalled && - serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled; + serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled && + serverIntListCalled && serverStrListCalled; + }; + serverSideNetworkBehaviourClass.OnIntListUpdated = (receivedLists) => + { + Assert.AreEqual(receivedLists.Length, intList.Length); + for (var i = 0; i < receivedLists.Length; ++i) + { + Assert.AreEqual(receivedLists[i].Count, intList[i].Count); + for (var j = 0; j < receivedLists[i].Count; ++j) + { + Assert.AreEqual(intList[i][j], receivedLists[i][j]); + } + } + serverIntListCalled = true; + m_FinishedTest = clientMyObjCalled && clientMySharedObjCalled && clientMyObjPassedWithThisRefCalled && + serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled && + serverIntListCalled && serverStrListCalled; + }; + serverSideNetworkBehaviourClass.OnStringListUpdated = (receivedLists) => + { + Assert.AreEqual(receivedLists.Length, strList.Length); + for (var i = 0; i < receivedLists.Length; ++i) + { + Assert.AreEqual(receivedLists[i].Count, strList[i].Count); + for (var j = 0; j < receivedLists[i].Count; ++j) + { + Assert.AreEqual(strList[i][j], receivedLists[i][j]); + } + } + serverStrListCalled = true; + m_FinishedTest = clientMyObjCalled && clientMySharedObjCalled && clientMyObjPassedWithThisRefCalled && + serverMyObjCalled && serverMySharedObjCalled && serverMyObjPassedWithThisRefCalled && + serverIntListCalled && serverStrListCalled; }; clientSideNetworkBehaviourClass.SendMyObjectServerRpc(objs); clientSideNetworkBehaviourClass.SendMySharedObjectReferencedByIdServerRpc(objs2); clientSideNetworkBehaviourClass.SendMyObjectPassedWithThisRefServerRpc(objs3); + clientSideNetworkBehaviourClass.SendIntListServerRpc(intList); + clientSideNetworkBehaviourClass.SendStringListServerRpc(strList); // Wait until the test has finished or we time out var timeOutPeriod = Time.realtimeSinceStartup + 5; @@ -736,6 +816,11 @@ public class TestSerializationComponent : NetworkBehaviour public delegate void OnMyObjectUpdatedDelgateHandler(MyObject obj); public OnMyObjectUpdatedDelgateHandler OnMyObjectUpdated; + public delegate void OnIntListUpdatedDelgateHandler(List lst); + public OnIntListUpdatedDelgateHandler OnIntListUpdated; + public delegate void OnStringListUpdatedDelgateHandler(List lst); + public OnStringListUpdatedDelgateHandler OnStringListUpdated; + public delegate void OnMyObjectPassedWithThisRefUpdatedDelgateHandler(MyObjectPassedWithThisRef obj); public OnMyObjectPassedWithThisRefUpdatedDelgateHandler OnMyObjectPassedWithThisRefUpdated; @@ -934,6 +1019,22 @@ public void SendMyObjectClientRpc(MyObject obj) } } [ClientRpc] + public void SendIntListClientRpc(List lst) + { + if (OnIntListUpdated != null) + { + OnIntListUpdated.Invoke(lst); + } + } + [ClientRpc] + public void SendStringListClientRpc(List lst) + { + if (OnStringListUpdated != null) + { + OnStringListUpdated.Invoke(lst); + } + } + [ClientRpc] public void SendMyObjectPassedWithThisRefClientRpc(MyObjectPassedWithThisRef obj) { if (OnMyObjectPassedWithThisRefUpdated != null) @@ -961,6 +1062,26 @@ public void SendMyObjectServerRpc(MyObject obj) SendMyObjectClientRpc(obj); } + [ServerRpc] + public void SendIntListServerRpc(List lst) + { + if (OnIntListUpdated != null) + { + OnIntListUpdated.Invoke(lst); + } + SendIntListClientRpc(lst); + } + + [ServerRpc] + public void SendStringListServerRpc(List lst) + { + if (OnStringListUpdated != null) + { + OnStringListUpdated.Invoke(lst); + } + SendStringListClientRpc(lst); + } + [ServerRpc] public void SendMyObjectPassedWithThisRefServerRpc(MyObjectPassedWithThisRef obj) { @@ -998,6 +1119,10 @@ public class TestCustomTypesArrayComponent : NetworkBehaviour public delegate void OnMyObjectUpdatedDelgateHandler(MyObject[] obj); public OnMyObjectUpdatedDelgateHandler OnMyObjectUpdated; + public delegate void OnIntListUpdatedDelgateHandler(List[] obj); + public OnIntListUpdatedDelgateHandler OnIntListUpdated; + public delegate void OnStringListUpdatedDelgateHandler(List[] obj); + public OnStringListUpdatedDelgateHandler OnStringListUpdated; public delegate void OnMyObjectPassedWithThisRefUpdatedDelgateHandler(MyObjectPassedWithThisRef[] obj); public OnMyObjectPassedWithThisRefUpdatedDelgateHandler OnMyObjectPassedWithThisRefUpdated; @@ -1093,6 +1218,24 @@ public void SendMyObjectClientRpc(MyObject[] objs) } } + [ClientRpc] + public void SendIntListClientRpc(List[] lists) + { + if (OnIntListUpdated != null) + { + OnIntListUpdated.Invoke(lists); + } + } + + [ClientRpc] + public void SendStringListClientRpc(List[] lists) + { + if (OnStringListUpdated != null) + { + OnStringListUpdated.Invoke(lists); + } + } + [ClientRpc] public void SendMyObjectPassedWithThisRefClientRpc(MyObjectPassedWithThisRef[] objs) { @@ -1121,6 +1264,26 @@ public void SendMyObjectServerRpc(MyObject[] objs) SendMyObjectClientRpc(objs); } + [ServerRpc] + public void SendIntListServerRpc(List[] lists) + { + if (OnIntListUpdated != null) + { + OnIntListUpdated.Invoke(lists); + } + SendIntListClientRpc(lists); + } + + [ServerRpc] + public void SendStringListServerRpc(List[] lists) + { + if (OnStringListUpdated != null) + { + OnStringListUpdated.Invoke(lists); + } + SendStringListClientRpc(lists); + } + [ServerRpc] public void SendMyObjectPassedWithThisRefServerRpc(MyObjectPassedWithThisRef[] objs) { @@ -1259,6 +1422,7 @@ public static void WriteValueSafe(this FastBufferWriter writer, MySharedObjectRe { writer.WriteValueSafe(value.I); } + public static void ReadValueSafe(this FastBufferReader reader, out MyObject[] values) { reader.ReadValueSafe(out int length); @@ -1269,6 +1433,112 @@ public static void ReadValueSafe(this FastBufferReader reader, out MyObject[] va } } + public static void ReadValueSafe(this FastBufferReader reader, out List value) + { + reader.ReadValueSafe(out int length); + value = new List(); + for (var i = 0; i < length; ++i) + { + reader.ReadValueSafe(out int val); + value.Add(val); + } + } + + //Serialization write for a List of ints + public static void WriteValueSafe(this FastBufferWriter writer, in List value) + { + writer.WriteValueSafe(value.Count); + foreach (var item in value) + { + writer.WriteValueSafe(item); + } + } + + //Serialization read for a List of strings + public static void ReadValueSafe(this FastBufferReader reader, out List value) + { + reader.ReadValueSafe(out int length); + value = new List(); + for (var i = 0; i < length; ++i) + { + reader.ReadValueSafe(out string val); + value.Add(val); + } + } + + //Serialization write for a List of strings + public static void WriteValueSafe(this FastBufferWriter writer, in List value) + { + writer.WriteValueSafe(value.Count); + foreach (var item in value) + { + writer.WriteValueSafe(item); + } + } + + public static void ReadValueSafe(this FastBufferReader reader, out List[] value) + { + reader.ReadValueSafe(out int length); + value = new List[length]; + for (var i = 0; i < length; ++i) + { + reader.ReadValueSafe(out int oneLength); + + value[i] = new List(); + for (var j = 0; j < oneLength; ++j) + { + reader.ReadValueSafe(out int val); + value[i].Add(val); + } + } + } + + //Serialization write for a List of ints + public static void WriteValueSafe(this FastBufferWriter writer, in List[] value) + { + writer.WriteValueSafe(value.Length); + foreach (var item in value) + { + writer.WriteValueSafe(item.Count); + foreach (var subItem in item) + { + writer.WriteValueSafe(subItem); + } + } + } + + //Serialization read for a List of strings + public static void ReadValueSafe(this FastBufferReader reader, out List[] value) + { + reader.ReadValueSafe(out int length); + value = new List[length]; + for (var i = 0; i < length; ++i) + { + reader.ReadValueSafe(out int oneLength); + + value[i] = new List(); + for (var j = 0; j < oneLength; ++j) + { + reader.ReadValueSafe(out string val); + value[i].Add(val); + } + } + } + + //Serialization write for a List of strings + public static void WriteValueSafe(this FastBufferWriter writer, in List[] value) + { + writer.WriteValueSafe(value.Length); + foreach (var item in value) + { + writer.WriteValueSafe(item.Count); + foreach (var subItem in item) + { + writer.WriteValueSafe(subItem); + } + } + } + public static void WriteValueSafe(this FastBufferWriter writer, in MyObject[] values) { writer.WriteValueSafe(values.Length); From c42ec046e4ffd0a2511d918db1aa8c5aea6caaf4 Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Wed, 24 Aug 2022 13:26:29 -0400 Subject: [PATCH 035/138] fix: not sending all NetworkVariables (#2135) * fix: not sending all NetworkVariables to all clients when a client connects to a server. (#1987) * fix This fixes the issue with some of the tests failing. Disabled the tests where clients don't have a prefab defined. * update Fixing issues with the last 5 tests. * update Got another (of the five) test working. Adding some helpers to assure the prefabs are spawned before trying to check them. Updated some comments. * test update This resolves the remaining issues with the deferred message tests for this PR. * test update I believe this will fix the tests having instability issues as well. Co-authored-by: NoelStephensUnity <73188597+NoelStephensUnity@users.noreply.github.com> Co-authored-by: Noel Stephens --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Spawning/NetworkSpawnManager.cs | 2 - .../Tests/Runtime/DeferredMessagingTests.cs | 404 +++++++++++++----- 3 files changed, 303 insertions(+), 104 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 9ab71d3968..13a9476e4c 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -24,6 +24,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed not sending all NetworkVariables to all clients when a client connects to a server. (#1987) - Fixed an issue where reading/writing more than 8 bits at a time with BitReader/BitWriter would write/read from the wrong place, returning and incorrect result. (#2130) - Fixed issue with the internal `NetworkTransformState.m_Bitset` flag not getting cleared upon the next tick advancement. (#2110) - Fixed interpolation issue with `NetworkTransform.Teleport`. (#2110) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 3300af9c50..4b6a214a9a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -575,8 +575,6 @@ internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject }; var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientId); NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size); - - networkObject.MarkVariablesDirty(true); } internal ulong? GetSpawnParentId(NetworkObject networkObject) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs index 28ed3793df..298f5f273c 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs @@ -198,29 +198,69 @@ public void SendTestClientRpc() { ClientRpcCalled = true; } + + public static readonly List ClientInstances = new List(); + public override void OnNetworkSpawn() + { + if (!IsServer) + { + ClientInstances.Add(NetworkManager.LocalClientId); + } + base.OnNetworkSpawn(); + } } public class DeferredMessageTestNetworkVariableComponent : NetworkBehaviour { - public NetworkVariable TestNetworkVariable = new NetworkVariable(); + public static readonly List ClientInstances = new List(); + + public NetworkVariable TestNetworkVariable; + + public void Awake() + { + TestNetworkVariable = new NetworkVariable(default, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); + } + + public override void OnNetworkSpawn() + { + if (!IsServer) + { + ClientInstances.Add(NetworkManager.LocalClientId); + } + base.OnNetworkSpawn(); + } } public class DeferredMessageTestRpcAndNetworkVariableComponent : NetworkBehaviour { + public static readonly List ClientInstances = new List(); public bool ClientRpcCalled; + public NetworkVariable TestNetworkVariable; + + public void Awake() + { + TestNetworkVariable = new NetworkVariable(default, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); + } + + public override void OnNetworkSpawn() + { + if (!IsServer) + { + ClientInstances.Add(NetworkManager.LocalClientId); + } + base.OnNetworkSpawn(); + } [ClientRpc] public void SendTestClientRpc() { ClientRpcCalled = true; } - - public NetworkVariable TestNetworkVariable = new NetworkVariable(); } public class DeferredMessagingTest : NetcodeIntegrationTest { - protected override int NumberOfClients => 2; + protected override int NumberOfClients => 0; private List m_ClientSpawnCatchers = new List(); @@ -228,32 +268,17 @@ public class DeferredMessagingTest : NetcodeIntegrationTest private GameObject m_NetworkVariablePrefab; private GameObject m_RpcAndNetworkVariablePrefab; + private int m_NumberOfClientsToLateJoin = 2; + protected override IEnumerator OnSetup() { + DeferredMessageTestRpcAndNetworkVariableComponent.ClientInstances.Clear(); + DeferredMessageTestRpcComponent.ClientInstances.Clear(); + DeferredMessageTestNetworkVariableComponent.ClientInstances.Clear(); + m_SkipAddingPrefabsToClient = false; // Host is irrelevant, messages don't get sent to the host "client" m_UseHost = false; - m_RpcPrefab = new GameObject("Object With RPC"); - var networkObject = m_RpcPrefab.AddComponent(); - m_RpcPrefab.AddComponent(); - - // Make it a prefab - NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); - - m_NetworkVariablePrefab = new GameObject("Object With NetworkVariable"); - networkObject = m_NetworkVariablePrefab.AddComponent(); - m_NetworkVariablePrefab.AddComponent(); - - // Make it a prefab - NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); - - m_RpcAndNetworkVariablePrefab = new GameObject("Object With NetworkVariable And RPC"); - networkObject = m_RpcAndNetworkVariablePrefab.AddComponent(); - m_RpcAndNetworkVariablePrefab.AddComponent(); - - // Make it a prefab - NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); - // Replace the IDeferredMessageManager component with our test one in the component factory ComponentFactory.Register(networkManager => new TestDeferredMessageManager(networkManager)); yield return null; @@ -269,13 +294,54 @@ protected override IEnumerator OnTearDown() protected override void OnServerAndClientsCreated() { - m_ServerNetworkManager.AddNetworkPrefab(m_RpcPrefab); - m_ServerNetworkManager.AddNetworkPrefab(m_NetworkVariablePrefab); - m_ServerNetworkManager.AddNetworkPrefab(m_RpcAndNetworkVariablePrefab); + // Note: This is where prefabs should be created + m_RpcPrefab = CreateNetworkObjectPrefab("Object With RPC"); + var networkObject = m_RpcPrefab.GetComponent(); + m_RpcPrefab.AddComponent(); + + m_NetworkVariablePrefab = CreateNetworkObjectPrefab("Object With NetworkVariable"); + networkObject = m_NetworkVariablePrefab.GetComponent(); + m_NetworkVariablePrefab.AddComponent(); + + m_RpcAndNetworkVariablePrefab = CreateNetworkObjectPrefab("Object With NetworkVariable And RPC"); + networkObject = m_RpcAndNetworkVariablePrefab.GetComponent(); + m_RpcAndNetworkVariablePrefab.AddComponent(); + m_ServerNetworkManager.NetworkConfig.ForceSamePrefabs = false; - foreach (var client in m_ClientNetworkManagers) + + } + + private bool m_SkipAddingPrefabsToClient = false; + + private void AddPrefabsToClient(NetworkManager networkManager) + { + networkManager.AddNetworkPrefab(m_RpcPrefab); + networkManager.AddNetworkPrefab(m_NetworkVariablePrefab); + networkManager.AddNetworkPrefab(m_RpcAndNetworkVariablePrefab); + } + + protected override void OnNewClientCreated(NetworkManager networkManager) + { + networkManager.NetworkConfig.ForceSamePrefabs = false; + if (!m_SkipAddingPrefabsToClient) + { + AddPrefabsToClient(networkManager); + } + + base.OnNewClientCreated(networkManager); + } + + private IEnumerator SpawnClients(bool clearTestDeferredMessageManagerCallFlags = true) + { + for (int i = 0; i < m_NumberOfClientsToLateJoin; i++) + { + // Create and join client + yield return CreateAndStartNewClient(); + } + + if (clearTestDeferredMessageManagerCallFlags) { - client.NetworkConfig.ForceSamePrefabs = false; + ClearTestDeferredMessageManagerCallFlags(); } } @@ -302,21 +368,6 @@ private void CatchSpawns() } } - private void RegisterClientPrefabs(bool clearTestDeferredMessageManagerCallFlags = true) - { - foreach (var client in m_ClientNetworkManagers) - { - client.AddNetworkPrefab(m_RpcPrefab); - client.AddNetworkPrefab(m_NetworkVariablePrefab); - client.AddNetworkPrefab(m_RpcAndNetworkVariablePrefab); - } - - if (clearTestDeferredMessageManagerCallFlags) - { - ClearTestDeferredMessageManagerCallFlags(); - } - } - private void ReleaseSpawns() { for (var i = 0; i < m_ClientNetworkManagers.Length; ++i) @@ -433,6 +484,21 @@ private List WaitForAllClientsToReceive WaitForAllClientsToReceive() + where TFirstMessage : INetworkMessage + where TSecondMessage : INetworkMessage + where TThirdMessage : INetworkMessage + { + var waiters = new List(); + foreach (var client in m_ClientNetworkManagers) + { + waiters.Add(NetcodeIntegrationTestHelpers.WaitForMessageOfTypeReceived(client)); + waiters.Add(NetcodeIntegrationTestHelpers.WaitForMessageOfTypeReceived(client)); + waiters.Add(NetcodeIntegrationTestHelpers.WaitForMessageOfTypeReceived(client)); + } + return waiters; + } + private List WaitForAllClientsToReceive() where TFirstMessage : INetworkMessage where TSecondMessage : INetworkMessage @@ -454,7 +520,7 @@ private List WaitForAllClientsToReceive().NetworkManagerOwner = m_ServerNetworkManager; @@ -477,7 +543,7 @@ public IEnumerator WhenAnRpcArrivesBeforeASpawnArrives_ItIsDeferred() [UnityTest] public IEnumerator WhenADespawnArrivesBeforeASpawnArrives_ItIsDeferred() { - RegisterClientPrefabs(); + yield return SpawnClients(); CatchSpawns(); var serverObject = Object.Instantiate(m_RpcPrefab); serverObject.GetComponent().NetworkManagerOwner = m_ServerNetworkManager; @@ -500,7 +566,7 @@ public IEnumerator WhenADespawnArrivesBeforeASpawnArrives_ItIsDeferred() [UnityTest] public IEnumerator WhenAChangeOwnershipMessageArrivesBeforeASpawnArrives_ItIsDeferred() { - RegisterClientPrefabs(); + yield return SpawnClients(); CatchSpawns(); var serverObject = Object.Instantiate(m_RpcPrefab); serverObject.GetComponent().NetworkManagerOwner = m_ServerNetworkManager; @@ -521,12 +587,13 @@ public IEnumerator WhenAChangeOwnershipMessageArrivesBeforeASpawnArrives_ItIsDef [UnityTest] public IEnumerator WhenANetworkVariableDeltaMessageArrivesBeforeASpawnArrives_ItIsDeferred() { - RegisterClientPrefabs(); + m_SkipAddingPrefabsToClient = true; + yield return SpawnClients(); CatchSpawns(); // Have to start these before spawning here because spawning sends a NetworkVariableDeltaMessage, too // Depending on timing, if we start this after spawning, we may end up missing the first one. - var waiters = WaitForAllClientsToReceive(); + var waiters = WaitForAllClientsToReceive(); var coroutines = StartMultiple(waiters); var serverObject = Object.Instantiate(m_NetworkVariablePrefab); @@ -545,13 +612,16 @@ public IEnumerator WhenANetworkVariableDeltaMessageArrivesBeforeASpawnArrives_It Assert.IsFalse(manager.ProcessTriggersCalled); // TODO: Network Variables generate an extra message immediately at spawn for some reason... // Seems like a bug since the network variable data is in the spawn message already. - AssertSpawnTriggerCountForObject(manager, serverObject, 2); + AssertSpawnTriggerCountForObject(manager, serverObject, 1); } } [UnityTest] + //[Ignore("Disabling this temporarily until it is migrated into new integration test.")] public IEnumerator WhenASpawnMessageArrivesBeforeThePrefabIsAvailable_ItIsDeferred() { + m_SkipAddingPrefabsToClient = true; + yield return SpawnClients(); var serverObject = Object.Instantiate(m_RpcPrefab); serverObject.GetComponent().NetworkManagerOwner = m_ServerNetworkManager; serverObject.GetComponent().Spawn(); @@ -631,8 +701,28 @@ public IEnumerator WhenAChangeOwnershipMessageIsDeferred_ItIsProcessedOnSpawn() public IEnumerator WhenANetworkVariableDeltaMessageIsDeferred_ItIsProcessedOnSpawn() { yield return WhenANetworkVariableDeltaMessageArrivesBeforeASpawnArrives_ItIsDeferred(); + + foreach (var client in m_ClientNetworkManagers) + { + AddPrefabsToClient(client); + } + ReleaseSpawns(); + // Wait for the clients to spawn the NetworkObjects + bool HaveAllClientsSpawned() + { + foreach (var client in m_ClientNetworkManagers) + { + if (!DeferredMessageTestNetworkVariableComponent.ClientInstances.Contains(client.LocalClientId)) + { + return false; + } + } + return true; + } + yield return WaitForConditionOrTimeOut(HaveAllClientsSpawned); + foreach (var client in m_ClientNetworkManagers) { var manager = (TestDeferredMessageManager)client.DeferredMessageManager; @@ -647,9 +737,31 @@ public IEnumerator WhenANetworkVariableDeltaMessageIsDeferred_ItIsProcessedOnSpa [UnityTest] public IEnumerator WhenASpawnMessageIsDeferred_ItIsProcessedOnAddPrefab() { + // This will prevent spawned clients from adding prefabs + m_SkipAddingPrefabsToClient = true; yield return WhenASpawnMessageArrivesBeforeThePrefabIsAvailable_ItIsDeferred(); - RegisterClientPrefabs(false); + // Now add the prefabs + foreach (var client in m_ClientNetworkManagers) + { + AddPrefabsToClient(client); + } + + // Wait for the clients to spawn the NetworkObjects + bool HaveAllClientsSpawned() + { + foreach (var client in m_ClientNetworkManagers) + { + if (!DeferredMessageTestRpcComponent.ClientInstances.Contains(client.LocalClientId)) + { + return false; + } + } + return true; + } + yield return WaitForConditionOrTimeOut(HaveAllClientsSpawned); + + // Validate this test foreach (var client in m_ClientNetworkManagers) { var manager = (TestDeferredMessageManager)client.DeferredMessageManager; @@ -664,12 +776,12 @@ public IEnumerator WhenASpawnMessageIsDeferred_ItIsProcessedOnAddPrefab() [UnityTest] public IEnumerator WhenMultipleSpawnTriggeredMessagesAreDeferred_TheyAreAllProcessedOnSpawn() { - RegisterClientPrefabs(); + m_SkipAddingPrefabsToClient = true; + yield return SpawnClients(); CatchSpawns(); - // Have to start these before spawning here because spawning sends a NetworkVariableDeltaMessage, too - // Depending on timing, if we start this after spawning, we may end up missing the first one. - var waiters = WaitForAllClientsToReceive(); + // We wait for the RPC message, the NetworkVariableDeltaMessage, and the ChangeOwnershipMessage messages + var waiters = WaitForAllClientsToReceive(); var coroutines = StartMultiple(waiters); var serverObject = Object.Instantiate(m_RpcAndNetworkVariablePrefab); @@ -679,6 +791,9 @@ public IEnumerator WhenMultipleSpawnTriggeredMessagesAreDeferred_TheyAreAllProce serverObject.GetComponent().SendTestClientRpc(); serverObject.GetComponent().TestNetworkVariable.Value = 1; + // TODO: Remove this if we figure out how to work around the NetworkVariableDeltaMessage.Serialized issue at line 59 + // Otherwise, we have to wait for at least 1 tick for the NetworkVariableDeltaMessage to be generated before changing ownership + yield return s_DefaultWaitForTick; serverObject.GetComponent().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); // Should be received in order so we'll wait for the last one. @@ -690,29 +805,49 @@ public IEnumerator WhenMultipleSpawnTriggeredMessagesAreDeferred_TheyAreAllProce Assert.IsTrue(manager.DeferMessageCalled); Assert.IsFalse(manager.ProcessTriggersCalled); - Assert.AreEqual(4, manager.DeferredMessageCountTotal()); - Assert.AreEqual(4, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn)); - Assert.AreEqual(4, manager.DeferredMessageCountForKey(IDeferredMessageManager.TriggerType.OnSpawn, serverObject.GetComponent().NetworkObjectId)); + Assert.AreEqual(3, manager.DeferredMessageCountTotal()); + Assert.AreEqual(3, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn)); + Assert.AreEqual(3, manager.DeferredMessageCountForKey(IDeferredMessageManager.TriggerType.OnSpawn, serverObject.GetComponent().NetworkObjectId)); Assert.AreEqual(0, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnAddPrefab)); + AddPrefabsToClient(client); } + ReleaseSpawns(); + // Wait for the clients to spawn the NetworkObjects + bool HaveAllClientsSpawned() + { + foreach (var client in m_ClientNetworkManagers) + { + if (!DeferredMessageTestRpcAndNetworkVariableComponent.ClientInstances.Contains(client.LocalClientId)) + { + return false; + } + } + return true; + } + yield return WaitForConditionOrTimeOut(HaveAllClientsSpawned); + yield return new WaitForSeconds(0.1f); + + // Validate the spawned objects foreach (var client in m_ClientNetworkManagers) { var manager = (TestDeferredMessageManager)client.DeferredMessageManager; - Assert.IsTrue(manager.ProcessTriggersCalled); - Assert.AreEqual(0, manager.DeferredMessageCountTotal()); + Assert.IsTrue(manager.ProcessTriggersCalled, "Process triggers were not called!"); + Assert.AreEqual(0, manager.DeferredMessageCountTotal(), $"Deferred message count ({manager.DeferredMessageCountTotal()}) is not zero!"); var component = GetComponentForClient(client.LocalClientId); - Assert.IsTrue(component.ClientRpcCalled); - Assert.AreEqual(1, component.TestNetworkVariable.Value); - Assert.AreEqual(m_ClientNetworkManagers[0].LocalClientId, component.OwnerClientId); + Assert.IsTrue(component.ClientRpcCalled, "Client RPC was not called!"); + Assert.AreEqual(1, component.TestNetworkVariable.Value, $"Test {nameof(NetworkVariable)} ({component.TestNetworkVariable.Value}) does not equal 1!"); + Assert.AreEqual(m_ClientNetworkManagers[0].LocalClientId, component.OwnerClientId, $"{component.name} owner id ({component.OwnerClientId}) does not equal first client id ({m_ClientNetworkManagers[0].LocalClientId})"); } } [UnityTest] public IEnumerator WhenMultipleAddPrefabTriggeredMessagesAreDeferred_TheyAreAllProcessedOnAddNetworkPrefab() { + m_SkipAddingPrefabsToClient = true; + yield return SpawnClients(); var serverObject = Object.Instantiate(m_RpcPrefab); serverObject.GetComponent().NetworkManagerOwner = m_ServerNetworkManager; serverObject.GetComponent().Spawn(); @@ -733,9 +868,23 @@ public IEnumerator WhenMultipleAddPrefabTriggeredMessagesAreDeferred_TheyAreAllP Assert.AreEqual(0, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn)); Assert.AreEqual(2, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnAddPrefab)); Assert.AreEqual(2, manager.DeferredMessageCountForKey(IDeferredMessageManager.TriggerType.OnAddPrefab, serverObject.GetComponent().GlobalObjectIdHash)); + AddPrefabsToClient(client); } - RegisterClientPrefabs(false); + // Wait for the clients to spawn the NetworkObjects + bool HaveAllClientsSpawned() + { + foreach (var client in m_ClientNetworkManagers) + { + if (!DeferredMessageTestRpcComponent.ClientInstances.Contains(client.LocalClientId)) + { + return false; + } + } + return true; + } + yield return WaitForConditionOrTimeOut(HaveAllClientsSpawned); + foreach (var client in m_ClientNetworkManagers) { @@ -769,8 +918,9 @@ public IEnumerator WhenMultipleAddPrefabTriggeredMessagesAreDeferred_TheyAreAllP [UnityTest] public IEnumerator WhenSpawnTriggeredMessagesAreDeferredBeforeThePrefabIsAdded_AddingThePrefabCausesThemToBeProcessed() { - // Because we're not waiting for the client to receive the spawn before we change the network variable value, - // there's only one NetworkVariableDeltaMessage this time. + m_SkipAddingPrefabsToClient = true; + yield return SpawnClients(); + var waiters = WaitForAllClientsToReceive(); var coroutines = StartMultiple(waiters); @@ -780,10 +930,14 @@ public IEnumerator WhenSpawnTriggeredMessagesAreDeferredBeforeThePrefabIsAdded_A serverObject.GetComponent().SendTestClientRpc(); serverObject.GetComponent().TestNetworkVariable.Value = 1; + // TODO: Remove this if we figure out how to work around the NetworkVariableDeltaMessage.Serialized issue at line 59 + // Otherwise, we have to wait for at least 1 tick for the NetworkVariableDeltaMessage to be generated before changing ownership + yield return s_DefaultWaitForTick; serverObject.GetComponent().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); yield return WaitMultiple(coroutines); + // Validate messages are deferred and pending foreach (var client in m_ClientNetworkManagers) { var manager = (TestDeferredMessageManager)client.DeferredMessageManager; @@ -794,10 +948,26 @@ public IEnumerator WhenSpawnTriggeredMessagesAreDeferredBeforeThePrefabIsAdded_A Assert.AreEqual(3, manager.DeferredMessageCountForKey(IDeferredMessageManager.TriggerType.OnSpawn, serverObject.GetComponent().NetworkObjectId)); Assert.AreEqual(1, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnAddPrefab)); Assert.AreEqual(1, manager.DeferredMessageCountForKey(IDeferredMessageManager.TriggerType.OnAddPrefab, serverObject.GetComponent().GlobalObjectIdHash)); + AddPrefabsToClient(client); } - RegisterClientPrefabs(false); + // Wait for the clients to spawn the NetworkObjects + bool HaveAllClientsSpawned() + { + foreach (var client in m_ClientNetworkManagers) + { + if (!DeferredMessageTestRpcAndNetworkVariableComponent.ClientInstances.Contains(client.LocalClientId)) + { + return false; + } + } + return true; + } + yield return WaitForConditionOrTimeOut(HaveAllClientsSpawned); + yield return new WaitForSeconds(0.1f); + + // Validate the test foreach (var client in m_ClientNetworkManagers) { var manager = (TestDeferredMessageManager)client.DeferredMessageManager; @@ -813,10 +983,10 @@ public IEnumerator WhenSpawnTriggeredMessagesAreDeferredBeforeThePrefabIsAdded_A } [UnityTest] - [Ignore("This test is unstable (MTT-4146)")] public IEnumerator WhenAMessageIsDeferredForMoreThanTheConfiguredTime_ItIsRemoved([Values(1, 2, 3)] int timeout) { - RegisterClientPrefabs(); + m_SkipAddingPrefabsToClient = true; + yield return SpawnClients(); CatchSpawns(); foreach (var client in m_ClientNetworkManagers) { @@ -870,23 +1040,30 @@ public IEnumerator WhenAMessageIsDeferredForMoreThanTheConfiguredTime_ItIsRemove yield return new WaitForSeconds(timeout + 0.1f); - Assert.AreEqual(NumberOfClients, purgeCount); - foreach (var client in m_ClientNetworkManagers) + bool HaveAllClientsPurged() { - var manager = (TestDeferredMessageManager)client.DeferredMessageManager; - Assert.AreEqual(0, manager.DeferredMessageCountTotal()); + foreach (var client in m_ClientNetworkManagers) + { + var manager = (TestDeferredMessageManager)client.DeferredMessageManager; + if (manager.DeferredMessageCountTotal() != 0) + { + return false; + } + } + return true; } + + yield return WaitForConditionOrTimeOut(HaveAllClientsPurged); + AssertOnTimeout("Timed out waiting for all clients to purge their deferred messages!"); } [UnityTest] - [Ignore("This test is unstable on standalones")] public IEnumerator WhenMultipleMessagesForTheSameObjectAreDeferredForMoreThanTheConfiguredTime_TheyAreAllRemoved([Values(1, 2, 3)] int timeout) { - RegisterClientPrefabs(); + m_SkipAddingPrefabsToClient = true; + yield return SpawnClients(); CatchSpawns(); - // Have to start these before spawning here because spawning sends a NetworkVariableDeltaMessage, too - // Depending on timing, if we start this after spawning, we may end up missing the first one. - var waiters = WaitForAllClientsToReceive(); + var waiters = WaitForAllClientsToReceive(); var coroutines = StartMultiple(waiters); foreach (var client in m_ClientNetworkManagers) @@ -932,35 +1109,42 @@ public IEnumerator WhenMultipleMessagesForTheSameObjectAreDeferredForMoreThanThe ++purgeCount; var elapsed = Time.realtimeSinceStartup - start; Assert.GreaterOrEqual(elapsed, timeout - 0.05f); - Assert.AreEqual(4, manager.DeferredMessageCountTotal()); - Assert.AreEqual(4, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn)); - Assert.AreEqual(4, manager.DeferredMessageCountForKey(IDeferredMessageManager.TriggerType.OnSpawn, key)); + Assert.AreEqual(3, manager.DeferredMessageCountTotal()); + Assert.AreEqual(3, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn)); + Assert.AreEqual(3, manager.DeferredMessageCountForKey(IDeferredMessageManager.TriggerType.OnSpawn, key)); Assert.AreEqual(serverObject.GetComponent().NetworkObjectId, key); }; var manager = (TestDeferredMessageManager)client.DeferredMessageManager; manager.OnBeforePurge = beforePurge; } - yield return new WaitForSeconds(timeout + 0.1f); - - Assert.AreEqual(NumberOfClients, purgeCount); - foreach (var client in m_ClientNetworkManagers) + bool HaveAllClientsPurged() { - var manager = (TestDeferredMessageManager)client.DeferredMessageManager; - Assert.AreEqual(0, manager.DeferredMessageCountTotal()); + foreach (var client in m_ClientNetworkManagers) + { + var manager = (TestDeferredMessageManager)client.DeferredMessageManager; + if (manager.DeferredMessageCountTotal() != 0) + { + return false; + } + } + return true; } + + yield return WaitForConditionOrTimeOut(HaveAllClientsPurged); + AssertOnTimeout("Timed out waiting for all clients to purge their deferred messages!"); } [UnityTest] public IEnumerator WhenMultipleMessagesForDifferentObjectsAreDeferredForMoreThanTheConfiguredTime_TheyAreAllRemoved([Values(1, 2, 3)] int timeout) { - RegisterClientPrefabs(); + m_SkipAddingPrefabsToClient = true; + yield return SpawnClients(); CatchSpawns(); - // Have to start these before spawning here because spawning sends a NetworkVariableDeltaMessage, too - // Depending on timing, if we start this after spawning, we may end up missing the first one. - var waiters = WaitForAllClientsToReceive(); - waiters.AddRange(WaitForAllClientsToReceive()); + // Since there are two unique objects we need to look for two sets of messages + var waiters = WaitForAllClientsToReceive(); + waiters.AddRange(WaitForAllClientsToReceive()); var coroutines = StartMultiple(waiters); foreach (var client in m_ClientNetworkManagers) @@ -1004,6 +1188,7 @@ public IEnumerator WhenMultipleMessagesForDifferentObjectsAreDeferredForMoreThan foreach (var unused in m_ClientNetworkManagers) { + LogAssert.Expect(LogType.Warning, $"[Netcode] Deferred messages were received for a trigger of type {IDeferredMessageManager.TriggerType.OnSpawn} with key {serverObject.GetComponent().NetworkObjectId}, but that trigger was not received within within {timeout} second(s)."); LogAssert.Expect(LogType.Warning, $"[Netcode] Deferred messages were received for a trigger of type {IDeferredMessageManager.TriggerType.OnSpawn} with key {serverObject2.GetComponent().NetworkObjectId}, but that trigger was not received within within {timeout} second(s)."); } @@ -1011,7 +1196,7 @@ public IEnumerator WhenMultipleMessagesForDifferentObjectsAreDeferredForMoreThan int purgeCount = 0; foreach (var client in m_ClientNetworkManagers) { - var remainingMessagesTotalThisClient = 8; + var remainingMessagesTotalThisClient = 6; TestDeferredMessageManager.BeforePurgeDelegate beforePurge = (manager, key) => { ++purgeCount; @@ -1019,16 +1204,19 @@ public IEnumerator WhenMultipleMessagesForDifferentObjectsAreDeferredForMoreThan Assert.GreaterOrEqual(elapsed, timeout - 0.05f); Assert.AreEqual(remainingMessagesTotalThisClient, manager.DeferredMessageCountTotal()); Assert.AreEqual(remainingMessagesTotalThisClient, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn)); - Assert.AreEqual(4, manager.DeferredMessageCountForKey(IDeferredMessageManager.TriggerType.OnSpawn, key)); - remainingMessagesTotalThisClient -= 4; + Assert.AreEqual(3, manager.DeferredMessageCountForKey(IDeferredMessageManager.TriggerType.OnSpawn, key)); + remainingMessagesTotalThisClient -= 3; }; var manager = (TestDeferredMessageManager)client.DeferredMessageManager; manager.OnBeforePurge = beforePurge; } yield return new WaitForSeconds(timeout + 0.1f); - - Assert.AreEqual(NumberOfClients * 2, purgeCount); + foreach (var client in m_ClientNetworkManagers) + { + AddPrefabsToClient(client); + } + Assert.AreEqual(m_NumberOfClientsToLateJoin * 2, purgeCount); foreach (var client in m_ClientNetworkManagers) { var manager = (TestDeferredMessageManager)client.DeferredMessageManager; @@ -1039,7 +1227,8 @@ public IEnumerator WhenMultipleMessagesForDifferentObjectsAreDeferredForMoreThan [UnityTest] public IEnumerator WhenADeferredMessageIsRemoved_OtherMessagesForSameObjectAreRemoved([Values(1, 2, 3)] int timeout) { - RegisterClientPrefabs(); + m_SkipAddingPrefabsToClient = true; + yield return SpawnClients(); CatchSpawns(); foreach (var client in m_ClientNetworkManagers) { @@ -1112,9 +1301,14 @@ public IEnumerator WhenADeferredMessageIsRemoved_OtherMessagesForSameObjectAreRe manager.OnBeforePurge = beforePurge; } + foreach (var client in m_ClientNetworkManagers) + { + AddPrefabsToClient(client); + } + yield return new WaitForSeconds(0.6f); - Assert.AreEqual(NumberOfClients, purgeCount); + Assert.AreEqual(m_NumberOfClientsToLateJoin, purgeCount); foreach (var client in m_ClientNetworkManagers) { var manager = (TestDeferredMessageManager)client.DeferredMessageManager; @@ -1125,7 +1319,8 @@ public IEnumerator WhenADeferredMessageIsRemoved_OtherMessagesForSameObjectAreRe [UnityTest] public IEnumerator WhenADeferredMessageIsRemoved_OtherMessagesForDifferentObjectsAreNotRemoved([Values(1, 2, 3)] int timeout) { - RegisterClientPrefabs(); + m_SkipAddingPrefabsToClient = true; + yield return SpawnClients(); CatchSpawns(); foreach (var client in m_ClientNetworkManagers) { @@ -1206,9 +1401,14 @@ public IEnumerator WhenADeferredMessageIsRemoved_OtherMessagesForDifferentObject manager.OnBeforePurge = beforePurge; } + foreach (var client in m_ClientNetworkManagers) + { + AddPrefabsToClient(client); + } + yield return new WaitForSeconds(0.6f); - Assert.AreEqual(NumberOfClients, purgeCount); + Assert.AreEqual(m_NumberOfClientsToLateJoin, purgeCount); foreach (var client in m_ClientNetworkManagers) { var manager = (TestDeferredMessageManager)client.DeferredMessageManager; From d75acc98ee7c33c2d8d4c36774cb26d9f723f347 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 24 Aug 2022 12:54:45 -0500 Subject: [PATCH 036/138] fix: TransformInterpolationTests Threshold Adjustment (#2145) * fix increasing the threshold slightly for consoles. * style adding additional error information. --- .../Tests/Runtime/TransformInterpolationTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs index 8ed5b78849..dd0b3ef98d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs @@ -11,7 +11,7 @@ namespace Unity.Netcode.RuntimeTests public class TransformInterpolationObject : NetworkBehaviour { // Set the minimum threshold which we will use as our margin of error - public const float MinThreshold = 0.001f; + public const float MinThreshold = 0.005f; public bool CheckPosition; public bool IsMoving; @@ -24,7 +24,7 @@ private void Update() { if (transform.position.y < -MinThreshold || transform.position.y > 100.0f + MinThreshold) { - Debug.LogError($"Interpolation failure. transform.position.y is {transform.position.y}. Should be between 0.0 and 100.0"); + Debug.LogError($"Interpolation failure. transform.position.y is {transform.position.y}. Should be between 0.0 and 100.0. Current threshold is [+/- {MinThreshold}]."); } } From 45cfa907128e7ac37d89a134bb8baa068fdff0e0 Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Wed, 24 Aug 2022 14:18:47 -0400 Subject: [PATCH 037/138] fix: splitting the internal and visible parts of OnNetworkSpawn (#2143) * fix: splitting the internal and visible parts of OnNetworkSpawn * fix: Not throwing an exception when NetworkVariable is written to before being spawned. Logging a warning instead Co-authored-by: Noel Stephens --- .../Runtime/Core/NetworkBehaviour.cs | 4 ++++ .../Runtime/Core/NetworkObject.cs | 7 +++++++ .../Runtime/NetworkVariable/NetworkVariableBase.cs | 11 ++++++++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 6f96f03697..f55972bb57 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -436,6 +436,10 @@ internal void InternalOnNetworkSpawn() IsSpawned = true; InitializeVariables(); UpdateNetworkProperties(); + } + + internal void VisibleOnNetworkSpawn() + { OnNetworkSpawn(); } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 0d889cb1e4..ce3b200d94 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -821,6 +821,13 @@ internal void InvokeBehaviourNetworkSpawn() Debug.LogWarning($"{ChildNetworkBehaviours[i].gameObject.name} is disabled! Netcode for GameObjects does not support spawning disabled NetworkBehaviours! The {ChildNetworkBehaviours[i].GetType().Name} component was skipped during spawn!"); } } + for (int i = 0; i < ChildNetworkBehaviours.Count; i++) + { + if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy) + { + ChildNetworkBehaviours[i].VisibleOnNetworkSpawn(); + } + } } internal void InvokeBehaviourNetworkDespawn() diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs index 4b8ea84930..96635f33b3 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -1,4 +1,5 @@ using System; +using UnityEngine; namespace Unity.Netcode { @@ -79,7 +80,15 @@ protected NetworkVariableBase( public virtual void SetDirty(bool isDirty) { m_IsDirty = isDirty; - if (m_IsDirty && m_NetworkBehaviour != null) + + if (m_NetworkBehaviour == null) + { + Debug.LogWarning($"NetworkVariable is written to, but doesn't know its NetworkBehaviour yet. " + + "Are you modifying a NetworkVariable before the NetworkObject is spawned?"); + return; + } + + if (m_IsDirty) { m_NetworkBehaviour.NetworkManager.MarkNetworkObjectDirty(m_NetworkBehaviour.NetworkObject); } From 93e06801e4a198d70c9569af8e1c3101658f8b3e Mon Sep 17 00:00:00 2001 From: Kitty Draper <284434+ShadauxCat@users.noreply.github.com> Date: Wed, 24 Aug 2022 18:20:09 -0500 Subject: [PATCH 038/138] fix: Corrects memory leak in MessageRegistrationTests (#2148) --- .../Editor/Messaging/MessageRegistrationTests.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs index 1228712c2e..38ca9b0bf1 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs @@ -137,9 +137,9 @@ public void WhenCreatingMessageSystem_OnlyProvidedTypesAreRegistered() { var sender = new NopMessageSender(); - var systemOne = new MessagingSystem(sender, null, new TestMessageProviderOne()); - var systemTwo = new MessagingSystem(sender, null, new TestMessageProviderTwo()); - var systemThree = new MessagingSystem(sender, null, new TestMessageProviderThree()); + using var systemOne = new MessagingSystem(sender, null, new TestMessageProviderOne()); + using var systemTwo = new MessagingSystem(sender, null, new TestMessageProviderTwo()); + using var systemThree = new MessagingSystem(sender, null, new TestMessageProviderThree()); using (systemOne) using (systemTwo) @@ -161,9 +161,9 @@ public void WhenCreatingMessageSystem_BoundTypeMessageHandlersAreRegistered() { var sender = new NopMessageSender(); - var systemOne = new MessagingSystem(sender, null, new TestMessageProviderOne()); - var systemTwo = new MessagingSystem(sender, null, new TestMessageProviderTwo()); - var systemThree = new MessagingSystem(sender, null, new TestMessageProviderThree()); + using var systemOne = new MessagingSystem(sender, null, new TestMessageProviderOne()); + using var systemTwo = new MessagingSystem(sender, null, new TestMessageProviderTwo()); + using var systemThree = new MessagingSystem(sender, null, new TestMessageProviderThree()); using (systemOne) using (systemTwo) @@ -235,7 +235,7 @@ public void MessagesGetPrioritizedCorrectly() { var sender = new NopMessageSender(); var provider = new OrderingMessageProvider(); - var messagingSystem = new MessagingSystem(sender, null, provider); + using var messagingSystem = new MessagingSystem(sender, null, provider); // the 3 priority messages should appear first, in lexicographic order Assert.AreEqual(messagingSystem.MessageTypes[0], typeof(ConnectionApprovedMessage)); From bb7998e36c2cd0a06ad52a2ede8a5cf16c4a75d2 Mon Sep 17 00:00:00 2001 From: Kitty Draper <284434+ShadauxCat@users.noreply.github.com> Date: Wed, 24 Aug 2022 19:18:11 -0500 Subject: [PATCH 039/138] fix: Don't let exceptions in OnNetworkSpawn/OnNetworkDespawn block processing of the next callback. [MTT-1378] (#1739) * fix: Don't let exceptions in OnNetworkSpawn/OnNetworkDespawn block processing of the next callback. * Standards and changelog. * Standards fixes * It would be good to check in the right file. * I swear these tests passed before but I have no idea how. Also review feedback. * Address review feedback. * Switched to LogAssert.Expect * Updated tests to NetcodeIntegrationTest style * Standards fix * Rewrote tests according to feedback. Also fixed merge issue with changelog. * Test the behavior on both server and client * style fixing white space issue. * Update com.unity.netcode.gameobjects/CHANGELOG.md Co-authored-by: Fatih Mar Co-authored-by: Noel Stephens Co-authored-by: Fatih Mar --- com.unity.netcode.gameobjects/CHANGELOG.md | 27 +- .../Runtime/Core/NetworkBehaviour.cs | 18 +- .../TestHelpers/Runtime/MessageHooks.cs | 16 +- .../Runtime/OnNetworkSpawnExceptionTests.cs | 297 ++++++++++++++++++ .../OnNetworkSpawnExceptionTests.cs.meta | 3 + 5 files changed, 344 insertions(+), 17 deletions(-) create mode 100644 testproject/Assets/Tests/Runtime/OnNetworkSpawnExceptionTests.cs create mode 100644 testproject/Assets/Tests/Runtime/OnNetworkSpawnExceptionTests.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 13a9476e4c..688d039d93 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -12,6 +12,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed - Fixed RPC codegen failing to choose the correct extension methods for FastBufferReader and FastBufferWriter when the parameters were a generic type (i.e., List) and extensions for multiple instantiations of that type have been defined (i.e., List and List) (#2142) +- Fixed throwing an exception in OnNetworkUpdate causing other OnNetworkUpdate calls to not be executed. (#1739) ## [1.0.1] - 2022-08-23 @@ -20,7 +21,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Changed version to 1.0.1. (#2131) - Updated dependency on `com.unity.transport` to 1.2.0. (#2129) - When using `UnityTransport`, _reliable_ payloads are now allowed to exceed the configured 'Max Payload Size'. Unreliable payloads remain bounded by this setting. (#2081) -- Preformance improvements for cases with large number of NetworkObjects, by not iterating over all unchanged NetworkObjects +- Performance improvements for cases with large number of NetworkObjects, by not iterating over all unchanged NetworkObjects ### Fixed @@ -238,7 +239,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - ResetTrigger function to NetworkAnimator (#1327) -### Fixed +### Fixed - Overflow exception when syncing Animator state. (#1327) - Added `try`/`catch` around RPC calls, preventing exception from causing further RPC calls to fail (#1329) @@ -263,7 +264,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Added `ClientNetworkTransform` sample to the SDK package (#1168) - Added `Bootstrap` sample to the SDK package (#1140) - Enhanced `NetworkSceneManager` implementation with additive scene loading capabilities (#1080, #955, #913) - - `NetworkSceneManager.OnSceneEvent` provides improved scene event notificaitons + - `NetworkSceneManager.OnSceneEvent` provides improved scene event notificaitons - Enhanced `NetworkTransform` implementation with per axis/component based and threshold based state replication (#1042, #1055, #1061, #1084, #1101) - Added a jitter-resistent `BufferedLinearInterpolator` for `NetworkTransform` (#1060) - Implemented `NetworkPrefabHandler` that provides support for object pooling and `NetworkPrefab` overrides (#1073, #1004, #977, #905,#749, #727) @@ -320,7 +321,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Removed `NetworkDictionary`, `NetworkSet` (#1149) - Removed `NetworkVariableSettings` (#1097) - Removed predefined `NetworkVariable` types (#1093) - - Removed `NetworkVariableBool`, `NetworkVariableByte`, `NetworkVariableSByte`, `NetworkVariableUShort`, `NetworkVariableShort`, `NetworkVariableUInt`, `NetworkVariableInt`, `NetworkVariableULong`, `NetworkVariableLong`, `NetworkVariableFloat`, `NetworkVariableDouble`, `NetworkVariableVector2`, `NetworkVariableVector3`, `NetworkVariableVector4`, `NetworkVariableColor`, `NetworkVariableColor32`, `NetworkVariableRay`, `NetworkVariableQuaternion` + - Removed `NetworkVariableBool`, `NetworkVariableByte`, `NetworkVariableSByte`, `NetworkVariableUShort`, `NetworkVariableShort`, `NetworkVariableUInt`, `NetworkVariableInt`, `NetworkVariableULong`, `NetworkVariableLong`, `NetworkVariableFloat`, `NetworkVariableDouble`, `NetworkVariableVector2`, `NetworkVariableVector3`, `NetworkVariableVector4`, `NetworkVariableColor`, `NetworkVariableColor32`, `NetworkVariableRay`, `NetworkVariableQuaternion` - Removed `NetworkChannel` and `MultiplexTransportAdapter` (#1133) - Removed ILPP backend for 2019.4, minimum required version is 2020.3+ (#895) - `NetworkManager.NetworkConfig` had the following properties removed: (#1080) @@ -392,14 +393,14 @@ This is the initial experimental Unity MLAPI Package, v0.1.0. - Integrated MLAPI with the Unity Profiler for versions 2020.2 and later: - Added new profiler modules for MLAPI that report important network data. - Attached the profiler to a remote player to view network data over the wire. -- A test project is available for building and experimenting with MLAPI features. This project is available in the MLAPI GitHub [testproject folder](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/tree/release/0.1.0/testproject). +- A test project is available for building and experimenting with MLAPI features. This project is available in the MLAPI GitHub [testproject folder](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/tree/release/0.1.0/testproject). - Added a [MLAPI Community Contributions](https://github.com/Unity-Technologies/mlapi-community-contributions/tree/master/com.mlapi.contrib.extensions) new GitHub repository to accept extensions from the MLAPI community. Current extensions include moved MLAPI features for lag compensation (useful for Server Authoritative actions) and `TrackedObject`. ### Changed - [GitHub 520](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/520): MLAPI now uses the Unity Package Manager for installation management. -- Added functionality and usability to `NetworkVariable`, previously called `NetworkVar`. Updates enhance options and fully replace the need for `SyncedVar`s. -- [GitHub 507](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/507): Reimplemented `NetworkAnimator`, which synchronizes animation states for networked objects. +- Added functionality and usability to `NetworkVariable`, previously called `NetworkVar`. Updates enhance options and fully replace the need for `SyncedVar`s. +- [GitHub 507](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/507): Reimplemented `NetworkAnimator`, which synchronizes animation states for networked objects. - GitHub [444](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/444) and [455](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/455): Channels are now represented as bytes instead of strings. For users of previous versions of MLAPI, this release renames APIs due to refactoring. All obsolete marked APIs have been removed as per [GitHub 513](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/513) and [GitHub 514](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/514). @@ -432,7 +433,7 @@ For users of previous versions of MLAPI, this release renames APIs due to refact ### Fixed -- [GitHub 460](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/460): Fixed an issue for RPC where the host-server was not receiving RPCs from the host-client and vice versa without the loopback flag set in `NetworkingManager`. +- [GitHub 460](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/460): Fixed an issue for RPC where the host-server was not receiving RPCs from the host-client and vice versa without the loopback flag set in `NetworkingManager`. - Fixed an issue where data in the Profiler was incorrectly aggregated and drawn, which caused the profiler data to increment indefinitely instead of resetting each frame. - Fixed an issue the client soft-synced causing PlayMode client-only scene transition issues, caused when running the client in the editor and the host as a release build. Users may have encountered a soft sync of `NetworkedInstanceId` issues in the `SpawnManager.ClientCollectSoftSyncSceneObjectSweep` method. - [GitHub 458](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/458): Fixed serialization issues in `NetworkList` and `NetworkDictionary` when running in Server mode. @@ -447,10 +448,10 @@ With a new release of MLAPI in Unity, some features have been removed: - SyncVars have been removed from MLAPI. Use `NetworkVariable`s in place of this functionality. - [GitHub 527](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/527): Lag compensation systems and `TrackedObject` have moved to the new [MLAPI Community Contributions](https://github.com/Unity-Technologies/mlapi-community-contributions/tree/master/com.mlapi.contrib.extensions) repo. - [GitHub 509](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/509): Encryption has been removed from MLAPI. The `Encryption` option in `NetworkConfig` on the `NetworkingManager` is not available in this release. This change will not block game creation or running. A current replacement for this functionality is not available, and may be developed in future releases. See the following changes: - - Removed `SecuritySendFlags` from all APIs. - - Removed encryption, cryptography, and certificate configurations from APIs including `NetworkManager` and `NetworkConfig`. - - Removed "hail handshake", including `NetworkManager` implementation and `NetworkConstants` entries. - - Modified `RpcQueue` and `RpcBatcher` internals to remove encryption and authentication from reading and writing. + - Removed `SecuritySendFlags` from all APIs. + - Removed encryption, cryptography, and certificate configurations from APIs including `NetworkManager` and `NetworkConfig`. + - Removed "hail handshake", including `NetworkManager` implementation and `NetworkConstants` entries. + - Modified `RpcQueue` and `RpcBatcher` internals to remove encryption and authentication from reading and writing. - Removed the previous MLAPI Profiler editor window from Unity versions 2020.2 and later. - Removed previous MLAPI Convenience and Performance RPC APIs with the new standard RPC API. See [RFC #1](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/blob/master/text/0001-std-rpc-api.md) for details. - [GitHub 520](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/520): Removed the MLAPI Installer. @@ -463,7 +464,7 @@ With a new release of MLAPI in Unity, some features have been removed: - For `NetworkVariable`, the `NetworkDictionary` `List` and `Set` must use the `reliableSequenced` channel. - `NetworkObjects`s are supported but when spawning a prefab with nested child network objects you have to manually call spawn on them - `NetworkTransform` have the following issues: - - Replicated objects may have jitter. + - Replicated objects may have jitter. - The owner is always authoritative about the object's position. - Scale is not synchronized. - Connection Approval is not called on the host client. diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index f55972bb57..945a8f834f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -440,14 +440,28 @@ internal void InternalOnNetworkSpawn() internal void VisibleOnNetworkSpawn() { - OnNetworkSpawn(); + try + { + OnNetworkSpawn(); + } + catch (Exception e) + { + Debug.LogException(e); + } } internal void InternalOnNetworkDespawn() { IsSpawned = false; UpdateNetworkProperties(); - OnNetworkDespawn(); + try + { + OnNetworkDespawn(); + } + catch (Exception e) + { + Debug.LogException(e); + } } /// diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/MessageHooks.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/MessageHooks.cs index dceceb3fe0..1f56850558 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/MessageHooks.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/MessageHooks.cs @@ -4,10 +4,12 @@ namespace Unity.Netcode.TestHelpers.Runtime { internal class MessageHooks : INetworkHooks { - public bool IsWaiting; + public bool IsWaiting = true; public delegate bool MessageReceiptCheck(object receivedMessage); public MessageReceiptCheck ReceiptCheck; + public static bool CurrentMessageHasTriggerdAHook = false; + public static bool CheckForMessageOfType(object receivedMessage) where T : INetworkMessage { return receivedMessage is T; @@ -57,13 +59,23 @@ public bool OnVerifyCanReceive(ulong senderId, Type messageType, FastBufferReade public void OnBeforeHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage { + // The way the system works, it goes through all hooks and calls OnBeforeHandleMessage, then handles the message, + // then goes thorugh all hooks and calls OnAfterHandleMessage. + // This ensures each message only manages to activate a single message hook - because we know that only + // one message will ever be handled between OnBeforeHandleMessage and OnAfterHandleMessage, + // we can reset the flag here, and then in OnAfterHandleMessage, the moment the message matches a hook, + // it'll flip this flag back on, and then other hooks will stop checking that message. + // Without this flag, waiting for 10 messages of the same type isn't possible - all 10 hooks would get + // tripped by the first message. + CurrentMessageHasTriggerdAHook = false; } public void OnAfterHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage { - if (IsWaiting && (ReceiptCheck == null || ReceiptCheck.Invoke(message))) + if (!CurrentMessageHasTriggerdAHook && IsWaiting && (ReceiptCheck == null || ReceiptCheck.Invoke(message))) { IsWaiting = false; + CurrentMessageHasTriggerdAHook = true; } } } diff --git a/testproject/Assets/Tests/Runtime/OnNetworkSpawnExceptionTests.cs b/testproject/Assets/Tests/Runtime/OnNetworkSpawnExceptionTests.cs new file mode 100644 index 0000000000..db2886ac0d --- /dev/null +++ b/testproject/Assets/Tests/Runtime/OnNetworkSpawnExceptionTests.cs @@ -0,0 +1,297 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using Unity.Netcode; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.Assertions; +using UnityEngine.TestTools; +using Random = UnityEngine.Random; + +namespace TestProject.RuntimeTests +{ + + public class OnNetworkSpawnNoExceptionComponent : NetworkBehaviour + { + public static int NumClientSpawns = 0; + public static int NumServerSpawns = 0; + public override void OnNetworkSpawn() + { + if (IsClient) + { + ++NumClientSpawns; + } + else + { + ++NumServerSpawns; + } + } + } + + public class OnNetworkSpawnThrowsExceptionComponent : NetworkBehaviour + { + public static int NumClientSpawns = 0; + public static int NumServerSpawns = 0; + public override void OnNetworkSpawn() + { + if (IsClient) + { + ++NumClientSpawns; + } + else + { + ++NumServerSpawns; + } + throw new Exception("Exception thrown in OnNetworkSpawn"); + } + } + + public class OnNetworkDespawnNoExceptionComponent : NetworkBehaviour + { + public static int NumClientDespawns = 0; + public static int NumServerDespawns = 0; + public override void OnNetworkDespawn() + { + if (IsClient) + { + ++NumClientDespawns; + } + else + { + ++NumServerDespawns; + } + } + } + + public class OnNetworkDespawnThrowsExceptionComponent : NetworkBehaviour + { + public static int NumClientDespawns = 0; + public static int NumServerDespawns = 0; + public override void OnNetworkDespawn() + { + if (IsClient) + { + ++NumClientDespawns; + } + else + { + ++NumServerDespawns; + } + throw new Exception("Exception thrown in OnNetworkDespawn"); + } + } + + public class OnNetworkSpawnExceptionTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 1; + + private GameObject m_SpawnExceptionPrefab; + private GameObject m_NoSpawnExceptionPrefab; + private GameObject m_SpawnWithAndWithoutExceptionPrefab; + private GameObject m_DespawnExceptionPrefab; + private GameObject m_NoDespawnExceptionPrefab; + private GameObject m_DespawnWithAndWithoutExceptionPrefab; + + private const int k_NumObjects = 10; + [UnityTest] + public IEnumerator WhenOnNetworkSpawnThrowsException_FutureOnNetworkSpawnsAreNotPrevented() + { + int numExceptionsExpected = 0; + int numExceptionFreeSpawnsExpected = 0; + + var messageHookEntriesForSpawn = new List(); + + for (var i = 0; i < k_NumObjects; ++i) + { + GameObject instance; + // Randomly create some different objects, but the first will always be with exception, + // the second will always be with both an exception behaviour and a non-exception behaviour, + // and the last will always be without exception, with random choices in between. + // Random.Range with int values 0 and 3 will always return 0, 1, or 2. + var rand = Random.Range(0, 3); + if (i == 0 || (i != 1 && i != k_NumObjects - 1 && rand == 0)) + { + instance = UnityEngine.Object.Instantiate(m_SpawnExceptionPrefab); + ++numExceptionsExpected; + // One for server, one for client. + LogAssert.Expect(LogType.Exception, new Regex("Exception thrown in OnNetworkSpawn")); + LogAssert.Expect(LogType.Exception, new Regex("Exception thrown in OnNetworkSpawn")); + } + else if (i == 1 || (i != k_NumObjects - 1 && rand == 1)) + { + instance = UnityEngine.Object.Instantiate(m_SpawnWithAndWithoutExceptionPrefab); + ++numExceptionsExpected; + // One for server, one for client. + LogAssert.Expect(LogType.Exception, new Regex("Exception thrown in OnNetworkSpawn")); + LogAssert.Expect(LogType.Exception, new Regex("Exception thrown in OnNetworkSpawn")); + ++numExceptionFreeSpawnsExpected; + } + else + { + instance = UnityEngine.Object.Instantiate(m_NoSpawnExceptionPrefab); + ++numExceptionFreeSpawnsExpected; + } + var networkObject = instance.GetComponent(); + networkObject.NetworkManagerOwner = m_ServerNetworkManager; + networkObject.Spawn(); + + var messageHook = new MessageHookEntry(m_ClientNetworkManagers[0]); + messageHook.AssignMessageType(); + messageHookEntriesForSpawn.Add(messageHook); + } + + var condition = new MessageHooksConditional(messageHookEntriesForSpawn); + + yield return WaitForConditionOrTimeOut(condition); + + // Assert that all objects had their OnNetworkSpawn called whether they threw exceptions or not + Assert.AreEqual(numExceptionsExpected, OnNetworkSpawnThrowsExceptionComponent.NumClientSpawns); + Assert.AreEqual(numExceptionsExpected, OnNetworkSpawnThrowsExceptionComponent.NumServerSpawns); + Assert.AreEqual(numExceptionFreeSpawnsExpected, OnNetworkSpawnNoExceptionComponent.NumClientSpawns); + Assert.AreEqual(numExceptionFreeSpawnsExpected, OnNetworkSpawnNoExceptionComponent.NumServerSpawns); + } + + [UnityTest] + public IEnumerator WhenOnNetworkDespawnThrowsException_FutureOnNetworkDespawnsAreNotPrevented() + { + int numExceptionsExpected = 0; + int numExceptionFreeDespawnsExpected = 0; + + var messageHookEntriesForSpawn = new List(); + + var allObjects = new List(); + + for (var i = 0; i < k_NumObjects; ++i) + { + GameObject instance; + // Randomly create some different objects, but the first will always be with exception, + // the second will always be with both an exception behaviour and a non-exception behaviour, + // and the last will always be without exception, with random choices in between. + // Random.Range with int values 0 and 3 will always return 0, 1, or 2. + var rand = Random.Range(0, 3); + if (i == 0 || (i != 1 && i != k_NumObjects - 1 && rand == 0)) + { + instance = UnityEngine.Object.Instantiate(m_DespawnExceptionPrefab); + ++numExceptionsExpected; + } + else if (i == 1 || (i != k_NumObjects - 1 && rand == 1)) + { + instance = UnityEngine.Object.Instantiate(m_DespawnWithAndWithoutExceptionPrefab); + ++numExceptionsExpected; + ++numExceptionFreeDespawnsExpected; + } + else + { + instance = UnityEngine.Object.Instantiate(m_NoDespawnExceptionPrefab); + ++numExceptionFreeDespawnsExpected; + } + var networkObject = instance.GetComponent(); + networkObject.NetworkManagerOwner = m_ServerNetworkManager; + networkObject.Spawn(); + + allObjects.Add(networkObject); + + var messageHook = new MessageHookEntry(m_ClientNetworkManagers[0]); + messageHook.AssignMessageType(); + messageHookEntriesForSpawn.Add(messageHook); + } + + var spawnCondition = new MessageHooksConditional(messageHookEntriesForSpawn); + + yield return WaitForConditionOrTimeOut(spawnCondition); + + // Make sure no exceptions were thrown in the spawn + LogAssert.NoUnexpectedReceived(); + + var messageHookEntriesForDespawn = new List(); + for (var i = 0; i < numExceptionsExpected; ++i) + { + // One for server, one for client + LogAssert.Expect(LogType.Exception, new Regex("Exception thrown in OnNetworkDespawn")); + LogAssert.Expect(LogType.Exception, new Regex("Exception thrown in OnNetworkDespawn")); + } + foreach (var networkObject in allObjects) + { + networkObject.Despawn(); + var messageHook = new MessageHookEntry(m_ClientNetworkManagers[0]); + messageHook.AssignMessageType(); + messageHookEntriesForDespawn.Add(messageHook); + } + var despawnCondition = new MessageHooksConditional(messageHookEntriesForDespawn); + + yield return WaitForConditionOrTimeOut(despawnCondition); + + // Assert that all objects had their OnNetworkSpawn called whether they threw exceptions or not + Assert.AreEqual(numExceptionsExpected, OnNetworkDespawnThrowsExceptionComponent.NumClientDespawns); + Assert.AreEqual(numExceptionsExpected, OnNetworkDespawnThrowsExceptionComponent.NumServerDespawns); + Assert.AreEqual(numExceptionFreeDespawnsExpected, OnNetworkDespawnNoExceptionComponent.NumClientDespawns); + Assert.AreEqual(numExceptionFreeDespawnsExpected, OnNetworkDespawnNoExceptionComponent.NumServerDespawns); + } + + protected override IEnumerator OnSetup() + { + m_UseHost = false; + OnNetworkSpawnThrowsExceptionComponent.NumClientSpawns = 0; + OnNetworkSpawnThrowsExceptionComponent.NumServerSpawns = 0; + OnNetworkSpawnNoExceptionComponent.NumClientSpawns = 0; + OnNetworkSpawnNoExceptionComponent.NumServerSpawns = 0; + OnNetworkDespawnThrowsExceptionComponent.NumClientDespawns = 0; + OnNetworkDespawnThrowsExceptionComponent.NumServerDespawns = 0; + OnNetworkDespawnNoExceptionComponent.NumClientDespawns = 0; + OnNetworkDespawnNoExceptionComponent.NumServerDespawns = 0; + yield return null; + } + + protected override void OnServerAndClientsCreated() + { + m_SpawnExceptionPrefab = new GameObject("Spawn Exception Object"); + m_NoSpawnExceptionPrefab = new GameObject("Spawn Normal Object"); + m_SpawnWithAndWithoutExceptionPrefab = new GameObject("Spawn Hybrid Object"); + m_DespawnExceptionPrefab = new GameObject("Despawn Exception Object"); + m_NoDespawnExceptionPrefab = new GameObject("Despawn Normal Object"); + m_DespawnWithAndWithoutExceptionPrefab = new GameObject("Despawn Hybrid Object"); + + m_SpawnExceptionPrefab.AddComponent(); + m_NoSpawnExceptionPrefab.AddComponent(); + + // Note: Unity does not actually guarantee that GetComponenetsInChildren() will return the components in the + // same order they are added, however in practice it *seems* to do so. This is an attempt to test that + // an exception thrown in one NetworkBehaviour won't prevent another NetworkBehaviour on the same object + // from executing its events... but this only works because that behaviour in Unity happens to work that way. + // If a future version of Unity changes that, then this test won't actually test that, but there doesn't + // seem to be any way to actually make this reliable, so this test is just doing the best it can with + // current Unity behavior to test this. + m_SpawnWithAndWithoutExceptionPrefab.AddComponent(); + m_SpawnWithAndWithoutExceptionPrefab.AddComponent(); + + m_DespawnExceptionPrefab.AddComponent(); + m_NoDespawnExceptionPrefab.AddComponent(); + m_DespawnWithAndWithoutExceptionPrefab.AddComponent(); + m_DespawnWithAndWithoutExceptionPrefab.AddComponent(); + + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(m_SpawnExceptionPrefab.AddComponent()); + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(m_NoSpawnExceptionPrefab.AddComponent()); + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(m_SpawnWithAndWithoutExceptionPrefab.AddComponent()); + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(m_DespawnExceptionPrefab.AddComponent()); + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(m_NoDespawnExceptionPrefab.AddComponent()); + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(m_DespawnWithAndWithoutExceptionPrefab.AddComponent()); + + m_ServerNetworkManager.AddNetworkPrefab(m_SpawnExceptionPrefab); + m_ServerNetworkManager.AddNetworkPrefab(m_NoSpawnExceptionPrefab); + m_ServerNetworkManager.AddNetworkPrefab(m_SpawnWithAndWithoutExceptionPrefab); + m_ServerNetworkManager.AddNetworkPrefab(m_DespawnExceptionPrefab); + m_ServerNetworkManager.AddNetworkPrefab(m_NoDespawnExceptionPrefab); + m_ServerNetworkManager.AddNetworkPrefab(m_DespawnWithAndWithoutExceptionPrefab); + foreach (var client in m_ClientNetworkManagers) + { + client.AddNetworkPrefab(m_SpawnExceptionPrefab); + client.AddNetworkPrefab(m_NoSpawnExceptionPrefab); + client.AddNetworkPrefab(m_SpawnWithAndWithoutExceptionPrefab); + client.AddNetworkPrefab(m_DespawnExceptionPrefab); + client.AddNetworkPrefab(m_NoDespawnExceptionPrefab); + client.AddNetworkPrefab(m_DespawnWithAndWithoutExceptionPrefab); + } + } + } +} diff --git a/testproject/Assets/Tests/Runtime/OnNetworkSpawnExceptionTests.cs.meta b/testproject/Assets/Tests/Runtime/OnNetworkSpawnExceptionTests.cs.meta new file mode 100644 index 0000000000..bdab26680d --- /dev/null +++ b/testproject/Assets/Tests/Runtime/OnNetworkSpawnExceptionTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4aed67943ab04ae68d0c137e564e1e0e +timeCreated: 1644863541 \ No newline at end of file From 9f14b357b7b9963570f6cbaf47e747820cdbee05 Mon Sep 17 00:00:00 2001 From: Fatih Mar Date: Thu, 25 Aug 2022 19:33:23 +0100 Subject: [PATCH 040/138] fix: update "Hello World" docs link --- README.md | 2 +- com.unity.netcode.gameobjects/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7548226a47..c7a2bee40f 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Netcode for GameObjects is a Unity package that provides networking capabilities Visit the [Multiplayer Docs Site](https://docs-multiplayer.unity3d.com/) for package & API documentation, as well as information about several samples which leverage the Netcode for GameObjects package. -You can also jump right into our [Hello World](https://docs-multiplayer.unity3d.com/netcode/current/tutorials/helloworld/helloworldintro) guide for a taste of how to use the framework for basic networked tasks. +You can also jump right into our [Hello World](https://docs-multiplayer.unity3d.com/netcode/current/tutorials/helloworld) guide for a taste of how to use the framework for basic networked tasks. ### Community and Feedback diff --git a/com.unity.netcode.gameobjects/README.md b/com.unity.netcode.gameobjects/README.md index a264b60ce8..5aac046b3d 100644 --- a/com.unity.netcode.gameobjects/README.md +++ b/com.unity.netcode.gameobjects/README.md @@ -9,7 +9,7 @@ Netcode for GameObjects is a Unity package that provides networking capabilities Visit the [Multiplayer Docs Site](https://docs-multiplayer.unity3d.com/) for package & API documentation, as well as information about several samples which leverage the Netcode for GameObjects package. -You can also jump right into our [Hello World](https://docs-multiplayer.unity3d.com/netcode/current/tutorials/helloworld/helloworldintro) guide for a taste of how to use the framework for basic networked tasks. +You can also jump right into our [Hello World](https://docs-multiplayer.unity3d.com/netcode/current/tutorials/helloworld) guide for a taste of how to use the framework for basic networked tasks. ### Community and Feedback From 1b1d8c043a4d4cd937a21b64c063ef4f3e180dc5 Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Thu, 25 Aug 2022 22:27:59 -0400 Subject: [PATCH 041/138] fix: #2147. Removing extra warning on despawning NetworkObjects with dirty NetworkVariables (#2151) --- .../Runtime/Core/NetworkObject.cs | 1 + .../NetworkVariable/NetworkVariableBase.cs | 13 ++++---- .../Tests/Runtime/NetworkShowHideTests.cs | 30 ++++++++++++++++++- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index ce3b200d94..b08900a080 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -509,6 +509,7 @@ public void SpawnAsPlayerObject(ulong clientId, bool destroyWithScene = false) /// (true) the will be destroyed (false) the will persist after being despawned public void Despawn(bool destroy = true) { + MarkVariablesDirty(false); NetworkManager.SpawnManager.DespawnObject(this, destroy); } diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs index 96635f33b3..d3e4c0e512 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -81,15 +81,14 @@ public virtual void SetDirty(bool isDirty) { m_IsDirty = isDirty; - if (m_NetworkBehaviour == null) - { - Debug.LogWarning($"NetworkVariable is written to, but doesn't know its NetworkBehaviour yet. " + - "Are you modifying a NetworkVariable before the NetworkObject is spawned?"); - return; - } - if (m_IsDirty) { + if (m_NetworkBehaviour == null) + { + Debug.LogWarning($"NetworkVariable is written to, but doesn't know its NetworkBehaviour yet. " + + "Are you modifying a NetworkVariable before the NetworkObject is spawned?"); + return; + } m_NetworkBehaviour.NetworkManager.MarkNetworkObjectDirty(m_NetworkBehaviour.NetworkObject); } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs index bd95fa4f41..35672294e7 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs @@ -17,6 +17,7 @@ public class ShowHideObject : NetworkBehaviour { public static List ClientTargetedNetworkObjects = new List(); public static ulong ClientIdToTarget; + public static bool Silent; public static NetworkObject GetNetworkObjectById(ulong networkObjectId) { @@ -58,7 +59,10 @@ private void Awake() public void Changed(int before, int after) { - Debug.Log($"Value changed from {before} to {after}"); + if (!Silent) + { + Debug.Log($"Value changed from {before} to {after}"); + } } } @@ -264,5 +268,29 @@ public IEnumerator NetworkShowHideQuickTest() yield return CheckVisible(true); } } + + [UnityTest] + public IEnumerator NetworkHideDespawnTest() + { + m_ClientId0 = m_ClientNetworkManagers[0].LocalClientId; + ShowHideObject.ClientTargetedNetworkObjects.Clear(); + ShowHideObject.ClientIdToTarget = m_ClientId0; + ShowHideObject.Silent = true; + + var spawnedObject1 = SpawnObject(m_PrefabToSpawn, m_ServerNetworkManager); + var spawnedObject2 = SpawnObject(m_PrefabToSpawn, m_ServerNetworkManager); + var spawnedObject3 = SpawnObject(m_PrefabToSpawn, m_ServerNetworkManager); + m_NetSpawnedObject1 = spawnedObject1.GetComponent(); + m_NetSpawnedObject2 = spawnedObject2.GetComponent(); + m_NetSpawnedObject3 = spawnedObject3.GetComponent(); + + m_NetSpawnedObject1.GetComponent().MyNetworkVariable.Value++; + m_NetSpawnedObject1.NetworkHide(m_ClientId0); + m_NetSpawnedObject1.Despawn(); + + yield return NetcodeIntegrationTestHelpers.WaitForTicks(m_ServerNetworkManager, 5); + + LogAssert.NoUnexpectedReceived(); + } } } From de3fc7c6c018ef9a1cc41fed1586788ca61c6599 Mon Sep 17 00:00:00 2001 From: Kitty Draper <284434+ShadauxCat@users.noreply.github.com> Date: Fri, 26 Aug 2022 12:16:50 -0500 Subject: [PATCH 042/138] fix: Client RPCs always reporting all RPCs as going to all clients, even when limited by ClientRpcParams [MTT-4468] (#2144) * fix: Client RPCs always reporting all RPCs as going to all clients, even when limited by ClientRpcParams * Changelog --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Core/NetworkBehaviour.cs | 42 +++++++++-- .../Runtime/Metrics/RpcTestComponent.cs | 2 +- .../Tests/Runtime/Metrics/RpcMetricsTests.cs | 72 ++++++++++++++++--- 4 files changed, 98 insertions(+), 19 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 688d039d93..bccad9721e 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -11,6 +11,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed ClientRpcs always reporting in the profiler view as going to all clients, even when limited to a subset of clients by ClientRpcParams. (#2144) - Fixed RPC codegen failing to choose the correct extension methods for FastBufferReader and FastBufferWriter when the parameters were a generic type (i.e., List) and extensions for multiple instantiations of that type have been defined (i.e., List and List) (#2142) - Fixed throwing an exception in OnNetworkUpdate causing other OnNetworkUpdate calls to not be executed. (#1739) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 945a8f834f..afd004875f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -235,14 +235,42 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth #if DEVELOPMENT_BUILD || UNITY_EDITOR if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName)) { - foreach (var client in NetworkManager.ConnectedClients) + if (clientRpcParams.Send.TargetClientIds != null) { - NetworkManager.NetworkMetrics.TrackRpcSent( - client.Key, - NetworkObject, - rpcMethodName, - __getTypeName(), - rpcWriteSize); + foreach (var targetClientId in clientRpcParams.Send.TargetClientIds) + { + NetworkManager.NetworkMetrics.TrackRpcSent( + targetClientId, + NetworkObject, + rpcMethodName, + __getTypeName(), + rpcWriteSize); + } + } + else if (clientRpcParams.Send.TargetClientIdsNativeArray != null) + { + foreach (var targetClientId in clientRpcParams.Send.TargetClientIdsNativeArray) + { + NetworkManager.NetworkMetrics.TrackRpcSent( + targetClientId, + NetworkObject, + rpcMethodName, + __getTypeName(), + rpcWriteSize); + } + } + else + { + var observerEnumerator = NetworkObject.Observers.GetEnumerator(); + while (observerEnumerator.MoveNext()) + { + NetworkManager.NetworkMetrics.TrackRpcSent( + observerEnumerator.Current, + NetworkObject, + rpcMethodName, + __getTypeName(), + rpcWriteSize); + } } } #endif diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/Metrics/RpcTestComponent.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/Metrics/RpcTestComponent.cs index 354a801126..158f5d3c42 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/Metrics/RpcTestComponent.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/Metrics/RpcTestComponent.cs @@ -14,7 +14,7 @@ public void MyServerRpc() } [ClientRpc] - public void MyClientRpc() + public void MyClientRpc(ClientRpcParams rpcParams = default) { OnClientRpcAction?.Invoke(); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/RpcMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/RpcMetricsTests.cs index 0d802ce156..be68374c1a 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/RpcMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/RpcMetricsTests.cs @@ -2,13 +2,14 @@ using System.Collections; using System.Linq; using NUnit.Framework; +using Unity.Collections; using Unity.Multiplayer.Tools.MetricTypes; using UnityEngine.TestTools; using Unity.Netcode.TestHelpers.Runtime.Metrics; namespace Unity.Netcode.RuntimeTests.Metrics { - internal class RpcMetricsTests : SingleClientMetricTestBase + internal class RpcMetricsTests : DualClientMetricTestBase { protected override void OnCreatePlayerPrefab() { @@ -17,30 +18,79 @@ protected override void OnCreatePlayerPrefab() } [UnityTest] - public IEnumerator TrackRpcSentMetricOnServer() + public IEnumerator TrackRpcSentMetricOnServerToOnlyOneClientWithArray() { var waitForMetricValues = new WaitForEventMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.RpcSent); - m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][Client.LocalClientId].GetComponent().MyClientRpc(); + m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][FirstClient.LocalClientId].GetComponent().MyClientRpc(new ClientRpcParams + { + Send = new ClientRpcSendParams + { + TargetClientIds = new []{FirstClient.LocalClientId} + } + }); yield return waitForMetricValues.WaitForMetricsReceived(); var serverRpcSentValues = waitForMetricValues.AssertMetricValuesHaveBeenFound(); - Assert.AreEqual(2, serverRpcSentValues.Count); // Server will receive this, since it's host + Assert.AreEqual(1, serverRpcSentValues.Count); + + Assert.That(serverRpcSentValues, Has.All.Matches(x => x.Name == nameof(RpcTestComponent.MyClientRpc))); + Assert.That(serverRpcSentValues, Has.All.Matches(x => x.NetworkBehaviourName == nameof(RpcTestComponent))); + Assert.That(serverRpcSentValues, Has.All.Matches(x => x.BytesCount != 0)); + Assert.AreEqual(FirstClient.LocalClientId, serverRpcSentValues.First().Connection.Id); + } + + [UnityTest] + public IEnumerator TrackRpcSentMetricOnServerToOnlyOneClientWithNativeArray() + { + var waitForMetricValues = new WaitForEventMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.RpcSent); + + m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][FirstClient.LocalClientId].GetComponent().MyClientRpc(new ClientRpcParams + { + Send = new ClientRpcSendParams + { + TargetClientIdsNativeArray = new NativeArray(new []{FirstClient.LocalClientId}, Allocator.Temp) + } + }); + + yield return waitForMetricValues.WaitForMetricsReceived(); + + var serverRpcSentValues = waitForMetricValues.AssertMetricValuesHaveBeenFound(); + Assert.AreEqual(1, serverRpcSentValues.Count); + + Assert.That(serverRpcSentValues, Has.All.Matches(x => x.Name == nameof(RpcTestComponent.MyClientRpc))); + Assert.That(serverRpcSentValues, Has.All.Matches(x => x.NetworkBehaviourName == nameof(RpcTestComponent))); + Assert.That(serverRpcSentValues, Has.All.Matches(x => x.BytesCount != 0)); + Assert.AreEqual(FirstClient.LocalClientId, serverRpcSentValues.First().Connection.Id); + } + + [UnityTest] + public IEnumerator TrackRpcSentMetricOnServerToAllClients() + { + var waitForMetricValues = new WaitForEventMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.RpcSent); + + m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][FirstClient.LocalClientId].GetComponent().MyClientRpc(); + + yield return waitForMetricValues.WaitForMetricsReceived(); + + var serverRpcSentValues = waitForMetricValues.AssertMetricValuesHaveBeenFound(); + Assert.AreEqual(3, serverRpcSentValues.Count); // Server will receive this, since it's host Assert.That(serverRpcSentValues, Has.All.Matches(x => x.Name == nameof(RpcTestComponent.MyClientRpc))); Assert.That(serverRpcSentValues, Has.All.Matches(x => x.NetworkBehaviourName == nameof(RpcTestComponent))); Assert.That(serverRpcSentValues, Has.All.Matches(x => x.BytesCount != 0)); Assert.Contains(Server.LocalClientId, serverRpcSentValues.Select(x => x.Connection.Id).ToArray()); - Assert.Contains(Client.LocalClientId, serverRpcSentValues.Select(x => x.Connection.Id).ToArray()); + Assert.Contains(FirstClient.LocalClientId, serverRpcSentValues.Select(x => x.Connection.Id).ToArray()); + Assert.Contains(SecondClient.LocalClientId, serverRpcSentValues.Select(x => x.Connection.Id).ToArray()); } [UnityTest] public IEnumerator TrackRpcSentMetricOnClient() { - var waitForClientMetricsValues = new WaitForEventMetricValues(ClientMetrics.Dispatcher, NetworkMetricTypes.RpcSent); + var waitForClientMetricsValues = new WaitForEventMetricValues(FirstClientMetrics.Dispatcher, NetworkMetricTypes.RpcSent); - m_PlayerNetworkObjects[Client.LocalClientId][Client.LocalClientId].GetComponent().MyServerRpc(); + m_PlayerNetworkObjects[FirstClient.LocalClientId][FirstClient.LocalClientId].GetComponent().MyServerRpc(); yield return waitForClientMetricsValues.WaitForMetricsReceived(); @@ -58,7 +108,7 @@ public IEnumerator TrackRpcSentMetricOnClient() public IEnumerator TrackRpcReceivedMetricOnServer() { var waitForServerMetricsValues = new WaitForEventMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.RpcReceived); - m_PlayerNetworkObjects[Client.LocalClientId][Client.LocalClientId].GetComponent().MyServerRpc(); + m_PlayerNetworkObjects[FirstClient.LocalClientId][FirstClient.LocalClientId].GetComponent().MyServerRpc(); yield return waitForServerMetricsValues.WaitForMetricsReceived(); @@ -66,7 +116,7 @@ public IEnumerator TrackRpcReceivedMetricOnServer() Assert.AreEqual(1, serverRpcReceivedValues.Count); var rpcReceived = serverRpcReceivedValues.First(); - Assert.AreEqual(Client.LocalClientId, rpcReceived.Connection.Id); + Assert.AreEqual(FirstClient.LocalClientId, rpcReceived.Connection.Id); Assert.AreEqual(nameof(RpcTestComponent.MyServerRpc), rpcReceived.Name); Assert.AreEqual(nameof(RpcTestComponent), rpcReceived.NetworkBehaviourName); Assert.AreNotEqual(0, rpcReceived.BytesCount); @@ -75,9 +125,9 @@ public IEnumerator TrackRpcReceivedMetricOnServer() [UnityTest] public IEnumerator TrackRpcReceivedMetricOnClient() { - var waitForClientMetricsValues = new WaitForEventMetricValues(ClientMetrics.Dispatcher, NetworkMetricTypes.RpcReceived); + var waitForClientMetricsValues = new WaitForEventMetricValues(FirstClientMetrics.Dispatcher, NetworkMetricTypes.RpcReceived); - m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][Client.LocalClientId].GetComponent().MyClientRpc(); + m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][FirstClient.LocalClientId].GetComponent().MyClientRpc(); yield return waitForClientMetricsValues.WaitForMetricsReceived(); From f1d9880bdc9665585c978daf45b36b5e2d7a99e9 Mon Sep 17 00:00:00 2001 From: ashwini <36935028+ashwinimurt@users.noreply.github.com> Date: Mon, 29 Aug 2022 09:21:36 -0700 Subject: [PATCH 043/138] fix: Delete stray NetworkAnimator.meta file [MTT-4024] (#2153) * fix:Delete stray NetworkAnimator.meta file * Add changelog Co-authored-by: Jesse Olmer --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Tests/Runtime/NetworkAnimator.meta | 8 -------- 2 files changed, 1 insertion(+), 8 deletions(-) delete mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index bccad9721e..afffda00bc 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -14,6 +14,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed ClientRpcs always reporting in the profiler view as going to all clients, even when limited to a subset of clients by ClientRpcParams. (#2144) - Fixed RPC codegen failing to choose the correct extension methods for FastBufferReader and FastBufferWriter when the parameters were a generic type (i.e., List) and extensions for multiple instantiations of that type have been defined (i.e., List and List) (#2142) - Fixed throwing an exception in OnNetworkUpdate causing other OnNetworkUpdate calls to not be executed. (#1739) +- Fixed warning resulting from a stray NetworkAnimator.meta file (#2153) ## [1.0.1] - 2022-08-23 diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator.meta deleted file mode 100644 index c08562e8db..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkAnimator.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 3acde7838205d4b09ae3a035554c51c5 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: From d86657832f5bf9f3fdea1e8035eff5700959fb0d Mon Sep 17 00:00:00 2001 From: Jesse Olmer Date: Tue, 30 Aug 2022 04:50:04 -0700 Subject: [PATCH 044/138] perf: Improve testproject DrawRay.cs performance (#2166) Achieves a more GC friendly DrawRay debug code through caching the gameobject transform, reducing the engine calls to getTransform Co-authored-by: Gustavo Santos --- testproject/Assets/Scripts/DrawRay.cs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/testproject/Assets/Scripts/DrawRay.cs b/testproject/Assets/Scripts/DrawRay.cs index 6f546d4160..ff479472d2 100644 --- a/testproject/Assets/Scripts/DrawRay.cs +++ b/testproject/Assets/Scripts/DrawRay.cs @@ -3,23 +3,26 @@ [RequireComponent(typeof(LineRenderer))] public class DrawRay : MonoBehaviour { + private const float k_RayLength = 10; + + private Transform m_Transform; private LineRenderer m_LineRenderer; private void Awake() { - m_LineRenderer = GetComponent(); + TryGetComponent(out m_Transform); + TryGetComponent(out m_LineRenderer); m_LineRenderer.SetPosition(0, transform.position); } private void FixedUpdate() { - if (Physics.Raycast(new Ray(transform.position, transform.forward * 10), out RaycastHit hit, 10, Physics.DefaultRaycastLayers)) - { - m_LineRenderer.SetPosition(1, hit.point); - } - else - { - m_LineRenderer.SetPosition(1, transform.position + transform.forward * 10); - } + var ray = new Ray(m_Transform.position, m_Transform.forward * k_RayLength); + + var point = Physics.Raycast(ray, out var hit, k_RayLength, Physics.DefaultRaycastLayers) + ? hit.point + : m_Transform.position + m_Transform.forward * k_RayLength; + + m_LineRenderer.SetPosition(1, point); } } From 646ec35c7821e35a7fb523521381091040d9d319 Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Tue, 30 Aug 2022 11:39:02 -0400 Subject: [PATCH 045/138] fix: preventing exception from NetworkList, when modified before spawn. (#2169) --- .../Runtime/NetworkVariable/Collections/NetworkList.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index 8f69b770f7..81f2f7f30e 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Unity.Collections; +using UnityEngine; namespace Unity.Netcode { @@ -65,6 +66,13 @@ public override bool IsDirty() internal void MarkNetworkObjectDirty() { + if (m_NetworkBehaviour == null) + { + Debug.LogWarning($"NetworkList is written to, but doesn't know its NetworkBehaviour yet. " + + "Are you modifying a NetworkList before the NetworkObject is spawned?"); + return; + } + m_NetworkBehaviour.NetworkManager.MarkNetworkObjectDirty(m_NetworkBehaviour.NetworkObject); } From fb94ca855d5c9156e0f2ed096e2ca762be4fef06 Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Wed, 31 Aug 2022 15:21:39 -0400 Subject: [PATCH 046/138] chore: UTP Transport 2.0 support [MTT-4473] (#2162) * chore: adding preprocessor define in Netcode for GameObjects, when UTP Transport is 2.0 or above * chore: adding preprocessor define in Netcode for GameObjects, when UTP Transport is 2.0 or above * chore: adding preprocessor define in Netcode for GameObjects tests, when UTP Transport is 2.0 or above * chore: conditional use of UTP 2.0 * style: coding standards * style: coding standards * Adding some fixed from PR review comment * fix: fixing tools package compilation, when used in netcode for gameobjects, with UTP 2.0 * fix: fixing tools package compilation, when used in netcode for gameobjects, with UTP 2.0 * fix: build break for UTP 2.0 builds without tools installed * fix: fixing build errors with Collection 2.1.0-exp3 --- .../Collections/NetworkList.cs | 2 +- .../Transports/UTP/BatchedReceiveQueue.cs | 12 +++ .../Transports/UTP/BatchedSendQueue.cs | 17 ++++ .../Runtime/Transports/UTP/UnityTransport.cs | 80 ++++++++++++++----- .../Runtime/com.unity.netcode.runtime.asmdef | 5 ++ .../Runtime/Metrics/PacketLossMetricsTests.cs | 3 + .../Transports/UnityTransportDriverClient.cs | 4 + .../com.unity.netcode.runtimetests.asmdef | 7 +- 8 files changed, 107 insertions(+), 23 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index 81f2f7f30e..c2ced507dc 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -53,7 +53,7 @@ public override void ResetDirty() if (m_DirtyEvents.Length > 0) { m_DirtyEvents.Clear(); - m_ListAtLastReset.CopyFrom(m_List); + m_ListAtLastReset.CopyFrom(m_List.AsArray()); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedReceiveQueue.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedReceiveQueue.cs index c6a3f55d04..e0cdeebb49 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedReceiveQueue.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedReceiveQueue.cs @@ -1,5 +1,9 @@ using System; using Unity.Networking.Transport; +#if UTP_TRANSPORT_2_0_ABOVE +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +#endif namespace Unity.Netcode.Transports.UTP { @@ -25,7 +29,11 @@ public BatchedReceiveQueue(DataStreamReader reader) { fixed (byte* dataPtr = m_Data) { +#if UTP_TRANSPORT_2_0_ABOVE + reader.ReadBytesUnsafe(dataPtr, reader.Length); +#else reader.ReadBytes(dataPtr, reader.Length); +#endif } } @@ -62,7 +70,11 @@ public void PushReader(DataStreamReader reader) { fixed (byte* dataPtr = m_Data) { +#if UTP_TRANSPORT_2_0_ABOVE + reader.ReadBytesUnsafe(dataPtr + m_Offset + m_Length, reader.Length); +#else reader.ReadBytes(dataPtr + m_Offset + m_Length, reader.Length); +#endif } } diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedSendQueue.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedSendQueue.cs index e217e37c43..e811ff7a3e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedSendQueue.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedSendQueue.cs @@ -79,7 +79,11 @@ private void AppendDataAtTail(ArraySegment data) fixed (byte* dataPtr = data.Array) { +#if UTP_TRANSPORT_2_0_ABOVE + writer.WriteBytesUnsafe(dataPtr + data.Offset, data.Count); +#else writer.WriteBytes(dataPtr + data.Offset, data.Count); +#endif } } @@ -149,7 +153,12 @@ public int FillWriterWithMessages(ref DataStreamWriter writer) unsafe { +#if UTP_TRANSPORT_2_0_ABOVE + var slice = m_Data.GetSubArray(HeadIndex, Length); + var reader = new DataStreamReader(slice); +#else var reader = new DataStreamReader((byte*)m_Data.GetUnsafePtr() + HeadIndex, Length); +#endif var writerAvailable = writer.Capacity; var readerOffset = 0; @@ -168,7 +177,11 @@ public int FillWriterWithMessages(ref DataStreamWriter writer) writer.WriteInt(messageLength); var messageOffset = HeadIndex + reader.GetBytesRead(); +#if UTP_TRANSPORT_2_0_ABOVE + writer.WriteBytesUnsafe((byte*)m_Data.GetUnsafePtr() + messageOffset, messageLength); +#else writer.WriteBytes((byte*)m_Data.GetUnsafePtr() + messageOffset, messageLength); +#endif writerAvailable -= sizeof(int) + messageLength; readerOffset += sizeof(int) + messageLength; @@ -205,7 +218,11 @@ public int FillWriterWithBytes(ref DataStreamWriter writer) unsafe { +#if UTP_TRANSPORT_2_0_ABOVE + writer.WriteBytesUnsafe((byte*)m_Data.GetUnsafePtr() + HeadIndex, copyLength); +#else writer.WriteBytes((byte*)m_Data.GetUnsafePtr() + HeadIndex, copyLength); +#endif } return copyLength; diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index 5a06191c36..1e6302338b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -9,6 +9,10 @@ using Unity.Networking.Transport.Relay; using Unity.Networking.Transport.Utilities; +#if !UTP_TRANSPORT_2_0_ABOVE +using NetworkEndpoint = Unity.Networking.Transport.NetworkEndPoint; +#endif + namespace Unity.Netcode.Transports.UTP { /// @@ -263,9 +267,9 @@ public struct ConnectionAddressData [SerializeField] public string ServerListenAddress; - private static NetworkEndPoint ParseNetworkEndpoint(string ip, ushort port) + private static NetworkEndpoint ParseNetworkEndpoint(string ip, ushort port) { - if (!NetworkEndPoint.TryParse(ip, port, out var endpoint)) + if (!NetworkEndpoint.TryParse(ip, port, out var endpoint)) { Debug.LogError($"Invalid network endpoint: {ip}:{port}."); return default; @@ -277,12 +281,12 @@ private static NetworkEndPoint ParseNetworkEndpoint(string ip, ushort port) /// /// Endpoint (IP address and port) clients will connect to. /// - public NetworkEndPoint ServerEndPoint => ParseNetworkEndpoint(Address, Port); + public NetworkEndpoint ServerEndPoint => ParseNetworkEndpoint(Address, Port); /// /// Endpoint (IP address and port) server will listen/bind on. /// - public NetworkEndPoint ListenEndPoint => ParseNetworkEndpoint((ServerListenAddress == string.Empty) ? Address : ServerListenAddress, Port); + public NetworkEndpoint ListenEndPoint => ParseNetworkEndpoint((ServerListenAddress == string.Empty) ? Address : ServerListenAddress, Port); } /// @@ -425,7 +429,7 @@ private NetworkPipeline SelectSendPipeline(NetworkDelivery delivery) private bool ClientBindAndConnect() { - var serverEndpoint = default(NetworkEndPoint); + var serverEndpoint = default(NetworkEndpoint); if (m_ProtocolType == ProtocolType.RelayUnityTransport) { @@ -446,7 +450,7 @@ private bool ClientBindAndConnect() InitDriver(); - int result = m_Driver.Bind(NetworkEndPoint.AnyIpv4); + int result = m_Driver.Bind(NetworkEndpoint.AnyIpv4); if (result != 0) { Debug.LogError("Client failed to bind"); @@ -459,7 +463,7 @@ private bool ClientBindAndConnect() return true; } - private bool ServerBindAndListen(NetworkEndPoint endPoint) + private bool ServerBindAndListen(NetworkEndpoint endPoint) { InitDriver(); @@ -536,7 +540,7 @@ public void SetRelayServerData(string ipv4Address, ushort port, byte[] allocatio { RelayConnectionData hostConnectionData; - if (!NetworkEndPoint.TryParse(ipv4Address, port, out var serverEndpoint)) + if (!NetworkEndpoint.TryParse(ipv4Address, port, out var serverEndpoint)) { Debug.LogError($"Invalid address {ipv4Address}:{port}"); @@ -613,7 +617,7 @@ public void SetConnectionData(string ipv4Address, ushort port, string listenAddr /// /// The remote end point /// The local listen endpoint - public void SetConnectionData(NetworkEndPoint endPoint, NetworkEndPoint listenEndPoint = default) + public void SetConnectionData(NetworkEndpoint endPoint, NetworkEndpoint listenEndPoint = default) { string serverAddress = endPoint.Address.Split(':')[0]; @@ -662,7 +666,7 @@ private bool StartRelayServer() else { m_NetworkSettings.WithRelayParameters(ref m_RelayServerData, m_HeartbeatTimeoutMS); - return ServerBindAndListen(NetworkEndPoint.AnyIpv4); + return ServerBindAndListen(NetworkEndpoint.AnyIpv4); } } @@ -907,7 +911,11 @@ private void ExtractNetworkMetricsFromPipeline(NetworkPipeline pipeline, Network { //Don't need to dispose of the buffers, they are filled with data pointers. m_Driver.GetPipelineBuffers(pipeline, +#if UTP_TRANSPORT_2_0_ABOVE + NetworkPipelineStageId.Get(), +#else NetworkPipelineStageCollection.GetStageId(typeof(NetworkMetricsPipelineStage)), +#endif networkConnection, out _, out _, @@ -934,7 +942,11 @@ private int ExtractRtt(NetworkConnection networkConnection) } m_Driver.GetPipelineBuffers(m_ReliableSequencedPipeline, +#if UTP_TRANSPORT_2_0_ABOVE + NetworkPipelineStageId.Get(), +#else NetworkPipelineStageCollection.GetStageId(typeof(ReliableSequencedPipelineStage)), +#endif networkConnection, out _, out _, @@ -956,7 +968,11 @@ private float ExtractPacketLoss(NetworkConnection networkConnection) } m_Driver.GetPipelineBuffers(m_ReliableSequencedPipeline, +#if UTP_TRANSPORT_2_0_ABOVE + NetworkPipelineStageId.Get(), +#else NetworkPipelineStageCollection.GetStageId(typeof(ReliableSequencedPipelineStage)), +#endif networkConnection, out _, out _, @@ -1117,11 +1133,12 @@ public override void Initialize(NetworkManager networkManager = null) // account for the overhead of its length when we store it in the send queue. var fragmentationCapacity = m_MaxPayloadSize + BatchedSendQueue.PerMessageOverhead; - m_NetworkSettings - .WithFragmentationStageParameters(payloadCapacity: fragmentationCapacity) - .WithBaselibNetworkInterfaceParameters( - receiveQueueCapacity: m_MaxPacketQueueSize, - sendQueueCapacity: m_MaxPacketQueueSize); + m_NetworkSettings.WithFragmentationStageParameters(payloadCapacity: fragmentationCapacity); +#if !UTP_TRANSPORT_2_0_ABOVE + m_NetworkSettings.WithBaselibNetworkInterfaceParameters( + receiveQueueCapacity: m_MaxPacketQueueSize, + sendQueueCapacity: m_MaxPacketQueueSize); +#endif #endif } @@ -1306,6 +1323,9 @@ private void ConfigureSimulator() packetDelayMs: DebugSimulator.PacketDelayMS, packetJitterMs: DebugSimulator.PacketJitterMS, packetDropPercentage: DebugSimulator.PacketDropRate +#if UTP_TRANSPORT_2_0_ABOVE + , mode: ApplyMode.AllPackets +#endif ); } @@ -1323,8 +1343,10 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, out NetworkPipeline reliableSequencedPipeline) { #if MULTIPLAYER_TOOLS_1_0_0_PRE_7 +#if !UTP_TRANSPORT_2_0_ABOVE NetworkPipelineStageCollection.RegisterPipelineStage(new NetworkMetricsPipelineStage()); #endif +#endif #if UNITY_EDITOR || DEVELOPMENT_BUILD ConfigureSimulator(); @@ -1334,17 +1356,29 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, maxConnectAttempts: transport.m_MaxConnectAttempts, connectTimeoutMS: transport.m_ConnectTimeoutMS, disconnectTimeoutMS: transport.m_DisconnectTimeoutMS, +#if UTP_TRANSPORT_2_0_ABOVE + sendQueueCapacity: m_MaxPacketQueueSize, + receiveQueueCapacity: m_MaxPacketQueueSize, +#endif heartbeatTimeoutMS: transport.m_HeartbeatTimeoutMS); driver = NetworkDriver.Create(m_NetworkSettings); +#if MULTIPLAYER_TOOLS_1_0_0_PRE_7 +#if UTP_TRANSPORT_2_0_ABOVE + driver.RegisterPipelineStage(new NetworkMetricsPipelineStage()); +#endif +#endif + #if UNITY_EDITOR || DEVELOPMENT_BUILD if (DebugSimulator.PacketDelayMS > 0 || DebugSimulator.PacketDropRate > 0) { unreliableFragmentedPipeline = driver.CreatePipeline( typeof(FragmentationPipelineStage), - typeof(SimulatorPipelineStage), - typeof(SimulatorPipelineStageInSend) + typeof(SimulatorPipelineStage) +#if !UTP_TRANSPORT_2_0_ABOVE + , typeof(SimulatorPipelineStageInSend) +#endif #if MULTIPLAYER_TOOLS_1_0_0_PRE_7 , typeof(NetworkMetricsPipelineStage) #endif @@ -1352,16 +1386,20 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, unreliableSequencedFragmentedPipeline = driver.CreatePipeline( typeof(FragmentationPipelineStage), typeof(UnreliableSequencedPipelineStage), - typeof(SimulatorPipelineStage), - typeof(SimulatorPipelineStageInSend) + typeof(SimulatorPipelineStage) +#if !UTP_TRANSPORT_2_0_ABOVE + , typeof(SimulatorPipelineStageInSend) +#endif #if MULTIPLAYER_TOOLS_1_0_0_PRE_7 ,typeof(NetworkMetricsPipelineStage) #endif ); reliableSequencedPipeline = driver.CreatePipeline( typeof(ReliableSequencedPipelineStage), - typeof(SimulatorPipelineStage), - typeof(SimulatorPipelineStageInSend) + typeof(SimulatorPipelineStage) +#if !UTP_TRANSPORT_2_0_ABOVE + , typeof(SimulatorPipelineStageInSend) +#endif #if MULTIPLAYER_TOOLS_1_0_0_PRE_7 ,typeof(NetworkMetricsPipelineStage) #endif diff --git a/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef b/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef index 24a10f6533..334966a50f 100644 --- a/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef +++ b/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef @@ -30,6 +30,11 @@ "name": "com.unity.multiplayer.tools", "expression": "1.0.0-pre.7", "define": "MULTIPLAYER_TOOLS_1_0_0_PRE_7" + }, + { + "name": "com.unity.transport", + "expression": "2.0.0-exp", + "define": "UTP_TRANSPORT_2_0_ABOVE" } ] } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs index d8c19c9861..2dad18e132 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs @@ -55,6 +55,9 @@ public IEnumerator TrackPacketLossAsServer() } [UnityTest] +#if UTP_TRANSPORT_2_0_ABOVE + [Ignore("Pending adjustment for UTP 2.0")] +#endif public IEnumerator TrackPacketLossAsClient() { double packetLossRateMinRange = (m_PacketLossRate-m_PacketLossRangeDelta) / 100d; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportDriverClient.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportDriverClient.cs index e61ce4b009..d41d9bf26d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportDriverClient.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportDriverClient.cs @@ -65,7 +65,11 @@ private void OnDestroy() public void Connect() { +#if UTP_TRANSPORT_2_0_ABOVE + var endpoint = NetworkEndpoint.LoopbackIpv4; +#else var endpoint = NetworkEndPoint.LoopbackIpv4; +#endif endpoint.Port = 7777; m_Connection = m_Driver.Connect(endpoint); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/com.unity.netcode.runtimetests.asmdef b/com.unity.netcode.gameobjects/Tests/Runtime/com.unity.netcode.runtimetests.asmdef index fbb6f71d51..e245a308de 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/com.unity.netcode.runtimetests.asmdef +++ b/com.unity.netcode.gameobjects/Tests/Runtime/com.unity.netcode.runtimetests.asmdef @@ -39,6 +39,11 @@ "name": "com.unity.modules.physics", "expression": "", "define": "COM_UNITY_MODULES_PHYSICS" + }, + { + "name": "com.unity.transport", + "expression": "2.0.0-exp", + "define": "UTP_TRANSPORT_2_0_ABOVE" } ] -} \ No newline at end of file +} From 354e3733b713ea8f9a6bf6d7e43c2d528f8b76b3 Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Thu, 1 Sep 2022 11:58:56 -0400 Subject: [PATCH 047/138] style: fixing the 'tools' part of the SDK to pass standards checks. (#2177) --- .../Runtime/Metrics/NetworkMetrics.cs | 11 ++++------ .../Runtime/Metrics/NetworkObjectProvider.cs | 4 ++-- .../UTP/NetworkMetricsPipelineStage.cs | 13 +++++------ .../Runtime/Transports/UTP/UnityTransport.cs | 12 +++++----- .../Metrics/WaitForEventMetricValues.cs | 9 +++----- .../NetworkMetricsRegistrationTests.cs | 5 +++-- .../Runtime/Metrics/ConnectionMetricsTests.cs | 1 - .../Runtime/Metrics/MetricsDispatchTests.cs | 1 - .../Metrics/OwnershipChangeMetricsTests.cs | 3 +-- .../Runtime/Metrics/PacketLossMetricsTests.cs | 22 +++++++------------ .../Runtime/Metrics/PacketMetricsTests.cs | 1 - .../Tests/Runtime/Metrics/RpcMetricsTests.cs | 4 ++-- .../Metrics/TransportBytesMetricsTests.cs | 7 +++--- 13 files changed, 38 insertions(+), 55 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Metrics/NetworkMetrics.cs b/com.unity.netcode.gameobjects/Runtime/Metrics/NetworkMetrics.cs index fb92716d4c..b171932103 100644 --- a/com.unity.netcode.gameobjects/Runtime/Metrics/NetworkMetrics.cs +++ b/com.unity.netcode.gameobjects/Runtime/Metrics/NetworkMetrics.cs @@ -5,17 +5,14 @@ using Unity.Multiplayer.Tools.MetricTypes; using Unity.Multiplayer.Tools.NetStats; using Unity.Profiling; -using UnityEngine; namespace Unity.Netcode { internal class NetworkMetrics : INetworkMetrics { - const ulong k_MaxMetricsPerFrame = 1000L; - - static Dictionary s_SceneEventTypeNames; - - static ProfilerMarker s_FrameDispatch = new ProfilerMarker($"{nameof(NetworkMetrics)}.DispatchFrame"); + private const ulong k_MaxMetricsPerFrame = 1000L; + private static Dictionary s_SceneEventTypeNames; + private static ProfilerMarker s_FrameDispatch = new ProfilerMarker($"{nameof(NetworkMetrics)}.DispatchFrame"); static NetworkMetrics() { @@ -531,7 +528,7 @@ private static NetworkObjectIdentifier GetObjectIdentifier(NetworkObject network } } - internal class NetcodeObserver + internal class NetcodeObserver { public static IMetricObserver Observer { get; } = MetricObserverFactory.Construct(); } diff --git a/com.unity.netcode.gameobjects/Runtime/Metrics/NetworkObjectProvider.cs b/com.unity.netcode.gameobjects/Runtime/Metrics/NetworkObjectProvider.cs index e1f6f011b3..7d2ad6ebe4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Metrics/NetworkObjectProvider.cs +++ b/com.unity.netcode.gameobjects/Runtime/Metrics/NetworkObjectProvider.cs @@ -4,7 +4,7 @@ namespace Unity.Netcode { - class NetworkObjectProvider : INetworkObjectProvider + internal class NetworkObjectProvider : INetworkObjectProvider { private readonly NetworkManager m_NetworkManager; @@ -15,7 +15,7 @@ public NetworkObjectProvider(NetworkManager networkManager) public Object GetNetworkObject(ulong networkObjectId) { - if(m_NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject value)) + if (m_NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject value)) { return value; } diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/NetworkMetricsPipelineStage.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/NetworkMetricsPipelineStage.cs index d74fe94048..30c7dd2691 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/NetworkMetricsPipelineStage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/NetworkMetricsPipelineStage.cs @@ -4,25 +4,24 @@ using Unity.Burst; using Unity.Collections.LowLevel.Unsafe; using Unity.Networking.Transport; -using UnityEngine; namespace Unity.Netcode.Transports.UTP { [BurstCompile] internal unsafe struct NetworkMetricsPipelineStage : INetworkPipelineStage { - static TransportFunctionPointer ReceiveFunction = new TransportFunctionPointer(Receive); - static TransportFunctionPointer SendFunction = new TransportFunctionPointer(Send); - static TransportFunctionPointer InitializeConnectionFunction = new TransportFunctionPointer(InitializeConnection); + private static TransportFunctionPointer s_ReceiveFunction = new TransportFunctionPointer(Receive); + private static TransportFunctionPointer s_SendFunction = new TransportFunctionPointer(Send); + private static TransportFunctionPointer s_InitializeConnectionFunction = new TransportFunctionPointer(InitializeConnection); public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer, int staticInstanceBufferLength, NetworkSettings settings) { return new NetworkPipelineStage( - ReceiveFunction, - SendFunction, - InitializeConnectionFunction, + s_ReceiveFunction, + s_SendFunction, + s_InitializeConnectionFunction, ReceiveCapacity: 0, SendCapacity: 0, HeaderCapacity: 0, diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index 1e6302338b..b46720ea66 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -895,7 +895,7 @@ private void ExtractNetworkMetrics() private void ExtractNetworkMetricsForClient(ulong transportClientId) { - var networkConnection = ParseClientId(transportClientId); + var networkConnection = ParseClientId(transportClientId); ExtractNetworkMetricsFromPipeline(m_UnreliableFragmentedPipeline, networkConnection); ExtractNetworkMetricsFromPipeline(m_UnreliableSequencedFragmentedPipeline, networkConnection); ExtractNetworkMetricsFromPipeline(m_ReliableSequencedPipeline, networkConnection); @@ -1391,7 +1391,7 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, , typeof(SimulatorPipelineStageInSend) #endif #if MULTIPLAYER_TOOLS_1_0_0_PRE_7 - ,typeof(NetworkMetricsPipelineStage) + , typeof(NetworkMetricsPipelineStage) #endif ); reliableSequencedPipeline = driver.CreatePipeline( @@ -1401,7 +1401,7 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, , typeof(SimulatorPipelineStageInSend) #endif #if MULTIPLAYER_TOOLS_1_0_0_PRE_7 - ,typeof(NetworkMetricsPipelineStage) + , typeof(NetworkMetricsPipelineStage) #endif ); } @@ -1411,20 +1411,20 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, unreliableFragmentedPipeline = driver.CreatePipeline( typeof(FragmentationPipelineStage) #if MULTIPLAYER_TOOLS_1_0_0_PRE_7 - ,typeof(NetworkMetricsPipelineStage) + , typeof(NetworkMetricsPipelineStage) #endif ); unreliableSequencedFragmentedPipeline = driver.CreatePipeline( typeof(FragmentationPipelineStage), typeof(UnreliableSequencedPipelineStage) #if MULTIPLAYER_TOOLS_1_0_0_PRE_7 - ,typeof(NetworkMetricsPipelineStage) + , typeof(NetworkMetricsPipelineStage) #endif ); reliableSequencedPipeline = driver.CreatePipeline( typeof(ReliableSequencedPipelineStage) #if MULTIPLAYER_TOOLS_1_0_0_PRE_7 - ,typeof(NetworkMetricsPipelineStage) + , typeof(NetworkMetricsPipelineStage) #endif ); } diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/Metrics/WaitForEventMetricValues.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/Metrics/WaitForEventMetricValues.cs index 8378f16225..a9af59427a 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/Metrics/WaitForEventMetricValues.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/Metrics/WaitForEventMetricValues.cs @@ -1,10 +1,6 @@ #if MULTIPLAYER_TOOLS -using System; -using System.Collections; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; -using NUnit.Framework; using Unity.Multiplayer.Tools.MetricTypes; using Unity.Multiplayer.Tools.NetStats; @@ -12,10 +8,11 @@ namespace Unity.Netcode.TestHelpers.Runtime.Metrics { internal class WaitForEventMetricValues : WaitForMetricValues { - IReadOnlyCollection m_EventValues; + private IReadOnlyCollection m_EventValues; public delegate bool EventFilter(TMetric metric); - EventFilter m_EventFilterDelegate; + + private EventFilter m_EventFilterDelegate; public WaitForEventMetricValues(IMetricDispatcher dispatcher, DirectionalMetricInfo directionalMetricName) : base(dispatcher, directionalMetricName) diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Metrics/NetworkMetricsRegistrationTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Metrics/NetworkMetricsRegistrationTests.cs index 56fb1321ee..e962c86eb7 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Metrics/NetworkMetricsRegistrationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Metrics/NetworkMetricsRegistrationTests.cs @@ -10,12 +10,13 @@ namespace Unity.Netcode.EditorTests.Metrics { public class NetworkMetricsRegistrationTests { - static Type[] s_MetricTypes = AppDomain.CurrentDomain.GetAssemblies() + private static Type[] s_MetricTypes = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(x => x.GetTypes()) .Where(x => x.GetInterfaces().Contains(typeof(INetworkMetricEvent))) .ToArray(); - [TestCaseSource(nameof(s_MetricTypes))][Ignore("Disable test while we reevaluate the assumption that INetworkMetricEvent interfaces must be reported from MLAPI.")] + [TestCaseSource(nameof(s_MetricTypes))] + [Ignore("Disable test while we reevaluate the assumption that INetworkMetricEvent interfaces must be reported from MLAPI.")] public void ValidateThatAllMetricTypesAreRegistered(Type metricType) { var dispatcher = new NetworkMetrics().Dispatcher as MetricDispatcher; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/ConnectionMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/ConnectionMetricsTests.cs index 9f678f1cdc..35356d4bcf 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/ConnectionMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/ConnectionMetricsTests.cs @@ -2,7 +2,6 @@ #if MULTIPLAYER_TOOLS_1_0_0_PRE_7 using System.Collections; -using System.Collections.Generic; using NUnit.Framework; using Unity.Multiplayer.Tools.MetricTypes; using Unity.Netcode.TestHelpers.Runtime; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/MetricsDispatchTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/MetricsDispatchTests.cs index 12adec0940..519ddfa697 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/MetricsDispatchTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/MetricsDispatchTests.cs @@ -5,7 +5,6 @@ using Unity.Multiplayer.Tools.NetStats; using UnityEngine.TestTools; using Unity.Netcode.TestHelpers.Runtime; -using Unity.Netcode.TestHelpers.Runtime.Metrics; namespace Unity.Netcode.RuntimeTests.Metrics { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs index f41b6eea4f..cb4febf5e4 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs @@ -1,5 +1,4 @@ #if MULTIPLAYER_TOOLS -using System; using System.Collections; using System.Linq; using NUnit.Framework; @@ -36,7 +35,7 @@ protected override void OnServerAndClientsCreated() private NetworkObject SpawnNetworkObject() { // Spawn another network object so we can hide multiple. - var gameObject = UnityEngine.Object.Instantiate(m_NewNetworkPrefab); // new GameObject(NewNetworkObjectName); + var gameObject = Object.Instantiate(m_NewNetworkPrefab); // new GameObject(NewNetworkObjectName); var networkObject = gameObject.GetComponent(); networkObject.NetworkManagerOwner = Server; networkObject.Spawn(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs index 2dad18e132..32abea2463 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs @@ -1,7 +1,6 @@ #if MULTIPLAYER_TOOLS #if MULTIPLAYER_TOOLS_1_0_0_PRE_7 -using System; using System.Collections; using NUnit.Framework; using Unity.Collections; @@ -9,7 +8,6 @@ using Unity.Netcode.TestHelpers.Runtime; using Unity.Netcode.TestHelpers.Runtime.Metrics; using Unity.Netcode.Transports.UTP; -using UnityEngine; using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests.Metrics @@ -22,7 +20,7 @@ public class PacketLossMetricsTests : NetcodeIntegrationTest public PacketLossMetricsTests() : base(HostOrServer.Server) - {} + { } protected override void OnServerAndClientsCreated() { @@ -41,11 +39,9 @@ public IEnumerator TrackPacketLossAsServer() for (int i = 0; i < 1000; ++i) { - using (var writer = new FastBufferWriter(sizeof(byte), Allocator.Persistent)) - { - writer.WriteByteSafe(42); - m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage("Test", m_ServerNetworkManager.ConnectedClientsIds, writer); - } + using var writer = new FastBufferWriter(sizeof(byte), Allocator.Persistent); + writer.WriteByteSafe(42); + m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage("Test", m_ServerNetworkManager.ConnectedClientsIds, writer); } yield return waitForPacketLossMetric.WaitForMetricsReceived(); @@ -60,7 +56,7 @@ public IEnumerator TrackPacketLossAsServer() #endif public IEnumerator TrackPacketLossAsClient() { - double packetLossRateMinRange = (m_PacketLossRate-m_PacketLossRangeDelta) / 100d; + double packetLossRateMinRange = (m_PacketLossRate - m_PacketLossRangeDelta) / 100d; double packetLossRateMaxrange = (m_PacketLossRate + m_PacketLossRangeDelta) / 100d; var clientNetworkManager = m_ClientNetworkManagers[0]; var waitForPacketLossMetric = new WaitForGaugeMetricValues((clientNetworkManager.NetworkMetrics as NetworkMetrics).Dispatcher, @@ -69,11 +65,9 @@ public IEnumerator TrackPacketLossAsClient() for (int i = 0; i < 1000; ++i) { - using (var writer = new FastBufferWriter(sizeof(byte), Allocator.Persistent)) - { - writer.WriteByteSafe(42); - m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage("Test", m_ServerNetworkManager.ConnectedClientsIds, writer); - } + using var writer = new FastBufferWriter(sizeof(byte), Allocator.Persistent); + writer.WriteByteSafe(42); + m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage("Test", m_ServerNetworkManager.ConnectedClientsIds, writer); } yield return waitForPacketLossMetric.WaitForMetricsReceived(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketMetricsTests.cs index a9e4cc0915..90707de0b8 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketMetricsTests.cs @@ -5,7 +5,6 @@ using Unity.Collections; using Unity.Multiplayer.Tools.MetricTypes; using UnityEngine.TestTools; -using Unity.Netcode.TestHelpers.Runtime; using Unity.Netcode.TestHelpers.Runtime.Metrics; namespace Unity.Netcode.RuntimeTests.Metrics diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/RpcMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/RpcMetricsTests.cs index be68374c1a..b2b186a6f6 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/RpcMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/RpcMetricsTests.cs @@ -26,7 +26,7 @@ public IEnumerator TrackRpcSentMetricOnServerToOnlyOneClientWithArray() { Send = new ClientRpcSendParams { - TargetClientIds = new []{FirstClient.LocalClientId} + TargetClientIds = new[] { FirstClient.LocalClientId } } }); @@ -50,7 +50,7 @@ public IEnumerator TrackRpcSentMetricOnServerToOnlyOneClientWithNativeArray() { Send = new ClientRpcSendParams { - TargetClientIdsNativeArray = new NativeArray(new []{FirstClient.LocalClientId}, Allocator.Temp) + TargetClientIdsNativeArray = new NativeArray(new[] { FirstClient.LocalClientId }, Allocator.Temp) } }); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/TransportBytesMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/TransportBytesMetricsTests.cs index a6ded69ae8..f1891bb815 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/TransportBytesMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/TransportBytesMetricsTests.cs @@ -1,7 +1,6 @@ #if MULTIPLAYER_TOOLS using System; using System.Collections; -using System.IO; using NUnit.Framework; using Unity.Collections; using Unity.Multiplayer.Tools.MetricTypes; @@ -15,7 +14,7 @@ internal class TransportBytesMetricsTests : SingleClientMetricTestBase { // Header is dynamically sized due to packing, will be 2 bytes for all test messages. private const int k_MessageHeaderSize = 2; - static readonly long MessageOverhead = 8 + FastBufferWriter.GetWriteSize() + k_MessageHeaderSize; + private static readonly long k_MessageOverhead = 8 + FastBufferWriter.GetWriteSize() + k_MessageHeaderSize; [UnityTest] public IEnumerator TrackTotalNumberOfBytesSent() @@ -42,7 +41,7 @@ public IEnumerator TrackTotalNumberOfBytesSent() } Assert.True(observer.Found); - Assert.AreEqual(FastBufferWriter.GetWriteSize(messageName) + MessageOverhead, observer.Value); + Assert.AreEqual(FastBufferWriter.GetWriteSize(messageName) + k_MessageOverhead, observer.Value); } [UnityTest] @@ -72,7 +71,7 @@ public IEnumerator TrackTotalNumberOfBytesReceived() } Assert.True(observer.Found); - Assert.AreEqual(FastBufferWriter.GetWriteSize(messageName) + MessageOverhead, observer.Value); + Assert.AreEqual(FastBufferWriter.GetWriteSize(messageName) + k_MessageOverhead, observer.Value); } private class TotalBytesObserver : IMetricObserver From 5984fc3fb4aeeb9401a2a80b995b1aa1d1e9bd7e Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Tue, 6 Sep 2022 13:56:29 -0400 Subject: [PATCH 048/138] fix: Rework packet loss metric tests to work with UTP 2.0 [MTT-4535] (#2174) * Allow fixing the simulator RNG seed * Fix packet loss test for UTP 2.0 * Add comment about random seed in packet loss test --- .../Runtime/Transports/UTP/UnityTransport.cs | 5 ++++- .../Runtime/Metrics/PacketLossMetricsTests.cs | 20 +++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index b46720ea66..d209d8b856 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -337,6 +337,8 @@ public struct SimulatorParameters PacketDropRate = 0 }; + internal uint DebugSimulatorRandomSeed { get; set; } = 0; + private struct PacketLossCache { public int PacketsReceived; @@ -1322,7 +1324,8 @@ private void ConfigureSimulator() maxPacketSize: NetworkParameterConstants.MTU, packetDelayMs: DebugSimulator.PacketDelayMS, packetJitterMs: DebugSimulator.PacketJitterMS, - packetDropPercentage: DebugSimulator.PacketDropRate + packetDropPercentage: DebugSimulator.PacketDropRate, + randomSeed: DebugSimulatorRandomSeed #if UTP_TRANSPORT_2_0_ABOVE , mode: ApplyMode.AllPackets #endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs index 32abea2463..2839189b34 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs @@ -16,7 +16,8 @@ public class PacketLossMetricsTests : NetcodeIntegrationTest { protected override int NumberOfClients => 1; private readonly int m_PacketLossRate = 25; - private readonly int m_PacketLossRangeDelta = 5; + private readonly int m_PacketLossRangeDelta = 3; + private readonly int m_MessageSize = 200; public PacketLossMetricsTests() : base(HostOrServer.Server) @@ -27,6 +28,12 @@ protected override void OnServerAndClientsCreated() var clientTransport = (UnityTransport)m_ClientNetworkManagers[0].NetworkConfig.NetworkTransport; clientTransport.SetDebugSimulatorParameters(0, 0, m_PacketLossRate); + // Determined through trial and error. With both UTP 1.2 and 2.0, this random seed + // results in an effective packet loss percentage between 22% and 28%. Future UTP + // updates may change the RNG call patterns and cause this test to fail, in which + // case the value should be modified again. + clientTransport.DebugSimulatorRandomSeed = 4; + base.OnServerAndClientsCreated(); } @@ -39,8 +46,8 @@ public IEnumerator TrackPacketLossAsServer() for (int i = 0; i < 1000; ++i) { - using var writer = new FastBufferWriter(sizeof(byte), Allocator.Persistent); - writer.WriteByteSafe(42); + using var writer = new FastBufferWriter(m_MessageSize, Allocator.Persistent); + writer.WriteBytesSafe(new byte[m_MessageSize]); m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage("Test", m_ServerNetworkManager.ConnectedClientsIds, writer); } @@ -51,9 +58,6 @@ public IEnumerator TrackPacketLossAsServer() } [UnityTest] -#if UTP_TRANSPORT_2_0_ABOVE - [Ignore("Pending adjustment for UTP 2.0")] -#endif public IEnumerator TrackPacketLossAsClient() { double packetLossRateMinRange = (m_PacketLossRate - m_PacketLossRangeDelta) / 100d; @@ -65,8 +69,8 @@ public IEnumerator TrackPacketLossAsClient() for (int i = 0; i < 1000; ++i) { - using var writer = new FastBufferWriter(sizeof(byte), Allocator.Persistent); - writer.WriteByteSafe(42); + using var writer = new FastBufferWriter(m_MessageSize, Allocator.Persistent); + writer.WriteBytesSafe(new byte[m_MessageSize]); m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage("Test", m_ServerNetworkManager.ConnectedClientsIds, writer); } From 19bbe54c1abce83204b317f93e9d568d0f676064 Mon Sep 17 00:00:00 2001 From: Kitty Draper <284434+ShadauxCat@users.noreply.github.com> Date: Tue, 6 Sep 2022 15:09:46 -0500 Subject: [PATCH 049/138] fix: Adds a null check for NetworkObjectReference => GameObject conversion [NCCBUG-172] (#2158) * fix: Adds a null check for NetworkObjectReference => GameObject conversion [NCCBUG-172] * Changelog * style * Update com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs Co-authored-by: Jesse Olmer * Review feedback. Co-authored-by: Jesse Olmer --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Serialization/NetworkObjectReference.cs | 11 +++++++- .../NetworkObjectReferenceTests.cs | 26 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index afffda00bc..f0c0cd8abc 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -13,6 +13,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed ClientRpcs always reporting in the profiler view as going to all clients, even when limited to a subset of clients by ClientRpcParams. (#2144) - Fixed RPC codegen failing to choose the correct extension methods for FastBufferReader and FastBufferWriter when the parameters were a generic type (i.e., List) and extensions for multiple instantiations of that type have been defined (i.e., List and List) (#2142) +- Implicit conversion of NetworkObjectReference to GameObject will now return null instead of throwing an exception if the referenced object could not be found (i.e., was already despawned) (#2158) - Fixed throwing an exception in OnNetworkUpdate causing other OnNetworkUpdate calls to not be executed. (#1739) - Fixed warning resulting from a stray NetworkAnimator.meta file (#2153) diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs index 11a2ab2a38..263403589e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs @@ -139,7 +139,16 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade /// /// The to convert from. /// This returns the that the is attached to and is referenced by the passed in as a parameter - public static implicit operator GameObject(NetworkObjectReference networkObjectRef) => Resolve(networkObjectRef).gameObject; + public static implicit operator GameObject(NetworkObjectReference networkObjectRef) + { + var networkObject = Resolve(networkObjectRef); + if (networkObject != null) + { + return networkObject.gameObject; + } + + return null; + } /// /// Implicitly convert to . diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs index 49c4a1f059..07cd5769c7 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs @@ -110,6 +110,32 @@ public void TestSerializeGameObject() } } + [Test] + public void TestImplicitConversionToGameObject() + { + using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); + networkObjectContext.Object.Spawn(); + + NetworkObjectReference outReference = networkObjectContext.Object.gameObject; + + GameObject go = outReference; + Assert.AreEqual(networkObjectContext.Object.gameObject, go); + } + + [Test] + public void TestImplicitToGameObjectIsNullWhenNotFound() + { + using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); + networkObjectContext.Object.Spawn(); + + NetworkObjectReference outReference = networkObjectContext.Object.gameObject; + + networkObjectContext.Object.Despawn(); + Object.DestroyImmediate(networkObjectContext.Object.gameObject); + + GameObject go = outReference; + Assert.IsNull(go); + } [Test] public void TestTryGet() { From 1d0bcee11b7fe44fc4aecf3134de0b4f6ea777ae Mon Sep 17 00:00:00 2001 From: Kitty Draper <284434+ShadauxCat@users.noreply.github.com> Date: Wed, 7 Sep 2022 09:46:52 -0500 Subject: [PATCH 050/138] chore: netcode.standards --fix (#2173) --- .../Tests/Runtime/NetworkVariableTests.cs | 4 ++-- .../SceneTransitioningAdditive/SceneEventNotificationQueue.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs index f8b733985c..64e0d2e247 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs @@ -818,8 +818,8 @@ protected override bool OnHasConditionBeenReached() /// private string ConditionFailedInfo() { - return $"{m_NetworkListTestState} condition test failed:\n Server List Count: { m_Player1OnServer.TheList.Count} vs Client List Count: { m_Player1OnClient1.TheList.Count}\n" + - $"Server List Count: { m_Player1OnServer.TheLargeList.Count} vs Client List Count: { m_Player1OnClient1.TheLargeList.Count}\n" + + return $"{m_NetworkListTestState} condition test failed:\n Server List Count: {m_Player1OnServer.TheList.Count} vs Client List Count: {m_Player1OnClient1.TheList.Count}\n" + + $"Server List Count: {m_Player1OnServer.TheLargeList.Count} vs Client List Count: {m_Player1OnClient1.TheLargeList.Count}\n" + $"Server Delegate Triggered: {m_Player1OnServer.ListDelegateTriggered} | Client Delegate Triggered: {m_Player1OnClient1.ListDelegateTriggered}\n"; } diff --git a/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/SceneEventNotificationQueue.cs b/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/SceneEventNotificationQueue.cs index 2668b218ca..8e1072b186 100644 --- a/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/SceneEventNotificationQueue.cs +++ b/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/SceneEventNotificationQueue.cs @@ -66,7 +66,7 @@ private void OnSceneEvent(SceneEvent sceneEvent) var sceneEventMsg = $"({NetworkManager.Singleton.LocalClientId})-[{sceneEvent.ClientId} | {sceneEvent.SceneEventType} | {sceneEvent.SceneName}"; if (sceneEvent.SceneEventType == SceneEventType.Load || sceneEvent.SceneEventType == SceneEventType.LoadComplete) { - sceneEventMsg += $" | { sceneEvent.LoadSceneMode}"; + sceneEventMsg += $" | {sceneEvent.LoadSceneMode}"; if (sceneEvent.Scene.IsValid() && sceneEvent.SceneEventType == SceneEventType.LoadComplete) { sceneEventMsg += $" | SHID: {sceneEvent.Scene.handle}"; From c504ef53a27a3f227f96c34a53da5eb5673e04a0 Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Thu, 8 Sep 2022 10:18:16 -0400 Subject: [PATCH 051/138] fix: mtt-4516 NetworkList change inside OnNetworkSpawn (#2181) * fix: Marking NetworkVariable dirty upon spawn on the host/server. Falls short as the NetworkBehaviour doesn't yet have its NetworkVariables initialized * fix: clearing variable dirty state post spawning. Also, Initializing NetworkVariable upon spawn, as it is the logical place to do so * test: tests and removing debug logging --- .../Runtime/Core/NetworkBehaviour.cs | 29 ++++++++++++++++--- .../Runtime/Core/NetworkBehaviourUpdater.cs | 5 +--- .../Runtime/Core/NetworkObject.cs | 8 +++++ .../Collections/NetworkList.cs | 25 ++-------------- .../Tests/Runtime/NetworkShowHideTests.cs | 14 +++++++++ 5 files changed, 51 insertions(+), 30 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index afd004875f..7f95289319 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -476,6 +476,15 @@ internal void VisibleOnNetworkSpawn() { Debug.LogException(e); } + + InitializeVariables(); + if (IsServer) + { + // Since we just spawned the object and since user code might have modified their NetworkVariable, esp. + // NetworkList, we need to mark the object as free of updates. + // This should happen for all objects on the machine triggering the spawn. + PostNetworkVariableWrite(true); + } } internal void InternalOnNetworkDespawn() @@ -622,12 +631,24 @@ internal void PreNetworkVariableWrite() NetworkVariableIndexesToResetSet.Clear(); } - internal void PostNetworkVariableWrite() + internal void PostNetworkVariableWrite(bool forced = false) { - // mark any variables we wrote as no longer dirty - for (int i = 0; i < NetworkVariableIndexesToReset.Count; i++) + if (forced) + { + // Mark every variable as no longer dirty. We just spawned the object and whatever the game code did + // during OnNetworkSpawn has been sent and needs to be cleared + for (int i = 0; i < NetworkVariableFields.Count; i++) + { + NetworkVariableFields[i].ResetDirty(); + } + } + else { - NetworkVariableFields[NetworkVariableIndexesToReset[i]].ResetDirty(); + // mark any variables we wrote as no longer dirty + for (int i = 0; i < NetworkVariableIndexesToReset.Count; i++) + { + NetworkVariableFields[NetworkVariableIndexesToReset[i]].ResetDirty(); + } } MarkVariablesDirty(false); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs index 7e9176efbf..b75375e1ee 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs @@ -75,10 +75,7 @@ internal void NetworkBehaviourUpdate(NetworkManager networkManager) // Now, reset all the no-longer-dirty variables foreach (var dirtyobj in m_DirtyNetworkObjects) { - for (int k = 0; k < dirtyobj.ChildNetworkBehaviours.Count; k++) - { - dirtyobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite(); - } + dirtyobj.PostNetworkVariableWrite(); } m_DirtyNetworkObjects.Clear(); } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index b08900a080..a772ca895c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1094,6 +1094,14 @@ public unsafe void Deserialize(FastBufferReader reader) } } + internal void PostNetworkVariableWrite() + { + for (int k = 0; k < ChildNetworkBehaviours.Count; k++) + { + ChildNetworkBehaviours[k].PostNetworkVariableWrite(); + } + } + internal SceneObject GetMessageSceneObject(ulong targetClientId) { var obj = new SceneObject diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index c2ced507dc..78b6eede44 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -12,7 +12,6 @@ namespace Unity.Netcode public class NetworkList : NetworkVariableBase where T : unmanaged, IEquatable { private NativeList m_List = new NativeList(64, Allocator.Persistent); - private NativeList m_ListAtLastReset = new NativeList(64, Allocator.Persistent); private NativeList> m_DirtyEvents = new NativeList>(64, Allocator.Persistent); /// @@ -53,7 +52,6 @@ public override void ResetDirty() if (m_DirtyEvents.Length > 0) { m_DirtyEvents.Clear(); - m_ListAtLastReset.CopyFrom(m_List.AsArray()); } } @@ -135,26 +133,10 @@ public override void WriteDelta(FastBufferWriter writer) /// public override void WriteField(FastBufferWriter writer) { - // The listAtLastReset mechanism was put in place to deal with duplicate adds - // upon initial spawn. However, it causes issues with in-scene placed objects - // due to difference in spawn order. In order to address this, we pick the right - // list based on the type of object. - bool isSceneObject = m_NetworkBehaviour.NetworkObject.IsSceneObject != false; - if (isSceneObject) + writer.WriteValueSafe((ushort)m_List.Length); + for (int i = 0; i < m_List.Length; i++) { - writer.WriteValueSafe((ushort)m_ListAtLastReset.Length); - for (int i = 0; i < m_ListAtLastReset.Length; i++) - { - NetworkVariableSerialization.Write(writer, ref m_ListAtLastReset.ElementAt(i)); - } - } - else - { - writer.WriteValueSafe((ushort)m_List.Length); - for (int i = 0; i < m_List.Length; i++) - { - NetworkVariableSerialization.Write(writer, ref m_List.ElementAt(i)); - } + NetworkVariableSerialization.Write(writer, ref m_List.ElementAt(i)); } } @@ -528,7 +510,6 @@ public int LastModifiedTick public override void Dispose() { m_List.Dispose(); - m_ListAtLastReset.Dispose(); m_DirtyEvents.Dispose(); } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs index 35672294e7..8707bd95eb 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs @@ -37,6 +37,17 @@ public override void OnNetworkSpawn() { ClientTargetedNetworkObjects.Add(this); } + + if (IsServer) + { + MyListSetOnSpawn.Add(45); + } + else + { + Debug.Assert(MyListSetOnSpawn.Count == 1); + Debug.Assert(MyListSetOnSpawn[0] == 45); + } + base.OnNetworkSpawn(); } @@ -50,11 +61,14 @@ public override void OnNetworkDespawn() } public NetworkVariable MyNetworkVariable; + public NetworkList MyListSetOnSpawn; private void Awake() { MyNetworkVariable = new NetworkVariable(); MyNetworkVariable.OnValueChanged += Changed; + + MyListSetOnSpawn = new NetworkList(); } public void Changed(int before, int after) From 2a6a1460a1f2f36886084497b52dd985f377bf5a Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 8 Sep 2022 10:46:30 -0500 Subject: [PATCH 052/138] fix: NetworkTransform was not ending extrapolation for some states. [MTT-4521] (#2170) * update working version that needs clean up * update Some touch ups and adjustments to the fix for the extrapolation issue. * style adding some minor comments * update Added comments and renamed a property. * update some code clean up and additional comments * update MTT-4521 updating changelog file * update MTT-4521 Renamed StopExtrapolatingLastState to TryToStopExtrapolatingLastState. Added/adjusted comments. * update Applying suggested changes. * update removing some changes I made while testing out extrapolating past the target. adjusted comment. * style adjusted comment for clarity purposes. * test Adding some of the tests. Still WIP, but wanted to see how the first completed test fairs in Yamato and on consoles. * style removing unused namespace. * test update Added a form of tick synchronization prior to making changes to the authoritative transform state. * style adjusting comments. * test - Added NetworkTransformParentedLocalSpaceTests with associated helper methods, components, and properties. - Added an "Authority" suffixe to the Authority enums for better clarity when looking at the tests within test runner. - Updated/added additional comments * style Making NetworkTransformParentedLocalSpaceTests singular (NetworkTransformParentedLocalSpaceTest) * style adding remarks around comments * style splitting the comment into a summary with remarks. * style updating a comment on the UpdateAuthoritativeState method. * fix Copy paste issue. (need to revisit tests to see why they didn't catch this) * fix z component * fix using the eurlerAngles... that section of code was a mess! (sorry!) * update Just adding some parenthesis per Kitty's suggestion. * update and fix Updated ApplyAuthoritativeState so that it is less complicated to follow. Fixed another issue discovered where the replicated network state values were being used for resetting interpolators during spawn initialization. Since the replicated network state only has the most recent deltas at the time the NetworkObject is serialized, this would cause "seemingly random" late joining issues. * test update This validates the issue with the more recent updates that revealed the network state only having the most recent deltas and not the full transform state. This resulted in various "seemingly" random late join transform synchronization issues (specifically the scale that is not being synchronized when spawning NetworkObjects). * update removed the unnecessary else if (useInterpolatedValue) condition and re-organized it such that it checks to see if it needs to apply interpolation or just depends upon the state to apply the values directly. Added additional comments about this area of the code and future improvements. * fix Removing unclamped slerp and lerp due to a few timing related issues. These issues should be solved before re-enabling any form of unclamped lerping/slerping with BufferedLinearInterpolator. Added additional comments in some areas to be investigated for a potential fix. * fix temporarily disabling two editor based interpolator extrapolation based tests until I can refactor them to not take extrapolated values into consideration. * fix removing debug code used to verify NetworkTransform is not the only thing within NGO that fails when dropping packets (most likely to occur 20-30% drop rate). * fix Make sure to initialize based on whether it is set to world or local space. * fix When authority is applying the current position and rotation during OnNetworkSpawn, use the appropriate local vs world space values. * update Adding another fix to the change log Co-authored-by: ashwini <36935028+ashwinimurt@users.noreply.github.com> --- com.unity.netcode.gameobjects/CHANGELOG.md | 10 +- .../BufferedLinearInterpolator.cs | 14 +- .../Components/NetworkTransform.cs | 302 ++++++++------ .../Tests/Editor/InterpolatorTests.cs | 2 + .../NetworkTransform/NetworkTransformTests.cs | 389 +++++++++++++++++- 5 files changed, 571 insertions(+), 146 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index f0c0cd8abc..68e31dae3d 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -11,11 +11,15 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed -- Fixed ClientRpcs always reporting in the profiler view as going to all clients, even when limited to a subset of clients by ClientRpcParams. (#2144) -- Fixed RPC codegen failing to choose the correct extension methods for FastBufferReader and FastBufferWriter when the parameters were a generic type (i.e., List) and extensions for multiple instantiations of that type have been defined (i.e., List and List) (#2142) +- Fixed issue where `NetworkTransform` was not ending extrapolation for the previous state causing non-authoritative instances to become out of synch. (#2170) +- Fixed issue where `NetworkTransform` was not continuing to interpolate for the remainder of the associated tick period. (#2170) +- Fixed issue during `NetworkTransform.OnNetworkSpawn` for non-authoritative instances where it was initializing interpolators with the replicated network state which now only contains the transform deltas that occurred during a network tick and not the entire transform state. (#2170) +- Fixed issue where `NetworkTransform` was not honoring the InLocalSpace property on the authority side during OnNetworkSpawn. (#2170) - Implicit conversion of NetworkObjectReference to GameObject will now return null instead of throwing an exception if the referenced object could not be found (i.e., was already despawned) (#2158) -- Fixed throwing an exception in OnNetworkUpdate causing other OnNetworkUpdate calls to not be executed. (#1739) - Fixed warning resulting from a stray NetworkAnimator.meta file (#2153) +- Fixed ClientRpcs always reporting in the profiler view as going to all clients, even when limited to a subset of clients by `ClientRpcParams`. (#2144) +- Fixed RPC codegen failing to choose the correct extension methods for `FastBufferReader` and `FastBufferWriter` when the parameters were a generic type (i.e., List) and extensions for multiple instantiations of that type have been defined (i.e., List and List) (#2142) +- Fixed throwing an exception in `OnNetworkUpdate` causing other `OnNetworkUpdate` calls to not be executed. (#1739) ## [1.0.1] - 2022-08-23 diff --git a/com.unity.netcode.gameobjects/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Components/Interpolator/BufferedLinearInterpolator.cs index dc4919b1a0..9d248299ff 100644 --- a/com.unity.netcode.gameobjects/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Components/Interpolator/BufferedLinearInterpolator.cs @@ -248,6 +248,8 @@ public void AddMeasurement(T newMeasurement, double sentTime) return; } + // Part the of reason for disabling extrapolation is how we add and use measurements over time. + // TODO: Add detailed description of this area in Jira ticket if (sentTime > m_EndTimeConsumed || m_LifetimeConsumedCount == 0) // treat only if value is newer than the one being interpolated to right now { m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime); @@ -292,7 +294,9 @@ public class BufferedLinearInterpolatorFloat : BufferedLinearInterpolator /// protected override float InterpolateUnclamped(float start, float end, float time) { - return Mathf.LerpUnclamped(start, end, time); + // Disabling Extrapolation: + // TODO: Add Jira Ticket + return Mathf.Lerp(start, end, time); } /// @@ -311,13 +315,17 @@ public class BufferedLinearInterpolatorQuaternion : BufferedLinearInterpolator protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion end, float time) { - return Quaternion.SlerpUnclamped(start, end, time); + // Disabling Extrapolation: + // TODO: Add Jira Ticket + return Quaternion.Slerp(start, end, time); } /// protected override Quaternion Interpolate(Quaternion start, Quaternion end, float time) { - return Quaternion.SlerpUnclamped(start, end, time); + // Disabling Extrapolation: + // TODO: Add Jira Ticket + return Quaternion.Slerp(start, end, time); } } } diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index f54d08dee3..b1ff99ec60 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -207,8 +207,13 @@ internal bool IsTeleportingNextFrame internal float ScaleX, ScaleY, ScaleZ; internal double SentTime; + // Authoritative and non-authoritative sides use this to determine if a NetworkTransformState is + // dirty or not. internal bool IsDirty; + // Non-Authoritative side uses this for ending extrapolation of the last applied state + internal int EndExtrapolationTick; + /// /// This will reset the NetworkTransform BitSet /// @@ -301,6 +306,15 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade /// Whether or not z component of position will be replicated /// public bool SyncPositionZ = true; + + private bool SynchronizePosition + { + get + { + return SyncPositionX || SyncPositionY || SyncPositionZ; + } + } + /// /// Whether or not x component of rotation will be replicated /// @@ -313,6 +327,15 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade /// Whether or not z component of rotation will be replicated /// public bool SyncRotAngleZ = true; + + private bool SynchronizeRotation + { + get + { + return SyncRotAngleX || SyncRotAngleY || SyncRotAngleZ; + } + } + /// /// Whether or not x component of scale will be replicated /// @@ -326,6 +349,15 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade /// public bool SyncScaleZ = true; + + private bool SynchronizeScale + { + get + { + return SyncScaleX || SyncScaleY || SyncScaleZ; + } + } + /// /// The current position threshold value /// Any changes to the position that exceeds the current threshold value will be replicated @@ -347,7 +379,6 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade /// public float ScaleThreshold = ScaleThresholdDefault; - /// /// Sets whether the transform should be treated as local (true) or world (false) space. /// @@ -390,7 +421,6 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade private readonly NetworkVariable m_ReplicatedNetworkStateServer = new NetworkVariable(new NetworkTransformState(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server); private readonly NetworkVariable m_ReplicatedNetworkStateOwner = new NetworkVariable(new NetworkTransformState(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); - internal NetworkVariable ReplicatedNetworkState { get @@ -404,10 +434,10 @@ internal NetworkVariable ReplicatedNetworkState } } + // Used by both authoritative and non-authoritative instances. + // This represents the most recent local authoritative state. private NetworkTransformState m_LocalAuthoritativeNetworkState; - private bool m_HasSentLastValue = false; // used to send one last value, so clients can make the difference between lost replication data (clients extrapolate) and no more data to send. - private ClientRpcParams m_ClientRpcParams = new ClientRpcParams() { Send = new ClientRpcSendParams() }; private List m_ClientIds = new List() { 0 }; @@ -420,7 +450,7 @@ internal NetworkVariable ReplicatedNetworkState private BufferedLinearInterpolator m_ScaleZInterpolator; private readonly List> m_AllFloatInterpolators = new List>(6); - private int m_LastSentTick; + // Used by integration test private NetworkTransformState m_LastSentState; internal NetworkTransformState GetLastSentState() @@ -428,6 +458,19 @@ internal NetworkTransformState GetLastSentState() return m_LastSentState; } + /// + /// Calculated when spawned, this is used to offset a newly received non-authority side state by 1 tick duration + /// in order to end the extrapolation for that state's values. + /// + /// + /// Example: + /// NetworkState-A is received, processed, and measurements added + /// NetworkState-A is duplicated (NetworkState-A-Post) and its sent time is offset by the tick frequency + /// One tick later, NetworkState-A-Post is applied to end that delta's extrapolation. + /// to see how NetworkState-A-Post doesn't get excluded/missed + /// + private double m_TickFrequency; + /// /// This will try to send/commit the current transform delta states (if any) /// @@ -445,19 +488,12 @@ protected void TryCommitTransformToServer(Transform transformToCommit, double di return; } - /// If authority is invoking this, then treat it like we do with + // If we are authority, update the authoritative state if (CanCommitToTransform) { - // If our replicated state is not dirty and our local authority state is dirty, clear it. - if (!ReplicatedNetworkState.IsDirty() && m_LocalAuthoritativeNetworkState.IsDirty) - { - // Now clear our bitset and prepare for next network tick state update - m_LocalAuthoritativeNetworkState.ClearBitSetForNextTick(); - } - - TryCommitTransform(transformToCommit, m_CachedNetworkManager.LocalTime.Time); + UpdateAuthoritativeState(transform); } - else + else // Non-Authority { // We are an owner requesting to update our state if (!m_CachedIsServer) @@ -483,52 +519,46 @@ private void TryCommitTransform(Transform transformToCommit, double dirtyTime) NetworkLog.LogError($"[{name}] is trying to commit the transform without authority!"); return; } - var isDirty = ApplyTransformToNetworkState(ref m_LocalAuthoritativeNetworkState, dirtyTime, transformToCommit); - // if dirty, send - // if not dirty anymore, but hasn't sent last value for limiting extrapolation, still set isDirty - // if not dirty and has already sent last value, don't do anything - // extrapolation works by using last two values. if it doesn't receive anything anymore, it'll continue to extrapolate. - // This is great in case there's message loss, not so great if we just don't have new values to send. - // the following will send one last "copied" value so unclamped interpolation tries to extrapolate between two identical values, effectively - // making it immobile. - if (isDirty) + // If the transform has deltas (returns dirty) then... + if (ApplyTransformToNetworkState(ref m_LocalAuthoritativeNetworkState, dirtyTime, transformToCommit)) { - // Commit the state + // ...commit the state ReplicatedNetworkState.Value = m_LocalAuthoritativeNetworkState; - m_HasSentLastValue = false; - m_LastSentTick = m_CachedNetworkManager.LocalTime.Tick; - m_LastSentState = m_LocalAuthoritativeNetworkState; - } - else if (!m_HasSentLastValue && m_CachedNetworkManager.LocalTime.Tick >= m_LastSentTick + 1) // check for state.IsDirty since update can happen more than once per tick. No need for client, RPCs will just queue up - { - // Since the last m_LocalAuthoritativeNetworkState could have included a IsTeleportingNextFrame - // we need to reset this here so only the deltas are applied and interpolation is not reset again. - m_LastSentState.IsTeleportingNextFrame = false; - m_LastSentState.SentTime = m_CachedNetworkManager.LocalTime.Time; // time 1+ tick later - // Commit the state - ReplicatedNetworkState.Value = m_LastSentState; - m_HasSentLastValue = true; } } private void ResetInterpolatedStateToCurrentAuthoritativeState() { var serverTime = NetworkManager.ServerTime.Time; - try { - m_PositionXInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionX, serverTime); - } catch(Exception e) { - Debug.LogError(gameObject.name + " will crash :("); - throw e; - } - m_PositionYInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionY, serverTime); - m_PositionZInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionZ, serverTime); - - m_RotationInterpolator.ResetTo(Quaternion.Euler(m_LocalAuthoritativeNetworkState.RotAngleX, m_LocalAuthoritativeNetworkState.RotAngleY, m_LocalAuthoritativeNetworkState.RotAngleZ), serverTime); - m_ScaleXInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleX, serverTime); - m_ScaleYInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleY, serverTime); - m_ScaleZInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleZ, serverTime); + // TODO: Look into a better way to communicate the entire state for late joining clients. + // Since the replicated network state will just be the most recent deltas and not the entire state. + //m_PositionXInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionX, serverTime); + //m_PositionYInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionY, serverTime); + //m_PositionZInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionZ, serverTime); + + //m_RotationInterpolator.ResetTo(Quaternion.Euler(m_LocalAuthoritativeNetworkState.RotAngleX, m_LocalAuthoritativeNetworkState.RotAngleY, m_LocalAuthoritativeNetworkState.RotAngleZ), serverTime); + + //m_ScaleXInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleX, serverTime); + //m_ScaleYInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleY, serverTime); + //m_ScaleZInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleZ, serverTime); + + // NOTE ABOUT THIS CHANGE: + // !!! This will exclude any scale changes because we currently do not spawn network objects with scale !!! + // Regarding Scale: It will be the same scale as the default scale for the object being spawned. + var position = InLocalSpace ? transform.localPosition : transform.position; + m_PositionXInterpolator.ResetTo(position.x, serverTime); + m_PositionYInterpolator.ResetTo(position.y, serverTime); + m_PositionZInterpolator.ResetTo(position.z, serverTime); + var rotation = InLocalSpace ? transform.localRotation : transform.rotation; + m_RotationInterpolator.ResetTo(rotation, serverTime); + + // TODO: (Create Jira Ticket) Synchronize local scale during NetworkObject synchronization + // (We will probably want to byte pack TransformData to offset the 3 float addition) + m_ScaleXInterpolator.ResetTo(transform.localScale.x, serverTime); + m_ScaleYInterpolator.ResetTo(transform.localScale.y, serverTime); + m_ScaleZInterpolator.ResetTo(transform.localScale.z, serverTime); } /// @@ -661,104 +691,92 @@ private bool ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState netw private void ApplyAuthoritativeState() { var networkState = ReplicatedNetworkState.Value; - var interpolatedPosition = networkState.InLocalSpace ? transform.localPosition : transform.position; + var adjustedPosition = networkState.InLocalSpace ? transform.localPosition : transform.position; - // todo: we should store network state w/ quats vs. euler angles - var interpolatedRotAngles = networkState.InLocalSpace ? transform.localEulerAngles : transform.eulerAngles; - var interpolatedScale = transform.localScale; - var isTeleporting = networkState.IsTeleportingNextFrame; + // TODO: We should store network state w/ quats vs. euler angles + var adjustedRotAngles = networkState.InLocalSpace ? transform.localEulerAngles : transform.eulerAngles; + var adjustedScale = transform.localScale; // InLocalSpace Read: InLocalSpace = networkState.InLocalSpace; - // Update the position values that were changed in this state update - if (networkState.HasPositionX) - { - interpolatedPosition.x = isTeleporting || !Interpolate ? networkState.PositionX : m_PositionXInterpolator.GetInterpolatedValue(); - } - - if (networkState.HasPositionY) + // NOTE ABOUT INTERPOLATING AND THE CODE BELOW: + // We always apply the interpolated state for any axis we are synchronizing even when the state has no deltas + // to assure we fully interpolate to our target even after we stop extrapolating 1 tick later. + var useInterpolatedValue = !networkState.IsTeleportingNextFrame && Interpolate; + if (useInterpolatedValue) { - interpolatedPosition.y = isTeleporting || !Interpolate ? networkState.PositionY : m_PositionYInterpolator.GetInterpolatedValue(); - } + if (SyncPositionX) { adjustedPosition.x = m_PositionXInterpolator.GetInterpolatedValue(); } + if (SyncPositionY) { adjustedPosition.y = m_PositionYInterpolator.GetInterpolatedValue(); } + if (SyncPositionZ) { adjustedPosition.z = m_PositionZInterpolator.GetInterpolatedValue(); } - if (networkState.HasPositionZ) - { - interpolatedPosition.z = isTeleporting || !Interpolate ? networkState.PositionZ : m_PositionZInterpolator.GetInterpolatedValue(); - } + if (SyncScaleX) { adjustedScale.x = m_ScaleXInterpolator.GetInterpolatedValue(); } + if (SyncScaleY) { adjustedScale.y = m_ScaleYInterpolator.GetInterpolatedValue(); } + if (SyncScaleZ) { adjustedScale.z = m_ScaleZInterpolator.GetInterpolatedValue(); } - // Update the rotation values that were changed in this state update - if (networkState.HasRotAngleChange) - { - var eulerAngles = new Vector3(); - if (Interpolate) + if (SynchronizeRotation) { - eulerAngles = m_RotationInterpolator.GetInterpolatedValue().eulerAngles; - } - - if (networkState.HasRotAngleX) - { - interpolatedRotAngles.x = isTeleporting || !Interpolate ? networkState.RotAngleX : eulerAngles.x; - } - - if (networkState.HasRotAngleY) - { - interpolatedRotAngles.y = isTeleporting || !Interpolate ? networkState.RotAngleY : eulerAngles.y; - } - - if (networkState.HasRotAngleZ) - { - interpolatedRotAngles.z = isTeleporting || !Interpolate ? networkState.RotAngleZ : eulerAngles.z; + var interpolatedEulerAngles = m_RotationInterpolator.GetInterpolatedValue().eulerAngles; + if (SyncRotAngleX) { adjustedRotAngles.x = interpolatedEulerAngles.x; } + if (SyncRotAngleY) { adjustedRotAngles.y = interpolatedEulerAngles.y; } + if (SyncRotAngleZ) { adjustedRotAngles.z = interpolatedEulerAngles.z; } } } - - // Update all scale axis that were changed in this state update - if (networkState.HasScaleX) + else { - interpolatedScale.x = isTeleporting || !Interpolate ? networkState.ScaleX : m_ScaleXInterpolator.GetInterpolatedValue(); - } + if (networkState.HasPositionX) { adjustedPosition.x = networkState.PositionX; } + if (networkState.HasPositionY) { adjustedPosition.y = networkState.PositionY; } + if (networkState.HasPositionZ) { adjustedPosition.z = networkState.PositionZ; } - if (networkState.HasScaleY) - { - interpolatedScale.y = isTeleporting || !Interpolate ? networkState.ScaleY : m_ScaleYInterpolator.GetInterpolatedValue(); - } + if (networkState.HasScaleX) { adjustedScale.x = networkState.ScaleX; } + if (networkState.HasScaleY) { adjustedScale.y = networkState.ScaleY; } + if (networkState.HasScaleZ) { adjustedScale.z = networkState.ScaleZ; } - if (networkState.HasScaleZ) - { - interpolatedScale.z = isTeleporting || !Interpolate ? networkState.ScaleZ : m_ScaleZInterpolator.GetInterpolatedValue(); + if (networkState.HasRotAngleX) { adjustedRotAngles.x = networkState.RotAngleX; } + if (networkState.HasRotAngleY) { adjustedRotAngles.y = networkState.RotAngleY; } + if (networkState.HasRotAngleZ) { adjustedRotAngles.z = networkState.RotAngleZ; } } - // Apply the new position - if (networkState.HasPositionChange) + // NOTE: The below conditional checks for applying axial values are required in order to + // prevent the non-authoritative side from making adjustments when interpolation is off. + + // TODO: Determine if we want to enforce, frame by frame, the non-authoritative transform values. + // We would want save the position, rotation, and scale (each individually) after applying each + // authoritative transform state received. Otherwise, the non-authoritative side could make + // changes to an axial value (if interpolation is turned off) until authority sends an update for + // that same axial value. When interpolation is on, the state's values being synchronized are + // always applied each frame. + + // Apply the new position if it has changed or we are interpolating and synchronizing position + if (networkState.HasPositionChange || (useInterpolatedValue && SynchronizePosition)) { if (InLocalSpace) { - - transform.localPosition = interpolatedPosition; + transform.localPosition = adjustedPosition; } else { - transform.position = interpolatedPosition; + transform.position = adjustedPosition; } } - // Apply the new rotation - if (networkState.HasRotAngleChange) + // Apply the new rotation if it has changed or we are interpolating and synchronizing rotation + if (networkState.HasRotAngleChange || (useInterpolatedValue && SynchronizeRotation)) { if (InLocalSpace) { - transform.localRotation = Quaternion.Euler(interpolatedRotAngles); + transform.localRotation = Quaternion.Euler(adjustedRotAngles); } else { - transform.rotation = Quaternion.Euler(interpolatedRotAngles); + transform.rotation = Quaternion.Euler(adjustedRotAngles); } } - // Apply the new scale - if (networkState.HasScaleChange) + // Apply the new scale if it has changed or we are interpolating and synchronizing scale + if (networkState.HasScaleChange || (useInterpolatedValue && SynchronizeScale)) { - transform.localScale = interpolatedScale; + transform.localScale = adjustedScale; } } @@ -915,6 +933,7 @@ private void AddInterpolatedState(NetworkTransformState newState) } currentRotation.eulerAngles = currentEulerAngles; + m_RotationInterpolator.AddMeasurement(currentRotation, sentTime); } } @@ -937,6 +956,7 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf if (Interpolate) { + // Add measurements for the new state's deltas AddInterpolatedState(newState); } } @@ -959,18 +979,25 @@ public void SetMaxInterpolationBound(float maxInterpolationBound) m_ScaleZInterpolator.MaxInterpolationBound = maxInterpolationBound; } + /// + /// Create interpolators when first instantiated to avoid memory allocations if the + /// associated NetworkObject persists (i.e. despawned but not destroyed or pools) + /// private void Awake() { - // we only want to create our interpolators during Awake so that, when pooled, we do not create tons - // of gc thrash each time objects wink out and are re-used + // Rotation is a single Quaternion since each Euler axis will affect the quaternion's final value + m_RotationInterpolator = new BufferedLinearInterpolatorQuaternion(); + + // All other interpolators are BufferedLinearInterpolatorFloats m_PositionXInterpolator = new BufferedLinearInterpolatorFloat(); m_PositionYInterpolator = new BufferedLinearInterpolatorFloat(); m_PositionZInterpolator = new BufferedLinearInterpolatorFloat(); - m_RotationInterpolator = new BufferedLinearInterpolatorQuaternion(); // rotation is a single Quaternion since each euler axis will affect the quaternion's final value m_ScaleXInterpolator = new BufferedLinearInterpolatorFloat(); m_ScaleYInterpolator = new BufferedLinearInterpolatorFloat(); m_ScaleZInterpolator = new BufferedLinearInterpolatorFloat(); + // Used to quickly iteration over the BufferedLinearInterpolatorFloat + // instances if (m_AllFloatInterpolators.Count == 0) { m_AllFloatInterpolators.Add(m_PositionXInterpolator); @@ -987,6 +1014,7 @@ public override void OnNetworkSpawn() { m_CachedIsServer = IsServer; m_CachedNetworkManager = NetworkManager; + m_TickFrequency = 1.0 / NetworkManager.NetworkConfig.TickRate; Initialize(); @@ -995,8 +1023,10 @@ public override void OnNetworkSpawn() // that can be invoked when ownership changes. if (CanCommitToTransform) { + var currentPosition = InLocalSpace ? transform.localPosition : transform.position; + var currentRotation = InLocalSpace ? transform.localRotation : transform.rotation; // Teleport to current position - SetStateInternal(transform.position, transform.rotation, transform.localScale, true); + SetStateInternal(currentPosition, currentRotation, transform.localScale, true); // Force the state update to be sent TryCommitTransform(transform, m_CachedNetworkManager.LocalTime.Time); @@ -1168,8 +1198,25 @@ private void SetStateServerRpc(Vector3 pos, Quaternion rot, Vector3 scale, bool TryCommitTransform(transform, m_CachedNetworkManager.LocalTime.Time); } - // todo: this is currently in update, to be able to catch any transform changes. A FixedUpdate mode could be added to be less intense, but it'd be - // conditional to users only making transform update changes in FixedUpdate. + /// + /// Will update the authoritative transform state if any deltas are detected. + /// This will also reset the m_LocalAuthoritativeNetworkState if it is still dirty + /// but the replicated network state is not. + /// + /// transform to be updated + private void UpdateAuthoritativeState(Transform transformSource) + { + // If our replicated state is not dirty and our local authority state is dirty, clear it. + if (!ReplicatedNetworkState.IsDirty() && m_LocalAuthoritativeNetworkState.IsDirty) + { + m_LastSentState = m_LocalAuthoritativeNetworkState; + // Now clear our bitset and prepare for next network tick state update + m_LocalAuthoritativeNetworkState.ClearBitSetForNextTick(); + } + + TryCommitTransform(transformSource, m_CachedNetworkManager.LocalTime.Time); + } + /// /// /// If you override this method, be sure that: @@ -1184,21 +1231,15 @@ protected virtual void Update() return; } + // If we are authority, update the authoritative state if (CanCommitToTransform) { - // If our replicated state is not dirty and our local authority state is dirty, clear it. - if (!ReplicatedNetworkState.IsDirty() && m_LocalAuthoritativeNetworkState.IsDirty) - { - // Now clear our bitset and prepare for next network tick state update - m_LocalAuthoritativeNetworkState.ClearBitSetForNextTick(); - } - TryCommitTransform(transform, m_CachedNetworkManager.LocalTime.Time); + UpdateAuthoritativeState(transform); } - else + else // Non-Authority { if (Interpolate) { - // eventually, we could hoist this calculation so that it happens once for all objects, not once per object var serverTime = NetworkManager.ServerTime; var cachedDeltaTime = Time.deltaTime; var cachedServerTime = serverTime.Time; @@ -1210,7 +1251,8 @@ protected virtual void Update() m_RotationInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime); } - // Now apply the current authoritative state + + // Apply the current authoritative state ApplyAuthoritativeState(); } } diff --git a/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs index ee71ad40dc..224a25e0f8 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs @@ -105,6 +105,7 @@ public void OutOfOrderShouldStillWork() Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(2f).Within(k_Precision)); } + [Ignore("TODO: Fix this test to still handle testing message loss without extrapolation")] [Test] public void MessageLoss() { @@ -305,6 +306,7 @@ public void TestUpdatingInterpolatorWithNoData() Assert.Throws(() => interpolator.Update(1f, serverTime)); } + [Ignore("TODO: Fix this test to still test duplicated values without extrapolation")] [Test] public void TestDuplicatedValues() { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index 207b691b75..d88c0ceb73 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Collections.Generic; using Unity.Netcode.Components; using NUnit.Framework; using UnityEngine; @@ -7,6 +8,9 @@ namespace Unity.Netcode.RuntimeTests { + /// + /// Helper component for all NetworkTransformTests + /// public class NetworkTransformTestComponent : NetworkTransform { public bool ServerAuthority; @@ -37,15 +41,52 @@ public void CommitToTransform() } } - [TestFixture(HostOrServer.Host, Authority.Server)] - [TestFixture(HostOrServer.Host, Authority.Owner)] - [TestFixture(HostOrServer.Server, Authority.Server)] - [TestFixture(HostOrServer.Server, Authority.Owner)] + /// + /// Helper component for NetworkTransform parenting tests + /// + public class ChildObjectComponent : NetworkBehaviour + { + public readonly static List Instances = new List(); + public static ChildObjectComponent ServerInstance { get; internal set; } + public readonly static Dictionary ClientInstances = new Dictionary(); + + public static void Reset() + { + ServerInstance = null; + ClientInstances.Clear(); + Instances.Clear(); + } + + public override void OnNetworkSpawn() + { + if (IsServer) + { + ServerInstance = this; + } + else + { + ClientInstances.Add(NetworkManager.LocalClientId, NetworkObject); + } + Instances.Add(this); + base.OnNetworkSpawn(); + } + } + + /// + /// Integration tests for NetworkTransform that will test both + /// server and host operating modes and will test both authoritative + /// models for each operating mode. + /// + [TestFixture(HostOrServer.Host, Authority.ServerAuthority)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority)] + [TestFixture(HostOrServer.Server, Authority.OwnerAuthority)] public class NetworkTransformTests : NetcodeIntegrationTest { private NetworkObject m_AuthoritativePlayer; private NetworkObject m_NonAuthoritativePlayer; + private NetworkObject m_ChildObjectToBeParented; private NetworkTransformTestComponent m_AuthoritativeTransform; private NetworkTransformTestComponent m_NonAuthoritativeTransform; @@ -55,8 +96,8 @@ public class NetworkTransformTests : NetcodeIntegrationTest public enum Authority { - Server, - Owner + ServerAuthority, + OwnerAuthority } public enum Interpolation @@ -78,14 +119,32 @@ public NetworkTransformTests(HostOrServer testWithHost, Authority authority) protected override int NumberOfClients => 1; + protected override IEnumerator OnSetup() + { + ChildObjectComponent.Reset(); + return base.OnSetup(); + } + protected override void OnCreatePlayerPrefab() { var networkTransformTestComponent = m_PlayerPrefab.AddComponent(); - networkTransformTestComponent.ServerAuthority = m_Authority == Authority.Server; + networkTransformTestComponent.ServerAuthority = m_Authority == Authority.ServerAuthority; } protected override void OnServerAndClientsCreated() { + var childObject = CreateNetworkObjectPrefab("ChildObject"); + childObject.AddComponent(); + var childNetworkTransform = childObject.AddComponent(); + childNetworkTransform.InLocalSpace = true; + m_ChildObjectToBeParented = childObject.GetComponent(); + + // Now apply local transform values + m_ChildObjectToBeParented.transform.position = m_ChildObjectLocalPosition; + var childRotation = m_ChildObjectToBeParented.transform.rotation; + childRotation.eulerAngles = m_ChildObjectLocalRotation; + m_ChildObjectToBeParented.transform.rotation = childRotation; + m_ChildObjectToBeParented.transform.localScale = m_ChildObjectLocalScale; if (m_EnableVerboseDebug) { m_ServerNetworkManager.LogLevel = LogLevel.Developer; @@ -102,8 +161,8 @@ protected override IEnumerator OnServerAndClientsConnected() var serverSideClientPlayer = m_ServerNetworkManager.ConnectedClients[m_ClientNetworkManagers[0].LocalClientId].PlayerObject; var clientSideClientPlayer = m_ClientNetworkManagers[0].LocalClient.PlayerObject; - m_AuthoritativePlayer = m_Authority == Authority.Server ? serverSideClientPlayer : clientSideClientPlayer; - m_NonAuthoritativePlayer = m_Authority == Authority.Server ? clientSideClientPlayer : serverSideClientPlayer; + m_AuthoritativePlayer = m_Authority == Authority.ServerAuthority ? serverSideClientPlayer : clientSideClientPlayer; + m_NonAuthoritativePlayer = m_Authority == Authority.ServerAuthority ? clientSideClientPlayer : serverSideClientPlayer; // Get the NetworkTransformTestComponent to make sure the client side is ready before starting test m_AuthoritativeTransform = m_AuthoritativePlayer.GetComponent(); @@ -118,7 +177,6 @@ protected override IEnumerator OnServerAndClientsConnected() Assert.True(m_AuthoritativeTransform.CanCommitToTransform); Assert.False(m_NonAuthoritativeTransform.CanCommitToTransform); - yield return base.OnServerAndClientsConnected(); } @@ -134,6 +192,305 @@ public enum OverrideState CommitToTransform } + /// + /// Returns true when the server-host and all clients have + /// instantiated the child object to be used in + /// + /// + private bool AllChildObjectInstancesAreSpawned() + { + if (ChildObjectComponent.ServerInstance == null) + { + return false; + } + + foreach (var clientNetworkManager in m_ClientNetworkManagers) + { + if (!ChildObjectComponent.ClientInstances.ContainsKey(clientNetworkManager.LocalClientId)) + { + return false; + } + } + return true; + } + + private bool AllChildObjectInstancesHaveChild() + { + foreach (var instance in ChildObjectComponent.ClientInstances.Values) + { + if (instance.transform.parent == null) + { + return false; + } + } + return true; + } + + // To test that local position, rotation, and scale remain the same when parented. + private Vector3 m_ChildObjectLocalPosition = new Vector3(5.0f, 0.0f, -5.0f); + private Vector3 m_ChildObjectLocalRotation = new Vector3(-35.0f, 90.0f, 270.0f); + private Vector3 m_ChildObjectLocalScale = new Vector3(0.1f, 0.5f, 0.4f); + + /// + /// A wait condition specific method that assures the local space coordinates + /// are not impacted by NetworkTransform when parented. + /// + private bool AllInstancesKeptLocalTransformValues() + { + foreach (var childInstance in ChildObjectComponent.Instances) + { + var childLocalPosition = childInstance.transform.localPosition; + var childLocalRotation = childInstance.transform.localRotation.eulerAngles; + var childLocalScale = childInstance.transform.localScale; + + if (!Aproximately(childLocalPosition, m_ChildObjectLocalPosition)) + { + return false; + } + if (!AproximatelyEuler(childLocalRotation, m_ChildObjectLocalRotation)) + { + return false; + } + if (!Aproximately(childLocalScale, m_ChildObjectLocalScale)) + { + return false; + } + } + return true; + } + + /// + /// Handles validating the local space values match the original local space values. + /// If not, it generates a message containing the axial values that did not match + /// the target/start local space values. + /// + private IEnumerator WaitForAllChildrenLocalTransformValuesToMatch() + { + yield return WaitForConditionOrTimeOut(AllInstancesKeptLocalTransformValues); + var infoMessage = string.Empty; + if (s_GlobalTimeoutHelper.TimedOut) + { + foreach (var childInstance in ChildObjectComponent.Instances) + { + var childLocalPosition = childInstance.transform.localPosition; + var childLocalRotation = childInstance.transform.localRotation.eulerAngles; + var childLocalScale = childInstance.transform.localScale; + + if (!Aproximately(childLocalPosition, m_ChildObjectLocalPosition)) + { + infoMessage += $"[{childInstance.name}] Child's Local Position ({childLocalPosition}) | Original Local Position ({m_ChildObjectLocalPosition})\n"; + } + if (!AproximatelyEuler(childLocalRotation, m_ChildObjectLocalRotation)) + { + infoMessage += $"[{childInstance.name}] Child's Local Rotation ({childLocalRotation}) | Original Local Rotation ({m_ChildObjectLocalRotation})\n"; + } + if (!Aproximately(childLocalScale, m_ChildObjectLocalScale)) + { + infoMessage += $"[{childInstance.name}] Child's Local Scale ({childLocalScale}) | Original Local Rotation ({m_ChildObjectLocalScale})\n"; + } + } + AssertOnTimeout($"Timed out waiting for all children to have the correct local space values:\n {infoMessage}"); + } + yield return null; + } + + /// + /// Validates that local space transform values remain the same when a NetworkTransform is + /// parented under another NetworkTransform + /// + [UnityTest] + public IEnumerator NetworkTransformParentedLocalSpaceTest([Values] Interpolation interpolation, [Values] OverrideState overideState) + { + var overrideUpdate = overideState == OverrideState.CommitToTransform; + m_AuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate; + m_NonAuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate; + var authoritativeChildObject = SpawnObject(m_ChildObjectToBeParented.gameObject, m_AuthoritativeTransform.NetworkManager); + + // Assure all of the child object instances are spawned + yield return WaitForConditionOrTimeOut(AllChildObjectInstancesAreSpawned); + AssertOnTimeout("Timed out waiting for all child instances to be spawned!"); + // Just a sanity check as it should have timed out before this check + Assert.IsNotNull(ChildObjectComponent.ServerInstance, $"The server-side {nameof(ChildObjectComponent)} instance is null!"); + + // This determines which parent on the server side should be the parent + if (m_AuthoritativeTransform.IsServerAuthoritative()) + { + Assert.True(ChildObjectComponent.ServerInstance.NetworkObject.TrySetParent(m_AuthoritativeTransform.transform, false), "[Authoritative] Failed to parent the child object!"); + } + else + { + Assert.True(ChildObjectComponent.ServerInstance.NetworkObject.TrySetParent(m_NonAuthoritativeTransform.transform, false), "[Non-Authoritative] Failed to parent the child object!"); + } + + // This waits for all child instances to be parented + yield return WaitForConditionOrTimeOut(AllChildObjectInstancesHaveChild); + AssertOnTimeout("Timed out waiting for all instances to have parented a child!"); + + // This validates each child instance has preserved their local space values + yield return WaitForAllChildrenLocalTransformValuesToMatch(); + } + + /// + /// Validates that moving, rotating, and scaling the authority side with a single + /// tick will properly synchronize the non-authoritative side with the same values. + /// + private IEnumerator MoveRotateAndScaleAuthority(Vector3 position, Vector3 rotation, Vector3 scale) + { + m_AuthoritativeTransform.transform.position = position; + yield return null; + var authoritativeRotation = m_AuthoritativeTransform.transform.rotation; + authoritativeRotation.eulerAngles = rotation; + m_AuthoritativeTransform.transform.rotation = authoritativeRotation; + yield return null; + m_AuthoritativeTransform.transform.localScale = scale; + } + + /// + /// Validates we don't extrapolate beyond the target value + /// + /// + /// This will first wait for any authoritative changes to have been synchronized + /// with the non-authoritative side. It will then wait for the specified number + /// of tick periods to assure the values don't change + /// + private IEnumerator WaitForPositionRotationAndScaleToMatch(int ticksToWait) + { + // Validate we interpolate to the appropriate position and rotation + yield return WaitForConditionOrTimeOut(PositionRotationScaleMatches); + AssertOnTimeout("Timed out waiting for non-authority to match authority's position or rotation"); + + // Wait for the specified number of ticks + for (int i = 0; i < ticksToWait; i++) + { + yield return s_DefaultWaitForTick; + } + + // Verify both sides match (i.e. no drifting or over-extrapolating) + Assert.IsTrue(PositionsMatch(), $"Non-authority position did not match after waiting for {ticksToWait} ticks! " + + $"Authority ({m_AuthoritativeTransform.transform.position}) Non-Authority ({m_NonAuthoritativeTransform.transform.position})"); + Assert.IsTrue(RotationsMatch(), $"Non-authority rotation did not match after waiting for {ticksToWait} ticks! " + + $"Authority ({m_AuthoritativeTransform.transform.rotation.eulerAngles}) Non-Authority ({m_NonAuthoritativeTransform.transform.rotation.eulerAngles})"); + } + + /// + /// Waits until the next tick + /// + private IEnumerator WaitForNextTick() + { + var currentTick = m_AuthoritativeTransform.NetworkManager.LocalTime.Tick; + while (m_AuthoritativeTransform.NetworkManager.LocalTime.Tick == currentTick) + { + yield return null; + } + } + + // The number of iterations to change position, rotation, and scale for NetworkTransformMultipleChangesOverTime + private const int k_PositionRotationScaleIterations = 8; + + protected override void OnNewClientCreated(NetworkManager networkManager) + { + networkManager.NetworkConfig.NetworkPrefabs = m_ServerNetworkManager.NetworkConfig.NetworkPrefabs; + base.OnNewClientCreated(networkManager); + } + + /// + /// This validates that multiple changes can occur within the same tick or over + /// several ticks while still keeping non-authoritative instances synchronized. + /// + [UnityTest] + public IEnumerator NetworkTransformMultipleChangesOverTime([Values] TransformSpace testLocalTransform, [Values] OverrideState overideState) + { + var overrideUpdate = overideState == OverrideState.CommitToTransform; + m_AuthoritativeTransform.InLocalSpace = testLocalTransform == TransformSpace.Local; + + var positionStart = new Vector3(1.0f, 0.5f, 2.0f); + var rotationStart = new Vector3(0.0f, 45.0f, 0.0f); + var scaleStart = new Vector3(1.0f, 1.0f, 1.0f); + var position = positionStart; + var rotation = rotationStart; + var scale = scaleStart; + + // Move and rotate within the same tick, validate the non-authoritative instance updates + // to each set of changes. Repeat several times. + for (int i = 1; i < k_PositionRotationScaleIterations + 1; i++) + { + position = positionStart * i; + rotation = rotationStart * i; + scale = scaleStart * i; + // Wait for tick to change so we cam start close to the beginning the next tick in order + // to apply both deltas within the same tick period. + yield return WaitForNextTick(); + + // Apply deltas + MoveRotateAndScaleAuthority(position, rotation, scale); + + // Wait for deltas to synchronize on non-authoritative side + yield return WaitForPositionRotationAndScaleToMatch(4); + } + + // Check scale for all player instances when a client late joins + // NOTE: This validates the use of the spawned object's transform values as opposed to the replicated state (which now is only the last deltas) + yield return CreateAndStartNewClient(); + var newClientNetworkManager = m_ClientNetworkManagers[NumberOfClients]; + foreach (var playerRelativeEntry in m_PlayerNetworkObjects) + { + foreach (var playerInstanceEntry in playerRelativeEntry.Value) + { + var playerInstance = playerInstanceEntry.Value; + if (newClientNetworkManager.LocalClientId == playerInstance.OwnerClientId) + { + Assert.IsTrue(Aproximately(m_PlayerPrefab.transform.localScale, playerInstance.transform.localScale), $"{playerInstance.name}'s cloned instance's scale does not match original scale!\n" + + $"[ClientId-{playerRelativeEntry.Key} Relative] Player-{playerInstance.OwnerClientId}'s LocalScale ({playerInstance.transform.localScale}) vs Target Scale ({m_PlayerPrefab.transform.localScale})"); + } + } + } + + // Repeat this in the opposite direction + for (int i = -1; i > -1 * (k_PositionRotationScaleIterations + 1); i--) + { + position = positionStart * i; + rotation = rotationStart * i; + scale = scaleStart * i; + // Wait for tick to change so we cam start close to the beginning the next tick in order + // to apply both deltas within the same tick period. + yield return WaitForNextTick(); + + MoveRotateAndScaleAuthority(position, rotation, scale); + yield return WaitForPositionRotationAndScaleToMatch(4); + } + + // Wait for tick to change so we cam start close to the beginning the next tick in order + // to apply as many deltas within the same tick period as we can (if not all) + yield return WaitForNextTick(); + + // Move and rotate within the same tick several times, then validate the non-authoritative + // instance updates to the authoritative instance's final position and rotation. + for (int i = 1; i < k_PositionRotationScaleIterations + 1; i++) + { + position = positionStart * i; + rotation = rotationStart * i; + scale = scaleStart * i; + + MoveRotateAndScaleAuthority(position, rotation, scale); + } + + yield return WaitForPositionRotationAndScaleToMatch(1); + + // Wait for tick to change so we cam start close to the beginning the next tick in order + // to apply as many deltas within the same tick period as we can (if not all) + yield return WaitForNextTick(); + + // Repeat this in the opposite direction and rotation + for (int i = -1; i > -1 * (k_PositionRotationScaleIterations + 1); i--) + { + position = positionStart * i; + rotation = rotationStart * i; + scale = scaleStart * i; + MoveRotateAndScaleAuthority(position, rotation, scale); + } + yield return WaitForPositionRotationAndScaleToMatch(1); + } + /// /// Tests changing all axial values one at a time. /// These tests are performed: @@ -419,6 +776,13 @@ private bool Aproximately(Vector3 a, Vector3 b) Mathf.Abs(a.z - b.z) <= k_AproximateDeltaVariance; } + private bool AproximatelyEuler(Vector3 a, Vector3 b) + { + return Mathf.DeltaAngle(a.x, b.x) <= k_AproximateDeltaVariance && + Mathf.DeltaAngle(a.y, b.y) <= k_AproximateDeltaVariance && + Mathf.DeltaAngle(a.z, b.z) <= k_AproximateDeltaVariance; + } + private const float k_AproximateDeltaVariance = 0.01f; private bool PositionsMatchesValue(Vector3 positionToMatch) { @@ -504,6 +868,11 @@ private bool PositionRotationScaleMatches(Vector3 position, Vector3 eulerRotatio return PositionsMatchesValue(position) && RotationMatchesValue(eulerRotation) && ScaleMatchesValue(scale); } + private bool PositionRotationScaleMatches() + { + return RotationsMatch() && PositionsMatch() && ScaleValuesMatch(); + } + private bool RotationsMatch() { var authorityEulerRotation = m_AuthoritativeTransform.transform.rotation.eulerAngles; From 3662aafc224a561f1d9d4992179b4636e5a84589 Mon Sep 17 00:00:00 2001 From: Kitty Draper <284434+ShadauxCat@users.noreply.github.com> Date: Thu, 8 Sep 2022 15:57:49 -0500 Subject: [PATCH 053/138] fix: Fixed ConnectionApprovalTimeout not working on the client side [MTT-3451] (#2164) * fix: Fixed ConnectionApprovalTimeout not working on the client side * Changelog * standards --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Core/NetworkManager.cs | 55 +++++-- .../Runtime/NetcodeIntegrationTest.cs | 6 +- .../TestHelpers/Runtime/NetcodeLogAssert.cs | 152 ++++++++++++++++++ .../Runtime/NetcodeLogAssert.cs.meta | 3 + .../Runtime/ConnectionApprovalTimeoutTests.cs | 104 ++++++++++++ .../ConnectionApprovalTimeoutTests.cs.meta | 3 + .../Tests/Runtime/DeferredMessagingTests.cs | 90 +---------- .../Tests/Runtime/TestHelpers.meta | 3 + .../Runtime/TestHelpers/MessageCatcher.cs | 101 ++++++++++++ .../TestHelpers/MessageCatcher.cs.meta | 3 + .../Runtime/TestHelpers/MessageLogger.cs | 69 ++++++++ .../Runtime/TestHelpers/MessageLogger.cs.meta | 3 + 13 files changed, 495 insertions(+), 98 deletions(-) create mode 100644 com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeLogAssert.cs create mode 100644 com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeLogAssert.cs.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/MessageCatcher.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/MessageCatcher.cs.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/MessageLogger.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/MessageLogger.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 68e31dae3d..682e8802d4 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -17,6 +17,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed issue where `NetworkTransform` was not honoring the InLocalSpace property on the authority side during OnNetworkSpawn. (#2170) - Implicit conversion of NetworkObjectReference to GameObject will now return null instead of throwing an exception if the referenced object could not be found (i.e., was already despawned) (#2158) - Fixed warning resulting from a stray NetworkAnimator.meta file (#2153) +- Fixed Connection Approval Timeout not working client side. (#2164) - Fixed ClientRpcs always reporting in the profiler view as going to all clients, even when limited to a subset of clients by `ClientRpcParams`. (#2144) - Fixed RPC codegen failing to choose the correct extension methods for `FastBufferReader` and `FastBufferWriter` when the parameters were a generic type (i.e., List) and extensions for multiple instantiations of that type have been defined (i.e., List and List) (#2142) - Fixed throwing an exception in `OnNetworkUpdate` causing other `OnNetworkUpdate` calls to not be executed. (#1739) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 28c35b65a1..f481f16a2d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1630,23 +1630,56 @@ private void SendConnectionRequest() private IEnumerator ApprovalTimeout(ulong clientId) { - NetworkTime timeStarted = LocalTime; - - //We yield every frame incase a pending client disconnects and someone else gets its connection id - while ((LocalTime - timeStarted).Time < NetworkConfig.ClientConnectionBufferTimeout && PendingClients.ContainsKey(clientId)) + if (IsServer) { - yield return null; - } + NetworkTime timeStarted = LocalTime; + + //We yield every frame incase a pending client disconnects and someone else gets its connection id + while (IsListening && (LocalTime - timeStarted).Time < NetworkConfig.ClientConnectionBufferTimeout && PendingClients.ContainsKey(clientId)) + { + yield return null; + } + + if (!IsListening) + { + yield break; + } + + if (PendingClients.ContainsKey(clientId) && !ConnectedClients.ContainsKey(clientId)) + { + // Timeout + if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) + { + NetworkLog.LogInfo($"Client {clientId} Handshake Timed Out"); + } - if (PendingClients.ContainsKey(clientId) && !ConnectedClients.ContainsKey(clientId)) + DisconnectClient(clientId); + } + } + else { - // Timeout - if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) + float timeStarted = Time.realtimeSinceStartup; + + //We yield every frame incase a pending client disconnects and someone else gets its connection id + while (IsListening && (Time.realtimeSinceStartup - timeStarted) < NetworkConfig.ClientConnectionBufferTimeout && !IsConnectedClient) + { + yield return null; + } + + if (!IsListening) { - NetworkLog.LogInfo($"Client {clientId} Handshake Timed Out"); + yield break; } - DisconnectClient(clientId); + if (!IsConnectedClient) + { + // Timeout + if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) + { + NetworkLog.LogInfo("Server Handshake Timed Out"); + } + Shutdown(true); + } } } diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index 8ab56179ee..86de88176d 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -7,7 +7,7 @@ using UnityEngine.SceneManagement; using UnityEngine.TestTools; using System.Runtime.CompilerServices; - +using Unity.Netcode.RuntimeTests; using Object = UnityEngine.Object; namespace Unity.Netcode.TestHelpers.Runtime @@ -25,6 +25,8 @@ public abstract class NetcodeIntegrationTest protected static TimeoutHelper s_GlobalTimeoutHelper = new TimeoutHelper(8.0f); protected static WaitForSeconds s_DefaultWaitForTick = new WaitForSeconds(1.0f / k_DefaultTickRate); + public NetcodeLogAssert NetcodeLogAssert; + /// /// Registered list of all NetworkObjects spawned. /// Format is as follows: @@ -207,6 +209,7 @@ public IEnumerator SetUp() { VerboseDebug($"Entering {nameof(SetUp)}"); + NetcodeLogAssert = new NetcodeLogAssert(); yield return OnSetup(); if (m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.AllTests && m_ServerNetworkManager == null || m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.PerTest) @@ -596,6 +599,7 @@ public IEnumerator TearDown() } VerboseDebug($"Exiting {nameof(TearDown)}"); + NetcodeLogAssert.Dispose(); } /// diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeLogAssert.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeLogAssert.cs new file mode 100644 index 0000000000..d6e4136bc9 --- /dev/null +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeLogAssert.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using NUnit.Framework; +using UnityEngine; + +namespace Unity.Netcode.RuntimeTests +{ + public class NetcodeLogAssert + { + private struct LogData + { + public LogType LogType; + public string Message; + public string StackTrace; + } + + private readonly object m_Lock = new object(); + private bool m_Disposed; + + private List AllLogs { get; } + + public NetcodeLogAssert() + { + AllLogs = new List(); + Activate(); + } + + private void Activate() + { + Application.logMessageReceivedThreaded += AddLog; + } + + private void Deactivate() + { + Application.logMessageReceivedThreaded -= AddLog; + } + + public void AddLog(string message, string stacktrace, LogType type) + { + lock (m_Lock) + { + var log = new LogData + { + LogType = type, + Message = message, + StackTrace = stacktrace, + }; + + AllLogs.Add(log); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (m_Disposed) + { + return; + } + + m_Disposed = true; + + if (disposing) + { + Deactivate(); + } + } + + public void LogWasNotReceived(LogType type, string message) + { + lock (m_Lock) + { + foreach (var logEvent in AllLogs) + { + if (logEvent.LogType == type && message.Equals(logEvent.Message)) + { + Assert.Fail($"Unexpected log: [{logEvent.LogType}] {logEvent.Message}"); + } + } + } + } + + public void LogWasNotReceived(LogType type, Regex messageRegex) + { + lock (m_Lock) + { + foreach (var logEvent in AllLogs) + { + if (logEvent.LogType == type && messageRegex.IsMatch(logEvent.Message)) + { + Assert.Fail($"Unexpected log: [{logEvent.LogType}] {logEvent.Message}"); + } + } + } + } + + public void LogWasReceived(LogType type, string message) + { + lock (m_Lock) + { + var found = false; + foreach (var logEvent in AllLogs) + { + if (logEvent.LogType == type && message.Equals(logEvent.Message)) + { + found = true; + break; + } + } + + if (!found) + { + Assert.Fail($"Expected log was not received: [{type}] {message}"); + } + } + } + + public void LogWasReceived(LogType type, Regex messageRegex) + { + lock (m_Lock) + { + var found = false; + foreach (var logEvent in AllLogs) + { + if (logEvent.LogType == type && messageRegex.IsMatch(logEvent.Message)) + { + found = true; + } + } + + if (!found) + { + Assert.Fail($"Expected log was not received: [{type}] {messageRegex}"); + } + } + } + + public void Reset() + { + lock (m_Lock) + { + AllLogs.Clear(); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeLogAssert.cs.meta b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeLogAssert.cs.meta new file mode 100644 index 0000000000..af8692842d --- /dev/null +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeLogAssert.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 61774c54cd14423ca4de6d56c9fd0fe8 +timeCreated: 1661800793 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs new file mode 100644 index 0000000000..bca5bd8271 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs @@ -0,0 +1,104 @@ +using System.Collections; +using System.Linq; +using System.Text.RegularExpressions; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + [TestFixture(true)] + [TestFixture(false)] + public class ConnectionApprovalTimeoutTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 1; + + protected override bool CanStartServerAndClients() => false; + + private bool m_UseSceneManagement; + public ConnectionApprovalTimeoutTests(bool useSceneManagement) + { + m_UseSceneManagement = useSceneManagement; + } + + // Must be >= 2 since this is an int value and the test waits for timeout - 1 to try to verify it doesn't + // time out early + private const int k_TestTimeoutPeriod = 2; + + private void Start() + { + m_ServerNetworkManager.NetworkConfig.EnableSceneManagement = m_UseSceneManagement; + m_ClientNetworkManagers[0].NetworkConfig.EnableSceneManagement = m_UseSceneManagement; + if (!NetcodeIntegrationTestHelpers.Start(false, m_ServerNetworkManager, m_ClientNetworkManagers)) + { + Debug.LogError("Failed to start instances"); + Assert.Fail("Failed to start instances"); + } + } + + [UnityTest] + public IEnumerator WhenClientDoesntRequestApproval_ServerTimesOut() + { + Start(); + var hook = new MessageCatcher(m_ServerNetworkManager); + m_ServerNetworkManager.MessagingSystem.Hook(hook); ; + + m_ServerNetworkManager.NetworkConfig.ClientConnectionBufferTimeout = k_TestTimeoutPeriod; + m_ServerNetworkManager.LogLevel = LogLevel.Developer; + m_ClientNetworkManagers[0].LogLevel = LogLevel.Developer; + + yield return new WaitForSeconds(m_ServerNetworkManager.NetworkConfig.ClientConnectionBufferTimeout - 1); + + Assert.AreEqual(0, m_ServerNetworkManager.ConnectedClients.Count); + Assert.AreEqual(1, m_ServerNetworkManager.PendingClients.Count); + + var expectedLogMessage = new Regex($"Client {m_ServerNetworkManager.PendingClients.FirstOrDefault().Key} Handshake Timed Out"); + + NetcodeLogAssert.LogWasNotReceived(LogType.Log, expectedLogMessage); + + yield return new WaitForSeconds(2); + + NetcodeLogAssert.LogWasReceived(LogType.Log, expectedLogMessage); + + Assert.AreEqual(0, m_ServerNetworkManager.ConnectedClients.Count); + Assert.AreEqual(0, m_ServerNetworkManager.PendingClients.Count); + } + + [UnityTest] + public IEnumerator WhenServerDoesntRespondWithApproval_ClientTimesOut() + { + Start(); + + if (m_UseSceneManagement) + { + var sceneEventHook = new MessageCatcher(m_ClientNetworkManagers[0]); + m_ClientNetworkManagers[0].MessagingSystem.Hook(sceneEventHook); + } + else + { + var approvalHook = new MessageCatcher(m_ClientNetworkManagers[0]); + m_ClientNetworkManagers[0].MessagingSystem.Hook(approvalHook); + } + + m_ClientNetworkManagers[0].NetworkConfig.ClientConnectionBufferTimeout = k_TestTimeoutPeriod; + m_ServerNetworkManager.LogLevel = LogLevel.Developer; + m_ClientNetworkManagers[0].LogLevel = LogLevel.Developer; + + yield return new WaitForSeconds(m_ClientNetworkManagers[0].NetworkConfig.ClientConnectionBufferTimeout - 1); + + Assert.IsFalse(m_ClientNetworkManagers[0].IsConnectedClient); + Assert.IsTrue(m_ClientNetworkManagers[0].IsListening); + + var expectedLogMessage = new Regex("Server Handshake Timed Out"); + NetcodeLogAssert.LogWasNotReceived(LogType.Log, expectedLogMessage); + + yield return new WaitForSeconds(2); + + NetcodeLogAssert.LogWasReceived(LogType.Log, expectedLogMessage); + + Assert.IsFalse(m_ClientNetworkManagers[0].IsConnectedClient); + Assert.IsFalse(m_ClientNetworkManagers[0].IsListening); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs.meta new file mode 100644 index 0000000000..34b8e91413 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b0c4159ea234415fa9497860e6ef4fc2 +timeCreated: 1661796642 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs index 298f5f273c..198802d022 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs @@ -1,8 +1,6 @@ -using System; using System.Collections; using System.Collections.Generic; using NUnit.Framework; -using Unity.Collections; using UnityEngine; using UnityEngine.TestTools; using Unity.Netcode.TestHelpers.Runtime; @@ -113,82 +111,6 @@ public override void CleanupStaleTriggers() } } - internal class SpawnCatcher : INetworkHooks - { - public struct TriggerData - { - public FastBufferReader Reader; - public MessageHeader Header; - public ulong SenderId; - public float Timestamp; - public int SerializedHeaderSize; - } - public readonly List CaughtMessages = new List(); - - public void OnBeforeSendMessage(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage - { - } - - public void OnAfterSendMessage(ulong clientId, ref T message, NetworkDelivery delivery, int messageSizeBytes) where T : INetworkMessage - { - } - - public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) - { - } - - public void OnAfterReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) - { - } - - public void OnBeforeSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery) - { - } - - public void OnAfterSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery) - { - } - - public void OnBeforeReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes) - { - } - - public void OnAfterReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes) - { - } - - public bool OnVerifyCanSend(ulong destinationId, Type messageType, NetworkDelivery delivery) - { - return true; - } - - public bool OnVerifyCanReceive(ulong senderId, Type messageType, FastBufferReader messageContent, ref NetworkContext context) - { - if (messageType == typeof(CreateObjectMessage)) - { - CaughtMessages.Add(new TriggerData - { - Reader = new FastBufferReader(messageContent, Allocator.Persistent), - Header = context.Header, - Timestamp = context.Timestamp, - SenderId = context.SenderId, - SerializedHeaderSize = context.SerializedHeaderSize - }); - return false; - } - - return true; - } - - public void OnBeforeHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage - { - } - - public void OnAfterHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage - { - } - } - public class DeferredMessageTestRpcComponent : NetworkBehaviour { public bool ClientRpcCalled; @@ -262,7 +184,7 @@ public class DeferredMessagingTest : NetcodeIntegrationTest { protected override int NumberOfClients => 0; - private List m_ClientSpawnCatchers = new List(); + private List> m_ClientSpawnCatchers = new List>(); private GameObject m_RpcPrefab; private GameObject m_NetworkVariablePrefab; @@ -362,7 +284,7 @@ private void CatchSpawns() { foreach (var client in m_ClientNetworkManagers) { - var catcher = new SpawnCatcher(); + var catcher = new MessageCatcher(client); m_ClientSpawnCatchers.Add(catcher); client.MessagingSystem.Hook(catcher); } @@ -374,11 +296,7 @@ private void ReleaseSpawns() { // Unhook first so the spawn catcher stops catching spawns m_ClientNetworkManagers[i].MessagingSystem.Unhook(m_ClientSpawnCatchers[i]); - foreach (var caughtSpawn in m_ClientSpawnCatchers[i].CaughtMessages) - { - // Reader will be disposed within HandleMessage - m_ClientNetworkManagers[i].MessagingSystem.HandleMessage(caughtSpawn.Header, caughtSpawn.Reader, caughtSpawn.SenderId, caughtSpawn.Timestamp, caughtSpawn.SerializedHeaderSize); - } + m_ClientSpawnCatchers[i].ReleaseMessages(); } m_ClientSpawnCatchers.Clear(); } @@ -396,7 +314,7 @@ private IEnumerator WaitForClientsToCatchSpawns(int count = 1) { foreach (var catcher in m_ClientSpawnCatchers) { - if (catcher.CaughtMessages.Count != count) + if (catcher.CaughtMessageCount != count) { return false; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers.meta b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers.meta new file mode 100644 index 0000000000..ce3ac002e3 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1f398e1797944b5db4d3aa473629f46e +timeCreated: 1661800773 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/MessageCatcher.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/MessageCatcher.cs new file mode 100644 index 0000000000..044818541f --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/MessageCatcher.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using Unity.Collections; + +namespace Unity.Netcode.RuntimeTests +{ + internal class MessageCatcher : INetworkHooks where TMessageType : INetworkMessage + { + private NetworkManager m_OwnerNetworkManager; + + public MessageCatcher(NetworkManager ownerNetworkManager) + { + m_OwnerNetworkManager = ownerNetworkManager; + } + + private struct TriggerData + { + public FastBufferReader Reader; + public MessageHeader Header; + public ulong SenderId; + public float Timestamp; + public int SerializedHeaderSize; + } + private readonly List m_CaughtMessages = new List(); + + public void ReleaseMessages() + { + + foreach (var caughtSpawn in m_CaughtMessages) + { + // Reader will be disposed within HandleMessage + m_OwnerNetworkManager.MessagingSystem.HandleMessage(caughtSpawn.Header, caughtSpawn.Reader, caughtSpawn.SenderId, caughtSpawn.Timestamp, caughtSpawn.SerializedHeaderSize); + } + } + + public int CaughtMessageCount => m_CaughtMessages.Count; + + public void OnBeforeSendMessage(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage + { + } + + public void OnAfterSendMessage(ulong clientId, ref T message, NetworkDelivery delivery, int messageSizeBytes) where T : INetworkMessage + { + } + + public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) + { + } + + public void OnAfterReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) + { + } + + public void OnBeforeSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery) + { + } + + public void OnAfterSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery) + { + } + + public void OnBeforeReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes) + { + } + + public void OnAfterReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes) + { + } + + public bool OnVerifyCanSend(ulong destinationId, Type messageType, NetworkDelivery delivery) + { + return true; + } + + public bool OnVerifyCanReceive(ulong senderId, Type messageType, FastBufferReader messageContent, ref NetworkContext context) + { + if (messageType == typeof(TMessageType)) + { + m_CaughtMessages.Add(new TriggerData + { + Reader = new FastBufferReader(messageContent, Allocator.Persistent), + Header = context.Header, + Timestamp = context.Timestamp, + SenderId = context.SenderId, + SerializedHeaderSize = context.SerializedHeaderSize + }); + return false; + } + + return true; + } + + public void OnBeforeHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage + { + } + + public void OnAfterHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage + { + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/MessageCatcher.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/MessageCatcher.cs.meta new file mode 100644 index 0000000000..93a0c6f408 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/MessageCatcher.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f008d074bcb841ae90b1949f9e2f0854 +timeCreated: 1661796973 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/MessageLogger.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/MessageLogger.cs new file mode 100644 index 0000000000..3ed7ac35ff --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/MessageLogger.cs @@ -0,0 +1,69 @@ +using System; +using UnityEngine; + +namespace Unity.Netcode.RuntimeTests +{ + internal class MessageLogger : INetworkHooks + { + private NetworkManager m_OwningNetworkManager; + public MessageLogger(NetworkManager owningNetworkManager) + { + m_OwningNetworkManager = owningNetworkManager; + } + + public void OnBeforeSendMessage(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage + { + Debug.Log($"{(m_OwningNetworkManager.IsServer ? "Server" : "Client")} {m_OwningNetworkManager.LocalClientId}: Sending {message.GetType().FullName} to {clientId} with {delivery}"); + } + + public void OnAfterSendMessage(ulong clientId, ref T message, NetworkDelivery delivery, int messageSizeBytes) where T : INetworkMessage + { + } + + public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) + { + Debug.Log($"{(m_OwningNetworkManager.IsServer ? "Server" : "Client")} {m_OwningNetworkManager.LocalClientId}: Receiving {messageType.FullName} from {senderId}"); + } + + public void OnAfterReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) + { + } + + public void OnBeforeSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery) + { + Debug.Log($"{(m_OwningNetworkManager.IsServer ? "Server" : "Client")} {m_OwningNetworkManager.LocalClientId}: Sending a batch of to {clientId}: {messageCount} messages, {batchSizeInBytes} bytes, with {delivery}"); + } + + public void OnAfterSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery) + { + } + + public void OnBeforeReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes) + { + Debug.Log($"{(m_OwningNetworkManager.IsServer ? "Server" : "Client")} {m_OwningNetworkManager.LocalClientId}: Received a batch from {senderId}, {messageCount} messages, {batchSizeInBytes} bytes"); + } + + public void OnAfterReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes) + { + } + + public bool OnVerifyCanSend(ulong destinationId, Type messageType, NetworkDelivery delivery) + { + return true; + } + + public bool OnVerifyCanReceive(ulong senderId, Type messageType, FastBufferReader messageContent, ref NetworkContext context) + { + return true; + } + + public void OnBeforeHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage + { + Debug.Log($"{(m_OwningNetworkManager.IsServer ? "Server" : "Client")} {m_OwningNetworkManager.LocalClientId}: Handling message {message.GetType().FullName}"); + } + + public void OnAfterHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage + { + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/MessageLogger.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/MessageLogger.cs.meta new file mode 100644 index 0000000000..290d14939e --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/MessageLogger.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4dbf404fdf544a409bf1bcab2c3f8b3e +timeCreated: 1661799489 \ No newline at end of file From 638ef30f8dda3ab3b11f2f4bddfc9bcbe576094f Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Fri, 9 Sep 2022 14:20:06 -0400 Subject: [PATCH 054/138] fix: Removing objects that got deleted from list of Dirty NetworkObjects (#2185) --- .../Runtime/Core/NetworkBehaviourUpdater.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs index b75375e1ee..daf3d95479 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs @@ -26,6 +26,10 @@ internal void NetworkBehaviourUpdate(NetworkManager networkManager) #endif try { + // NetworkObject references can become null, when hidden or despawned. Once NUll, there is no point + // trying to process them, even if they were previously marked as dirty. + m_DirtyNetworkObjects.RemoveWhere((sobj) => sobj == null); + if (networkManager.IsServer) { foreach (var dirtyObj in m_DirtyNetworkObjects) From 4c7c762443697db6472bf46b6cfe8366f310516f Mon Sep 17 00:00:00 2001 From: ashwini <36935028+ashwinimurt@users.noreply.github.com> Date: Mon, 12 Sep 2022 15:49:48 -0700 Subject: [PATCH 055/138] =?UTF-8?q?fix:=20Update=20Testproject=20Addressab?= =?UTF-8?q?les=20bundle=20name=20to=20use=20hash=20of=20file=20=E2=80=A6?= =?UTF-8?q?=20(#2198)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: Update Testproject Addressables bundle name to use hash of file name --- .../AddressableAssetsData/AddressableAssetSettings.asset | 3 +++ .../Default Local Group_BundledAssetGroupSchema.asset | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/testproject/Assets/AddressableAssetsData/AddressableAssetSettings.asset b/testproject/Assets/AddressableAssetsData/AddressableAssetSettings.asset index 5a54d802ca..87c46389ef 100644 --- a/testproject/Assets/AddressableAssetsData/AddressableAssetSettings.asset +++ b/testproject/Assets/AddressableAssetsData/AddressableAssetSettings.asset @@ -32,11 +32,14 @@ MonoBehaviour: m_ShaderBundleNaming: 0 m_ShaderBundleCustomNaming: m_MonoScriptBundleNaming: 0 + m_CheckForContentUpdateRestrictionsOption: 0 m_MonoScriptBundleCustomNaming: m_RemoteCatalogBuildPath: m_Id: eaae5cc67c56cfe4299363a742a284b3 m_RemoteCatalogLoadPath: m_Id: 2a3d80e942fdbfe49a979d4a22bbe893 + m_ContentStateBuildPathProfileVariableName: + m_CustomContentStateBuildPath: m_ContentStateBuildPath: m_BuildAddressablesWithPlayerBuild: 2 m_overridePlayerVersion: diff --git a/testproject/Assets/AddressableAssetsData/AssetGroups/Schemas/Default Local Group_BundledAssetGroupSchema.asset b/testproject/Assets/AddressableAssetsData/AssetGroups/Schemas/Default Local Group_BundledAssetGroupSchema.asset index 07d0ea5074..953b41bbf4 100644 --- a/testproject/Assets/AddressableAssetsData/AssetGroups/Schemas/Default Local Group_BundledAssetGroupSchema.asset +++ b/testproject/Assets/AddressableAssetsData/AssetGroups/Schemas/Default Local Group_BundledAssetGroupSchema.asset @@ -13,11 +13,13 @@ MonoBehaviour: m_Name: Default Local Group_BundledAssetGroupSchema m_EditorClassIdentifier: m_Group: {fileID: 11400000, guid: cb34f2b4f49b0a64aa04c7c5bcb3e41e, type: 2} + m_InternalBundleIdMode: 1 m_Compression: 1 m_IncludeAddressInCatalog: 1 m_IncludeGUIDInCatalog: 1 m_IncludeLabelsInCatalog: 1 m_InternalIdNamingMode: 0 + m_CacheClearBehavior: 0 m_IncludeInBuild: 1 m_BundledAssetProviderType: m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null @@ -26,6 +28,7 @@ MonoBehaviour: m_UseAssetBundleCache: 1 m_UseAssetBundleCrc: 1 m_UseAssetBundleCrcForCachedBundles: 1 + m_UseUWRForLocalBundles: 0 m_Timeout: 0 m_ChunkedTransfer: 0 m_RedirectLimit: -1 @@ -38,4 +41,5 @@ MonoBehaviour: m_AssetBundleProviderType: m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider - m_BundleNaming: 0 + m_BundleNaming: 3 + m_AssetLoadMode: 0 From 1fa99d394537d38aaf9df0ffef4e27d9696ce9fd Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Mon, 12 Sep 2022 20:02:26 -0400 Subject: [PATCH 056/138] fix: Use Time.unscaledDeltaTime instead of Time.deltaTime as time step (#2187) * fix: Use Time.unscaledDeltaTime instead of Time.deltaTime as time step (#2171) This fixes the case of Time.timeScale = 0 stopping the NetworkManager to update. This as no influence on all projects that always have Time.timeScale = 1. + Add tests + Change existing test using Assert.Equal(boolExpr) to the correct Assert function to display real values expected instead of just "true" or "false" * style: removing unrelated stylistic differences. Fixing brackets placement (netcode.standards) * test: test adjustments * test: test adjustments * test: Applying PR review suggestion of saving the time scale * test: Applying PR review suggestion of saving the time scale, second location * Reverted changes to AddNetworkPrefabTests and fixed instabilities caused by NetworkManager.Singleton (can we get rid of that completely soon?) * reverting unwanted whitespace change * reducing the test cases to 0.0f, 1.0f and 2.0f timescales * Revert "Reverted changes to AddNetworkPrefabTests and fixed instabilities caused by NetworkManager.Singleton (can we get rid of that completely soon?)" This reverts commit 02a222eeee58b806bb8153774e5c1c6c4ff104f3. Co-authored-by: PitouGames Co-authored-by: Kitty Draper --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Core/NetworkManager.cs | 4 +- .../Runtime/Timing/NetworkTime.cs | 6 +- .../Runtime/Timing/NetworkTimeSystem.cs | 2 +- .../Runtime/NetcodeIntegrationTest.cs | 6 +- .../Tests/Runtime/AddNetworkPrefabTests.cs | 9 +- .../Tests/Runtime/NetworkVariableTests.cs | 26 ++++ .../Runtime/Timing/NetworkTimeSystemTests.cs | 125 ++++++++++++++---- 8 files changed, 137 insertions(+), 42 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 682e8802d4..acbb641087 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -21,6 +21,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed ClientRpcs always reporting in the profiler view as going to all clients, even when limited to a subset of clients by `ClientRpcParams`. (#2144) - Fixed RPC codegen failing to choose the correct extension methods for `FastBufferReader` and `FastBufferWriter` when the parameters were a generic type (i.e., List) and extensions for multiple instantiations of that type have been defined (i.e., List and List) (#2142) - Fixed throwing an exception in `OnNetworkUpdate` causing other `OnNetworkUpdate` calls to not be executed. (#1739) +- Fixed synchronisation when Time.timeScale is set to 0. This changes timing update to use unscaled deltatime. Now network updates rate are independant from the local time scale. (#2171) ## [1.0.1] - 2022-08-23 diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index f481f16a2d..912b7537a4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1567,7 +1567,7 @@ private void OnNetworkPreUpdate() } // Only update RTT here, server time is updated by time sync messages - var reset = NetworkTimeSystem.Advance(Time.deltaTime); + var reset = NetworkTimeSystem.Advance(Time.unscaledDeltaTime); if (reset) { NetworkTickSystem.Reset(NetworkTimeSystem.LocalTime, NetworkTimeSystem.ServerTime); @@ -1576,7 +1576,7 @@ private void OnNetworkPreUpdate() if (IsServer == false) { - NetworkTimeSystem.Sync(NetworkTimeSystem.LastSyncedServerTimeSec + Time.deltaTime, NetworkConfig.NetworkTransport.GetCurrentRtt(ServerClientId) / 1000d); + NetworkTimeSystem.Sync(NetworkTimeSystem.LastSyncedServerTimeSec + Time.unscaledDeltaTime, NetworkConfig.NetworkTransport.GetCurrentRtt(ServerClientId) / 1000d); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs index 9f86de9b16..7af210b9bf 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs @@ -25,7 +25,7 @@ public struct NetworkTime public double TickOffset => m_CachedTickOffset; /// - /// Gets the current time. This is a non fixed time value and similar to + /// Gets the current time. This is a non fixed time value and similar to . /// public double Time => m_TimeSec; @@ -35,13 +35,13 @@ public struct NetworkTime public float TimeAsFloat => (float)m_TimeSec; /// - /// Gets he current fixed network time. This is the time value of the last network tick. Similar to + /// Gets he current fixed network time. This is the time value of the last network tick. Similar to . /// public double FixedTime => m_CachedTick * m_TickInterval; /// /// Gets the fixed delta time. This value is based on the and stays constant. - /// Similar to There is no equivalent to + /// Similar to There is no equivalent to . /// public float FixedDeltaTime => (float)m_TickInterval; diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs index e9de73a7f5..f0831332ed 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs @@ -76,7 +76,7 @@ public static NetworkTimeSystem ServerTimeSystem() } /// - /// Advances the time system by a certain amount of time. Should be called once per frame with Time.deltaTime or similar. + /// Advances the time system by a certain amount of time. Should be called once per frame with Time.unscaledDeltaTime or similar. /// /// The amount of time to advance. The delta time which passed since Advance was last called. /// diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index 86de88176d..ee085737d3 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -23,7 +23,7 @@ public abstract class NetcodeIntegrationTest /// internal static bool IsRunning { get; private set; } protected static TimeoutHelper s_GlobalTimeoutHelper = new TimeoutHelper(8.0f); - protected static WaitForSeconds s_DefaultWaitForTick = new WaitForSeconds(1.0f / k_DefaultTickRate); + protected static WaitForSecondsRealtime s_DefaultWaitForTick = new WaitForSecondsRealtime(1.0f / k_DefaultTickRate); public NetcodeLogAssert NetcodeLogAssert; @@ -339,7 +339,7 @@ protected void CreateServerAndClients(int numberOfClients) if (m_ServerNetworkManager != null) { - s_DefaultWaitForTick = new WaitForSeconds(1.0f / m_ServerNetworkManager.NetworkConfig.TickRate); + s_DefaultWaitForTick = new WaitForSecondsRealtime(1.0f / m_ServerNetworkManager.NetworkConfig.TickRate); } // Set the player prefab for the server and clients @@ -574,7 +574,7 @@ protected void ShutdownAndCleanUp() UnloadRemainingScenes(); // reset the m_ServerWaitForTick for the next test to initialize - s_DefaultWaitForTick = new WaitForSeconds(1.0f / k_DefaultTickRate); + s_DefaultWaitForTick = new WaitForSecondsRealtime(1.0f / k_DefaultTickRate); VerboseDebug($"Exiting {nameof(ShutdownAndCleanUp)}"); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/AddNetworkPrefabTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/AddNetworkPrefabTests.cs index fdf2cb43c4..5ed703be26 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/AddNetworkPrefabTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/AddNetworkPrefabTests.cs @@ -22,17 +22,18 @@ protected override IEnumerator OnSetup() // Host is irrelevant, messages don't get sent to the host "client" m_UseHost = false; + yield return null; + } + + protected override void OnServerAndClientsCreated() + { m_Prefab = new GameObject("Object"); var networkObject = m_Prefab.AddComponent(); m_Prefab.AddComponent(); // Make it a prefab NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); - yield return null; - } - protected override void OnServerAndClientsCreated() - { m_ServerNetworkManager.NetworkConfig.SpawnTimeout = 0; m_ServerNetworkManager.NetworkConfig.ForceSamePrefabs = false; foreach (var client in m_ClientNetworkManagers) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs index 64e0d2e247..24ad1bb56e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs @@ -536,6 +536,23 @@ public IEnumerator ClientWritePermissionTest([Values(true, false)] bool useHost) Assert.Throws(() => m_Player1OnClient1.TheScalar.Value = k_TestVal1); } + /// + /// Runs tests that network variables sync on client whatever the local value of . + /// + [UnityTest] + public IEnumerator NetworkVariableSync_WithDifferentTimeScale([Values(true, false)] bool useHost, [Values(0.0f, 1.0f, 2.0f)] float timeScale) + { + Time.timeScale = timeScale; + + yield return InitializeServerAndClients(useHost); + + m_Player1OnServer.TheScalar.Value = k_TestVal1; + + // Now wait for the client side version to be updated to k_TestVal1 + yield return WaitForConditionOrTimeOut(() => m_Player1OnClient1.TheScalar.Value == k_TestVal1); + Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for client-side NetworkVariable to update!"); + } + [UnityTest] public IEnumerator FixedString32Test([Values(true, false)] bool useHost) { @@ -758,9 +775,18 @@ public IEnumerator NetworkListIEnumerator([Values(true, false)] bool useHost) } #endregion + private float m_OriginalTimeScale = 1.0f; + + protected override IEnumerator OnSetup() + { + m_OriginalTimeScale = Time.timeScale; + yield return null; + } protected override IEnumerator OnTearDown() { + Time.timeScale = m_OriginalTimeScale; + m_NetworkListPredicateHandler = null; yield return base.OnTearDown(); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Timing/NetworkTimeSystemTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Timing/NetworkTimeSystemTests.cs index c03de7f6f2..a59d035728 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Timing/NetworkTimeSystemTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Timing/NetworkTimeSystemTests.cs @@ -1,6 +1,7 @@ using System.Collections; using NUnit.Framework; using UnityEngine; +using UnityEngine.Assertions.Comparers; using UnityEngine.TestTools; using Unity.Netcode.TestHelpers.Runtime; @@ -11,25 +12,45 @@ namespace Unity.Netcode.RuntimeTests /// public class NetworkTimeSystemTests { - private MonoBehaviourTest m_MonoBehaviourTest; // cache for teardown + private MonoBehaviourTest m_PlayerLoopFixedTimeTestComponent; // cache for teardown + private MonoBehaviourTest m_PlayerLoopTimeTestComponent; // cache for teardown + + private float m_OriginalTimeScale = 1.0f; [SetUp] public void Setup() { + m_OriginalTimeScale = Time.timeScale; + // Create, instantiate, and host Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out _)); } /// /// Tests whether time is accessible and has correct values inside Update/FixedUpdate. + /// This test applies only when is 1. + /// + /// + [UnityTest] + public IEnumerator PlayerLoopFixedTimeTest() + { + m_PlayerLoopFixedTimeTestComponent = new MonoBehaviourTest(); + + yield return m_PlayerLoopFixedTimeTestComponent; + } + + /// + /// Tests whether time is accessible and has correct values inside Update, for multiples values. /// /// [UnityTest] - public IEnumerator PlayerLoopTimeTest() + public IEnumerator PlayerLoopTimeTest_WithDifferentTimeScale([Values(0.0f, 0.1f, 0.5f, 1.0f, 2.0f, 5.0f)] float timeScale) { - m_MonoBehaviourTest = new MonoBehaviourTest(); + Time.timeScale = timeScale; + + m_PlayerLoopTimeTestComponent = new MonoBehaviourTest(); - yield return m_MonoBehaviourTest; + yield return m_PlayerLoopTimeTestComponent; } /// @@ -40,10 +61,10 @@ public IEnumerator PlayerLoopTimeTest() [UnityTest] public IEnumerator CorrectAmountTicksTest() { - var tickSystem = NetworkManager.Singleton.NetworkTickSystem; - var delta = tickSystem.LocalTime.FixedDeltaTime; - var previous_localTickCalculated = 0; - var previous_serverTickCalculated = 0; + NetworkTickSystem tickSystem = NetworkManager.Singleton.NetworkTickSystem; + float delta = tickSystem.LocalTime.FixedDeltaTime; + int previous_localTickCalculated = 0; + int previous_serverTickCalculated = 0; while (tickSystem.LocalTime.Time < 3f) { @@ -70,7 +91,7 @@ public IEnumerator CorrectAmountTicksTest() Assert.AreEqual(previous_localTickCalculated, NetworkManager.Singleton.LocalTime.Tick, $"Calculated local tick {previous_localTickCalculated} does not match local tick {NetworkManager.Singleton.LocalTime.Tick}!"); Assert.AreEqual(previous_serverTickCalculated, NetworkManager.Singleton.ServerTime.Tick, $"Calculated server tick {previous_serverTickCalculated} does not match server tick {NetworkManager.Singleton.ServerTime.Tick}!"); - Assert.True(Mathf.Approximately((float)NetworkManager.Singleton.LocalTime.Time, (float)NetworkManager.Singleton.ServerTime.Time), $"Local time {(float)NetworkManager.Singleton.LocalTime.Time} is not approximately server time {(float)NetworkManager.Singleton.ServerTime.Time}!"); + Assert.AreEqual((float)NetworkManager.Singleton.LocalTime.Time, (float)NetworkManager.Singleton.ServerTime.Time, $"Local time {(float)NetworkManager.Singleton.LocalTime.Time} is not approximately server time {(float)NetworkManager.Singleton.ServerTime.Time}!", FloatComparer.s_ComparerWithDefaultTolerance); } } @@ -80,15 +101,23 @@ public void TearDown() // Stop, shutdown, and destroy NetworkManagerHelper.ShutdownNetworkManager(); - if (m_MonoBehaviourTest != null) + Time.timeScale = m_OriginalTimeScale; + + if (m_PlayerLoopFixedTimeTestComponent != null) { - Object.DestroyImmediate(m_MonoBehaviourTest.gameObject); + Object.DestroyImmediate(m_PlayerLoopFixedTimeTestComponent.gameObject); + m_PlayerLoopFixedTimeTestComponent = null; } - } + if (m_PlayerLoopTimeTestComponent != null) + { + Object.DestroyImmediate(m_PlayerLoopTimeTestComponent.gameObject); + m_PlayerLoopTimeTestComponent = null; + } + } } - public class PlayerLoopTimeTestComponent : MonoBehaviour, IMonoBehaviourTest + public class PlayerLoopFixedTimeTestComponent : MonoBehaviour, IMonoBehaviourTest { public const int Passes = 100; @@ -101,7 +130,7 @@ public class PlayerLoopTimeTestComponent : MonoBehaviour, IMonoBehaviourTest private NetworkTime m_ServerTimePreviousUpdate; private NetworkTime m_LocalTimePreviousFixedUpdate; - public void Start() + private void Start() { // Run fixed update at same rate as network tick Time.fixedDeltaTime = NetworkManager.Singleton.LocalTime.FixedDeltaTime; @@ -110,23 +139,23 @@ public void Start() Time.maximumDeltaTime = float.MaxValue; } - public void Update() + private void Update() { // This must run first else it wont run if there is an exception m_UpdatePasses++; - var localTime = NetworkManager.Singleton.LocalTime; - var serverTime = NetworkManager.Singleton.ServerTime; + NetworkTime localTime = NetworkManager.Singleton.LocalTime; + NetworkTime serverTime = NetworkManager.Singleton.ServerTime; // time should have advanced on the host/server - Assert.True(m_LocalTimePreviousUpdate.Time < localTime.Time); - Assert.True(m_ServerTimePreviousUpdate.Time < serverTime.Time); + Assert.Less(m_LocalTimePreviousUpdate.Time, localTime.Time); + Assert.Less(m_ServerTimePreviousUpdate.Time, serverTime.Time); // time should be further then last fixed step in update - Assert.True(m_LocalTimePreviousFixedUpdate.FixedTime < localTime.Time); + Assert.Less(m_LocalTimePreviousFixedUpdate.FixedTime, localTime.Time); // we should be in same or further tick then fixed update - Assert.True(m_LocalTimePreviousFixedUpdate.Tick <= localTime.Tick); + Assert.LessOrEqual(m_LocalTimePreviousFixedUpdate.Tick, localTime.Tick); // fixed update should result in same amounts of tick as network time if (m_TickOffset == -1) @@ -135,23 +164,61 @@ public void Update() } else { - // offset of 1 is ok, this happens due to different tick duration offsets - Assert.True(Mathf.Abs(serverTime.Tick - m_TickOffset - m_LastFixedUpdateTick) <= 1); + // offset of 1 is ok, this happens due to different tick duration offsets + Assert.LessOrEqual(Mathf.Abs(serverTime.Tick - m_TickOffset - m_LastFixedUpdateTick), 1); } m_LocalTimePreviousUpdate = localTime; + m_ServerTimePreviousUpdate = serverTime; } - public void FixedUpdate() + private void FixedUpdate() { - var time = NetworkManager.Singleton.LocalTime; + m_LocalTimePreviousFixedUpdate = NetworkManager.Singleton.LocalTime; - m_LocalTimePreviousFixedUpdate = time; + Assert.AreEqual(Time.fixedDeltaTime, m_LocalTimePreviousFixedUpdate.FixedDeltaTime); + Assert.AreEqual((float)NetworkManager.Singleton.LocalTime.Time, (float)NetworkManager.Singleton.ServerTime.Time, null, FloatComparer.s_ComparerWithDefaultTolerance); + m_LastFixedUpdateTick++; + } - Assert.AreEqual(Time.fixedDeltaTime, time.FixedDeltaTime); - Assert.True(Mathf.Approximately((float)NetworkManager.Singleton.LocalTime.Time, (float)NetworkManager.Singleton.ServerTime.Time)); + public bool IsTestFinished => m_UpdatePasses >= Passes; + } - m_LastFixedUpdateTick++; + public class PlayerLoopTimeTestComponent : MonoBehaviour, IMonoBehaviourTest + { + public const int Passes = 100; + + private int m_UpdatePasses = 0; + + private NetworkTime m_LocalTimePreviousUpdate; + private NetworkTime m_ServerTimePreviousUpdate; + private NetworkTime m_LocalTimePreviousFixedUpdate; + + private void Update() + { + // This must run first else it wont run if there is an exception + m_UpdatePasses++; + + NetworkTime localTime = NetworkManager.Singleton.LocalTime; + NetworkTime serverTime = NetworkManager.Singleton.ServerTime; + + // time should have advanced on the host/server + Assert.Less(m_LocalTimePreviousUpdate.Time, localTime.Time); + Assert.Less(m_ServerTimePreviousUpdate.Time, serverTime.Time); + + // time should be further then last fixed step in update + Assert.Less(m_LocalTimePreviousFixedUpdate.FixedTime, localTime.Time); + + // we should be in same or further tick then fixed update + Assert.LessOrEqual(m_LocalTimePreviousFixedUpdate.Tick, localTime.Tick); + + m_LocalTimePreviousUpdate = localTime; + m_ServerTimePreviousUpdate = serverTime; + } + + private void FixedUpdate() + { + m_LocalTimePreviousFixedUpdate = NetworkManager.Singleton.LocalTime; } public bool IsTestFinished => m_UpdatePasses >= Passes; From 2e3818c6538f90ac60b9765ca1b8238613b07abf Mon Sep 17 00:00:00 2001 From: Fatih Mar Date: Tue, 13 Sep 2022 13:37:58 +0100 Subject: [PATCH 057/138] refactor: ILPP to import references from companion modules (#2199) --- com.unity.netcode.gameobjects/CHANGELOG.md | 3 +- .../Editor/CodeGen/CodeGenHelpers.cs | 67 +++++ .../Editor/CodeGen/INetworkMessageILPP.cs | 123 +++++++--- .../CodeGen/INetworkSerializableILPP.cs | 1 - .../Editor/CodeGen/NetworkBehaviourILPP.cs | 228 ++++++++++++------ 5 files changed, 318 insertions(+), 104 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index acbb641087..3bfeaadbb9 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -11,7 +11,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed -- Fixed issue where `NetworkTransform` was not ending extrapolation for the previous state causing non-authoritative instances to become out of synch. (#2170) +- Fixed ILPP `TypeLoadException` on WebGL on MacOS Editor and potentially other platforms. (#2199) +- Fixed issue where `NetworkTransform` was not ending extrapolation for the previous state causing non-authoritative instances to become out of sync. (#2170) - Fixed issue where `NetworkTransform` was not continuing to interpolate for the remainder of the associated tick period. (#2170) - Fixed issue during `NetworkTransform.OnNetworkSpawn` for non-authoritative instances where it was initializing interpolators with the replicated network state which now only contains the transform deltas that occurred during a network tick and not the entire transform state. (#2170) - Fixed issue where `NetworkTransform` was not honoring the InLocalSpace property on the authority side during OnNetworkSpawn. (#2170) diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs index 24e63157ce..306d86edf8 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs @@ -15,6 +15,10 @@ namespace Unity.Netcode.Editor.CodeGen { internal static class CodeGenHelpers { + public const string DotnetModuleName = "netstandard.dll"; + public const string UnityModuleName = "UnityEngine.CoreModule.dll"; + public const string NetcodeModuleName = "Unity.Netcode.Runtime.dll"; + public const string RuntimeAssemblyName = "Unity.Netcode.Runtime"; public static readonly string NetworkBehaviour_FullName = typeof(NetworkBehaviour).FullName; @@ -380,5 +384,68 @@ public static AssemblyDefinition AssemblyDefinitionFor(ICompiledAssembly compile return assemblyDefinition; } + + public static (ModuleDefinition DotnetModule, ModuleDefinition UnityModule, ModuleDefinition NetcodeModule) FindBaseModules(AssemblyDefinition assemblyDefinition, PostProcessorAssemblyResolver assemblyResolver) + { + ModuleDefinition dotnetModule = null; + ModuleDefinition unityModule = null; + ModuleDefinition netcodeModule = null; + + foreach (var module in assemblyDefinition.Modules) + { + if (dotnetModule == null && module.Name == DotnetModuleName) + { + dotnetModule = module; + continue; + } + + if (unityModule == null && module.Name == UnityModuleName) + { + unityModule = module; + continue; + } + + if (netcodeModule == null && module.Name == NetcodeModuleName) + { + netcodeModule = module; + continue; + } + } + + if (dotnetModule != null && unityModule != null && netcodeModule != null) + { + return (dotnetModule, unityModule, netcodeModule); + } + + foreach (var assemblyNameReference in assemblyDefinition.MainModule.AssemblyReferences) + { + foreach (var module in assemblyResolver.Resolve(assemblyNameReference).Modules) + { + if (dotnetModule == null && module.Name == DotnetModuleName) + { + dotnetModule = module; + continue; + } + if (unityModule == null && module.Name == UnityModuleName) + { + unityModule = module; + continue; + } + + if (netcodeModule == null && module.Name == NetcodeModuleName) + { + netcodeModule = module; + continue; + } + } + + if (dotnetModule != null && unityModule != null && netcodeModule != null) + { + return (dotnetModule, unityModule, netcodeModule); + } + } + + return (dotnetModule, unityModule, netcodeModule); + } } } diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs index 309e268ca9..2a25561f66 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs @@ -2,7 +2,6 @@ using System.IO; using System.Linq; using System.Collections.Generic; -using System.Reflection; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Cecil.Rocks; @@ -17,8 +16,7 @@ internal sealed class INetworkMessageILPP : ILPPInterface { public override ILPPInterface GetInstance() => this; - public override bool WillProcess(ICompiledAssembly compiledAssembly) => - compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName; + public override bool WillProcess(ICompiledAssembly compiledAssembly) => compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName; private readonly List m_Diagnostics = new List(); @@ -32,13 +30,28 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) m_Diagnostics.Clear(); // read - var assemblyDefinition = CodeGenHelpers.AssemblyDefinitionFor(compiledAssembly, out var resolver); + var assemblyDefinition = CodeGenHelpers.AssemblyDefinitionFor(compiledAssembly, out m_AssemblyResolver); if (assemblyDefinition == null) { m_Diagnostics.AddError($"Cannot read assembly definition: {compiledAssembly.Name}"); return null; } + // modules + (m_DotnetModule, _, m_NetcodeModule) = CodeGenHelpers.FindBaseModules(assemblyDefinition, m_AssemblyResolver); + + if (m_DotnetModule == null) + { + m_Diagnostics.AddError($"Cannot find .NET module: {CodeGenHelpers.DotnetModuleName}"); + return null; + } + + if (m_NetcodeModule == null) + { + m_Diagnostics.AddError($"Cannot find Netcode module: {CodeGenHelpers.NetcodeModuleName}"); + return null; + } + // process var mainModule = assemblyDefinition.MainModule; if (mainModule != null) @@ -91,6 +104,9 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics); } + private ModuleDefinition m_DotnetModule; + private ModuleDefinition m_NetcodeModule; + private PostProcessorAssemblyResolver m_AssemblyResolver; private MethodReference m_MessagingSystem_ReceiveMessage_MethodRef; private TypeReference m_MessagingSystem_MessageWithHandler_TypeRef; @@ -105,63 +121,108 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) private bool ImportReferences(ModuleDefinition moduleDefinition) { - m_MessagingSystem_MessageHandler_Constructor_TypeRef = moduleDefinition.ImportReference(typeof(MessagingSystem.MessageHandler).GetConstructors()[0]); + TypeDefinition typeTypeDef = null; + TypeDefinition listTypeDef = null; + foreach (var dotnetTypeDef in m_DotnetModule.GetAllTypes()) + { + if (typeTypeDef == null && dotnetTypeDef.Name == typeof(Type).Name) + { + typeTypeDef = dotnetTypeDef; + continue; + } + + if (listTypeDef == null && dotnetTypeDef.Name == typeof(List<>).Name) + { + listTypeDef = dotnetTypeDef; + continue; + } + } + + TypeDefinition messageHandlerTypeDef = null; + TypeDefinition messageWithHandlerTypeDef = null; + TypeDefinition ilppMessageProviderTypeDef = null; + TypeDefinition messagingSystemTypeDef = null; + foreach (var netcodeTypeDef in m_NetcodeModule.GetAllTypes()) + { + if (messageHandlerTypeDef == null && netcodeTypeDef.Name == nameof(MessagingSystem.MessageHandler)) + { + messageHandlerTypeDef = netcodeTypeDef; + continue; + } + + if (messageWithHandlerTypeDef == null && netcodeTypeDef.Name == nameof(MessagingSystem.MessageWithHandler)) + { + messageWithHandlerTypeDef = netcodeTypeDef; + continue; + } + + if (ilppMessageProviderTypeDef == null && netcodeTypeDef.Name == nameof(ILPPMessageProvider)) + { + ilppMessageProviderTypeDef = netcodeTypeDef; + continue; + } + + if (messagingSystemTypeDef == null && netcodeTypeDef.Name == nameof(MessagingSystem)) + { + messagingSystemTypeDef = netcodeTypeDef; + continue; + } + } + + m_MessagingSystem_MessageHandler_Constructor_TypeRef = moduleDefinition.ImportReference(messageHandlerTypeDef.GetConstructors().First()); - var messageWithHandlerType = typeof(MessagingSystem.MessageWithHandler); - m_MessagingSystem_MessageWithHandler_TypeRef = moduleDefinition.ImportReference(messageWithHandlerType); - foreach (var fieldInfo in messageWithHandlerType.GetFields()) + m_MessagingSystem_MessageWithHandler_TypeRef = moduleDefinition.ImportReference(messageWithHandlerTypeDef); + foreach (var fieldDef in messageWithHandlerTypeDef.Fields) { - switch (fieldInfo.Name) + switch (fieldDef.Name) { case nameof(MessagingSystem.MessageWithHandler.MessageType): - m_MessagingSystem_MessageWithHandler_MessageType_FieldRef = moduleDefinition.ImportReference(fieldInfo); + m_MessagingSystem_MessageWithHandler_MessageType_FieldRef = moduleDefinition.ImportReference(fieldDef); break; case nameof(MessagingSystem.MessageWithHandler.Handler): - m_MessagingSystem_MessageWithHandler_Handler_FieldRef = moduleDefinition.ImportReference(fieldInfo); + m_MessagingSystem_MessageWithHandler_Handler_FieldRef = moduleDefinition.ImportReference(fieldDef); break; } } - var typeType = typeof(Type); - foreach (var methodInfo in typeType.GetMethods()) + foreach (var methodDef in typeTypeDef.Methods) { - switch (methodInfo.Name) + switch (methodDef.Name) { case nameof(Type.GetTypeFromHandle): - m_Type_GetTypeFromHandle_MethodRef = moduleDefinition.ImportReference(methodInfo); + m_Type_GetTypeFromHandle_MethodRef = moduleDefinition.ImportReference(methodDef); break; } } - var ilppMessageProviderType = typeof(ILPPMessageProvider); - foreach (var fieldInfo in ilppMessageProviderType.GetFields(BindingFlags.Static | BindingFlags.NonPublic)) + foreach (var fieldDef in ilppMessageProviderTypeDef.Fields) { - switch (fieldInfo.Name) + switch (fieldDef.Name) { case nameof(ILPPMessageProvider.__network_message_types): - m_ILPPMessageProvider___network_message_types_FieldRef = moduleDefinition.ImportReference(fieldInfo); + m_ILPPMessageProvider___network_message_types_FieldRef = moduleDefinition.ImportReference(fieldDef); break; } } - var listType = typeof(List); - foreach (var methodInfo in listType.GetMethods()) + foreach (var methodDef in listTypeDef.Methods) { - switch (methodInfo.Name) + switch (methodDef.Name) { - case nameof(List.Add): - m_List_Add_MethodRef = moduleDefinition.ImportReference(methodInfo); + case "Add": + m_List_Add_MethodRef = methodDef; + m_List_Add_MethodRef.DeclaringType = listTypeDef.MakeGenericInstanceType(messageWithHandlerTypeDef); + m_List_Add_MethodRef = moduleDefinition.ImportReference(m_List_Add_MethodRef); break; } } - var messagingSystemType = typeof(MessagingSystem); - foreach (var methodInfo in messagingSystemType.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)) + foreach (var methodDef in messagingSystemTypeDef.Methods) { - switch (methodInfo.Name) + switch (methodDef.Name) { case k_ReceiveMessageName: - m_MessagingSystem_ReceiveMessage_MethodRef = moduleDefinition.ImportReference(methodInfo); + m_MessagingSystem_ReceiveMessage_MethodRef = moduleDefinition.ImportReference(methodDef); break; } } @@ -217,10 +278,8 @@ private void CreateInstructionsToRegisterType(ILProcessor processor, List networkMessageTypes) diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkSerializableILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkSerializableILPP.cs index 31d7f3440a..48b28a182e 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkSerializableILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkSerializableILPP.cs @@ -10,7 +10,6 @@ namespace Unity.Netcode.Editor.CodeGen { - internal sealed class INetworkSerializableILPP : ILPPInterface { public override ILPPInterface GetInstance() => this; diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index 01a2e88d10..bb6163d642 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -2,7 +2,6 @@ using System.IO; using System.Linq; using System.Collections.Generic; -using System.Reflection; using System.Runtime.CompilerServices; using Mono.Cecil; using Mono.Cecil.Cil; @@ -23,8 +22,7 @@ internal sealed class NetworkBehaviourILPP : ILPPInterface public override ILPPInterface GetInstance() => this; - public override bool WillProcess(ICompiledAssembly compiledAssembly) => - compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName); + public override bool WillProcess(ICompiledAssembly compiledAssembly) => compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName); private readonly List m_Diagnostics = new List(); @@ -35,7 +33,6 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) return null; } - m_Diagnostics.Clear(); // read @@ -46,11 +43,27 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) return null; } + // modules + (_, m_UnityModule, m_NetcodeModule) = CodeGenHelpers.FindBaseModules(assemblyDefinition, m_AssemblyResolver); + + if (m_UnityModule == null) + { + m_Diagnostics.AddError($"Cannot find Unity module: {CodeGenHelpers.UnityModuleName}"); + return null; + } + + if (m_NetcodeModule == null) + { + m_Diagnostics.AddError($"Cannot find Netcode module: {CodeGenHelpers.NetcodeModuleName}"); + return null; + } + // process var mainModule = assemblyDefinition.MainModule; if (mainModule != null) { m_MainModule = mainModule; + if (ImportReferences(mainModule)) { // process `NetworkBehaviour` types @@ -93,6 +106,8 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) } private ModuleDefinition m_MainModule; + private ModuleDefinition m_UnityModule; + private ModuleDefinition m_NetcodeModule; private PostProcessorAssemblyResolver m_AssemblyResolver; private MethodReference m_Debug_LogError_MethodRef; @@ -125,12 +140,12 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) private TypeReference m_ClientRpcParams_TypeRef; private TypeReference m_FastBufferWriter_TypeRef; - private Dictionary m_FastBufferWriter_WriteValue_MethodRefs = new Dictionary(); - private List m_FastBufferWriter_ExtensionMethodRefs = new List(); + private readonly Dictionary m_FastBufferWriter_WriteValue_MethodRefs = new Dictionary(); + private readonly List m_FastBufferWriter_ExtensionMethodRefs = new List(); private TypeReference m_FastBufferReader_TypeRef; - private Dictionary m_FastBufferReader_ReadValue_MethodRefs = new Dictionary(); - private List m_FastBufferReader_ExtensionMethodRefs = new List(); + private readonly Dictionary m_FastBufferReader_ReadValue_MethodRefs = new Dictionary(); + private readonly List m_FastBufferReader_ExtensionMethodRefs = new List(); private const string k_Debug_LogError = nameof(Debug.LogError); private const string k_NetworkManager_LocalClientId = nameof(NetworkManager.LocalClientId); @@ -159,158 +174,231 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) private bool ImportReferences(ModuleDefinition moduleDefinition) { - var debugType = typeof(Debug); - foreach (var methodInfo in debugType.GetMethods()) + TypeDefinition debugTypeDef = null; + foreach (var unityTypeDef in m_UnityModule.GetAllTypes()) + { + if (debugTypeDef == null && unityTypeDef.FullName == typeof(Debug).FullName) + { + debugTypeDef = unityTypeDef; + continue; + } + } + + TypeDefinition networkManagerTypeDef = null; + TypeDefinition networkBehaviourTypeDef = null; + TypeDefinition networkHandlerDelegateTypeDef = null; + TypeDefinition rpcParamsTypeDef = null; + TypeDefinition serverRpcParamsTypeDef = null; + TypeDefinition clientRpcParamsTypeDef = null; + TypeDefinition fastBufferWriterTypeDef = null; + TypeDefinition fastBufferReaderTypeDef = null; + foreach (var netcodeTypeDef in m_NetcodeModule.GetAllTypes()) { - switch (methodInfo.Name) + if (networkManagerTypeDef == null && netcodeTypeDef.Name == nameof(NetworkManager)) + { + networkManagerTypeDef = netcodeTypeDef; + continue; + } + + if (networkBehaviourTypeDef == null && netcodeTypeDef.Name == nameof(NetworkBehaviour)) + { + networkBehaviourTypeDef = netcodeTypeDef; + continue; + } + + if (networkHandlerDelegateTypeDef == null && netcodeTypeDef.Name == nameof(NetworkManager.RpcReceiveHandler)) + { + networkHandlerDelegateTypeDef = netcodeTypeDef; + continue; + } + + if (rpcParamsTypeDef == null && netcodeTypeDef.Name == nameof(__RpcParams)) + { + rpcParamsTypeDef = netcodeTypeDef; + continue; + } + + if (serverRpcParamsTypeDef == null && netcodeTypeDef.Name == nameof(ServerRpcParams)) + { + serverRpcParamsTypeDef = netcodeTypeDef; + continue; + } + + if (clientRpcParamsTypeDef == null && netcodeTypeDef.Name == nameof(ClientRpcParams)) + { + clientRpcParamsTypeDef = netcodeTypeDef; + continue; + } + + if (fastBufferWriterTypeDef == null && netcodeTypeDef.Name == nameof(FastBufferWriter)) + { + fastBufferWriterTypeDef = netcodeTypeDef; + continue; + } + + if (fastBufferReaderTypeDef == null && netcodeTypeDef.Name == nameof(FastBufferReader)) + { + fastBufferReaderTypeDef = netcodeTypeDef; + continue; + } + } + + foreach (var methodDef in debugTypeDef.Methods) + { + switch (methodDef.Name) { case k_Debug_LogError: - if (methodInfo.GetParameters().Length == 1) + if (methodDef.Parameters.Count == 1) { - m_Debug_LogError_MethodRef = moduleDefinition.ImportReference(methodInfo); + m_Debug_LogError_MethodRef = moduleDefinition.ImportReference(methodDef); } break; } } - var networkManagerType = typeof(NetworkManager); - m_NetworkManager_TypeRef = moduleDefinition.ImportReference(networkManagerType); - foreach (var propertyInfo in networkManagerType.GetProperties()) + m_NetworkManager_TypeRef = moduleDefinition.ImportReference(networkManagerTypeDef); + foreach (var propertyDef in networkManagerTypeDef.Properties) { - switch (propertyInfo.Name) + switch (propertyDef.Name) { case k_NetworkManager_LocalClientId: - m_NetworkManager_getLocalClientId_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod); + m_NetworkManager_getLocalClientId_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod); break; case k_NetworkManager_IsListening: - m_NetworkManager_getIsListening_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod); + m_NetworkManager_getIsListening_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod); break; case k_NetworkManager_IsHost: - m_NetworkManager_getIsHost_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod); + m_NetworkManager_getIsHost_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod); break; case k_NetworkManager_IsServer: - m_NetworkManager_getIsServer_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod); + m_NetworkManager_getIsServer_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod); break; case k_NetworkManager_IsClient: - m_NetworkManager_getIsClient_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod); + m_NetworkManager_getIsClient_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod); break; } } - foreach (var fieldInfo in networkManagerType.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) + foreach (var fieldDef in networkManagerTypeDef.Fields) { - switch (fieldInfo.Name) + switch (fieldDef.Name) { case k_NetworkManager_LogLevel: - m_NetworkManager_LogLevel_FieldRef = moduleDefinition.ImportReference(fieldInfo); + m_NetworkManager_LogLevel_FieldRef = moduleDefinition.ImportReference(fieldDef); break; case k_NetworkManager_rpc_func_table: - m_NetworkManager_rpc_func_table_FieldRef = moduleDefinition.ImportReference(fieldInfo); - m_NetworkManager_rpc_func_table_Add_MethodRef = moduleDefinition.ImportReference(fieldInfo.FieldType.GetMethod("Add")); + m_NetworkManager_rpc_func_table_FieldRef = moduleDefinition.ImportReference(fieldDef); + + m_NetworkManager_rpc_func_table_Add_MethodRef = fieldDef.FieldType.Resolve().Methods.First(m => m.Name == "Add"); + m_NetworkManager_rpc_func_table_Add_MethodRef.DeclaringType = fieldDef.FieldType; + m_NetworkManager_rpc_func_table_Add_MethodRef = moduleDefinition.ImportReference(m_NetworkManager_rpc_func_table_Add_MethodRef); break; case k_NetworkManager_rpc_name_table: - m_NetworkManager_rpc_name_table_FieldRef = moduleDefinition.ImportReference(fieldInfo); - m_NetworkManager_rpc_name_table_Add_MethodRef = moduleDefinition.ImportReference(fieldInfo.FieldType.GetMethod("Add")); + m_NetworkManager_rpc_name_table_FieldRef = moduleDefinition.ImportReference(fieldDef); + + m_NetworkManager_rpc_name_table_Add_MethodRef = fieldDef.FieldType.Resolve().Methods.First(m => m.Name == "Add"); + m_NetworkManager_rpc_name_table_Add_MethodRef.DeclaringType = fieldDef.FieldType; + m_NetworkManager_rpc_name_table_Add_MethodRef = moduleDefinition.ImportReference(m_NetworkManager_rpc_name_table_Add_MethodRef); break; } } - var networkBehaviourType = typeof(NetworkBehaviour); - m_NetworkBehaviour_TypeRef = moduleDefinition.ImportReference(networkBehaviourType); - foreach (var propertyInfo in networkBehaviourType.GetProperties()) + m_NetworkBehaviour_TypeRef = moduleDefinition.ImportReference(networkBehaviourTypeDef); + foreach (var propertyDef in networkBehaviourTypeDef.Properties) { - switch (propertyInfo.Name) + switch (propertyDef.Name) { case k_NetworkBehaviour_NetworkManager: - m_NetworkBehaviour_getNetworkManager_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod); + m_NetworkBehaviour_getNetworkManager_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod); break; case k_NetworkBehaviour_OwnerClientId: - m_NetworkBehaviour_getOwnerClientId_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod); + m_NetworkBehaviour_getOwnerClientId_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod); break; } } - foreach (var methodInfo in networkBehaviourType.GetMethods(BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) + foreach (var methodDef in networkBehaviourTypeDef.Methods) { - switch (methodInfo.Name) + switch (methodDef.Name) { case k_NetworkBehaviour_beginSendServerRpc: - m_NetworkBehaviour_beginSendServerRpc_MethodRef = moduleDefinition.ImportReference(methodInfo); + m_NetworkBehaviour_beginSendServerRpc_MethodRef = moduleDefinition.ImportReference(methodDef); break; case k_NetworkBehaviour_endSendServerRpc: - m_NetworkBehaviour_endSendServerRpc_MethodRef = moduleDefinition.ImportReference(methodInfo); + m_NetworkBehaviour_endSendServerRpc_MethodRef = moduleDefinition.ImportReference(methodDef); break; case k_NetworkBehaviour_beginSendClientRpc: - m_NetworkBehaviour_beginSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodInfo); + m_NetworkBehaviour_beginSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodDef); break; case k_NetworkBehaviour_endSendClientRpc: - m_NetworkBehaviour_endSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodInfo); + m_NetworkBehaviour_endSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodDef); break; } } - foreach (var fieldInfo in networkBehaviourType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) + foreach (var fieldDef in networkBehaviourTypeDef.Fields) { - switch (fieldInfo.Name) + switch (fieldDef.Name) { case k_NetworkBehaviour_rpc_exec_stage: - m_NetworkBehaviour_rpc_exec_stage_FieldRef = moduleDefinition.ImportReference(fieldInfo); + m_NetworkBehaviour_rpc_exec_stage_FieldRef = moduleDefinition.ImportReference(fieldDef); break; } } - var networkHandlerDelegateType = typeof(NetworkManager.RpcReceiveHandler); - m_NetworkHandlerDelegateCtor_MethodRef = moduleDefinition.ImportReference(networkHandlerDelegateType.GetConstructor(new[] { typeof(object), typeof(IntPtr) })); + foreach (var ctor in networkHandlerDelegateTypeDef.Resolve().GetConstructors()) + { + if (ctor.HasParameters && + ctor.Parameters.Count == 2 && + ctor.Parameters[0].ParameterType.Name == nameof(System.Object) && + ctor.Parameters[1].ParameterType.Name == nameof(IntPtr)) + { + m_NetworkHandlerDelegateCtor_MethodRef = moduleDefinition.ImportReference(ctor); + break; + } + } - var rpcParamsType = typeof(__RpcParams); - m_RpcParams_TypeRef = moduleDefinition.ImportReference(rpcParamsType); - foreach (var fieldInfo in rpcParamsType.GetFields()) + m_RpcParams_TypeRef = moduleDefinition.ImportReference(rpcParamsTypeDef); + foreach (var fieldDef in rpcParamsTypeDef.Fields) { - switch (fieldInfo.Name) + switch (fieldDef.Name) { case k_RpcParams_Server: - m_RpcParams_Server_FieldRef = moduleDefinition.ImportReference(fieldInfo); + m_RpcParams_Server_FieldRef = moduleDefinition.ImportReference(fieldDef); break; case k_RpcParams_Client: - m_RpcParams_Client_FieldRef = moduleDefinition.ImportReference(fieldInfo); + m_RpcParams_Client_FieldRef = moduleDefinition.ImportReference(fieldDef); break; } } - var serverRpcParamsType = typeof(ServerRpcParams); - m_ServerRpcParams_TypeRef = moduleDefinition.ImportReference(serverRpcParamsType); - foreach (var fieldInfo in serverRpcParamsType.GetFields()) + m_ServerRpcParams_TypeRef = moduleDefinition.ImportReference(serverRpcParamsTypeDef); + foreach (var fieldDef in serverRpcParamsTypeDef.Fields) { - switch (fieldInfo.Name) + switch (fieldDef.Name) { case k_ServerRpcParams_Receive: - foreach (var recvFieldInfo in fieldInfo.FieldType.GetFields()) + foreach (var recvFieldDef in fieldDef.FieldType.Resolve().Fields) { - switch (recvFieldInfo.Name) + switch (recvFieldDef.Name) { case k_ServerRpcReceiveParams_SenderClientId: - m_ServerRpcParams_Receive_SenderClientId_FieldRef = moduleDefinition.ImportReference(recvFieldInfo); + m_ServerRpcParams_Receive_SenderClientId_FieldRef = moduleDefinition.ImportReference(recvFieldDef); break; } } - m_ServerRpcParams_Receive_FieldRef = moduleDefinition.ImportReference(fieldInfo); + m_ServerRpcParams_Receive_FieldRef = moduleDefinition.ImportReference(fieldDef); break; } } - var clientRpcParamsType = typeof(ClientRpcParams); - m_ClientRpcParams_TypeRef = moduleDefinition.ImportReference(clientRpcParamsType); - - var fastBufferWriterType = typeof(FastBufferWriter); - m_FastBufferWriter_TypeRef = moduleDefinition.ImportReference(fastBufferWriterType); - - var fastBufferReaderType = typeof(FastBufferReader); - m_FastBufferReader_TypeRef = moduleDefinition.ImportReference(fastBufferReaderType); + m_ClientRpcParams_TypeRef = moduleDefinition.ImportReference(clientRpcParamsTypeDef); + m_FastBufferWriter_TypeRef = moduleDefinition.ImportReference(fastBufferWriterTypeDef); + m_FastBufferReader_TypeRef = moduleDefinition.ImportReference(fastBufferReaderTypeDef); - // Find all extension methods for FastBufferReader and FastBufferWriter to enable user-implemented - // methods to be called. + // Find all extension methods for FastBufferReader and FastBufferWriter to enable user-implemented methods to be called var assemblies = new List { m_MainModule.Assembly }; foreach (var reference in m_MainModule.AssemblyReferences) { From 85922e7a675a563eafa4fd27d87d655f6c448046 Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Tue, 13 Sep 2022 11:30:30 -0400 Subject: [PATCH 058/138] feat: Non-deterministic debug simulator in UnityTransport [MTT-4592] (#2196) * feat: Non-deterministic debug simulator in UnityTransport [MTT-4592] * Add PR number to CHANGELOG entry --- com.unity.netcode.gameobjects/CHANGELOG.md | 4 ++++ .../Runtime/Transports/UTP/UnityTransport.cs | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 3bfeaadbb9..e4ba64a1f0 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -9,6 +9,10 @@ Additional documentation and release notes are available at [Multiplayer Documen ## [Unreleased] +### Changed + +- The debug simulator in `UnityTransport` is now non-deterministic. Its random number generator used to be seeded with a constant value, leading to the same pattern of packet drops, delays, and jitter in every run. (#2196) + ### Fixed - Fixed ILPP `TypeLoadException` on WebGL on MacOS Editor and potentially other platforms. (#2199) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index d209d8b856..d5de9abfd9 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -337,7 +337,7 @@ public struct SimulatorParameters PacketDropRate = 0 }; - internal uint DebugSimulatorRandomSeed { get; set; } = 0; + internal uint? DebugSimulatorRandomSeed { get; set; } = null; private struct PacketLossCache { @@ -1325,7 +1325,7 @@ private void ConfigureSimulator() packetDelayMs: DebugSimulator.PacketDelayMS, packetJitterMs: DebugSimulator.PacketJitterMS, packetDropPercentage: DebugSimulator.PacketDropRate, - randomSeed: DebugSimulatorRandomSeed + randomSeed: DebugSimulatorRandomSeed ?? (uint)System.Diagnostics.Stopwatch.GetTimestamp() #if UTP_TRANSPORT_2_0_ABOVE , mode: ApplyMode.AllPackets #endif From 61dd3898f4801bda3ee51e062fec89503ba37360 Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Wed, 14 Sep 2022 00:01:56 -0400 Subject: [PATCH 059/138] chore: expose websockets (#2201) * chore: websockets. Minimalist UI to use WebSockets * fix: adjusting #ifdef on transport version to allow building non-webgl platforms with UTP 2.0 * fix: addressing build issue where WebGL platform requires different name for UTP symbols * fix: emitting a warning and switching to WebSockets if uers don't, on WebGL platform * Addressing PR review comments * Addressing PR review comments * Update com.unity.netcode.gameobjects/CHANGELOG.md Co-authored-by: Fatih Mar Co-authored-by: Fatih Mar --- com.unity.netcode.gameobjects/CHANGELOG.md | 4 +++ .../Runtime/Transports/UTP/UnityTransport.cs | 28 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index e4ba64a1f0..a1019456ef 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -9,6 +9,10 @@ Additional documentation and release notes are available at [Multiplayer Documen ## [Unreleased] +### Added + +- Added WebSocket support when using UTP 2.0 with `UseWebSockets` property in the `UnityTransport` component of the `NetworkManager` allowing to pick WebSockets for communication. When building for WebGL, this selection happens automatically. (#2201) + ### Changed - The debug simulator in `UnityTransport` is now non-deterministic. Its random number generator used to be seeded with a constant value, leading to the same pattern of packet drops, delays, and jitter in every run. (#2196) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index d5de9abfd9..49d81e50c3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -150,6 +150,18 @@ private enum State [SerializeField] private ProtocolType m_ProtocolType; +#if UTP_TRANSPORT_2_0_ABOVE + [Tooltip("Whether or not to use WebSockets as Network Interface")] + [SerializeField] + private bool m_UseWebSockets = false; + + public bool UseWebSockets + { + set => m_UseWebSockets = value; + get => m_UseWebSockets; + } +#endif + [Tooltip("The maximum amount of packets that can be in the internal send/receive queues. Basically this is how many packets can be sent/received in a single update/frame.")] [SerializeField] private int m_MaxPacketQueueSize = InitialMaxPacketQueueSize; @@ -1365,7 +1377,23 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, #endif heartbeatTimeoutMS: transport.m_HeartbeatTimeoutMS); +#if UTP_TRANSPORT_2_0_ABOVE + if (m_UseWebSockets) + { + driver = NetworkDriver.Create(new WebSocketNetworkInterface(), m_NetworkSettings); + } + else + { +#if UNITY_WEBGL + Debug.LogWarning($"WebSockets were used even though they're not selected in NetworkManager. You should check {nameof(UseWebSockets)}', on the Unity Transport component, to silence this warning."); + driver = NetworkDriver.Create(new WebSocketNetworkInterface(), m_NetworkSettings); +#else + driver = NetworkDriver.Create(new UDPNetworkInterface(), m_NetworkSettings); +#endif + } +#else driver = NetworkDriver.Create(m_NetworkSettings); +#endif #if MULTIPLAYER_TOOLS_1_0_0_PRE_7 #if UTP_TRANSPORT_2_0_ABOVE From 96197ee7a81eccd69670f0cdd0e199cf16bc4af8 Mon Sep 17 00:00:00 2001 From: ashwini <36935028+ashwinimurt@users.noreply.github.com> Date: Wed, 14 Sep 2022 07:44:50 -0700 Subject: [PATCH 060/138] chore: update changelog for 1.0.2 (#2200) --- com.unity.netcode.gameobjects/CHANGELOG.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index a1019456ef..91a764c869 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -20,10 +20,6 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed - Fixed ILPP `TypeLoadException` on WebGL on MacOS Editor and potentially other platforms. (#2199) -- Fixed issue where `NetworkTransform` was not ending extrapolation for the previous state causing non-authoritative instances to become out of sync. (#2170) -- Fixed issue where `NetworkTransform` was not continuing to interpolate for the remainder of the associated tick period. (#2170) -- Fixed issue during `NetworkTransform.OnNetworkSpawn` for non-authoritative instances where it was initializing interpolators with the replicated network state which now only contains the transform deltas that occurred during a network tick and not the entire transform state. (#2170) -- Fixed issue where `NetworkTransform` was not honoring the InLocalSpace property on the authority side during OnNetworkSpawn. (#2170) - Implicit conversion of NetworkObjectReference to GameObject will now return null instead of throwing an exception if the referenced object could not be found (i.e., was already despawned) (#2158) - Fixed warning resulting from a stray NetworkAnimator.meta file (#2153) - Fixed Connection Approval Timeout not working client side. (#2164) @@ -31,6 +27,16 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed RPC codegen failing to choose the correct extension methods for `FastBufferReader` and `FastBufferWriter` when the parameters were a generic type (i.e., List) and extensions for multiple instantiations of that type have been defined (i.e., List and List) (#2142) - Fixed throwing an exception in `OnNetworkUpdate` causing other `OnNetworkUpdate` calls to not be executed. (#1739) - Fixed synchronisation when Time.timeScale is set to 0. This changes timing update to use unscaled deltatime. Now network updates rate are independant from the local time scale. (#2171) +- Fixed not sending all NetworkVariables to all clients when a client connects to a server. (#1987) + +## [1.0.2] - 2022-09-12 + +### Fixed + +- Fixed issue where `NetworkTransform` was not honoring the InLocalSpace property on the authority side during OnNetworkSpawn. (#2170) +- Fixed issue where `NetworkTransform` was not ending extrapolation for the previous state causing non-authoritative instances to become out of synch. (#2170) +- Fixed issue where `NetworkTransform` was not continuing to interpolate for the remainder of the associated tick period. (#2170) +- Fixed issue during `NetworkTransform.OnNetworkSpawn` for non-authoritative instances where it was initializing interpolators with the replicated network state which now only contains the transform deltas that occurred during a network tick and not the entire transform state. (#2170) ## [1.0.1] - 2022-08-23 @@ -43,7 +49,6 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed -- Fixed not sending all NetworkVariables to all clients when a client connects to a server. (#1987) - Fixed an issue where reading/writing more than 8 bits at a time with BitReader/BitWriter would write/read from the wrong place, returning and incorrect result. (#2130) - Fixed issue with the internal `NetworkTransformState.m_Bitset` flag not getting cleared upon the next tick advancement. (#2110) - Fixed interpolation issue with `NetworkTransform.Teleport`. (#2110) From d1c1617d556b3551e64746afa4140c5f53064427 Mon Sep 17 00:00:00 2001 From: ashwini <36935028+ashwinimurt@users.noreply.github.com> Date: Wed, 14 Sep 2022 09:21:10 -0700 Subject: [PATCH 061/138] chore: Add CI to test minimal project with different versions of transport (#2190) chore: add ci to test minimal project with different versions of transport --- .yamato/_run-all.yml | 38 ++++++++++++++++++++++++++++++++++++++ .yamato/package-tests.yml | 27 +++++++++++++++++++++++++++ .yamato/project-pack.yml | 27 +++++++++++++++++++++++++++ .yamato/project.metafile | 8 ++++++++ 4 files changed, 100 insertions(+) diff --git a/.yamato/_run-all.yml b/.yamato/_run-all.yml index 52e4ae5698..dfc0667efb 100644 --- a/.yamato/_run-all.yml +++ b/.yamato/_run-all.yml @@ -18,6 +18,17 @@ run_all_tests: - .yamato/project-tests.yml#test_{{ project.name }}_{{ editor }}_{{ platform.name }} {% endif -%} {% endfor -%} + +## Test minimal project with different versions of dependencies +{% if project.name == "minimalproject" -%} +{% for dependency in dependencies -%} +{% for depeditor in dependency.test_editors -%} +{% if depeditor != "trunk" -%} + - .yamato/package-tests.yml#test_compatibility_{{ project.name }}_{{ project.packages.first.name }}_with_{{ dependency.name }}@{{ dependency.version }}_{{ depeditor }}_{{ platform.name }} +{% endif -%} +{% endfor -%} +{% endfor -%} +{% endif -%} {% endfor -%} {% endfor -%} @@ -34,6 +45,17 @@ run_all_tests_trunk: - .yamato/project-tests.yml#test_{{ project.name }}_{{ editor }}_{{ platform.name }} {% endif -%} {% endfor -%} + +## Test minimal project with different versions of dependencies on trunk +{% if project.name == "minimalproject" -%} +{% for dependency in dependencies -%} +{% for depeditor in dependency.test_editors -%} +{% if depeditor == "trunk" -%} + - .yamato/package-tests.yml#test_compatibility_{{ project.name }}_{{ project.packages.first.name }}_with_{{ dependency.name }}@{{ dependency.version }}_{{ depeditor }}_{{ platform.name }} +{% endif -%} +{% endfor -%} +{% endfor -%} +{% endif -%} {% endfor -%} {% endfor -%} @@ -63,6 +85,22 @@ all_package_tests: {% endfor -%} {% endfor -%} +# Test minimal project with different versions of dependencies +all_compatibility_tests: + name: Run All Compatibility Tests + dependencies: +{% for platform in test_platforms -%} +{% for project in projects -%} +{% if project.name == "minimalproject" -%} +{% for dependency in dependencies -%} +{% for editor in dependency.test_editors -%} + - .yamato/package-tests.yml#test_compatibility_{{ project.name }}_{{ project.packages.first.name }}_with_{{ dependency.name }}@{{ dependency.version }}_{{ editor }}_{{ platform.name }} +{% endfor -%} +{% endfor -%} +{% endif -%} +{% endfor -%} +{% endfor -%} + all_singlenode_multiprocess_tests: name: Run All Multiprocess Tests - Single Node dependencies: diff --git a/.yamato/package-tests.yml b/.yamato/package-tests.yml index 3414062add..1779a7008e 100644 --- a/.yamato/package-tests.yml +++ b/.yamato/package-tests.yml @@ -27,4 +27,31 @@ test_{{project.name}}_{{ package.name }}_{{ editor }}_{{ platform.name }}: {% endfor -%} {% endfor -%} {% endfor -%} +{% endfor -%} + +# Test minimal project with different versions of dependencies +{% for project in projects -%} +{% if project.name == "minimalproject" -%} +{% for dependency in dependencies -%} +{% for editor in dependency.test_editors -%} +{% for platform in test_platforms -%} +test_compatibility_{{project.name}}_{{ project.packages.first.name }}_with_{{ dependency.name }}@{{ dependency.version }}_{{ editor }}_{{ platform.name }}: + name : {{ project.name }} - {{ project.packages.first.name }} with {{ dependency.name }}@{{ dependency.version }} - {{ editor }} on {{ platform.name }} + agent: + type: {{ platform.type }} + image: {{ platform.image }} + flavor: {{ platform.flavor}} + commands: + - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm + - {% if platform.name == "ubuntu" %}DISPLAY=:0 {% endif %}upm-ci project test -u {{ editor }} --type project-tests --project-path {{ project.name }} --package-filter {{ project.packages.first.name }} + artifacts: + logs: + paths: + - "upm-ci~/test-results/**/*" + dependencies: + - .yamato/project-pack.yml#pack_{{ project.name }}_{{ dependency.name }}@{{ dependency.version }} +{% endfor -%} +{% endfor -%} +{% endfor -%} +{% endif -%} {% endfor -%} \ No newline at end of file diff --git a/.yamato/project-pack.yml b/.yamato/project-pack.yml index a2fb57c351..96faa157b9 100644 --- a/.yamato/project-pack.yml +++ b/.yamato/project-pack.yml @@ -14,4 +14,31 @@ pack_{{ project.name }}: packages: paths: - "upm-ci~/packages/**/*" +{% endfor -%} + +# Pack minimal project with different versions of dependencies +{% for project in projects -%} +{% if project.name == "minimalproject" -%} +{% for dependency in dependencies -%} +pack_{{ project.name }}_{{ dependency.name }}@{{ dependency.version }}: + name: Pack {{ project.name }} with {{ dependency.name }}@{{ dependency.version }} + agent: + type: Unity::VM + image: package-ci/ubuntu:stable + flavor: b1.small + commands: + - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm + - curl -L https://artifactory.prd.it.unity3d.com/artifactory/api/gpg/key/public | sudo apt-key add - + - sudo sh -c "echo 'deb https://artifactory.prd.it.unity3d.com/artifactory/unity-apt-local bionic main' > /etc/apt/sources.list.d/unity.list" + - sudo apt update + - sudo apt install -y unity-config + - unity-config settings project-path {{ project.path }} + - unity-config project add dependency {{ dependency.name }}@{{ dependency.version }} + - upm-ci project pack --project-path {{ project.path }} + artifacts: + packages: + paths: + - "upm-ci~/packages/**/*" +{% endfor -%} +{% endif -%} {% endfor -%} \ No newline at end of file diff --git a/.yamato/project.metafile b/.yamato/project.metafile index dd8704b1f1..ad351ee0ae 100644 --- a/.yamato/project.metafile +++ b/.yamato/project.metafile @@ -66,6 +66,14 @@ projects: - 2022.2 - trunk +# Package dependencies +dependencies: + - name: com.unity.transport + version: 2.0.0-exp.6 + test_editors: + - 2022.2 + - trunk + # Scripting backends used by Standalone Playmode Tests scripting_backends: - mono From 0cb06c2d48d935145dfa6603029d2d6972bcfb51 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 14 Sep 2022 13:19:12 -0500 Subject: [PATCH 062/138] fix: Set state private RPC handlers were not honoring local vs world space assignment of position and rotation (#2203) * fix SetStateClientRpc and SetStateServerRpc were not updated to use SetStateInternal which honors local vs world space transform setting.s * update changelog * test Modified existing tests to include testing SetState which validates the changes made to NetworkTransform.SetState and associated private methods. * update adding the PR number to the changelog entry. * test removing conditional check that was not needed. * update adding further clarity as to where the two rpc methods were located. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Components/NetworkTransform.cs | 13 +--- .../NetworkTransform/NetworkTransformTests.cs | 69 +++++++++++++------ 3 files changed, 51 insertions(+), 32 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 91a764c869..4828204f7e 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -19,6 +19,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where `NetworkTransform.SetStateServerRpc` and `NetworkTransform.SetStateClientRpc` were not honoring local vs world space settings when applying the position and rotation. (#2203) - Fixed ILPP `TypeLoadException` on WebGL on MacOS Editor and potentially other platforms. (#2199) - Implicit conversion of NetworkObjectReference to GameObject will now return null instead of throwing an exception if the referenced object could not be found (i.e., was already despawned) (#2158) - Fixed warning resulting from a stray NetworkAnimator.meta file (#2153) diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index b1ff99ec60..8e4769028a 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -1169,11 +1169,7 @@ private void SetStateInternal(Vector3 pos, Quaternion rot, Vector3 scale, bool s private void SetStateClientRpc(Vector3 pos, Quaternion rot, Vector3 scale, bool shouldTeleport, ClientRpcParams clientRpcParams = default) { // Server dictated state is always applied - transform.position = pos; - transform.rotation = rot; - transform.localScale = scale; - m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldTeleport; - TryCommitTransform(transform, m_CachedNetworkManager.LocalTime.Time); + SetStateInternal(pos, rot, scale, shouldTeleport); } /// @@ -1190,12 +1186,7 @@ private void SetStateServerRpc(Vector3 pos, Quaternion rot, Vector3 scale, bool { (pos, rot, scale) = OnClientRequestChange(pos, rot, scale); } - - transform.position = pos; - transform.rotation = rot; - transform.localScale = scale; - m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldTeleport; - TryCommitTransform(transform, m_CachedNetworkManager.LocalTime.Time); + SetStateInternal(pos, rot, scale, shouldTeleport); } /// diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index d88c0ceb73..9486bdf26d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -189,7 +189,8 @@ public enum TransformSpace public enum OverrideState { Update, - CommitToTransform + CommitToTransform, + SetState } /// @@ -299,9 +300,8 @@ private IEnumerator WaitForAllChildrenLocalTransformValuesToMatch() /// parented under another NetworkTransform /// [UnityTest] - public IEnumerator NetworkTransformParentedLocalSpaceTest([Values] Interpolation interpolation, [Values] OverrideState overideState) + public IEnumerator NetworkTransformParentedLocalSpaceTest([Values] Interpolation interpolation) { - var overrideUpdate = overideState == OverrideState.CommitToTransform; m_AuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate; m_NonAuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate; var authoritativeChildObject = SpawnObject(m_ChildObjectToBeParented.gameObject, m_AuthoritativeTransform.NetworkManager); @@ -334,15 +334,28 @@ public IEnumerator NetworkTransformParentedLocalSpaceTest([Values] Interpolation /// Validates that moving, rotating, and scaling the authority side with a single /// tick will properly synchronize the non-authoritative side with the same values. /// - private IEnumerator MoveRotateAndScaleAuthority(Vector3 position, Vector3 rotation, Vector3 scale) + private IEnumerator MoveRotateAndScaleAuthority(Vector3 position, Vector3 rotation, Vector3 scale, OverrideState overrideState) { - m_AuthoritativeTransform.transform.position = position; - yield return null; - var authoritativeRotation = m_AuthoritativeTransform.transform.rotation; - authoritativeRotation.eulerAngles = rotation; - m_AuthoritativeTransform.transform.rotation = authoritativeRotation; - yield return null; - m_AuthoritativeTransform.transform.localScale = scale; + switch (overrideState) + { + case OverrideState.SetState: + { + m_AuthoritativeTransform.SetState(position, Quaternion.Euler(rotation), scale); + break; + } + case OverrideState.Update: + default: + { + m_AuthoritativeTransform.transform.position = position; + yield return null; + var authoritativeRotation = m_AuthoritativeTransform.transform.rotation; + authoritativeRotation.eulerAngles = rotation; + m_AuthoritativeTransform.transform.rotation = authoritativeRotation; + yield return null; + m_AuthoritativeTransform.transform.localScale = scale; + break; + } + } } /// @@ -400,7 +413,6 @@ protected override void OnNewClientCreated(NetworkManager networkManager) [UnityTest] public IEnumerator NetworkTransformMultipleChangesOverTime([Values] TransformSpace testLocalTransform, [Values] OverrideState overideState) { - var overrideUpdate = overideState == OverrideState.CommitToTransform; m_AuthoritativeTransform.InLocalSpace = testLocalTransform == TransformSpace.Local; var positionStart = new Vector3(1.0f, 0.5f, 2.0f); @@ -422,7 +434,7 @@ public IEnumerator NetworkTransformMultipleChangesOverTime([Values] TransformSpa yield return WaitForNextTick(); // Apply deltas - MoveRotateAndScaleAuthority(position, rotation, scale); + MoveRotateAndScaleAuthority(position, rotation, scale, overideState); // Wait for deltas to synchronize on non-authoritative side yield return WaitForPositionRotationAndScaleToMatch(4); @@ -455,7 +467,7 @@ public IEnumerator NetworkTransformMultipleChangesOverTime([Values] TransformSpa // to apply both deltas within the same tick period. yield return WaitForNextTick(); - MoveRotateAndScaleAuthority(position, rotation, scale); + MoveRotateAndScaleAuthority(position, rotation, scale, overideState); yield return WaitForPositionRotationAndScaleToMatch(4); } @@ -471,7 +483,7 @@ public IEnumerator NetworkTransformMultipleChangesOverTime([Values] TransformSpa rotation = rotationStart * i; scale = scaleStart * i; - MoveRotateAndScaleAuthority(position, rotation, scale); + MoveRotateAndScaleAuthority(position, rotation, scale, overideState); } yield return WaitForPositionRotationAndScaleToMatch(1); @@ -486,7 +498,7 @@ public IEnumerator NetworkTransformMultipleChangesOverTime([Values] TransformSpa position = positionStart * i; rotation = rotationStart * i; scale = scaleStart * i; - MoveRotateAndScaleAuthority(position, rotation, scale); + MoveRotateAndScaleAuthority(position, rotation, scale, overideState); } yield return WaitForPositionRotationAndScaleToMatch(1); } @@ -513,11 +525,16 @@ public IEnumerator TestAuthoritativeTransformChangeOneAtATime([Values] Transform Assert.AreEqual(Vector3.zero, m_NonAuthoritativeTransform.transform.position, "server side pos should be zero at first"); // sanity check - authPlayerTransform.position = new Vector3(10, 20, 30); - if (overrideUpdate) + var nextPosition = new Vector3(10, 20, 30); + if (overideState != OverrideState.SetState) { + authPlayerTransform.position = nextPosition; m_OwnerTransform.CommitToTransform(); } + else + { + m_OwnerTransform.SetState(nextPosition, null, null, m_AuthoritativeTransform.Interpolate); + } yield return WaitForConditionOrTimeOut(PositionsMatch); AssertOnTimeout($"Timed out waiting for positions to match"); @@ -525,20 +542,30 @@ public IEnumerator TestAuthoritativeTransformChangeOneAtATime([Values] Transform // test rotation Assert.AreEqual(Quaternion.identity, m_NonAuthoritativeTransform.transform.rotation, "wrong initial value for rotation"); // sanity check - authPlayerTransform.rotation = Quaternion.Euler(45, 40, 35); // using euler angles instead of quaternions directly to really see issues users might encounter - if (overrideUpdate) + var nextRotation = Quaternion.Euler(45, 40, 35); // using euler angles instead of quaternions directly to really see issues users might encounter + if (overideState != OverrideState.SetState) { + authPlayerTransform.rotation = nextRotation; m_OwnerTransform.CommitToTransform(); } + else + { + m_OwnerTransform.SetState(null, nextRotation, null, m_AuthoritativeTransform.Interpolate); + } yield return WaitForConditionOrTimeOut(RotationsMatch); AssertOnTimeout($"Timed out waiting for rotations to match"); - authPlayerTransform.localScale = new Vector3(2, 3, 4); + var nextScale = new Vector3(2, 3, 4); if (overrideUpdate) { + authPlayerTransform.localScale = nextScale; m_OwnerTransform.CommitToTransform(); } + else + { + m_OwnerTransform.SetState(null, null, nextScale, m_AuthoritativeTransform.Interpolate); + } yield return WaitForConditionOrTimeOut(ScaleValuesMatch); AssertOnTimeout($"Timed out waiting for scale values to match"); From 3cbfacb7a16a992a5e84273d827d7d5600345f13 Mon Sep 17 00:00:00 2001 From: Jesse Olmer Date: Mon, 19 Sep 2022 08:23:10 -0700 Subject: [PATCH 063/138] fix: Update Add Component menu structure [MTT-4078] (#2191) Put netcode components into a common sub-folder of the Add Component menu, and also fixed word spacing inconsistencies in the entries --- com.unity.netcode.gameobjects/Components/NetworkAnimator.cs | 2 +- com.unity.netcode.gameobjects/Components/NetworkRigidbody.cs | 1 + com.unity.netcode.gameobjects/Components/NetworkRigidbody2D.cs | 1 + com.unity.netcode.gameobjects/Components/NetworkTransform.cs | 2 +- com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs | 2 +- com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs | 2 +- .../Runtime/Transports/UNET/UNetTransport.cs | 1 + .../Runtime/Transports/UTP/UnityTransport.cs | 1 + 8 files changed, 8 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs index 41ab79d855..d914ac2a59 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs @@ -156,7 +156,7 @@ internal NetworkAnimatorStateChangeHandler(NetworkAnimator networkAnimator) /// /// NetworkAnimator enables remote synchronization of state for on network objects. /// - [AddComponentMenu("Netcode/" + nameof(NetworkAnimator))] + [AddComponentMenu("Netcode/Network Animator")] [RequireComponent(typeof(Animator))] public class NetworkAnimator : NetworkBehaviour { diff --git a/com.unity.netcode.gameobjects/Components/NetworkRigidbody.cs b/com.unity.netcode.gameobjects/Components/NetworkRigidbody.cs index 6515f7e2af..0569aa97ed 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkRigidbody.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkRigidbody.cs @@ -9,6 +9,7 @@ namespace Unity.Netcode.Components /// [RequireComponent(typeof(Rigidbody))] [RequireComponent(typeof(NetworkTransform))] + [AddComponentMenu("Netcode/Network Rigidbody")] public class NetworkRigidbody : NetworkBehaviour { /// diff --git a/com.unity.netcode.gameobjects/Components/NetworkRigidbody2D.cs b/com.unity.netcode.gameobjects/Components/NetworkRigidbody2D.cs index 1ac82bbf30..246519cf88 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkRigidbody2D.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkRigidbody2D.cs @@ -9,6 +9,7 @@ namespace Unity.Netcode.Components /// [RequireComponent(typeof(Rigidbody2D))] [RequireComponent(typeof(NetworkTransform))] + [AddComponentMenu("Netcode/Network Rigidbody 2D")] public class NetworkRigidbody2D : NetworkBehaviour { private Rigidbody2D m_Rigidbody; diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index 8e4769028a..8d1c919484 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -10,7 +10,7 @@ namespace Unity.Netcode.Components /// The replicated value will be automatically be interpolated (if active) and applied to the underlying GameObject's transform. /// [DisallowMultipleComponent] - [AddComponentMenu("Netcode/" + nameof(NetworkTransform))] + [AddComponentMenu("Netcode/Network Transform")] [DefaultExecutionOrder(100000)] // this is needed to catch the update time after the transform was updated by user scripts public class NetworkTransform : NetworkBehaviour { diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 912b7537a4..f30a6bd384 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -20,7 +20,7 @@ namespace Unity.Netcode /// /// The main component of the library /// - [AddComponentMenu("Netcode/" + nameof(NetworkManager), -100)] + [AddComponentMenu("Netcode/Network Manager", -100)] public class NetworkManager : MonoBehaviour, INetworkUpdateSystem { #pragma warning disable IDE1006 // disable naming rule violation check diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index a772ca895c..1d3ae5be02 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -9,7 +9,7 @@ namespace Unity.Netcode /// /// A component used to identify that a GameObject in the network /// - [AddComponentMenu("Netcode/" + nameof(NetworkObject), -99)] + [AddComponentMenu("Netcode/Network Object", -99)] [DisallowMultipleComponent] public sealed class NetworkObject : MonoBehaviour { diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UNET/UNetTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UNET/UNetTransport.cs index 992a791dba..7afb7b392e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UNET/UNetTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UNET/UNetTransport.cs @@ -7,6 +7,7 @@ namespace Unity.Netcode.Transports.UNET { + [AddComponentMenu("Netcode/UNet Transport")] public class UNetTransport : NetworkTransport { public enum SendMode diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index 49d81e50c3..87b72d1451 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -92,6 +92,7 @@ public static string ErrorToString(Networking.Transport.Error.StatusCode error, /// The Netcode for GameObjects NetworkTransport for UnityTransport. /// Note: This is highly recommended to use over UNet. /// + [AddComponentMenu("Netcode/Unity Transport")] public partial class UnityTransport : NetworkTransport, INetworkStreamDriverConstructor { /// From 526e2bcb7cad0b8cba6482e2d682335622422a6e Mon Sep 17 00:00:00 2001 From: Kitty Draper <284434+ShadauxCat@users.noreply.github.com> Date: Thu, 22 Sep 2022 13:44:58 -0500 Subject: [PATCH 064/138] fix: Remove "Script" fields from Netcode components in the inspector [MTT-2947] (#2172) * fix: Remove "Script" fields from Netcode components in the inspector * Standards * standards again * Fix compile errors in minimalproject * Whoops, those version defines were for the runtime, had to add them to editor too. * validator... --- .../Editor/HiddenScriptEditor.cs | 74 +++++++++++++++++++ .../Editor/HiddenScriptEditor.cs.meta | 3 + .../Editor/NetworkManagerEditor.cs | 12 --- .../Editor/NetworkObjectEditor.cs | 8 +- .../Editor/com.unity.netcode.editor.asmdef | 17 ++++- com.unity.netcode.gameobjects/package.json | 2 +- 6 files changed, 101 insertions(+), 15 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Editor/HiddenScriptEditor.cs create mode 100644 com.unity.netcode.gameobjects/Editor/HiddenScriptEditor.cs.meta diff --git a/com.unity.netcode.gameobjects/Editor/HiddenScriptEditor.cs b/com.unity.netcode.gameobjects/Editor/HiddenScriptEditor.cs new file mode 100644 index 0000000000..58000fd503 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/HiddenScriptEditor.cs @@ -0,0 +1,74 @@ +using Unity.Netcode.Components; +using Unity.Netcode.Transports.UNET; +using Unity.Netcode.Transports.UTP; +using UnityEditor; + +namespace Unity.Netcode.Editor +{ + /// + /// Internal use. Hides the script field for the given component. + /// + public class HiddenScriptEditor : UnityEditor.Editor + { + private static readonly string[] k_HiddenFields = { "m_Script" }; + public override void OnInspectorGUI() + { + EditorGUI.BeginChangeCheck(); + serializedObject.UpdateIfRequiredOrScript(); + DrawPropertiesExcluding(serializedObject, k_HiddenFields); + serializedObject.ApplyModifiedProperties(); + EditorGUI.EndChangeCheck(); + } + } + + /// + /// Internal use. Hides the script field for UNetTransport. + /// + [CustomEditor(typeof(UNetTransport), true)] + public class UNetTransportEditor : HiddenScriptEditor + { + + } + + /// + /// Internal use. Hides the script field for UnityTransport. + /// + [CustomEditor(typeof(UnityTransport), true)] + public class UnityTransportEditor : HiddenScriptEditor + { + + } + +#if COM_UNITY_MODULES_ANIMATION + /// + /// Internal use. Hides the script field for NetworkAnimator. + /// + [CustomEditor(typeof(NetworkAnimator), true)] + public class NetworkAnimatorEditor : HiddenScriptEditor + { + + } +#endif + +#if COM_UNITY_MODULES_PHYSICS + /// + /// Internal use. Hides the script field for NetworkRigidbody. + /// + [CustomEditor(typeof(NetworkRigidbody), true)] + public class NetworkRigidbodyEditor : HiddenScriptEditor + { + + } +#endif + +#if COM_UNITY_MODULES_PHYSICS2D + /// + /// Internal use. Hides the script field for NetworkRigidbody2D. + /// + [CustomEditor(typeof(NetworkRigidbody2D), true)] + public class NetworkRigidbody2DEditor : HiddenScriptEditor + { + + } +#endif +} diff --git a/com.unity.netcode.gameobjects/Editor/HiddenScriptEditor.cs.meta b/com.unity.netcode.gameobjects/Editor/HiddenScriptEditor.cs.meta new file mode 100644 index 0000000000..f8dd8da119 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/HiddenScriptEditor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ebf622cc80e94f488e59caf8b7419f50 +timeCreated: 1661959406 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs index a788cb8f3e..debaca2e45 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs @@ -214,18 +214,6 @@ public override void OnInspectorGUI() DrawInstallMultiplayerToolsTip(); #endif - { - var iterator = serializedObject.GetIterator(); - - for (bool enterChildren = true; iterator.NextVisible(enterChildren); enterChildren = false) - { - using (new EditorGUI.DisabledScope("m_Script" == iterator.propertyPath)) - { - EditorGUILayout.PropertyField(iterator, false); - } - } - } - if (!m_NetworkManager.IsServer && !m_NetworkManager.IsClient) { serializedObject.Update(); diff --git a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs index 5111e92fcc..c63f9fc10e 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs @@ -15,6 +15,8 @@ public class NetworkObjectEditor : UnityEditor.Editor private NetworkObject m_NetworkObject; private bool m_ShowObservers; + private static readonly string[] k_HiddenFields = { "m_Script" }; + private void Initialize() { if (m_Initialized) @@ -95,7 +97,11 @@ public override void OnInspectorGUI() } else { - base.OnInspectorGUI(); + EditorGUI.BeginChangeCheck(); + serializedObject.UpdateIfRequiredOrScript(); + DrawPropertiesExcluding(serializedObject, k_HiddenFields); + serializedObject.ApplyModifiedProperties(); + EditorGUI.EndChangeCheck(); var guiEnabled = GUI.enabled; GUI.enabled = false; diff --git a/com.unity.netcode.gameobjects/Editor/com.unity.netcode.editor.asmdef b/com.unity.netcode.gameobjects/Editor/com.unity.netcode.editor.asmdef index ab1d2717e4..0648dd9341 100644 --- a/com.unity.netcode.gameobjects/Editor/com.unity.netcode.editor.asmdef +++ b/com.unity.netcode.gameobjects/Editor/com.unity.netcode.editor.asmdef @@ -13,6 +13,21 @@ "name": "com.unity.multiplayer.tools", "expression": "", "define": "MULTIPLAYER_TOOLS" + }, + { + "name": "com.unity.modules.animation", + "expression": "", + "define": "COM_UNITY_MODULES_ANIMATION" + }, + { + "name": "com.unity.modules.physics", + "expression": "", + "define": "COM_UNITY_MODULES_PHYSICS" + }, + { + "name": "com.unity.modules.physics2d", + "expression": "", + "define": "COM_UNITY_MODULES_PHYSICS2D" } ] -} \ No newline at end of file +} diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index 641a768117..75c3b561cb 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -2,7 +2,7 @@ "name": "com.unity.netcode.gameobjects", "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", - "version": "1.0.1", + "version": "1.1.0", "unity": "2020.3", "dependencies": { "com.unity.nuget.mono-cecil": "1.10.1", From 3cd7a2b10fd24cb80d0ab530d87c6e3847664f99 Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Thu, 22 Sep 2022 15:39:51 -0400 Subject: [PATCH 065/138] chore: Remove soon-to-be-deprecated API from UnityTransport (#2213) --- .../Runtime/Transports/UTP/UnityTransport.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index 87b72d1451..c27b7f3089 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -579,7 +579,6 @@ public void SetRelayServerData(string ipv4Address, ushort port, byte[] allocatio } m_RelayServerData = new RelayServerData(ref serverEndpoint, 0, ref allocationId, ref connectionData, ref hostConnectionData, ref key, isSecure); - m_RelayServerData.ComputeNewNonce(); SetProtocol(ProtocolType.RelayUnityTransport); } From 65a38ce38ea154fadd5ad07ae99490a3a88b41ec Mon Sep 17 00:00:00 2001 From: JS Fauteux Date: Thu, 22 Sep 2022 22:13:51 -0400 Subject: [PATCH 066/138] feat: Adding support for netsim to be able to work with Tools adapter (#2184) * Adding support for netsim to be able to work with Tools adapter * Setting up Simulator Pipeline stage if NetSim is available * Deprecate DebugSimulator when using UTP2 Co-authored-by: Jesse Olmer Co-authored-by: DenninDalke --- .../Runtime/AssemblyInfo.cs | 1 + .../Runtime/Transports/UTP/UnityTransport.cs | 134 ++++++++++++++---- .../Runtime/Metrics/PacketLossMetricsTests.cs | 13 ++ .../Runtime/Transports/UnityTransportTests.cs | 2 + 4 files changed, 126 insertions(+), 24 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/AssemblyInfo.cs b/com.unity.netcode.gameobjects/Runtime/AssemblyInfo.cs index 0dddbabbd4..935d27338a 100644 --- a/com.unity.netcode.gameobjects/Runtime/AssemblyInfo.cs +++ b/com.unity.netcode.gameobjects/Runtime/AssemblyInfo.cs @@ -12,3 +12,4 @@ [assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")] [assembly: InternalsVisibleTo("Unity.Netcode.TestHelpers.Runtime")] [assembly: InternalsVisibleTo("Unity.Netcode.Adapter.UTP")] +[assembly: InternalsVisibleTo("Unity.Multiplayer.Tools.Adapters.Ngo1WithUtp2")] diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index c27b7f3089..fa3ec74627 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -1,3 +1,10 @@ +// NetSim Implementation compilation boilerplate +// All references to UNITY_MP_TOOLS_NETSIM_IMPLEMENTATION_ENABLED should be defined in the same way, +// as any discrepancies are likely to result in build failures +#if UNITY_EDITOR || (DEVELOPMENT_BUILD && !UNITY_MP_TOOLS_NETSIM_DISABLED_IN_DEVELOP) || (!DEVELOPMENT_BUILD && UNITY_MP_TOOLS_NETSIM_ENABLED_IN_RELEASE) +#define UNITY_MP_TOOLS_NETSIM_IMPLEMENTATION_ENABLED +#endif + using System; using System.Collections.Generic; using UnityEngine; @@ -343,6 +350,9 @@ public struct SimulatorParameters /// - packet jitter (variances in latency, see: https://en.wikipedia.org/wiki/Jitter) /// - packet drop rate (packet loss) /// +#if UTP_TRANSPORT_2_0_ABOVE + [Obsolete("DebugSimulator is no longer supported and has no effect. Use Network Simulator from the Multiplayer Tools package.", false)] +#endif public SimulatorParameters DebugSimulator = new SimulatorParameters { PacketDelayMS = 0, @@ -359,6 +369,10 @@ private struct PacketLossCache public float PacketLoss; }; + internal static event Action TransportInitialized; + internal static event Action TransportDisposed; + internal NetworkDriver NetworkDriver => m_Driver; + private PacketLossCache m_PacketLossCache = new PacketLossCache(); private State m_State = State.Disconnected; @@ -402,6 +416,8 @@ private void InitDriver() out m_UnreliableFragmentedPipeline, out m_UnreliableSequencedFragmentedPipeline, out m_ReliableSequencedPipeline); + + TransportInitialized?.Invoke(GetInstanceID(), NetworkDriver); } private void DisposeInternals() @@ -419,6 +435,8 @@ private void DisposeInternals() } m_SendQueue.Clear(); + + TransportDisposed?.Invoke(GetInstanceID()); } private NetworkPipeline SelectSendPipeline(NetworkDelivery delivery) @@ -652,6 +670,9 @@ public void SetConnectionData(NetworkEndpoint endPoint, NetworkEndpoint listenEn /// Packet delay in milliseconds. /// Packet jitter in milliseconds. /// Packet drop percentage. +#if UTP_TRANSPORT_2_0_ABOVE + [Obsolete("SetDebugSimulatorParameters is no longer supported and has no effect. Use Network Simulator from the Multiplayer Tools package.", false)] +#endif public void SetDebugSimulatorParameters(int packetDelay, int packetJitter, int dropRate) { if (m_Driver.IsCreated) @@ -1329,7 +1350,25 @@ public override void Shutdown() m_ServerClientId = 0; } - private void ConfigureSimulator() +#if UTP_TRANSPORT_2_0_ABOVE + private void ConfigureSimulatorForUtp2() + { + // As DebugSimulator is deprecated, the 'packetDelayMs', 'packetJitterMs' and 'packetDropPercentage' + // parameters are set to the default and are supposed to be changed using Network Simulator tool instead. + m_NetworkSettings.WithSimulatorStageParameters( + maxPacketCount: 300, // TODO Is there any way to compute a better value? + maxPacketSize: NetworkParameterConstants.MTU, + packetDelayMs: 0, + packetJitterMs: 0, + packetDropPercentage: 0, + randomSeed: DebugSimulatorRandomSeed ?? (uint)System.Diagnostics.Stopwatch.GetTimestamp() + , mode: ApplyMode.AllPackets + ); + + m_NetworkSettings.WithNetworkSimulatorParameters(); + } +#else + private void ConfigureSimulatorForUtp1() { m_NetworkSettings.WithSimulatorStageParameters( maxPacketCount: 300, // TODO Is there any way to compute a better value? @@ -1338,11 +1377,9 @@ private void ConfigureSimulator() packetJitterMs: DebugSimulator.PacketJitterMS, packetDropPercentage: DebugSimulator.PacketDropRate, randomSeed: DebugSimulatorRandomSeed ?? (uint)System.Diagnostics.Stopwatch.GetTimestamp() -#if UTP_TRANSPORT_2_0_ABOVE - , mode: ApplyMode.AllPackets -#endif ); } +#endif /// /// Creates the internal NetworkDriver @@ -1357,14 +1394,14 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, out NetworkPipeline unreliableSequencedFragmentedPipeline, out NetworkPipeline reliableSequencedPipeline) { -#if MULTIPLAYER_TOOLS_1_0_0_PRE_7 -#if !UTP_TRANSPORT_2_0_ABOVE +#if MULTIPLAYER_TOOLS_1_0_0_PRE_7 && !UTP_TRANSPORT_2_0_ABOVE NetworkPipelineStageCollection.RegisterPipelineStage(new NetworkMetricsPipelineStage()); #endif -#endif -#if UNITY_EDITOR || DEVELOPMENT_BUILD - ConfigureSimulator(); +#if UTP_TRANSPORT_2_0_ABOVE && UNITY_MP_TOOLS_NETSIM_IMPLEMENTATION_ENABLED + ConfigureSimulatorForUtp2(); +#elif !UTP_TRANSPORT_2_0_ABOVE && (UNITY_EDITOR || DEVELOPMENT_BUILD) + ConfigureSimulatorForUtp1(); #endif m_NetworkSettings.WithNetworkConfigParameters( @@ -1395,21 +1432,36 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, driver = NetworkDriver.Create(m_NetworkSettings); #endif -#if MULTIPLAYER_TOOLS_1_0_0_PRE_7 -#if UTP_TRANSPORT_2_0_ABOVE - driver.RegisterPipelineStage(new NetworkMetricsPipelineStage()); +#if MULTIPLAYER_TOOLS_1_0_0_PRE_7 && UTP_TRANSPORT_2_0_ABOVE + driver.RegisterPipelineStage(new NetworkMetricsPipelineStage()); #endif + +#if !UTP_TRANSPORT_2_0_ABOVE + SetupPipelinesForUtp1(driver, + out unreliableFragmentedPipeline, + out unreliableSequencedFragmentedPipeline, + out reliableSequencedPipeline); +#else + SetupPipelinesForUtp2(driver, + out unreliableFragmentedPipeline, + out unreliableSequencedFragmentedPipeline, + out reliableSequencedPipeline); #endif + } +#if !UTP_TRANSPORT_2_0_ABOVE + private void SetupPipelinesForUtp1(NetworkDriver driver, + out NetworkPipeline unreliableFragmentedPipeline, + out NetworkPipeline unreliableSequencedFragmentedPipeline, + out NetworkPipeline reliableSequencedPipeline) + { #if UNITY_EDITOR || DEVELOPMENT_BUILD if (DebugSimulator.PacketDelayMS > 0 || DebugSimulator.PacketDropRate > 0) { unreliableFragmentedPipeline = driver.CreatePipeline( typeof(FragmentationPipelineStage), - typeof(SimulatorPipelineStage) -#if !UTP_TRANSPORT_2_0_ABOVE - , typeof(SimulatorPipelineStageInSend) -#endif + typeof(SimulatorPipelineStage), + typeof(SimulatorPipelineStageInSend) #if MULTIPLAYER_TOOLS_1_0_0_PRE_7 , typeof(NetworkMetricsPipelineStage) #endif @@ -1417,20 +1469,16 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, unreliableSequencedFragmentedPipeline = driver.CreatePipeline( typeof(FragmentationPipelineStage), typeof(UnreliableSequencedPipelineStage), - typeof(SimulatorPipelineStage) -#if !UTP_TRANSPORT_2_0_ABOVE - , typeof(SimulatorPipelineStageInSend) -#endif + typeof(SimulatorPipelineStage), + typeof(SimulatorPipelineStageInSend) #if MULTIPLAYER_TOOLS_1_0_0_PRE_7 , typeof(NetworkMetricsPipelineStage) #endif ); reliableSequencedPipeline = driver.CreatePipeline( typeof(ReliableSequencedPipelineStage), - typeof(SimulatorPipelineStage) -#if !UTP_TRANSPORT_2_0_ABOVE - , typeof(SimulatorPipelineStageInSend) -#endif + typeof(SimulatorPipelineStage), + typeof(SimulatorPipelineStageInSend) #if MULTIPLAYER_TOOLS_1_0_0_PRE_7 , typeof(NetworkMetricsPipelineStage) #endif @@ -1460,7 +1508,45 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, ); } } +#else + private void SetupPipelinesForUtp2(NetworkDriver driver, + out NetworkPipeline unreliableFragmentedPipeline, + out NetworkPipeline unreliableSequencedFragmentedPipeline, + out NetworkPipeline reliableSequencedPipeline) + { + unreliableFragmentedPipeline = driver.CreatePipeline( + typeof(FragmentationPipelineStage) +#if UNITY_MP_TOOLS_NETSIM_IMPLEMENTATION_ENABLED + , typeof(SimulatorPipelineStage) +#endif +#if MULTIPLAYER_TOOLS_1_0_0_PRE_7 + , typeof(NetworkMetricsPipelineStage) +#endif + ); + + unreliableSequencedFragmentedPipeline = driver.CreatePipeline( + typeof(FragmentationPipelineStage), + typeof(UnreliableSequencedPipelineStage) +#if UNITY_MP_TOOLS_NETSIM_IMPLEMENTATION_ENABLED + , typeof(SimulatorPipelineStage) +#endif +#if MULTIPLAYER_TOOLS_1_0_0_PRE_7 + , typeof(NetworkMetricsPipelineStage) +#endif + ); + + reliableSequencedPipeline = driver.CreatePipeline( + typeof(ReliableSequencedPipelineStage) +#if UNITY_MP_TOOLS_NETSIM_IMPLEMENTATION_ENABLED + , typeof(SimulatorPipelineStage) +#endif +#if MULTIPLAYER_TOOLS_1_0_0_PRE_7 + , typeof(NetworkMetricsPipelineStage) +#endif + ); + } +#endif // -------------- Utility Types ------------------------------------------------------------------------------- diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs index 2839189b34..4a008cfe5e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs @@ -8,6 +8,9 @@ using Unity.Netcode.TestHelpers.Runtime; using Unity.Netcode.TestHelpers.Runtime.Metrics; using Unity.Netcode.Transports.UTP; +#if UTP_TRANSPORT_2_0_ABOVE +using Unity.Networking.Transport.Utilities; +#endif using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests.Metrics @@ -26,7 +29,9 @@ public PacketLossMetricsTests() protected override void OnServerAndClientsCreated() { var clientTransport = (UnityTransport)m_ClientNetworkManagers[0].NetworkConfig.NetworkTransport; +#if !UTP_TRANSPORT_2_0_ABOVE clientTransport.SetDebugSimulatorParameters(0, 0, m_PacketLossRate); +#endif // Determined through trial and error. With both UTP 1.2 and 2.0, this random seed // results in an effective packet loss percentage between 22% and 28%. Future UTP @@ -63,6 +68,14 @@ public IEnumerator TrackPacketLossAsClient() double packetLossRateMinRange = (m_PacketLossRate - m_PacketLossRangeDelta) / 100d; double packetLossRateMaxrange = (m_PacketLossRate + m_PacketLossRangeDelta) / 100d; var clientNetworkManager = m_ClientNetworkManagers[0]; + +#if UTP_TRANSPORT_2_0_ABOVE + var clientTransport = (UnityTransport)clientNetworkManager.NetworkConfig.NetworkTransport; + clientTransport.NetworkDriver.CurrentSettings.TryGet(out var parameters); + parameters.PacketDropPercentage = m_PacketLossRate; + clientTransport.NetworkDriver.ModifySimulatorStageParameters(parameters); +#endif + var waitForPacketLossMetric = new WaitForGaugeMetricValues((clientNetworkManager.NetworkMetrics as NetworkMetrics).Dispatcher, NetworkMetricTypes.PacketLoss, metric => packetLossRateMinRange <= metric && metric <= packetLossRateMaxrange); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs index 481366e216..8c9257cf4d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs @@ -340,6 +340,7 @@ public IEnumerator SendCompletesOnUnreliableSendQueueOverflow() yield return null; } +#if !UTP_TRANSPORT_2_0_ABOVE // Check that simulator parameters are effective. We only check with the drop rate, because // that's easy to check and we only really want to make sure the simulator parameters are // configured properly (the simulator pipeline stage is already well-tested in UTP). @@ -394,6 +395,7 @@ public IEnumerator CurrentRttReportedCorrectly() yield return null; } +#endif [UnityTest] public IEnumerator SendQueuesFlushedOnShutdown([ValueSource("k_DeliveryParameters")] NetworkDelivery delivery) From cf27374f85e8ab5f22c06bf1455c2b766ea940d6 Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Fri, 23 Sep 2022 09:23:31 -0400 Subject: [PATCH 067/138] perf: performance improvements MTT-4641 #2176 (#2204) * Performance Improvement - Merge if else into return - Merge .rotation and .position into .SetPositionAndRotation - Remove .ToString() call on strings - Use TryGetComponent to reduce garbage allocation - Merge .Where().Count() calls into .Count() call - Remove indirect gameObject call - Remove redundant null check - Use native .Count instead of LINQ method - Use faster string comparison * style: adjusting to code standards Co-authored-by: smitdylan2001 --- .../Components/NetworkTransform.cs | 12 ++---------- .../Editor/CodeGen/INetworkMessageILPP.cs | 2 +- .../Editor/CodeGen/INetworkSerializableILPP.cs | 2 +- .../Editor/CodeGen/NetworkBehaviourILPP.cs | 2 +- .../Editor/NetworkBehaviourEditor.cs | 6 ++---- .../Editor/NetworkManagerHelper.cs | 5 ++--- .../Runtime/Core/NetworkManager.cs | 17 ++++++----------- .../Runtime/Core/NetworkObject.cs | 5 ++--- .../Runtime/SceneManagement/SceneEventData.cs | 8 ++++---- .../Runtime/Spawning/NetworkPrefabHandler.cs | 3 +-- .../Runtime/Transports/UTP/UnityTransport.cs | 2 +- 11 files changed, 23 insertions(+), 41 deletions(-) diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index 8d1c919484..2a6fe62305 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -282,14 +282,7 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade { // Go ahead and mark the local state dirty or not dirty as well /// - if (HasPositionChange || HasRotAngleChange || HasScaleChange) - { - IsDirty = true; - } - else - { - IsDirty = false; - } + IsDirty = HasPositionChange || HasRotAngleChange || HasScaleChange; } } } @@ -1150,8 +1143,7 @@ private void SetStateInternal(Vector3 pos, Quaternion rot, Vector3 scale, bool s } else { - transform.position = pos; - transform.rotation = rot; + transform.SetPositionAndRotation(pos, rot); } transform.localScale = scale; m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldTeleport; diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs index 2a25561f66..d1d63b2e6f 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs @@ -73,7 +73,7 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) } catch (Exception e) { - m_Diagnostics.AddError((e.ToString() + e.StackTrace.ToString()).Replace("\n", "|").Replace("\r", "|")); + m_Diagnostics.AddError((e.ToString() + e.StackTrace).Replace("\n", "|").Replace("\r", "|")); } } else diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkSerializableILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkSerializableILPP.cs index 48b28a182e..22b2f14888 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkSerializableILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkSerializableILPP.cs @@ -91,7 +91,7 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) } catch (Exception e) { - m_Diagnostics.AddError((e.ToString() + e.StackTrace.ToString()).Replace("\n", "|").Replace("\r", "|")); + m_Diagnostics.AddError((e.ToString() + e.StackTrace).Replace("\n", "|").Replace("\r", "|")); } } else diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index bb6163d642..0d71c0f016 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -76,7 +76,7 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) } catch (Exception e) { - m_Diagnostics.AddError((e.ToString() + e.StackTrace.ToString()).Replace("\n", "|").Replace("\r", "|")); + m_Diagnostics.AddError((e.ToString() + e.StackTrace).Replace("\n", "|").Replace("\r", "|")); } } else diff --git a/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs index 7da70eac4c..95e353a8ca 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs @@ -263,8 +263,7 @@ public static void CheckForNetworkObject(GameObject gameObject, bool networkObje // Now get the root parent transform to the current GameObject (or itself) var rootTransform = GetRootParentTransform(gameObject.transform); - var networkManager = rootTransform.GetComponent(); - if (networkManager == null) + if (!rootTransform.TryGetComponent(out var networkManager)) { networkManager = rootTransform.GetComponentInChildren(); } @@ -299,8 +298,7 @@ public static void CheckForNetworkObject(GameObject gameObject, bool networkObje // Otherwise, check to see if there is any NetworkObject from the root GameObject down to all children. // If not, notify the user that NetworkBehaviours require that the relative GameObject has a NetworkObject component. - var networkObject = rootTransform.GetComponent(); - if (networkObject == null) + if (!rootTransform.TryGetComponent(out var networkObject)) { networkObject = rootTransform.GetComponentInChildren(); diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs index 543040472e..4c4aabdd5d 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs @@ -64,7 +64,7 @@ private static void ScenesInBuildActiveSceneCheck() { var scenesList = EditorBuildSettings.scenes.ToList(); var activeScene = SceneManager.GetActiveScene(); - var isSceneInBuildSettings = scenesList.Where((c) => c.path == activeScene.path).Count() == 1; + var isSceneInBuildSettings = scenesList.Count((c) => c.path == activeScene.path) == 1; var networkManager = Object.FindObjectOfType(); if (!isSceneInBuildSettings && networkManager != null) { @@ -109,9 +109,8 @@ private static void EditorApplication_hierarchyChanged() public void CheckAndNotifyUserNetworkObjectRemoved(NetworkManager networkManager, bool editorTest = false) { // Check for any NetworkObject at the same gameObject relative layer - var networkObject = networkManager.gameObject.GetComponent(); - if (networkObject == null) + if (!networkManager.gameObject.TryGetComponent(out var networkObject)) { // if none is found, check to see if any children have a NetworkObject networkObject = networkManager.gameObject.GetComponentInChildren(); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index f30a6bd384..61ca0fef5c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -51,7 +51,7 @@ public class NetworkManager : MonoBehaviour, INetworkUpdateSystem internal static string PrefabDebugHelper(NetworkPrefab networkPrefab) { - return $"{nameof(NetworkPrefab)} \"{networkPrefab.Prefab.gameObject.name}\""; + return $"{nameof(NetworkPrefab)} \"{networkPrefab.Prefab.name}\""; } internal NetworkBehaviourUpdater BehaviourUpdater { get; set; } @@ -187,8 +187,7 @@ public void Send(ulong clientId, NetworkDelivery delivery, FastBufferWriter batc /// a that is either the override or if no overrides exist it returns the same as the one passed in as a parameter public GameObject GetNetworkPrefabOverride(GameObject gameObject) { - var networkObject = gameObject.GetComponent(); - if (networkObject != null) + if (gameObject.TryGetComponent(out var networkObject)) { if (NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(networkObject.GlobalObjectIdHash)) { @@ -524,8 +523,7 @@ internal void OnValidate() var networkPrefabGo = networkPrefab?.Prefab; if (networkPrefabGo != null) { - var networkObject = networkPrefabGo.GetComponent(); - if (networkObject == null) + if (!networkPrefabGo.TryGetComponent(out var networkObject)) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { @@ -700,8 +698,7 @@ private bool ShouldAddPrefab(NetworkPrefab networkPrefab, out uint sourcePrefabG } else if (networkPrefab.Override == NetworkPrefabOverride.None) { - networkObject = networkPrefab.Prefab.GetComponent(); - if (networkObject == null) + if (!networkPrefab.Prefab.TryGetComponent(out networkObject)) { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { @@ -747,8 +744,7 @@ private bool ShouldAddPrefab(NetworkPrefab networkPrefab, out uint sourcePrefabG } else { - networkObject = networkPrefab.SourcePrefabToOverride.GetComponent(); - if (networkObject == null) + if (!networkPrefab.SourcePrefabToOverride.TryGetComponent(out networkObject)) { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { @@ -973,8 +969,7 @@ private void Initialize(bool server) // If we have a player prefab, then we need to verify it is in the list of NetworkPrefabOverrideLinks for client side spawning. if (NetworkConfig.PlayerPrefab != null) { - var playerPrefabNetworkObject = NetworkConfig.PlayerPrefab.GetComponent(); - if (playerPrefabNetworkObject != null) + if (NetworkConfig.PlayerPrefab.TryGetComponent(out var playerPrefabNetworkObject)) { //In the event there is no NetworkPrefab entry (i.e. no override for default player prefab) if (!NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(playerPrefabNetworkObject diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 1d3ae5be02..3f4d7a3ebc 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -435,7 +435,7 @@ public static void NetworkHide(List networkObjects, ulong clientI private void OnDestroy() { if (NetworkManager != null && NetworkManager.IsListening && NetworkManager.IsServer == false && IsSpawned && - (IsSceneObject == null || (IsSceneObject != null && IsSceneObject.Value != true))) + (IsSceneObject == null || (IsSceneObject.Value != true))) { throw new NotServerException($"Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call {nameof(Destroy)} or {nameof(Despawn)} on the server/host instead."); } @@ -690,8 +690,7 @@ private void OnTransformParentChanged() var parentTransform = transform.parent; if (parentTransform != null) { - var parentObject = transform.parent.GetComponent(); - if (parentObject == null) + if (!transform.parent.TryGetComponent(out var parentObject)) { transform.parent = m_CachedParent; Debug.LogException(new InvalidParentException($"Invalid parenting, {nameof(NetworkObject)} moved under a non-{nameof(NetworkObject)} parent")); diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs index 197f38f030..69640d15cf 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs @@ -398,9 +398,9 @@ internal void WriteSceneSynchronizationData(FastBufferWriter writer) int totalBytes = 0; // Write the number of NetworkObjects we are serializing - BytePacker.WriteValuePacked(writer, m_NetworkObjectsSync.Count()); + BytePacker.WriteValuePacked(writer, m_NetworkObjectsSync.Count); // Serialize all NetworkObjects that are spawned - for (var i = 0; i < m_NetworkObjectsSync.Count(); ++i) + for (var i = 0; i < m_NetworkObjectsSync.Count; ++i) { var noStart = writer.Position; var sceneObject = m_NetworkObjectsSync[i].GetMessageSceneObject(TargetClientId); @@ -411,9 +411,9 @@ internal void WriteSceneSynchronizationData(FastBufferWriter writer) } // Write the number of despawned in-scene placed NetworkObjects - writer.WriteValueSafe(m_DespawnedInSceneObjectsSync.Count()); + writer.WriteValueSafe(m_DespawnedInSceneObjectsSync.Count); // Write the scene handle and GlobalObjectIdHash value - for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count(); ++i) + for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count; ++i) { var noStart = writer.Position; var sceneObject = m_DespawnedInSceneObjectsSync[i].GetMessageSceneObject(TargetClientId); diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs index ec8ca8d372..a123dc2170 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs @@ -119,8 +119,7 @@ public void RegisterHostGlobalObjectIdHashValues(GameObject sourceNetworkPrefab, // Now we register all foreach (var gameObject in networkPrefabOverrides) { - var targetNetworkObject = gameObject.GetComponent(); - if (targetNetworkObject != null) + if (gameObject.TryGetComponent(out var targetNetworkObject)) { if (!m_PrefabInstanceToPrefabAsset.ContainsKey(targetNetworkObject.GlobalObjectIdHash)) { diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index fa3ec74627..1a8e8c01e2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -306,7 +306,7 @@ private static NetworkEndpoint ParseNetworkEndpoint(string ip, ushort port) /// /// Endpoint (IP address and port) server will listen/bind on. /// - public NetworkEndpoint ListenEndPoint => ParseNetworkEndpoint((ServerListenAddress == string.Empty) ? Address : ServerListenAddress, Port); + public NetworkEndpoint ListenEndPoint => ParseNetworkEndpoint((ServerListenAddress?.Length == 0) ? Address : ServerListenAddress, Port); } /// From b93652e1e52b4a9fdeb4835707209fed51868e32 Mon Sep 17 00:00:00 2001 From: Kitty Draper <284434+ShadauxCat@users.noreply.github.com> Date: Fri, 23 Sep 2022 11:55:35 -0500 Subject: [PATCH 068/138] fix: Fixed compile error in 2022.2 (#2214) * Fix compile error in 2022.2+ --- com.unity.netcode.gameobjects/Editor/HiddenScriptEditor.cs | 5 ++++- .../Editor/com.unity.netcode.editor.asmdef | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Editor/HiddenScriptEditor.cs b/com.unity.netcode.gameobjects/Editor/HiddenScriptEditor.cs index 58000fd503..1236fb8332 100644 --- a/com.unity.netcode.gameobjects/Editor/HiddenScriptEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/HiddenScriptEditor.cs @@ -1,5 +1,7 @@ using Unity.Netcode.Components; +#if UNITY_UNET_PRESENT using Unity.Netcode.Transports.UNET; +#endif using Unity.Netcode.Transports.UTP; using UnityEditor; @@ -20,7 +22,7 @@ public override void OnInspectorGUI() EditorGUI.EndChangeCheck(); } } - +#if UNITY_UNET_PRESENT /// /// Internal use. Hides the script field for UNetTransport. /// @@ -29,6 +31,7 @@ public class UNetTransportEditor : HiddenScriptEditor { } +#endif /// /// Internal use. Hides the script field for UnityTransport. diff --git a/com.unity.netcode.gameobjects/Editor/com.unity.netcode.editor.asmdef b/com.unity.netcode.gameobjects/Editor/com.unity.netcode.editor.asmdef index 0648dd9341..29b759dc49 100644 --- a/com.unity.netcode.gameobjects/Editor/com.unity.netcode.editor.asmdef +++ b/com.unity.netcode.gameobjects/Editor/com.unity.netcode.editor.asmdef @@ -14,6 +14,11 @@ "expression": "", "define": "MULTIPLAYER_TOOLS" }, + { + "name": "Unity", + "expression": "(0,2022.2.0a5)", + "define": "UNITY_UNET_PRESENT" + }, { "name": "com.unity.modules.animation", "expression": "", From d8310f2d6c16cd37983baade150d11034fe17946 Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Tue, 27 Sep 2022 13:46:19 -0400 Subject: [PATCH 069/138] feat: Dynamically size the UnityTransport send queues [MTT-2816] (#2212) * Make BatchedSendQueue dynamically-sized * Remove MaxSendQueueSize as a serialized field * Add CHANGELOG entries * Add PR number to CHANGELOG entries * Add extra tests for BatchedSendQueue * Address some PR comments * Keep the standards checks happy * Address a review comment + some cleanup --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 + .../Transports/UTP/BatchedSendQueue.cs | 115 +++++++++++++----- .../Runtime/Transports/UTP/UnityTransport.cs | 43 +++++-- .../Transports/BatchedSendQueueTests.cs | 94 ++++++++++++-- .../Transports/UnityTransportTestHelpers.cs | 6 +- .../Runtime/Transports/UnityTransportTests.cs | 19 +-- 6 files changed, 216 insertions(+), 63 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 4828204f7e..0aa8d1478d 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -15,6 +15,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- The send queues of `UnityTransport` are now dynamically-sized. This means that there shouldn't be any need anymore to tweak the 'Max Send Queue Size' value. In fact, this field is now removed from the inspector and will not be serialized anymore. It is still possible to set it manually using the `MaxSendQueueSize` property, but it is not recommended to do so aside from some specific needs (e.g. limiting the amount of memory used by the send queues in very constrained environments). (#2212) +- As a consequence of the above change, the `UnityTransport.InitialMaxSendQueueSize` field is now deprecated. There is no default value anymore since send queues are dynamically-sized. (#2212) - The debug simulator in `UnityTransport` is now non-deterministic. Its random number generator used to be seeded with a constant value, leading to the same pattern of packet drops, delays, and jitter in every run. (#2196) ### Fixed diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedSendQueue.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedSendQueue.cs index e811ff7a3e..0f3929b1f8 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedSendQueue.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedSendQueue.cs @@ -8,22 +8,30 @@ namespace Unity.Netcode.Transports.UTP /// Queue for batched messages meant to be sent through UTP. /// /// Messages should be pushed on the queue with . To send batched - /// messages, call with the obtained from - /// . This will fill the writer with as many messages as - /// possible. If the send is successful, call to remove the data from the - /// queue. + /// messages, call or + /// with the obtained from . + /// This will fill the writer with as many messages/bytes as possible. If the send is + /// successful, call to remove the data from the queue. /// /// This is meant as a companion to , which should be used to /// read messages sent with this queue. /// internal struct BatchedSendQueue : IDisposable { - private NativeArray m_Data; + // Note that we're using NativeList basically like a growable NativeArray, where the length + // of the list is the capacity of our array. (We can't use the capacity of the list as our + // queue capacity because NativeList may elect to set it higher than what we'd set it to + // with SetCapacity, which breaks the logic of our code.) + private NativeList m_Data; private NativeArray m_HeadTailIndices; + private int m_MaximumCapacity; + private int m_MinimumCapacity; /// Overhead that is added to each message in the queue. public const int PerMessageOverhead = sizeof(int); + internal const int MinimumMinimumCapacity = 4096; + // Indices into m_HeadTailIndicies. private const int k_HeadInternalIndex = 0; private const int k_TailInternalIndex = 1; @@ -43,18 +51,33 @@ private int TailIndex } public int Length => TailIndex - HeadIndex; - + public int Capacity => m_Data.Length; public bool IsEmpty => HeadIndex == TailIndex; - public bool IsCreated => m_Data.IsCreated; /// Construct a new empty send queue. /// Maximum capacity of the send queue. public BatchedSendQueue(int capacity) { - m_Data = new NativeArray(capacity, Allocator.Persistent); + // Make sure the maximum capacity will be even. + m_MaximumCapacity = capacity + (capacity & 1); + + // We pick the minimum capacity such that if we keep doubling it, we'll eventually hit + // the maximum capacity exactly. The alternative would be to use capacities that are + // powers of 2, but this can lead to over-allocating quite a bit of memory (especially + // since we expect maximum capacities to be in the megabytes range). The approach taken + // here avoids this issue, at the cost of not having allocations of nice round sizes. + m_MinimumCapacity = m_MaximumCapacity; + while (m_MinimumCapacity / 2 >= MinimumMinimumCapacity) + { + m_MinimumCapacity /= 2; + } + + m_Data = new NativeList(m_MinimumCapacity, Allocator.Persistent); m_HeadTailIndices = new NativeArray(2, Allocator.Persistent); + m_Data.ResizeUninitialized(m_MinimumCapacity); + HeadIndex = 0; TailIndex = 0; } @@ -68,22 +91,28 @@ public void Dispose() } } + /// Write a raw buffer to a DataStreamWriter. + private unsafe void WriteBytes(ref DataStreamWriter writer, byte* data, int length) + { +#if UTP_TRANSPORT_2_0_ABOVE + writer.WriteBytesUnsafe(data, length); +#else + writer.WriteBytes(data, length); +#endif + } + /// Append data at the tail of the queue. No safety checks. private void AppendDataAtTail(ArraySegment data) { unsafe { - var writer = new DataStreamWriter((byte*)m_Data.GetUnsafePtr() + TailIndex, m_Data.Length - TailIndex); + var writer = new DataStreamWriter((byte*)m_Data.GetUnsafePtr() + TailIndex, Capacity - TailIndex); writer.WriteInt(data.Count); fixed (byte* dataPtr = data.Array) { -#if UTP_TRANSPORT_2_0_ABOVE - writer.WriteBytesUnsafe(dataPtr + data.Offset, data.Count); -#else - writer.WriteBytes(dataPtr + data.Offset, data.Count); -#endif + WriteBytes(ref writer, dataPtr + data.Offset, data.Count); } } @@ -104,16 +133,16 @@ public bool PushMessage(ArraySegment message) } // Check if there's enough room after the current tail index. - if (m_Data.Length - TailIndex >= sizeof(int) + message.Count) + if (Capacity - TailIndex >= sizeof(int) + message.Count) { AppendDataAtTail(message); return true; } - // Check if there would be enough room if we moved data at the beginning of m_Data. - if (m_Data.Length - TailIndex + HeadIndex >= sizeof(int) + message.Count) + // Move the data at the beginning of of m_Data. Either it will leave enough space for + // the message, or we'll grow m_Data and will want the data at the beginning anyway. + if (HeadIndex > 0 && Length > 0) { - // Move the data back at the beginning of m_Data. unsafe { UnsafeUtility.MemMove(m_Data.GetUnsafePtr(), (byte*)m_Data.GetUnsafePtr() + HeadIndex, Length); @@ -121,12 +150,38 @@ public bool PushMessage(ArraySegment message) TailIndex = Length; HeadIndex = 0; + } + // If there's enough space left at the end for the message, now is a good time to trim + // the capacity of m_Data if it got very large. We define "very large" here as having + // more than 75% of m_Data unused after adding the new message. + if (Capacity - TailIndex >= sizeof(int) + message.Count) + { AppendDataAtTail(message); + + while (TailIndex < Capacity / 4 && Capacity > m_MinimumCapacity) + { + m_Data.ResizeUninitialized(Capacity / 2); + } + return true; } - return false; + // If we get here we need to grow m_Data until the data fits (or it's too large). + while (Capacity - TailIndex < sizeof(int) + message.Count) + { + // Can't grow m_Data anymore. Message simply won't fit. + if (Capacity * 2 > m_MaximumCapacity) + { + return false; + } + + m_Data.ResizeUninitialized(Capacity * 2); + } + + // If we get here we know there's now enough room for the message. + AppendDataAtTail(message); + return true; } /// @@ -153,11 +208,13 @@ public int FillWriterWithMessages(ref DataStreamWriter writer) unsafe { + var dataPtr = (byte*)m_Data.GetUnsafePtr() + HeadIndex; + #if UTP_TRANSPORT_2_0_ABOVE - var slice = m_Data.GetSubArray(HeadIndex, Length); + var slice = NativeArray.ConvertExistingDataToNativeArray(dataPtr, Length, Allocator.None); var reader = new DataStreamReader(slice); #else - var reader = new DataStreamReader((byte*)m_Data.GetUnsafePtr() + HeadIndex, Length); + var reader = new DataStreamReader(dataPtr, Length); #endif var writerAvailable = writer.Capacity; @@ -177,11 +234,7 @@ public int FillWriterWithMessages(ref DataStreamWriter writer) writer.WriteInt(messageLength); var messageOffset = HeadIndex + reader.GetBytesRead(); -#if UTP_TRANSPORT_2_0_ABOVE - writer.WriteBytesUnsafe((byte*)m_Data.GetUnsafePtr() + messageOffset, messageLength); -#else - writer.WriteBytes((byte*)m_Data.GetUnsafePtr() + messageOffset, messageLength); -#endif + WriteBytes(ref writer, (byte*)m_Data.GetUnsafePtr() + messageOffset, messageLength); writerAvailable -= sizeof(int) + messageLength; readerOffset += sizeof(int) + messageLength; @@ -218,11 +271,7 @@ public int FillWriterWithBytes(ref DataStreamWriter writer) unsafe { -#if UTP_TRANSPORT_2_0_ABOVE - writer.WriteBytesUnsafe((byte*)m_Data.GetUnsafePtr() + HeadIndex, copyLength); -#else - writer.WriteBytes((byte*)m_Data.GetUnsafePtr() + HeadIndex, copyLength); -#endif + WriteBytes(ref writer, (byte*)m_Data.GetUnsafePtr() + HeadIndex, copyLength); } return copyLength; @@ -236,10 +285,14 @@ public int FillWriterWithBytes(ref DataStreamWriter writer) /// Number of bytes to consume from the queue. public void Consume(int size) { + // Adjust the head/tail indices such that we consume the given size. if (size >= Length) { HeadIndex = 0; TailIndex = 0; + + // This is a no-op if m_Data is already at minimum capacity. + m_Data.ResizeUninitialized(m_MinimumCapacity); } else { diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index 1a8e8c01e2..d31a42fce6 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -137,8 +137,13 @@ private enum State /// /// The default maximum send queue size /// + [Obsolete("MaxSendQueueSize is now determined dynamically (can still be set programmatically using the MaxSendQueueSize property). This initial value is not used anymore.", false)] public const int InitialMaxSendQueueSize = 16 * InitialMaxPayloadSize; + // Maximum reliable throughput, assuming the full reliable window can be sent on every + // frame at 60 FPS. This will be a large over-estimation in any realistic scenario. + private const int k_MaxReliableThroughput = (NetworkParameterConstants.MTU * 32 * 60) / 1000; // bytes per millisecond + private static ConnectionAddressData s_DefaultConnectionAddressData = new ConnectionAddressData { Address = "127.0.0.1", Port = 7777, ServerListenAddress = string.Empty }; #pragma warning disable IDE1006 // Naming Styles @@ -193,15 +198,17 @@ public int MaxPayloadSize set => m_MaxPayloadSize = value; } - [Tooltip("The maximum size in bytes of the transport send queue. The send queue accumulates messages for batching and stores messages when other internal send queues are full. If you routinely observe an error about too many in-flight packets, try increasing this.")] - [SerializeField] - private int m_MaxSendQueueSize = InitialMaxSendQueueSize; + private int m_MaxSendQueueSize = 0; /// The maximum size in bytes of the transport send queue. /// /// The send queue accumulates messages for batching and stores messages when other internal - /// send queues are full. If you routinely observe an error about too many in-flight packets, - /// try increasing this. + /// send queues are full. Note that there should not be any need to set this value manually + /// since the send queue size is dynamically sized based on need. + /// + /// This value should only be set if you have particular requirements (e.g. if you want to + /// limit the memory usage of the send queues). Note however that setting this value too low + /// can easily lead to disconnections under heavy traffic. /// public int MaxSendQueueSize { @@ -551,11 +558,6 @@ private static RelayConnectionData ConvertConnectionData(byte[] connectionData) } } - internal void SetMaxPayloadSize(int maxPayloadSize) - { - m_MaxPayloadSize = maxPayloadSize; - } - private void SetProtocol(ProtocolType inProtocol) { m_ProtocolType = inProtocol; @@ -1211,7 +1213,23 @@ public override void Send(ulong clientId, ArraySegment payload, NetworkDel var sendTarget = new SendTarget(clientId, pipeline); if (!m_SendQueue.TryGetValue(sendTarget, out var queue)) { - queue = new BatchedSendQueue(Math.Max(m_MaxSendQueueSize, m_MaxPayloadSize)); + // The maximum size of a send queue is determined according to the disconnection + // timeout. The idea being that if the send queue contains enough reliable data that + // sending it all out would take longer than the disconnection timeout, then there's + // no point storing even more in the queue (it would be like having a ping higher + // than the disconnection timeout, which is far into the realm of unplayability). + // + // The throughput used to determine what consists the maximum send queue size is + // the maximum theoritical throughput of the reliable pipeline assuming we only send + // on each update at 60 FPS, which turns out to be around 2.688 MB/s. + // + // Note that we only care about reliable throughput for send queues because that's + // the only case where a full send queue causes a connection loss. Full unreliable + // send queues are dealt with by flushing it out to the network or simply dropping + // new messages if that fails. + var maxCapacity = m_MaxSendQueueSize > 0 ? m_MaxSendQueueSize : m_DisconnectTimeoutMS * k_MaxReliableThroughput; + + queue = new BatchedSendQueue(Math.Max(maxCapacity, m_MaxPayloadSize)); m_SendQueue.Add(sendTarget, queue); } @@ -1225,8 +1243,7 @@ public override void Send(ulong clientId, ArraySegment payload, NetworkDel var ngoClientId = NetworkManager?.TransportIdToClientId(clientId) ?? clientId; Debug.LogError($"Couldn't add payload of size {payload.Count} to reliable send queue. " + - $"Closing connection {ngoClientId} as reliability guarantees can't be maintained. " + - $"Perhaps 'Max Send Queue Size' ({m_MaxSendQueueSize}) is too small for workload."); + $"Closing connection {ngoClientId} as reliability guarantees can't be maintained."); if (clientId == m_ServerClientId) { diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedSendQueueTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedSendQueueTests.cs index 34a8a53400..eac6b73f6e 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedSendQueueTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedSendQueueTests.cs @@ -8,8 +8,9 @@ namespace Unity.Netcode.EditorTests { public class BatchedSendQueueTests { - private const int k_TestQueueCapacity = 1024; - private const int k_TestMessageSize = 42; + private const int k_TestQueueCapacity = 16 * 1024; + private const int k_TestMessageSize = 1020; + private const int k_NumMessagesToFillQueue = k_TestQueueCapacity / (k_TestMessageSize + BatchedSendQueue.PerMessageOverhead); private ArraySegment m_TestMessage; @@ -52,7 +53,14 @@ public void BatchedSendQueue_NotCreatedAfterDispose() } [Test] - public void BatchedSendQueue_PushMessageReturnValue() + public void BatchedSendQueue_InitialCapacityLessThanMaximum() + { + using var q = new BatchedSendQueue(k_TestQueueCapacity); + Assert.AreEqual(q.Capacity, BatchedSendQueue.MinimumMinimumCapacity); + } + + [Test] + public void BatchedSendQueue_PushMessage_ReturnValue() { // Will fit a single test message, but not two (with overhead included). var queueCapacity = (k_TestMessageSize * 2) + BatchedSendQueue.PerMessageOverhead; @@ -64,7 +72,7 @@ public void BatchedSendQueue_PushMessageReturnValue() } [Test] - public void BatchedSendQueue_LengthIncreasedAfterPush() + public void BatchedSendQueue_PushMessage_IncreasesLength() { using var q = new BatchedSendQueue(k_TestQueueCapacity); @@ -73,13 +81,12 @@ public void BatchedSendQueue_LengthIncreasedAfterPush() } [Test] - public void BatchedSendQueue_PushedMessageGeneratesCopy() + public void BatchedSendQueue_PushMessage_SucceedsAfterConsume() { var messageLength = k_TestMessageSize + BatchedSendQueue.PerMessageOverhead; var queueCapacity = messageLength * 2; using var q = new BatchedSendQueue(queueCapacity); - using var data = new NativeArray(k_TestQueueCapacity, Allocator.Temp); q.PushMessage(m_TestMessage); q.PushMessage(m_TestMessage); @@ -89,6 +96,60 @@ public void BatchedSendQueue_PushedMessageGeneratesCopy() Assert.AreEqual(queueCapacity, q.Length); } + [Test] + public void BatchedSendQueue_PushMessage_GrowsDataIfNeeded() + { + using var q = new BatchedSendQueue(k_TestQueueCapacity); + var messageLength = k_TestMessageSize + BatchedSendQueue.PerMessageOverhead; + + Assert.AreEqual(q.Capacity, BatchedSendQueue.MinimumMinimumCapacity); + + var numMessagesToFillMinimum = BatchedSendQueue.MinimumMinimumCapacity / messageLength; + for (int i = 0; i < numMessagesToFillMinimum; i++) + { + q.PushMessage(m_TestMessage); + } + + Assert.AreEqual(q.Capacity, BatchedSendQueue.MinimumMinimumCapacity); + + q.PushMessage(m_TestMessage); + + Assert.AreEqual(q.Capacity, BatchedSendQueue.MinimumMinimumCapacity * 2); + } + + [Test] + public void BatchedSendQueue_PushMessage_DoesNotGrowDataPastMaximum() + { + using var q = new BatchedSendQueue(k_TestQueueCapacity); + + for (int i = 0; i < k_NumMessagesToFillQueue; i++) + { + Assert.IsTrue(q.PushMessage(m_TestMessage)); + } + + Assert.AreEqual(q.Capacity, k_TestQueueCapacity); + Assert.IsFalse(q.PushMessage(m_TestMessage)); + Assert.AreEqual(q.Capacity, k_TestQueueCapacity); + } + + [Test] + public void BatchedSendQueue_PushMessage_TrimsDataAfterGrowing() + { + using var q = new BatchedSendQueue(k_TestQueueCapacity); + var messageLength = k_TestMessageSize + BatchedSendQueue.PerMessageOverhead; + + for (int i = 0; i < k_NumMessagesToFillQueue; i++) + { + Assert.IsTrue(q.PushMessage(m_TestMessage)); + } + + Assert.AreEqual(q.Capacity, k_TestQueueCapacity); + q.Consume(messageLength * (k_NumMessagesToFillQueue - 1)); + Assert.IsTrue(q.PushMessage(m_TestMessage)); + Assert.AreEqual(messageLength * 2, q.Length); + Assert.AreEqual(q.Capacity, BatchedSendQueue.MinimumMinimumCapacity * 2); + } + [Test] public void BatchedSendQueue_FillWriterWithMessages_ReturnValue() { @@ -227,7 +288,7 @@ public void BatchedSendQueue_FillWriterWithBytes_WriterCapacityEqualToLength() } [Test] - public void BatchedSendQueue_ConsumeLessThanLength() + public void BatchedSendQueue_Consume_LessThanLength() { using var q = new BatchedSendQueue(k_TestQueueCapacity); @@ -240,7 +301,7 @@ public void BatchedSendQueue_ConsumeLessThanLength() } [Test] - public void BatchedSendQueue_ConsumeExactLength() + public void BatchedSendQueue_Consume_ExactLength() { using var q = new BatchedSendQueue(k_TestQueueCapacity); @@ -252,7 +313,7 @@ public void BatchedSendQueue_ConsumeExactLength() } [Test] - public void BatchedSendQueue_ConsumeMoreThanLength() + public void BatchedSendQueue_Consume_MoreThanLength() { using var q = new BatchedSendQueue(k_TestQueueCapacity); @@ -262,5 +323,20 @@ public void BatchedSendQueue_ConsumeMoreThanLength() Assert.AreEqual(0, q.Length); Assert.True(q.IsEmpty); } + + [Test] + public void BatchedSendQueue_Consume_TrimsDataOnEmpty() + { + using var q = new BatchedSendQueue(k_TestQueueCapacity); + + for (int i = 0; i < k_NumMessagesToFillQueue; i++) + { + q.PushMessage(m_TestMessage); + } + + Assert.AreEqual(q.Capacity, k_TestQueueCapacity); + q.Consume(k_TestQueueCapacity); + Assert.AreEqual(q.Capacity, BatchedSendQueue.MinimumMinimumCapacity); + } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTestHelpers.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTestHelpers.cs index 254cc8689f..31add35154 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTestHelpers.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTestHelpers.cs @@ -36,14 +36,16 @@ public static IEnumerator WaitForNetworkEvent(NetworkEvent type, List events, int maxPayloadSize = UnityTransport.InitialMaxPayloadSize) + public static void InitializeTransport(out UnityTransport transport, out List events, + int maxPayloadSize = UnityTransport.InitialMaxPayloadSize, int maxSendQueueSize = 0) { var logger = new TransportEventLogger(); events = logger.Events; transport = new GameObject().AddComponent(); transport.OnTransportEvent += logger.HandleEvent; - transport.SetMaxPayloadSize(maxPayloadSize); + transport.MaxPayloadSize = maxPayloadSize; + transport.MaxSendQueueSize = maxSendQueueSize; transport.Initialize(); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs index 8c9257cf4d..b08e49eb6b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs @@ -273,8 +273,10 @@ public IEnumerator ReceiveMultipleClients([ValueSource("k_DeliveryParameters")] [UnityTest] public IEnumerator DisconnectOnReliableSendQueueOverflow() { - InitializeTransport(out m_Server, out m_ServerEvents); - InitializeTransport(out m_Client1, out m_Client1Events); + const int maxSendQueueSize = 16 * 1024; + + InitializeTransport(out m_Server, out m_ServerEvents, maxSendQueueSize: maxSendQueueSize); + InitializeTransport(out m_Client1, out m_Client1Events, maxSendQueueSize: maxSendQueueSize); m_Server.StartServer(); m_Client1.StartClient(); @@ -283,7 +285,7 @@ public IEnumerator DisconnectOnReliableSendQueueOverflow() m_Server.Shutdown(); - var numSends = (UnityTransport.InitialMaxSendQueueSize / 1024); + var numSends = (maxSendQueueSize / 1024); for (int i = 0; i < numSends; i++) { @@ -292,8 +294,7 @@ public IEnumerator DisconnectOnReliableSendQueueOverflow() } LogAssert.Expect(LogType.Error, "Couldn't add payload of size 1024 to reliable send queue. " + - $"Closing connection {m_Client1.ServerClientId} as reliability guarantees can't be maintained. " + - $"Perhaps 'Max Send Queue Size' ({UnityTransport.InitialMaxSendQueueSize}) is too small for workload."); + $"Closing connection {m_Client1.ServerClientId} as reliability guarantees can't be maintained."); Assert.AreEqual(2, m_Client1Events.Count); Assert.AreEqual(NetworkEvent.Disconnect, m_Client1Events[1].Type); @@ -308,15 +309,17 @@ public IEnumerator DisconnectOnReliableSendQueueOverflow() [UnityPlatform(exclude = new[] { RuntimePlatform.Switch, RuntimePlatform.PS4, RuntimePlatform.PS5 })] public IEnumerator SendCompletesOnUnreliableSendQueueOverflow() { - InitializeTransport(out m_Server, out m_ServerEvents); - InitializeTransport(out m_Client1, out m_Client1Events); + const int maxSendQueueSize = 16 * 1024; + + InitializeTransport(out m_Server, out m_ServerEvents, maxSendQueueSize: maxSendQueueSize); + InitializeTransport(out m_Client1, out m_Client1Events, maxSendQueueSize: maxSendQueueSize); m_Server.StartServer(); m_Client1.StartClient(); yield return WaitForNetworkEvent(NetworkEvent.Connect, m_Client1Events); - var numSends = (UnityTransport.InitialMaxSendQueueSize / 1024) + 1; + var numSends = (maxSendQueueSize / 1024) + 1; for (int i = 0; i < numSends; i++) { From c3471ea41ac7d78199a29f5ca7f54dbe8454b7f3 Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Tue, 27 Sep 2022 15:53:20 -0400 Subject: [PATCH 070/138] fix: Restarting UnityTransport after it failed to start [MTT-3452] (#2220) * Add test for transport restart failure * Fix restart after transport failure * Add CHANGELOG entry * Add PR number to CHANGELOG entry --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Transports/UTP/UnityTransport.cs | 12 ++++---- .../Editor/Transports/UnityTransportTests.cs | 30 +++++++++++++++---- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 0aa8d1478d..a61a9bdbc7 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -21,6 +21,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue #1924 where `UnityTransport` would fail to restart after a first failure (even if what caused the initial failure was addressed). (#2220) - Fixed issue where `NetworkTransform.SetStateServerRpc` and `NetworkTransform.SetStateClientRpc` were not honoring local vs world space settings when applying the position and rotation. (#2203) - Fixed ILPP `TypeLoadException` on WebGL on MacOS Editor and potentially other platforms. (#2199) - Implicit conversion of NetworkObjectReference to GameObject will now return null instead of throwing an exception if the referenced object could not be found (i.e., was already despawned) (#2158) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index d31a42fce6..3c8b549912 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -1292,9 +1292,9 @@ public override bool StartClient() } var succeeded = ClientBindAndConnect(); - if (!succeeded) + if (!succeeded && m_Driver.IsCreated) { - Shutdown(); + m_Driver.Dispose(); } return succeeded; } @@ -1319,16 +1319,16 @@ public override bool StartServer() { case ProtocolType.UnityTransport: succeeded = ServerBindAndListen(ConnectionData.ListenEndPoint); - if (!succeeded) + if (!succeeded && m_Driver.IsCreated) { - Shutdown(); + m_Driver.Dispose(); } return succeeded; case ProtocolType.RelayUnityTransport: succeeded = StartRelayServer(); - if (!succeeded) + if (!succeeded && m_Driver.IsCreated) { - Shutdown(); + m_Driver.Dispose(); } return succeeded; default: diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs index 20b83b0379..c26cc81b61 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs @@ -1,6 +1,7 @@ using NUnit.Framework; using Unity.Netcode.Transports.UTP; using UnityEngine; +using UnityEngine.TestTools; namespace Unity.Netcode.EditorTests { @@ -8,7 +9,7 @@ public class UnityTransportTests { // Check that starting a server doesn't immediately result in faulted tasks. [Test] - public void BasicInitServer() + public void UnityTransport_BasicInitServer() { UnityTransport transport = new GameObject().AddComponent(); transport.Initialize(); @@ -20,7 +21,7 @@ public void BasicInitServer() // Check that starting a client doesn't immediately result in faulted tasks. [Test] - public void BasicInitClient() + public void UnityTransport_BasicInitClient() { UnityTransport transport = new GameObject().AddComponent(); transport.Initialize(); @@ -32,7 +33,7 @@ public void BasicInitClient() // Check that we can't restart a server. [Test] - public void NoRestartServer() + public void UnityTransport_NoRestartServer() { UnityTransport transport = new GameObject().AddComponent(); transport.Initialize(); @@ -45,7 +46,7 @@ public void NoRestartServer() // Check that we can't restart a client. [Test] - public void NoRestartClient() + public void UnityTransport_NoRestartClient() { UnityTransport transport = new GameObject().AddComponent(); transport.Initialize(); @@ -58,7 +59,7 @@ public void NoRestartClient() // Check that we can't start both a server and client on the same transport. [Test] - public void NotBothServerAndClient() + public void UnityTransport_NotBothServerAndClient() { UnityTransport transport; @@ -80,5 +81,24 @@ public void NotBothServerAndClient() transport.Shutdown(); } + + // Check that restarting after failure succeeds. + [Test] + public void UnityTransport_RestartSucceedsAfterFailure() + { + UnityTransport transport = new GameObject().AddComponent(); + transport.Initialize(); + + transport.SetConnectionData("127.0.0.", 4242); + Assert.False(transport.StartServer()); + + LogAssert.Expect(LogType.Error, "Invalid network endpoint: 127.0.0.:4242."); + LogAssert.Expect(LogType.Error, "Server failed to bind"); + + transport.SetConnectionData("127.0.0.1", 4242); + Assert.True(transport.StartServer()); + + transport.Shutdown(); + } } } From 0995b2d2e503da9a303fa0a81ab744340d89054a Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Wed, 28 Sep 2022 13:25:27 -0400 Subject: [PATCH 071/138] fix: Set default nonce of Relay server data to 1 (#2223) --- .../Runtime/Transports/UTP/UnityTransport.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index 3c8b549912..186ee81204 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -598,7 +598,7 @@ public void SetRelayServerData(string ipv4Address, ushort port, byte[] allocatio hostConnectionData = connectionData; } - m_RelayServerData = new RelayServerData(ref serverEndpoint, 0, ref allocationId, ref connectionData, ref hostConnectionData, ref key, isSecure); + m_RelayServerData = new RelayServerData(ref serverEndpoint, 1, ref allocationId, ref connectionData, ref hostConnectionData, ref key, isSecure); SetProtocol(ProtocolType.RelayUnityTransport); } From 995269980eacb7f4668ff35cb8245b384aaa5b70 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 28 Sep 2022 13:18:53 -0500 Subject: [PATCH 072/138] fix: client authoritative NetworkAnimator skips sending to 2nd player when in server mode (#2127) co-authored-by: Vadim Tsvetkov co-authored-by: Andrei Soprachev * fix: animator syncronization for >2 clients * fix: remove redundant rpc call when host * test This includes coverage for running in server mode only as well as validates the fix where a second client will receive updates. co-authored-by: Noel Stephens - Unity * fix Fixed some additional issues with trigger synchronization. Removed the bool from ProcessAnimationMessageQueue as it was no longer needed. * fix This is a new approach to synchronizing transitions with late joining players without having to actually set the associated conditional trigger. By building a small list of all states and then building a quick lookup table, we can just synchronize the "transition state" which for late joining clients is a cross fade between the start and destination state. We synchronize the normalized time (where it was in the transition on the server side when the client connected) of the transition this way as well. Now, we just synchronize states (which some can be transition states). * test increased number of clients Fixed issue where the late joining client was not being shutdown at the end of the LateJoinSynchronizationTest. * update NetworkAnimator uses ISerializationCallbackReceiver to build its transition to states table for late joining client synchronization when a transition is ocurring. Co-authored-by: Vadim Tsvetkov Co-authored-by: Andrei Soprachev Co-authored-by: Unity Netcode CI <74025435+netcode-ci-service@users.noreply.github.com> --- com.unity.netcode.gameobjects/CHANGELOG.md | 4 +- .../Components/NetworkAnimator.cs | 589 +++++++++++++++--- .../CheckStateEnterCount.cs | 48 +- .../Runtime/Animation/AnimatorTestHelper.cs | 18 +- .../Runtime/Animation/NetworkAnimatorTests.cs | 38 +- 5 files changed, 582 insertions(+), 115 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index a61a9bdbc7..17e3a848e2 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -29,8 +29,10 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed Connection Approval Timeout not working client side. (#2164) - Fixed ClientRpcs always reporting in the profiler view as going to all clients, even when limited to a subset of clients by `ClientRpcParams`. (#2144) - Fixed RPC codegen failing to choose the correct extension methods for `FastBufferReader` and `FastBufferWriter` when the parameters were a generic type (i.e., List) and extensions for multiple instantiations of that type have been defined (i.e., List and List) (#2142) +- Fixed the issue where running a server (i.e. not host) the second player would not receive updates (unless a third player joined). (#2127) +- Fixed issue where late-joining client transition synchronization could fail when more than one transition was occurring.(#2127) - Fixed throwing an exception in `OnNetworkUpdate` causing other `OnNetworkUpdate` calls to not be executed. (#1739) -- Fixed synchronisation when Time.timeScale is set to 0. This changes timing update to use unscaled deltatime. Now network updates rate are independant from the local time scale. (#2171) +- Fixed synchronization when Time.timeScale is set to 0. This changes timing update to use unscaled deltatime. Now network updates rate are independent from the local time scale. (#2171) - Fixed not sending all NetworkVariables to all clients when a client connects to a server. (#1987) ## [1.0.2] - 2022-09-12 diff --git a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs index d914ac2a59..c6089f0546 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs @@ -1,14 +1,19 @@ #if COM_UNITY_MODULES_ANIMATION +using System; using System.Collections.Generic; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using UnityEngine; +#if UNITY_EDITOR +using UnityEditor.Animations; +#endif namespace Unity.Netcode.Components { internal class NetworkAnimatorStateChangeHandler : INetworkUpdateSystem { private NetworkAnimator m_NetworkAnimator; + private bool m_IsServer; /// /// This removes sending RPCs from within RPCs when the @@ -32,7 +37,14 @@ private void FlushMessages() foreach (var sendEntry in m_SendTriggerUpdates) { - m_NetworkAnimator.SendAnimTriggerClientRpc(sendEntry.AnimationTriggerMessage, sendEntry.ClientRpcParams); + if (!sendEntry.SendToServer) + { + m_NetworkAnimator.SendAnimTriggerClientRpc(sendEntry.AnimationTriggerMessage, sendEntry.ClientRpcParams); + } + else + { + m_NetworkAnimator.SendAnimTriggerServerRpc(sendEntry.AnimationTriggerMessage); + } } m_SendTriggerUpdates.Clear(); } @@ -44,8 +56,8 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) { case NetworkUpdateStage.PreUpdate: { - // Only the server forwards messages and synchronizes players - if (m_NetworkAnimator.NetworkManager.IsServer) + // Only the owner or the server send messages + if (m_NetworkAnimator.IsOwner || m_IsServer) { // Flush any pending messages FlushMessages(); @@ -125,6 +137,7 @@ internal void ProcessParameterUpdate(NetworkAnimator.ParametersUpdateMessage par private struct TriggerUpdate { + public bool SendToServer; public ClientRpcParams ClientRpcParams; public NetworkAnimator.AnimationTriggerMessage AnimationTriggerMessage; } @@ -134,11 +147,23 @@ private struct TriggerUpdate /// /// Invoked when a server needs to forward an update to a Trigger state /// - internal void SendTriggerUpdate(NetworkAnimator.AnimationTriggerMessage animationTriggerMessage, ClientRpcParams clientRpcParams = default) + internal void QueueTriggerUpdateToClient(NetworkAnimator.AnimationTriggerMessage animationTriggerMessage, ClientRpcParams clientRpcParams = default) { m_SendTriggerUpdates.Add(new TriggerUpdate() { ClientRpcParams = clientRpcParams, AnimationTriggerMessage = animationTriggerMessage }); } + internal void QueueTriggerUpdateToServer(NetworkAnimator.AnimationTriggerMessage animationTriggerMessage) + { + m_SendTriggerUpdates.Add(new TriggerUpdate() { AnimationTriggerMessage = animationTriggerMessage, SendToServer = true }); + } + + private Queue m_AnimationMessageQueue = new Queue(); + + internal void AddAnimationMessageToProcessQueue(NetworkAnimator.AnimationMessage message) + { + m_AnimationMessageQueue.Enqueue(message); + } + internal void DeregisterUpdate() { NetworkUpdateLoop.UnregisterNetworkUpdate(this, NetworkUpdateStage.PreUpdate); @@ -147,34 +172,278 @@ internal void DeregisterUpdate() internal NetworkAnimatorStateChangeHandler(NetworkAnimator networkAnimator) { m_NetworkAnimator = networkAnimator; + m_IsServer = networkAnimator.NetworkManager.IsServer; NetworkUpdateLoop.RegisterNetworkUpdate(this, NetworkUpdateStage.PreUpdate); } } - - /// /// NetworkAnimator enables remote synchronization of state for on network objects. /// [AddComponentMenu("Netcode/Network Animator")] [RequireComponent(typeof(Animator))] - public class NetworkAnimator : NetworkBehaviour + public class NetworkAnimator : NetworkBehaviour, ISerializationCallbackReceiver + { - internal struct AnimationMessage : INetworkSerializable + [Serializable] + internal class TransitionStateinfo { - // state hash per layer. if non-zero, then Play() this animation, skipping transitions - internal bool Transition; + public int Layer; + public int OriginatingState; + public int DestinationState; + public float TransitionDuration; + public int TriggerNameHash; + public int TransitionIndex; + } + + /// + /// Used to build the destination state to transition info table + /// + [HideInInspector] + [SerializeField] + internal List TransitionStateInfoList; + + // Used to get the associated transition information required to synchronize late joining clients with transitions + // [Layer][DestinationState][TransitionStateInfo] + private Dictionary> m_DestinationStateToTransitioninfo = new Dictionary>(); + + /// + /// Builds the m_DestinationStateToTransitioninfo lookup table + /// + private void BuildDestinationToTransitionInfoTable() + { + foreach (var entry in TransitionStateInfoList) + { + if (!m_DestinationStateToTransitioninfo.ContainsKey(entry.Layer)) + { + m_DestinationStateToTransitioninfo.Add(entry.Layer, new Dictionary()); + } + var destinationStateTransitionInfo = m_DestinationStateToTransitioninfo[entry.Layer]; + if (!destinationStateTransitionInfo.ContainsKey(entry.DestinationState)) + { + destinationStateTransitionInfo.Add(entry.DestinationState, entry); + } + } + } + + /// + /// Creates the + /// + private void BuildTransitionStateInfoList() + { +#if UNITY_EDITOR + TransitionStateInfoList = new List(); + var animatorController = m_Animator.runtimeAnimatorController as AnimatorController; + if (animatorController == null) + { + return; + } + + for (int x = 0; x < animatorController.layers.Length; x++) + { + var layer = animatorController.layers[x]; + + for (int y = 0; y < layer.stateMachine.states.Length; y++) + { + var animatorState = layer.stateMachine.states[y].state; + var transitions = layer.stateMachine.GetStateMachineTransitions(layer.stateMachine); + for (int z = 0; z < animatorState.transitions.Length; z++) + { + var transition = animatorState.transitions[z]; + if (transition.conditions.Length == 0 && transition.isExit) + { + // We don't need to worry about exit transitions with no conditions + continue; + } + + foreach (var condition in transition.conditions) + { + var parameterName = condition.parameter; + var parameters = animatorController.parameters; + foreach (var parameter in parameters) + { + switch (parameter.type) + { + case AnimatorControllerParameterType.Trigger: + { + // Match the condition with an existing trigger + if (parameterName == parameter.name) + { + var transitionInfo = new TransitionStateinfo() + { + Layer = x, + OriginatingState = animatorState.nameHash, + DestinationState = transition.destinationState.nameHash, + TransitionDuration = transition.duration, + TriggerNameHash = parameter.nameHash, + TransitionIndex = z + }; + TransitionStateInfoList.Add(transitionInfo); + } + break; + } + default: + break; + } + } + } + } + } + } +#endif + } + + public void OnAfterDeserialize() + { + BuildDestinationToTransitionInfoTable(); + } + + public void OnBeforeSerialize() + { + BuildTransitionStateInfoList(); + } + + internal struct AnimationState : INetworkSerializable + { + internal bool IsDirty; + // Not to be serialized, used for processing the animation state + internal bool HasBeenProcessed; internal int StateHash; internal float NormalizedTime; internal int Layer; internal float Weight; + // For synchronizing transitions + internal bool Transition; + // The StateHash is where the transition starts + // and the DestinationStateHash is the destination state + internal int DestinationStateHash; + public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { - serializer.SerializeValue(ref StateHash); - serializer.SerializeValue(ref NormalizedTime); - serializer.SerializeValue(ref Layer); - serializer.SerializeValue(ref Weight); + if (serializer.IsWriter) + { + var writer = serializer.GetFastBufferWriter(); + var writeSize = FastBufferWriter.GetWriteSize(Transition); + writeSize += FastBufferWriter.GetWriteSize(StateHash); + writeSize += FastBufferWriter.GetWriteSize(NormalizedTime); + writeSize += FastBufferWriter.GetWriteSize(Layer); + writeSize += FastBufferWriter.GetWriteSize(Weight); + if (Transition) + { + writeSize += FastBufferWriter.GetWriteSize(DestinationStateHash); + } + + if (!writer.TryBeginWrite(writeSize)) + { + throw new OverflowException($"[{GetType().Name}] Could not serialize: Out of buffer space."); + } + + writer.WriteValue(Transition); + writer.WriteValue(StateHash); + writer.WriteValue(NormalizedTime); + writer.WriteValue(Layer); + writer.WriteValue(Weight); + if (Transition) + { + writer.WriteValue(DestinationStateHash); + } + } + else + { + var reader = serializer.GetFastBufferReader(); + // Begin reading the Transition flag + if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(Transition))) + { + throw new OverflowException($"[{GetType().Name}] Could not deserialize: Out of buffer space."); + } + reader.ReadValue(out Transition); + + // Now determine what remains to be read + var readSize = FastBufferWriter.GetWriteSize(StateHash); + readSize += FastBufferWriter.GetWriteSize(NormalizedTime); + readSize += FastBufferWriter.GetWriteSize(Layer); + readSize += FastBufferWriter.GetWriteSize(Weight); + if (Transition) + { + readSize += FastBufferWriter.GetWriteSize(DestinationStateHash); + } + + // Now read the remaining information about this AnimationState + if (!reader.TryBeginRead(readSize)) + { + throw new OverflowException($"[{GetType().Name}] Could not deserialize: Out of buffer space."); + } + + reader.ReadValue(out StateHash); + reader.ReadValue(out NormalizedTime); + reader.ReadValue(out Layer); + reader.ReadValue(out Weight); + if (Transition) + { + reader.ReadValue(out DestinationStateHash); + } + } + } + } + + internal struct AnimationMessage : INetworkSerializable + { + // Not to be serialized, used for processing the animation message + internal bool HasBeenProcessed; + + // state hash per layer. if non-zero, then Play() this animation, skipping transitions + internal List AnimationStates; + + /// + /// Resets all AnimationStates' IsDirty flag + /// + internal void ClearDirty() + { + if (AnimationStates == null) + { + return; + } + for (int i = 0; i < AnimationStates.Count; i++) + { + var animationState = AnimationStates[i]; + animationState.IsDirty = false; + AnimationStates[i] = animationState; + } + } + + public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter + { + if (serializer.IsReader) + { + if (AnimationStates == null) + { + AnimationStates = new List(); + } + else if (AnimationStates.Count > 0) + { + AnimationStates.Clear(); + } + } + var count = AnimationStates.Count; + serializer.SerializeValue(ref count); + + var animationState = new AnimationState(); + for (int i = 0; i < count; i++) + { + if (serializer.IsWriter) + { + if (AnimationStates[i].IsDirty) + { + animationState = AnimationStates[i]; + } + } + serializer.SerializeNetworkSerializable(ref animationState); + if (serializer.IsReader) + { + AnimationStates.Add(animationState); + } + } } } @@ -223,7 +492,8 @@ protected virtual bool OnIsServerAuthoritative() return true; } - // Animators only support up to 32 params + // Animators only support up to 32 parameters + // TODO: Look into making this a range limited property private const int k_MaxAnimationParams = 32; private int[] m_TransitionHash; @@ -269,9 +539,9 @@ private void Cleanup() m_NetworkAnimatorStateChangeHandler = null; } - if (IsServer) + if (m_CachedNetworkManager != null) { - NetworkManager.OnClientConnectedCallback -= OnClientConnectedCallback; + m_CachedNetworkManager.OnClientConnectedCallback -= OnClientConnectedCallback; } if (m_CachedAnimatorParameters != null && m_CachedAnimatorParameters.IsCreated) @@ -293,40 +563,52 @@ public override void OnDestroy() private List m_ParametersToUpdate; private List m_ClientSendList; private ClientRpcParams m_ClientRpcParams; + private List m_AnimationMessageStates; + + // Only used in Cleanup + private NetworkManager m_CachedNetworkManager; + /// public override void OnNetworkSpawn() { - if (IsOwner || IsServer) - { - int layers = m_Animator.layerCount; - m_TransitionHash = new int[layers]; - m_AnimationHash = new int[layers]; - m_LayerWeights = new float[layers]; + int layers = m_Animator.layerCount; - if (IsServer) - { - NetworkManager.OnClientConnectedCallback += OnClientConnectedCallback; - } + // Initializing the below arrays for everyone handles an issue + // when running in owner authoritative mode and the owner changes. + m_TransitionHash = new int[layers]; + m_AnimationHash = new int[layers]; + m_LayerWeights = new float[layers]; - // Store off our current layer weights - for (int layer = 0; layer < m_Animator.layerCount; layer++) - { - float layerWeightNow = m_Animator.GetLayerWeight(layer); - if (layerWeightNow != m_LayerWeights[layer]) - { - m_LayerWeights[layer] = layerWeightNow; - } - } + if (IsServer) + { + m_ClientSendList = new List(128); + m_ClientRpcParams = new ClientRpcParams(); + m_ClientRpcParams.Send = new ClientRpcSendParams(); + m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList; + + // Cache the NetworkManager instance to remove the OnClientConnectedCallback subscription + m_CachedNetworkManager = NetworkManager; + NetworkManager.OnClientConnectedCallback += OnClientConnectedCallback; + } - if (IsServer) + // !! Note !! + // Do not clear this list. We re-use the AnimationState entries + // initialized below + m_AnimationMessageStates = new List(); + + // Store off our current layer weights and create our animation + // state entries per layer. + for (int layer = 0; layer < m_Animator.layerCount; layer++) + { + m_AnimationMessageStates.Add(new AnimationState()); + float layerWeightNow = m_Animator.GetLayerWeight(layer); + if (layerWeightNow != m_LayerWeights[layer]) { - m_ClientSendList = new List(128); - m_ClientRpcParams = new ClientRpcParams(); - m_ClientRpcParams.Send = new ClientRpcSendParams(); - m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList; + m_LayerWeights[layer] = layerWeightNow; } } + // Build our reference parameter values to detect when they change var parameters = m_Animator.parameters; m_CachedAnimatorParameters = new NativeArray(parameters.Length, Allocator.Persistent); m_ParametersToUpdate = new List(parameters.Length); @@ -373,6 +655,7 @@ public override void OnNetworkSpawn() m_NetworkAnimatorStateChangeHandler = new NetworkAnimatorStateChangeHandler(this); } + /// public override void OnNetworkDespawn() { Cleanup(); @@ -393,18 +676,25 @@ internal void ServerSynchronizeNewPlayer(ulong playerId) m_ParametersToUpdate.Add(i); } SendParametersUpdate(m_ClientRpcParams); + + var animationMessage = new AnimationMessage + { + // Assign the existing m_AnimationMessageStates list + AnimationStates = m_AnimationMessageStates + }; + for (int layer = 0; layer < m_Animator.layerCount; layer++) { AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(layer); - var stateHash = st.fullPathHash; var normalizedTime = st.normalizedTime; - var totalSpeed = st.speed * st.speedMultiplier; - var adjustedNormalizedMaxTime = totalSpeed > 0.0f ? 1.0f / totalSpeed : 0.0f; - // NOTE: - // When synchronizing, for now we will just complete the transition and - // synchronize the player to the next state being transitioned into - if (m_Animator.IsInTransition(layer)) + var isInTransition = m_Animator.IsInTransition(layer); + var animMsg = m_AnimationMessageStates[layer]; + + // Synchronizing transitions with trigger conditions for late joining clients is now + // handled by cross fading between the late joining client's current layer's AnimationState + // and the transition's destination AnimationState. + if (isInTransition) { var tt = m_Animator.GetAnimatorTransitionInfo(layer); var nextState = m_Animator.GetNextAnimatorStateInfo(layer); @@ -422,23 +712,41 @@ internal void ServerSynchronizeNewPlayer(ulong playerId) { normalizedTime = 0.0f; } - stateHash = nextState.fullPathHash; + + // Use the destination state to transition info lookup table to see if this is a transition we can + // synchronize using cross fading + if (m_DestinationStateToTransitioninfo.ContainsKey(layer)) + { + if (m_DestinationStateToTransitioninfo[layer].ContainsKey(nextState.shortNameHash)) + { + var destinationInfo = m_DestinationStateToTransitioninfo[layer][nextState.shortNameHash]; + stateHash = destinationInfo.OriginatingState; + // Set the destination state to cross fade to from the originating state + animMsg.DestinationStateHash = destinationInfo.DestinationState; + } + } } - var animMsg = new AnimationMessage - { - Transition = m_Animator.IsInTransition(layer), - StateHash = stateHash, - NormalizedTime = normalizedTime, - Layer = layer, - Weight = m_LayerWeights[layer] - }; + animMsg.Transition = isInTransition; // The only time this could be set to true + animMsg.StateHash = stateHash; // When a transition, this is the originating/starting state + animMsg.NormalizedTime = normalizedTime; + animMsg.Layer = layer; + animMsg.Weight = m_LayerWeights[layer]; + animMsg.IsDirty = true; + m_AnimationMessageStates[layer] = animMsg; + } + if (animationMessage.AnimationStates.Count > 0) + { // Server always send via client RPC - SendAnimStateClientRpc(animMsg, m_ClientRpcParams); + SendAnimStateClientRpc(animationMessage, m_ClientRpcParams); + animationMessage.ClearDirty(); } } + /// + /// Required for the server to synchronize newly joining players + /// private void OnClientConnectedCallback(ulong playerId) { m_NetworkAnimatorStateChangeHandler.SynchronizeClient(playerId); @@ -461,47 +769,59 @@ internal void CheckForAnimatorChanges() if (m_Animator.runtimeAnimatorController == null) { + if (NetworkManager.LogLevel == LogLevel.Developer) + { + Debug.LogError($"[{GetType().Name}] Could not find an assigned {nameof(RuntimeAnimatorController)}! Cannot check {nameof(Animator)} for changes in state!"); + } return; } int stateHash; float normalizedTime; - // This sends updates only if a layer change or transition is happening + var animationMessage = new AnimationMessage + { + // Assign the existing m_AnimationMessageStates list + AnimationStates = m_AnimationMessageStates + }; + + // This sends updates only if a layer's AnimationState changes for (int layer = 0; layer < m_Animator.layerCount; layer++) { AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(layer); var totalSpeed = st.speed * st.speedMultiplier; var adjustedNormalizedMaxTime = totalSpeed > 0.0f ? 1.0f / totalSpeed : 0.0f; - // determine if we have reached the end of our state time, if so we can skip - if (st.normalizedTime >= adjustedNormalizedMaxTime) - { - continue; - } - if (!CheckAnimStateChanged(out stateHash, out normalizedTime, layer)) { continue; } - var animMsg = new AnimationMessage + var animationState = new AnimationState { - Transition = m_Animator.IsInTransition(layer), + IsDirty = true, + Transition = false, // Only used during synchronization StateHash = stateHash, NormalizedTime = normalizedTime, Layer = layer, Weight = m_LayerWeights[layer] }; + animationMessage.AnimationStates.Add(animationState); + } + + // Make sure there is something to send + if (animationMessage.AnimationStates.Count > 0) + { if (!IsServer && IsOwner) { - SendAnimStateServerRpc(animMsg); + SendAnimStateServerRpc(animationMessage); } else { - SendAnimStateClientRpc(animMsg); + SendAnimStateClientRpc(animationMessage); } + animationMessage.ClearDirty(); } } @@ -596,7 +916,7 @@ unsafe private bool CheckParametersChanged() /// /// Checks if any of the Animator's states have changed /// - private unsafe bool CheckAnimStateChanged(out int stateHash, out float normalizedTime, int layer) + private bool CheckAnimStateChanged(out int stateHash, out float normalizedTime, int layer) { stateHash = 0; normalizedTime = 0; @@ -746,9 +1066,9 @@ internal unsafe void UpdateParameters(ParametersUpdateMessage parametersUpdate) } /// - /// Applies the AnimationMessage state to the Animator + /// Applies the AnimationState state to the Animator /// - private unsafe void UpdateAnimationState(AnimationMessage animationState) + internal void UpdateAnimationState(AnimationState animationState) { if (animationState.StateHash == 0) { @@ -756,9 +1076,46 @@ private unsafe void UpdateAnimationState(AnimationMessage animationState) } var currentState = m_Animator.GetCurrentAnimatorStateInfo(animationState.Layer); - if (currentState.fullPathHash != animationState.StateHash || m_Animator.IsInTransition(animationState.Layer) != animationState.Transition) + // If it is a transition, then we are synchronizing transitions in progress when a client late joins + if (animationState.Transition) + { + // We should have all valid entries for any animation state transition update + // Verify the AnimationState's assigned Layer exists + if (m_DestinationStateToTransitioninfo.ContainsKey(animationState.Layer)) + { + // Verify the inner-table has the destination AnimationState name hash + if (m_DestinationStateToTransitioninfo[animationState.Layer].ContainsKey(animationState.DestinationStateHash)) + { + // Make sure we are on the originating/starting state we are going to cross fade into + if (currentState.shortNameHash == animationState.StateHash) + { + // Get the transition state information + var transitionStateInfo = m_DestinationStateToTransitioninfo[animationState.Layer][animationState.DestinationStateHash]; + + // Cross fade from the current to the destination state for the transitions duration while starting at the server's current normalized time of the transition + m_Animator.CrossFade(transitionStateInfo.DestinationState, transitionStateInfo.TransitionDuration, transitionStateInfo.Layer, 0.0f, animationState.NormalizedTime); + } + else if (NetworkManager.LogLevel == LogLevel.Developer) + { + NetworkLog.LogWarning($"Current State Hash ({currentState.fullPathHash}) != AnimationState.StateHash ({animationState.StateHash})"); + } + } + else if (NetworkManager.LogLevel == LogLevel.Developer) + { + NetworkLog.LogError($"[DestinationState To Transition Info] Layer ({animationState.Layer}) sub-table does not contain destination state ({animationState.DestinationStateHash})!"); + } + } + else if (NetworkManager.LogLevel == LogLevel.Developer) + { + NetworkLog.LogError($"[DestinationState To Transition Info] Layer ({animationState.Layer}) does not exist!"); + } + } + else { - m_Animator.Play(animationState.StateHash, animationState.Layer, animationState.NormalizedTime); + if (currentState.fullPathHash != animationState.StateHash) + { + m_Animator.Play(animationState.StateHash, animationState.Layer, animationState.NormalizedTime); + } } m_Animator.SetLayerWeight(animationState.Layer, animationState.Weight); } @@ -781,7 +1138,7 @@ private unsafe void SendParametersUpdateServerRpc(ParametersUpdateMessage parame return; } UpdateParameters(parametersUpdate); - if (NetworkManager.ConnectedClientsIds.Count - 2 > 0) + if (NetworkManager.ConnectedClientsIds.Count > (IsHost ? 2 : 1)) { m_ClientSendList.Clear(); m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds); @@ -811,11 +1168,11 @@ internal unsafe void SendParametersUpdateClientRpc(ParametersUpdateMessage param /// The server sets its local state and then forwards the message to the remaining clients /// [ServerRpc] - private unsafe void SendAnimStateServerRpc(AnimationMessage animSnapshot, ServerRpcParams serverRpcParams = default) + private unsafe void SendAnimStateServerRpc(AnimationMessage animationMessage, ServerRpcParams serverRpcParams = default) { if (IsServerAuthoritative()) { - m_NetworkAnimatorStateChangeHandler.SendAnimationUpdate(animSnapshot); + m_NetworkAnimatorStateChangeHandler.SendAnimationUpdate(animationMessage); } else { @@ -823,15 +1180,21 @@ private unsafe void SendAnimStateServerRpc(AnimationMessage animSnapshot, Server { return; } - UpdateAnimationState(animSnapshot); - if (NetworkManager.ConnectedClientsIds.Count - 2 > 0) + + foreach (var animationState in animationMessage.AnimationStates) + { + UpdateAnimationState(animationState); + } + + m_NetworkAnimatorStateChangeHandler.AddAnimationMessageToProcessQueue(animationMessage); + if (NetworkManager.ConnectedClientsIds.Count > (IsHost ? 2 : 1)) { m_ClientSendList.Clear(); m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds); m_ClientSendList.Remove(serverRpcParams.Receive.SenderClientId); m_ClientSendList.Remove(NetworkManager.ServerClientId); m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList; - m_NetworkAnimatorStateChangeHandler.SendAnimationUpdate(animSnapshot, m_ClientRpcParams); + m_NetworkAnimatorStateChangeHandler.SendAnimationUpdate(animationMessage, m_ClientRpcParams); } } } @@ -840,16 +1203,25 @@ private unsafe void SendAnimStateServerRpc(AnimationMessage animSnapshot, Server /// Internally-called RPC client receiving function to update some animation state on a client /// [ClientRpc] - private unsafe void SendAnimStateClientRpc(AnimationMessage animSnapshot, ClientRpcParams clientRpcParams = default) + private unsafe void SendAnimStateClientRpc(AnimationMessage animationMessage, ClientRpcParams clientRpcParams = default) { - if (IsServer) + // This should never happen + if (IsHost) { + if (NetworkManager.LogLevel == LogLevel.Developer) + { + NetworkLog.LogWarning("Detected the Host is sending itself animation updates! Please report this issue."); + } return; } + var isServerAuthoritative = IsServerAuthoritative(); if (!isServerAuthoritative && !IsOwner || isServerAuthoritative) { - UpdateAnimationState(animSnapshot); + foreach (var animationState in animationMessage.AnimationStates) + { + UpdateAnimationState(animationState); + } } } @@ -858,44 +1230,67 @@ private unsafe void SendAnimStateClientRpc(AnimationMessage animSnapshot, Client /// The server sets its local state and then forwards the message to the remaining clients /// [ServerRpc] - private void SendAnimTriggerServerRpc(AnimationTriggerMessage animationTriggerMessage, ServerRpcParams serverRpcParams = default) + internal void SendAnimTriggerServerRpc(AnimationTriggerMessage animationTriggerMessage, ServerRpcParams serverRpcParams = default) { + // If it is server authoritative if (IsServerAuthoritative()) { - m_NetworkAnimatorStateChangeHandler.SendTriggerUpdate(animationTriggerMessage); + // The only condition where this should (be allowed to) happen is when the owner sends the server a trigger message + if (OwnerClientId == serverRpcParams.Receive.SenderClientId) + { + m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage); + } + else if (NetworkManager.LogLevel == LogLevel.Developer) + { + NetworkLog.LogWarning($"[Server Authoritative] Detected the a non-authoritative client is sending the server animation trigger updates. If you recently changed ownership of the {name} object, then this could be the reason."); + } } else { + // Ignore if a non-owner sent this. if (serverRpcParams.Receive.SenderClientId != OwnerClientId) { + if (NetworkManager.LogLevel == LogLevel.Developer) + { + NetworkLog.LogWarning($"[Owner Authoritative] Detected the a non-authoritative client is sending the server animation trigger updates. If you recently changed ownership of the {name} object, then this could be the reason."); + } return; } - // trigger the animation locally on the server... - m_Animator.SetBool(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet); - if (NetworkManager.ConnectedClientsIds.Count - 2 > 0) + // set the trigger locally on the server + InternalSetTrigger(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet); + + // send the message to all non-authority clients excluding the server and the owner + if (NetworkManager.ConnectedClientsIds.Count > (IsHost ? 2 : 1)) { m_ClientSendList.Clear(); m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds); m_ClientSendList.Remove(serverRpcParams.Receive.SenderClientId); m_ClientSendList.Remove(NetworkManager.ServerClientId); m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList; - m_NetworkAnimatorStateChangeHandler.SendTriggerUpdate(animationTriggerMessage, m_ClientRpcParams); + m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage, m_ClientRpcParams); } } + } + /// + /// See above + /// + private void InternalSetTrigger(int hash, bool isSet = true) + { + m_Animator.SetBool(hash, isSet); } /// /// Internally-called RPC client receiving function to update a trigger when the server wants to forward /// a trigger for a client to play / reset /// - /// the payload containing the trigger data to apply + /// the payload containing the trigger data to apply /// unused [ClientRpc] internal void SendAnimTriggerClientRpc(AnimationTriggerMessage animationTriggerMessage, ClientRpcParams clientRpcParams = default) { - m_Animator.SetBool(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet); + InternalSetTrigger(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet); } /// @@ -923,14 +1318,20 @@ public void SetTrigger(int hash, bool setTrigger = true) var animTriggerMessage = new AnimationTriggerMessage() { Hash = hash, IsTriggerSet = setTrigger }; if (IsServer) { - SendAnimTriggerClientRpc(animTriggerMessage); + /// as to why we queue + m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animTriggerMessage); + if (!IsHost) + { + InternalSetTrigger(hash); + } } else { - SendAnimTriggerServerRpc(animTriggerMessage); + /// as to why we queue + m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToServer(animTriggerMessage); if (!IsServerAuthoritative()) { - m_Animator.SetTrigger(hash); + InternalSetTrigger(hash); } } } diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CheckStateEnterCount.cs b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CheckStateEnterCount.cs index a95842402e..136a9c43c9 100644 --- a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CheckStateEnterCount.cs +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CheckStateEnterCount.cs @@ -9,6 +9,7 @@ public class CheckStateEnterCount : StateMachineBehaviour public static Dictionary>> OnStateEnterCounter = new Dictionary>>(); public static bool IsIntegrationTest; public static bool IsManualTestEnabled = true; + public static bool IsVerboseDebug = false; public static void ResetTest(bool isIntegrationTest = true) { @@ -17,6 +18,14 @@ public static void ResetTest(bool isIntegrationTest = true) OnStateEnterCounter.Clear(); } + public static void LogMessage(string message) + { + if (IsVerboseDebug) + { + Debug.Log(message); + } + } + public static bool AllStatesEnteredMatch(List clientIdsToCheck) { if (clientIdsToCheck.Contains(NetworkManager.ServerClientId)) @@ -26,7 +35,7 @@ public static bool AllStatesEnteredMatch(List clientIdsToCheck) if (!OnStateEnterCounter.ContainsKey(NetworkManager.ServerClientId)) { - Debug.Log($"Server has not entered into any states! OnStateEntered Entry Count ({OnStateEnterCounter.Count})"); + LogMessage($"Server has not entered into any states! OnStateEntered Entry Count ({OnStateEnterCounter.Count})"); return false; } @@ -38,7 +47,11 @@ public static bool AllStatesEnteredMatch(List clientIdsToCheck) var layerStates = layerEntries.Value; if (layerStates.Count > 1) { - Debug.Log($"Server layer ({layerIndex}) state was entered ({layerStates.Count}) times!"); + if (IsVerboseDebug) + { + + } + LogMessage($"Server layer ({layerIndex}) state was entered ({layerStates.Count}) times!"); return false; } @@ -46,7 +59,7 @@ public static bool AllStatesEnteredMatch(List clientIdsToCheck) { if (!OnStateEnterCounter.ContainsKey(clientId)) { - Debug.Log($"Client-{clientId} never entered into any state for layer index ({layerIndex})!"); + LogMessage($"Client-{clientId} never entered into any state for layer index ({layerIndex})!"); return false; } var clientStates = OnStateEnterCounter[clientId]; @@ -58,7 +71,7 @@ public static bool AllStatesEnteredMatch(List clientIdsToCheck) var clientLayerStateEntries = clientStates[layerIndex]; if (clientLayerStateEntries.Count > 1) { - Debug.Log($"Client-{clientId} layer ({layerIndex}) state was entered ({layerStates.Count}) times!"); + LogMessage($"Client-{clientId} layer ({layerIndex}) state was entered ({layerStates.Count}) times!"); return false; } // We should have only entered into the state once on the server @@ -68,7 +81,7 @@ public static bool AllStatesEnteredMatch(List clientIdsToCheck) // We just need to make sure we are looking at the same state if (clientAnimStateInfo.fullPathHash != serverAnimStateInfo.fullPathHash) { - Debug.Log($"Client-{clientId} full path hash ({clientAnimStateInfo.fullPathHash}) for layer ({layerIndex}) was not the same as the Server full path hash ({serverAnimStateInfo.fullPathHash})!"); + LogMessage($"Client-{clientId} full path hash ({clientAnimStateInfo.fullPathHash}) for layer ({layerIndex}) was not the same as the Server full path hash ({serverAnimStateInfo.fullPathHash})!"); return false; } } @@ -91,11 +104,36 @@ public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo OnStateEnterCounter[localClientId].Add(layerIndex, new List()); } OnStateEnterCounter[localClientId][layerIndex].Add(stateInfo); + LogMessage($"[{layerIndex}][{stateInfo.shortNameHash}][{stateInfo.normalizedTime}][{animator.IsInTransition(layerIndex)}]"); } else if (IsManualTestEnabled) { Debug.Log($"[{layerIndex}][{stateInfo.shortNameHash}][{stateInfo.normalizedTime}][{animator.IsInTransition(layerIndex)}]"); } } + + //public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) + //{ + // if (IsIntegrationTest) + // { + // var networkObject = animator.GetComponent(); + // var localClientId = networkObject.NetworkManager.IsServer ? NetworkManager.ServerClientId : networkObject.NetworkManager.LocalClientId; + // if (!OnStateEnterCounter.ContainsKey(localClientId)) + // { + // OnStateEnterCounter.Add(localClientId, new Dictionary>()); + // if (!OnStateEnterCounter[localClientId].ContainsKey(layerIndex)) + // { + // OnStateEnterCounter[localClientId].Add(layerIndex, new List()); + // } + // OnStateEnterCounter[localClientId][layerIndex].Add(stateInfo); + // LogMessage($"[{layerIndex}][{stateInfo.shortNameHash}][{stateInfo.normalizedTime}][{animator.IsInTransition(layerIndex)}]"); + // } + // } + // else if (IsManualTestEnabled) + // { + // Debug.Log($"[{layerIndex}][{stateInfo.shortNameHash}][{stateInfo.normalizedTime}][{animator.IsInTransition(layerIndex)}]"); + // } + // base.OnStateUpdate(animator, stateInfo, layerIndex); + //} } } diff --git a/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs b/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs index 4d09240e9b..fa721b4225 100644 --- a/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs +++ b/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs @@ -108,9 +108,25 @@ public bool GetCurrentTriggerState() return m_Animator.GetBool("TestTrigger"); } - public void SetTrigger(string name = "TestTrigger") + public void SetTrigger(string name = "TestTrigger", bool monitorTrigger = false) { m_NetworkAnimator.SetTrigger(name); + if (monitorTrigger && IsServer) + { + StartCoroutine(TriggerMonitor(name)); + } + } + + private System.Collections.IEnumerator TriggerMonitor(string triggerName) + { + var triggerStatus = m_Animator.GetBool(triggerName); + var waitTime = new WaitForSeconds(2 * (1.0f / NetworkManager.NetworkConfig.TickRate)); + while (triggerStatus) + { + Debug.Log($"[{triggerName}] is still triggered."); + yield return waitTime; + } + Debug.Log($"[{triggerName}] is no longer triggered."); } public void SetLateJoinParam(bool isEnabled) diff --git a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs index 4e28df0472..8d5fd4f52c 100644 --- a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs +++ b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs @@ -19,12 +19,14 @@ namespace TestProject.RuntimeTests /// Possibly we could build this at runtime, but for now it uses the same animator controller as the manual /// test does. /// + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] public class NetworkAnimatorTests : NetcodeIntegrationTest { private const string k_AnimatorObjectName = "AnimatorObject"; private const string k_OwnerAnimatorObjectName = "OwnerAnimatorObject"; - protected override int NumberOfClients => 1; + protected override int NumberOfClients => 3; private GameObject m_AnimationTestPrefab => m_AnimatorObjectPrefab ? m_AnimatorObjectPrefab as GameObject : null; private GameObject m_AnimationOwnerTestPrefab => m_OwnerAnimatorObjectPrefab ? m_OwnerAnimatorObjectPrefab as GameObject : null; @@ -32,6 +34,11 @@ public class NetworkAnimatorTests : NetcodeIntegrationTest private Object m_AnimatorObjectPrefab; private Object m_OwnerAnimatorObjectPrefab; + public NetworkAnimatorTests(HostOrServer hostOrServer) + { + m_UseHost = hostOrServer == HostOrServer.Host; + } + protected override void OnOneTimeSetup() { m_AnimatorObjectPrefab = Resources.Load(k_AnimatorObjectName); @@ -315,6 +322,7 @@ public IEnumerator LateJoinTriggerSynchronizationTest([Values] OwnerShipMode own { VerboseDebug($" ++++++++++++++++++ Late Join Trigger Test [{TriggerTest.Iteration}][{ownerShipMode}] Starting ++++++++++++++++++ "); TriggerTest.IsVerboseDebug = m_EnableVerboseDebug; + CheckStateEnterCount.IsVerboseDebug = m_EnableVerboseDebug; AnimatorTestHelper.IsTriggerTest = m_EnableVerboseDebug; bool isClientOwner = ownerShipMode == OwnerShipMode.ClientOwner; @@ -339,7 +347,7 @@ public IEnumerator LateJoinTriggerSynchronizationTest([Values] OwnerShipMode own else { // Set the animation trigger via the server - AnimatorTestHelper.ServerSideInstance.SetTrigger(); + AnimatorTestHelper.ServerSideInstance.SetTrigger("TestTrigger", m_EnableVerboseDebug); } // Wait for all triggers to fire @@ -367,14 +375,15 @@ public IEnumerator LateJoinTriggerSynchronizationTest([Values] OwnerShipMode own yield return CreateAndStartNewClient(); - Assert.IsTrue(m_ClientNetworkManagers.Length == 2, $"Newly created and connected client was not added to {nameof(m_ClientNetworkManagers)}!"); + Assert.IsTrue(m_ClientNetworkManagers.Length == NumberOfClients + 1, $"Newly created and connected client was not added to {nameof(m_ClientNetworkManagers)}!"); // Wait for it to spawn client-side yield return WaitForConditionOrTimeOut(WaitForClientsToInitialize); AssertOnTimeout($"Timed out waiting for the late joining client-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!"); - // Make sure the AnimatorTestHelper client side instances (plus host) is the same as the TotalClients - Assert.True((AnimatorTestHelper.ClientSideInstances.Count + 1) == TotalClients); + // Make sure the AnimatorTestHelper client side instances is the same as the TotalClients + var calculatedClients = (AnimatorTestHelper.ClientSideInstances.Count + (m_UseHost ? 1 : 0)); + Assert.True(calculatedClients == TotalClients, $"Number of client"); // Now check that the late joining client and all other clients are synchronized to the trigger yield return WaitForConditionOrTimeOut(() => AllTriggersDetected(ownerShipMode)); @@ -393,7 +402,7 @@ public IEnumerator LateJoinTriggerSynchronizationTest([Values] OwnerShipMode own yield return WaitForConditionOrTimeOut(() => ParameterValuesMatch(ownerShipMode, authoritativeMode, m_EnableVerboseDebug)); AssertOnTimeout($"Timed out waiting for the client-side parameters to match {m_ParameterValues.ValuesToString()}!"); - var newlyJoinedClient = m_ClientNetworkManagers[1]; + var newlyJoinedClient = m_ClientNetworkManagers[NumberOfClients]; yield return StopOneClient(newlyJoinedClient); VerboseDebug($" ------------------ Late Join Trigger Test [{TriggerTest.Iteration}][{ownerShipMode}] Stopping ------------------ "); } @@ -448,16 +457,17 @@ public IEnumerator LateJoinSynchronizationTest([Values] OwnerShipMode ownerShipM // Create and join a new client (late joining client) yield return CreateAndStartNewClient(); - Assert.IsTrue(m_ClientNetworkManagers.Length == 2, $"Newly created and connected client was not added to {nameof(m_ClientNetworkManagers)}!"); + Assert.IsTrue(m_ClientNetworkManagers.Length == NumberOfClients + 1, $"Newly created and connected client was not added to {nameof(m_ClientNetworkManagers)}!"); // Wait for the client to have spawned and the spawned prefab to be instantiated yield return WaitForConditionOrTimeOut(WaitForClientsToInitialize); AssertOnTimeout($"Timed out waiting for the late joining client-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!"); - // Make sure the AnimatorTestHelper client side instances (plus host) is the same as the TotalClients - Assert.True((AnimatorTestHelper.ClientSideInstances.Count + 1) == TotalClients); + // Make sure the AnimatorTestHelper client side instances is the same as the TotalClients + var calculatedClients = (AnimatorTestHelper.ClientSideInstances.Count + (m_UseHost ? 1 : 0)); + Assert.True(calculatedClients == TotalClients, $"Number of client"); - var lateJoinObjectInstance = AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[1].LocalClientId]; + var lateJoinObjectInstance = AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[NumberOfClients].LocalClientId]; yield return WaitForConditionOrTimeOut(() => Mathf.Approximately(lateJoinObjectInstance.transform.rotation.eulerAngles.y, 180.0f)); AssertOnTimeout($"[Late Join] Timed out waiting for cube to reach 180.0f!"); @@ -465,7 +475,7 @@ public IEnumerator LateJoinSynchronizationTest([Values] OwnerShipMode ownerShipM yield return WaitForConditionOrTimeOut(LateJoinClientSynchronized); AssertOnTimeout("[Late Join] Timed out waiting for newly joined client to have expected state synchronized!"); - var newlyJoinedClient = m_ClientNetworkManagers[1]; + var newlyJoinedClient = m_ClientNetworkManagers[NumberOfClients]; yield return StopOneClient(newlyJoinedClient); VerboseDebug($" ------------------ Late Join Synchronization Test [{TriggerTest.Iteration}][{ownerShipMode}] Stopping ------------------ "); } @@ -496,18 +506,18 @@ private void Server_OnClientConnectedCallback(ulong obj) /// private bool LateJoinClientSynchronized() { - if (!StateSyncTest.StatesEntered.ContainsKey(m_ClientNetworkManagers[1].LocalClientId)) + if (!StateSyncTest.StatesEntered.ContainsKey(m_ClientNetworkManagers[NumberOfClients].LocalClientId)) { VerboseDebug($"Late join client has not had any states synchronized yet!"); return false; } var serverStates = StateSyncTest.StatesEntered[m_ServerNetworkManager.LocalClientId]; - var clientStates = StateSyncTest.StatesEntered[m_ClientNetworkManagers[1].LocalClientId]; + var clientStates = StateSyncTest.StatesEntered[m_ClientNetworkManagers[NumberOfClients].LocalClientId]; if (serverStates.Count() != clientStates.Count()) { - VerboseDebug($"[Count][Server] {serverStates.Count} | [Client-{m_ClientNetworkManagers[1].LocalClientId}]{clientStates.Count}"); + VerboseDebug($"[Count][Server] {serverStates.Count} | [Client-{m_ClientNetworkManagers[NumberOfClients].LocalClientId}]{clientStates.Count}"); return false; } From e1abb024183a1e84ae0729a4f245e7499cbb0e3d Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Wed, 28 Sep 2022 17:24:24 -0400 Subject: [PATCH 073/138] fix: Issues when using UTP 2.0.0-exp.6 (#2224) * fix: Bind to server endpoint when using Relay * Fix NativeArray conversion * Simplify creation of DataStreamReader --- .../Runtime/Transports/UTP/BatchedSendQueue.cs | 13 +++---------- .../Runtime/Transports/UTP/UnityTransport.cs | 1 + 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedSendQueue.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedSendQueue.cs index 0f3929b1f8..c305884e16 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedSendQueue.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedSendQueue.cs @@ -208,19 +208,12 @@ public int FillWriterWithMessages(ref DataStreamWriter writer) unsafe { - var dataPtr = (byte*)m_Data.GetUnsafePtr() + HeadIndex; - -#if UTP_TRANSPORT_2_0_ABOVE - var slice = NativeArray.ConvertExistingDataToNativeArray(dataPtr, Length, Allocator.None); - var reader = new DataStreamReader(slice); -#else - var reader = new DataStreamReader(dataPtr, Length); -#endif + var reader = new DataStreamReader(m_Data.AsArray()); var writerAvailable = writer.Capacity; - var readerOffset = 0; + var readerOffset = HeadIndex; - while (readerOffset < Length) + while (readerOffset < TailIndex) { reader.SeekSet(readerOffset); var messageLength = reader.ReadInt(); diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index 186ee81204..68862254fb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -482,6 +482,7 @@ private bool ClientBindAndConnect() } m_NetworkSettings.WithRelayParameters(ref m_RelayServerData, m_HeartbeatTimeoutMS); + serverEndpoint = m_RelayServerData.Endpoint; } else { From ce04afbb7ab7f80eae98dd797799b3a48ea1f1c9 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 30 Sep 2022 16:32:44 -0500 Subject: [PATCH 074/138] fix: player objects do not honor the DontDestroyWithOwner property [MTT-4458] (#2225) * fix This fixes the issue with player objects not honoring the DontDestroyWithOwner property. The summary for DontDestroyWithOwner was incorrect. When true the NetworkObject's ownership is given back to the server. * test Adding a test to verify that when a client disconnects and the client's player object has DontDestroyWithOwner set to true that the NetworkObject's ownership is transferred back to the client. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Core/NetworkManager.cs | 13 +++-- .../Runtime/Core/NetworkObject.cs | 2 +- .../Tests/Runtime/DisconnectTests.cs | 48 +++++++++++++++++++ 4 files changed, 60 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 17e3a848e2..b581150df8 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -21,6 +21,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed the issue where player objects were not taking the `DontDestroyWithOwner` property into consideration when a client disconnected. (#2225) - Fixed issue #1924 where `UnityTransport` would fail to restart after a first failure (even if what caused the initial failure was addressed). (#2220) - Fixed issue where `NetworkTransform.SetStateServerRpc` and `NetworkTransform.SetStateClientRpc` were not honoring local vs world space settings when applying the position and rotation. (#2203) - Fixed ILPP `TypeLoadException` on WebGL on MacOS Editor and potentially other platforms. (#2199) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 61ca0fef5c..dd37f1f538 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1951,13 +1951,20 @@ private void OnClientDisconnectFromServer(ulong clientId) var playerObject = networkClient.PlayerObject; if (playerObject != null) { - if (PrefabHandler.ContainsHandler(ConnectedClients[clientId].PlayerObject.GlobalObjectIdHash)) + if (!playerObject.DontDestroyWithOwner) { - PrefabHandler.HandleNetworkPrefabDestroy(ConnectedClients[clientId].PlayerObject); + if (PrefabHandler.ContainsHandler(ConnectedClients[clientId].PlayerObject.GlobalObjectIdHash)) + { + PrefabHandler.HandleNetworkPrefabDestroy(ConnectedClients[clientId].PlayerObject); + } + else + { + Destroy(playerObject.gameObject); + } } else { - Destroy(playerObject.gameObject); + playerObject.RemoveOwnership(); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 3f4d7a3ebc..390ae58979 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -129,7 +129,7 @@ internal void GenerateGlobalObjectIdHash() /// /// Whether or not to destroy this object if it's owner is destroyed. - /// If false, the objects ownership will be given to the server. + /// If true, the objects ownership will be given to the server. /// public bool DontDestroyWithOwner; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs index 75907693ec..aae933b386 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs @@ -60,5 +60,53 @@ private void OnClientDisconnectCallback(ulong obj) { m_ClientDisconnected = true; } + + [UnityTest] + public IEnumerator ClientDisconnectPlayerObjectCleanup() + { + // create server and client instances + NetcodeIntegrationTestHelpers.Create(1, out NetworkManager server, out NetworkManager[] clients); + + // create prefab + var gameObject = new GameObject("PlayerObject"); + var networkObject = gameObject.AddComponent(); + networkObject.DontDestroyWithOwner = true; + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); + + server.NetworkConfig.PlayerPrefab = gameObject; + + for (int i = 0; i < clients.Length; i++) + { + clients[i].NetworkConfig.PlayerPrefab = gameObject; + } + + // start server and connect clients + NetcodeIntegrationTestHelpers.Start(false, server, clients); + + // wait for connection on client side + yield return NetcodeIntegrationTestHelpers.WaitForClientsConnected(clients); + + // wait for connection on server side + yield return NetcodeIntegrationTestHelpers.WaitForClientConnectedToServer(server); + + // disconnect the remote client + m_ClientDisconnected = false; + + server.OnClientDisconnectCallback += OnClientDisconnectCallback; + + var serverSideClientPlayer = server.ConnectedClients[clients[0].LocalClientId].PlayerObject; + + // Stopping the client is the same as the client disconnecting + NetcodeIntegrationTestHelpers.StopOneClient(clients[0]); + + var timeoutHelper = new TimeoutHelper(); + yield return NetcodeIntegrationTest.WaitForConditionOrTimeOut(() => m_ClientDisconnected, timeoutHelper); + + // ensure the object was destroyed + Assert.True(serverSideClientPlayer.IsOwnedByServer, $"The client's player object's ownership was not transferred back to the server!"); + + // cleanup + NetcodeIntegrationTestHelpers.Destroy(); + } } } From f647e66b05e905f4608aa7b2944b9c114076be7f Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 30 Sep 2022 18:12:26 -0500 Subject: [PATCH 075/138] fix: NetworkConfig cached hash was not being regenerated on clients for each unique ConnectionRequestMessage [MTT-4605] (#2226) MTT-4605 * fix This resolves the issue where clients would not regenerate their NetworkConfig's hash value if a client disconnected from one server and tried to join another server. * test This validates that passing false to the NetworkConfig.GetConfig method generates a unique hash value. * update updating the changelog entry with the PR number. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Core/NetworkManager.cs | 4 +++- .../Tests/Runtime/ConnectionApproval.cs | 16 ++++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index b581150df8..7105a6e705 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -21,6 +21,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where clients were not rebuilding the `NetworkConfig` hash value for each unique connection request. (#2226) - Fixed the issue where player objects were not taking the `DontDestroyWithOwner` property into consideration when a client disconnected. (#2225) - Fixed issue #1924 where `UnityTransport` would fail to restart after a first failure (even if what caused the initial failure was addressed). (#2220) - Fixed issue where `NetworkTransform.SetStateServerRpc` and `NetworkTransform.SetStateClientRpc` were not honoring local vs world space settings when applying the position and rotation. (#2203) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index dd37f1f538..84f97fbc6e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1616,7 +1616,9 @@ private void SendConnectionRequest() { var message = new ConnectionRequestMessage { - ConfigHash = NetworkConfig.GetConfig(), + // Since only a remote client will send a connection request, + // we should always force the rebuilding of the NetworkConfig hash value + ConfigHash = NetworkConfig.GetConfig(false), ShouldSendConnectionData = NetworkConfig.ConnectionApproval, ConnectionData = NetworkConfig.ConnectionData }; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs b/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs index 8c8f6e594a..4e7ec9581d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs @@ -62,11 +62,27 @@ private void NetworkManagerObject_ConnectionApprovalCallback(NetworkManager.Conn response.PlayerPrefabHash = null; } + + [Test] + public void VerifyUniqueNetworkConfigPerRequest() + { + var networkConfig = new NetworkConfig(); + networkConfig.EnableSceneManagement = true; + networkConfig.TickRate = 30; + var currentHash = networkConfig.GetConfig(); + networkConfig.EnableSceneManagement = false; + networkConfig.TickRate = 60; + var newHash = networkConfig.GetConfig(false); + + Assert.True(currentHash != newHash, $"Hashed {nameof(NetworkConfig)} values {currentHash} and {newHash} should not be the same!"); + } + [TearDown] public void TearDown() { // Stop, shutdown, and destroy NetworkManagerHelper.ShutdownNetworkManager(); } + } } From 1f4ae8de10ffed1c9547e9f172fbba9d88af0845 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 30 Sep 2022 21:30:52 -0500 Subject: [PATCH 076/138] fix: test project physics sample (#2227) * fix Removed the extra NetworkTransform no longer needed form the PhyscisPlayer prefab since the PlayerMovement class derives from NetworkTransform. * update moving the physics sample higher in the list and the sample scene to the bottom. --- .../Assets/Prefabs/PhysicsPlayer.prefab | 48 +++++++------------ testproject/Assets/Samples/SamplesMenu.unity | 4 +- 2 files changed, 20 insertions(+), 32 deletions(-) diff --git a/testproject/Assets/Prefabs/PhysicsPlayer.prefab b/testproject/Assets/Prefabs/PhysicsPlayer.prefab index 07a195b3ea..01695a3c70 100644 --- a/testproject/Assets/Prefabs/PhysicsPlayer.prefab +++ b/testproject/Assets/Prefabs/PhysicsPlayer.prefab @@ -14,7 +14,6 @@ GameObject: - component: {fileID: 4079352819444256612} - component: {fileID: -3775814466963834669} - component: {fileID: 1750810845806302260} - - component: {fileID: -6757728931092222887} - component: {fileID: 4053684789567975459} - component: {fileID: 3885376173097824340} - component: {fileID: 1991357795387791033} @@ -35,6 +34,7 @@ Transform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 5, y: 0.5, z: 5} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} m_RootOrder: 0 @@ -58,6 +58,7 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 @@ -133,33 +134,6 @@ Rigidbody: m_Interpolate: 1 m_Constraints: 112 m_CollisionDetection: 1 ---- !u!114 &-6757728931092222887 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 4079352819444256614} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: e96cb6065543e43c4a752faaa1468eb1, type: 3} - m_Name: - m_EditorClassIdentifier: - SyncPositionX: 1 - SyncPositionY: 1 - SyncPositionZ: 1 - SyncRotAngleX: 1 - SyncRotAngleY: 1 - SyncRotAngleZ: 1 - SyncScaleX: 1 - SyncScaleY: 1 - SyncScaleZ: 1 - PositionThreshold: 0 - RotAngleThreshold: 0 - ScaleThreshold: 0 - InLocalSpace: 0 - Interpolate: 1 - FixedSendsPerSecond: 5 --- !u!114 &4053684789567975459 MonoBehaviour: m_ObjectHideFlags: 0 @@ -184,8 +158,22 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 82b41b172a31546ffba450f1418f4e69, type: 3} m_Name: m_EditorClassIdentifier: - m_Speed: 5 - m_RotSpeed: 2.5 + SyncPositionX: 1 + SyncPositionY: 1 + SyncPositionZ: 1 + SyncRotAngleX: 1 + SyncRotAngleY: 1 + SyncRotAngleZ: 1 + SyncScaleX: 1 + SyncScaleY: 1 + SyncScaleZ: 1 + PositionThreshold: 0.001 + RotAngleThreshold: 0.01 + ScaleThreshold: 0.01 + InLocalSpace: 0 + Interpolate: 1 + Speed: 4 + RotSpeed: 1 --- !u!114 &1991357795387791033 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/testproject/Assets/Samples/SamplesMenu.unity b/testproject/Assets/Samples/SamplesMenu.unity index 3267b0f797..b4ecd94dc4 100644 --- a/testproject/Assets/Samples/SamplesMenu.unity +++ b/testproject/Assets/Samples/SamplesMenu.unity @@ -2526,11 +2526,11 @@ MonoBehaviour: - {fileID: 11400000, guid: 0509eb053ce4ef749afd8495f13128f1, type: 2} - {fileID: 11400000, guid: 9a8d9296fb33f794f95514bf38de3cf9, type: 2} - {fileID: 11400000, guid: 7fdc32fee173cca45a4601ba234954d0, type: 2} - - {fileID: 11400000, guid: 21aae92071ad50448a45b013d8346639, type: 2} + - {fileID: 11400000, guid: 7289196bc52552d458b24c94498df352, type: 2} - {fileID: 11400000, guid: 660535b6e155b5b4bbede52313fcb32e, type: 2} - {fileID: 11400000, guid: d2e34ed37c087154dbd7f89fd463801b, type: 2} - {fileID: 11400000, guid: 138603ab28f532140b48a57bea0e54b0, type: 2} - - {fileID: 11400000, guid: 7289196bc52552d458b24c94498df352, type: 2} + - {fileID: 11400000, guid: 21aae92071ad50448a45b013d8346639, type: 2} m_SceneMenusDropDownList: {fileID: 1362704539} m_OptionsList: m_Options: From 9dfa8606a678249e2b4358bb5479ae48ed25519f Mon Sep 17 00:00:00 2001 From: Kitty Draper <284434+ShadauxCat@users.noreply.github.com> Date: Mon, 3 Oct 2022 12:42:32 -0500 Subject: [PATCH 077/138] fix: Fix IsOwner/IsOwnedByServer being wrong on the server after calling RemoveOwnership [MTT-4259] (#2211) * fix: Fix IsOwner/IsOwnedByServer being wrong on the server after calling RemoveOwnership --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Spawning/NetworkSpawnManager.cs | 4 +- .../Runtime/MessageHooksConditional.cs | 12 +++- .../Runtime/NetcodeIntegrationTest.cs | 33 +++++++++ ...orkObjectNetworkClientOwnedObjectsTests.cs | 72 +++++++++++++++++-- 5 files changed, 115 insertions(+), 7 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 7105a6e705..4bfdff566e 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -36,6 +36,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed throwing an exception in `OnNetworkUpdate` causing other `OnNetworkUpdate` calls to not be executed. (#1739) - Fixed synchronization when Time.timeScale is set to 0. This changes timing update to use unscaled deltatime. Now network updates rate are independent from the local time scale. (#2171) - Fixed not sending all NetworkVariables to all clients when a client connects to a server. (#1987) +- Fixed IsOwner/IsOwnedByServer being wrong on the server after calling RemoveOwnership (#2211) ## [1.0.2] - 2022-09-12 diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 4b6a214a9a..91c5a52147 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -213,11 +213,11 @@ internal void RemoveOwnership(NetworkObject networkObject) return; } + networkObject.OwnerClientId = NetworkManager.ServerClientId; + // Server removes the entry and takes over ownership before notifying UpdateOwnershipTable(networkObject, NetworkManager.ServerClientId, true); - networkObject.OwnerClientId = NetworkManager.ServerClientId; - var message = new ChangeOwnershipMessage { NetworkObjectId = networkObject.NetworkObjectId, diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/MessageHooksConditional.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/MessageHooksConditional.cs index 71791d2b6e..733cae8c90 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/MessageHooksConditional.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/MessageHooksConditional.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -86,10 +87,19 @@ internal void AssignMessageType() where T : INetworkMessage Initialize(); } + internal void AssignMessageType(Type type) + { + MessageType = type.Name; + m_MessageReceiptCheck = (message) => + { + return message.GetType() == type; + }; + Initialize(); + } + public MessageHookEntry(NetworkManager networkManager) { m_NetworkManager = networkManager; } } } - diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index ee085737d3..a83d0bdb18 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -762,6 +762,39 @@ protected IEnumerator WaitForClientsConnectedOrTimeOut() yield return WaitForClientsConnectedOrTimeOut(m_ClientNetworkManagers); } + internal IEnumerator WaitForMessageReceived(List wiatForReceivedBy) where T : INetworkMessage + { + // Build our message hook entries tables so we can determine if all clients received spawn or ownership messages + var messageHookEntriesForSpawn = new List(); + foreach (var clientNetworkManager in wiatForReceivedBy) + { + var messageHook = new MessageHookEntry(clientNetworkManager); + messageHook.AssignMessageType(); + messageHookEntriesForSpawn.Add(messageHook); + } + // Used to determine if all clients received the CreateObjectMessage + var hooks = new MessageHooksConditional(messageHookEntriesForSpawn); + yield return WaitForConditionOrTimeOut(hooks); + } + + internal IEnumerator WaitForMessagesReceived(List messagesInOrder, List wiatForReceivedBy) + { + // Build our message hook entries tables so we can determine if all clients received spawn or ownership messages + var messageHookEntriesForSpawn = new List(); + foreach (var clientNetworkManager in wiatForReceivedBy) + { + foreach (var message in messagesInOrder) + { + var messageHook = new MessageHookEntry(clientNetworkManager); + messageHook.AssignMessageType(message); + messageHookEntriesForSpawn.Add(messageHook); + } + } + // Used to determine if all clients received the CreateObjectMessage + var hooks = new MessageHooksConditional(messageHookEntriesForSpawn); + yield return WaitForConditionOrTimeOut(hooks); + } + /// /// Creates a basic NetworkObject test prefab, assigns it to a new /// NetworkPrefab entry, and then adds it to the server and client(s) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectNetworkClientOwnedObjectsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectNetworkClientOwnedObjectsTests.cs index fcdbfd886a..bcdd613102 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectNetworkClientOwnedObjectsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectNetworkClientOwnedObjectsTests.cs @@ -9,6 +9,11 @@ namespace Unity.Netcode.RuntimeTests { public class NetworkObjectNetworkClientOwnedObjectsTests : NetcodeIntegrationTest { + private class DummyNetworkBehaviour : NetworkBehaviour + { + + } + protected override int NumberOfClients => 1; private NetworkPrefab m_NetworkPrefab; protected override void OnServerAndClientsCreated() @@ -16,6 +21,7 @@ protected override void OnServerAndClientsCreated() // create prefab var gameObject = new GameObject("ClientOwnedObject"); var networkObject = gameObject.AddComponent(); + gameObject.AddComponent(); NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); m_NetworkPrefab = (new NetworkPrefab() @@ -39,7 +45,7 @@ public IEnumerator ChangeOwnershipOwnedObjectsAddTest() serverObject.Spawn(); // Provide enough time for the client to receive and process the spawned message. - yield return s_DefaultWaitForTick; + yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); // The object is owned by server Assert.False(m_ServerNetworkManager.SpawnManager.GetClientOwnedObjects(m_ClientNetworkManagers[0].LocalClientId).Any(x => x.NetworkObjectId == serverObject.NetworkObjectId)); @@ -48,13 +54,71 @@ public IEnumerator ChangeOwnershipOwnedObjectsAddTest() serverObject.ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); // Provide enough time for the client to receive and process the change in ownership message. - yield return s_DefaultWaitForTick; + yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); // Ensure it's now added to the list - yield return WaitForConditionOrTimeOut(() => m_ClientNetworkManagers[0].SpawnManager.GetClientOwnedObjects(m_ClientNetworkManagers[0].LocalClientId).Any(x => x.NetworkObjectId == serverObject.NetworkObjectId)); - Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for client to gain ownership!"); Assert.True(m_ClientNetworkManagers[0].SpawnManager.GetClientOwnedObjects(m_ClientNetworkManagers[0].LocalClientId).Any(x => x.NetworkObjectId == serverObject.NetworkObjectId)); Assert.True(m_ServerNetworkManager.SpawnManager.GetClientOwnedObjects(m_ClientNetworkManagers[0].LocalClientId).Any(x => x.NetworkObjectId == serverObject.NetworkObjectId)); } + + [UnityTest] + public IEnumerator WhenOwnershipIsChanged_OwnershipValuesUpdateCorrectly() + { + NetworkObject serverObject = Object.Instantiate(m_NetworkPrefab.Prefab).GetComponent(); + serverObject.NetworkManagerOwner = m_ServerNetworkManager; + serverObject.Spawn(); + + // Provide enough time for the client to receive and process the spawned message. + yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); + + // The object is owned by server + Assert.False(m_ServerNetworkManager.SpawnManager.GetClientOwnedObjects(m_ClientNetworkManagers[0].LocalClientId).Any(x => x.NetworkObjectId == serverObject.NetworkObjectId)); + + // Change the ownership + serverObject.ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); + + // Provide enough time for the client to receive and process the change in ownership message. + yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); + + Assert.IsFalse(serverObject.IsOwner); + Assert.IsFalse(serverObject.IsOwnedByServer); + Assert.AreEqual(m_ClientNetworkManagers[0].LocalClientId, serverObject.OwnerClientId); + + var serverBehaviour = serverObject.GetComponent(); + Assert.IsFalse(serverBehaviour.IsOwner); + Assert.IsFalse(serverBehaviour.IsOwnedByServer); + Assert.AreEqual(m_ClientNetworkManagers[0].LocalClientId, serverBehaviour.OwnerClientId); + + var clientObject = Object.FindObjectsOfType().Where((obj) => obj.NetworkManagerOwner == m_ClientNetworkManagers[0]).FirstOrDefault(); + + Assert.IsNotNull(clientObject); + Assert.IsTrue(clientObject.IsOwner); + Assert.IsFalse(clientObject.IsOwnedByServer); + Assert.AreEqual(m_ClientNetworkManagers[0].LocalClientId, clientObject.OwnerClientId); + + var clientBehaviour = clientObject.GetComponent(); + Assert.IsTrue(clientBehaviour.IsOwner); + Assert.IsFalse(clientBehaviour.IsOwnedByServer); + Assert.AreEqual(m_ClientNetworkManagers[0].LocalClientId, clientBehaviour.OwnerClientId); + + serverObject.RemoveOwnership(); + + // Provide enough time for the client to receive and process the change in ownership message. + yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); + + Assert.IsTrue(serverObject.IsOwner); + Assert.IsTrue(serverObject.IsOwnedByServer); + Assert.AreEqual(NetworkManager.ServerClientId, serverObject.OwnerClientId); + Assert.IsTrue(serverBehaviour.IsOwner); + Assert.IsTrue(serverBehaviour.IsOwnedByServer); + Assert.AreEqual(NetworkManager.ServerClientId, serverBehaviour.OwnerClientId); + + Assert.IsFalse(clientObject.IsOwner); + Assert.IsTrue(clientObject.IsOwnedByServer); + Assert.AreEqual(NetworkManager.ServerClientId, clientObject.OwnerClientId); + Assert.IsFalse(clientBehaviour.IsOwner); + Assert.IsTrue(clientBehaviour.IsOwnedByServer); + Assert.AreEqual(NetworkManager.ServerClientId, clientBehaviour.OwnerClientId); + } } } From e939e27af6062b03b5d2a70e2f479fa3132f33fd Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 3 Oct 2022 14:53:50 -0500 Subject: [PATCH 078/138] fix: SceneEventProgress OnComplete not invoked on timeouts [MTT-4772] (#2222) * fix Need to track any clients that disconnected to add them to the list of clients that did not complete the scene event. Reducing the logic used to determine when the SceneEventProgress is finished. Renamed a few properties and methods for code readability purposes. Fixed the logic behind determining when the SceneEventProgress should complete. Renaming the SetSceneAsyncOperation to just SetAsyncOperation. * refactor Completely removed the entire SceneEventAction. Replaced it with an even more simplified version of SceneEventProgress. We will no longer allocate memory for SceneEventAction. * style Added additional comments for clarity fixing grammar in a comment removing two CR/LFs moving a comment above a few lines of code (otherwise it doesn't make sense). Last comment adjustment * test Added several tests to verify that SceneEventProgress will complete if all clients finished the scene event, if clients disconnect while it is still in progress, will time out if one or more clients never finish, and finally will still complete if one or more clients late join while it is still in progress. --- com.unity.netcode.gameobjects/CHANGELOG.md | 3 + .../SceneManagement/ISceneManagerHandler.cs | 22 +- .../SceneManagement/NetworkSceneManager.cs | 82 ++++---- .../SceneManagement/SceneEventProgress.cs | 170 ++++++++++------ .../Runtime/IntegrationTestSceneHandler.cs | 31 ++- .../SceneEventProgressTests.cs | 191 ++++++++++++++++++ .../SceneEventProgressTests.cs.meta | 11 + 7 files changed, 363 insertions(+), 147 deletions(-) create mode 100644 testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventProgressTests.cs create mode 100644 testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventProgressTests.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 4bfdff566e..b5c96378a7 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -23,6 +23,9 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed issue where clients were not rebuilding the `NetworkConfig` hash value for each unique connection request. (#2226) - Fixed the issue where player objects were not taking the `DontDestroyWithOwner` property into consideration when a client disconnected. (#2225) +- Fixed issue where `SceneEventProgress` would not complete if a client late joins while it is still in progress. (#2222) +- Fixed issue where `SceneEventProgress` would not complete if a client disconnects. (#2222) +- Fixed issues with detecting if a `SceneEventProgress` has timed out. (#2222) - Fixed issue #1924 where `UnityTransport` would fail to restart after a first failure (even if what caused the initial failure was addressed). (#2220) - Fixed issue where `NetworkTransform.SetStateServerRpc` and `NetworkTransform.SetStateClientRpc` were not honoring local vs world space settings when applying the position and rotation. (#2203) - Fixed ILPP `TypeLoadException` on WebGL on MacOS Editor and potentially other platforms. (#2199) diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/ISceneManagerHandler.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/ISceneManagerHandler.cs index 92976f3eac..8b5b7e7fda 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/ISceneManagerHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/ISceneManagerHandler.cs @@ -1,4 +1,3 @@ -using System; using UnityEngine; using UnityEngine.SceneManagement; @@ -10,25 +9,8 @@ namespace Unity.Netcode /// internal interface ISceneManagerHandler { - // Generic action to call when a scene is finished loading/unloading - struct SceneEventAction - { - internal uint SceneEventId; - internal Action EventAction; - /// - /// Used server-side for integration testing in order to - /// invoke the SceneEventProgress once done loading - /// - internal Action Completed; - internal void Invoke() - { - Completed?.Invoke(); - EventAction.Invoke(SceneEventId); - } - } + AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress); - AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventAction sceneEventAction); - - AsyncOperation UnloadSceneAsync(Scene scene, SceneEventAction sceneEventAction); + AsyncOperation UnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress); } } diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 7f7c9ff2c9..531e582f55 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -338,17 +338,17 @@ public class NetworkSceneManager : IDisposable /// private class DefaultSceneManagerHandler : ISceneManagerHandler { - public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, ISceneManagerHandler.SceneEventAction sceneEventAction) + public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress) { var operation = SceneManager.LoadSceneAsync(sceneName, loadSceneMode); - operation.completed += new Action(asyncOp2 => { sceneEventAction.Invoke(); }); + sceneEventProgress.SetAsyncOperation(operation); return operation; } - public AsyncOperation UnloadSceneAsync(Scene scene, ISceneManagerHandler.SceneEventAction sceneEventAction) + public AsyncOperation UnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress) { var operation = SceneManager.UnloadSceneAsync(scene); - operation.completed += new Action(asyncOp2 => { sceneEventAction.Invoke(); }); + sceneEventProgress.SetAsyncOperation(operation); return operation; } } @@ -887,12 +887,14 @@ private SceneEventProgress ValidateSceneEvent(string sceneName, bool isUnloading private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress) { var sceneEventData = BeginSceneEvent(); + var clientsThatCompleted = sceneEventProgress.GetClientsWithStatus(true); + var clientsThatTimedOut = sceneEventProgress.GetClientsWithStatus(false); sceneEventData.SceneEventProgressId = sceneEventProgress.Guid; sceneEventData.SceneHash = sceneEventProgress.SceneHash; sceneEventData.SceneEventType = sceneEventProgress.SceneEventType; - sceneEventData.ClientsCompleted = sceneEventProgress.DoneClients; + sceneEventData.ClientsCompleted = clientsThatCompleted; sceneEventData.LoadSceneMode = sceneEventProgress.LoadSceneMode; - sceneEventData.ClientsTimedOut = sceneEventProgress.ClientsThatStartedSceneEvent.Except(sceneEventProgress.DoneClients).ToList(); + sceneEventData.ClientsTimedOut = clientsThatTimedOut; var message = new SceneEventMessage { @@ -913,8 +915,8 @@ private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress SceneName = SceneNameFromHash(sceneEventProgress.SceneHash), ClientId = NetworkManager.ServerClientId, LoadSceneMode = sceneEventProgress.LoadSceneMode, - ClientsThatCompleted = sceneEventProgress.DoneClients, - ClientsThatTimedOut = m_NetworkManager.ConnectedClients.Keys.Except(sceneEventProgress.DoneClients).ToList(), + ClientsThatCompleted = clientsThatCompleted, + ClientsThatTimedOut = clientsThatTimedOut, }); if (sceneEventData.SceneEventType == SceneEventType.LoadEventCompleted) @@ -969,18 +971,9 @@ public SceneEventProgressStatus UnloadScene(Scene scene) sceneEventProgress.SceneEventType = SceneEventType.UnloadEventCompleted; ScenesLoaded.Remove(scene.handle); - var sceneEventAction = new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneUnloaded }; - var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene, sceneEventAction); - - // If integration testing, IntegrationTestSceneHandler returns null - if (sceneUnload == null) - { - sceneEventProgress.SetSceneLoadOperation(sceneEventAction); - } - else - { - sceneEventProgress.SetSceneLoadOperation(sceneUnload); - } + sceneEventProgress.SceneEventId = sceneEventData.SceneEventId; + sceneEventProgress.OnSceneEventCompleted = OnSceneUnloaded; + var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene, sceneEventProgress); // Notify local server that a scene is going to be unloaded OnSceneEvent?.Invoke(new SceneEvent() @@ -1024,9 +1017,10 @@ private void OnClientUnloadScene(uint sceneEventId) $"because the client scene handle {sceneHandle} was not found in ScenesLoaded!"); } m_IsSceneEventActive = true; - - var sceneUnload = SceneManagerHandler.UnloadSceneAsync(ScenesLoaded[sceneHandle], - new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneUnloaded }); + var sceneEventProgress = new SceneEventProgress(m_NetworkManager); + sceneEventProgress.SceneEventId = sceneEventData.SceneEventId; + sceneEventProgress.OnSceneEventCompleted = OnSceneUnloaded; + var sceneUnload = SceneManagerHandler.UnloadSceneAsync(ScenesLoaded[sceneHandle], sceneEventProgress); ScenesLoaded.Remove(sceneHandle); @@ -1070,7 +1064,7 @@ private void OnSceneUnloaded(uint sceneEventId) //Only if we are a host do we want register having loaded for the associated SceneEventProgress if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost) { - SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(NetworkManager.ServerClientId); + SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(NetworkManager.ServerClientId); } } @@ -1119,8 +1113,10 @@ internal void UnloadAdditivelyLoadedScenes(uint sceneEventId) // Validate the scene as well as ignore the DDOL (which will have a negative buildIndex) if (currentActiveScene.name != keyHandleEntry.Value.name && keyHandleEntry.Value.buildIndex >= 0) { - var sceneUnload = SceneManagerHandler.UnloadSceneAsync(keyHandleEntry.Value, - new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = EmptySceneUnloadedOperation }); + var sceneEventProgress = new SceneEventProgress(m_NetworkManager); + sceneEventProgress.SceneEventId = sceneEventId; + sceneEventProgress.OnSceneEventCompleted = EmptySceneUnloadedOperation; + var sceneUnload = SceneManagerHandler.UnloadSceneAsync(keyHandleEntry.Value, sceneEventProgress); SceneUnloadEventHandler.RegisterScene(this, keyHandleEntry.Value, LoadSceneMode.Additive, sceneUnload); } } @@ -1180,18 +1176,9 @@ public SceneEventProgressStatus LoadScene(string sceneName, LoadSceneMode loadSc } // Now start loading the scene - var sceneEventAction = new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = OnSceneLoaded }; - var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, sceneEventAction); - // If integration testing, IntegrationTestSceneHandler returns null - if (sceneLoad == null) - { - sceneEventProgress.SetSceneLoadOperation(sceneEventAction); - } - else - { - sceneEventProgress.SetSceneLoadOperation(sceneLoad); - } - + sceneEventProgress.SceneEventId = sceneEventId; + sceneEventProgress.OnSceneEventCompleted = OnSceneLoaded; + var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, sceneEventProgress); // Notify the local server that a scene loading event has begun OnSceneEvent?.Invoke(new SceneEvent() { @@ -1355,9 +1342,10 @@ private void OnClientSceneLoadingEvent(uint sceneEventId) SceneUnloadEventHandler.RegisterScene(this, SceneManager.GetActiveScene(), LoadSceneMode.Single); } - - var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, sceneEventData.LoadSceneMode, - new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = OnSceneLoaded }); + var sceneEventProgress = new SceneEventProgress(m_NetworkManager); + sceneEventProgress.SceneEventId = sceneEventId; + sceneEventProgress.OnSceneEventCompleted = OnSceneLoaded; + var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, sceneEventData.LoadSceneMode, sceneEventProgress); OnSceneEvent?.Invoke(new SceneEvent() { @@ -1488,7 +1476,7 @@ private void OnServerLoadedScene(uint sceneEventId, Scene scene) //Second, only if we are a host do we want register having loaded for the associated SceneEventProgress if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost) { - SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(NetworkManager.ServerClientId); + SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(NetworkManager.ServerClientId); } EndSceneEvent(sceneEventId); } @@ -1662,8 +1650,10 @@ private void OnClientBeginSync(uint sceneEventId) if (!shouldPassThrough) { // If not, then load the scene - sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, - new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = ClientLoadedSynchronization }); + var sceneEventProgress = new SceneEventProgress(m_NetworkManager); + sceneEventProgress.SceneEventId = sceneEventId; + sceneEventProgress.OnSceneEventCompleted = ClientLoadedSynchronization; + sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, sceneEventProgress); // Notify local client that a scene load has begun OnSceneEvent?.Invoke(new SceneEvent() @@ -1880,7 +1870,7 @@ private void HandleServerSceneEvent(uint sceneEventId, ulong clientId) if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId)) { - SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(clientId); + SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(clientId); } EndSceneEvent(sceneEventId); break; @@ -1889,7 +1879,7 @@ private void HandleServerSceneEvent(uint sceneEventId, ulong clientId) { if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId)) { - SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(clientId); + SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(clientId); } // Notify the local server that the client has finished unloading a scene OnSceneEvent?.Invoke(new SceneEvent() diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs index 838bbfcd6f..80997c80d9 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs @@ -58,12 +58,13 @@ internal class SceneEventProgress /// /// List of clientIds of those clients that is done loading the scene. /// - internal List DoneClients { get; } = new List(); + internal Dictionary ClientsProcessingSceneEvent { get; } = new Dictionary(); + internal List ClientsThatDisconnected = new List(); /// - /// The local time when the scene event was "roughly started" + /// This is when the current scene event will have timed out /// - internal float TimeAtInitiation { get; } + internal float WhenSceneEventHasTimedOut; /// /// Delegate type for when the switch scene progress is completed. Either by all clients done loading the scene or by time out. @@ -75,17 +76,15 @@ internal class SceneEventProgress /// internal OnCompletedDelegate OnComplete; - /// - /// Is this scene switch progresses completed, all clients are done loading the scene or a timeout has occurred. - /// - internal bool IsCompleted { get; private set; } - - internal bool TimedOut { get; private set; } + internal Action OnSceneEventCompleted; /// - /// If all clients are done loading the scene, at the moment of completed. + /// This will make sure that we only have timed out if we never completed /// - internal bool AreAllClientsDoneLoading { get; private set; } + internal bool HasTimedOut() + { + return WhenSceneEventHasTimedOut <= Time.realtimeSinceStartup; + } /// /// The hash value generated from the full scene path @@ -93,9 +92,10 @@ internal class SceneEventProgress internal uint SceneHash { get; set; } internal Guid Guid { get; } = Guid.NewGuid(); + internal uint SceneEventId; private Coroutine m_TimeOutCoroutine; - private AsyncOperation m_SceneLoadOperation; + private AsyncOperation m_AsyncOperation; private NetworkManager m_NetworkManager { get; } @@ -105,21 +105,62 @@ internal class SceneEventProgress internal LoadSceneMode LoadSceneMode; - internal List ClientsThatStartedSceneEvent; + internal List GetClientsWithStatus(bool completedSceneEvent) + { + var clients = new List(); + foreach (var clientStatus in ClientsProcessingSceneEvent) + { + if (clientStatus.Value == completedSceneEvent) + { + clients.Add(clientStatus.Key); + } + } + + // If we are getting the list of clients that have not completed the + // scene event, then add any clients that disconnected during this + // scene event. + if (!completedSceneEvent) + { + clients.AddRange(ClientsThatDisconnected); + } + return clients; + } internal SceneEventProgress(NetworkManager networkManager, SceneEventProgressStatus status = SceneEventProgressStatus.Started) { if (status == SceneEventProgressStatus.Started) { - // Track the clients that were connected when we started this event - ClientsThatStartedSceneEvent = new List(networkManager.ConnectedClientsIds); m_NetworkManager = networkManager; - m_TimeOutCoroutine = m_NetworkManager.StartCoroutine(TimeOutSceneEventProgress()); - TimeAtInitiation = Time.realtimeSinceStartup; + + if (networkManager.IsServer) + { + m_NetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback; + // Track the clients that were connected when we started this event + foreach (var connectedClientId in networkManager.ConnectedClientsIds) + { + ClientsProcessingSceneEvent.Add(connectedClientId, false); + } + + WhenSceneEventHasTimedOut = Time.realtimeSinceStartup + networkManager.NetworkConfig.LoadSceneTimeOut; + m_TimeOutCoroutine = m_NetworkManager.StartCoroutine(TimeOutSceneEventProgress()); + } } Status = status; } + /// + /// Remove the client from the clients processing the current scene event + /// Add this client to the clients that disconnected list + /// + private void OnClientDisconnectCallback(ulong clientId) + { + if (ClientsProcessingSceneEvent.ContainsKey(clientId)) + { + ClientsThatDisconnected.Add(clientId); + ClientsProcessingSceneEvent.Remove(clientId); + } + } + /// /// Coroutine that checks to see if the scene event is complete every network tick period. /// This will handle completing the scene event when one or more client(s) disconnect(s) @@ -129,80 +170,79 @@ internal SceneEventProgress(NetworkManager networkManager, SceneEventProgressSta internal IEnumerator TimeOutSceneEventProgress() { var waitForNetworkTick = new WaitForSeconds(1.0f / m_NetworkManager.NetworkConfig.TickRate); - while (!TimedOut && !IsCompleted) + while (!HasTimedOut()) { yield return waitForNetworkTick; - CheckCompletion(); - if (!IsCompleted) - { - TimedOut = TimeAtInitiation - Time.realtimeSinceStartup >= m_NetworkManager.NetworkConfig.LoadSceneTimeOut; - } + TryFinishingSceneEventProgress(); } } - internal void AddClientAsDone(ulong clientId) + /// + /// Sets the client's scene event progress to finished/true + /// + internal void ClientFinishedSceneEvent(ulong clientId) { - DoneClients.Add(clientId); - CheckCompletion(); + if (ClientsProcessingSceneEvent.ContainsKey(clientId)) + { + ClientsProcessingSceneEvent[clientId] = true; + TryFinishingSceneEventProgress(); + } } - internal void RemoveClientAsDone(ulong clientId) + /// + /// Determines if the scene event has finished for both + /// client(s) and server. + /// + /// + /// The server checks if all known clients processing this scene event + /// have finished and then it returns its local AsyncOperation status. + /// Clients finish when their AsyncOperation finishes. + /// + private bool HasFinished() { - DoneClients.Remove(clientId); - CheckCompletion(); - } + // Clients skip over this + foreach (var clientStatus in ClientsProcessingSceneEvent) + { + if (!clientStatus.Value) + { + return false; + } + } - internal void SetSceneLoadOperation(AsyncOperation sceneLoadOperation) - { - m_SceneLoadOperation = sceneLoadOperation; - m_SceneLoadOperation.completed += operation => CheckCompletion(); + // Return the local scene event's AsyncOperation status + return m_AsyncOperation.isDone; } /// - /// Called only on the server-side during integration test (NetcodeIntegrationTest specific) - /// scene loading and unloading. - /// - /// Note: During integration testing we must queue all scene loading and unloading requests for - /// both the server and all clients so they can be processed in a FIFO/linear fashion to avoid - /// conflicts when the and - /// events are triggered. The Completed action simulates the event. - /// (See: Unity.Netcode.TestHelpers.Runtime.IntegrationTestSceneHandler) + /// Sets the AsyncOperation for the scene load/unload event /// - internal void SetSceneLoadOperation(ISceneManagerHandler.SceneEventAction sceneEventAction) + internal void SetAsyncOperation(AsyncOperation asyncOperation) { - sceneEventAction.Completed = SetComplete; + m_AsyncOperation = asyncOperation; + m_AsyncOperation.completed += new Action(asyncOp2 => + { + OnSceneEventCompleted?.Invoke(SceneEventId); + TryFinishingSceneEventProgress(); + }); } /// - /// Finalizes the SceneEventProgress + /// Will try to finish the current scene event in progress as long as + /// all conditions are met. /// - internal void SetComplete() + internal void TryFinishingSceneEventProgress() { - IsCompleted = true; - AreAllClientsDoneLoading = true; - - // If OnComplete is not registered or it is and returns true then remove this from the progress tracking - if (OnComplete == null || (OnComplete != null && OnComplete.Invoke(this))) + if (HasFinished() || HasTimedOut()) { + OnComplete?.Invoke(this); m_NetworkManager.SceneManager.SceneEventProgressTracking.Remove(Guid); - } - m_NetworkManager.StopCoroutine(m_TimeOutCoroutine); - } - - internal void CheckCompletion() - { - try - { - if ((!IsCompleted && DoneClients.Count == m_NetworkManager.ConnectedClientsList.Count && (m_SceneLoadOperation == null || m_SceneLoadOperation.isDone)) || (!IsCompleted && TimedOut)) + m_NetworkManager.OnClientDisconnectCallback -= OnClientDisconnectCallback; + if (m_NetworkManager.IsServer) { - SetComplete(); + m_NetworkManager.StopCoroutine(m_TimeOutCoroutine); } } - catch (Exception ex) - { - Debug.LogException(ex); - } } } } diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs index cee1b8b12d..15b6bc13f9 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs @@ -48,7 +48,7 @@ public enum JobTypes public JobTypes JobType; public string SceneName; public Scene Scene; - public ISceneManagerHandler.SceneEventAction SceneAction; + public SceneEventProgress SceneEventProgress; public IntegrationTestSceneHandler IntegrationTestSceneHandler; } @@ -106,7 +106,8 @@ static internal IEnumerator ProcessLoadingSceneJob(QueuedSceneJob queuedSceneJob SceneManager.sceneLoaded += SceneManager_sceneLoaded; // We always load additively for all scenes during integration tests - SceneManager.LoadSceneAsync(queuedSceneJob.SceneName, LoadSceneMode.Additive); + var asyncOperation = SceneManager.LoadSceneAsync(queuedSceneJob.SceneName, LoadSceneMode.Additive); + queuedSceneJob.SceneEventProgress.SetAsyncOperation(asyncOperation); // Wait for it to finish while (queuedSceneJob.JobType != QueuedSceneJob.JobTypes.Completed) @@ -114,7 +115,6 @@ static internal IEnumerator ProcessLoadingSceneJob(QueuedSceneJob queuedSceneJob yield return s_WaitForSeconds; } yield return s_WaitForSeconds; - CurrentQueuedSceneJob.SceneAction.Invoke(); } /// @@ -176,7 +176,8 @@ static internal IEnumerator ProcessUnloadingSceneJob(QueuedSceneJob queuedSceneJ SceneManager.sceneUnloaded += SceneManager_sceneUnloaded; if (queuedSceneJob.Scene.IsValid() && queuedSceneJob.Scene.isLoaded && !queuedSceneJob.Scene.name.Contains(NetcodeIntegrationTestHelpers.FirstPartOfTestRunnerSceneName)) { - SceneManager.UnloadSceneAsync(queuedSceneJob.Scene); + var asyncOperation = SceneManager.UnloadSceneAsync(queuedSceneJob.Scene); + queuedSceneJob.SceneEventProgress.SetAsyncOperation(asyncOperation); } else { @@ -188,7 +189,6 @@ static internal IEnumerator ProcessUnloadingSceneJob(QueuedSceneJob queuedSceneJ { yield return s_WaitForSeconds; } - CurrentQueuedSceneJob.SceneAction.Invoke(); } /// @@ -246,7 +246,7 @@ private void AddJobToQueue(QueuedSceneJob queuedSceneJob) /// /// Server always loads like it normally would /// - public AsyncOperation GenericLoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, ISceneManagerHandler.SceneEventAction sceneEventAction) + public AsyncOperation GenericLoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress) { m_ServerSceneBeingLoaded = sceneName; if (NetcodeIntegrationTest.IsRunning) @@ -254,8 +254,7 @@ public AsyncOperation GenericLoadSceneAsync(string sceneName, LoadSceneMode load SceneManager.sceneLoaded += Sever_SceneLoaded; } var operation = SceneManager.LoadSceneAsync(sceneName, loadSceneMode); - - operation.completed += new Action(asyncOp2 => { sceneEventAction.Invoke(); }); + sceneEventProgress.SetAsyncOperation(operation); return operation; } @@ -271,39 +270,39 @@ private void Sever_SceneLoaded(Scene scene, LoadSceneMode arg1) /// /// Server always unloads like it normally would /// - public AsyncOperation GenericUnloadSceneAsync(Scene scene, ISceneManagerHandler.SceneEventAction sceneEventAction) + public AsyncOperation GenericUnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress) { var operation = SceneManager.UnloadSceneAsync(scene); - operation.completed += new Action(asyncOp2 => { sceneEventAction.Invoke(); }); + sceneEventProgress.SetAsyncOperation(operation); return operation; } - public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, ISceneManagerHandler.SceneEventAction sceneEventAction) + public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress) { // Server and non NetcodeIntegrationTest tests use the generic load scene method if (!NetcodeIntegrationTest.IsRunning) { - return GenericLoadSceneAsync(sceneName, loadSceneMode, sceneEventAction); + return GenericLoadSceneAsync(sceneName, loadSceneMode, sceneEventProgress); } else // NetcodeIntegrationTest Clients always get added to the jobs queue { - AddJobToQueue(new QueuedSceneJob() { IntegrationTestSceneHandler = this, SceneName = sceneName, SceneAction = sceneEventAction, JobType = QueuedSceneJob.JobTypes.Loading }); + AddJobToQueue(new QueuedSceneJob() { IntegrationTestSceneHandler = this, SceneName = sceneName, SceneEventProgress = sceneEventProgress, JobType = QueuedSceneJob.JobTypes.Loading }); } return null; } - public AsyncOperation UnloadSceneAsync(Scene scene, ISceneManagerHandler.SceneEventAction sceneEventAction) + public AsyncOperation UnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress) { // Server and non NetcodeIntegrationTest tests use the generic unload scene method if (!NetcodeIntegrationTest.IsRunning) { - return GenericUnloadSceneAsync(scene, sceneEventAction); + return GenericUnloadSceneAsync(scene, sceneEventProgress); } else // NetcodeIntegrationTest Clients always get added to the jobs queue { - AddJobToQueue(new QueuedSceneJob() { IntegrationTestSceneHandler = this, Scene = scene, SceneAction = sceneEventAction, JobType = QueuedSceneJob.JobTypes.Unloading }); + AddJobToQueue(new QueuedSceneJob() { IntegrationTestSceneHandler = this, Scene = scene, SceneEventProgress = sceneEventProgress, JobType = QueuedSceneJob.JobTypes.Unloading }); } // This is OK to return a "nothing" AsyncOperation since we are simulating client loading return null; diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventProgressTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventProgressTests.cs new file mode 100644 index 0000000000..575a875763 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventProgressTests.cs @@ -0,0 +1,191 @@ +using System.Collections; +using System.Collections.Generic; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.SceneManagement; +using UnityEngine.TestTools; +using Unity.Netcode; +using Unity.Netcode.TestHelpers.Runtime; +using Random = UnityEngine.Random; + +namespace TestProject.RuntimeTests +{ + public class SceneEventProgressTests : NetcodeIntegrationTest + { + private const string k_SceneUsedToGetAsyncOperation = "EmptyScene"; + protected override int NumberOfClients => 4; + + private bool m_SceneEventProgressCompleted; + private SceneEventProgress m_CurrentSceneEventProgress; + + private List m_ClientThatShouldNotHaveCompleted = new List(); + private List m_ClientThatShouldHaveCompleted = new List(); + + private bool SceneEventProgressComplete(SceneEventProgress sceneEventProgress) + { + m_SceneEventProgressCompleted = true; + return true; + } + + private void SetClientFinished(ulong clientId, bool finished) + { + if (finished) + { + m_ClientThatShouldHaveCompleted.Add(clientId); + m_CurrentSceneEventProgress.ClientFinishedSceneEvent(clientId); + } + else + { + m_ClientThatShouldNotHaveCompleted.Add(clientId); + } + } + + private void StartNewSceneEventProgress() + { + m_SceneEventProgressCompleted = false; + m_ClientThatShouldNotHaveCompleted.Clear(); + m_ClientThatShouldHaveCompleted.Clear(); + m_CurrentSceneEventProgress = new SceneEventProgress(m_ServerNetworkManager, SceneEventProgressStatus.Started); + m_CurrentSceneEventProgress.OnComplete = SceneEventProgressComplete; + SceneManager.sceneLoaded += MockServerLoadedSene; + m_CurrentSceneEventProgress.SetAsyncOperation(SceneManager.LoadSceneAsync(k_SceneUsedToGetAsyncOperation, LoadSceneMode.Additive)); + } + + private void MockServerLoadedSene(Scene scene, LoadSceneMode loadSceneMode) + { + if (scene.name == k_SceneUsedToGetAsyncOperation) + { + SetClientFinished(NetworkManager.ServerClientId, true); + SceneManager.sceneLoaded -= MockServerLoadedSene; + } + } + + private void VerifyClientsThatCompleted() + { + var clientsDidComplete = m_CurrentSceneEventProgress.GetClientsWithStatus(true); + var clientsDidNotComplete = m_CurrentSceneEventProgress.GetClientsWithStatus(false); + + foreach (var clientId in clientsDidComplete) + { + Assert.IsTrue(m_ClientThatShouldHaveCompleted.Contains(clientId), $"Client-{clientId} was not in the SceneEventProgress completed list!"); + } + + foreach (var clientId in clientsDidNotComplete) + { + Assert.IsTrue(m_ClientThatShouldNotHaveCompleted.Contains(clientId), $"Client-{clientId} was not in the SceneEventProgress did not complete list!"); + } + } + + /// + /// This verifies when all clients finish, the SceneEventProgress + /// completes as well. + /// + [UnityTest] + public IEnumerator AllClientsFinish() + { + StartNewSceneEventProgress(); + + for (int i = 0; i < NumberOfClients; i++) + { + var currentClientNetworkManager = m_ClientNetworkManagers[i]; + SetClientFinished(currentClientNetworkManager.LocalClientId, true); + + // stagger when the clients mock finish loading by 1 network tick + yield return s_DefaultWaitForTick; + } + + yield return WaitForConditionOrTimeOut(() => m_SceneEventProgressCompleted); + AssertOnTimeout($"Timed out waiting for SceneEventProgress to time out!"); + + VerifyClientsThatCompleted(); + } + + /// + /// This verifies that SceneEventProgress will still invoke the + /// OnComplete delegate handler when it times out + /// + [UnityTest] + public IEnumerator CompletesWhenTimedOut() + { + // Adjust the timeout for 2 seconds for this test + m_ServerNetworkManager.NetworkConfig.LoadSceneTimeOut = 2; + StartNewSceneEventProgress(); + + for (int i = 0; i < NumberOfClients; i++) + { + // Every other client fails to finish + var clientFinished = i % 2 == 0; + var currentClientNetworkManager = m_ClientNetworkManagers[i]; + SetClientFinished(currentClientNetworkManager.LocalClientId, clientFinished); + } + + yield return WaitForConditionOrTimeOut(() => m_SceneEventProgressCompleted); + AssertOnTimeout($"Timed out waiting for SceneEventProgress to time out!"); + + VerifyClientsThatCompleted(); + } + + /// + /// This verifies that SceneEventProgress will still complete + /// even when some of the originally connected clients disconnect + /// during a SceneEventProgress. + /// + [UnityTest] + public IEnumerator ClientsDisconnectDuring() + { + StartNewSceneEventProgress(); + + for (int i = 0; i < NumberOfClients; i++) + { + var currentClientNetworkManager = m_ClientNetworkManagers[i]; + // Two clients will disconnect + var clientFinished = i % 2 == 0; + SetClientFinished(currentClientNetworkManager.LocalClientId, clientFinished); + + if (!clientFinished) + { + currentClientNetworkManager.Shutdown(); + } + // wait anywhere from 100-500ms until processing next client + var randomWaitPeriod = Random.Range(0.1f, 0.5f); + yield return new WaitForSeconds(randomWaitPeriod); + } + yield return WaitForConditionOrTimeOut(() => m_SceneEventProgressCompleted); + AssertOnTimeout($"Timed out waiting for SceneEventProgress to time out!"); + VerifyClientsThatCompleted(); + } + + /// + /// This verifies that SceneEventProgress will still complete + /// even when clients late join. + /// + [UnityTest] + public IEnumerator ClientsLateJoinDuring() + { + StartNewSceneEventProgress(); + + for (int i = 0; i < NumberOfClients; i++) + { + // Two clients will connect during a SceneEventProgress + var shouldNewClientJoin = i % 2 == 0; + var currentClientNetworkManager = m_ClientNetworkManagers[i]; + + // All connected clients will finish their SceneEventProgress + SetClientFinished(currentClientNetworkManager.LocalClientId, true); + + if (shouldNewClientJoin) + { + yield return CreateAndStartNewClient(); + } + // wait anywhere from 100-500ms until processing next client + var randomWaitPeriod = Random.Range(0.1f, 0.5f); + yield return new WaitForSeconds(randomWaitPeriod); + } + + yield return WaitForConditionOrTimeOut(() => m_SceneEventProgressCompleted); + AssertOnTimeout($"Timed out waiting for SceneEventProgress to finish!"); + + VerifyClientsThatCompleted(); + } + } +} diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventProgressTests.cs.meta b/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventProgressTests.cs.meta new file mode 100644 index 0000000000..df89d3fb60 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventProgressTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 319d8f12c0057694b954c877e69103c7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From bdf21b8c259c26ca81b0451dc96be57e59387044 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 4 Oct 2022 11:17:45 -0500 Subject: [PATCH 079/138] test: NetworkAnimator manual test updates for PR-2127 (#2205) Fixes and additions to the NetworkAnimator manual tests. --- .../AnimatedCubeController.cs | 101 +++++++++++++---- .../AnimationUserInput.cs | 2 +- .../CubeAnimatorController.controller | 86 ++++++++++++++- .../NetworkAnimatorTests/Cube_Pulse.anim | 18 +-- .../NetworkAnimatorTestPrefab.prefab | 53 ++++++--- .../PlayerPositionOffset.cs | 103 ++++++++++++++++-- .../Animation/Resources/AnimatorObject.prefab | 19 ++++ .../Resources/OwnerAnimatorObject.prefab | 19 ++++ .../Tests/Runtime/Animation/TriggerTest.cs | 19 ++-- 9 files changed, 355 insertions(+), 65 deletions(-) diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimatedCubeController.cs b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimatedCubeController.cs index 2863598699..1b946b0426 100644 --- a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimatedCubeController.cs +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimatedCubeController.cs @@ -9,7 +9,6 @@ namespace Tests.Manual.NetworkAnimatorTests [RequireComponent(typeof(Animator))] public class AnimatedCubeController : NetworkBehaviour { - public int TestIterations = 20; private Animator m_Animator; private bool m_Rotate; private NetworkAnimator m_NetworkAnimator; @@ -30,12 +29,10 @@ private void DetermineNetworkAnimatorComponentType() public override void OnNetworkSpawn() { - if (HasAuthority()) - { - enabled = false; - } - m_Animator = GetComponent(); DetermineNetworkAnimatorComponentType(); + + m_Animator = GetComponent(); + m_Rotate = m_Animator.GetBool("Rotate"); } @@ -106,7 +103,7 @@ internal void PlayPulseAnimation() { if (!IsServer && IsOwner) { - PlayPulseAnimationServerRpc(m_Rotate); + m_NetworkAnimator.SetTrigger("Pulse"); } else if (IsServer && IsOwner) { @@ -119,7 +116,6 @@ internal void PlayPulseAnimation() } } - private Coroutine m_TestAnimatorRoutine; internal void TestAnimator(bool useNetworkAnimator = true) @@ -133,24 +129,89 @@ internal void TestAnimator(bool useNetworkAnimator = true) } } - private IEnumerator TestAnimatorRoutine() { - var interations = 0; - while (interations < TestIterations) + var waitForSeconds = new WaitForSeconds(0.016f); + var counter = 1.0f; + Debug.Log("Linearly increase test:"); + while (counter < 100) + { + m_Animator.SetFloat("TestFloat", counter); + m_Animator.SetInteger("TestInt", (int)counter); + counter++; + yield return waitForSeconds; + } + Debug.Log("Random value assignment test:"); + counter = 0.0f; + while (counter < 100) + { + m_Animator.SetFloat("TestFloat", UnityEngine.Random.Range(0.0f, 100.0f)); + m_Animator.SetInteger("TestInt", UnityEngine.Random.Range(0, 100)); + counter++; + yield return waitForSeconds; + } + StopCoroutine(m_TestAnimatorRoutine); + m_TestAnimatorRoutine = null; + } + + private int m_TestIntValue; + private float m_TestFloatValue; + + private void DisplayTestIntValueIfChanged() + { + var testIntValue = m_Animator.GetInteger("TestInt"); + if (m_TestIntValue != testIntValue) { - var counter = 1.0f; - m_NetworkAnimator.SetTrigger("TestTrigger"); - while (counter < 100) + m_TestIntValue = testIntValue; + Debug.Log($"[{name}]TestInt value changed to = {m_TestIntValue}"); + } + var testFloatValue = m_Animator.GetInteger("TestFloat"); + if (m_TestFloatValue != testFloatValue) + { + m_TestFloatValue = testFloatValue; + Debug.Log($"[{name}]TestFloat value changed to = {m_TestIntValue}"); + } + } + + private void LateUpdate() + { + + if (!IsSpawned || !IsOwner) + { + if (!IsOwner && IsSpawned) { - m_Animator.SetFloat("TestFloat", counter); - m_Animator.SetInteger("TestInt", (int)counter); - counter++; - yield return null; + DisplayTestIntValueIfChanged(); + return; } - interations++; + + return; + } + + DisplayTestIntValueIfChanged(); + + // Rotates the cube + if (Input.GetKeyDown(KeyCode.C)) + { + ToggleRotateAnimation(); + } + + // Pulse animation (scale down and up slowly) + if (Input.GetKeyDown(KeyCode.Space)) + { + PlayPulseAnimation(); + } + + // Test changing Animator parameters over time + if (Input.GetKeyDown(KeyCode.T)) + { + TestAnimator(); + } + + if (Input.GetKeyDown(KeyCode.R)) + { + Debug.Log($"[{name}] TestInt value = {m_TestIntValue}"); + Debug.Log($"[{name}] TestInt value = {m_TestIntValue}"); } - yield return null; } } } diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimationUserInput.cs b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimationUserInput.cs index 4d7eed6421..b61610a816 100644 --- a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimationUserInput.cs +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimationUserInput.cs @@ -17,7 +17,7 @@ private void Start() // Update is called once per frame private void LateUpdate() { - if (!IsSpawned || !IsOwner) + if (!IsSpawned) { return; } diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CubeAnimatorController.controller b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CubeAnimatorController.controller index af3d9502eb..8a16197348 100644 --- a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CubeAnimatorController.controller +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CubeAnimatorController.controller @@ -95,9 +95,9 @@ AnimatorStateTransition: m_Mute: 0 m_IsExit: 0 serializedVersion: 3 - m_TransitionDuration: 0.3220486 + m_TransitionDuration: 2.2832582 m_TransitionOffset: 0 - m_ExitTime: 0.8417445 + m_ExitTime: 0.6903777 m_HasExitTime: 1 m_HasFixedDuration: 1 m_InterruptionSource: 0 @@ -128,6 +128,28 @@ AnimatorStateTransition: m_InterruptionSource: 0 m_OrderedInterruption: 1 m_CanTransitionToSelf: 1 +--- !u!1101 &-4455521266437619866 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: [] + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: -1676030328622575462} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.25 + m_TransitionOffset: 0 + m_ExitTime: 0.986124 + m_HasExitTime: 1 + m_HasFixedDuration: 1 + m_InterruptionSource: 0 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 --- !u!1101 &-4282378417640754704 AnimatorStateTransition: m_ObjectHideFlags: 1 @@ -203,6 +225,31 @@ AnimatorStateTransition: m_InterruptionSource: 0 m_OrderedInterruption: 1 m_CanTransitionToSelf: 1 +--- !u!1101 &-2947710236483485956 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 1 + m_ConditionEvent: TestTrigger + m_EventTreshold: 0 + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: -1603678049383302394} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.25 + m_TransitionOffset: 0 + m_ExitTime: 0.75 + m_HasExitTime: 1 + m_HasFixedDuration: 1 + m_InterruptionSource: 0 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 --- !u!1101 &-2547562858931433901 AnimatorStateTransition: m_ObjectHideFlags: 1 @@ -253,6 +300,7 @@ AnimatorState: m_Transitions: - {fileID: -4282378417640754704} - {fileID: 1678733063235620591} + - {fileID: -2947710236483485956} m_StateMachineBehaviours: - {fileID: 6706570197837314945} m_Position: {x: 50, y: 50, z: 0} @@ -297,6 +345,33 @@ AnimatorState: m_MirrorParameter: m_CycleOffsetParameter: m_TimeParameter: +--- !u!1102 &-1603678049383302394 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: TriggerTest2 + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: + - {fileID: -4455521266437619866} + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 7400000, guid: 52bc8e8008543df4594fd2f9a1b9d424, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: --- !u!114 &-1261272140589342921 MonoBehaviour: m_ObjectHideFlags: 1 @@ -366,7 +441,10 @@ AnimatorStateMachine: m_Position: {x: 290, y: -150, z: 0} - serializedVersion: 1 m_State: {fileID: 2733578584814225138} - m_Position: {x: 290, y: 180, z: 0} + m_Position: {x: 50, y: 190, z: 0} + - serializedVersion: 1 + m_State: {fileID: -1603678049383302394} + m_Position: {x: 440, y: 190, z: 0} m_ChildStateMachines: [] m_AnyStateTransitions: [] m_EntryTransitions: [] @@ -644,7 +722,7 @@ AnimatorState: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_Name: Pulse - m_Speed: 0.25 + m_Speed: 0.1 m_CycleOffset: 0 m_Transitions: - {fileID: 1830534497079063084} diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/Cube_Pulse.anim b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/Cube_Pulse.anim index 3038f0ab3c..d59a94a04d 100644 --- a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/Cube_Pulse.anim +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/Cube_Pulse.anim @@ -29,7 +29,7 @@ AnimationClip: inWeight: {x: 0.33333334, y: 0.33333334, z: 0.33333334} outWeight: {x: 0.33333334, y: 0.33333334, z: 0.33333334} - serializedVersion: 3 - time: 0.2 + time: 0.5 value: {x: 3, y: 3, z: 3} inSlope: {x: 0, y: 0, z: 0} outSlope: {x: 0, y: 0, z: 0} @@ -38,7 +38,7 @@ AnimationClip: inWeight: {x: 0.33333334, y: 0.33333334, z: 0.33333334} outWeight: {x: 0.33333334, y: 0.33333334, z: 0.33333334} - serializedVersion: 3 - time: 0.4 + time: 1 value: {x: 1, y: 1, z: 1} inSlope: {x: 0, y: 0, z: 0} outSlope: {x: 0, y: 0, z: 0} @@ -95,7 +95,7 @@ AnimationClip: m_AdditiveReferencePoseClip: {fileID: 0} m_AdditiveReferencePoseTime: 0 m_StartTime: 0 - m_StopTime: 0.4 + m_StopTime: 1 m_OrientationOffsetY: 0 m_Level: 0 m_CycleOffset: 0 @@ -124,7 +124,7 @@ AnimationClip: inWeight: 0.33333334 outWeight: 0.33333334 - serializedVersion: 3 - time: 0.2 + time: 0.5 value: 3 inSlope: 0 outSlope: 0 @@ -133,7 +133,7 @@ AnimationClip: inWeight: 0.33333334 outWeight: 0.33333334 - serializedVersion: 3 - time: 0.4 + time: 1 value: 1 inSlope: 0 outSlope: 0 @@ -161,7 +161,7 @@ AnimationClip: inWeight: 0.33333334 outWeight: 0.33333334 - serializedVersion: 3 - time: 0.2 + time: 0.5 value: 3 inSlope: 0 outSlope: 0 @@ -170,7 +170,7 @@ AnimationClip: inWeight: 0.33333334 outWeight: 0.33333334 - serializedVersion: 3 - time: 0.4 + time: 1 value: 1 inSlope: 0 outSlope: 0 @@ -198,7 +198,7 @@ AnimationClip: inWeight: 0.33333334 outWeight: 0.33333334 - serializedVersion: 3 - time: 0.2 + time: 0.5 value: 3 inSlope: 0 outSlope: 0 @@ -207,7 +207,7 @@ AnimationClip: inWeight: 0.33333334 outWeight: 0.33333334 - serializedVersion: 3 - time: 0.4 + time: 1 value: 1 inSlope: 0 outSlope: 0 diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorTestPrefab.prefab b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorTestPrefab.prefab index c1b99a54f2..293a5c28f9 100644 --- a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorTestPrefab.prefab +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorTestPrefab.prefab @@ -343,7 +343,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: b322be05414199349ae9d1aea28228a7, type: 3} m_Name: m_EditorClassIdentifier: - TestIterations: 20 + TestIterations: 10 --- !u!114 &3632991713004262889 MonoBehaviour: m_ObjectHideFlags: 0 @@ -356,6 +356,25 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: f6a3556fb5f3bee4e8a0fb88acff87ff, type: 3} m_Name: m_EditorClassIdentifier: + TransitionStateInfoList: + - Layer: 0 + OriginatingState: 2081823275 + DestinationState: -570305638 + TransitionDuration: 0.30227518 + TriggerNameHash: 1033918907 + TransitionIndex: 1 + - Layer: 0 + OriginatingState: 2081823275 + DestinationState: -1509639022 + TransitionDuration: 0.25 + TriggerNameHash: 1033918907 + TransitionIndex: 2 + - Layer: 1 + OriginatingState: 2081823275 + DestinationState: -623385122 + TransitionDuration: 2.2832582 + TriggerNameHash: -623385122 + TransitionIndex: 0 m_Animator: {fileID: 3078684837575715027} --- !u!1 &3214090169133903718 GameObject: @@ -473,7 +492,6 @@ GameObject: - component: {fileID: 3214090169675393153} - component: {fileID: 5628721071472145353} - component: {fileID: 2564457647300903827} - - component: {fileID: 3406550028494568410} m_Layer: 0 m_Name: NetworkAnimatorTestPrefab m_TagString: Untagged @@ -555,18 +573,6 @@ MonoBehaviour: ScaleThreshold: 0.01 InLocalSpace: 0 Interpolate: 0 ---- !u!114 &3406550028494568410 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 3214090169675393154} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: c869ae2381a7034469e7b4d4f165c72a, type: 3} - m_Name: - m_EditorClassIdentifier: --- !u!1 &3953046479900177945 GameObject: m_ObjectHideFlags: 0 @@ -647,6 +653,25 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: e8d0727d5ae3244e3b569694d3912374, type: 3} m_Name: m_EditorClassIdentifier: + TransitionStateInfoList: + - Layer: 0 + OriginatingState: 2081823275 + DestinationState: -570305638 + TransitionDuration: 0.30227518 + TriggerNameHash: 1033918907 + TransitionIndex: 1 + - Layer: 0 + OriginatingState: 2081823275 + DestinationState: -1509639022 + TransitionDuration: 0.25 + TriggerNameHash: 1033918907 + TransitionIndex: 2 + - Layer: 1 + OriginatingState: 2081823275 + DestinationState: -623385122 + TransitionDuration: 2.2832582 + TriggerNameHash: -623385122 + TransitionIndex: 0 m_Animator: {fileID: 6047057957339659638} --- !u!1 &4033769488171516769 GameObject: diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/PlayerPositionOffset.cs b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/PlayerPositionOffset.cs index 6c3906d4ee..24e615d8be 100644 --- a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/PlayerPositionOffset.cs +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/PlayerPositionOffset.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using Unity.Netcode; @@ -11,11 +12,85 @@ public class PlayerPositionOffset : NetworkBehaviour [SerializeField] private Text m_OwnerAuthText; - - private Vector3[] m_Positions = new Vector3[] { Vector3.right, Vector3.left, Vector3.forward, Vector3.back }; + // This creates the normalized vectors to offset each newly spawned instance in a "circular" pattern around the host instance + private Vector3[] m_Positions = new Vector3[] { Vector3.right, Vector3.right + Vector3.forward, Vector3.forward, Vector3.forward + Vector3.left, Vector3.left, Vector3.back + Vector3.left, Vector3.back, Vector3.back + Vector3.right }; private static uint s_PositionIndex; private const float k_Spacing = 64; - private static float s_Layers = 0; + private static uint s_Layers = 1; + //[Layer Index][Position Index][Assigned Client] + private static Dictionary> s_ClientPositions = new Dictionary>(); + + private void RemoveClientOffset(ulong cliendId) + { + foreach (var layerEntry in s_ClientPositions) + { + var keyToRemove = -1; + foreach (var positionEntry in layerEntry.Value) + { + if (positionEntry.Value == cliendId) + { + keyToRemove = (int)positionEntry.Key; + break; + } + } + if (keyToRemove != -1) + { + layerEntry.Value.Remove((uint)keyToRemove); + // For next client that joins, start at the removed position index + // and layer only if the entry's position index or layer is less than + // or equal to the current position and layer values + if ((uint)keyToRemove <= s_PositionIndex && layerEntry.Key <= s_Layers) + { + s_PositionIndex = (uint)keyToRemove; + s_Layers = layerEntry.Key; + } + } + } + } + + private bool CanAssignClient(ulong clientId) + { + if (s_ClientPositions.ContainsKey(s_Layers)) + { + if (s_ClientPositions[s_Layers].ContainsKey(s_PositionIndex)) + { + return s_ClientPositions[s_Layers][s_PositionIndex] == 0; + } + } + return true; + } + + private void RecordClientOffset(ulong clientId) + { + while (!CanAssignClient(clientId)) + { + IncrementOffset(); + } + if (!s_ClientPositions.ContainsKey(s_Layers)) + { + s_ClientPositions.Add(s_Layers, new Dictionary()); + } + if (!s_ClientPositions[s_Layers].ContainsKey(s_PositionIndex)) + { + s_ClientPositions[s_Layers].Add(s_PositionIndex, clientId); + } + else + { + s_ClientPositions[s_Layers][s_PositionIndex] = clientId; + } + } + + private void IncrementOffset() + { + s_PositionIndex++; + s_PositionIndex = (uint)(s_PositionIndex % m_Positions.Length); + + // This means we rolled over + if (s_PositionIndex == 0) + { + s_Layers++; + } + } public override void OnNetworkSpawn() { @@ -23,17 +98,19 @@ public override void OnNetworkSpawn() { if (IsOwner) { + // Make sure we start with fresh values when host instantiates + s_Layers = 1; + s_PositionIndex = 0; + s_ClientPositions.Clear(); + + // Host always is at the center transform.position = Vector3.zero; + NetworkManager.OnClientDisconnectCallback += NetworkManager_OnClientDisconnectCallback; } else { - if (s_PositionIndex == 0) - { - s_Layers++; - } + RecordClientOffset(OwnerClientId); transform.position = m_Positions[s_PositionIndex] * s_Layers * k_Spacing; - s_PositionIndex++; - s_PositionIndex = (uint)(s_PositionIndex % m_Positions.Length); } } @@ -42,5 +119,13 @@ public override void OnNetworkSpawn() base.OnNetworkSpawn(); } + + private void NetworkManager_OnClientDisconnectCallback(ulong clientId) + { + if (clientId != NetworkManager.LocalClientId) + { + RemoveClientOffset(clientId); + } + } } } diff --git a/testproject/Assets/Tests/Runtime/Animation/Resources/AnimatorObject.prefab b/testproject/Assets/Tests/Runtime/Animation/Resources/AnimatorObject.prefab index 218a45f9b9..d3c2768550 100644 --- a/testproject/Assets/Tests/Runtime/Animation/Resources/AnimatorObject.prefab +++ b/testproject/Assets/Tests/Runtime/Animation/Resources/AnimatorObject.prefab @@ -378,6 +378,25 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: e8d0727d5ae3244e3b569694d3912374, type: 3} m_Name: m_EditorClassIdentifier: + TableData: + - Layer: 0 + OriginatingState: 2081823275 + DestinationState: -570305638 + TransitionDuration: 0.30227518 + TriggerNameHash: 1033918907 + TransitionIndex: 1 + - Layer: 0 + OriginatingState: 2081823275 + DestinationState: -1509639022 + TransitionDuration: 0.25 + TriggerNameHash: 1033918907 + TransitionIndex: 2 + - Layer: 1 + OriginatingState: 2081823275 + DestinationState: -623385122 + TransitionDuration: 0.8019884 + TriggerNameHash: -623385122 + TransitionIndex: 0 m_Animator: {fileID: 6515743261518512780} --- !u!114 &-8876216387850298050 MonoBehaviour: diff --git a/testproject/Assets/Tests/Runtime/Animation/Resources/OwnerAnimatorObject.prefab b/testproject/Assets/Tests/Runtime/Animation/Resources/OwnerAnimatorObject.prefab index f0e171a86c..9d5cd94cce 100644 --- a/testproject/Assets/Tests/Runtime/Animation/Resources/OwnerAnimatorObject.prefab +++ b/testproject/Assets/Tests/Runtime/Animation/Resources/OwnerAnimatorObject.prefab @@ -378,6 +378,25 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: f6a3556fb5f3bee4e8a0fb88acff87ff, type: 3} m_Name: m_EditorClassIdentifier: + TableData: + - Layer: 0 + OriginatingState: 2081823275 + DestinationState: -570305638 + TransitionDuration: 0.30227518 + TriggerNameHash: 1033918907 + TransitionIndex: 1 + - Layer: 0 + OriginatingState: 2081823275 + DestinationState: -1509639022 + TransitionDuration: 0.25 + TriggerNameHash: 1033918907 + TransitionIndex: 2 + - Layer: 1 + OriginatingState: 2081823275 + DestinationState: -623385122 + TransitionDuration: 0.8019884 + TriggerNameHash: -623385122 + TransitionIndex: 0 m_Animator: {fileID: 6515743261518512780} --- !u!114 &-8876216387850298050 MonoBehaviour: diff --git a/testproject/Assets/Tests/Runtime/Animation/TriggerTest.cs b/testproject/Assets/Tests/Runtime/Animation/TriggerTest.cs index 69e7c94bdd..b5b59dcc6c 100644 --- a/testproject/Assets/Tests/Runtime/Animation/TriggerTest.cs +++ b/testproject/Assets/Tests/Runtime/Animation/TriggerTest.cs @@ -73,17 +73,20 @@ override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { var networkObject = animator.GetComponent(); - var clientId = networkObject.NetworkManager.LocalClientId; - Debug.Log($"Client-{clientId} state exited (trigger reset)!"); - if (ClientsThatTriggered.Contains(clientId) && networkObject.OwnerClientId == clientId) + if (networkObject != null) { - ClientsThatTriggered.Remove(clientId); - } + var clientId = networkObject.NetworkManager.LocalClientId; + Debug.Log($"Client-{clientId} state exited (trigger reset)!"); + if (ClientsThatTriggered.Contains(clientId) && networkObject.OwnerClientId == clientId) + { + ClientsThatTriggered.Remove(clientId); + } - if (!ClientsThatResetTrigger.Contains(clientId) && networkObject.OwnerClientId == clientId) - { - ClientsThatResetTrigger.Add(clientId); + if (!ClientsThatResetTrigger.Contains(clientId) && networkObject.OwnerClientId == clientId) + { + ClientsThatResetTrigger.Add(clientId); + } } } From fe018d8e93e5b02d5a3cd14da59a0b66785f9768 Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Tue, 4 Oct 2022 16:08:32 -0400 Subject: [PATCH 080/138] feat: IPv6 support in UnityTransport [MTT-4801] (#2232) * feat: IPv6 support in UnityTransport [MTT-4801] * Add PR number to CHANGELOG entry * Make standards happy --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Transports/UTP/UnityTransport.cs | 11 ++-- .../Editor/Transports/UnityTransportTests.cs | 34 ++++++++-- .../Transports/UnityTransportTestHelpers.cs | 10 ++- .../Runtime/Transports/UnityTransportTests.cs | 62 +++++++++++++------ 5 files changed, 89 insertions(+), 29 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index b5c96378a7..f007ab7831 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -11,6 +11,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- IPv6 is now supported for direct connections when using `UnityTransport`. (#2232) - Added WebSocket support when using UTP 2.0 with `UseWebSockets` property in the `UnityTransport` component of the `NetworkManager` allowing to pick WebSockets for communication. When building for WebGL, this selection happens automatically. (#2201) ### Changed diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index 68862254fb..dbbd8dd0c4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -296,10 +296,12 @@ public struct ConnectionAddressData private static NetworkEndpoint ParseNetworkEndpoint(string ip, ushort port) { - if (!NetworkEndpoint.TryParse(ip, port, out var endpoint)) + NetworkEndpoint endpoint = default; + + if (!NetworkEndpoint.TryParse(ip, port, out endpoint, NetworkFamily.Ipv4) && + !NetworkEndpoint.TryParse(ip, port, out endpoint, NetworkFamily.Ipv6)) { Debug.LogError($"Invalid network endpoint: {ip}:{port}."); - return default; } return endpoint; @@ -491,7 +493,8 @@ private bool ClientBindAndConnect() InitDriver(); - int result = m_Driver.Bind(NetworkEndpoint.AnyIpv4); + var bindEndpoint = serverEndpoint.Family == NetworkFamily.Ipv6 ? NetworkEndpoint.AnyIpv6 : NetworkEndpoint.AnyIpv4; + int result = m_Driver.Bind(bindEndpoint); if (result != 0) { Debug.LogError("Client failed to bind"); @@ -632,7 +635,7 @@ public void SetClientRelayData(string ipAddress, ushort port, byte[] allocationI /// /// Sets IP and Port information. This will be ignored if using the Unity Relay and you should call /// - /// The remote IP address + /// The remote IP address (despite the name, can be an IPv6 address) /// The remote port /// The local listen address public void SetConnectionData(string ipv4Address, ushort port, string listenAddress = null) diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs index c26cc81b61..fa53588973 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs @@ -7,9 +7,9 @@ namespace Unity.Netcode.EditorTests { public class UnityTransportTests { - // Check that starting a server doesn't immediately result in faulted tasks. + // Check that starting an IPv4 server succeeds. [Test] - public void UnityTransport_BasicInitServer() + public void UnityTransport_BasicInitServer_IPv4() { UnityTransport transport = new GameObject().AddComponent(); transport.Initialize(); @@ -19,9 +19,9 @@ public void UnityTransport_BasicInitServer() transport.Shutdown(); } - // Check that starting a client doesn't immediately result in faulted tasks. + // Check that starting an IPv4 client succeeds. [Test] - public void UnityTransport_BasicInitClient() + public void UnityTransport_BasicInitClient_IPv4() { UnityTransport transport = new GameObject().AddComponent(); transport.Initialize(); @@ -31,6 +31,32 @@ public void UnityTransport_BasicInitClient() transport.Shutdown(); } + // Check that starting an IPv6 server succeeds. + [Test] + public void UnityTransport_BasicInitServer_IPv6() + { + UnityTransport transport = new GameObject().AddComponent(); + transport.Initialize(); + transport.SetConnectionData("::1", 7777); + + Assert.True(transport.StartServer()); + + transport.Shutdown(); + } + + // Check that starting an IPv6 client succeeds. + [Test] + public void UnityTransport_BasicInitClient_IPv6() + { + UnityTransport transport = new GameObject().AddComponent(); + transport.Initialize(); + transport.SetConnectionData("::1", 7777); + + Assert.True(transport.StartClient()); + + transport.Shutdown(); + } + // Check that we can't restart a server. [Test] public void UnityTransport_NoRestartServer() diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTestHelpers.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTestHelpers.cs index 31add35154..f6d73f94ed 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTestHelpers.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTestHelpers.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using Unity.Netcode.Transports.UTP; +using Unity.Networking.Transport; using UnityEngine; namespace Unity.Netcode.RuntimeTests @@ -37,15 +38,22 @@ public static IEnumerator WaitForNetworkEvent(NetworkEvent type, List events, - int maxPayloadSize = UnityTransport.InitialMaxPayloadSize, int maxSendQueueSize = 0) + int maxPayloadSize = UnityTransport.InitialMaxPayloadSize, int maxSendQueueSize = 0, NetworkFamily family = NetworkFamily.Ipv4) { var logger = new TransportEventLogger(); events = logger.Events; transport = new GameObject().AddComponent(); + transport.OnTransportEvent += logger.HandleEvent; transport.MaxPayloadSize = maxPayloadSize; transport.MaxSendQueueSize = maxSendQueueSize; + + if (family == NetworkFamily.Ipv6) + { + transport.SetConnectionData("::1", 7777); + } + transport.Initialize(); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs index b08e49eb6b..53fccb0229 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using Unity.Netcode.Transports.UTP; +using Unity.Networking.Transport; using UnityEngine; using UnityEngine.TestTools; using static Unity.Netcode.RuntimeTests.UnityTransportTestHelpers; @@ -21,6 +22,15 @@ public class UnityTransportTests NetworkDelivery.Reliable }; + private static readonly NetworkFamily[] k_NetworkFamiltyParameters = + { + NetworkFamily.Ipv4, +#if !(UNITY_SWITCH || UNITY_PS4 || UNITY_PS5) + // IPv6 is not supported on Switch, PS4, and PS5. + NetworkFamily.Ipv6 +#endif + }; + private UnityTransport m_Server, m_Client1, m_Client2; private List m_ServerEvents, m_Client1Events, m_Client2Events; @@ -60,10 +70,12 @@ public IEnumerator Cleanup() // Check if can make a simple data exchange. [UnityTest] - public IEnumerator PingPong([ValueSource("k_DeliveryParameters")] NetworkDelivery delivery) + public IEnumerator PingPong( + [ValueSource("k_DeliveryParameters")] NetworkDelivery delivery, + [ValueSource("k_NetworkFamiltyParameters")] NetworkFamily family) { - InitializeTransport(out m_Server, out m_ServerEvents); - InitializeTransport(out m_Client1, out m_Client1Events); + InitializeTransport(out m_Server, out m_ServerEvents, family: family); + InitializeTransport(out m_Client1, out m_Client1Events, family: family); m_Server.StartServer(); m_Client1.StartClient(); @@ -89,10 +101,12 @@ public IEnumerator PingPong([ValueSource("k_DeliveryParameters")] NetworkDeliver // Check if can make a simple data exchange (both ways at a time). [UnityTest] - public IEnumerator PingPongSimultaneous([ValueSource("k_DeliveryParameters")] NetworkDelivery delivery) + public IEnumerator PingPongSimultaneous( + [ValueSource("k_DeliveryParameters")] NetworkDelivery delivery, + [ValueSource("k_NetworkFamiltyParameters")] NetworkFamily family) { - InitializeTransport(out m_Server, out m_ServerEvents); - InitializeTransport(out m_Client1, out m_Client1Events); + InitializeTransport(out m_Server, out m_ServerEvents, family: family); + InitializeTransport(out m_Client1, out m_Client1Events, family: family); m_Server.StartServer(); m_Client1.StartClient(); @@ -126,13 +140,15 @@ public IEnumerator PingPongSimultaneous([ValueSource("k_DeliveryParameters")] Ne // loopback traffic are too small for the amount of data sent in a single update here. [UnityTest] [UnityPlatform(exclude = new[] { RuntimePlatform.Switch, RuntimePlatform.PS4, RuntimePlatform.PS5 })] - public IEnumerator SendMaximumPayloadSize([ValueSource("k_DeliveryParameters")] NetworkDelivery delivery) + public IEnumerator SendMaximumPayloadSize( + [ValueSource("k_DeliveryParameters")] NetworkDelivery delivery, + [ValueSource("k_NetworkFamiltyParameters")] NetworkFamily family) { // We want something that's over the old limit of ~44KB for reliable payloads. var payloadSize = 64 * 1024; - InitializeTransport(out m_Server, out m_ServerEvents, payloadSize); - InitializeTransport(out m_Client1, out m_Client1Events, payloadSize); + InitializeTransport(out m_Server, out m_ServerEvents, payloadSize, family: family); + InitializeTransport(out m_Client1, out m_Client1Events, payloadSize, family: family); m_Server.StartServer(); m_Client1.StartClient(); @@ -164,10 +180,12 @@ public IEnumerator SendMaximumPayloadSize([ValueSource("k_DeliveryParameters")] // Check making multiple sends to a client in a single frame. [UnityTest] - public IEnumerator MultipleSendsSingleFrame([ValueSource("k_DeliveryParameters")] NetworkDelivery delivery) + public IEnumerator MultipleSendsSingleFrame( + [ValueSource("k_DeliveryParameters")] NetworkDelivery delivery, + [ValueSource("k_NetworkFamiltyParameters")] NetworkFamily family) { - InitializeTransport(out m_Server, out m_ServerEvents); - InitializeTransport(out m_Client1, out m_Client1Events); + InitializeTransport(out m_Server, out m_ServerEvents, family: family); + InitializeTransport(out m_Client1, out m_Client1Events, family: family); m_Server.StartServer(); m_Client1.StartClient(); @@ -193,11 +211,13 @@ public IEnumerator MultipleSendsSingleFrame([ValueSource("k_DeliveryParameters") // Check sending data to multiple clients. [UnityTest] - public IEnumerator SendMultipleClients([ValueSource("k_DeliveryParameters")] NetworkDelivery delivery) + public IEnumerator SendMultipleClients( + [ValueSource("k_DeliveryParameters")] NetworkDelivery delivery, + [ValueSource("k_NetworkFamiltyParameters")] NetworkFamily family) { - InitializeTransport(out m_Server, out m_ServerEvents); - InitializeTransport(out m_Client1, out m_Client1Events); - InitializeTransport(out m_Client2, out m_Client2Events); + InitializeTransport(out m_Server, out m_ServerEvents, family: family); + InitializeTransport(out m_Client1, out m_Client1Events, family: family); + InitializeTransport(out m_Client2, out m_Client2Events, family: family); m_Server.StartServer(); m_Client1.StartClient(); @@ -234,11 +254,13 @@ public IEnumerator SendMultipleClients([ValueSource("k_DeliveryParameters")] Net // Check receiving data from multiple clients. [UnityTest] - public IEnumerator ReceiveMultipleClients([ValueSource("k_DeliveryParameters")] NetworkDelivery delivery) + public IEnumerator ReceiveMultipleClients( + [ValueSource("k_DeliveryParameters")] NetworkDelivery delivery, + [ValueSource("k_NetworkFamiltyParameters")] NetworkFamily family) { - InitializeTransport(out m_Server, out m_ServerEvents); - InitializeTransport(out m_Client1, out m_Client1Events); - InitializeTransport(out m_Client2, out m_Client2Events); + InitializeTransport(out m_Server, out m_ServerEvents, family: family); + InitializeTransport(out m_Client1, out m_Client1Events, family: family); + InitializeTransport(out m_Client2, out m_Client2Events, family: family); m_Server.StartServer(); m_Client1.StartClient(); From a2d10e8068ca6753c7433cab2c6ad9f7917cd004 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 4 Oct 2022 19:25:18 -0500 Subject: [PATCH 081/138] fix: Parenting does not preserve WorldPositionStays option and other parenting related issues (#2146) * fix Parenting: Fixed the issue where WorldPositionStays was not being synchronized with clients. Removed m_IsReparented and associated properties. Added world position stays parameter to CreateLocalNetworkObject to preserve worldPostionStays. This fixes some edge case scenarios and issues with parenting in-scene placed NetworkObjects. One in particular is to provide the local space position and rotation in ParentSyncMessage if the WorldPositionStays value is false. The server might maintain the original local space values and upon being reparented it re-applies the local space values. This is only pertinent if the user decides they don't want to use a NetworkTransform for the NetworkObject in question. Now, a users can control what the final position, rotation, and scale of a NetworkObject is when being added/removed to/from a parent's sibling list ("parented or de-parented"). Parenting and Scale: Fixed issue where GetMessageSceneObject wasn't using the NetworkObject's lossy scale value. This would result in improper scaling of parented (WorldPositionStays = true) child NetworkObjects for late joining clients. Added comments with details on this. Fixed edge case scenario where nested in-scene placed NetworkObject children are de-parented and then a client late joins. Under this scenario (only for in-scene placed NetworkObjects) we want the late joining client to remove its parent, then set the transform values, and then get spawned. NetworkTransform: Fixed issue where TryCommitTransformToServer was not honoring the NetworkTransform.InLocalSpace property. Fixed issue where teleporting did not honor the NetworkTransforms's current synchronize axis settings. Removed the private m_TickFrequency property as it is no longer being used. * refactor CreateLocalNetworkObject now just accepts the NetworkObject.SceneObject structure as opposed to the 10 properties of the NetworkObject.SceneObject structure passed in as parameters. Provided the ability to invoke NetworkObject.TrySetParent with a null value in order to be able to remove a parent while also selecting whether they want WorldPositionStay to be true or false (when removing a parent WorldPositionStays impacts the final transform values and can lead to scale issue if you parent with WorldPositionStays set to false and then it remove the parent with WorldPositionStays set to true). Consolidated the CreateLocalNetworkObject's assignment of the transform values, parenting, and DDOL migration. Removed parenting from CreateLocalNetworkObject Removed setting the cached parent directly within SpawnNetworkObjectLocallyCommon as this is done within NetworkObject.ApplyNetworkParenting. * add Added RemoveParent to the ParentSync message. Added applying scale to CreateLocalNetworkObject. Added parent child sorting to the SceneEventData's synchronization serialization process. Added the TryRemoveParent helper method to make removing a parent easier. Without this method users will have to use TrySetParent and cast the null value to either GameObject or NetworkObject. * test Added ParentingWorldPositionStaysTest: Testing with and without WorldPositionStays while also varying child position, rotation, and scale values. Validates scale with multi-generation nested children where most of the parents have a scale other than Vector3.one. Created abstract IntegrationTestWithApproximation to avoid replicating the exact same set of Approximately methods. Added ParentingInSceneObjectsTests: This includes all of the scripts and assets that the ParentingInSceneObjectsTests needs. It includes adding the ParentingInSceneObjects scene to the scenes in build list as well. This also verifies an in-scene placed NetworkObject parented under a GameObject will preserve that hierarchy. NetworkTransformStateTest: Updated the bool parameters to be more meaningful named enums so you know what exactly is being tested. Added additional NetworkTansformState.IsTeleportingNextFrame logic to the existing test. Added an additional "step" which verifies the NetworkTransformState is additively collapsing for each detected axial delta over the relative threshold value. Co-authored-by: Jesse Olmer Co-authored-by: Unity Netcode CI <74025435+netcode-ci-service@users.noreply.github.com> --- com.unity.netcode.gameobjects/CHANGELOG.md | 7 + .../Components/NetworkTransform.cs | 67 +- .../Runtime/Core/NetworkManager.cs | 33 +- .../Runtime/Core/NetworkObject.cs | 274 +-- .../Messaging/Messages/ParentSyncMessage.cs | 64 +- .../Runtime/SceneManagement/SceneEventData.cs | 45 + .../Runtime/Spawning/NetworkSpawnManager.cs | 145 +- .../NetworkTransformStateTests.cs | 437 ++++- .../InSceneParentChildHandler.cs | 249 +++ .../InSceneParentChildHandler.cs.meta | 11 + .../InSceneParentedUnderGameObjectHandler.cs | 18 + ...ceneParentedUnderGameObjectHandler.cs.meta | 11 + .../Scripts/ManualTestAssetsDestroyer.cs | 17 + .../Scripts/ManualTestAssetsDestroyer.cs.meta | 11 + .../Scripts/NetworkManagerTestDisabler.cs | 22 + .../NetworkManagerTestDisabler.cs.meta | 11 + .../IntegrationTestWithApproximation.cs | 44 + .../IntegrationTestWithApproximation.cs.meta | 11 + .../ParentingInSceneObjects.unity | 1473 +++++++++++++++++ .../ParentingInSceneObjects.unity.meta | 7 + .../ParentingInSceneObjectsTests.cs | 312 ++++ .../ParentingInSceneObjectsTests.cs.meta | 11 + .../ParentingWorldPositionStaysTests.cs | 463 ++++++ .../ParentingWorldPositionStaysTests.cs.meta | 11 + .../ProjectSettings/EditorBuildSettings.asset | 3 + 25 files changed, 3491 insertions(+), 266 deletions(-) create mode 100644 testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs create mode 100644 testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs.meta create mode 100644 testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentedUnderGameObjectHandler.cs create mode 100644 testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentedUnderGameObjectHandler.cs.meta create mode 100644 testproject/Assets/Tests/Manual/Scripts/ManualTestAssetsDestroyer.cs create mode 100644 testproject/Assets/Tests/Manual/Scripts/ManualTestAssetsDestroyer.cs.meta create mode 100644 testproject/Assets/Tests/Manual/Scripts/NetworkManagerTestDisabler.cs create mode 100644 testproject/Assets/Tests/Manual/Scripts/NetworkManagerTestDisabler.cs.meta create mode 100644 testproject/Assets/Tests/Runtime/ObjectParenting/IntegrationTestWithApproximation.cs create mode 100644 testproject/Assets/Tests/Runtime/ObjectParenting/IntegrationTestWithApproximation.cs.meta create mode 100644 testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjects.unity create mode 100644 testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjects.unity.meta create mode 100644 testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs create mode 100644 testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs.meta create mode 100644 testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs create mode 100644 testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index f007ab7831..a8c49431c3 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -13,6 +13,8 @@ Additional documentation and release notes are available at [Multiplayer Documen - IPv6 is now supported for direct connections when using `UnityTransport`. (#2232) - Added WebSocket support when using UTP 2.0 with `UseWebSockets` property in the `UnityTransport` component of the `NetworkManager` allowing to pick WebSockets for communication. When building for WebGL, this selection happens automatically. (#2201) +- Added position, rotation, and scale to the `ParentSyncMessage` which provides users the ability to specify the final values on the server-side when `OnNetworkObjectParentChanged` is invoked just before the message is created (when the `Transform` values are applied to the message). (#2146) +- Added `NetworkObject.TryRemoveParent` method for convenience purposes opposed to having to cast null to either `GameObject` or `NetworkObject`. (#2146) ### Changed @@ -33,6 +35,11 @@ Additional documentation and release notes are available at [Multiplayer Documen - Implicit conversion of NetworkObjectReference to GameObject will now return null instead of throwing an exception if the referenced object could not be found (i.e., was already despawned) (#2158) - Fixed warning resulting from a stray NetworkAnimator.meta file (#2153) - Fixed Connection Approval Timeout not working client side. (#2164) +- Fixed issue where the `WorldPositionStays` parenting parameter was not being synchronized with clients. (#2146) +- Fixed issue where parented in-scene placed `NetworkObject`s would fail for late joining clients. (#2146) +- Fixed issue where scale was not being synchronized which caused issues with nested parenting and scale when `WorldPositionStays` was true. (#2146) +- Fixed issue with `NetworkTransform.ApplyTransformToNetworkStateWithInfo` where it was not honoring axis sync settings when `NetworkTransformState.IsTeleportingNextFrame` was true. (#2146) +- Fixed issue with `NetworkTransform.TryCommitTransformToServer` where it was not honoring the `InLocalSpace` setting. (#2146) - Fixed ClientRpcs always reporting in the profiler view as going to all clients, even when limited to a subset of clients by `ClientRpcParams`. (#2144) - Fixed RPC codegen failing to choose the correct extension methods for `FastBufferReader` and `FastBufferWriter` when the parameters were a generic type (i.e., List) and extensions for multiple instantiations of that type have been defined (i.e., List and List) (#2142) - Fixed the issue where running a server (i.e. not host) the second player would not receive updates (unless a third player joined). (#2127) diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index 2a6fe62305..f8d4dbd6a7 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -451,19 +451,6 @@ internal NetworkTransformState GetLastSentState() return m_LastSentState; } - /// - /// Calculated when spawned, this is used to offset a newly received non-authority side state by 1 tick duration - /// in order to end the extrapolation for that state's values. - /// - /// - /// Example: - /// NetworkState-A is received, processed, and measurements added - /// NetworkState-A is duplicated (NetworkState-A-Post) and its sent time is offset by the tick frequency - /// One tick later, NetworkState-A-Post is applied to end that delta's extrapolation. - /// to see how NetworkState-A-Post doesn't get excluded/missed - /// - private double m_TickFrequency; - /// /// This will try to send/commit the current transform delta states (if any) /// @@ -488,14 +475,16 @@ protected void TryCommitTransformToServer(Transform transformToCommit, double di } else // Non-Authority { + var position = InLocalSpace ? transformToCommit.localPosition : transformToCommit.position; + var rotation = InLocalSpace ? transformToCommit.localRotation : transformToCommit.rotation; // We are an owner requesting to update our state if (!m_CachedIsServer) { - SetStateServerRpc(transformToCommit.position, transformToCommit.rotation, transformToCommit.localScale, false); + SetStateServerRpc(position, rotation, transformToCommit.localScale, false); } else // Server is always authoritative (including owner authoritative) { - SetStateClientRpc(transformToCommit.position, transformToCommit.rotation, transformToCommit.localScale, false); + SetStateClientRpc(position, rotation, transformToCommit.localScale, false); } } } @@ -521,37 +510,24 @@ private void TryCommitTransform(Transform transformToCommit, double dirtyTime) } } + /// + /// Initializes the interpolators with the current transform values + /// private void ResetInterpolatedStateToCurrentAuthoritativeState() { var serverTime = NetworkManager.ServerTime.Time; - - // TODO: Look into a better way to communicate the entire state for late joining clients. - // Since the replicated network state will just be the most recent deltas and not the entire state. - //m_PositionXInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionX, serverTime); - //m_PositionYInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionY, serverTime); - //m_PositionZInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionZ, serverTime); - - //m_RotationInterpolator.ResetTo(Quaternion.Euler(m_LocalAuthoritativeNetworkState.RotAngleX, m_LocalAuthoritativeNetworkState.RotAngleY, m_LocalAuthoritativeNetworkState.RotAngleZ), serverTime); - - //m_ScaleXInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleX, serverTime); - //m_ScaleYInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleY, serverTime); - //m_ScaleZInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleZ, serverTime); - - // NOTE ABOUT THIS CHANGE: - // !!! This will exclude any scale changes because we currently do not spawn network objects with scale !!! - // Regarding Scale: It will be the same scale as the default scale for the object being spawned. var position = InLocalSpace ? transform.localPosition : transform.position; m_PositionXInterpolator.ResetTo(position.x, serverTime); m_PositionYInterpolator.ResetTo(position.y, serverTime); m_PositionZInterpolator.ResetTo(position.z, serverTime); + var rotation = InLocalSpace ? transform.localRotation : transform.rotation; m_RotationInterpolator.ResetTo(rotation, serverTime); - // TODO: (Create Jira Ticket) Synchronize local scale during NetworkObject synchronization - // (We will probably want to byte pack TransformData to offset the 3 float addition) - m_ScaleXInterpolator.ResetTo(transform.localScale.x, serverTime); - m_ScaleYInterpolator.ResetTo(transform.localScale.y, serverTime); - m_ScaleZInterpolator.ResetTo(transform.localScale.z, serverTime); + var scale = transform.localScale; + m_ScaleXInterpolator.ResetTo(scale.x, serverTime); + m_ScaleYInterpolator.ResetTo(scale.y, serverTime); + m_ScaleZInterpolator.ResetTo(scale.z, serverTime); } /// @@ -602,63 +578,63 @@ private bool ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState netw isDirty = true; } - if (SyncPositionX && Mathf.Abs(networkState.PositionX - position.x) >= PositionThreshold || networkState.IsTeleportingNextFrame) + if (SyncPositionX && (Mathf.Abs(networkState.PositionX - position.x) >= PositionThreshold || networkState.IsTeleportingNextFrame)) { networkState.PositionX = position.x; networkState.HasPositionX = true; isPositionDirty = true; } - if (SyncPositionY && Mathf.Abs(networkState.PositionY - position.y) >= PositionThreshold || networkState.IsTeleportingNextFrame) + if (SyncPositionY && (Mathf.Abs(networkState.PositionY - position.y) >= PositionThreshold || networkState.IsTeleportingNextFrame)) { networkState.PositionY = position.y; networkState.HasPositionY = true; isPositionDirty = true; } - if (SyncPositionZ && Mathf.Abs(networkState.PositionZ - position.z) >= PositionThreshold || networkState.IsTeleportingNextFrame) + if (SyncPositionZ && (Mathf.Abs(networkState.PositionZ - position.z) >= PositionThreshold || networkState.IsTeleportingNextFrame)) { networkState.PositionZ = position.z; networkState.HasPositionZ = true; isPositionDirty = true; } - if (SyncRotAngleX && Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleX, rotAngles.x)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame) + if (SyncRotAngleX && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleX, rotAngles.x)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame)) { networkState.RotAngleX = rotAngles.x; networkState.HasRotAngleX = true; isRotationDirty = true; } - if (SyncRotAngleY && Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleY, rotAngles.y)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame) + if (SyncRotAngleY && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleY, rotAngles.y)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame)) { networkState.RotAngleY = rotAngles.y; networkState.HasRotAngleY = true; isRotationDirty = true; } - if (SyncRotAngleZ && Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleZ, rotAngles.z)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame) + if (SyncRotAngleZ && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleZ, rotAngles.z)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame)) { networkState.RotAngleZ = rotAngles.z; networkState.HasRotAngleZ = true; isRotationDirty = true; } - if (SyncScaleX && Mathf.Abs(networkState.ScaleX - scale.x) >= ScaleThreshold || networkState.IsTeleportingNextFrame) + if (SyncScaleX && (Mathf.Abs(networkState.ScaleX - scale.x) >= ScaleThreshold || networkState.IsTeleportingNextFrame)) { networkState.ScaleX = scale.x; networkState.HasScaleX = true; isScaleDirty = true; } - if (SyncScaleY && Mathf.Abs(networkState.ScaleY - scale.y) >= ScaleThreshold || networkState.IsTeleportingNextFrame) + if (SyncScaleY && (Mathf.Abs(networkState.ScaleY - scale.y) >= ScaleThreshold || networkState.IsTeleportingNextFrame)) { networkState.ScaleY = scale.y; networkState.HasScaleY = true; isScaleDirty = true; } - if (SyncScaleZ && Mathf.Abs(networkState.ScaleZ - scale.z) >= ScaleThreshold || networkState.IsTeleportingNextFrame) + if (SyncScaleZ && (Mathf.Abs(networkState.ScaleZ - scale.z) >= ScaleThreshold || networkState.IsTeleportingNextFrame)) { networkState.ScaleZ = scale.z; networkState.HasScaleZ = true; @@ -1007,7 +983,6 @@ public override void OnNetworkSpawn() { m_CachedIsServer = IsServer; m_CachedNetworkManager = NetworkManager; - m_TickFrequency = 1.0 / NetworkManager.NetworkConfig.TickRate; Initialize(); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 84f97fbc6e..11cae4478c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -2077,14 +2077,31 @@ internal void HandleConnectionApproval(ulong ownerClientId, ConnectionApprovalRe if (response.CreatePlayerObject) { - var networkObject = SpawnManager.CreateLocalNetworkObject( - isSceneObject: false, - response.PlayerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent().GlobalObjectIdHash, - ownerClientId, - parentNetworkId: null, - networkSceneHandle: null, - response.Position, - response.Rotation); + var playerPrefabHash = response.PlayerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent().GlobalObjectIdHash; + + // Generate a SceneObject for the player object to spawn + var sceneObject = new NetworkObject.SceneObject + { + Header = new NetworkObject.SceneObject.HeaderData + { + IsPlayerObject = true, + OwnerClientId = ownerClientId, + IsSceneObject = false, + HasTransform = true, + Hash = playerPrefabHash, + }, + TargetClientId = ownerClientId, + Transform = new NetworkObject.SceneObject.TransformData + { + Position = response.Position.GetValueOrDefault(), + Rotation = response.Rotation.GetValueOrDefault() + } + }; + + // Create the player NetworkObject locally + var networkObject = SpawnManager.CreateLocalNetworkObject(sceneObject); + + // Spawn the player NetworkObject locally SpawnManager.SpawnNetworkObjectLocally( networkObject, SpawnManager.GetNetworkObjectId(), diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 390ae58979..6b21c5f89a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -573,21 +573,21 @@ internal void InvokeBehaviourOnNetworkObjectParentChanged(NetworkObject parentNe } } - private bool m_IsReparented; // Did initial parent (came from the scene hierarchy) change at runtime? private ulong? m_LatestParent; // What is our last set parent NetworkObject's ID? private Transform m_CachedParent; // What is our last set parent Transform reference? + private bool m_CachedWorldPositionStays = true; // Used to preserve the world position stays parameter passed in TrySetParent internal void SetCachedParent(Transform parentTransform) { m_CachedParent = parentTransform; } - internal (bool IsReparented, ulong? LatestParent) GetNetworkParenting() => (m_IsReparented, m_LatestParent); + internal ulong? GetNetworkParenting() => m_LatestParent; - internal void SetNetworkParenting(bool isReparented, ulong? latestParent) + internal void SetNetworkParenting(ulong? latestParent, bool worldPositionStays) { - m_IsReparented = isReparented; m_LatestParent = latestParent; + m_CachedWorldPositionStays = worldPositionStays; } /// @@ -598,7 +598,10 @@ internal void SetNetworkParenting(bool isReparented, ulong? latestParent) /// Whether or not reparenting was successful. public bool TrySetParent(Transform parent, bool worldPositionStays = true) { - return TrySetParent(parent.GetComponent(), worldPositionStays); + var networkObject = parent.GetComponent(); + + // If the parent doesn't have a NetworkObjet then return false, otherwise continue trying to parent + return networkObject == null ? false : TrySetParent(networkObject, worldPositionStays); } /// @@ -609,7 +612,29 @@ public bool TrySetParent(Transform parent, bool worldPositionStays = true) /// Whether or not reparenting was successful. public bool TrySetParent(GameObject parent, bool worldPositionStays = true) { - return TrySetParent(parent.GetComponent(), worldPositionStays); + // If we are removing ourself from a parent + if (parent == null) + { + return TrySetParent((NetworkObject)null, worldPositionStays); + } + + var networkObject = parent.GetComponent(); + + // If the parent doesn't have a NetworkObjet then return false, otherwise continue trying to parent + return networkObject == null ? false : TrySetParent(networkObject, worldPositionStays); + } + + /// + /// Removes the parent of the NetworkObject's transform + /// + /// + /// This is a more convenient way to remove the parent without having to cast the null value to either or + /// + /// If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before. + /// + public bool TryRemoveParent(bool worldPositionStays = true) + { + return TrySetParent((NetworkObject)null, worldPositionStays); } /// @@ -640,17 +665,21 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) return false; } - if (parent == null) + if (parent != null && !parent.IsSpawned) { return false; } + m_CachedWorldPositionStays = worldPositionStays; - if (!parent.IsSpawned) + if (parent == null) { - return false; + transform.SetParent(null, worldPositionStays); + } + else + { + transform.SetParent(parent.transform, worldPositionStays); } - transform.SetParent(parent.transform, worldPositionStays); return true; } @@ -686,7 +715,7 @@ private void OnTransformParentChanged() Debug.LogException(new SpawnStateException($"{nameof(NetworkObject)} can only be reparented after being spawned")); return; } - + var removeParent = false; var parentTransform = transform.parent; if (parentTransform != null) { @@ -709,19 +738,31 @@ private void OnTransformParentChanged() else { m_LatestParent = null; + removeParent = m_CachedParent != null; } - m_IsReparented = true; - ApplyNetworkParenting(); + ApplyNetworkParenting(removeParent); var message = new ParentSyncMessage { NetworkObjectId = NetworkObjectId, - IsReparented = m_IsReparented, IsLatestParentSet = m_LatestParent != null && m_LatestParent.HasValue, - LatestParent = m_LatestParent + LatestParent = m_LatestParent, + RemoveParent = removeParent, + WorldPositionStays = m_CachedWorldPositionStays, + Position = m_CachedWorldPositionStays ? transform.position : transform.localPosition, + Rotation = m_CachedWorldPositionStays ? transform.rotation : transform.localRotation, + Scale = transform.localScale, }; + // We need to preserve the m_CachedWorldPositionStays value until after we create the message + // in order to assure any local space values changed/reset get applied properly. If our + // parent is null then go ahead and reset the m_CachedWorldPositionStays the default value. + if (parentTransform == null) + { + m_CachedWorldPositionStays = true; + } + unsafe { var maxCount = NetworkManager.ConnectedClientsIds.Count; @@ -749,42 +790,90 @@ private void OnTransformParentChanged() // we call CheckOrphanChildren() method and quickly iterate over OrphanChildren set and see if we can reparent/adopt one. internal static HashSet OrphanChildren = new HashSet(); - internal bool ApplyNetworkParenting() + internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpawned = false) { if (!AutoObjectParentSync) { return false; } - if (!IsSpawned) + // SPECIAL CASE: + // The ignoreNotSpawned is a special case scenario where a late joining client has joined + // and loaded one or more scenes that contain nested in-scene placed NetworkObject children + // yet the server's synchronization information does not indicate the NetworkObject in question + // has a parent. Under this scenario, we want to remove the parent before spawning and setting + // the transform values. This is the only scenario where the ignoreNotSpawned parameter is used. + if (!IsSpawned && !ignoreNotSpawned) { return false; } - if (!m_IsReparented) + // Handle the first in-scene placed NetworkObject parenting scenarios. Once the m_LatestParent + // has been set, this will not be entered into again (i.e. the later code will be invoked and + // users will get notifications when the parent changes). + var isInScenePlaced = IsSceneObject.HasValue && IsSceneObject.Value; + if (transform.parent != null && !removeParent && !m_LatestParent.HasValue && isInScenePlaced) { - return true; + var parentNetworkObject = transform.parent.GetComponent(); + + // If parentNetworkObject is null then the parent is a GameObject without a NetworkObject component + // attached. Under this case, we preserve the hierarchy but we don't keep track of the parenting. + // Note: We only start tracking parenting if the user removes the child from the standard GameObject + // parent and then re-parents the child under a GameObject with a NetworkObject component attached. + if (parentNetworkObject == null) + { + return true; + } + else // If the parent still isn't spawned add this to the orphaned children and return false + if (!parentNetworkObject.IsSpawned) + { + OrphanChildren.Add(this); + return false; + } + else + { + // If we made it this far, go ahead and set the network parenting values + // with the default WorldPoisitonSays value + SetNetworkParenting(parentNetworkObject.NetworkObjectId, true); + + // Set the cached parent + m_CachedParent = parentNetworkObject.transform; + + return true; + } } - if (m_LatestParent == null || !m_LatestParent.HasValue) + // If we are removing the parent or our latest parent is not set, then remove the parent + // removeParent is only set when: + // - The server-side NetworkObject.OnTransformParentChanged is invoked and the parent is being removed + // - The client-side when handling a ParentSyncMessage + // When clients are synchronizing only the m_LatestParent.HasValue will not have a value if there is no parent + // or a parent was removed prior to the client connecting (i.e. in-scene placed NetworkObjects) + if (removeParent || !m_LatestParent.HasValue) { m_CachedParent = null; - transform.parent = null; - + // We must use Transform.SetParent when taking WorldPositionStays into + // consideration, otherwise just setting transform.parent = null defaults + // to WorldPositionStays which can cause scaling issues if the parent's + // scale is not the default (Vetctor3.one) value. + transform.SetParent(null, m_CachedWorldPositionStays); InvokeBehaviourOnNetworkObjectParentChanged(null); return true; } - if (!NetworkManager.SpawnManager.SpawnedObjects.ContainsKey(m_LatestParent.Value)) + // If we have a latest parent id but it hasn't been spawned yet, then add this instance to the orphanChildren + // HashSet and return false (i.e. parenting not applied yet) + if (m_LatestParent.HasValue && !NetworkManager.SpawnManager.SpawnedObjects.ContainsKey(m_LatestParent.Value)) { OrphanChildren.Add(this); return false; } + // If we made it here, then parent this instance under the parentObject var parentObject = NetworkManager.SpawnManager.SpawnedObjects[m_LatestParent.Value]; m_CachedParent = parentObject.transform; - transform.parent = parentObject.transform; + transform.SetParent(parentObject.transform, m_CachedWorldPositionStays); InvokeBehaviourOnNetworkObjectParentChanged(parentObject); return true; @@ -969,7 +1058,6 @@ public struct HeaderData : INetworkSerializeByMemcpy public bool HasParent; public bool IsSceneObject; public bool HasTransform; - public bool IsReparented; } public HeaderData Header; @@ -982,6 +1070,7 @@ public struct TransformData : INetworkSerializeByMemcpy { public Vector3 Position; public Quaternion Rotation; + public Vector3 Scale; } public TransformData Transform; @@ -997,12 +1086,19 @@ public struct TransformData : INetworkSerializeByMemcpy public int NetworkSceneHandle; + public bool WorldPositionStays; + public unsafe void Serialize(FastBufferWriter writer) { var writeSize = sizeof(HeaderData); - writeSize += Header.HasParent ? FastBufferWriter.GetWriteSize(ParentObjectId) : 0; - writeSize += Header.HasTransform ? FastBufferWriter.GetWriteSize(Transform) : 0; - writeSize += Header.IsReparented ? FastBufferWriter.GetWriteSize(IsLatestParentSet) + (IsLatestParentSet ? FastBufferWriter.GetWriteSize() : 0) : 0; + if (Header.HasParent) + { + writeSize += FastBufferWriter.GetWriteSize(ParentObjectId); + writeSize += FastBufferWriter.GetWriteSize(WorldPositionStays); + writeSize += FastBufferWriter.GetWriteSize(IsLatestParentSet); + writeSize += IsLatestParentSet ? FastBufferWriter.GetWriteSize() : 0; + } + writeSize += Header.HasTransform ? FastBufferWriter.GetWriteSize() : 0; writeSize += Header.IsSceneObject ? FastBufferWriter.GetWriteSize() : 0; if (!writer.TryBeginWrite(writeSize)) @@ -1015,6 +1111,12 @@ public unsafe void Serialize(FastBufferWriter writer) if (Header.HasParent) { writer.WriteValue(ParentObjectId); + writer.WriteValue(WorldPositionStays); + writer.WriteValue(IsLatestParentSet); + if (IsLatestParentSet) + { + writer.WriteValue(LatestParent.Value); + } } if (Header.HasTransform) @@ -1022,15 +1124,6 @@ public unsafe void Serialize(FastBufferWriter writer) writer.WriteValue(Transform); } - if (Header.IsReparented) - { - writer.WriteValue(IsLatestParentSet); - if (IsLatestParentSet) - { - writer.WriteValue((ulong)LatestParent); - } - } - // In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their // NetworkSceneHandle and GlobalObjectIdHash. Since each loaded scene has a unique // handle, it provides us with a unique and persistent "scene prefab asset" instance. @@ -1051,19 +1144,41 @@ public unsafe void Deserialize(FastBufferReader reader) throw new OverflowException("Could not deserialize SceneObject: Out of buffer space."); } reader.ReadValue(out Header); - var readSize = Header.HasParent ? FastBufferWriter.GetWriteSize(ParentObjectId) : 0; - readSize += Header.HasTransform ? FastBufferWriter.GetWriteSize(Transform) : 0; - readSize += Header.IsReparented ? FastBufferWriter.GetWriteSize(IsLatestParentSet) + (IsLatestParentSet ? FastBufferWriter.GetWriteSize() : 0) : 0; + var readSize = 0; + if (Header.HasParent) + { + readSize += FastBufferWriter.GetWriteSize(ParentObjectId); + readSize += FastBufferWriter.GetWriteSize(WorldPositionStays); + readSize += FastBufferWriter.GetWriteSize(IsLatestParentSet); + // We need to read at this point in order to get the IsLatestParentSet value + if (!reader.TryBeginRead(readSize)) + { + throw new OverflowException("Could not deserialize SceneObject: Out of buffer space."); + } + + // Read the initial parenting related properties + reader.ReadValue(out ParentObjectId); + reader.ReadValue(out WorldPositionStays); + reader.ReadValue(out IsLatestParentSet); + + // Now calculate the remaining bytes to read + readSize = 0; + readSize += IsLatestParentSet ? FastBufferWriter.GetWriteSize() : 0; + } + + readSize += Header.HasTransform ? FastBufferWriter.GetWriteSize() : 0; readSize += Header.IsSceneObject ? FastBufferWriter.GetWriteSize() : 0; + // Try to begin reading the remaining bytes if (!reader.TryBeginRead(readSize)) { throw new OverflowException("Could not deserialize SceneObject: Out of buffer space."); } - if (Header.HasParent) + if (IsLatestParentSet) { - reader.ReadValue(out ParentObjectId); + reader.ReadValueSafe(out ulong latestParent); + LatestParent = latestParent; } if (Header.HasTransform) @@ -1071,16 +1186,6 @@ public unsafe void Deserialize(FastBufferReader reader) reader.ReadValue(out Transform); } - if (Header.IsReparented) - { - reader.ReadValue(out IsLatestParentSet); - if (IsLatestParentSet) - { - reader.ReadValueSafe(out ulong latestParent); - LatestParent = latestParent; - } - } - // In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their // NetworkSceneHandle and GlobalObjectIdHash. Since each loaded scene has a unique // handle, it provides us with a unique and persistent "scene prefab asset" instance. @@ -1124,33 +1229,39 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId) parentNetworkObject = transform.parent.GetComponent(); } - if (parentNetworkObject) + if (parentNetworkObject != null) { obj.Header.HasParent = true; obj.ParentObjectId = parentNetworkObject.NetworkObjectId; + obj.WorldPositionStays = m_CachedWorldPositionStays; + var latestParent = GetNetworkParenting(); + var isLatestParentSet = latestParent != null && latestParent.HasValue; + obj.IsLatestParentSet = isLatestParentSet; + if (isLatestParentSet) + { + obj.LatestParent = latestParent.Value; + } } + if (IncludeTransformWhenSpawning == null || IncludeTransformWhenSpawning(OwnerClientId)) { obj.Header.HasTransform = true; obj.Transform = new SceneObject.TransformData { - Position = transform.position, - Rotation = transform.rotation + // If we are parented and we have the m_CachedWorldPositionStays disabled, then use local space + // values as opposed world space values. + Position = parentNetworkObject && !m_CachedWorldPositionStays ? transform.localPosition : transform.position, + Rotation = parentNetworkObject && !m_CachedWorldPositionStays ? transform.localRotation : transform.rotation, + + // We only use the lossyScale if the NetworkObject has a parent. Multi-generation nested children scales can + // impact the final scale of the child NetworkObject in question. The solution is to use the lossy scale + // which can be thought of as "world space scale". + // More information: + // https://docs.unity3d.com/ScriptReference/Transform-lossyScale.html + Scale = parentNetworkObject && !m_CachedWorldPositionStays ? transform.localScale : transform.lossyScale, }; } - var (isReparented, latestParent) = GetNetworkParenting(); - obj.Header.IsReparented = isReparented; - if (isReparented) - { - var isLatestParentSet = latestParent != null && latestParent.HasValue; - obj.IsLatestParentSet = isLatestParentSet; - if (isLatestParentSet) - { - obj.LatestParent = latestParent.Value; - } - } - return obj; } @@ -1164,33 +1275,8 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId) /// optional to use NetworkObject deserialized internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBufferReader variableData, NetworkManager networkManager) { - Vector3? position = null; - Quaternion? rotation = null; - ulong? parentNetworkId = null; - int? networkSceneHandle = null; - - if (sceneObject.Header.HasTransform) - { - position = sceneObject.Transform.Position; - rotation = sceneObject.Transform.Rotation; - } - - if (sceneObject.Header.HasParent) - { - parentNetworkId = sceneObject.ParentObjectId; - } - - if (sceneObject.Header.IsSceneObject) - { - networkSceneHandle = sceneObject.NetworkSceneHandle; - } - //Attempt to create a local NetworkObject - var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject( - sceneObject.Header.IsSceneObject, sceneObject.Header.Hash, - sceneObject.Header.OwnerClientId, parentNetworkId, networkSceneHandle, position, rotation, sceneObject.Header.IsReparented); - - networkObject?.SetNetworkParenting(sceneObject.Header.IsReparented, sceneObject.LatestParent); + var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject(sceneObject); if (networkObject == null) { @@ -1205,7 +1291,7 @@ internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBuf return null; } - // Spawn the NetworkObject( + // Spawn the NetworkObject networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, sceneObject, variableData, false); return networkObject; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs index 95d20ea2f7..0023b1a66c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs @@ -1,10 +1,12 @@ +using UnityEngine; + namespace Unity.Netcode { internal struct ParentSyncMessage : INetworkMessage { public ulong NetworkObjectId; - public bool IsReparented; + public bool WorldPositionStays; //If(Metadata.IsReparented) public bool IsLatestParentSet; @@ -12,18 +14,36 @@ internal struct ParentSyncMessage : INetworkMessage //If(IsLatestParentSet) public ulong? LatestParent; + // Is set when the parent should be removed (similar to IsReparented functionality but only for removing the parent) + public bool RemoveParent; + + // These additional properties are used to synchronize clients with the current position, + // rotation, and scale after parenting/de-parenting (world/local space relative). This + // allows users to control the final child's transform values without having to have a + // NetworkTransform component on the child. (i.e. picking something up) + public Vector3 Position; + public Quaternion Rotation; + public Vector3 Scale; + public void Serialize(FastBufferWriter writer) { - writer.WriteValueSafe(NetworkObjectId); - writer.WriteValueSafe(IsReparented); - if (IsReparented) + BytePacker.WriteValuePacked(writer, NetworkObjectId); + writer.WriteValueSafe(RemoveParent); + writer.WriteValueSafe(WorldPositionStays); + if (!RemoveParent) { writer.WriteValueSafe(IsLatestParentSet); + if (IsLatestParentSet) { - writer.WriteValueSafe((ulong)LatestParent); + BytePacker.WriteValueBitPacked(writer, (ulong)LatestParent); } } + + // Whether parenting or removing a parent, we always update the position, rotation, and scale + writer.WriteValueSafe(Position); + writer.WriteValueSafe(Rotation); + writer.WriteValueSafe(Scale); } public bool Deserialize(FastBufferReader reader, ref NetworkContext context) @@ -34,24 +54,30 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context) return false; } - reader.ReadValueSafe(out NetworkObjectId); - reader.ReadValueSafe(out IsReparented); - if (IsReparented) + ByteUnpacker.ReadValuePacked(reader, out NetworkObjectId); + reader.ReadValueSafe(out RemoveParent); + reader.ReadValueSafe(out WorldPositionStays); + if (!RemoveParent) { reader.ReadValueSafe(out IsLatestParentSet); + if (IsLatestParentSet) { - reader.ReadValueSafe(out ulong latestParent); + ByteUnpacker.ReadValueBitPacked(reader, out ulong latestParent); LatestParent = latestParent; } } + // Whether parenting or removing a parent, we always update the position, rotation, and scale + reader.ReadValueSafe(out Position); + reader.ReadValueSafe(out Rotation); + reader.ReadValueSafe(out Scale); + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId)) { networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context); return false; } - return true; } @@ -59,8 +85,22 @@ public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId]; - networkObject.SetNetworkParenting(IsReparented, LatestParent); - networkObject.ApplyNetworkParenting(); + networkObject.SetNetworkParenting(LatestParent, WorldPositionStays); + networkObject.ApplyNetworkParenting(RemoveParent); + + // We set all of the transform values after parenting as they are + // the values of the server-side post-parenting transform values + if (!WorldPositionStays) + { + networkObject.transform.localPosition = Position; + networkObject.transform.localRotation = Rotation; + } + else + { + networkObject.transform.position = Position; + networkObject.transform.rotation = Rotation; + } + networkObject.transform.localScale = Scale; } } } diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs index 69640d15cf..ae8d5a31a9 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs @@ -243,7 +243,26 @@ internal void AddSpawnedNetworkObjects() m_NetworkObjectsSync.Add(sobj); } } + + // Sort by parents before children + m_NetworkObjectsSync.Sort(SortParentedNetworkObjects); + + // Sort by INetworkPrefabInstanceHandler implementation before the + // NetworkObjects spawned by the implementation m_NetworkObjectsSync.Sort(SortNetworkObjects); + + // This is useful to know what NetworkObjects a client is going to be synchronized with + // as well as the order in which they will be deserialized + if (m_NetworkManager.LogLevel == LogLevel.Developer) + { + var messageBuilder = new System.Text.StringBuilder(0xFFFF); + messageBuilder.Append("[Server-Side Client-Synchronization] NetworkObject serialization order:"); + foreach (var networkObject in m_NetworkObjectsSync) + { + messageBuilder.Append($"{networkObject.name}"); + } + NetworkLog.LogInfo(messageBuilder.ToString()); + } } internal void AddDespawnedInSceneNetworkObjects() @@ -323,6 +342,32 @@ private int SortNetworkObjects(NetworkObject first, NetworkObject second) return 0; } + /// + /// Sorts the synchronization order of the NetworkObjects to be serialized + /// by parents before children. + /// + /// + /// This only handles late joining players. Spawning and nesting several children + /// dynamically is still handled by the orphaned child list when deserialized out of + /// hierarchical order (i.e. Spawn parent and child dynamically, parent message is + /// dropped and re-sent but child object is received and processed) + /// + private int SortParentedNetworkObjects(NetworkObject first, NetworkObject second) + { + // If the first has a parent, move the first down + if (first.transform.parent != null) + { + return 1; + } + else // If the second has a parent and the first does not, then move the first up + if (second.transform.parent != null) + { + return -1; + } + return 0; + } + + /// /// Client and Server Side: /// Serializes data based on the SceneEvent type () diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 91c5a52147..175e21a451 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -314,48 +314,33 @@ internal bool HasPrefab(NetworkObject.SceneObject sceneObject) } /// - /// Should only run on the client + /// Creates a local NetowrkObject to be spawned. /// - internal NetworkObject CreateLocalNetworkObject(bool isSceneObject, uint globalObjectIdHash, ulong ownerClientId, ulong? parentNetworkId, int? networkSceneHandle, Vector3? position, Quaternion? rotation, bool isReparented = false) + /// + /// For most cases this is client-side only, with the exception of when the server + /// is spawning a player. + /// + internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneObject) { - NetworkObject parentNetworkObject = null; - - if (parentNetworkId != null && !isReparented) - { - if (SpawnedObjects.TryGetValue(parentNetworkId.Value, out NetworkObject networkObject)) - { - parentNetworkObject = networkObject; - } - else - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning("Cannot find parent. Parent objects always have to be spawned and replicated BEFORE the child"); - } - } - } - - if (!NetworkManager.NetworkConfig.EnableSceneManagement || !isSceneObject) + NetworkObject networkObject = null; + var globalObjectIdHash = sceneObject.Header.Hash; + var position = sceneObject.Header.HasTransform ? sceneObject.Transform.Position : default; + var rotation = sceneObject.Header.HasTransform ? sceneObject.Transform.Rotation : default; + var scale = sceneObject.Header.HasTransform ? sceneObject.Transform.Scale : default; + var parentNetworkId = sceneObject.Header.HasParent ? sceneObject.ParentObjectId : default; + var worldPositionStays = sceneObject.Header.HasParent ? sceneObject.WorldPositionStays : true; + var isSpawnedByPrefabHandler = false; + + // If scene management is disabled or the NetworkObject was dynamically spawned + if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.Header.IsSceneObject) { // If the prefab hash has a registered INetworkPrefabInstanceHandler derived class if (NetworkManager.PrefabHandler.ContainsHandler(globalObjectIdHash)) { // Let the handler spawn the NetworkObject - var networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, ownerClientId, position.GetValueOrDefault(Vector3.zero), rotation.GetValueOrDefault(Quaternion.identity)); - + networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, sceneObject.Header.OwnerClientId, position, rotation); networkObject.NetworkManagerOwner = NetworkManager; - - if (parentNetworkObject != null) - { - networkObject.transform.SetParent(parentNetworkObject.transform, true); - } - - if (NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad) - { - UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject); - } - - return networkObject; + isSpawnedByPrefabHandler = true; } else { @@ -383,31 +368,18 @@ internal NetworkObject CreateLocalNetworkObject(bool isSceneObject, uint globalO { NetworkLog.LogError($"Failed to create object locally. [{nameof(globalObjectIdHash)}={globalObjectIdHash}]. {nameof(NetworkPrefab)} could not be found. Is the prefab registered with {nameof(NetworkManager)}?"); } - - return null; - } - - // Otherwise, instantiate an instance of the NetworkPrefab linked to the prefabHash - var networkObject = ((position == null && rotation == null) ? UnityEngine.Object.Instantiate(networkPrefabReference) : UnityEngine.Object.Instantiate(networkPrefabReference, position.GetValueOrDefault(Vector3.zero), rotation.GetValueOrDefault(Quaternion.identity))).GetComponent(); - - networkObject.NetworkManagerOwner = NetworkManager; - - if (parentNetworkObject != null) - { - networkObject.transform.SetParent(parentNetworkObject.transform, true); } - - if (NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad) + else { - UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject); + // Create prefab instance + networkObject = UnityEngine.Object.Instantiate(networkPrefabReference).GetComponent(); + networkObject.NetworkManagerOwner = NetworkManager; } - - return networkObject; } } - else + else // Get the in-scene placed NetworkObject { - var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash, networkSceneHandle); + networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash, sceneObject.NetworkSceneHandle); if (networkObject == null) { @@ -415,17 +387,71 @@ internal NetworkObject CreateLocalNetworkObject(bool isSceneObject, uint globalO { NetworkLog.LogError($"{nameof(NetworkPrefab)} hash was not found! In-Scene placed {nameof(NetworkObject)} soft synchronization failure for Hash: {globalObjectIdHash}!"); } + } + } - return null; + if (networkObject != null) + { + // SPECIAL CASE: + // This is a special case scenario where a late joining client has joined and loaded one or + // more scenes that contain nested in-scene placed NetworkObject children yet the server's + // synchronization information does not indicate the NetworkObject in question has a parent. + // Under this scenario, we want to remove the parent before spawning and setting the transform values. + if (sceneObject.Header.IsSceneObject && !sceneObject.Header.HasParent && networkObject.transform.parent != null) + { + // if the in-scene placed NetworkObject has a parent NetworkObject but the synchronization information does not + // include parenting, then we need to force the removal of that parent + if (networkObject.transform.parent.GetComponent() != null) + { + // remove the parent + networkObject.ApplyNetworkParenting(true, true); + } } - if (parentNetworkObject != null) + // Set the transform unless we were spawned by a prefab handler + // Note: prefab handlers are provided the position and rotation + // but it is up to the user to set those values + if (sceneObject.Header.HasTransform && !isSpawnedByPrefabHandler) { - networkObject.transform.SetParent(parentNetworkObject.transform, true); + if (worldPositionStays) + { + networkObject.transform.position = position; + networkObject.transform.rotation = rotation; + } + else + { + networkObject.transform.localPosition = position; + networkObject.transform.localRotation = rotation; + } + + // SPECIAL CASE: + // Since players are created uniquely we don't apply scale because + // the ConnectionApprovalResponse does not currently provide the + // ability to specify scale. So, we just use the default scale of + // the network prefab used to represent the player. + // Note: not doing this would set the player's scale to zero since + // that is the default value of Vector3. + if (!sceneObject.Header.IsPlayerObject) + { + networkObject.transform.localScale = scale; + } } - return networkObject; + if (sceneObject.Header.HasParent) + { + // Go ahead and set network parenting properties + networkObject.SetNetworkParenting(parentNetworkId, worldPositionStays); + } + + + // Dynamically spawned NetworkObjects that occur during a LoadSceneMode.Single load scene event are migrated into the DDOL + // until the scene is loaded. They are then migrated back into the newly loaded and currently active scene. + if (!sceneObject.Header.IsSceneObject && NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad) + { + UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject); + } } + return networkObject; } // Ran on both server and client @@ -545,7 +571,6 @@ private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong } } - networkObject.SetCachedParent(networkObject.transform.parent); networkObject.ApplyNetworkParenting(); NetworkObject.CheckOrphanChildren(); @@ -748,8 +773,8 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec // Move child NetworkObjects to the root when parent NetworkObject is destroyed foreach (var spawnedNetObj in SpawnedObjectsList) { - var (isReparented, latestParent) = spawnedNetObj.GetNetworkParenting(); - if (isReparented && latestParent == networkObject.NetworkObjectId) + var latestParent = spawnedNetObj.GetNetworkParenting(); + if (latestParent.HasValue && latestParent.Value == networkObject.NetworkObjectId) { spawnedNetObj.gameObject.transform.parent = null; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs index 572b04a1f8..2c56188eb7 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs @@ -4,15 +4,87 @@ namespace Unity.Netcode.RuntimeTests { + + [TestFixture(TransformSpace.World)] + [TestFixture(TransformSpace.Local)] public class NetworkTransformStateTests { + public enum SyncAxis + { + SyncPosX, + SyncPosY, + SyncPosZ, + SyncPosXY, + SyncPosXZ, + SyncPosYZ, + SyncPosXYZ, + SyncRotX, + SyncRotY, + SyncRotZ, + SyncRotXY, + SyncRotXZ, + SyncRotYZ, + SyncRotXYZ, + SyncScaleX, + SyncScaleY, + SyncScaleZ, + SyncScaleXY, + SyncScaleXZ, + SyncScaleYZ, + SyncScaleXYZ, + SyncAllX, + SyncAllY, + SyncAllZ, + SyncAllXY, + SyncAllXZ, + SyncAllYZ, + SyncAllXYZ + } + + public enum TransformSpace + { + World, + Local + } + + public enum SynchronizationType + { + Delta, + Teleport + } + + private TransformSpace m_TransformSpace; + + public NetworkTransformStateTests(TransformSpace transformSpace) + { + m_TransformSpace = transformSpace; + } + + private bool WillAnAxisBeSynchronized(ref NetworkTransform networkTransform) + { + return networkTransform.SyncScaleX || networkTransform.SyncScaleY || networkTransform.SyncScaleZ || + networkTransform.SyncRotAngleX || networkTransform.SyncRotAngleY || networkTransform.SyncRotAngleZ || + networkTransform.SyncPositionX || networkTransform.SyncPositionY || networkTransform.SyncPositionZ; + } + [Test] - public void TestSyncAxes( - [Values] bool inLocalSpace, - [Values] bool syncPosX, [Values] bool syncPosY, [Values] bool syncPosZ, - [Values] bool syncRotX, [Values] bool syncRotY, [Values] bool syncRotZ, - [Values] bool syncScaX, [Values] bool syncScaY, [Values] bool syncScaZ) + public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Values] SyncAxis syncAxis) + { + bool inLocalSpace = m_TransformSpace == TransformSpace.Local; + bool isTeleporting = synchronizationType == SynchronizationType.Teleport; + bool syncPosX = syncAxis == SyncAxis.SyncPosX || syncAxis == SyncAxis.SyncPosXY || syncAxis == SyncAxis.SyncPosXZ || syncAxis == SyncAxis.SyncPosXYZ || syncAxis == SyncAxis.SyncAllX || syncAxis == SyncAxis.SyncAllXY || syncAxis == SyncAxis.SyncAllXZ || syncAxis == SyncAxis.SyncAllXYZ; + bool syncPosY = syncAxis == SyncAxis.SyncPosY || syncAxis == SyncAxis.SyncPosXY || syncAxis == SyncAxis.SyncPosYZ || syncAxis == SyncAxis.SyncPosXYZ || syncAxis == SyncAxis.SyncAllY || syncAxis == SyncAxis.SyncAllXY || syncAxis == SyncAxis.SyncAllYZ || syncAxis == SyncAxis.SyncAllXYZ; + bool syncPosZ = syncAxis == SyncAxis.SyncPosZ || syncAxis == SyncAxis.SyncPosXZ || syncAxis == SyncAxis.SyncPosYZ || syncAxis == SyncAxis.SyncPosXYZ || syncAxis == SyncAxis.SyncAllZ || syncAxis == SyncAxis.SyncAllXZ || syncAxis == SyncAxis.SyncAllYZ || syncAxis == SyncAxis.SyncAllXYZ; + + bool syncRotX = syncAxis == SyncAxis.SyncRotX || syncAxis == SyncAxis.SyncRotXY || syncAxis == SyncAxis.SyncRotXZ || syncAxis == SyncAxis.SyncRotXYZ || syncAxis == SyncAxis.SyncRotX || syncAxis == SyncAxis.SyncAllXY || syncAxis == SyncAxis.SyncAllXZ || syncAxis == SyncAxis.SyncAllXYZ; + bool syncRotY = syncAxis == SyncAxis.SyncRotY || syncAxis == SyncAxis.SyncRotXY || syncAxis == SyncAxis.SyncRotYZ || syncAxis == SyncAxis.SyncRotXYZ || syncAxis == SyncAxis.SyncRotY || syncAxis == SyncAxis.SyncAllXY || syncAxis == SyncAxis.SyncAllYZ || syncAxis == SyncAxis.SyncAllXYZ; + bool syncRotZ = syncAxis == SyncAxis.SyncRotZ || syncAxis == SyncAxis.SyncRotXZ || syncAxis == SyncAxis.SyncRotYZ || syncAxis == SyncAxis.SyncRotXYZ || syncAxis == SyncAxis.SyncRotZ || syncAxis == SyncAxis.SyncAllXZ || syncAxis == SyncAxis.SyncAllYZ || syncAxis == SyncAxis.SyncAllXYZ; + + bool syncScaX = syncAxis == SyncAxis.SyncScaleX || syncAxis == SyncAxis.SyncScaleXY || syncAxis == SyncAxis.SyncScaleXZ || syncAxis == SyncAxis.SyncScaleXYZ || syncAxis == SyncAxis.SyncAllX || syncAxis == SyncAxis.SyncAllXY || syncAxis == SyncAxis.SyncAllXZ || syncAxis == SyncAxis.SyncAllXYZ; + bool syncScaY = syncAxis == SyncAxis.SyncScaleY || syncAxis == SyncAxis.SyncScaleXY || syncAxis == SyncAxis.SyncScaleYZ || syncAxis == SyncAxis.SyncScaleXYZ || syncAxis == SyncAxis.SyncAllY || syncAxis == SyncAxis.SyncAllXY || syncAxis == SyncAxis.SyncAllYZ || syncAxis == SyncAxis.SyncAllXYZ; + bool syncScaZ = syncAxis == SyncAxis.SyncScaleZ || syncAxis == SyncAxis.SyncScaleXZ || syncAxis == SyncAxis.SyncScaleYZ || syncAxis == SyncAxis.SyncScaleXYZ || syncAxis == SyncAxis.SyncAllZ || syncAxis == SyncAxis.SyncAllXZ || syncAxis == SyncAxis.SyncAllYZ || syncAxis == SyncAxis.SyncAllXYZ; + var gameObject = new GameObject($"Test-{nameof(NetworkTransformStateTests)}.{nameof(TestSyncAxes)}"); var networkObject = gameObject.AddComponent(); var networkTransform = gameObject.AddComponent(); @@ -36,27 +108,13 @@ public void TestSyncAxes( networkTransform.SyncScaleZ = syncScaZ; networkTransform.InLocalSpace = inLocalSpace; + // We want a relatively clean networkTransform state before we try to apply the transform to it + // We only preserve InLocalSpace and IsTeleportingNextFrame properties as they are the only things + // needed when applying a transform to a NetworkTransformState var networkTransformState = new NetworkTransform.NetworkTransformState { - PositionX = initialPosition.x, - PositionY = initialPosition.y, - PositionZ = initialPosition.z, - RotAngleX = initialRotAngles.x, - RotAngleY = initialRotAngles.y, - RotAngleZ = initialRotAngles.z, - ScaleX = initialScale.x, - ScaleY = initialScale.y, - ScaleZ = initialScale.z, - HasPositionX = syncPosX, - HasPositionY = syncPosY, - HasPositionZ = syncPosZ, - HasRotAngleX = syncRotX, - HasRotAngleY = syncRotY, - HasRotAngleZ = syncRotZ, - HasScaleX = syncScaX, - HasScaleY = syncScaY, - HasScaleZ = syncScaZ, - InLocalSpace = inLocalSpace + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, }; // Step 1: change properties, expect state to be dirty @@ -71,95 +129,382 @@ public void TestSyncAxes( } } - // Step 2: disable a particular sync flag, expect state to be not dirty + // We want to start with a fresh NetworkTransformState since it could have other state + // information from the last time we applied the transform + networkTransformState = new NetworkTransform.NetworkTransformState + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }; + var position = networkTransform.transform.position; + var rotAngles = networkTransform.transform.eulerAngles; + var scale = networkTransform.transform.localScale; + + // Step 2: Verify the state changes in a tick are additive + // TODO: This will need to change if we update NetworkTransform to send all of the + // axis deltas that happened over a tick as a collection instead of collapsing them + // as the changes are detected. + { + networkTransformState = new NetworkTransform.NetworkTransformState + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }; + + // SyncPositionX + if (syncPosX) + { + position.x++; + networkTransform.transform.position = position; + Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + Assert.IsTrue(networkTransformState.HasPositionX); + } + + // SyncPositionY + if (syncPosY) + { + position = networkTransform.transform.position; + position.y++; + networkTransform.transform.position = position; + Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + Assert.IsTrue(networkTransformState.HasPositionX || !syncPosX); + Assert.IsTrue(networkTransformState.HasPositionY); + } + + // SyncPositionZ + if (syncPosZ) + { + position = networkTransform.transform.position; + position.z++; + networkTransform.transform.position = position; + Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + Assert.IsTrue(networkTransformState.HasPositionX || !syncPosX); + Assert.IsTrue(networkTransformState.HasPositionY || !syncPosY); + Assert.IsTrue(networkTransformState.HasPositionZ); + } + + // SyncRotAngleX + if (syncRotX) + { + rotAngles = networkTransform.transform.eulerAngles; + rotAngles.x++; + networkTransform.transform.eulerAngles = rotAngles; + Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + Assert.IsTrue(networkTransformState.HasPositionX || !syncPosX); + Assert.IsTrue(networkTransformState.HasPositionY || !syncPosY); + Assert.IsTrue(networkTransformState.HasPositionZ || !syncPosZ); + Assert.IsTrue(networkTransformState.HasRotAngleX); + } + + // SyncRotAngleY + if (syncRotY) + { + rotAngles = networkTransform.transform.eulerAngles; + rotAngles.y++; + networkTransform.transform.eulerAngles = rotAngles; + Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + Assert.IsTrue(networkTransformState.HasPositionX || !syncPosX); + Assert.IsTrue(networkTransformState.HasPositionY || !syncPosY); + Assert.IsTrue(networkTransformState.HasPositionZ || !syncPosZ); + Assert.IsTrue(networkTransformState.HasRotAngleX || !syncRotX); + Assert.IsTrue(networkTransformState.HasRotAngleY); + } + // SyncRotAngleZ + if (syncRotZ) + { + rotAngles = networkTransform.transform.eulerAngles; + rotAngles.z++; + networkTransform.transform.eulerAngles = rotAngles; + Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + Assert.IsTrue(networkTransformState.HasPositionX || !syncPosX); + Assert.IsTrue(networkTransformState.HasPositionY || !syncPosY); + Assert.IsTrue(networkTransformState.HasPositionZ || !syncPosZ); + Assert.IsTrue(networkTransformState.HasRotAngleX || !syncRotX); + Assert.IsTrue(networkTransformState.HasRotAngleY || !syncRotY); + Assert.IsTrue(networkTransformState.HasRotAngleZ); + } + + // SyncScaleX + if (syncScaX) + { + scale = networkTransform.transform.localScale; + scale.x++; + networkTransform.transform.localScale = scale; + Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + Assert.IsTrue(networkTransformState.HasPositionX || !syncPosX); + Assert.IsTrue(networkTransformState.HasPositionY || !syncPosY); + Assert.IsTrue(networkTransformState.HasPositionZ || !syncPosZ); + Assert.IsTrue(networkTransformState.HasRotAngleX || !syncRotX); + Assert.IsTrue(networkTransformState.HasRotAngleY || !syncRotY); + Assert.IsTrue(networkTransformState.HasRotAngleZ || !syncRotZ); + Assert.IsTrue(networkTransformState.HasScaleX); + } + // SyncScaleY + if (syncScaY) + { + scale = networkTransform.transform.localScale; + scale.y++; + networkTransform.transform.localScale = scale; + Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + Assert.IsTrue(networkTransformState.HasPositionX || !syncPosX); + Assert.IsTrue(networkTransformState.HasPositionY || !syncPosY); + Assert.IsTrue(networkTransformState.HasPositionZ || !syncPosZ); + Assert.IsTrue(networkTransformState.HasRotAngleX || !syncRotX); + Assert.IsTrue(networkTransformState.HasRotAngleY || !syncRotY); + Assert.IsTrue(networkTransformState.HasRotAngleZ || !syncRotZ); + Assert.IsTrue(networkTransformState.HasScaleX || !syncScaX); + Assert.IsTrue(networkTransformState.HasScaleY); + } + // SyncScaleZ + if (syncScaZ) + { + scale = networkTransform.transform.localScale; + scale.z++; + networkTransform.transform.localScale = scale; + Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + Assert.IsTrue(networkTransformState.HasPositionX || !syncPosX); + Assert.IsTrue(networkTransformState.HasPositionY || !syncPosY); + Assert.IsTrue(networkTransformState.HasPositionZ || !syncPosZ); + Assert.IsTrue(networkTransformState.HasRotAngleX || !syncRotX); + Assert.IsTrue(networkTransformState.HasRotAngleY || !syncRotY); + Assert.IsTrue(networkTransformState.HasRotAngleZ || !syncRotZ); + Assert.IsTrue(networkTransformState.HasScaleX || !syncScaX); + Assert.IsTrue(networkTransformState.HasScaleY || !syncScaY); + Assert.IsTrue(networkTransformState.HasScaleZ); + } + } + + // Step 3: disable a particular sync flag, expect state to be not dirty + // We do this last because it changes which axis will be synchronized. { - var position = networkTransform.transform.position; - var rotAngles = networkTransform.transform.eulerAngles; - var scale = networkTransform.transform.localScale; + networkTransformState = new NetworkTransform.NetworkTransformState + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }; + + position = networkTransform.transform.position; + rotAngles = networkTransform.transform.eulerAngles; + scale = networkTransform.transform.localScale; // SyncPositionX + if (syncPosX) { networkTransform.SyncPositionX = false; position.x++; networkTransform.transform.position = position; - Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + // If we are synchronizing more than 1 axis (teleporting impacts this too) + if (syncAxis != SyncAxis.SyncPosX && WillAnAxisBeSynchronized(ref networkTransform)) + { + // For the x axis position value We should expect the state to still be considered dirty (more than one axis is being synchronized and we are teleporting) + Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + // However, we expect it to not have applied the position x delta + Assert.IsFalse(networkTransformState.HasPositionX); + } + else + { + Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + } } + // SyncPositionY + if (syncPosY) { networkTransform.SyncPositionY = false; position.y++; networkTransform.transform.position = position; - - Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + if (syncAxis != SyncAxis.SyncPosY && WillAnAxisBeSynchronized(ref networkTransform)) + { + // We want to start with a fresh NetworkTransformState since it could have other state + // information from the last time we applied the transform + networkTransformState = new NetworkTransform.NetworkTransformState + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }; + Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + Assert.IsFalse(networkTransformState.HasPositionY); + } + else + { + Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + } } + // SyncPositionZ + if (syncPosZ) { networkTransform.SyncPositionZ = false; position.z++; networkTransform.transform.position = position; - - Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + if (syncAxis != SyncAxis.SyncPosZ && WillAnAxisBeSynchronized(ref networkTransform)) + { + // We want to start with a fresh NetworkTransformState since it could have other state + // information from the last time we applied the transform + networkTransformState = new NetworkTransform.NetworkTransformState + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }; + Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + Assert.IsFalse(networkTransformState.HasPositionZ); + } + else + { + Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + } } // SyncRotAngleX + if (syncRotX) { networkTransform.SyncRotAngleX = false; rotAngles.x++; networkTransform.transform.eulerAngles = rotAngles; - - Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + if (syncAxis != SyncAxis.SyncRotX && WillAnAxisBeSynchronized(ref networkTransform)) + { + // We want to start with a fresh NetworkTransformState since it could have other state + // information from the last time we applied the transform + networkTransformState = new NetworkTransform.NetworkTransformState + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }; + Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + Assert.IsFalse(networkTransformState.HasRotAngleX); + } + else + { + Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + } } // SyncRotAngleY + if (syncRotY) { networkTransform.SyncRotAngleY = false; rotAngles.y++; networkTransform.transform.eulerAngles = rotAngles; - - Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + if (syncAxis != SyncAxis.SyncRotY && WillAnAxisBeSynchronized(ref networkTransform)) + { + // We want to start with a fresh NetworkTransformState since it could have other state + // information from the last time we applied the transform + networkTransformState = new NetworkTransform.NetworkTransformState + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }; + Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + Assert.IsFalse(networkTransformState.HasRotAngleY); + } + else + { + Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + } } // SyncRotAngleZ + if (syncRotZ) { networkTransform.SyncRotAngleZ = false; rotAngles.z++; networkTransform.transform.eulerAngles = rotAngles; - - Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + if (syncAxis != SyncAxis.SyncRotZ && WillAnAxisBeSynchronized(ref networkTransform)) + { + // We want to start with a fresh NetworkTransformState since it could have other state + // information from the last time we applied the transform + networkTransformState = new NetworkTransform.NetworkTransformState + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }; + Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + Assert.IsFalse(networkTransformState.HasRotAngleZ); + } + else + { + Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + } } // SyncScaleX + if (syncScaX) { networkTransform.SyncScaleX = false; scale.x++; networkTransform.transform.localScale = scale; - - Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + if (syncAxis != SyncAxis.SyncScaleX && WillAnAxisBeSynchronized(ref networkTransform)) + { + // We want to start with a fresh NetworkTransformState since it could have other state + // information from the last time we applied the transform + networkTransformState = new NetworkTransform.NetworkTransformState + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }; + Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + Assert.IsFalse(networkTransformState.HasScaleX); + } + else + { + Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + } } // SyncScaleY + if (syncScaY) { networkTransform.SyncScaleY = false; scale.y++; networkTransform.transform.localScale = scale; - - Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + if (syncAxis != SyncAxis.SyncScaleY && WillAnAxisBeSynchronized(ref networkTransform)) + { + // We want to start with a fresh NetworkTransformState since it could have other state + // information from the last time we applied the transform + networkTransformState = new NetworkTransform.NetworkTransformState + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }; + Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + Assert.IsFalse(networkTransformState.HasScaleY); + } + else + { + Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + } } // SyncScaleZ + if (syncScaZ) { networkTransform.SyncScaleZ = false; scale.z++; networkTransform.transform.localScale = scale; - - Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + if (syncAxis != SyncAxis.SyncScaleZ && WillAnAxisBeSynchronized(ref networkTransform)) + { + // We want to start with a fresh NetworkTransformState since it could have other state + // information from the last time we applied the transform + networkTransformState = new NetworkTransform.NetworkTransformState + { + InLocalSpace = inLocalSpace, + IsTeleportingNextFrame = isTeleporting, + }; + Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + Assert.IsFalse(networkTransformState.HasScaleZ); + } + else + { + Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); + } } + } Object.DestroyImmediate(gameObject); @@ -168,11 +513,11 @@ public void TestSyncAxes( [Test] public void TestThresholds( - [Values] bool inLocalSpace, [Values(NetworkTransform.PositionThresholdDefault, 1.0f)] float positionThreshold, [Values(NetworkTransform.RotAngleThresholdDefault, 1.0f)] float rotAngleThreshold, [Values(NetworkTransform.ScaleThresholdDefault, 0.5f)] float scaleThreshold) { + var inLocalSpace = m_TransformSpace == TransformSpace.Local; var gameObject = new GameObject($"Test-{nameof(NetworkTransformStateTests)}.{nameof(TestThresholds)}"); var networkTransform = gameObject.AddComponent(); networkTransform.enabled = false; // do not tick `FixedUpdate()` or `Update()` diff --git a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs new file mode 100644 index 0000000000..80e9ac63e1 --- /dev/null +++ b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs @@ -0,0 +1,249 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using Unity.Netcode; +using Unity.Netcode.Components; +using TestProject.ManualTests; +using Random = UnityEngine.Random; + +namespace TestProject.RuntimeTests +{ + public class InSceneParentChildHandler : NetworkBehaviour + { + public static InSceneParentChildHandler ServerRootParent; + public static bool EnableVerboseDebug = true; + public static bool AddNetworkTransform; + public static bool WorldPositionStays; + public static InSceneParentChildHandler RootParent { get; private set; } + private static bool s_GenerateRandomValues; + + public bool ParentHasNoNetworkObject; + public bool IsRootParent; + public bool IsLastChild; + public bool PreserveLocalSpace; + + public Vector3 PositionMax = new Vector3(5.0f, 4.0f, 5.0f); + public Vector3 PositionMin = new Vector3(-5.00f, 0.5f, -5.0f); + public Vector3 RotationMax = new Vector3(359.99f, 359.99f, 359.99f); + public Vector3 RotationMin = new Vector3(0.01f, 0.01f, 0.01f); + public float ScaleMax = 3.0f; + public float ScaleMin = 0.75f; + + + private InSceneParentChildHandler m_Parent; + private InSceneParentChildHandler m_Child; + private Vector3 m_TargetLocalPosition; + private Vector3 m_TargetLocalRotation; + private Vector3 m_TargetLocalScale; + + private NetworkTransform m_NetworkTransform; + + public static Dictionary ServerRelativeInstances = new Dictionary(); + public static Dictionary> ClientRelativeInstances = new Dictionary>(); + + public static void ResetInstancesTracking(bool enableVerboseDebug) + { + EnableVerboseDebug = enableVerboseDebug; + ServerRelativeInstances.Clear(); + ClientRelativeInstances.Clear(); + } + + private Vector3 GenerateVector3(Vector3 min, Vector3 max) + { + var result = Vector3.zero; + result.x = Random.Range(min.x, max.y); + result.y = Random.Range(min.x, max.y); + result.z = Random.Range(min.x, max.y); + return result; + } + + private void LogMessage(string message) + { + if (EnableVerboseDebug) + { + Debug.Log(message); + } + } + + private void Start() + { + if (IsRootParent) + { + Random.InitState((int)Random.Range(Time.deltaTime, Time.realtimeSinceStartup)); + RootParent = this; + } + } + + private int CountNestedChildren(Transform currentParent, int count = 0) + { + if (currentParent.childCount > 0) + { + var child = currentParent.GetChild(0); + count++; + CountNestedChildren(child, count); + } + return count; + } + + private Transform GetLastChild(Transform currentParent) + { + if (currentParent.childCount > 0) + { + var child = currentParent.GetChild(0); + var childHandler = child.GetComponent(); + var parentHandler = currentParent.GetComponent(); + parentHandler.m_Child = childHandler; + childHandler.m_Parent = parentHandler; + return GetLastChild(child); + } + return currentParent; + } + + private void RemoveParent(Transform child, bool worldPositionStays = true) + { + var childNetworkObject = child.GetComponent(); + var parentOfChild = child.parent; + if (parentOfChild != null) + { + if (!childNetworkObject.TryRemoveParent(worldPositionStays)) + { + throw new Exception($"[RemoveParent] {child.name} Failed to remove itself from parent {parentOfChild.name}!"); + } + else + { + RemoveParent(parentOfChild, worldPositionStays); + } + } + } + + public void DeparentAllChildren(bool worldPositionStays = true) + { + if (IsRootParent && IsServer) + { + var lastChild = GetLastChild(transform); + if (lastChild != null) + { + RemoveParent(lastChild, worldPositionStays); + } + } + } + + private void ParentChild(InSceneParentChildHandler child, bool worldPositionStays = true) + { + if (child != null) + { + if (child.m_Parent != null) + { + var childNetworkObject = child.GetComponent(); + if (!childNetworkObject.TrySetParent(child.m_Parent.transform, worldPositionStays)) + { + throw new Exception($"[Parent] {child.name} Failed to parent itself under parent {child.m_Parent.name}!"); + } + else + { + ParentChild(child.m_Child, worldPositionStays); + } + } + } + } + + public void ReParentAllChildren(bool worldPositionStays = true) + { + if (IsRootParent && IsServer) + { + ParentChild(m_Child, worldPositionStays); + } + } + + public override void OnNetworkSpawn() + { + if (IsServer) + { + LogMessage($"[{NetworkObjectId}] Pos = ({m_TargetLocalPosition}) | Rotation ({m_TargetLocalRotation}) | Scale ({m_TargetLocalScale})"); + if (AddNetworkTransform) + { + m_NetworkTransform = gameObject.AddComponent(); + m_NetworkTransform.InLocalSpace = PreserveLocalSpace; + } + if (IsRootParent) + { + ServerRootParent = this; + } + } + else + { + if (!ClientRelativeInstances.ContainsKey(NetworkManager.LocalClientId)) + { + ClientRelativeInstances.Add(NetworkManager.LocalClientId, new Dictionary()); + } + if (!ClientRelativeInstances[NetworkManager.LocalClientId].ContainsKey(NetworkObjectId)) + { + ClientRelativeInstances[NetworkManager.LocalClientId].Add(NetworkObjectId, this); + } + } + + base.OnNetworkSpawn(); + } + + public void DeparentSetValuesAndReparent() + { + if (IsServer && IsRootParent) + { + // Back to back de-parenting and re-parenting + s_GenerateRandomValues = true; + DeparentAllChildren(WorldPositionStays); + s_GenerateRandomValues = false; + ReParentAllChildren(WorldPositionStays); + } + } + + /// + /// This handles applying the final desired transform values before the ParentSyncMessage + /// is created and sent. + /// + public override void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) + { + if (!IsServer || !IsSpawned || parentNetworkObject != null || !s_GenerateRandomValues) + { + return; + } + + m_TargetLocalPosition = GenerateVector3(PositionMin, PositionMax); + m_TargetLocalRotation = GenerateVector3(RotationMin, RotationMax); + var scale = Random.Range(ScaleMin, ScaleMax); + m_TargetLocalScale = Vector3.one * scale; + transform.position = m_TargetLocalPosition; + transform.rotation = Quaternion.Euler(m_TargetLocalRotation); + transform.localScale = m_TargetLocalScale; + + base.OnNetworkObjectParentChanged(parentNetworkObject); + } + + + private void LateUpdate() + { + if (!IsSpawned || !IsServer || NetworkManagerTestDisabler.IsIntegrationTest) + { + return; + } + + // De-parent + if (Input.GetKeyDown(KeyCode.D) && IsRootParent) + { + DeparentAllChildren(WorldPositionStays); + } + + // Re-parent + if (Input.GetKeyDown(KeyCode.R) && IsRootParent) + { + ReParentAllChildren(WorldPositionStays); + } + + // De-parent, initialize with new values, and re-parent + if (Input.GetKeyDown(KeyCode.Tab)) + { + RootParent.DeparentSetValuesAndReparent(); + } + } + } +} diff --git a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs.meta b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs.meta new file mode 100644 index 0000000000..94a4b95f5a --- /dev/null +++ b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4f542d9d67c235941831b6428602cd27 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentedUnderGameObjectHandler.cs b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentedUnderGameObjectHandler.cs new file mode 100644 index 0000000000..e1daa100a6 --- /dev/null +++ b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentedUnderGameObjectHandler.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using Unity.Netcode; + +namespace TestProject.ManualTests +{ + public class InSceneParentedUnderGameObjectHandler : NetworkBehaviour + { + static public List Instances = new List(); + + public override void OnNetworkSpawn() + { + if (IsServer) + { + Instances.Add(this); + } + } + } +} diff --git a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentedUnderGameObjectHandler.cs.meta b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentedUnderGameObjectHandler.cs.meta new file mode 100644 index 0000000000..4dbf901158 --- /dev/null +++ b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentedUnderGameObjectHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8384b01f654da304ebe165da1624cf56 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Manual/Scripts/ManualTestAssetsDestroyer.cs b/testproject/Assets/Tests/Manual/Scripts/ManualTestAssetsDestroyer.cs new file mode 100644 index 0000000000..50def2e431 --- /dev/null +++ b/testproject/Assets/Tests/Manual/Scripts/ManualTestAssetsDestroyer.cs @@ -0,0 +1,17 @@ +using UnityEngine; + +namespace TestProject.ManualTests +{ + public class ManualTestAssetsDestroyer : MonoBehaviour + { + public static bool IsIntegrationTest; + + private void Awake() + { + if (IsIntegrationTest) + { + Destroy(gameObject); + } + } + } +} diff --git a/testproject/Assets/Tests/Manual/Scripts/ManualTestAssetsDestroyer.cs.meta b/testproject/Assets/Tests/Manual/Scripts/ManualTestAssetsDestroyer.cs.meta new file mode 100644 index 0000000000..b7f2edb622 --- /dev/null +++ b/testproject/Assets/Tests/Manual/Scripts/ManualTestAssetsDestroyer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 60e2340f7e1668a4da162b39850fbdb9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Manual/Scripts/NetworkManagerTestDisabler.cs b/testproject/Assets/Tests/Manual/Scripts/NetworkManagerTestDisabler.cs new file mode 100644 index 0000000000..71b86ac71b --- /dev/null +++ b/testproject/Assets/Tests/Manual/Scripts/NetworkManagerTestDisabler.cs @@ -0,0 +1,22 @@ +using UnityEngine; +using Unity.Netcode; + +namespace TestProject.ManualTests +{ + public class NetworkManagerTestDisabler : MonoBehaviour + { + public static bool IsIntegrationTest; + + private void Awake() + { + if (IsIntegrationTest) + { + var networkManager = GetComponent(); + if (networkManager != null) + { + Destroy(gameObject); + } + } + } + } +} diff --git a/testproject/Assets/Tests/Manual/Scripts/NetworkManagerTestDisabler.cs.meta b/testproject/Assets/Tests/Manual/Scripts/NetworkManagerTestDisabler.cs.meta new file mode 100644 index 0000000000..dcaaeea919 --- /dev/null +++ b/testproject/Assets/Tests/Manual/Scripts/NetworkManagerTestDisabler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cf54eec7d39507743a95b32caf5b7c2a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/IntegrationTestWithApproximation.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/IntegrationTestWithApproximation.cs new file mode 100644 index 0000000000..2547a102ca --- /dev/null +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/IntegrationTestWithApproximation.cs @@ -0,0 +1,44 @@ +using UnityEngine; +using Unity.Netcode.TestHelpers.Runtime; + +namespace TestProject.RuntimeTests +{ + public abstract class IntegrationTestWithApproximation : NetcodeIntegrationTest + { + private const float k_AproximateDeltaVariance = 0.01f; + + protected virtual float GetDeltaVarianceThreshold() + { + return k_AproximateDeltaVariance; + } + + protected bool Approximately(float a, float b) + { + return Mathf.Abs(a - b) <= GetDeltaVarianceThreshold(); + } + + protected bool Approximately(Vector2 a, Vector2 b) + { + var deltaVariance = GetDeltaVarianceThreshold(); + return Mathf.Abs(a.x - b.x) <= deltaVariance && + Mathf.Abs(a.y - b.y) <= deltaVariance; + } + + protected bool Approximately(Vector3 a, Vector3 b) + { + var deltaVariance = GetDeltaVarianceThreshold(); + return Mathf.Abs(a.x - b.x) <= deltaVariance && + Mathf.Abs(a.y - b.y) <= deltaVariance && + Mathf.Abs(a.z - b.z) <= deltaVariance; + } + + protected bool Approximately(Quaternion a, Quaternion b) + { + var deltaVariance = GetDeltaVarianceThreshold(); + return Mathf.Abs(a.x - b.x) <= deltaVariance && + Mathf.Abs(a.y - b.y) <= deltaVariance && + Mathf.Abs(a.z - b.z) <= deltaVariance && + Mathf.Abs(a.w - b.w) <= deltaVariance; + } + } +} diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/IntegrationTestWithApproximation.cs.meta b/testproject/Assets/Tests/Runtime/ObjectParenting/IntegrationTestWithApproximation.cs.meta new file mode 100644 index 0000000000..af1adfd855 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/IntegrationTestWithApproximation.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 50a3b194bb5b8714d883dafd911db1ba +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjects.unity b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjects.unity new file mode 100644 index 0000000000..355f3d788b --- /dev/null +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjects.unity @@ -0,0 +1,1473 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &118026864 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 118026867} + - component: {fileID: 118026866} + - component: {fileID: 118026865} + - component: {fileID: 118026868} + m_Layer: 0 + m_Name: NetworkManager + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &118026865 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 118026864} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 6960e84d07fb87f47956e7a81d71c4e6, type: 3} + m_Name: + m_EditorClassIdentifier: + m_ProtocolType: 0 + m_MaxPacketQueueSize: 128 + m_MaxPayloadSize: 6144 + m_MaxSendQueueSize: 98304 + m_HeartbeatTimeoutMS: 500 + m_ConnectTimeoutMS: 1000 + m_MaxConnectAttempts: 60 + m_DisconnectTimeoutMS: 30000 + ConnectionData: + Address: 127.0.0.1 + Port: 7777 + ServerListenAddress: + DebugSimulator: + PacketDelayMS: 0 + PacketJitterMS: 0 + PacketDropRate: 0 +--- !u!114 &118026866 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 118026864} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 593a2fe42fa9d37498c96f9a383b6521, type: 3} + m_Name: + m_EditorClassIdentifier: + RunInBackground: 1 + LogLevel: 1 + NetworkConfig: + ProtocolVersion: 0 + NetworkTransport: {fileID: 118026865} + PlayerPrefab: {fileID: 0} + NetworkPrefabs: [] + TickRate: 30 + ClientConnectionBufferTimeout: 10 + ConnectionApproval: 0 + ConnectionData: + EnableTimeResync: 0 + TimeResyncInterval: 30 + EnsureNetworkVariableLengthSafety: 0 + EnableSceneManagement: 1 + ForceSamePrefabs: 1 + RecycleNetworkIds: 1 + NetworkIdRecycleDelay: 120 + RpcHashSize: 0 + LoadSceneTimeOut: 120 + SpawnTimeout: 1 + EnableNetworkLogs: 1 +--- !u!4 &118026867 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 118026864} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0.5, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &118026868 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 118026864} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: cf54eec7d39507743a95b32caf5b7c2a, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!1 &295758851 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 295758855} + - component: {fileID: 295758854} + - component: {fileID: 295758853} + - component: {fileID: 295758852} + m_Layer: 5 + m_Name: Canvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &295758852 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 295758851} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &295758853 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 295758851} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 0 +--- !u!223 &295758854 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 295758851} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 0 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_AdditionalShaderChannelsFlag: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &295758855 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 295758851} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 844324455} + - {fileID: 605493857} + m_Father: {fileID: 1392712516} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!1 &321630914 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 321630915} + - component: {fileID: 321630917} + m_Layer: 0 + m_Name: MainCamera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &321630915 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 321630914} + m_LocalRotation: {x: -0.011012609, y: -0, z: -0, w: 0.99993944} + m_LocalPosition: {x: 0, y: 5, z: -15} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1392712516} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: -1.262, y: 0, z: 0} +--- !u!20 &321630917 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 321630914} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_FocalLength: 50 + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 80 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!1 &505911173 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 505911176} + - component: {fileID: 505911175} + - component: {fileID: 505911174} + - component: {fileID: 505911177} + - component: {fileID: 505911178} + m_Layer: 0 + m_Name: Child-Gen2 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!23 &505911174 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 505911173} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 126d2da9b339ba9418b15d150233e786, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &505911175 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 505911173} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!4 &505911176 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 505911173} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1601792191} + m_Father: {fileID: 733348968} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &505911177 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 505911173} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 1818541129 + AlwaysReplicateAsRoot: 0 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 +--- !u!114 &505911178 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 505911173} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4f542d9d67c235941831b6428602cd27, type: 3} + m_Name: + m_EditorClassIdentifier: + ParentHasNoNetworkObject: 0 + IsRootParent: 0 + IsLastChild: 0 + PreserveLocalSpace: 0 + PositionMax: {x: 100, y: 10, z: 100} + PositionMin: {x: 0, y: 0, z: 0} + RotationMax: {x: 359.99, y: 359.99, z: 359.99} + RotationMin: {x: 0.01, y: 0.01, z: 0.01} + ScaleMax: 3 + ScaleMin: 0.25 +--- !u!1 &605493856 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 605493857} + - component: {fileID: 605493859} + - component: {fileID: 605493858} + m_Layer: 5 + m_Name: EventSystem + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &605493857 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 605493856} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 295758855} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &605493858 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 605493856} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3} + m_Name: + m_EditorClassIdentifier: + m_HorizontalAxis: Horizontal + m_VerticalAxis: Vertical + m_SubmitButton: Submit + m_CancelButton: Cancel + m_InputActionsPerSecond: 10 + m_RepeatDelay: 0.5 + m_ForceModuleActive: 0 +--- !u!114 &605493859 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 605493856} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_FirstSelected: {fileID: 0} + m_sendNavigationEvents: 1 + m_DragThreshold: 10 +--- !u!1 &631269575 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 631269578} + - component: {fileID: 631269577} + - component: {fileID: 631269576} + - component: {fileID: 631269579} + - component: {fileID: 631269580} + m_Layer: 0 + m_Name: Child-Gen4 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!23 &631269576 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 631269575} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: a7b755ad8e4fe4bdb8f5518c951abc70, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &631269577 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 631269575} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!4 &631269578 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 631269575} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1601792191} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &631269579 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 631269575} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 1688266703 + AlwaysReplicateAsRoot: 0 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 +--- !u!114 &631269580 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 631269575} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4f542d9d67c235941831b6428602cd27, type: 3} + m_Name: + m_EditorClassIdentifier: + ParentHasNoNetworkObject: 0 + IsRootParent: 0 + IsLastChild: 1 + PreserveLocalSpace: 0 + PositionMax: {x: 100, y: 10, z: 100} + PositionMin: {x: 0, y: 0, z: 0} + RotationMax: {x: 359.99, y: 359.99, z: 359.99} + RotationMin: {x: 0.01, y: 0.01, z: 0.01} + ScaleMax: 3 + ScaleMin: 0.25 +--- !u!1 &733348965 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 733348968} + - component: {fileID: 733348967} + - component: {fileID: 733348966} + - component: {fileID: 733348969} + - component: {fileID: 733348970} + m_Layer: 0 + m_Name: Child-Gen1 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!23 &733348966 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 733348965} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: d2f6bf650dfcc483794cdacf53f9fe2b, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &733348967 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 733348965} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!4 &733348968 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 733348965} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 505911176} + m_Father: {fileID: 945457593} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &733348969 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 733348965} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 1181626381 + AlwaysReplicateAsRoot: 0 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 +--- !u!114 &733348970 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 733348965} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4f542d9d67c235941831b6428602cd27, type: 3} + m_Name: + m_EditorClassIdentifier: + ParentHasNoNetworkObject: 0 + IsRootParent: 0 + IsLastChild: 0 + PreserveLocalSpace: 0 + PositionMax: {x: 100, y: 10, z: 100} + PositionMin: {x: 0, y: 0, z: 0} + RotationMax: {x: 359.99, y: 359.99, z: 359.99} + RotationMin: {x: 0.01, y: 0.01, z: 0.01} + ScaleMax: 3 + ScaleMin: 0.25 +--- !u!1001 &844324454 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + m_TransformParent: {fileID: 295758855} + m_Modifications: + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_Pivot.x + value: 0.5 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_Pivot.y + value: 0.5 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_RootOrder + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_AnchorMax.x + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_AnchorMax.y + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_AnchorMin.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_AnchorMin.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_SizeDelta.x + value: -952 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_SizeDelta.y + value: -344 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_AnchoredPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_AnchoredPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6963777608485144162, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_Name + value: ConnectionModeButtons + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: d725b5588e1b956458798319e6541d84, type: 3} +--- !u!224 &844324455 stripped +RectTransform: + m_CorrespondingSourceObject: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + m_PrefabInstance: {fileID: 844324454} + m_PrefabAsset: {fileID: 0} +--- !u!1 &945457592 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 945457593} + - component: {fileID: 945457595} + - component: {fileID: 945457594} + - component: {fileID: 945457596} + - component: {fileID: 945457597} + m_Layer: 0 + m_Name: RootParent_NetworkObject + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &945457593 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 945457592} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 733348968} + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &945457594 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 945457592} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 16358fcb4e0c94cc8b980fbb17259843, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &945457595 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 945457592} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!114 &945457596 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 945457592} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 3960212516 + AlwaysReplicateAsRoot: 0 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 +--- !u!114 &945457597 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 945457592} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4f542d9d67c235941831b6428602cd27, type: 3} + m_Name: + m_EditorClassIdentifier: + ParentHasNoNetworkObject: 0 + IsRootParent: 1 + IsLastChild: 0 + PreserveLocalSpace: 0 + PositionMax: {x: 100, y: 10, z: 100} + PositionMin: {x: 0, y: 0, z: 0} + RotationMax: {x: 359.99, y: 359.99, z: 359.99} + RotationMin: {x: 0.01, y: 0.01, z: 0.01} + ScaleMax: 3 + ScaleMin: 0.25 +--- !u!1 &1343771061 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1343771062} + - component: {fileID: 1343771063} + m_Layer: 0 + m_Name: DirectionalLight + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1343771062 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1343771061} + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 2.5, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1392712516} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!108 &1343771063 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1343771061} + m_Enabled: 1 + serializedVersion: 10 + m_Type: 1 + m_Shape: 0 + m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.80208 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!1 &1392712513 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1392712516} + - component: {fileID: 1392712517} + - component: {fileID: 1392712518} + m_Layer: 0 + m_Name: ManualTestObjects + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1392712516 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1392712513} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 295758855} + - {fileID: 321630915} + - {fileID: 1343771062} + m_Father: {fileID: 0} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1392712517 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1392712513} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 60e2340f7e1668a4da162b39850fbdb9, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &1392712518 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1392712513} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 2701768183 + AlwaysReplicateAsRoot: 0 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 +--- !u!1 &1601792188 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1601792191} + - component: {fileID: 1601792190} + - component: {fileID: 1601792189} + - component: {fileID: 1601792192} + - component: {fileID: 1601792193} + m_Layer: 0 + m_Name: Child-Gen3 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!23 &1601792189 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1601792188} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 7a8ec12ee27208a42a11d2944b1c1371, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &1601792190 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1601792188} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!4 &1601792191 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1601792188} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 631269578} + m_Father: {fileID: 505911176} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1601792192 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1601792188} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 65702661 + AlwaysReplicateAsRoot: 0 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 +--- !u!114 &1601792193 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1601792188} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4f542d9d67c235941831b6428602cd27, type: 3} + m_Name: + m_EditorClassIdentifier: + ParentHasNoNetworkObject: 0 + IsRootParent: 0 + IsLastChild: 0 + PreserveLocalSpace: 0 + PositionMax: {x: 100, y: 10, z: 100} + PositionMin: {x: 0, y: 0, z: 0} + RotationMax: {x: 359.99, y: 359.99, z: 359.99} + RotationMin: {x: 0.01, y: 0.01, z: 0.01} + ScaleMax: 3 + ScaleMin: 0.25 +--- !u!1 &1839922012 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1839922013} + m_Layer: 0 + m_Name: RootParent_GameObject + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1839922013 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1839922012} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0.5, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 2062433907} + m_Father: {fileID: 0} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &2062433906 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2062433907} + - component: {fileID: 2062433908} + - component: {fileID: 2062433909} + m_Layer: 0 + m_Name: ChildNetworkObject + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2062433907 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2062433906} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1839922013} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &2062433908 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2062433906} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 136663336 + AlwaysReplicateAsRoot: 0 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 +--- !u!114 &2062433909 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2062433906} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8384b01f654da304ebe165da1624cf56, type: 3} + m_Name: + m_EditorClassIdentifier: + Instances: [] diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjects.unity.meta b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjects.unity.meta new file mode 100644 index 0000000000..b8b7ca5ac5 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjects.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 49fd14bff1eceda4f9299721a9029750 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs new file mode 100644 index 0000000000..419f74cb65 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs @@ -0,0 +1,312 @@ +using System.Collections; +using System.Text; +using NUnit.Framework; +using UnityEngine.SceneManagement; +using UnityEngine.TestTools; +using Unity.Netcode; +using TestProject.ManualTests; + +namespace TestProject.RuntimeTests +{ + public class ParentingInSceneObjectsTests : IntegrationTestWithApproximation + { + private const string k_BaseSceneToLoad = "UnitTestBaseScene"; + private const string k_TestSceneToLoad = "ParentingInSceneObjects"; + private const string k_NestedUndeGameObjectName = "RootParent_GameObject"; + private const int k_NumIterationsDeparentReparent = 100; + private const float k_AproximateThresholdValue = 0.001f; + + private bool m_InitialClientsLoadedScene; + private StringBuilder m_ErrorValidationLog = new StringBuilder(0x2000); + + protected override int NumberOfClients => 2; + + protected override void OnOneTimeSetup() + { + NetworkManagerTestDisabler.IsIntegrationTest = true; + ManualTestAssetsDestroyer.IsIntegrationTest = true; + base.OnOneTimeSetup(); + } + + protected override void OnOneTimeTearDown() + { + NetworkManagerTestDisabler.IsIntegrationTest = false; + ManualTestAssetsDestroyer.IsIntegrationTest = false; + base.OnOneTimeTearDown(); + } + + private Scene m_BaseSceneLoaded; + + protected override IEnumerator OnSetup() + { + InSceneParentChildHandler.ResetInstancesTracking(m_EnableVerboseDebug); + InSceneParentedUnderGameObjectHandler.Instances.Clear(); + return base.OnSetup(); + } + + private void SceneManager_sceneLoaded(Scene scene, LoadSceneMode mode) + { + if (scene.name == k_BaseSceneToLoad) + { + m_BaseSceneLoaded = scene; + SceneManager.sceneLoaded -= SceneManager_sceneLoaded; + } + } + + protected override IEnumerator OnTearDown() + { + if (m_BaseSceneLoaded.IsValid() && m_BaseSceneLoaded.isLoaded) + { + SceneManager.UnloadSceneAsync(m_BaseSceneLoaded); + } + yield return base.OnTearDown(); + } + + + private void GeneratePositionDoesNotMatch(InSceneParentChildHandler serverHandler, InSceneParentChildHandler clientHandler) + { + m_ErrorValidationLog.Append($"[Client-{clientHandler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{clientHandler.NetworkObjectId}'s position {clientHandler.transform.position} does not equal the server-side position {serverHandler.transform.position}"); + } + + private void GenerateRotationDoesNotMatch(InSceneParentChildHandler serverHandler, InSceneParentChildHandler clientHandler) + { + m_ErrorValidationLog.Append($"[Client-{clientHandler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{clientHandler.NetworkObjectId}'s rotation {clientHandler.transform.eulerAngles} does not equal the server-side scale {serverHandler.transform.eulerAngles}"); + } + + private void GenerateScaleDoesNotMatch(InSceneParentChildHandler serverHandler, InSceneParentChildHandler clientHandler) + { + m_ErrorValidationLog.Append($"[Client-{clientHandler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{clientHandler.NetworkObjectId}'s scale {clientHandler.transform.localScale} does not equal the server-side scale {serverHandler.transform.localScale}"); + } + + private void GenerateParentIsNotCorrect(InSceneParentChildHandler handler, bool shouldHaveParent) + { + var serverOrClient = handler.NetworkManager.IsServer ? "Server" : "Client"; + if (!shouldHaveParent) + { + m_ErrorValidationLog.Append($"[{serverOrClient }-{handler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{handler.NetworkObjectId}'s still has the parent {handler.transform.parent.name} when it should be null!"); + } + else + { + m_ErrorValidationLog.Append($"[{serverOrClient }-{handler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{handler.NetworkObjectId}'s does not have a parent when it should!"); + } + } + + private bool ValidateClientAgainstServerTransformValues() + { + // We reset this each time because we are only interested in the last time it checked and failed + m_ErrorValidationLog.Clear(); + foreach (var instance in InSceneParentChildHandler.ServerRelativeInstances) + { + var serverInstanceTransform = instance.Value.transform; + foreach (var clientInstances in InSceneParentChildHandler.ClientRelativeInstances) + { + Assert.True(clientInstances.Value.ContainsKey(instance.Key), $"Client-{clientInstances.Key} did not spawn NetworkObject-{instance.Key}!"); + var clientInstance = clientInstances.Value[instance.Key]; + var clientInstanceTransform = clientInstance.transform; + if (!Approximately(serverInstanceTransform.position, clientInstanceTransform.position)) + { + GeneratePositionDoesNotMatch(instance.Value, clientInstance); + return false; + } + + if (!Approximately(serverInstanceTransform.eulerAngles, clientInstanceTransform.eulerAngles)) + { + GeneratePositionDoesNotMatch(instance.Value, clientInstance); + return false; + } + + if (!Approximately(serverInstanceTransform.localScale, clientInstanceTransform.localScale)) + { + GeneratePositionDoesNotMatch(instance.Value, clientInstance); + return false; + } + } + } + return true; + } + + private bool ValidateAllChildrenParentingStatus(bool checkForParent) + { + foreach (var instance in InSceneParentChildHandler.ServerRelativeInstances) + { + if (!instance.Value.IsRootParent) + { + if (checkForParent && instance.Value.transform.parent == null) + { + GenerateParentIsNotCorrect(instance.Value, checkForParent); + return false; + } + else if (!checkForParent && instance.Value.transform.parent != null) + { + GenerateParentIsNotCorrect(instance.Value, checkForParent); + return false; + } + } + } + + foreach (var clientInstances in InSceneParentChildHandler.ClientRelativeInstances) + { + foreach (var instance in clientInstances.Value) + { + if (!instance.Value.IsRootParent) + { + if (checkForParent && instance.Value.transform.parent == null) + { + GenerateParentIsNotCorrect(instance.Value, checkForParent); + return false; + } + else if (!checkForParent && instance.Value.transform.parent != null) + { + GenerateParentIsNotCorrect(instance.Value, checkForParent); + return false; + } + } + } + } + + return true; + } + + protected override float GetDeltaVarianceThreshold() + { + return k_AproximateThresholdValue; + } + + public enum ParentingSpace + { + WorldPositionStays, + WorldPositionDoesNotStay + } + + /// + /// This tests various nested children scenarios where it tests: + /// Children have their parent removed + /// The associated children and root parent have their position, rotation, and scale changed + /// Children are placed back under their respective parents + /// Verifying a late joining client's cloned version of the in-scene placed NetworkObject children + /// have the correct transform values and parent set (or no parent) + /// That users can remove parents, assign parents, and set transform values in the same pass. + /// All of the above is tested with WorldPositionStays set to both true and false. + /// + [UnityTest] + public IEnumerator InSceneParentingTest([Values] ParentingSpace parentingSpace) + { + InSceneParentChildHandler.WorldPositionStays = parentingSpace == ParentingSpace.WorldPositionStays; + SceneManager.sceneLoaded += SceneManager_sceneLoaded; + SceneManager.LoadScene(k_BaseSceneToLoad, LoadSceneMode.Additive); + m_InitialClientsLoadedScene = false; + m_ServerNetworkManager.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; + + var sceneEventStartedStatus = m_ServerNetworkManager.SceneManager.LoadScene(k_TestSceneToLoad, LoadSceneMode.Additive); + Assert.True(sceneEventStartedStatus == SceneEventProgressStatus.Started, $"Failed to load scene {k_TestSceneToLoad} with a return status of {sceneEventStartedStatus}."); + yield return WaitForConditionOrTimeOut(() => m_InitialClientsLoadedScene); + AssertOnTimeout($"Timed out waiting for all clients to load scene {k_TestSceneToLoad}!"); + + // [Currently Connected Clients] + // remove the parents, change all transform values, and re-parent + InSceneParentChildHandler.ServerRootParent.DeparentSetValuesAndReparent(); + yield return WaitForConditionOrTimeOut(ValidateClientAgainstServerTransformValues); + AssertOnTimeout($"Timed out waiting for all clients transform values to match the server transform values!\n {m_ErrorValidationLog}"); + + // [Late Join Client #1] + // Make sure the late joining client synchronizes properly + yield return CreateAndStartNewClient(); + yield return WaitForConditionOrTimeOut(ValidateClientAgainstServerTransformValues); + AssertOnTimeout($"Timed out waiting for the late joining client's transform values to match the server transform values!\n {m_ErrorValidationLog}"); + + // Remove the parents from all of the children + InSceneParentChildHandler.ServerRootParent.DeparentAllChildren(); + yield return WaitForConditionOrTimeOut(ValidateClientAgainstServerTransformValues); + AssertOnTimeout($"[Late Join 1] Timed out waiting for all clients transform values to match the server transform values!\n {m_ErrorValidationLog}"); + + yield return WaitForConditionOrTimeOut(() => ValidateAllChildrenParentingStatus(false)); + AssertOnTimeout($"[Late Join 1] Timed out waiting for all children to be removed from their parent!\n {m_ErrorValidationLog}"); + + // [Late Join Client #2] + // Make sure the late joining client synchronizes properly with all children having their parent removed + yield return CreateAndStartNewClient(); + yield return WaitForConditionOrTimeOut(ValidateClientAgainstServerTransformValues); + AssertOnTimeout($"[Late Join 2] Timed out waiting for the late joining client's transform values to match the server transform values!\n {m_ErrorValidationLog}"); + + // Just a sanity check that late joining client #2 has no child parented + yield return WaitForConditionOrTimeOut(() => ValidateAllChildrenParentingStatus(false)); + AssertOnTimeout($"[Late Join 2] Timed out waiting for late joined client's children objects to have no parent!\n {m_ErrorValidationLog}"); + + // Finally, re-parent all of the children to make sure late joining client #2 synchronizes properly + InSceneParentChildHandler.ServerRootParent.ReParentAllChildren(); + yield return WaitForConditionOrTimeOut(ValidateClientAgainstServerTransformValues); + AssertOnTimeout($"[Late Join 2] Timed out waiting for all clients transform values to match the server transform values!\n {m_ErrorValidationLog}"); + yield return WaitForConditionOrTimeOut(() => ValidateAllChildrenParentingStatus(true)); + AssertOnTimeout($"[Late Join 2] Timed out waiting for all children to be removed from their parent!\n {m_ErrorValidationLog}"); + + // Now run through many iterations where we remove the parents, set the parents, and while + // the parents are being set the InSceneParentChildHandler assigns new position, rotation, and scale values + // in the OnNetworkObjectParentChanged overridden method on the server side only + for (int i = 0; i < k_NumIterationsDeparentReparent; i++) + { + InSceneParentChildHandler.ServerRootParent.DeparentSetValuesAndReparent(); + + yield return WaitForConditionOrTimeOut(ValidateClientAgainstServerTransformValues); + AssertOnTimeout($"[Final Pass] Timed out waiting for all clients transform values to match the server transform values!\n {m_ErrorValidationLog}"); + + yield return WaitForConditionOrTimeOut(() => ValidateAllChildrenParentingStatus(true)); + AssertOnTimeout($"[Final Pass] Timed out waiting for all children to be removed from their parent!\n {m_ErrorValidationLog}"); + } + + // In the final pass, we remove the second generation nested child + var firstGenChild = InSceneParentChildHandler.ServerRootParent.transform.GetChild(0); + var secondGenChild = firstGenChild.GetChild(0); + var secondGenChildNetworkObject = secondGenChild.GetComponent(); + Assert.True(secondGenChildNetworkObject.TrySetParent((NetworkObject)null, false), $"[Final Pass] Failed to remove the parent from the second generation child!"); + + // Validate all transform values match + yield return WaitForConditionOrTimeOut(ValidateClientAgainstServerTransformValues); + AssertOnTimeout($"[Final Pass] Timed out waiting for all clients transform values to match the server transform values after the second generation child's parent was removed!\n {m_ErrorValidationLog}"); + + // Now run through one last de-parent, re-parent, and set new values pass to make sure everything still synchronizes + InSceneParentChildHandler.ServerRootParent.DeparentSetValuesAndReparent(); + + yield return WaitForConditionOrTimeOut(ValidateClientAgainstServerTransformValues); + AssertOnTimeout($"[Final Pass - Last Test] Timed out waiting for all clients transform values to match the server transform values!\n {m_ErrorValidationLog}"); + + yield return WaitForConditionOrTimeOut(() => ValidateAllChildrenParentingStatus(true)); + AssertOnTimeout($"[Final Pass - Last Test] Timed out waiting for all children to be removed from their parent!\n {m_ErrorValidationLog}"); + } + + private void SceneManager_OnSceneEvent(SceneEvent sceneEvent) + { + if (sceneEvent.SceneName != k_TestSceneToLoad) + { + return; + } + + if (sceneEvent.ClientId == m_ServerNetworkManager.LocalClientId && sceneEvent.SceneEventType == SceneEventType.LoadEventCompleted) + { + m_InitialClientsLoadedScene = true; + } + } + + /// + /// This verifies in-scene placed NetworkObject's nested under a GameObject without a NetworkObject + /// component will preserve that parent hierarchy. + /// + [UnityTest] + public IEnumerator InSceneNestedUnderGameObjectTest() + { + SceneManager.sceneLoaded += SceneManager_sceneLoaded; + SceneManager.LoadScene(k_BaseSceneToLoad, LoadSceneMode.Additive); + m_InitialClientsLoadedScene = false; + m_ServerNetworkManager.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; + + var sceneEventStartedStatus = m_ServerNetworkManager.SceneManager.LoadScene(k_TestSceneToLoad, LoadSceneMode.Additive); + Assert.True(sceneEventStartedStatus == SceneEventProgressStatus.Started, $"Failed to load scene {k_TestSceneToLoad} with a return status of {sceneEventStartedStatus}."); + yield return WaitForConditionOrTimeOut(() => m_InitialClientsLoadedScene); + AssertOnTimeout($"Timed out waiting for all clients to load scene {k_TestSceneToLoad}!"); + + foreach (var instance in InSceneParentedUnderGameObjectHandler.Instances) + { + Assert.False(instance.transform.parent == null, $"{instance.name}'s parent is null when it should not be!"); + } + } + } +} diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs.meta b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs.meta new file mode 100644 index 0000000000..383199e0df --- /dev/null +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eb1bdec28131915419a2825132406932 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs new file mode 100644 index 0000000000..87507cfdc9 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs @@ -0,0 +1,463 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; +using Unity.Netcode; +using Unity.Netcode.Components; +using Object = UnityEngine.Object; + +namespace TestProject.RuntimeTests +{ + public class ParentingWorldPositionStaysTests : IntegrationTestWithApproximation + { + private const int k_NestedChildren = 10; + private const string k_ParentName = "Parent"; + private const string k_ChildName = "Child"; + + protected override int NumberOfClients => 2; + + internal class TestComponentHelper : NetworkBehaviour + { + internal class ChildInfo + { + public bool HasBeenParented; + public GameObject Child; + } + + internal class ParentChildInfo + { + public GameObject RootParent; + public List Children = new List(); + } + + public static Dictionary NetworkObjectIdToIndex = new Dictionary(); + + public static Dictionary ClientsRegistered = new Dictionary(); + + public Vector3 Scale; + public bool WorldPositionStays; + + public override void OnNetworkSpawn() + { + if (!IsServer) + { + var localClientId = NetworkManager.LocalClientId; + if (!ClientsRegistered.ContainsKey(localClientId)) + { + ClientsRegistered.Add(localClientId, new ParentChildInfo()); + // Fill the expected entries with null values + for (int i = 0; i < k_NestedChildren; i++) + { + ClientsRegistered[localClientId].Children.Add(new ChildInfo()); + } + } + + var entryToModify = ClientsRegistered[NetworkManager.LocalClientId]; + if (gameObject.name.Contains(k_ParentName)) + { + if (entryToModify.RootParent == null) + { + entryToModify.RootParent = gameObject; + return; + } + else + { + throw new Exception($"Failed to assigned {gameObject.name} as a parent! {nameof(GameObject)} {entryToModify.RootParent.name} is already assigned to Client-{localClientId}'s parent entry!"); + } + } + + + if (gameObject.name.Contains(k_ChildName)) + { + if (!NetworkObjectIdToIndex.ContainsKey(NetworkObjectId)) + { + //This should never happen (sanity check) + throw new Exception($"Client spawned {NetworkObjectId} but there was no index lookup table!"); + } + var childIndex = NetworkObjectIdToIndex[NetworkObjectId]; + var childInfo = ClientsRegistered[localClientId].Children[childIndex]; + if (childInfo.Child == null) + { + childInfo.Child = gameObject; + ClientsRegistered[localClientId].Children[childIndex] = childInfo; + return; + } + else + { + throw new Exception($"Failed to assigned {gameObject.name} already assigned! {nameof(GameObject)} { ClientsRegistered[localClientId].Children[childIndex].Child.name} is already assigned to Client-{localClientId}'s child entry!"); + } + } + // We should never reach this point + throw new Exception($"We spawned {name} but did not assign anything!"); + } + base.OnNetworkSpawn(); + } + + public override void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) + { + base.OnNetworkObjectParentChanged(parentNetworkObject); + if (parentNetworkObject == null || IsServer) + { + if (WorldPositionStays) + { + transform.localScale = Scale; + } + return; + } + var localClientId = NetworkManager.LocalClientId; + if (!ClientsRegistered.ContainsKey(localClientId)) + { + throw new Exception($"Parented {gameObject.name} before it was spawned!"); + } + + var netObjId = NetworkObject.NetworkObjectId; + var childIndex = NetworkObjectIdToIndex[netObjId]; + var childInfo = ClientsRegistered[localClientId].Children[childIndex]; + + if (!NetworkObjectIdToIndex.ContainsKey(netObjId)) + { + if (netObjId == 0) + { + return; + } + //This should never happen (sanity check) + throw new Exception($"Client spawned {NetworkObjectId} but there was no index lookup table!"); + } + + childInfo.HasBeenParented = true; + } + } + + public enum ParentingTestModes + { + LocalPositionStays, + WorldPositionStays + } + + public enum NetworkTransformSettings + { + None, + NetworkTransformInterpolate, + NetworkTransformImmediate + } + + private GameObject m_ParentPrefabObject; + private GameObject m_ChildPrefabObject; + + private GameObject m_ServerSideParent; + private List m_ServerSideChildren = new List(); + + private Vector3 m_ParentStartPosition = new Vector3(1.0f, 1.0f, 1.0f); + private Quaternion m_ParentStartRotation = Quaternion.Euler(0.0f, 90.0f, 0.0f); + private Vector3 m_ChildStartPosition = new Vector3(100.0f, -100.0f, 100.0f); + private Quaternion m_ChildStartRotation = Quaternion.Euler(-35.0f, 0.0f, -180.0f); + private Vector3 m_ChildStartScale = Vector3.one; + + protected override IEnumerator OnSetup() + { + TestComponentHelper.ClientsRegistered.Clear(); + TestComponentHelper.NetworkObjectIdToIndex.Clear(); + for (int i = 0; i < k_NestedChildren; i++) + { + m_ServerSideChildren.Add(null); + } + return base.OnSetup(); + } + + protected override IEnumerator OnTearDown() + { + if (m_ServerSideParent != null && m_ServerSideParent.GetComponent().IsSpawned) + { + // Clean up in reverse order (also makes sure we can despawn parents before children) + m_ServerSideParent.GetComponent().Despawn(); + } + + // Now despawn the children + // (and clean up our test) + for (int i = 0; i < k_NestedChildren; i++) + { + var serverSideChild = m_ServerSideChildren[i]; + if (serverSideChild != null && serverSideChild.GetComponent().IsSpawned) + { + serverSideChild.GetComponent().Despawn(); + } + } + + // Just allow the clients to run through despawning (also assures nothing throws an exception when destroying) + yield return new WaitForSeconds(0.2f); + + m_ServerSideChildren.Clear(); + TestComponentHelper.ClientsRegistered.Clear(); + TestComponentHelper.NetworkObjectIdToIndex.Clear(); + m_ParentPrefabObject = null; + m_ChildPrefabObject = null; + m_ServerSideParent = null; + yield return base.OnTearDown(); + } + + protected override void OnServerAndClientsCreated() + { + m_ParentPrefabObject = CreateNetworkObjectPrefab(k_ParentName); + m_ParentPrefabObject.AddComponent(); + m_ParentPrefabObject.transform.position = m_ParentStartPosition; + m_ParentPrefabObject.transform.rotation = m_ParentStartRotation; + m_ChildPrefabObject = CreateNetworkObjectPrefab(k_ChildName); + m_ChildPrefabObject.AddComponent(); + m_ChildPrefabObject.transform.position = m_ChildStartPosition; + m_ChildPrefabObject.transform.rotation = m_ChildStartRotation; + m_ChildPrefabObject.transform.localScale = m_ChildStartScale; + m_ServerNetworkManager.LogLevel = m_EnableVerboseDebug ? LogLevel.Developer : LogLevel.Normal; + + base.OnServerAndClientsCreated(); + } + + protected override void OnNewClientCreated(NetworkManager networkManager) + { + foreach (var networkPrefab in m_ServerNetworkManager.NetworkConfig.NetworkPrefabs) + { + networkManager.NetworkConfig.NetworkPrefabs.Add(networkPrefab); + } + } + + private bool HaveAllClientsSpawnedObjects() + { + foreach (var client in m_ClientNetworkManagers) + { + if (!s_GlobalNetworkObjects.ContainsKey(client.LocalClientId)) + { + return false; + } + var clientSpawnedObjects = s_GlobalNetworkObjects[client.LocalClientId]; + foreach (var gameObject in m_ServerSideChildren) + { + var networkOject = gameObject.GetComponent(); + if (!clientSpawnedObjects.ContainsKey(networkOject.NetworkObjectId)) + { + return false; + } + } + } + return true; + } + + private bool HaveAllClientsParentedChild() + { + foreach (var clientEntries in TestComponentHelper.ClientsRegistered) + { + foreach (var clientInfo in clientEntries.Value.Children) + { + if (!clientInfo.HasBeenParented) + { + return false; + } + } + } + return true; + } + + /// + /// Verifies that using worldPositionStays when parenting via NetworkObject.TrySetParent, + /// that the client-side transform values match that of the server-side. + /// This also tests nested parenting and out of hierarchical order child spawning. + /// + [UnityTest] + public IEnumerator WorldPositionStaysTest([Values] ParentingTestModes mode, [Values] NetworkTransformSettings networkTransformSettings) + { + var useNetworkTransform = networkTransformSettings != NetworkTransformSettings.None; + var interpolate = networkTransformSettings == NetworkTransformSettings.NetworkTransformInterpolate; + var worldPositionStays = mode == ParentingTestModes.WorldPositionStays; + var startTime = Time.realtimeSinceStartup; + m_ServerSideParent = Object.Instantiate(m_ParentPrefabObject); + + var serverSideChildNetworkObjects = new List(); + var childPosition = m_ChildStartPosition; + var childRotation = m_ChildStartRotation; + var childScale = m_ChildStartScale; + // Used to store the expected position and rotation for children (local space relative) + var childPositionList = new List(); + var childRotationList = new List(); + var childScaleList = new List(); + var childLarger = 1.15f; + var childSmaller = 0.85f; + if (useNetworkTransform) + { + var networkTransform = m_ChildPrefabObject.AddComponent(); + networkTransform.InLocalSpace = !worldPositionStays; + } + + var serverSideParentNetworkObject = m_ServerSideParent.GetComponent(); + serverSideParentNetworkObject.Spawn(); + + // Instantiate the children + for (int i = 0; i < k_NestedChildren; i++) + { + m_ServerSideChildren[i] = Object.Instantiate(m_ChildPrefabObject); + childPositionList.Add(childPosition); + childRotationList.Add(childRotation.eulerAngles); + childScaleList.Add(childScale); + // Change each child's position, rotation, and scale + childRotation = Quaternion.Euler(childRotation.eulerAngles * 0.80f); + childPosition = childPosition * 0.80f; + if ((i % 2) == 0) + { + childScale = m_ChildStartScale * childLarger; + childLarger *= childLarger; + } + else + { + childScale = m_ChildStartScale * childSmaller; + childSmaller *= childSmaller; + } + var serverSideChild = m_ServerSideChildren[i]; + + var serverSideChildNetworkObject = serverSideChild.GetComponent(); + serverSideChild.transform.position = childPositionList[i]; + serverSideChild.transform.rotation = Quaternion.Euler(childRotationList[i]); + + serverSideChild.transform.localScale = childScaleList[i]; + VerboseDebug($"[Server][PreSpawn] Set scale of NetworkObject to ({childScaleList[i]})"); + serverSideChildNetworkObject.Spawn(); + VerboseDebug($"[Server] Set scale of NetworkObjectID ({serverSideChildNetworkObject.NetworkObjectId}) to ({childScaleList[i]}) and is currently {serverSideChild.transform.localScale}"); + + TestComponentHelper.NetworkObjectIdToIndex.Add(serverSideChildNetworkObject.NetworkObjectId, i); + Assert.IsTrue(Approximately(m_ServerSideParent.transform.position, m_ParentStartPosition)); + Assert.IsTrue(Approximately(m_ServerSideParent.transform.rotation.eulerAngles, m_ParentStartRotation.eulerAngles)); + Assert.IsTrue(Approximately(serverSideChild.transform.position, childPositionList[i])); + Assert.IsTrue(Approximately(serverSideChild.transform.rotation.eulerAngles, childRotationList[i])); + Assert.IsTrue(Approximately(serverSideChild.transform.localScale, childScaleList[i]), $"[Initial Scale] Server-side child scale ({serverSideChild.transform.localScale}) does not equal the expected scale ({childScaleList[i]})"); + } + + VerboseDebug($"[{Time.realtimeSinceStartup - startTime}] Spawned parent and child objects."); + + // Wait for clients to spawn the NetworkObjects + yield return WaitForConditionOrTimeOut(HaveAllClientsSpawnedObjects); + AssertOnTimeout("Timed out waiting for all clients to spawn the respective parent and child objects"); + VerboseDebug($"[{Time.realtimeSinceStartup - startTime}] Clients spawned parent and child objects."); + + // Verify the positions are identical to the default values + foreach (var clientEntry in TestComponentHelper.ClientsRegistered) + { + var children = clientEntry.Value.Children; + var rootParent = clientEntry.Value.RootParent; + Assert.IsTrue(Approximately(rootParent.transform.position, m_ParentStartPosition)); + Assert.IsTrue(Approximately(rootParent.transform.rotation.eulerAngles, m_ParentStartRotation.eulerAngles)); + for (int i = 0; i < k_NestedChildren; i++) + { + var clientChildInfo = children[i]; + var serverChild = m_ServerSideChildren[i]; + + Assert.IsFalse(clientChildInfo.HasBeenParented, $"Client-{clientEntry.Key} has already been parented!"); + + Assert.IsTrue(Approximately(clientChildInfo.Child.transform.position, serverChild.transform.position), $"[Client-{clientEntry.Key}][{clientChildInfo.Child.name}] Child position ({clientChildInfo.Child.transform.position}) does not" + + $" equal the server-side child's position ({serverChild.transform.position})"); + Assert.IsTrue(Approximately(clientChildInfo.Child.transform.eulerAngles, serverChild.transform.eulerAngles), $"[Client-{clientEntry.Key}][{clientChildInfo.Child.name}] Child rotation ({clientChildInfo.Child.transform.eulerAngles}) does not" + + $" equal the server-side child's rotation ({serverChild.transform.eulerAngles})"); + Assert.IsTrue(Approximately(clientChildInfo.Child.transform.localScale, serverChild.transform.localScale), $"[Client-{clientEntry.Key}][{clientChildInfo.Child.name}] Child scale ({clientChildInfo.Child.transform.localScale}) does not" + + $" equal the server-side child's scale ({serverChild.transform.localScale})"); + } + } + + var currentParent = serverSideParentNetworkObject; + for (int i = 0; i < k_NestedChildren; i++) + { + var childNetworkObject = m_ServerSideChildren[i].GetComponent(); + VerboseDebug($"[Server Parenting][Before] Scale of NetworkObjectID ({childNetworkObject.NetworkObjectId}) is currently {childNetworkObject.transform.localScale}"); + Assert.True(childNetworkObject.TrySetParent(currentParent, worldPositionStays)); + VerboseDebug($"[Server Parenting][After] Scale of NetworkObjectID ({childNetworkObject.NetworkObjectId}) is now {childNetworkObject.transform.localScale}"); + currentParent = childNetworkObject; + } + + VerboseDebug($"[{Time.realtimeSinceStartup - startTime}] Parented all children."); + + // Wait for all client instances to have been parented. + yield return WaitForConditionOrTimeOut(HaveAllClientsParentedChild); + AssertOnTimeout("Timed out waiting for all clients to parent the child object!"); + VerboseDebug($"[{Time.realtimeSinceStartup - startTime}] All clients parented the child."); + + var serverParentTransform = m_ServerSideParent.transform; + // Verify the positions are identical to the default values + foreach (var clientEntry in TestComponentHelper.ClientsRegistered) + { + var children = clientEntry.Value.Children; + var rootParent = clientEntry.Value.RootParent; + Assert.IsTrue(Approximately(rootParent.transform.position, m_ServerSideParent.transform.position), $"Client-{clientEntry.Key} parent's position ({rootParent.transform.position}) does not equal the server parent's position ({serverParentTransform.position})!"); + Assert.IsTrue(Approximately(rootParent.transform.rotation.eulerAngles, m_ServerSideParent.transform.rotation.eulerAngles), $"Client-{clientEntry.Key} parent's rotation ({rootParent.transform.rotation.eulerAngles}) does not equal the server parent's position ({serverParentTransform.rotation.eulerAngles})!"); + for (int i = 0; i < k_NestedChildren; i++) + { + var clientChildInfo = children[i]; + var serverChild = m_ServerSideChildren[i]; + Assert.IsTrue(clientChildInfo.HasBeenParented, $"Client-{clientEntry.Key} has not been parented!"); + // Assure we mirror the server + Assert.IsTrue(Approximately(clientChildInfo.Child.transform.position, serverChild.transform.position), $"Client-{clientEntry.Key} child's position ({clientChildInfo.Child.transform.position}) does not equal the server child's position ({serverChild.transform.position})!"); + Assert.IsTrue(Approximately(clientChildInfo.Child.transform.eulerAngles, serverChild.transform.rotation.eulerAngles), $"Client-{clientEntry.Key} child's rotation ({clientChildInfo.Child.transform.rotation.eulerAngles}) does not equal the server child's rotation ({serverChild.transform.rotation.eulerAngles})!"); + if (useNetworkTransform) + { + yield return WaitForConditionOrTimeOut(() => Approximately(clientChildInfo.Child.transform.localScale, serverChild.transform.localScale)); + AssertOnTimeout($"Timed out waiting for client-{clientEntry.Key} child's scale ({clientChildInfo.Child.transform.localScale}) to equal the server child's scale ({serverChild.transform.localScale}) [Has NetworkTransform]"); + } + else + { + Assert.IsTrue(Approximately(clientChildInfo.Child.transform.localScale, serverChild.transform.localScale), $"Client-{clientEntry.Key} child's scale ({clientChildInfo.Child.transform.localScale}) does not equal the server child's scale ({serverChild.transform.localScale})"); + } + + // Assure we still have the same local space values when not preserving the world position + if (!worldPositionStays) + { + Assert.IsTrue(Approximately(clientChildInfo.Child.transform.localPosition, childPositionList[i]), $"Client-{clientEntry.Key} child's local space position ({clientChildInfo.Child.transform.localPosition}) does not equal the default child's position ({childPositionList[i]})!"); + Assert.IsTrue(Approximately(clientChildInfo.Child.transform.localRotation.eulerAngles, childRotationList[i]), $"Client-{clientEntry.Key} child's local space rotation ({clientChildInfo.Child.transform.localRotation.eulerAngles}) does not equal the server child's rotation ({childRotationList[i]})!"); + } + } + } + + // Late join a client and run through the same checks + yield return CreateAndStartNewClient(); + AssertOnTimeout("[Late-Join] Timed out waiting for client to late join!"); + + // Wait for clients to spawn the NetworkObjects + yield return WaitForConditionOrTimeOut(HaveAllClientsSpawnedObjects); + AssertOnTimeout("[Late-Join] Timed out waiting for all clients to spawn the respective parent and child objects"); + + // Wait for all client instances to have been parented. + yield return WaitForConditionOrTimeOut(HaveAllClientsParentedChild); + AssertOnTimeout("[Late-Join] Timed out waiting for all clients to parent the child object!"); + + // Verify the positions are identical to the default values + foreach (var clientEntry in TestComponentHelper.ClientsRegistered) + { + var children = clientEntry.Value.Children; + var rootParent = clientEntry.Value.RootParent; + Assert.IsTrue(Approximately(rootParent.transform.position, m_ServerSideParent.transform.position), $"[LateJoin] Client-{clientEntry.Key} parent's position ({rootParent.transform.position}) does not equal the server parent's position ({serverParentTransform.position})!"); + Assert.IsTrue(Approximately(rootParent.transform.rotation.eulerAngles, m_ServerSideParent.transform.rotation.eulerAngles), $"[LateJoin] Client-{clientEntry.Key} parent's rotation ({rootParent.transform.rotation.eulerAngles}) does not equal the server parent's position ({serverParentTransform.rotation.eulerAngles})!"); + for (int i = 0; i < k_NestedChildren; i++) + { + var clientChildInfo = children[i]; + var serverChild = m_ServerSideChildren[i]; + Assert.IsTrue(clientChildInfo.HasBeenParented, $"[LateJoin] Client-{clientEntry.Key} has not been parented!"); + // Assure we mirror the server + Assert.IsTrue(Approximately(clientChildInfo.Child.transform.position, serverChild.transform.position), $"[LateJoin] Client-{clientEntry.Key} child's position ({clientChildInfo.Child.transform.position}) does not equal the server child's position ({serverChild.transform.position})!"); + Assert.IsTrue(Approximately(clientChildInfo.Child.transform.eulerAngles, serverChild.transform.rotation.eulerAngles), $"[LateJoin] Client-{clientEntry.Key} child's rotation ({clientChildInfo.Child.transform.rotation.eulerAngles}) does not equal the server child's rotation ({serverChild.transform.rotation.eulerAngles})!"); + + if (useNetworkTransform) + { + yield return WaitForConditionOrTimeOut(() => Approximately(clientChildInfo.Child.transform.localScale, serverChild.transform.localScale)); + AssertOnTimeout($"[Late Join] Timed out waiting for client-{clientEntry.Key} child's scale ({clientChildInfo.Child.transform.localScale}) to equal the server child's scale ({serverChild.transform.localScale}) [Has NetworkTransform]"); + } + else + { + Assert.IsTrue(Approximately(clientChildInfo.Child.transform.localScale, serverChild.transform.localScale), $"[LateJoin] Client-{clientEntry.Key} child's scale ({clientChildInfo.Child.transform.localScale}) does not equal the server child's scale ({serverChild.transform.localScale})"); + } + + // Assure we still have the same local space values when not preserving the world position + if (!worldPositionStays) + { + Assert.IsTrue(Approximately(clientChildInfo.Child.transform.localPosition, childPositionList[i]), $"[LateJoin] Client-{clientEntry.Key} child's local space position ({clientChildInfo.Child.transform.localPosition}) does not equal the default child's position ({childPositionList[i]})!"); + Assert.IsTrue(Approximately(clientChildInfo.Child.transform.localRotation.eulerAngles, childRotationList[i]), $"[LateJoin] Client-{clientEntry.Key} child's local space rotation ({clientChildInfo.Child.transform.localRotation.eulerAngles}) does not equal the server child's rotation ({childRotationList[i]})!"); + } + } + } + VerboseDebug($"[{Time.realtimeSinceStartup - startTime}] Late joined client was validated. Test completed!"); + } + } +} diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs.meta b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs.meta new file mode 100644 index 0000000000..3b3e7d27fb --- /dev/null +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ea191714be7765a41ba7cbd26b6ecfa0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/ProjectSettings/EditorBuildSettings.asset b/testproject/ProjectSettings/EditorBuildSettings.asset index d10c3f223c..a1496f313c 100644 --- a/testproject/ProjectSettings/EditorBuildSettings.asset +++ b/testproject/ProjectSettings/EditorBuildSettings.asset @@ -110,6 +110,9 @@ EditorBuildSettings: - enabled: 1 path: Assets/Samples/Teleport/TeleportSample.unity guid: efa247d1f78ca694f8d2dcb5672e8f8b + - enabled: 1 + path: Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjects.unity + guid: 49fd14bff1eceda4f9299721a9029750 m_configObjects: com.unity.addressableassets: {fileID: 11400000, guid: 5a3d5c53c25349c48912726ae850f3b0, type: 2} From 8d8f50c34c8c6d8f8c3fdd50e5d606114e5d21a2 Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Tue, 4 Oct 2022 23:37:25 -0400 Subject: [PATCH 082/138] chore: secure connections (#2209) Interfaces with UTP 2.0 to allow secure connection. Adds UnityTransport APIs to set secrets. --- .../Runtime/Core/NetworkManager.cs | 73 +++++--- .../Transports/UTP/SecretsLoaderHelper.cs | 161 ++++++++++++++++++ .../UTP/SecretsLoaderHelper.cs.meta | 11 ++ .../Runtime/Transports/UTP/UnityTransport.cs | 108 +++++++++++- testproject/.gitignore | 4 + testproject/Assets/Secure/generate.sh | 6 + 6 files changed, 338 insertions(+), 25 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Runtime/Transports/UTP/SecretsLoaderHelper.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Transports/UTP/SecretsLoaderHelper.cs.meta create mode 100755 testproject/Assets/Secure/generate.sh diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 11cae4478c..0c1e0d9d59 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1026,23 +1026,37 @@ public bool StartServer() Initialize(true); - // If we failed to start then shutdown and notify user that the transport failed to start - if (NetworkConfig.NetworkTransport.StartServer()) + IsServer = true; + IsClient = false; + IsListening = true; + + try { - IsServer = true; - IsClient = false; - IsListening = true; + // If we failed to start then shutdown and notify user that the transport failed to start + if (NetworkConfig.NetworkTransport.StartServer()) + { + SpawnManager.ServerSpawnSceneObjectsOnStartSweep(); - SpawnManager.ServerSpawnSceneObjectsOnStartSweep(); + OnServerStarted?.Invoke(); + return true; + } + else + { + IsServer = false; + IsClient = false; + IsListening = false; - OnServerStarted?.Invoke(); - return true; + Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!"); + OnTransportFailure?.Invoke(); + Shutdown(); + } } - else + catch (Exception) { - Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!"); - OnTransportFailure?.Invoke(); - Shutdown(); + IsServer = false; + IsClient = false; + IsListening = false; + throw; } return false; @@ -1100,23 +1114,38 @@ public bool StartHost() Initialize(true); - // If we failed to start then shutdown and notify user that the transport failed to start - if (!NetworkConfig.NetworkTransport.StartServer()) + IsServer = true; + IsClient = true; + IsListening = true; + + try { - Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!"); - OnTransportFailure?.Invoke(); - Shutdown(); - return false; + // If we failed to start then shutdown and notify user that the transport failed to start + if (!NetworkConfig.NetworkTransport.StartServer()) + { + Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!"); + OnTransportFailure?.Invoke(); + Shutdown(); + + IsServer = false; + IsClient = false; + IsListening = false; + + return false; + } + } + catch (Exception) + { + IsServer = false; + IsClient = false; + IsListening = false; + throw; } MessagingSystem.ClientConnected(ServerClientId); LocalClientId = ServerClientId; NetworkMetrics.SetConnectionId(LocalClientId); - IsServer = true; - IsClient = true; - IsListening = true; - if (NetworkConfig.ConnectionApproval && ConnectionApprovalCallback != null) { var response = new ConnectionApprovalResponse(); diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/SecretsLoaderHelper.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/SecretsLoaderHelper.cs new file mode 100644 index 0000000000..82c4d37013 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/SecretsLoaderHelper.cs @@ -0,0 +1,161 @@ +using System; +using System.IO; +using UnityEngine; + +namespace Unity.Netcode.Transports.UTP +{ + /// + /// Component to add to a NetworkManager if you want the certificates to be loaded from files. + /// Mostly helpful to ease development and testing, especially with self-signed certificates + /// + /// Shipping code should make the calls to + /// - SetServerSecrets + /// - SetClientSecrets + /// directly, instead of relying on this. + /// + public class SecretsLoaderHelper : MonoBehaviour + { + internal struct ServerSecrets + { + public string ServerPrivate; + public string ServerCertificate; + }; + + internal struct ClientSecrets + { + public string ServerCommonName; + public string ClientCertificate; + }; + + private void Awake() + { + var serverSecrets = new ServerSecrets(); + + try + { + serverSecrets.ServerCertificate = ServerCertificate; + } + catch (Exception exception) + { + Debug.Log(exception); + } + + try + { + serverSecrets.ServerPrivate = ServerPrivate; + } + catch (Exception exception) + { + Debug.Log(exception); + } + + var clientSecrets = new ClientSecrets(); + try + { + clientSecrets.ClientCertificate = ClientCA; + } + catch (Exception exception) + { + Debug.Log(exception); + } + + try + { + clientSecrets.ServerCommonName = ServerCommonName; + } + catch (Exception exception) + { + Debug.Log(exception); + } + + var unityTransportComponent = GetComponent(); + + if (unityTransportComponent == null) + { + Debug.LogError($"You need to select the UnityTransport protocol, in the NetworkManager, in order for the SecretsLoaderHelper component to be useful."); + return; + } + + unityTransportComponent.SetServerSecrets(serverSecrets.ServerCertificate, serverSecrets.ServerPrivate); + unityTransportComponent.SetClientSecrets(clientSecrets.ServerCommonName, clientSecrets.ClientCertificate); + } + + [Tooltip("Hostname")] + [SerializeField] + private string m_ServerCommonName = "localhost"; + public string ServerCommonName + { + get => m_ServerCommonName; + set => m_ServerCommonName = value; + } + + [Tooltip("Client CA filepath. Useful with self-signed certificates")] + [SerializeField] + private string m_ClientCAFilePath = "Assets/Secure/myGameClientCA.pem"; + public string ClientCAFilePath + { + get => m_ClientCAFilePath; + set => m_ClientCAFilePath = value; + } + + [Tooltip("Client CA Override. Only useful for development with self-signed certificates. Certificate content, for platforms that lack file access (WebGL)")] + [SerializeField] + private string m_ClientCAOverride = ""; + public string ClientCAOverride + { + get => m_ClientCAOverride; + set => m_ClientCAOverride = value; + } + + [Tooltip("Server Certificate filepath")] + [SerializeField] + private string m_ServerCertificateFilePath = "Assets/Secure/myGameServerCertificate.pem"; + public string ServerCertificateFilePath + { + get => m_ServerCertificateFilePath; + set => m_ServerCertificateFilePath = value; + } + [Tooltip("Server Private Keyfilepath")] + [SerializeField] + private string m_ServerPrivateFilePath = "Assets/Secure/myGameServerPrivate.pem"; + public string ServerPrivateFilePath + { + get => m_ServerPrivateFilePath; + set => m_ServerPrivate = value; + } + + private string m_ClientCA; + public string ClientCA + { + get + { + if (m_ClientCAOverride != "") + { + return m_ClientCAOverride; + } + return ReadFile(m_ClientCAFilePath, "Client Certificate"); + } + set => m_ClientCA = value; + } + private string m_ServerCertificate; + public string ServerCertificate + { + get => ReadFile(m_ServerCertificateFilePath, "Server Certificate"); + set => m_ServerCertificate = value; + } + private string m_ServerPrivate; + public string ServerPrivate + { + get => ReadFile(m_ServerPrivateFilePath, "Server Key"); + set => m_ServerPrivate = value; + } + + private static string ReadFile(string path, string label) + { + var reader = new StreamReader(path); + string fileContent = reader.ReadToEnd(); + Debug.Log((fileContent.Length > 1) ? ("Successfully loaded " + fileContent.Length + " byte(s) from " + label) : ("Could not read " + label + " file")); + return fileContent; + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/SecretsLoaderHelper.cs.meta b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/SecretsLoaderHelper.cs.meta new file mode 100644 index 0000000000..7ceb56263d --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/SecretsLoaderHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dc1e7a8dc597cf24c95e4acf92c0edf5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index dbbd8dd0c4..f5a31966d2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -15,6 +15,9 @@ using Unity.Networking.Transport; using Unity.Networking.Transport.Relay; using Unity.Networking.Transport.Utilities; +#if UTP_TRANSPORT_2_0_ABOVE +using Unity.Networking.Transport.TLS; +#endif #if !UTP_TRANSPORT_2_0_ABOVE using NetworkEndpoint = Unity.Networking.Transport.NetworkEndPoint; @@ -164,14 +167,26 @@ private enum State private ProtocolType m_ProtocolType; #if UTP_TRANSPORT_2_0_ABOVE - [Tooltip("Whether or not to use WebSockets as Network Interface")] + [Tooltip("Per default the client/server will communicate over UDP. Set to true to communicate with WebSocket.")] [SerializeField] private bool m_UseWebSockets = false; public bool UseWebSockets { - set => m_UseWebSockets = value; get => m_UseWebSockets; + set => m_UseWebSockets = value; + } + + /// + /// Per default the client/server communication will not be encrypted. Select true to enable DTLS for UDP and TLS for Websocket. + /// + [Tooltip("Per default the client/server communication will not be encrypted. Select true to enable DTLS for UDP and TLS for Websocket.")] + [SerializeField] + private bool m_UseEncryption = false; + public bool UseEncryption + { + get => m_UseEncryption; + set => m_UseEncryption = value; } #endif @@ -602,7 +617,7 @@ public void SetRelayServerData(string ipv4Address, ushort port, byte[] allocatio hostConnectionData = connectionData; } - m_RelayServerData = new RelayServerData(ref serverEndpoint, 1, ref allocationId, ref connectionData, ref hostConnectionData, ref key, isSecure); + m_RelayServerData = new RelayServerData(ref serverEndpoint, 0, ref allocationId, ref connectionData, ref hostConnectionData, ref key, isSecure); SetProtocol(ProtocolType.RelayUnityTransport); } @@ -1402,6 +1417,36 @@ private void ConfigureSimulatorForUtp1() } #endif + private FixedString4096Bytes m_ServerPrivate; + private FixedString4096Bytes m_ServerCertificate; + + private FixedString512Bytes m_ServerCommonName; + private FixedString4096Bytes m_ClientCertificate; + + public void SetServerSecrets(string serverCertificate, string serverPrivateKey) + { + if (serverPrivateKey.Length > m_ServerPrivate.Capacity || + serverCertificate.Length > m_ServerCertificate.Capacity) + { + throw new Exception("Secret lengths are above what Unity Transport allows."); + } + + m_ServerPrivate = serverPrivateKey; + m_ServerCertificate = serverCertificate; + } + + public void SetClientSecrets(string serverCommonName, string clientCertificate = null) + { + if (serverCommonName.Length > m_ServerCommonName.Capacity || + clientCertificate?.Length > m_ClientCertificate.Capacity) + { + throw new Exception("Secret lengths are above what Unity Transport allows."); + } + + m_ServerCommonName = serverCommonName; + m_ClientCertificate = clientCertificate; + } + /// /// Creates the internal NetworkDriver /// @@ -1435,6 +1480,63 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, #endif heartbeatTimeoutMS: transport.m_HeartbeatTimeoutMS); +#if UNITY_WEBGL && !UNITY_EDITOR + if (NetworkManager.IsServer) + { + throw new Exception("WebGL as a server is not supported by Unity Transport, outside the Editor."); + } +#endif + +#if UTP_TRANSPORT_2_0_ABOVE + if (m_UseEncryption) + { + if (m_ProtocolType == ProtocolType.RelayUnityTransport) + { + if (m_RelayServerData.IsSecure != 0) + { + // log an error because we have mismatched configuration + Debug.LogError("Mismatched security configuration, between Relay and local NetworkManager settings"); + } + else + { + if (m_UseWebSockets) + { + // Todo: new code to support Relay+WSS + throw new NotImplementedException(); + } + } + } + else + { + try + { + if (NetworkManager.IsServer) + { + if (m_ServerCertificate.Length == 0 || + m_ServerPrivate.Length == 0) + { + throw new Exception("In order to use encrypted communications, when hosting, you must set the server certificate and key."); + } + m_NetworkSettings.WithSecureServerParameters(certificate: ref m_ServerCertificate, + privateKey: ref m_ServerPrivate); + } + else + { + if (m_ServerCommonName.Length == 0) + { + throw new Exception("In order to use encrypted communications, clients must set the server common name."); + } + m_NetworkSettings.WithSecureClientParameters(serverName: ref m_ServerCommonName, caCertificate: ref m_ClientCertificate); + } + } + catch(Exception e) + { + Debug.LogException(e,this); + } + } + } +#endif + #if UTP_TRANSPORT_2_0_ABOVE if (m_UseWebSockets) { diff --git a/testproject/.gitignore b/testproject/.gitignore index 25ea7e8013..2800399634 100644 --- a/testproject/.gitignore +++ b/testproject/.gitignore @@ -72,6 +72,10 @@ crashlytics-build.properties /[Aa]ssets/[Ss]treamingAssets/BuildInfo.json /[Aa]ssets/[Ss]treamingAssets/BuildInfo.json.meta +# Secrets +*.pem +*.pem.meta + InitTestScene* boot.config diff --git a/testproject/Assets/Secure/generate.sh b/testproject/Assets/Secure/generate.sh new file mode 100755 index 0000000000..ac556a3d20 --- /dev/null +++ b/testproject/Assets/Secure/generate.sh @@ -0,0 +1,6 @@ +openssl genrsa -out clientPrivateKeyForRootCA.pem 2048 +openssl req -x509 -new -nodes -key clientPrivateKeyForRootCA.pem -sha256 -days 1095 -out myGameClientCA.pem +openssl genrsa -out myGameServerPrivate.pem 2048 +openssl req -new -key myGameServerPrivate.pem -out myGameServerCertificateSigningRequest.pem +openssl x509 -req -in myGameServerCertificateSigningRequest.pem -CA myGameClientCA.pem -CAkey clientPrivateKeyForRootCA.pem -CAcreateserial -out myGameServerCertificate.pem -days 365 -sha256 + From ba57f768c6c9526942cdc915d34385ac2f758592 Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Wed, 5 Oct 2022 04:07:25 -0400 Subject: [PATCH 083/138] fix: correctly rejecting NetworkList modifications on unauthorized clients (#2233) * fix: correctly rejecting NetworkList modifications on unauthorized clients. Adding tests for it. Allowing null IEnumerable to mean empty list in NetworkList --- .../Collections/NetworkList.cs | 44 +++- .../Tests/Runtime/OwnerPermissionTests.cs | 209 ++++++++++++++++++ .../Runtime/OwnerPermissionTests.cs.meta | 11 + 3 files changed, 262 insertions(+), 2 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/OwnerPermissionTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/OwnerPermissionTests.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index 78b6eede44..53824316aa 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -39,9 +39,13 @@ public NetworkList(IEnumerable values = default, NetworkVariableWritePermission writePerm = DefaultWritePerm) : base(readPerm, writePerm) { - foreach (var value in values) + // allow null IEnumerable to mean "no values" + if (values != null) { - m_List.Add(value); + foreach (var value in values) + { + m_List.Add(value); + } } } @@ -364,6 +368,12 @@ public IEnumerator GetEnumerator() /// public void Add(T item) { + // check write permissions + if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId)) + { + throw new InvalidOperationException("Client is not allowed to write to this NetworkList"); + } + m_List.Add(item); var listEvent = new NetworkListEvent() @@ -379,6 +389,12 @@ public void Add(T item) /// public void Clear() { + // check write permissions + if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId)) + { + throw new InvalidOperationException("Client is not allowed to write to this NetworkList"); + } + m_List.Clear(); var listEvent = new NetworkListEvent() @@ -399,6 +415,12 @@ public bool Contains(T item) /// public bool Remove(T item) { + // check write permissions + if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId)) + { + throw new InvalidOperationException("Client is not allowed to write to this NetworkList"); + } + int index = NativeArrayExtensions.IndexOf(m_List, item); if (index == -1) { @@ -428,6 +450,12 @@ public int IndexOf(T item) /// public void Insert(int index, T item) { + // check write permissions + if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId)) + { + throw new InvalidOperationException("Client is not allowed to write to this NetworkList"); + } + if (index < m_List.Length) { m_List.InsertRangeWithBeginEnd(index, index + 1); @@ -451,6 +479,12 @@ public void Insert(int index, T item) /// public void RemoveAt(int index) { + // check write permissions + if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId)) + { + throw new InvalidOperationException("Client is not allowed to write to this NetworkList"); + } + m_List.RemoveAt(index); var listEvent = new NetworkListEvent() @@ -468,6 +502,12 @@ public T this[int index] get => m_List[index]; set { + // check write permissions + if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId)) + { + throw new InvalidOperationException("Client is not allowed to write to this NetworkList"); + } + var previousValue = m_List[index]; m_List[index] = value; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/OwnerPermissionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/OwnerPermissionTests.cs new file mode 100644 index 0000000000..abe4355a97 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/OwnerPermissionTests.cs @@ -0,0 +1,209 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; + +namespace Unity.Netcode.RuntimeTests +{ + public class OwnerPermissionObject : NetworkBehaviour + { + // indexed by [object, machine] + public static OwnerPermissionObject[,] Objects = new OwnerPermissionObject[3, 3]; + public static int CurrentlySpawning = 0; + + public static List ClientTargetedNetworkObjects = new List(); + // a client-owned NetworkVariable + public NetworkVariable MyNetworkVariableOwner; + // a server-owned NetworkVariable + public NetworkVariable MyNetworkVariableServer; + + // a client-owned NetworkVariable + public NetworkList MyNetworkListOwner; + // a server-owned NetworkVariable + public NetworkList MyNetworkListServer; + + // verifies two lists are identical + public static void CheckLists(NetworkList listA, NetworkList listB) + { + Debug.Assert(listA.Count == listB.Count); + for (var i = 0; i < listA.Count; i++) + { + Debug.Assert(listA[i] == listB[i]); + } + } + + // verifies all objects have consistent lists on all clients + public static void VerifyConsistency() + { + for (var objectIndex = 0; objectIndex < 3; objectIndex++) + { + CheckLists(Objects[objectIndex, 0].MyNetworkListOwner, Objects[objectIndex, 1].MyNetworkListOwner); + CheckLists(Objects[objectIndex, 0].MyNetworkListOwner, Objects[objectIndex, 2].MyNetworkListOwner); + + CheckLists(Objects[objectIndex, 0].MyNetworkListServer, Objects[objectIndex, 1].MyNetworkListServer); + CheckLists(Objects[objectIndex, 0].MyNetworkListServer, Objects[objectIndex, 2].MyNetworkListServer); + } + } + + public override void OnNetworkSpawn() + { + Objects[CurrentlySpawning, NetworkManager.LocalClientId] = GetComponent(); + Debug.Log($"Object index ({CurrentlySpawning}) spawned on client {NetworkManager.LocalClientId}"); + } + + private void Awake() + { + MyNetworkVariableOwner = new NetworkVariable(writePerm: NetworkVariableWritePermission.Owner); + MyNetworkVariableOwner.OnValueChanged += OwnerChanged; + + MyNetworkVariableServer = new NetworkVariable(writePerm: NetworkVariableWritePermission.Server); + MyNetworkVariableServer.OnValueChanged += ServerChanged; + + MyNetworkListOwner = new NetworkList(writePerm: NetworkVariableWritePermission.Owner); + MyNetworkListOwner.OnListChanged += ListOwnerChanged; + + MyNetworkListServer = new NetworkList(writePerm: NetworkVariableWritePermission.Server); + MyNetworkListServer.OnListChanged += ListServerChanged; + } + + public void OwnerChanged(int before, int after) + { + } + + public void ServerChanged(int before, int after) + { + } + + public void ListOwnerChanged(NetworkListEvent listEvent) + { + } + + public void ListServerChanged(NetworkListEvent listEvent) + { + } + } + + public class OwnerPermissionHideTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 2; + + private GameObject m_PrefabToSpawn; + + protected override void OnServerAndClientsCreated() + { + m_PrefabToSpawn = CreateNetworkObjectPrefab("OwnerPermissionObject"); + m_PrefabToSpawn.AddComponent(); + } + + [UnityTest] + public IEnumerator OwnerPermissionTest() + { + // create 3 objects + for (var objectIndex = 0; objectIndex < 3; objectIndex++) + { + OwnerPermissionObject.CurrentlySpawning = objectIndex; + + NetworkManager ownerManager = m_ServerNetworkManager; + if (objectIndex != 0) + { + ownerManager = m_ClientNetworkManagers[objectIndex - 1]; + } + SpawnObject(m_PrefabToSpawn, ownerManager); + + // wait for each object to spawn on each client + for (var clientIndex = 0; clientIndex < 3; clientIndex++) + { + while (OwnerPermissionObject.Objects[objectIndex, clientIndex] == null) + { + yield return new WaitForSeconds(0.0f); + } + } + } + + var nextValueToWrite = 1; + var serverIndex = 0; + + for (var objectIndex = 0; objectIndex < 3; objectIndex++) + { + for (var clientWriting = 0; clientWriting < 3; clientWriting++) + { + // ==== Server-writable NetworkVariable ==== + var gotException = false; + Debug.Log($"Writing to server-write variable on object {objectIndex} on client {clientWriting}"); + + try + { + nextValueToWrite++; + OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkVariableServer.Value = nextValueToWrite; + } + catch (Exception) + { + gotException = true; + } + + // Verify server-owned netvar can only be written by server + Debug.Assert(gotException == (clientWriting != serverIndex)); + + // ==== Owner-writable NetworkVariable ==== + gotException = false; + Debug.Log($"Writing to owner-write variable on object {objectIndex} on client {clientWriting}"); + + try + { + nextValueToWrite++; + OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkVariableOwner.Value = nextValueToWrite; + } + catch (Exception) + { + gotException = true; + } + + // Verify client-owned netvar can only be written by owner + Debug.Assert(gotException == (clientWriting != objectIndex)); + + // ==== Server-writable NetworkList ==== + gotException = false; + Debug.Log($"Writing to server-write list on object {objectIndex} on client {clientWriting}"); + + try + { + nextValueToWrite++; + OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkListServer.Add(nextValueToWrite); + } + catch (Exception) + { + gotException = true; + } + + // Verify server-owned networkList can only be written by server + Debug.Assert(gotException == (clientWriting != serverIndex)); + + // ==== Owner-writable NetworkList ==== + gotException = false; + Debug.Log($"Writing to owner-write list on object {objectIndex} on client {clientWriting}"); + + try + { + nextValueToWrite++; + OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkListOwner.Add(nextValueToWrite); + } + catch (Exception) + { + gotException = true; + } + + // Verify client-owned networkList can only be written by owner + Debug.Assert(gotException == (clientWriting != objectIndex)); + + yield return NetcodeIntegrationTestHelpers.WaitForTicks(m_ServerNetworkManager, 5); + yield return NetcodeIntegrationTestHelpers.WaitForTicks(m_ClientNetworkManagers[0], 5); + yield return NetcodeIntegrationTestHelpers.WaitForTicks(m_ClientNetworkManagers[1], 5); + + OwnerPermissionObject.VerifyConsistency(); + } + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/OwnerPermissionTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/OwnerPermissionTests.cs.meta new file mode 100644 index 0000000000..a7d90d5c54 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/OwnerPermissionTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 88c657dcbe9a2414ba551b60dab19acd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 06bf800b1159073a78e2ebe6e87669cb49ccd6cc Mon Sep 17 00:00:00 2001 From: ashwini <36935028+ashwinimurt@users.noreply.github.com> Date: Wed, 5 Oct 2022 03:55:59 -0700 Subject: [PATCH 084/138] chore: Update to use UTP 2.0.0-exp.7 for compatibility test (#2234) --- .yamato/project.metafile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.yamato/project.metafile b/.yamato/project.metafile index ad351ee0ae..ef1ad1c4e6 100644 --- a/.yamato/project.metafile +++ b/.yamato/project.metafile @@ -69,7 +69,7 @@ projects: # Package dependencies dependencies: - name: com.unity.transport - version: 2.0.0-exp.6 + version: 2.0.0-exp.7 test_editors: - 2022.2 - trunk From ed7ea1c8f33ed46b36b353c6e209f9aae292d959 Mon Sep 17 00:00:00 2001 From: Gregory Kim <114025876+kiyoji23@users.noreply.github.com> Date: Wed, 5 Oct 2022 08:15:09 -0400 Subject: [PATCH 085/138] chore: Update UTP dependency to 1.3.0 (#2231) * chore: Bump UTP version to 1.3 * chore: Bump UTP version to 1.3 --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + com.unity.netcode.gameobjects/package.json | 2 +- minimalproject/Packages/packages-lock.json | 4 ++-- testproject-tools-integration/Packages/packages-lock.json | 4 ++-- testproject/Packages/packages-lock.json | 4 ++-- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index a8c49431c3..b3cb633fce 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -18,6 +18,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- Updated `UnityTransport` dependency on `com.unity.transport` to 1.3.0. (#2231) - The send queues of `UnityTransport` are now dynamically-sized. This means that there shouldn't be any need anymore to tweak the 'Max Send Queue Size' value. In fact, this field is now removed from the inspector and will not be serialized anymore. It is still possible to set it manually using the `MaxSendQueueSize` property, but it is not recommended to do so aside from some specific needs (e.g. limiting the amount of memory used by the send queues in very constrained environments). (#2212) - As a consequence of the above change, the `UnityTransport.InitialMaxSendQueueSize` field is now deprecated. There is no default value anymore since send queues are dynamically-sized. (#2212) - The debug simulator in `UnityTransport` is now non-deterministic. Its random number generator used to be seeded with a constant value, leading to the same pattern of packet drops, delays, and jitter in every run. (#2196) diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index 75c3b561cb..c1d7cb20a3 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -6,6 +6,6 @@ "unity": "2020.3", "dependencies": { "com.unity.nuget.mono-cecil": "1.10.1", - "com.unity.transport": "1.2.0" + "com.unity.transport": "1.3.0" } } \ No newline at end of file diff --git a/minimalproject/Packages/packages-lock.json b/minimalproject/Packages/packages-lock.json index c6004bd04b..374b614f94 100644 --- a/minimalproject/Packages/packages-lock.json +++ b/minimalproject/Packages/packages-lock.json @@ -39,7 +39,7 @@ "source": "local", "dependencies": { "com.unity.nuget.mono-cecil": "1.10.1", - "com.unity.transport": "1.2.0" + "com.unity.transport": "1.3.0" } }, "com.unity.nuget.mono-cecil": { @@ -61,7 +61,7 @@ "url": "https://packages.unity.com" }, "com.unity.transport": { - "version": "1.2.0", + "version": "1.3.0", "depth": 1, "source": "registry", "dependencies": { diff --git a/testproject-tools-integration/Packages/packages-lock.json b/testproject-tools-integration/Packages/packages-lock.json index f9ea6d11b7..3e638a78e8 100644 --- a/testproject-tools-integration/Packages/packages-lock.json +++ b/testproject-tools-integration/Packages/packages-lock.json @@ -61,7 +61,7 @@ "source": "local", "dependencies": { "com.unity.nuget.mono-cecil": "1.10.1", - "com.unity.transport": "1.2.0" + "com.unity.transport": "1.3.0" } }, "com.unity.nuget.mono-cecil": { @@ -107,7 +107,7 @@ "url": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates" }, "com.unity.transport": { - "version": "1.2.0", + "version": "1.3.0", "depth": 1, "source": "registry", "dependencies": { diff --git a/testproject/Packages/packages-lock.json b/testproject/Packages/packages-lock.json index b6a4d09171..0ab03fe88f 100644 --- a/testproject/Packages/packages-lock.json +++ b/testproject/Packages/packages-lock.json @@ -83,7 +83,7 @@ "source": "local", "dependencies": { "com.unity.nuget.mono-cecil": "1.10.1", - "com.unity.transport": "1.2.0" + "com.unity.transport": "1.3.0" } }, "com.unity.nuget.mono-cecil": { @@ -195,7 +195,7 @@ "url": "https://packages.unity.com" }, "com.unity.transport": { - "version": "1.2.0", + "version": "1.3.0", "depth": 1, "source": "registry", "dependencies": { From 94cd99ac5284d4ff95e6c0f921f2a536a852d2a7 Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Wed, 5 Oct 2022 13:06:23 -0400 Subject: [PATCH 086/138] feat: Add new SetRelayServerData method to UnityTransport (#2235) * feat: Add new SetRelayServerData method to UnityTransport * Don't pass server data by reference * Add PR number to CHANGELOG entry --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Transports/UTP/UnityTransport.cs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index b3cb633fce..2e86ec1d02 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -11,6 +11,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- `UnityTransport` now provides a way to set the Relay server data directly from the `RelayServerData` structure (provided by the Unity Transport package) throuh its `SetRelayServerData` method. This allows making use of the new APIs in UTP 1.3 that simplify integration of the Relay SDK. (#2235) - IPv6 is now supported for direct connections when using `UnityTransport`. (#2232) - Added WebSocket support when using UTP 2.0 with `UseWebSockets` property in the `UnityTransport` component of the `NetworkManager` allowing to pick WebSockets for communication. When building for WebGL, this selection happens automatically. (#2201) - Added position, rotation, and scale to the `ParentSyncMessage` which provides users the ability to specify the final values on the server-side when `OnNetworkObjectParentChanged` is invoked just before the message is created (when the `Transform` values are applied to the message). (#2146) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index f5a31966d2..816691e6f5 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -622,6 +622,14 @@ public void SetRelayServerData(string ipv4Address, ushort port, byte[] allocatio SetProtocol(ProtocolType.RelayUnityTransport); } + /// Set the relay server data (using the lower-level Unity Transport data structure). + /// Data for the Relay server to use. + public void SetRelayServerData(RelayServerData serverData) + { + m_RelayServerData = serverData; + SetProtocol(ProtocolType.RelayUnityTransport); + } + /// Set the relay server data for the host. /// IP address of the relay server. /// UDP port of the relay server. From 066622481d8657e741901b2d890bc22492625b29 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 5 Oct 2022 14:12:18 -0500 Subject: [PATCH 087/138] test: Parenting manual test updates for PR-2146 (#2207) This PR includes updates to the In-Scene Object Parenting manual test. --- testproject/Assets/Scenes/SampleScene.unity | 265 ++++++++---------- .../ChildObjectScript.cs | 123 ++++++++ ...ript.cs.meta => ChildObjectScript.cs.meta} | 0 .../InSceneNetworkObjectParentingTest.unity | 14 +- .../InSceneNetworkObjectToLoad.unity | 6 +- .../ParentObjectScript.cs | 94 ------- .../Tests/Manual/Scripts/RandomMovement.cs | 1 + 7 files changed, 256 insertions(+), 247 deletions(-) create mode 100644 testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ChildObjectScript.cs rename testproject/Assets/Tests/Manual/InSceneObjectParentingTests/{ParentObjectScript.cs.meta => ChildObjectScript.cs.meta} (100%) delete mode 100644 testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ParentObjectScript.cs diff --git a/testproject/Assets/Scenes/SampleScene.unity b/testproject/Assets/Scenes/SampleScene.unity index 6e816c9676..5a51eaedcc 100644 --- a/testproject/Assets/Scenes/SampleScene.unity +++ b/testproject/Assets/Scenes/SampleScene.unity @@ -152,6 +152,7 @@ Transform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: -10} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 830204877} m_RootOrder: 3 @@ -247,6 +248,7 @@ Transform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 3, z: -10} m_LocalScale: {x: 30, y: 12, z: 10} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 367170261} m_RootOrder: 2 @@ -292,6 +294,7 @@ RectTransform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 0.017954798, y: 0.017954798, z: 0.017954798} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 772203958} m_RootOrder: 0 @@ -311,8 +314,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} m_RaycastTarget: 1 @@ -369,6 +372,7 @@ Transform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 573144465} - {fileID: 841610171} @@ -489,6 +493,7 @@ Transform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 10, y: 0.5, z: 10} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 789733233} m_Father: {fileID: 0} @@ -504,8 +509,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: GlobalObjectIdHash: 3604669530 AlwaysReplicateAsRoot: 0 DontDestroyWithOwner: 0 @@ -520,8 +525,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 06b508cdcc0ba48ceaaf392863dcf743, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: GrabDistance: 5 --- !u!114 &402668306 MonoBehaviour: @@ -533,8 +538,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: e96cb6065543e43c4a752faaa1468eb1, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: SyncPositionX: 1 SyncPositionY: 1 SyncPositionZ: 1 @@ -549,7 +554,6 @@ MonoBehaviour: ScaleThreshold: 0 InLocalSpace: 0 Interpolate: 1 - FixedSendsPerSecond: 30 --- !u!1 &403665142 GameObject: m_ObjectHideFlags: 0 @@ -577,6 +581,7 @@ Transform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 3, z: 10} m_LocalScale: {x: 30, y: 12, z: 10} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 367170261} m_RootOrder: 3 @@ -623,6 +628,7 @@ Transform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: -10, y: 0, z: -10} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 830204877} m_RootOrder: 4 @@ -720,6 +726,7 @@ Transform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: -10, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 830204877} m_RootOrder: 0 @@ -815,6 +822,7 @@ Transform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: -10, y: 3, z: 0} m_LocalScale: {x: 10, y: 12, z: 10} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 367170261} m_RootOrder: 0 @@ -861,8 +869,8 @@ MonoBehaviour: m_Enabled: 0 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: b84c2d8dfe509a34fb59e2b81f8e1319, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: MessageBufferSize: 5120 MaxConnections: 100 MaxSentMessageQueueSize: 128 @@ -880,9 +888,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 593a2fe42fa9d37498c96f9a383b6521, type: 3} - m_Name: - m_EditorClassIdentifier: - DontDestroy: 1 + m_Name: + m_EditorClassIdentifier: RunInBackground: 1 LogLevel: 1 NetworkConfig: @@ -900,10 +907,9 @@ MonoBehaviour: TickRate: 30 ClientConnectionBufferTimeout: 10 ConnectionApproval: 0 - ConnectionData: + ConnectionData: EnableTimeResync: 0 TimeResyncInterval: 30 - EnableNetworkVariable: 1 EnsureNetworkVariableLengthSafety: 0 EnableSceneManagement: 1 ForceSamePrefabs: 1 @@ -923,6 +929,7 @@ Transform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 1} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} m_RootOrder: 2 @@ -937,15 +944,24 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 6960e84d07fb87f47956e7a81d71c4e6, type: 3} - m_Name: - m_EditorClassIdentifier: - m_ProtocolType: 1 - m_MessageBufferSize: 1024 - m_ReciveQueueSize: 128 - m_SendQueueSize: 128 - m_SendQueueBatchSize: 4096 - m_ServerAddress: 127.0.0.1 - m_ServerPort: 7777 + m_Name: + m_EditorClassIdentifier: + m_ProtocolType: 0 + m_MaxPacketQueueSize: 128 + m_MaxPayloadSize: 6144 + m_MaxSendQueueSize: 98304 + m_HeartbeatTimeoutMS: 500 + m_ConnectTimeoutMS: 1000 + m_MaxConnectAttempts: 60 + m_DisconnectTimeoutMS: 30000 + ConnectionData: + Address: 127.0.0.1 + Port: 7777 + ServerListenAddress: + DebugSimulator: + PacketDelayMS: 0 + PacketJitterMS: 0 + PacketDropRate: 0 --- !u!1001 &627808638 PrefabInstance: m_ObjectHideFlags: 0 @@ -1071,7 +1087,7 @@ PrefabInstance: - target: {fileID: 5266522511616468950, guid: 3200770c16e3b2b4ebe7f604154faac7, type: 3} propertyPath: m_SceneMenuToLoad - value: + value: objectReference: {fileID: 11400000, guid: c10d995498e0c514a853c3506031d3fb, type: 2} m_RemovedComponents: [] @@ -1089,7 +1105,7 @@ LightingSettings: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_Name: Settings.lighting - serializedVersion: 3 + serializedVersion: 4 m_GIWorkflowMode: 0 m_EnableBakedLightmaps: 1 m_EnableRealtimeLightmaps: 0 @@ -1102,7 +1118,7 @@ LightingSettings: m_LightmapMaxSize: 1024 m_BakeResolution: 40 m_Padding: 2 - m_TextureCompression: 1 + m_LightmapCompression: 3 m_AO: 0 m_AOMaxDistance: 1 m_CompAOExponent: 1 @@ -1143,6 +1159,7 @@ LightingSettings: m_PVRFilteringAtrousPositionSigmaDirect: 0.5 m_PVRFilteringAtrousPositionSigmaIndirect: 2 m_PVRFilteringAtrousPositionSigmaAO: 1 + m_PVRTiledBaking: 0 --- !u!1 &702051983 GameObject: m_ObjectHideFlags: 0 @@ -1222,6 +1239,7 @@ Transform: m_LocalRotation: {x: 0.5, y: 0, z: 0, w: 0.8660254} m_LocalPosition: {x: 0, y: 20, z: -16} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} m_RootOrder: 5 @@ -1315,6 +1333,7 @@ Transform: m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} m_LocalPosition: {x: 0, y: 3, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} m_RootOrder: 0 @@ -1348,6 +1367,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 354062835} m_Father: {fileID: 1397037324} @@ -1368,8 +1388,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_IgnoreReversedGraphics: 1 m_BlockingObjects: 0 m_BlockingMask: @@ -1385,8 +1405,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_UiScaleMode: 0 m_ReferencePixelsPerUnit: 100 m_ScaleFactor: 1 @@ -1448,6 +1468,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1278360217} m_Father: {fileID: 402668302} @@ -1468,8 +1489,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_IgnoreReversedGraphics: 1 m_BlockingObjects: 0 m_BlockingMask: @@ -1485,8 +1506,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_UiScaleMode: 0 m_ReferencePixelsPerUnit: 100 m_ScaleFactor: 1 @@ -1545,6 +1566,7 @@ Transform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 535968795} - {fileID: 878759702} @@ -1584,6 +1606,7 @@ Transform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 10, y: 3, z: 0} m_LocalScale: {x: 10, y: 12, z: 10} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 367170261} m_RootOrder: 1 @@ -1629,8 +1652,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_HorizontalAxis: Horizontal m_VerticalAxis: Vertical m_SubmitButton: Submit @@ -1648,8 +1671,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_FirstSelected: {fileID: 0} m_sendNavigationEvents: 1 m_DragThreshold: 10 @@ -1663,6 +1686,7 @@ Transform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} m_RootOrder: 4 @@ -1696,6 +1720,7 @@ Transform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 10, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 830204877} m_RootOrder: 1 @@ -1802,9 +1827,10 @@ Transform: m_LocalRotation: {x: 0, y: 0.7071068, z: 0, w: 0.7071068} m_LocalPosition: {x: 12.06, y: 0.68515396, z: -8.870045} m_LocalScale: {x: 0.42366, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 11 + m_RootOrder: 10 m_LocalEulerAnglesHint: {x: 0, y: 90, z: 0} --- !u!114 &963826004 MonoBehaviour: @@ -1816,8 +1842,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 53eaad6aa77ed43acb8e4293f21f95b9, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: --- !u!114 &963826005 MonoBehaviour: m_ObjectHideFlags: 0 @@ -1828,8 +1854,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: e96cb6065543e43c4a752faaa1468eb1, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: SyncPositionX: 1 SyncPositionY: 1 SyncPositionZ: 1 @@ -1844,7 +1870,6 @@ MonoBehaviour: ScaleThreshold: 0 InLocalSpace: 0 Interpolate: 1 - FixedSendsPerSecond: 30 --- !u!114 &963826006 MonoBehaviour: m_ObjectHideFlags: 0 @@ -1855,8 +1880,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: GlobalObjectIdHash: 3972363333 AlwaysReplicateAsRoot: 0 DontDestroyWithOwner: 0 @@ -1924,51 +1949,6 @@ MeshFilter: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 963826002} m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} ---- !u!1 &1202924672 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 1202924674} - - component: {fileID: 1202924673} - m_Layer: 0 - m_Name: UnityChanSpawner - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!114 &1202924673 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1202924672} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 05037a80244af4174806bc7f242e4432, type: 3} - m_Name: - m_EditorClassIdentifier: - UnityChanPrefab: {fileID: 8133991607019124060, guid: 421bcf732fe69486d8abecfa5eee63bb, - type: 3} ---- !u!4 &1202924674 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1202924672} - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: -10, y: -0.1, z: 10} - m_LocalScale: {x: 1, y: 1, z: 1} - m_Children: [] - m_Father: {fileID: 0} - m_RootOrder: 7 - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1237561005 GameObject: m_ObjectHideFlags: 0 @@ -1998,6 +1978,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1572276077} m_Father: {fileID: 1402467451} @@ -2018,8 +1999,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_IgnoreReversedGraphics: 1 m_BlockingObjects: 0 m_BlockingMask: @@ -2035,8 +2016,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_UiScaleMode: 0 m_ReferencePixelsPerUnit: 100 m_ScaleFactor: 1 @@ -2097,6 +2078,7 @@ RectTransform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 0.017954798, y: 0.017954798, z: 0.017954798} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 789733233} m_RootOrder: 0 @@ -2116,8 +2098,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} m_RaycastTarget: 1 @@ -2177,8 +2159,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_IgnoreReversedGraphics: 1 m_BlockingObjects: 0 m_BlockingMask: @@ -2194,8 +2176,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_UiScaleMode: 1 m_ReferencePixelsPerUnit: 100 m_ScaleFactor: 1 @@ -2238,6 +2220,7 @@ RectTransform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 0, y: 0, z: 0} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 627808639} - {fileID: 1536251758} @@ -2275,10 +2258,11 @@ Transform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 6.8895, y: 0.68515396, z: -4.0977} m_LocalScale: {x: 0.42366, y: 0.42366, z: 0.42366} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1475593094} m_Father: {fileID: 0} - m_RootOrder: 10 + m_RootOrder: 9 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1397037315 GameObject: @@ -2313,8 +2297,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 06b508cdcc0ba48ceaaf392863dcf743, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: GrabDistance: 5 --- !u!114 &1397037317 MonoBehaviour: @@ -2326,8 +2310,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: e96cb6065543e43c4a752faaa1468eb1, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: SyncPositionX: 1 SyncPositionY: 1 SyncPositionZ: 1 @@ -2342,7 +2326,6 @@ MonoBehaviour: ScaleThreshold: 0 InLocalSpace: 0 Interpolate: 1 - FixedSendsPerSecond: 30 --- !u!114 &1397037319 MonoBehaviour: m_ObjectHideFlags: 0 @@ -2353,8 +2336,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: GlobalObjectIdHash: 1445980162 AlwaysReplicateAsRoot: 0 DontDestroyWithOwner: 0 @@ -2448,10 +2431,11 @@ Transform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: -6.53, y: 0.5, z: 10.33} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 772203958} m_Father: {fileID: 0} - m_RootOrder: 8 + m_RootOrder: 7 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1398767341 GameObject: @@ -2482,6 +2466,7 @@ Transform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 10} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 830204877} m_RootOrder: 2 @@ -2583,8 +2568,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 06b508cdcc0ba48ceaaf392863dcf743, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: GrabDistance: 5 --- !u!114 &1402467445 MonoBehaviour: @@ -2596,23 +2581,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 54c9647dc784a46bca664910f182491e, type: 3} - m_Name: - m_EditorClassIdentifier: - SyncPositionX: 1 - SyncPositionY: 1 - SyncPositionZ: 1 - SyncRotAngleX: 1 - SyncRotAngleY: 1 - SyncRotAngleZ: 1 - SyncScaleX: 1 - SyncScaleY: 1 - SyncScaleZ: 1 - PositionThreshold: 0 - RotAngleThreshold: 0 - ScaleThreshold: 0 - InLocalSpace: 0 - Interpolate: 1 - FixedSendsPerSecond: 30 + m_Name: + m_EditorClassIdentifier: --- !u!114 &1402467446 MonoBehaviour: m_ObjectHideFlags: 0 @@ -2623,8 +2593,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: GlobalObjectIdHash: 1148320762 AlwaysReplicateAsRoot: 0 DontDestroyWithOwner: 0 @@ -2718,10 +2688,11 @@ Transform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0.77, y: 0.5, z: 8.64} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1237561006} m_Father: {fileID: 0} - m_RootOrder: 9 + m_RootOrder: 8 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1463459130 GameObject: @@ -2816,6 +2787,7 @@ Transform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1815329520} m_RootOrder: 0 @@ -2849,6 +2821,7 @@ Transform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 10, y: 0, z: -10} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 830204877} m_RootOrder: 5 @@ -2949,8 +2922,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 53eaad6aa77ed43acb8e4293f21f95b9, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: --- !u!65 &1475593091 BoxCollider: m_ObjectHideFlags: 0 @@ -3024,6 +2997,7 @@ Transform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 5.2176, y: 0, z: -11.264561} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1345111613} m_RootOrder: 0 @@ -3038,8 +3012,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: GlobalObjectIdHash: 2710131580 AlwaysReplicateAsRoot: 0 DontDestroyWithOwner: 0 @@ -3054,8 +3028,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: e96cb6065543e43c4a752faaa1468eb1, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: SyncPositionX: 1 SyncPositionY: 1 SyncPositionZ: 1 @@ -3070,7 +3044,6 @@ MonoBehaviour: ScaleThreshold: 0 InLocalSpace: 0 Interpolate: 1 - FixedSendsPerSecond: 30 --- !u!224 &1536251758 stripped RectTransform: m_CorrespondingSourceObject: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, @@ -3105,6 +3078,7 @@ RectTransform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 0.017954798, y: 0.017954798, z: 0.017954798} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1237561006} m_RootOrder: 0 @@ -3124,8 +3098,8 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} - m_Name: - m_EditorClassIdentifier: + m_Name: + m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} m_RaycastTarget: 1 @@ -3185,6 +3159,7 @@ Transform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 10, y: 0, z: 10} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 830204877} m_RootOrder: 7 @@ -3279,6 +3254,7 @@ Transform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: -0.1, z: 0} m_LocalScale: {x: 3, y: 1, z: 3} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1463459134} - {fileID: 830204877} @@ -3326,7 +3302,7 @@ PrefabInstance: - target: {fileID: 4850072633501053442, guid: d725b5588e1b956458798319e6541d84, type: 3} propertyPath: m_JoinCodeInput - value: + value: objectReference: {fileID: 889928732} - target: {fileID: 4850072633501053442, guid: d725b5588e1b956458798319e6541d84, type: 3} @@ -3474,6 +3450,7 @@ Transform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: -10, y: 0, z: 10} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 830204877} m_RootOrder: 6 diff --git a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ChildObjectScript.cs b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ChildObjectScript.cs new file mode 100644 index 0000000000..aa780a93fb --- /dev/null +++ b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ChildObjectScript.cs @@ -0,0 +1,123 @@ +using UnityEngine; +using Unity.Netcode; + +namespace TestProject.ManualTests +{ + public class ChildObjectScript : NetworkBehaviour + { + private bool m_WorldPositionStays; + private NetworkObject m_LastParent; + private Vector3 m_OriginalLocalPosition; + private Quaternion m_OriginalLocalRotation; + private Vector3 m_OriginalLocalScale; + + private void Update() + { + if (IsSpawned) + { + // Parents with WorldPositionStays set to true + if (Input.GetKeyDown(KeyCode.G)) + { + PickupItemServerRpc(); + } + + // Parents with WorldPositionStays set to false + if (Input.GetKeyDown(KeyCode.F)) + { + PickupItemServerRpc(false); + } + + // Drops the object using the last WorldPositionStays setting + if (Input.GetKeyDown(KeyCode.D)) + { + DropItemServerRpc(); + } + } + } + + public override void OnNetworkSpawn() + { + if (IsServer) + { + m_OriginalLocalPosition = transform.localPosition; + m_OriginalLocalRotation = transform.localRotation; + m_OriginalLocalScale = transform.localScale; + } + base.OnNetworkSpawn(); + } + + /// + /// + /// This is an example of how to adjust the child object's local space transform + /// values on the server side that will be synchronized with clients without the + /// need to have a NetworkTransform component attached to the child object. + /// + public override void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) + { + // If we are not spawned, world position is "staying", or we are not the server + // then don't do anything + if (!IsSpawned || m_WorldPositionStays || !IsServer) + { + return; + } + + if (parentNetworkObject != null) + { + // This preserves the original position offset of the object when parented with + // WorldPositionStays set to false. + transform.localPosition = m_OriginalLocalPosition; + + // Optionally, you can also make other modifications to rotation or scale + transform.localRotation = m_OriginalLocalRotation; + transform.localScale = m_OriginalLocalScale; + } + else + if (parentNetworkObject == null && m_LastParent) + { + // This example will drop the object at the current world position + transform.position = m_LastParent.transform.position; + } + + m_LastParent = parentNetworkObject; + base.OnNetworkObjectParentChanged(parentNetworkObject); + } + + private void PickUpDropItem(NetworkObject player, bool worldPositionStays = true) + { + if (transform.parent != null && player != null) + { + Debug.Log(transform.parent == player.transform ? $"{player.name} already picked up {name}!" : $"{name} cannot be picked up by {player.name}! It is already parented under another player!"); + return; + } + m_WorldPositionStays = worldPositionStays; + + NetworkObject.TrySetParent(player, worldPositionStays); + + Debug.Log(player == null ? $"{name} is no longer parented." : $"{name} is now parented under {player.name}!"); + } + + + [ServerRpc(RequireOwnership = false)] + public void PickupItemServerRpc(bool worldPositionStays = true, ServerRpcParams serverRpcParams = default) + { + if (NetworkManager.ConnectedClients.ContainsKey(serverRpcParams.Receive.SenderClientId)) + { + PickUpDropItem(NetworkManager.ConnectedClients[serverRpcParams.Receive.SenderClientId].PlayerObject, worldPositionStays); + } + } + + [ServerRpc(RequireOwnership = false)] + public void DropItemServerRpc(ServerRpcParams serverRpcParams = default) + { + if (NetworkManager.ConnectedClients.ContainsKey(serverRpcParams.Receive.SenderClientId)) + { + var player = NetworkManager.ConnectedClients[serverRpcParams.Receive.SenderClientId].PlayerObject; + if (transform.parent == player.transform) + { + // When dropping, we drop with whatever WorldPositionStays setting we picked up with + PickUpDropItem(null, m_WorldPositionStays); + } + } + } + } +} diff --git a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ParentObjectScript.cs.meta b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ChildObjectScript.cs.meta similarity index 100% rename from testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ParentObjectScript.cs.meta rename to testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ChildObjectScript.cs.meta diff --git a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneNetworkObjectParentingTest.unity b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneNetworkObjectParentingTest.unity index 05829ef97d..b289022966 100644 --- a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneNetworkObjectParentingTest.unity +++ b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneNetworkObjectParentingTest.unity @@ -439,7 +439,6 @@ MonoBehaviour: m_EditorClassIdentifier: m_ClientServerToggle: {fileID: 1950573270} m_TrackSceneEvents: 1 - m_LogSceneEventsToConsole: 1 --- !u!4 &294628599 Transform: m_ObjectHideFlags: 0 @@ -623,8 +622,8 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0} m_AnchorMax: {x: 0.5, y: 0} - m_AnchoredPosition: {x: 0, y: 17} - m_SizeDelta: {x: 600, y: 35} + m_AnchoredPosition: {x: 0, y: 16} + m_SizeDelta: {x: 800, y: 70} m_Pivot: {x: 0.5, y: 0} --- !u!114 &596094238 MonoBehaviour: @@ -648,7 +647,7 @@ MonoBehaviour: m_Calls: [] m_FontData: m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} - m_FontSize: 25 + m_FontSize: 22 m_FontStyle: 0 m_BestFit: 0 m_MinSize: 0 @@ -659,7 +658,10 @@ MonoBehaviour: m_HorizontalOverflow: 0 m_VerticalOverflow: 0 m_LineSpacing: 1 - m_Text: Press "G" to pickup and "D" to drop the object. + m_Text: 'Press "G" WorldPositionStays (true) pickup, "F" WorldPositionStays (false), + + and + "D" to drops the object at its current location.' --- !u!222 &596094239 CanvasRenderer: m_ObjectHideFlags: 0 @@ -1337,7 +1339,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_ActivateOnLoad: 0 - m_SceneToLoad: In-SceneNetworkObjectToLoad + m_SceneToLoad: InSceneNetworkObjectToLoad m_SceneAsset: {fileID: 102900000, guid: f06a8525a4b5200459f62905c29ce09e, type: 3} --- !u!114 &1153518163 MonoBehaviour: diff --git a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneNetworkObjectToLoad.unity b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneNetworkObjectToLoad.unity index ccac5465c5..67ae210370 100644 --- a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneNetworkObjectToLoad.unity +++ b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneNetworkObjectToLoad.unity @@ -218,7 +218,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: GlobalObjectIdHash: 3392012702 - AlwaysReplicateAsRoot: 1 + AlwaysReplicateAsRoot: 0 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 --- !u!4 &818376011 @@ -228,8 +228,8 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 818376006} - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 4.05, z: 0} + m_LocalRotation: {x: 0.70710677, y: 0, z: 0, w: 0.70710677} + m_LocalPosition: {x: 0, y: 4, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] diff --git a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ParentObjectScript.cs b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ParentObjectScript.cs deleted file mode 100644 index b257bcc2f2..0000000000 --- a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ParentObjectScript.cs +++ /dev/null @@ -1,94 +0,0 @@ -using UnityEngine; -using Unity.Netcode; - -namespace TestProject.ManualTests -{ - public class ParentObjectScript : NetworkBehaviour - { - private void Update() - { - if (IsSpawned) - { - if (Input.GetKeyDown(KeyCode.G)) - { - PickupItemServerRpc(); - } - if (Input.GetKeyDown(KeyCode.D)) - { - DropItemServerRpc(); - } - } - } - - public override void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) - { - if (parentNetworkObject != null) - { - transform.localPosition = Vector3.up * transform.localPosition.y; - } - - base.OnNetworkObjectParentChanged(parentNetworkObject); - } - - private void PickUpItem(NetworkObject player) - { - if (transform.parent == null) - { - transform.parent = player.transform; - - transform.localPosition = Vector3.up * transform.localPosition.y; - - Debug.Log($"{name} is now parented under {player.name}!"); - } - else - { - if (transform.parent == player.transform) - { - Debug.Log($"{player.name} already picked up {name}!"); - } - else - { - Debug.Log($"{name} cannot be picked up by {player.name} as it is already picked up by another player!"); - } - } - } - - private void DropItem(NetworkObject player) - { - if (transform.parent == player.transform) - { - transform.parent = null; - Debug.Log($"{name} is no longer parented."); - } - else - { - if (transform.parent == null) - { - Debug.Log($"{player.name} is not the parent of {name}!"); - } - else - { - Debug.Log($"{name} cannot be dropped by {player.name} as it is already picked up by another player!"); - } - } - } - - [ServerRpc(RequireOwnership = false)] - public void PickupItemServerRpc(ServerRpcParams serverRpcParams = default) - { - if (NetworkManager.ConnectedClients.ContainsKey(serverRpcParams.Receive.SenderClientId)) - { - PickUpItem(NetworkManager.ConnectedClients[serverRpcParams.Receive.SenderClientId].PlayerObject); - } - } - - [ServerRpc(RequireOwnership = false)] - public void DropItemServerRpc(ServerRpcParams serverRpcParams = default) - { - if (NetworkManager.ConnectedClients.ContainsKey(serverRpcParams.Receive.SenderClientId)) - { - DropItem(NetworkManager.ConnectedClients[serverRpcParams.Receive.SenderClientId].PlayerObject); - } - } - } -} diff --git a/testproject/Assets/Tests/Manual/Scripts/RandomMovement.cs b/testproject/Assets/Tests/Manual/Scripts/RandomMovement.cs index 27412684cd..74eeab89bf 100644 --- a/testproject/Assets/Tests/Manual/Scripts/RandomMovement.cs +++ b/testproject/Assets/Tests/Manual/Scripts/RandomMovement.cs @@ -74,6 +74,7 @@ private void FixedUpdate() position += (m_Direction * m_CurrentSpeed); position.y = yAxis; m_Rigidbody.position = Vector3.Lerp(m_Rigidbody.position, position, Time.fixedDeltaTime); + m_Rigidbody.rotation = Quaternion.LookRotation(m_Direction); } } } From 26e9ceca03ec23e7b75249e185e96a2d7e15eb92 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 5 Oct 2022 15:32:18 -0500 Subject: [PATCH 088/138] fix: NetworkAnimator rising RTT issue (#2236) * fix This fixes the issue where NetworkAnimator was not completely updated to the changes with AnimationMessage. NetworkAnimator will now only send states that have changed. Removed the check for AnimationMessage.AnimationStates being null as it will always be null when deserializing. Updated several comments for readability and clarity. * test adding additional checks to assure or AnimationStates list does not increase in size over time. --- .../Components/NetworkAnimator.cs | 169 +++++++++--------- .../Runtime/Animation/AnimatorTestHelper.cs | 10 ++ .../Runtime/Animation/NetworkAnimatorTests.cs | 18 ++ 3 files changed, 109 insertions(+), 88 deletions(-) diff --git a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs index c6089f0546..b8a21282c3 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs @@ -227,7 +227,7 @@ private void BuildDestinationToTransitionInfoTable() } /// - /// Creates the + /// Creates the TransitionStateInfoList table /// private void BuildTransitionStateInfoList() { @@ -305,7 +305,6 @@ public void OnBeforeSerialize() internal struct AnimationState : INetworkSerializable { - internal bool IsDirty; // Not to be serialized, used for processing the animation state internal bool HasBeenProcessed; internal int StateHash; @@ -392,56 +391,41 @@ internal struct AnimationMessage : INetworkSerializable // Not to be serialized, used for processing the animation message internal bool HasBeenProcessed; - // state hash per layer. if non-zero, then Play() this animation, skipping transitions + // This is preallocated/populated in OnNetworkSpawn for all instances in the event ownership or + // authority changes. When serializing, IsDirtyCount determines how many AnimationState entries + // should be serialized from the list. When deserializing the list is created and populated with + // only the number of AnimationStates received which is dictated by the deserialized IsDirtyCount. internal List AnimationStates; - /// - /// Resets all AnimationStates' IsDirty flag - /// - internal void ClearDirty() - { - if (AnimationStates == null) - { - return; - } - for (int i = 0; i < AnimationStates.Count; i++) - { - var animationState = AnimationStates[i]; - animationState.IsDirty = false; - AnimationStates[i] = animationState; - } - } + // Used to determine how many AnimationState entries we are sending or receiving + internal int IsDirtyCount; public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { + var animationState = new AnimationState(); if (serializer.IsReader) { - if (AnimationStates == null) - { - AnimationStates = new List(); - } - else if (AnimationStates.Count > 0) + AnimationStates = new List(); + + serializer.SerializeValue(ref IsDirtyCount); + // Since we create a new AnimationMessage when deserializing + // we need to create new animation states for each incoming + // AnimationState being updated + for (int i = 0; i < IsDirtyCount; i++) { - AnimationStates.Clear(); + animationState = new AnimationState(); + serializer.SerializeValue(ref animationState); + AnimationStates.Add(animationState); } } - var count = AnimationStates.Count; - serializer.SerializeValue(ref count); - - var animationState = new AnimationState(); - for (int i = 0; i < count; i++) + else { - if (serializer.IsWriter) + // When writing, only send the counted dirty animation states + serializer.SerializeValue(ref IsDirtyCount); + for (int i = 0; i < IsDirtyCount; i++) { - if (AnimationStates[i].IsDirty) - { - animationState = AnimationStates[i]; - } - } - serializer.SerializeNetworkSerializable(ref animationState); - if (serializer.IsReader) - { - AnimationStates.Add(animationState); + animationState = AnimationStates[i]; + serializer.SerializeNetworkSerializable(ref animationState); } } } @@ -563,7 +547,17 @@ public override void OnDestroy() private List m_ParametersToUpdate; private List m_ClientSendList; private ClientRpcParams m_ClientRpcParams; - private List m_AnimationMessageStates; + private AnimationMessage m_AnimationMessage; + + /// + /// Used for integration test to validate that the + /// AnimationMessage.AnimationStates remains the same + /// size as the layer count. + /// + internal AnimationMessage GetAnimationMessage() + { + return m_AnimationMessage; + } // Only used in Cleanup private NetworkManager m_CachedNetworkManager; @@ -590,17 +584,19 @@ public override void OnNetworkSpawn() m_CachedNetworkManager = NetworkManager; NetworkManager.OnClientConnectedCallback += OnClientConnectedCallback; } - - // !! Note !! - // Do not clear this list. We re-use the AnimationState entries - // initialized below - m_AnimationMessageStates = new List(); + // We initialize the m_AnimationMessage for all instances in the event that + // ownership or authority changes during runtime. + m_AnimationMessage = new AnimationMessage(); + m_AnimationMessage.AnimationStates = new List(); // Store off our current layer weights and create our animation // state entries per layer. for (int layer = 0; layer < m_Animator.layerCount; layer++) { - m_AnimationMessageStates.Add(new AnimationState()); + // We create an AnimationState per layer to preallocate the maximum + // number of possible AnimationState changes we could send in one + // AnimationMessage. + m_AnimationMessage.AnimationStates.Add(new AnimationState()); float layerWeightNow = m_Animator.GetLayerWeight(layer); if (layerWeightNow != m_LayerWeights[layer]) { @@ -677,11 +673,8 @@ internal void ServerSynchronizeNewPlayer(ulong playerId) } SendParametersUpdate(m_ClientRpcParams); - var animationMessage = new AnimationMessage - { - // Assign the existing m_AnimationMessageStates list - AnimationStates = m_AnimationMessageStates - }; + // Reset the dirty count before synchronizing the newly connected client with all layers + m_AnimationMessage.IsDirtyCount = 0; for (int layer = 0; layer < m_Animator.layerCount; layer++) { @@ -689,7 +682,10 @@ internal void ServerSynchronizeNewPlayer(ulong playerId) var stateHash = st.fullPathHash; var normalizedTime = st.normalizedTime; var isInTransition = m_Animator.IsInTransition(layer); - var animMsg = m_AnimationMessageStates[layer]; + + // Grab one of the available AnimationState entries so we can fill it with the current + // layer's animation state. + var animationState = m_AnimationMessage.AnimationStates[layer]; // Synchronizing transitions with trigger conditions for late joining clients is now // handled by cross fading between the late joining client's current layer's AnimationState @@ -723,25 +719,23 @@ internal void ServerSynchronizeNewPlayer(ulong playerId) var destinationInfo = m_DestinationStateToTransitioninfo[layer][nextState.shortNameHash]; stateHash = destinationInfo.OriginatingState; // Set the destination state to cross fade to from the originating state - animMsg.DestinationStateHash = destinationInfo.DestinationState; + animationState.DestinationStateHash = destinationInfo.DestinationState; } } } - animMsg.Transition = isInTransition; // The only time this could be set to true - animMsg.StateHash = stateHash; // When a transition, this is the originating/starting state - animMsg.NormalizedTime = normalizedTime; - animMsg.Layer = layer; - animMsg.Weight = m_LayerWeights[layer]; - animMsg.IsDirty = true; - m_AnimationMessageStates[layer] = animMsg; - } - if (animationMessage.AnimationStates.Count > 0) - { - // Server always send via client RPC - SendAnimStateClientRpc(animationMessage, m_ClientRpcParams); - animationMessage.ClearDirty(); + animationState.Transition = isInTransition; // The only time this could be set to true + animationState.StateHash = stateHash; // When a transition, this is the originating/starting state + animationState.NormalizedTime = normalizedTime; + animationState.Layer = layer; + animationState.Weight = m_LayerWeights[layer]; + + // Apply the changes + m_AnimationMessage.AnimationStates[layer] = animationState; } + // Send all animation states + m_AnimationMessage.IsDirtyCount = m_Animator.layerCount; + SendAnimStateClientRpc(m_AnimationMessage, m_ClientRpcParams); } /// @@ -779,13 +773,10 @@ internal void CheckForAnimatorChanges() int stateHash; float normalizedTime; - var animationMessage = new AnimationMessage - { - // Assign the existing m_AnimationMessageStates list - AnimationStates = m_AnimationMessageStates - }; + // Reset the dirty count before checking for AnimationState updates + m_AnimationMessage.IsDirtyCount = 0; - // This sends updates only if a layer's AnimationState changes + // This sends updates only if a layer's state has changed for (int layer = 0; layer < m_Animator.layerCount; layer++) { AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(layer); @@ -797,31 +788,33 @@ internal void CheckForAnimatorChanges() continue; } - var animationState = new AnimationState - { - IsDirty = true, - Transition = false, // Only used during synchronization - StateHash = stateHash, - NormalizedTime = normalizedTime, - Layer = layer, - Weight = m_LayerWeights[layer] - }; + // If we made it here, then we need to synchronize this layer's animation state. + // Get one of the preallocated AnimationState entries and populate it with the + // current layer's state. + var animationState = m_AnimationMessage.AnimationStates[m_AnimationMessage.IsDirtyCount]; + + animationState.Transition = false; // Only used during synchronization + animationState.StateHash = stateHash; + animationState.NormalizedTime = normalizedTime; + animationState.Layer = layer; + animationState.Weight = m_LayerWeights[layer]; - animationMessage.AnimationStates.Add(animationState); + // Apply the changes + m_AnimationMessage.AnimationStates[m_AnimationMessage.IsDirtyCount] = animationState; + m_AnimationMessage.IsDirtyCount++; } - // Make sure there is something to send - if (animationMessage.AnimationStates.Count > 0) + // Send an AnimationMessage only if there are dirty AnimationStates to send + if (m_AnimationMessage.IsDirtyCount > 0) { if (!IsServer && IsOwner) { - SendAnimStateServerRpc(animationMessage); + SendAnimStateServerRpc(m_AnimationMessage); } else { - SendAnimStateClientRpc(animationMessage); + SendAnimStateClientRpc(m_AnimationMessage); } - animationMessage.ClearDirty(); } } diff --git a/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs b/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs index fa721b4225..05403caf81 100644 --- a/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs +++ b/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs @@ -29,6 +29,11 @@ private void Awake() m_NetworkAnimator = GetComponent(); } + internal int GetAnimatorStateCount() + { + return m_NetworkAnimator.GetAnimationMessage().AnimationStates.Count; + } + public override void OnNetworkSpawn() { if (IsTriggerTest) @@ -117,6 +122,11 @@ public void SetTrigger(string name = "TestTrigger", bool monitorTrigger = false) } } + public void SetBool(string name, bool valueToSet) + { + m_Animator.SetBool(name, valueToSet); + } + private System.Collections.IEnumerator TriggerMonitor(string triggerName) { var triggerStatus = m_Animator.GetBool(triggerName); diff --git a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs index 8d5fd4f52c..ac74c1ec42 100644 --- a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs +++ b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs @@ -239,6 +239,7 @@ private bool WaitForClientsToInitialize() public IEnumerator TriggerUpdateTests([Values] OwnerShipMode ownerShipMode, [Values] AuthoritativeMode authoritativeMode) { CheckStateEnterCount.ResetTest(); + VerboseDebug($" ++++++++++++++++++ Trigger Test [{TriggerTest.Iteration}][{ownerShipMode}] Starting ++++++++++++++++++ "); TriggerTest.IsVerboseDebug = m_EnableVerboseDebug; AnimatorTestHelper.IsTriggerTest = m_EnableVerboseDebug; @@ -254,6 +255,10 @@ public IEnumerator TriggerUpdateTests([Values] OwnerShipMode ownerShipMode, [Val yield return WaitForConditionOrTimeOut(WaitForClientsToInitialize); AssertOnTimeout($"Timed out waiting for the client-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!"); var animatorTestHelper = ownerShipMode == OwnerShipMode.ClientOwner ? AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[0].LocalClientId] : AnimatorTestHelper.ServerSideInstance; + var layerCount = animatorTestHelper.GetAnimator().layerCount; + var animationStateCount = animatorTestHelper.GetAnimatorStateCount(); + + Assert.True(layerCount == animationStateCount, $"AnimationState count {animationStateCount} does not equal the layer count {layerCount}!"); if (authoritativeMode == AuthoritativeMode.ServerAuth) { animatorTestHelper = AnimatorTestHelper.ServerSideInstance; @@ -300,6 +305,19 @@ public IEnumerator TriggerUpdateTests([Values] OwnerShipMode ownerShipMode, [Val yield return WaitForConditionOrTimeOut(() => CheckStateEnterCount.AllStatesEnteredMatch(clientIdList)); AssertOnTimeout($"Timed out waiting for all states entered to match!"); + // Now, update some states for several seconds to assure the AnimationState count does not grow + var waitForSeconds = new WaitForSeconds(0.25f); + bool rotateToggle = true; + for (int i = 0; i < 10; i++) + { + animatorTestHelper.SetBool("Rotate", rotateToggle); + animatorTestHelper.SetTrigger("Pulse"); + animationStateCount = animatorTestHelper.GetAnimatorStateCount(); + Assert.True(layerCount == animationStateCount, $"AnimationState count {animationStateCount} does not equal the layer count {layerCount}!"); + yield return waitForSeconds; + rotateToggle = !rotateToggle; + } + AnimatorTestHelper.IsTriggerTest = false; VerboseDebug($" ------------------ Trigger Test [{TriggerTest.Iteration}][{ownerShipMode}] Stopping ------------------ "); } From c94e9bcbf2dd8425f95cdc63cce87c3c9d8234e5 Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Wed, 5 Oct 2022 17:54:26 -0400 Subject: [PATCH 089/138] refactor: Simplify construction of RelayServerData (#2237) --- .../Runtime/Transports/UTP/UnityTransport.cs | 68 ++----------------- 1 file changed, 5 insertions(+), 63 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index 816691e6f5..2a4c2cce10 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -544,46 +544,13 @@ private bool ServerBindAndListen(NetworkEndpoint endPoint) return true; } - private static RelayAllocationId ConvertFromAllocationIdBytes(byte[] allocationIdBytes) - { - unsafe - { - fixed (byte* ptr = allocationIdBytes) - { - return RelayAllocationId.FromBytePointer(ptr, allocationIdBytes.Length); - } - } - } - - private static RelayHMACKey ConvertFromHMAC(byte[] hmac) - { - unsafe - { - fixed (byte* ptr = hmac) - { - return RelayHMACKey.FromBytePointer(ptr, RelayHMACKey.k_Length); - } - } - } - - private static RelayConnectionData ConvertConnectionData(byte[] connectionData) - { - unsafe - { - fixed (byte* ptr = connectionData) - { - return RelayConnectionData.FromBytePointer(ptr, RelayConnectionData.k_Length); - } - } - } - private void SetProtocol(ProtocolType inProtocol) { m_ProtocolType = inProtocol; } /// Set the relay server data for the server. - /// IP address of the relay server. + /// IP address or hostname of the relay server. /// UDP port of the relay server. /// Allocation ID as a byte array. /// Allocation key as a byte array. @@ -592,33 +559,8 @@ private void SetProtocol(ProtocolType inProtocol) /// Whether the connection is secure (uses DTLS). public void SetRelayServerData(string ipv4Address, ushort port, byte[] allocationIdBytes, byte[] keyBytes, byte[] connectionDataBytes, byte[] hostConnectionDataBytes = null, bool isSecure = false) { - RelayConnectionData hostConnectionData; - - if (!NetworkEndpoint.TryParse(ipv4Address, port, out var serverEndpoint)) - { - Debug.LogError($"Invalid address {ipv4Address}:{port}"); - - // We set this to default to cause other checks to fail to state you need to call this - // function again. - m_RelayServerData = default; - return; - } - - var allocationId = ConvertFromAllocationIdBytes(allocationIdBytes); - var key = ConvertFromHMAC(keyBytes); - var connectionData = ConvertConnectionData(connectionDataBytes); - - if (hostConnectionDataBytes != null) - { - hostConnectionData = ConvertConnectionData(hostConnectionDataBytes); - } - else - { - hostConnectionData = connectionData; - } - - m_RelayServerData = new RelayServerData(ref serverEndpoint, 0, ref allocationId, ref connectionData, ref hostConnectionData, ref key, isSecure); - + var hostConnectionData = hostConnectionDataBytes ?? connectionDataBytes; + m_RelayServerData = new RelayServerData(ipv4Address, port, allocationIdBytes, connectionDataBytes, hostConnectionData, keyBytes, isSecure); SetProtocol(ProtocolType.RelayUnityTransport); } @@ -631,7 +573,7 @@ public void SetRelayServerData(RelayServerData serverData) } /// Set the relay server data for the host. - /// IP address of the relay server. + /// IP address or hostname of the relay server. /// UDP port of the relay server. /// Allocation ID as a byte array. /// Allocation key as a byte array. @@ -643,7 +585,7 @@ public void SetHostRelayData(string ipAddress, ushort port, byte[] allocationId, } /// Set the relay server data for the host. - /// IP address of the relay server. + /// IP address or hostname of the relay server. /// UDP port of the relay server. /// Allocation ID as a byte array. /// Allocation key as a byte array. From d1372b82aac599af4b79fd11b2da6ee900080f8e Mon Sep 17 00:00:00 2001 From: Kitty Draper <284434+ShadauxCat@users.noreply.github.com> Date: Thu, 6 Oct 2022 11:08:18 -0500 Subject: [PATCH 090/138] feat: Supporting managed types in NetworkVariable [MTT-4594] (#2219) * feat: Supporting managed types in NetworkVariable. Also changes NetworkVariable to deserialize INetworkSerializable types in-place - this is done to avoid having a GC alloc every time the value changes. There will only be a GC alloc if it receives an updated value and the current value is null. Managed types are supported in two ways: 1. By making them INetworkSerializable, or 2. By assigning the delegates UserNetworkVariableSerialization.WriteValue and UserNetworkVariableSerialization.ReadValue If a managed type is passed in without meeting one of those criteria (or any unsupported type), then an exception will be thrown the first time the NetworkVariable is read or written. That timing is to ensure no weird behavior with exceptions being thrown before user code is able to assign the delegates. Co-authored-by: Fatih Mar --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 + .../Editor/CodeGen/CodeGenHelpers.cs | 13 + .../Editor/CodeGen/NetworkBehaviourILPP.cs | 264 ++++++++++++ .../Collections/NetworkList.cs | 15 +- .../NetworkVariable/NetworkVariable.cs | 24 +- .../NetworkVariableSerialization.cs | 284 ++++++++----- .../Serialization/BufferSerializerReader.cs | 2 +- .../Runtime/Serialization/FastBufferReader.cs | 32 ++ .../Runtime/NetworkVariableHelper.cs | 4 +- .../NetworkVariableTestComponent.cs | 248 ++++++++++++ .../Tests/Runtime/NetworkVariableTests.cs | 379 +++++++++++++++++- testproject/Assets/Secure.meta | 8 + testproject/Assets/Secure/generate.sh.meta | 7 + .../ParentingInSceneObjectsTests.cs | 4 +- .../ParentingWorldPositionStaysTests.cs | 2 +- testproject/Packages/manifest.json | 16 +- testproject/Packages/packages-lock.json | 39 +- .../ProjectSettings/ProjectVersion.txt | 4 +- 18 files changed, 1178 insertions(+), 169 deletions(-) create mode 100644 testproject/Assets/Secure.meta create mode 100644 testproject/Assets/Secure/generate.sh.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 2e86ec1d02..d871452658 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -23,6 +23,8 @@ Additional documentation and release notes are available at [Multiplayer Documen - The send queues of `UnityTransport` are now dynamically-sized. This means that there shouldn't be any need anymore to tweak the 'Max Send Queue Size' value. In fact, this field is now removed from the inspector and will not be serialized anymore. It is still possible to set it manually using the `MaxSendQueueSize` property, but it is not recommended to do so aside from some specific needs (e.g. limiting the amount of memory used by the send queues in very constrained environments). (#2212) - As a consequence of the above change, the `UnityTransport.InitialMaxSendQueueSize` field is now deprecated. There is no default value anymore since send queues are dynamically-sized. (#2212) - The debug simulator in `UnityTransport` is now non-deterministic. Its random number generator used to be seeded with a constant value, leading to the same pattern of packet drops, delays, and jitter in every run. (#2196) +- `NetworkVariable<>` now supports managed `INetworkSerializable` types, as well as other managed types with serialization/deserialization delegates registered to `UserNetworkVariableSerialization.WriteValue` and `UserNetworkVariableSerialization.ReadValue` (#2219) +- `NetworkVariable<>` and `BufferSerializer` now deserialize `INetworkSerializable` types in-place, rather than constructing new ones. (#2219) ### Fixed diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs index 306d86edf8..d76aef52db 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs @@ -123,6 +123,19 @@ public static bool HasInterface(this TypeReference typeReference, string interfa try { var typeDef = typeReference.Resolve(); + // Note: this won't catch generics correctly. + // + // class Foo: IInterface {} + // class Bar: Foo {} + // + // Bar.HasInterface(IInterface) -> returns false even though it should be true. + // + // This can be fixed (see GetAllFieldsAndResolveGenerics() in NetworkBehaviourILPP to understand how) + // but right now we don't need that to work so it's left alone to reduce complexity + if (typeDef.BaseType.HasInterface(interfaceTypeFullName)) + { + return true; + } var typeFaces = typeDef.Interfaces; return typeFaces.Any(iface => iface.InterfaceType.FullName == interfaceTypeFullName); } diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index 0d71c0f016..09b0551832 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -73,6 +73,8 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) .Where(t => t.IsSubclassOf(CodeGenHelpers.NetworkBehaviour_FullName)) .ToList() .ForEach(b => ProcessNetworkBehaviour(b, compiledAssembly.Defines)); + + CreateNetworkVariableTypeInitializers(assemblyDefinition); } catch (Exception e) { @@ -105,6 +107,114 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics); } + private MethodDefinition GetOrCreateStaticConstructor(TypeDefinition typeDefinition) + { + var staticCtorMethodDef = typeDefinition.GetStaticConstructor(); + if (staticCtorMethodDef == null) + { + staticCtorMethodDef = new MethodDefinition( + ".cctor", // Static Constructor (constant-constructor) + MethodAttributes.HideBySig | + MethodAttributes.SpecialName | + MethodAttributes.RTSpecialName | + MethodAttributes.Static, + typeDefinition.Module.TypeSystem.Void); + staticCtorMethodDef.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); + typeDefinition.Methods.Add(staticCtorMethodDef); + } + + return staticCtorMethodDef; + } + + private bool IsMemcpyableType(TypeReference type) + { + foreach (var supportedType in BaseSupportedTypes) + { + if (type.FullName == supportedType.FullName) + { + return true; + } + } + + return false; + } + + private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) + { + foreach (var typeDefinition in assembly.MainModule.Types) + { + if (typeDefinition.FullName == "") + { + var staticCtorMethodDef = GetOrCreateStaticConstructor(typeDefinition); + + var processor = staticCtorMethodDef.Body.GetILProcessor(); + + var instructions = new List(); + + foreach (var type in m_WrappedNetworkVariableTypes) + { + // If a serializable type isn't found, FallbackSerializer will be used automatically, which will + // call into UserNetworkVariableSerialization, giving the user a chance to define their own serializaiton + // for types that aren't in our official supported types list. + GenericInstanceMethod serializeMethod = null; + GenericInstanceMethod equalityMethod; + + if (type.IsValueType) + { + if (type.HasInterface(typeof(INetworkSerializeByMemcpy).FullName) || type.Resolve().IsEnum || IsMemcpyableType(type)) + { + serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpy_MethodRef); + } + else if (type.HasInterface(typeof(INetworkSerializable).FullName)) + { + serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializable_MethodRef); + } + else if (type.HasInterface(CodeGenHelpers.IUTF8Bytes_FullName) && type.HasInterface(k_INativeListBool_FullName)) + { + serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_FixedString_MethodRef); + } + + if (type.HasInterface(typeof(IEquatable<>).FullName + "<" + type.FullName + ">")) + { + equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatable_MethodRef); + } + else + { + equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef); + } + } + else + { + if (type.HasInterface(typeof(INetworkSerializable).FullName)) + { + serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_ManagedINetworkSerializable_MethodRef); + } + + if (type.HasInterface(typeof(IEquatable<>).FullName + "<" + type.FullName + ">")) + { + equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedIEquatable_MethodRef); + } + else + { + equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef); + } + } + + if (serializeMethod != null) + { + serializeMethod.GenericArguments.Add(type); + instructions.Add(processor.Create(OpCodes.Call, m_MainModule.ImportReference(serializeMethod))); + } + equalityMethod.GenericArguments.Add(type); + instructions.Add(processor.Create(OpCodes.Call, m_MainModule.ImportReference(equalityMethod))); + } + + instructions.ForEach(instruction => processor.Body.Instructions.Insert(processor.Body.Instructions.Count - 1, instruction)); + break; + } + } + } + private ModuleDefinition m_MainModule; private ModuleDefinition m_UnityModule; private ModuleDefinition m_NetcodeModule; @@ -138,6 +248,14 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) private FieldReference m_ServerRpcParams_Receive_FieldRef; private FieldReference m_ServerRpcParams_Receive_SenderClientId_FieldRef; private TypeReference m_ClientRpcParams_TypeRef; + private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpy_MethodRef; + private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializable_MethodRef; + private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_ManagedINetworkSerializable_MethodRef; + private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_FixedString_MethodRef; + private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedIEquatable_MethodRef; + private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatable_MethodRef; + private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef; + private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef; private TypeReference m_FastBufferWriter_TypeRef; private readonly Dictionary m_FastBufferWriter_WriteValue_MethodRefs = new Dictionary(); @@ -147,6 +265,35 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) private readonly Dictionary m_FastBufferReader_ReadValue_MethodRefs = new Dictionary(); private readonly List m_FastBufferReader_ExtensionMethodRefs = new List(); + private HashSet m_WrappedNetworkVariableTypes = new HashSet(); + + internal static readonly Type[] BaseSupportedTypes = new[] + { + typeof(bool), + typeof(byte), + typeof(sbyte), + typeof(char), + typeof(decimal), + typeof(double), + typeof(float), + typeof(int), + typeof(uint), + typeof(long), + typeof(ulong), + typeof(short), + typeof(ushort), + typeof(Vector2), + typeof(Vector3), + typeof(Vector2Int), + typeof(Vector3Int), + typeof(Vector4), + typeof(Quaternion), + typeof(Color), + typeof(Color32), + typeof(Ray), + typeof(Ray2D) + }; + private const string k_Debug_LogError = nameof(Debug.LogError); private const string k_NetworkManager_LocalClientId = nameof(NetworkManager.LocalClientId); private const string k_NetworkManager_IsListening = nameof(NetworkManager.IsListening); @@ -172,6 +319,9 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) private const string k_ServerRpcParams_Receive = nameof(ServerRpcParams.Receive); private const string k_ServerRpcReceiveParams_SenderClientId = nameof(ServerRpcReceiveParams.SenderClientId); + // CodeGen cannot reference the collections assembly to do a typeof() on it due to a bug that causes that to crash. + private const string k_INativeListBool_FullName = "Unity.Collections.INativeList`1"; + private bool ImportReferences(ModuleDefinition moduleDefinition) { TypeDefinition debugTypeDef = null; @@ -192,6 +342,7 @@ private bool ImportReferences(ModuleDefinition moduleDefinition) TypeDefinition clientRpcParamsTypeDef = null; TypeDefinition fastBufferWriterTypeDef = null; TypeDefinition fastBufferReaderTypeDef = null; + TypeDefinition networkVariableSerializationTypesTypeDef = null; foreach (var netcodeTypeDef in m_NetcodeModule.GetAllTypes()) { if (networkManagerTypeDef == null && netcodeTypeDef.Name == nameof(NetworkManager)) @@ -241,6 +392,12 @@ private bool ImportReferences(ModuleDefinition moduleDefinition) fastBufferReaderTypeDef = netcodeTypeDef; continue; } + + if (networkVariableSerializationTypesTypeDef == null && netcodeTypeDef.Name == nameof(NetworkVariableSerializationTypes)) + { + networkVariableSerializationTypesTypeDef = netcodeTypeDef; + continue; + } } foreach (var methodDef in debugTypeDef.Methods) @@ -459,9 +616,94 @@ private bool ImportReferences(ModuleDefinition moduleDefinition) } } + foreach (var method in networkVariableSerializationTypesTypeDef.Methods) + { + if (!method.IsStatic) + { + continue; + } + + switch (method.Name) + { + case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedByMemcpy): + m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpy_MethodRef = method; + break; + case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedINetworkSerializable): + m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializable_MethodRef = method; + break; + case nameof(NetworkVariableSerializationTypes.InitializeSerializer_ManagedINetworkSerializable): + m_NetworkVariableSerializationTypes_InitializeSerializer_ManagedINetworkSerializable_MethodRef = method; + break; + case nameof(NetworkVariableSerializationTypes.InitializeSerializer_FixedString): + m_NetworkVariableSerializationTypes_InitializeSerializer_FixedString_MethodRef = method; + break; + case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_ManagedIEquatable): + m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedIEquatable_MethodRef = method; + break; + case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedIEquatable): + m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatable_MethodRef = method; + break; + case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedValueEquals): + m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef = method; + break; + case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_ManagedClassEquals): + m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef = method; + break; + } + } + return true; } + // This gets all fields from this type as well as any parent types, up to (but not including) the base NetworkBehaviour class + // Importantly... this also resolves any generics, so if the base class is Foo and contains a field of NetworkVariable, + // and this class is Bar : Foo, it will properly resolve NetworkVariable to NetworkVariable. + private void GetAllFieldsAndResolveGenerics(TypeDefinition type, ref List fieldTypes, Dictionary genericParameters = null) + { + foreach (var field in type.Fields) + { + if (field.FieldType.IsGenericInstance) + { + var genericType = (GenericInstanceType)field.FieldType; + var newGenericType = new GenericInstanceType(field.FieldType.Resolve()); + for (var i = 0; i < genericType.GenericArguments.Count; ++i) + { + var argument = genericType.GenericArguments[i]; + + if (genericParameters != null && genericParameters.ContainsKey(argument.Name)) + { + newGenericType.GenericArguments.Add(genericParameters[argument.Name]); + } + else + { + newGenericType.GenericArguments.Add(argument); + } + } + fieldTypes.Add(newGenericType); + } + else + { + fieldTypes.Add(field.FieldType); + } + } + + if (type.BaseType == null || type.BaseType.Name == nameof(NetworkBehaviour)) + { + return; + } + var genericParams = new Dictionary(); + var resolved = type.BaseType.Resolve(); + if (type.BaseType.IsGenericInstance) + { + var genericType = (GenericInstanceType)type.BaseType; + for (var i = 0; i < genericType.GenericArguments.Count; ++i) + { + genericParams[resolved.GenericParameters[i].Name] = genericType.GenericArguments[i]; + } + } + GetAllFieldsAndResolveGenerics(resolved, ref fieldTypes, genericParams); + } + private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] assemblyDefines) { var rpcHandlers = new List<(uint RpcMethodId, MethodDefinition RpcHandler)>(); @@ -504,6 +746,28 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass } } + if (!typeDefinition.HasGenericParameters && !typeDefinition.IsGenericInstance) + { + var fieldTypes = new List(); + GetAllFieldsAndResolveGenerics(typeDefinition, ref fieldTypes); + foreach (var type in fieldTypes) + { + //var type = field.FieldType; + if (type.IsGenericInstance) + { + if (type.Resolve().Name == typeof(NetworkVariable<>).Name) + { + var genericInstanceType = (GenericInstanceType)type; + var wrappedType = genericInstanceType.GenericArguments[0]; + if (!m_WrappedNetworkVariableTypes.Contains(wrappedType)) + { + m_WrappedNetworkVariableTypes.Add(wrappedType); + } + } + } + } + } + if (rpcHandlers.Count > 0 || rpcNames.Count > 0) { var staticCtorMethodDef = typeDefinition.GetStaticConstructor(); diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index 53824316aa..e893db75a5 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -151,7 +151,8 @@ public override void ReadField(FastBufferReader reader) reader.ReadValueSafe(out ushort count); for (int i = 0; i < count; i++) { - NetworkVariableSerialization.Read(reader, out T value); + var value = new T(); + NetworkVariableSerialization.Read(reader, ref value); m_List.Add(value); } } @@ -167,7 +168,8 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) { case NetworkListEvent.EventType.Add: { - NetworkVariableSerialization.Read(reader, out T value); + var value = new T(); + NetworkVariableSerialization.Read(reader, ref value); m_List.Add(value); if (OnListChanged != null) @@ -195,7 +197,8 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) case NetworkListEvent.EventType.Insert: { reader.ReadValueSafe(out int index); - NetworkVariableSerialization.Read(reader, out T value); + var value = new T(); + NetworkVariableSerialization.Read(reader, ref value); if (index < m_List.Length) { @@ -231,7 +234,8 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) break; case NetworkListEvent.EventType.Remove: { - NetworkVariableSerialization.Read(reader, out T value); + var value = new T(); + NetworkVariableSerialization.Read(reader, ref value); int index = m_List.IndexOf(value); if (index == -1) { @@ -293,7 +297,8 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) case NetworkListEvent.EventType.Value: { reader.ReadValueSafe(out int index); - NetworkVariableSerialization.Read(reader, out T value); + var value = new T(); + NetworkVariableSerialization.Read(reader, ref value); if (index >= m_List.Length) { throw new Exception("Shouldn't be here, index is higher than list length"); diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs index 698fe01c05..5f62721e58 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs @@ -1,7 +1,5 @@ using UnityEngine; using System; -using System.Runtime.CompilerServices; -using Unity.Collections.LowLevel.Unsafe; namespace Unity.Netcode { @@ -10,7 +8,7 @@ namespace Unity.Netcode /// /// the unmanaged type for [Serializable] - public class NetworkVariable : NetworkVariableBase where T : unmanaged + public class NetworkVariable : NetworkVariableBase { /// /// Delegate type for value changed event @@ -52,7 +50,7 @@ public virtual T Value set { // Compare bitwise - if (ValueEquals(ref m_InternalValue, ref value)) + if (NetworkVariableSerialization.AreEqual(ref m_InternalValue, ref value)) { return; } @@ -66,20 +64,6 @@ public virtual T Value } } - // Compares two values of the same unmanaged type by underlying memory - // Ignoring any overridden value checks - // Size is fixed - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe bool ValueEquals(ref T a, ref T b) - { - // get unmanaged pointers - var aptr = UnsafeUtility.AddressOf(ref a); - var bptr = UnsafeUtility.AddressOf(ref b); - - // compare addresses - return UnsafeUtility.MemCmp(aptr, bptr, sizeof(T)) == 0; - } - /// /// Sets the , marks the dirty, and invokes the callback /// if there are subscribers to that event. @@ -115,7 +99,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) // would be stored in different fields T previousValue = m_InternalValue; - NetworkVariableSerialization.Read(reader, out m_InternalValue); + NetworkVariableSerialization.Read(reader, ref m_InternalValue); if (keepDirtyDelta) { @@ -128,7 +112,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) /// public override void ReadField(FastBufferReader reader) { - NetworkVariableSerialization.Read(reader, out m_InternalValue); + NetworkVariableSerialization.Read(reader, ref m_InternalValue); } /// diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs index cb3633f700..83c4de2de7 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs @@ -1,7 +1,6 @@ using System; -using System.Collections.Generic; using Unity.Collections; -using UnityEngine; +using Unity.Collections.LowLevel.Unsafe; namespace Unity.Netcode { @@ -18,7 +17,7 @@ internal interface INetworkVariableSerializer // Taking T as an in parameter like we do in other places would require making a copy // of it to pass it as a ref parameter. public void Write(FastBufferWriter writer, ref T value); - public void Read(FastBufferReader reader, out T value); + public void Read(FastBufferReader reader, ref T value); } /// @@ -35,79 +34,80 @@ public void Write(FastBufferWriter writer, ref T value) { writer.WriteUnmanagedSafe(value); } - public void Read(FastBufferReader reader, out T value) + public void Read(FastBufferReader reader, ref T value) { reader.ReadUnmanagedSafe(out value); } } /// - /// Serializer for FixedStrings, which does the same thing FastBufferWriter/FastBufferReader do, - /// but is implemented to get the data it needs using open instance delegates that are passed in - /// via reflection. This prevents needing T to meet any interface requirements (which isn't achievable - /// without incurring GC allocs on every call to Write or Read - reflection + Open Instance Delegates - /// circumvent that.) - /// - /// Tests show that calling these delegates doesn't cause any GC allocations even though they're - /// obtained via reflection and Delegate.CreateDelegate() and called on types that, at compile time, - /// aren't known to actually contain those methods. + /// Serializer for FixedStrings /// /// - internal class FixedStringSerializer : INetworkVariableSerializer where T : unmanaged + internal class FixedStringSerializer : INetworkVariableSerializer where T : unmanaged, INativeList, IUTF8Bytes { - internal delegate int GetLengthDelegate(ref T value); - internal delegate void SetLengthDelegate(ref T value, int length); - internal unsafe delegate byte* GetUnsafePtrDelegate(ref T value); - - internal GetLengthDelegate GetLength; - internal SetLengthDelegate SetLength; - internal GetUnsafePtrDelegate GetUnsafePtr; - - public unsafe void Write(FastBufferWriter writer, ref T value) + public void Write(FastBufferWriter writer, ref T value) { - int length = GetLength(ref value); - byte* data = GetUnsafePtr(ref value); - writer.WriteUnmanagedSafe(length); - writer.WriteBytesSafe(data, length); + writer.WriteValueSafe(value); } - public unsafe void Read(FastBufferReader reader, out T value) + public void Read(FastBufferReader reader, ref T value) { - value = new T(); - reader.ReadValueSafe(out int length); - SetLength(ref value, length); - reader.ReadBytesSafe(GetUnsafePtr(ref value), length); + reader.ReadValueSafeInPlace(ref value); } } /// - /// Serializer for INetworkSerializable types, which does the same thing - /// FastBufferWriter/FastBufferReader do, but is implemented to call the NetworkSerialize() method - /// via open instance delegates passed in via reflection. This prevents needing T to meet any interface - /// requirements (which isn't achievable without incurring GC allocs on every call to Write or Read - - /// reflection + Open Instance Delegates circumvent that.) - /// - /// Tests show that calling these delegates doesn't cause any GC allocations even though they're - /// obtained via reflection and Delegate.CreateDelegate() and called on types that, at compile time, - /// aren't known to actually contain those methods. + /// Serializer for unmanaged INetworkSerializable types /// /// - internal class NetworkSerializableSerializer : INetworkVariableSerializer where T : unmanaged + internal class UnmanagedNetworkSerializableSerializer : INetworkVariableSerializer where T : unmanaged, INetworkSerializable { - internal delegate void WriteValueDelegate(ref T value, BufferSerializer serializer); - internal delegate void ReadValueDelegate(ref T value, BufferSerializer serializer); + public void Write(FastBufferWriter writer, ref T value) + { + var bufferSerializer = new BufferSerializer(new BufferSerializerWriter(writer)); + value.NetworkSerialize(bufferSerializer); + } + public void Read(FastBufferReader reader, ref T value) + { + var bufferSerializer = new BufferSerializer(new BufferSerializerReader(reader)); + value.NetworkSerialize(bufferSerializer); - internal WriteValueDelegate WriteValue; - internal ReadValueDelegate ReadValue; + } + } + + /// + /// Serializer for managed INetworkSerializable types, which differs from the unmanaged implementation in that it + /// has to be null-aware + /// + internal class ManagedNetworkSerializableSerializer : INetworkVariableSerializer where T : class, INetworkSerializable, new() + { public void Write(FastBufferWriter writer, ref T value) { var bufferSerializer = new BufferSerializer(new BufferSerializerWriter(writer)); - WriteValue(ref value, bufferSerializer); + bool isNull = (value == null); + bufferSerializer.SerializeValue(ref isNull); + if (!isNull) + { + value.NetworkSerialize(bufferSerializer); + } } - public void Read(FastBufferReader reader, out T value) + public void Read(FastBufferReader reader, ref T value) { - value = new T(); var bufferSerializer = new BufferSerializer(new BufferSerializerReader(reader)); - ReadValue(ref value, bufferSerializer); + bool isNull = false; + bufferSerializer.SerializeValue(ref isNull); + if (isNull) + { + value = null; + } + else + { + if (value == null) + { + value = new T(); + } + value.NetworkSerialize(bufferSerializer); + } } } @@ -164,7 +164,7 @@ public void Write(FastBufferWriter writer, ref T value) } UserNetworkVariableSerialization.WriteValue(writer, value); } - public void Read(FastBufferReader reader, out T value) + public void Read(FastBufferReader reader, ref T value) { if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null) { @@ -174,34 +174,95 @@ public void Read(FastBufferReader reader, out T value) } } - internal static class NetworkVariableSerializationTypes + /// + /// This class contains initialization functions for various different types used in NetworkVariables. + /// Generally speaking, these methods are called by a module initializer created by codegen (NetworkBehaviourILPP) + /// and do not need to be called manually. + /// + /// There are two types of initializers: Serializers and EqualityCheckers. Every type must have an EqualityChecker + /// registered to it in order to be used in NetworkVariable; however, not all types need a Serializer. Types without + /// a serializer registered will fall back to using the delegates in . + /// If no such delegate has been registered, a type without a serializer will throw an exception on the first attempt + /// to serialize or deserialize it. (Again, however, codegen handles this automatically and this registration doesn't + /// typically need to be performed manually.) + /// + public static class NetworkVariableSerializationTypes { - internal static readonly HashSet BaseSupportedTypes = new HashSet + /// + /// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer + /// + /// + public static void InitializeSerializer_UnmanagedByMemcpy() where T : unmanaged + { + NetworkVariableSerialization.Serializer = new UnmanagedTypeSerializer(); + } + + /// + /// Registers an unmanaged type that implements INetworkSerializable and will be serialized through a call to + /// NetworkSerialize + /// + /// + public static void InitializeSerializer_UnmanagedINetworkSerializable() where T : unmanaged, INetworkSerializable + { + NetworkVariableSerialization.Serializer = new UnmanagedNetworkSerializableSerializer(); + } + + /// + /// Registers a managed type that implements INetworkSerializable and will be serialized through a call to + /// NetworkSerialize + /// + /// + public static void InitializeSerializer_ManagedINetworkSerializable() where T : class, INetworkSerializable, new() + { + NetworkVariableSerialization.Serializer = new ManagedNetworkSerializableSerializer(); + } + + /// + /// Registers a FixedString type that will be serialized through FastBufferReader/FastBufferWriter's FixedString + /// serializers + /// + /// + public static void InitializeSerializer_FixedString() where T : unmanaged, INativeList, IUTF8Bytes + { + NetworkVariableSerialization.Serializer = new FixedStringSerializer(); + } + + /// + /// Registers a managed type that will be checked for equality using T.Equals() + /// + /// + public static void InitializeEqualityChecker_ManagedIEquatable() where T : class, IEquatable + { + NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.EqualityEqualsObject; + } + + /// + /// Registers an unmanaged type that will be checked for equality using T.Equals() + /// + /// + public static void InitializeEqualityChecker_UnmanagedIEquatable() where T : unmanaged, IEquatable + { + NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.EqualityEquals; + } + + /// + /// Registers an unmanaged type that will be checked for equality using memcmp and only considered + /// equal if they are bitwise equivalent in memory + /// + /// + public static void InitializeEqualityChecker_UnmanagedValueEquals() where T : unmanaged + { + NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; + } + + /// + /// Registers a managed type that will be checked for equality using the == operator + /// + /// + public static void InitializeEqualityChecker_ManagedClassEquals() where T : class { - typeof(bool), - typeof(byte), - typeof(sbyte), - typeof(char), - typeof(decimal), - typeof(double), - typeof(float), - typeof(int), - typeof(uint), - typeof(long), - typeof(ulong), - typeof(short), - typeof(ushort), - typeof(Vector2), - typeof(Vector3), - typeof(Vector2Int), - typeof(Vector3Int), - typeof(Vector4), - typeof(Quaternion), - typeof(Color), - typeof(Color32), - typeof(Ray), - typeof(Ray2D) - }; + NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ClassEquals; + } } /// @@ -212,56 +273,59 @@ internal static class NetworkVariableSerializationTypes /// /// The type the associated NetworkVariable is templated on [Serializable] - public static class NetworkVariableSerialization where T : unmanaged + public static class NetworkVariableSerialization { - private static INetworkVariableSerializer s_Serializer = GetSerializer(); + internal static INetworkVariableSerializer Serializer = new FallbackSerializer(); + + internal delegate bool EqualsDelegate(ref T a, ref T b); + internal static EqualsDelegate AreEqual; - private static INetworkVariableSerializer GetSerializer() + // Compares two values of the same unmanaged type by underlying memory + // Ignoring any overridden value checks + // Size is fixed + internal static unsafe bool ValueEquals(ref TValueType a, ref TValueType b) where TValueType : unmanaged { - if (NetworkVariableSerializationTypes.BaseSupportedTypes.Contains(typeof(T))) - { - return new UnmanagedTypeSerializer(); - } - if (typeof(INetworkSerializeByMemcpy).IsAssignableFrom(typeof(T))) - { - return new UnmanagedTypeSerializer(); - } - if (typeof(Enum).IsAssignableFrom(typeof(T))) - { - return new UnmanagedTypeSerializer(); - } + // get unmanaged pointers + var aptr = UnsafeUtility.AddressOf(ref a); + var bptr = UnsafeUtility.AddressOf(ref b); + + // compare addresses + return UnsafeUtility.MemCmp(aptr, bptr, sizeof(TValueType)) == 0; + } - if (typeof(INetworkSerializable).IsAssignableFrom(typeof(T))) + internal static bool EqualityEqualsObject(ref TValueType a, ref TValueType b) where TValueType : class, IEquatable + { + if (a == null) { - // Obtains "Open Instance Delegates" for the type's NetworkSerialize() methods - - // one for an instance of the generic method taking BufferSerializerWriter as T, - // one for an instance of the generic method taking BufferSerializerReader as T - var writeMethod = (NetworkSerializableSerializer.WriteValueDelegate)Delegate.CreateDelegate(typeof(NetworkSerializableSerializer.WriteValueDelegate), null, typeof(T).GetMethod(nameof(INetworkSerializable.NetworkSerialize)).MakeGenericMethod(typeof(BufferSerializerWriter))); - var readMethod = (NetworkSerializableSerializer.ReadValueDelegate)Delegate.CreateDelegate(typeof(NetworkSerializableSerializer.ReadValueDelegate), null, typeof(T).GetMethod(nameof(INetworkSerializable.NetworkSerialize)).MakeGenericMethod(typeof(BufferSerializerReader))); - return new NetworkSerializableSerializer { WriteValue = writeMethod, ReadValue = readMethod }; + return b == null; } - if (typeof(IUTF8Bytes).IsAssignableFrom(typeof(T)) && typeof(INativeList).IsAssignableFrom(typeof(T))) + if (b == null) { - // Get "OpenInstanceDelegates" for the Length property (get and set, which are prefixed - // with "get_" and "set_" under the hood and emitted as methods) and GetUnsafePtr() - var getLength = (FixedStringSerializer.GetLengthDelegate)Delegate.CreateDelegate(typeof(FixedStringSerializer.GetLengthDelegate), null, typeof(T).GetMethod("get_" + nameof(INativeList.Length))); - var setLength = (FixedStringSerializer.SetLengthDelegate)Delegate.CreateDelegate(typeof(FixedStringSerializer.SetLengthDelegate), null, typeof(T).GetMethod("set_" + nameof(INativeList.Length))); - var getUnsafePtr = (FixedStringSerializer.GetUnsafePtrDelegate)Delegate.CreateDelegate(typeof(FixedStringSerializer.GetUnsafePtrDelegate), null, typeof(T).GetMethod(nameof(IUTF8Bytes.GetUnsafePtr))); - return new FixedStringSerializer { GetLength = getLength, SetLength = setLength, GetUnsafePtr = getUnsafePtr }; + return false; } - return new FallbackSerializer(); + return a.Equals(b); + } + + internal static bool EqualityEquals(ref TValueType a, ref TValueType b) where TValueType : unmanaged, IEquatable + { + return a.Equals(b); + } + + internal static bool ClassEquals(ref TValueType a, ref TValueType b) where TValueType : class + { + return a == b; } internal static void Write(FastBufferWriter writer, ref T value) { - s_Serializer.Write(writer, ref value); + Serializer.Write(writer, ref value); } - internal static void Read(FastBufferReader reader, out T value) + internal static void Read(FastBufferReader reader, ref T value) { - s_Serializer.Read(reader, out value); + Serializer.Read(reader, ref value); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerReader.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerReader.cs index 5d43fcaf6a..271f357165 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerReader.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializerReader.cs @@ -34,7 +34,7 @@ public FastBufferWriter GetFastBufferWriter() public void SerializeValue(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Reader.ReadValueSafe(out value); public void SerializeValue(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValueSafe(out value); public void SerializeValue(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValueSafe(out value); - public void SerializeValue(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Reader.ReadValue(out value); + public void SerializeValue(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Reader.ReadNetworkSerializableInPlace(ref value); public void SerializeValue(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Reader.ReadValue(out value); public void SerializeValue(ref T value, FastBufferWriter.ForFixedStrings unused = default) diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs index 90ecd4a112..e052c95e4f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs @@ -461,6 +461,19 @@ public unsafe byte[] ToArray() } } + /// + /// Read an INetworkSerializable in-place, without constructing a new one + /// Note that this will NOT check for null before calling NetworkSerialize + /// + /// + /// INetworkSerializable instance + /// + public void ReadNetworkSerializableInPlace(ref T value) where T : INetworkSerializable + { + var bufferSerializer = new BufferSerializer(new BufferSerializerReader(this)); + value.NetworkSerialize(bufferSerializer); + } + /// /// Reads a string /// NOTE: ALLOCATES @@ -1310,5 +1323,24 @@ public unsafe void ReadValueSafe(out T value, FastBufferWriter.ForFixedString value.Length = length; ReadBytesSafe(value.GetUnsafePtr(), length); } + + + /// + /// Read a FixedString value. + /// + /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking + /// for multiple reads at once by calling TryBeginRead. + /// + /// the value to read + /// An unused parameter used for enabling overload resolution based on generic constraints + /// The type being serialized + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void ReadValueSafeInPlace(ref T value, FastBufferWriter.ForFixedStrings unused = default) + where T : unmanaged, INativeList, IUTF8Bytes + { + ReadUnmanagedSafe(out int length); + value.Length = length; + ReadBytesSafe(value.GetUnsafePtr(), length); + } } } diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetworkVariableHelper.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetworkVariableHelper.cs index 880c14f78a..343e0bb788 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetworkVariableHelper.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetworkVariableHelper.cs @@ -13,7 +13,7 @@ namespace Unity.Netcode.TestHelpers.Runtime /// From both we can then at least determine if the value indeed changed /// /// - public class NetworkVariableHelper : NetworkVariableBaseHelper where T : unmanaged + public class NetworkVariableHelper : NetworkVariableBaseHelper { private readonly NetworkVariable m_NetworkVariable; public delegate void OnMyValueChangedDelegateHandler(T previous, T next); @@ -50,7 +50,7 @@ private void OnVariableChanged(T previous, T next) { if (previous is ValueType testValueType) { - CheckVariableChanged(previous, next); + CheckVariableChanged(previous as ValueType, next as ValueType); } else { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs index 6b24f18a73..5f1788f56a 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs @@ -1,8 +1,161 @@ +using System; +using NUnit.Framework; +using Unity.Collections; using UnityEngine; using Unity.Netcode.TestHelpers.Runtime; namespace Unity.Netcode.RuntimeTests { + public class ManagedNetworkSerializableType : INetworkSerializable, IEquatable + { + public string Str = ""; + public int[] Ints = Array.Empty(); + public int InMemoryValue; + + public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter + { + serializer.SerializeValue(ref Str, true); + var length = Ints.Length; + serializer.SerializeValue(ref length); + if (serializer.IsReader) + { + Ints = new int[length]; + } + + for (var i = 0; i < length; ++i) + { + var val = Ints[i]; + serializer.SerializeValue(ref val); + Ints[i] = val; + } + } + + public bool Equals(ManagedNetworkSerializableType other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (Str != other.Str) + { + return false; + } + + if (Ints.Length != other.Ints.Length) + { + return false; + } + + for (var i = 0; i < Ints.Length; ++i) + { + if (Ints[i] != other.Ints[i]) + { + return false; + } + } + + return true; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((ManagedNetworkSerializableType)obj); + } + + public override int GetHashCode() + { + return 0; + } + } + public struct UnmanagedNetworkSerializableType : INetworkSerializable, IEquatable + { + public FixedString32Bytes Str; + public int Int; + public int InMemoryValue; + + public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter + { + serializer.SerializeValue(ref Str); + serializer.SerializeValue(ref Int); + } + + public bool Equals(UnmanagedNetworkSerializableType other) + { + return Str.Equals(other.Str) && Int == other.Int; + } + + public override bool Equals(object obj) + { + if (obj == null) + { + return false; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((ManagedNetworkSerializableType)obj); + } + + public override int GetHashCode() + { + return Str.GetHashCode() ^ Int.GetHashCode() ^ InMemoryValue.GetHashCode(); + } + } + + + public struct UnmanagedTemplateNetworkSerializableType : INetworkSerializable where T : unmanaged, INetworkSerializable + { + public T Value; + + public void NetworkSerialize(BufferSerializer serializer) where TReaderWriterType : IReaderWriter + { + serializer.SerializeValue(ref Value); + } + } + + public struct ManagedTemplateNetworkSerializableType : INetworkSerializable where T : class, INetworkSerializable, new() + { + public T Value; + + public void NetworkSerialize(BufferSerializer serializer) where TReaderWriterType : IReaderWriter + { + bool isNull = Value == null; + serializer.SerializeValue(ref isNull); + if (!isNull) + { + if (Value == null) + { + Value = new T(); + } + serializer.SerializeValue(ref Value); + } + } + } + /// /// This provides coverage for all of the predefined NetworkVariable types /// The initial goal is for generalized full coverage of NetworkVariables: @@ -30,6 +183,12 @@ internal class NetworkVariableTestComponent : NetworkBehaviour private NetworkVariable m_NetworkVariableULong = new NetworkVariable(); private NetworkVariable m_NetworkVariableUInt = new NetworkVariable(); private NetworkVariable m_NetworkVariableUShort = new NetworkVariable(); + private NetworkVariable m_NetworkVariableFixedString32 = new NetworkVariable(); + private NetworkVariable m_NetworkVariableFixedString64 = new NetworkVariable(); + private NetworkVariable m_NetworkVariableFixedString128 = new NetworkVariable(); + private NetworkVariable m_NetworkVariableFixedString512 = new NetworkVariable(); + private NetworkVariable m_NetworkVariableFixedString4096 = new NetworkVariable(); + private NetworkVariable m_NetworkVariableManaged = new NetworkVariable(); public NetworkVariableHelper Bool_Var; @@ -50,6 +209,12 @@ internal class NetworkVariableTestComponent : NetworkBehaviour public NetworkVariableHelper Ulong_Var; public NetworkVariableHelper Uint_Var; public NetworkVariableHelper Ushort_Var; + public NetworkVariableHelper FixedString32_Var; + public NetworkVariableHelper FixedString64_Var; + public NetworkVariableHelper FixedString128_Var; + public NetworkVariableHelper FixedString512_Var; + public NetworkVariableHelper FixedString4096_Var; + public NetworkVariableHelper Managed_Var; public bool EnableTesting; @@ -80,6 +245,12 @@ private void InitializeTest() m_NetworkVariableULong = new NetworkVariable(); m_NetworkVariableUInt = new NetworkVariable(); m_NetworkVariableUShort = new NetworkVariable(); + m_NetworkVariableFixedString32 = new NetworkVariable(); + m_NetworkVariableFixedString64 = new NetworkVariable(); + m_NetworkVariableFixedString128 = new NetworkVariable(); + m_NetworkVariableFixedString512 = new NetworkVariable(); + m_NetworkVariableFixedString4096 = new NetworkVariable(); + m_NetworkVariableManaged = new NetworkVariable(); // NetworkVariable Value Type Constructor Test Coverage @@ -101,6 +272,16 @@ private void InitializeTest() m_NetworkVariableULong = new NetworkVariable(1); m_NetworkVariableUInt = new NetworkVariable(1); m_NetworkVariableUShort = new NetworkVariable(1); + m_NetworkVariableFixedString32 = new NetworkVariable("1234567890"); + m_NetworkVariableFixedString64 = new NetworkVariable("1234567890"); + m_NetworkVariableFixedString128 = new NetworkVariable("1234567890"); + m_NetworkVariableFixedString512 = new NetworkVariable("1234567890"); + m_NetworkVariableFixedString4096 = new NetworkVariable("1234567890"); + m_NetworkVariableManaged = new NetworkVariable(new ManagedNetworkSerializableType + { + Str = "1234567890", + Ints = new[] { 1, 2, 3, 4, 5 } + }); // Use this nifty class: NetworkVariableHelper // Tracks if NetworkVariable changed invokes the OnValueChanged callback for the given instance type @@ -122,6 +303,12 @@ private void InitializeTest() Ulong_Var = new NetworkVariableHelper(m_NetworkVariableULong); Uint_Var = new NetworkVariableHelper(m_NetworkVariableUInt); Ushort_Var = new NetworkVariableHelper(m_NetworkVariableUShort); + FixedString32_Var = new NetworkVariableHelper(m_NetworkVariableFixedString32); + FixedString64_Var = new NetworkVariableHelper(m_NetworkVariableFixedString64); + FixedString128_Var = new NetworkVariableHelper(m_NetworkVariableFixedString128); + FixedString512_Var = new NetworkVariableHelper(m_NetworkVariableFixedString512); + FixedString4096_Var = new NetworkVariableHelper(m_NetworkVariableFixedString4096); + Managed_Var = new NetworkVariableHelper(m_NetworkVariableManaged); } /// @@ -152,6 +339,57 @@ public void Awake() InitializeTest(); } + public void AssertAllValuesAreCorrect() + { + Assert.AreEqual(false, m_NetworkVariableBool.Value); + Assert.AreEqual(255, m_NetworkVariableByte.Value); + Assert.AreEqual(100, m_NetworkVariableColor.Value.r); + Assert.AreEqual(100, m_NetworkVariableColor.Value.g); + Assert.AreEqual(100, m_NetworkVariableColor.Value.b); + Assert.AreEqual(100, m_NetworkVariableColor32.Value.r); + Assert.AreEqual(100, m_NetworkVariableColor32.Value.g); + Assert.AreEqual(100, m_NetworkVariableColor32.Value.b); + Assert.AreEqual(100, m_NetworkVariableColor32.Value.a); + Assert.AreEqual(1000, m_NetworkVariableDouble.Value); + Assert.AreEqual(1000.0f, m_NetworkVariableFloat.Value); + Assert.AreEqual(1000, m_NetworkVariableInt.Value); + Assert.AreEqual(100000, m_NetworkVariableLong.Value); + Assert.AreEqual(-127, m_NetworkVariableSByte.Value); + Assert.AreEqual(100, m_NetworkVariableQuaternion.Value.w); + Assert.AreEqual(100, m_NetworkVariableQuaternion.Value.x); + Assert.AreEqual(100, m_NetworkVariableQuaternion.Value.y); + Assert.AreEqual(100, m_NetworkVariableQuaternion.Value.z); + Assert.AreEqual(short.MaxValue, m_NetworkVariableShort.Value); + Assert.AreEqual(1000, m_NetworkVariableVector4.Value.w); + Assert.AreEqual(1000, m_NetworkVariableVector4.Value.x); + Assert.AreEqual(1000, m_NetworkVariableVector4.Value.y); + Assert.AreEqual(1000, m_NetworkVariableVector4.Value.z); + Assert.AreEqual(1000, m_NetworkVariableVector3.Value.x); + Assert.AreEqual(1000, m_NetworkVariableVector3.Value.y); + Assert.AreEqual(1000, m_NetworkVariableVector3.Value.z); + Assert.AreEqual(1000, m_NetworkVariableVector2.Value.x); + Assert.AreEqual(1000, m_NetworkVariableVector2.Value.y); + Assert.AreEqual(Vector3.one.x, m_NetworkVariableRay.Value.origin.x); + Assert.AreEqual(Vector3.one.y, m_NetworkVariableRay.Value.origin.y); + Assert.AreEqual(Vector3.one.z, m_NetworkVariableRay.Value.origin.z); + Assert.AreEqual(Vector3.right.x, m_NetworkVariableRay.Value.direction.x); + Assert.AreEqual(Vector3.right.y, m_NetworkVariableRay.Value.direction.y); + Assert.AreEqual(Vector3.right.z, m_NetworkVariableRay.Value.direction.z); + Assert.AreEqual(ulong.MaxValue, m_NetworkVariableULong.Value); + Assert.AreEqual(uint.MaxValue, m_NetworkVariableUInt.Value); + Assert.AreEqual(ushort.MaxValue, m_NetworkVariableUShort.Value); + Assert.IsTrue(m_NetworkVariableFixedString32.Value.Equals("FixedString32Bytes")); + Assert.IsTrue(m_NetworkVariableFixedString64.Value.Equals("FixedString64Bytes")); + Assert.IsTrue(m_NetworkVariableFixedString128.Value.Equals("FixedString128Bytes")); + Assert.IsTrue(m_NetworkVariableFixedString512.Value.Equals("FixedString512Bytes")); + Assert.IsTrue(m_NetworkVariableFixedString4096.Value.Equals("FixedString4096Bytes")); + Assert.IsTrue(m_NetworkVariableManaged.Value.Equals(new ManagedNetworkSerializableType + { + Str = "ManagedNetworkSerializableType", + Ints = new[] { 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000 } + })); + } + // Update is called once per frame private void Update() { @@ -187,6 +425,16 @@ private void Update() m_NetworkVariableULong.Value = ulong.MaxValue; m_NetworkVariableUInt.Value = uint.MaxValue; m_NetworkVariableUShort.Value = ushort.MaxValue; + m_NetworkVariableFixedString32.Value = new FixedString32Bytes("FixedString32Bytes"); + m_NetworkVariableFixedString64.Value = new FixedString64Bytes("FixedString64Bytes"); + m_NetworkVariableFixedString128.Value = new FixedString128Bytes("FixedString128Bytes"); + m_NetworkVariableFixedString512.Value = new FixedString512Bytes("FixedString512Bytes"); + m_NetworkVariableFixedString4096.Value = new FixedString4096Bytes("FixedString4096Bytes"); + m_NetworkVariableManaged.Value = new ManagedNetworkSerializableType + { + Str = "ManagedNetworkSerializableType", + Ints = new[] { 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000 } + }; //Set the timeout (i.e. how long we will wait for all NetworkVariables to have registered their changes) m_WaitForChangesTimeout = Time.realtimeSinceStartup + 0.50f; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs index 24ad1bb56e..300ec59e3b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs @@ -17,6 +17,37 @@ public class NetVarPermTestComp : NetworkBehaviour public NetworkVariable OwnerReadWrite_Position = new NetworkVariable(Vector3.one, NetworkVariableReadPermission.Owner, NetworkVariableWritePermission.Owner); } + // The ILPP code for NetworkVariables to determine how to serialize them relies on them existing as fields of a NetworkBehaviour to find them. + // Some of the tests below create NetworkVariables on the stack, so this class is here just to make sure the relevant types are all accounted for. + public class NetVarILPPClassForTests : NetworkBehaviour + { + public NetworkVariable UnmanagedNetworkSerializableTypeVar; + public NetworkVariable ManagedNetworkSerializableTypeVar; + public NetworkVariable StringVar; + public NetworkVariable GuidVar; + } + + public class TemplateNetworkBehaviourType : NetworkBehaviour + { + public NetworkVariable TheVar; + } + + public class ClassHavingNetworkBehaviour : TemplateNetworkBehaviourType + { + + } + + // Please do not reference TestClass2 anywhere other than here! + public class ClassHavingNetworkBehaviour2 : TemplateNetworkBehaviourType + { + + } + + public class StructHavingNetworkBehaviour : TemplateNetworkBehaviourType + { + + } + [TestFixtureSource(nameof(TestDataSource))] public class NetworkVariablePermissionTests : NetcodeIntegrationTest { @@ -344,6 +375,53 @@ public override int GetHashCode() } } + public class TestClass : INetworkSerializable, IEquatable + { + public uint SomeInt; + public bool SomeBool; + public static bool NetworkSerializeCalledOnWrite; + public static bool NetworkSerializeCalledOnRead; + + public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter + { + if (serializer.IsReader) + { + NetworkSerializeCalledOnRead = true; + } + else + { + NetworkSerializeCalledOnWrite = true; + } + serializer.SerializeValue(ref SomeInt); + serializer.SerializeValue(ref SomeBool); + } + + public bool Equals(TestClass other) + { + return SomeInt == other.SomeInt && SomeBool == other.SomeBool; + } + + public override bool Equals(object obj) + { + return obj is TestClass other && Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + return ((int)SomeInt * 397) ^ SomeBool.GetHashCode(); + } + } + } + + // Used just to create a NetworkVariable in the templated NetworkBehaviour type that isn't referenced anywhere else + // Please do not reference this class anywhere else! + public class TestClass_ReferencedOnlyByTemplateNetworkBehavourType : TestClass + { + + } + public class NetworkVariableTest : NetworkBehaviour { public enum SomeEnum @@ -370,8 +448,12 @@ public void Awake() } public readonly NetworkVariable TheStruct = new NetworkVariable(); + public readonly NetworkVariable TheClass = new NetworkVariable(); public readonly NetworkList TheListOfStructs = new NetworkList(); + public NetworkVariable> TheTemplateStruct = new NetworkVariable>(); + public NetworkVariable> TheTemplateClass = new NetworkVariable>(); + public bool ListDelegateTriggered; public override void OnNetworkSpawn() @@ -433,6 +515,10 @@ private IEnumerator InitializeServerAndClients(bool useHost) s_ClientNetworkVariableTestInstances.Clear(); m_PlayerPrefab.AddComponent(); + m_PlayerPrefab.AddComponent(); + m_PlayerPrefab.AddComponent(); + m_PlayerPrefab.AddComponent(); + m_ServerNetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety = m_EnsureLengthSafety; m_ServerNetworkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab; foreach (var client in m_ClientNetworkManagers) @@ -517,6 +603,7 @@ public IEnumerator AllNetworkVariableTypes([Values(true, false)] bool useHost) networkVariableTestComponent.EnableTesting = false; Assert.IsTrue(networkVariableTestComponent.DidAllValuesChange()); + networkVariableTestComponent.AssertAllValuesAreCorrect(); // Disable this once we are done. networkVariableTestComponent.gameObject.SetActive(false); @@ -689,6 +776,43 @@ public IEnumerator NetworkListClear([Values(true, false)] bool useHost) yield return WaitForConditionOrTimeOut(m_NetworkListPredicateHandler); } + [UnityTest] + public IEnumerator TestNetworkVariableClass([Values(true, false)] bool useHost) + { + yield return InitializeServerAndClients(useHost); + + bool VerifyClass() + { + return m_Player1OnClient1.TheClass.Value != null && + m_Player1OnClient1.TheClass.Value.SomeBool == m_Player1OnServer.TheClass.Value.SomeBool && + m_Player1OnClient1.TheClass.Value.SomeInt == m_Player1OnServer.TheClass.Value.SomeInt; + } + + m_Player1OnServer.TheClass.Value = new TestClass { SomeInt = k_TestUInt, SomeBool = false }; + m_Player1OnServer.TheClass.SetDirty(true); + + // Wait for the client-side to notify it is finished initializing and spawning. + yield return WaitForConditionOrTimeOut(VerifyClass); + } + + [UnityTest] + public IEnumerator TestNetworkVariableTemplateClass([Values(true, false)] bool useHost) + { + yield return InitializeServerAndClients(useHost); + + bool VerifyClass() + { + return m_Player1OnClient1.TheTemplateClass.Value.Value != null && m_Player1OnClient1.TheTemplateClass.Value.Value.SomeBool == m_Player1OnServer.TheTemplateClass.Value.Value.SomeBool && + m_Player1OnClient1.TheTemplateClass.Value.Value.SomeInt == m_Player1OnServer.TheTemplateClass.Value.Value.SomeInt; + } + + m_Player1OnServer.TheTemplateClass.Value = new ManagedTemplateNetworkSerializableType { Value = new TestClass { SomeInt = k_TestUInt, SomeBool = false } }; + m_Player1OnServer.TheTemplateClass.SetDirty(true); + + // Wait for the client-side to notify it is finished initializing and spawning. + yield return WaitForConditionOrTimeOut(VerifyClass); + } + [UnityTest] public IEnumerator TestNetworkVariableStruct([Values(true, false)] bool useHost) { @@ -700,13 +824,85 @@ bool VerifyStructure() m_Player1OnClient1.TheStruct.Value.SomeInt == m_Player1OnServer.TheStruct.Value.SomeInt; } - m_Player1OnServer.TheStruct.Value = new TestStruct() { SomeInt = k_TestUInt, SomeBool = false }; + m_Player1OnServer.TheStruct.Value = new TestStruct { SomeInt = k_TestUInt, SomeBool = false }; m_Player1OnServer.TheStruct.SetDirty(true); // Wait for the client-side to notify it is finished initializing and spawning. yield return WaitForConditionOrTimeOut(VerifyStructure); } + [UnityTest] + public IEnumerator TestNetworkVariableTemplateStruct([Values(true, false)] bool useHost) + { + yield return InitializeServerAndClients(useHost); + + bool VerifyStructure() + { + return m_Player1OnClient1.TheTemplateStruct.Value.Value.SomeBool == m_Player1OnServer.TheTemplateStruct.Value.Value.SomeBool && + m_Player1OnClient1.TheTemplateStruct.Value.Value.SomeInt == m_Player1OnServer.TheTemplateStruct.Value.Value.SomeInt; + } + + m_Player1OnServer.TheTemplateStruct.Value = new UnmanagedTemplateNetworkSerializableType { Value = new TestStruct { SomeInt = k_TestUInt, SomeBool = false } }; + m_Player1OnServer.TheTemplateStruct.SetDirty(true); + + // Wait for the client-side to notify it is finished initializing and spawning. + yield return WaitForConditionOrTimeOut(VerifyStructure); + } + + [UnityTest] + public IEnumerator TestNetworkVariableTemplateBehaviourClass([Values(true, false)] bool useHost) + { + yield return InitializeServerAndClients(useHost); + + bool VerifyClass() + { + return m_Player1OnClient1.GetComponent().TheVar.Value != null && m_Player1OnClient1.GetComponent().TheVar.Value.SomeBool == m_Player1OnServer.GetComponent().TheVar.Value.SomeBool && + m_Player1OnClient1.GetComponent().TheVar.Value.SomeInt == m_Player1OnServer.GetComponent().TheVar.Value.SomeInt; + } + + m_Player1OnServer.GetComponent().TheVar.Value = new TestClass { SomeInt = k_TestUInt, SomeBool = false }; + m_Player1OnServer.GetComponent().TheVar.SetDirty(true); + + // Wait for the client-side to notify it is finished initializing and spawning. + yield return WaitForConditionOrTimeOut(VerifyClass); + } + + [UnityTest] + public IEnumerator TestNetworkVariableTemplateBehaviourClassNotReferencedElsewhere([Values(true, false)] bool useHost) + { + yield return InitializeServerAndClients(useHost); + + bool VerifyClass() + { + return m_Player1OnClient1.GetComponent().TheVar.Value != null && m_Player1OnClient1.GetComponent().TheVar.Value.SomeBool == m_Player1OnServer.GetComponent().TheVar.Value.SomeBool && + m_Player1OnClient1.GetComponent().TheVar.Value.SomeInt == m_Player1OnServer.GetComponent().TheVar.Value.SomeInt; + } + + m_Player1OnServer.GetComponent().TheVar.Value = new TestClass_ReferencedOnlyByTemplateNetworkBehavourType { SomeInt = k_TestUInt, SomeBool = false }; + m_Player1OnServer.GetComponent().TheVar.SetDirty(true); + + // Wait for the client-side to notify it is finished initializing and spawning. + yield return WaitForConditionOrTimeOut(VerifyClass); + } + + [UnityTest] + public IEnumerator TestNetworkVariableTemplateBehaviourStruct([Values(true, false)] bool useHost) + { + yield return InitializeServerAndClients(useHost); + + bool VerifyClass() + { + return m_Player1OnClient1.GetComponent().TheVar.Value.SomeBool == m_Player1OnServer.GetComponent().TheVar.Value.SomeBool && + m_Player1OnClient1.GetComponent().TheVar.Value.SomeInt == m_Player1OnServer.GetComponent().TheVar.Value.SomeInt; + } + + m_Player1OnServer.GetComponent().TheVar.Value = new TestStruct { SomeInt = k_TestUInt, SomeBool = false }; + m_Player1OnServer.GetComponent().TheVar.SetDirty(true); + + // Wait for the client-side to notify it is finished initializing and spawning. + yield return WaitForConditionOrTimeOut(VerifyClass); + } + [UnityTest] public IEnumerator TestNetworkVariableEnum([Values(true, false)] bool useHost) { @@ -725,7 +921,25 @@ bool VerifyStructure() } [UnityTest] - public IEnumerator TestINetworkSerializableCallsNetworkSerialize([Values(true, false)] bool useHost) + public IEnumerator TestINetworkSerializableClassCallsNetworkSerialize([Values(true, false)] bool useHost) + { + yield return InitializeServerAndClients(useHost); + TestClass.NetworkSerializeCalledOnWrite = false; + TestClass.NetworkSerializeCalledOnRead = false; + m_Player1OnServer.TheClass.Value = new TestClass + { + SomeBool = true, + SomeInt = 32 + }; + + static bool VerifyCallback() => TestClass.NetworkSerializeCalledOnWrite && TestClass.NetworkSerializeCalledOnRead; + + // Wait for the client-side to notify it is finished initializing and spawning. + yield return WaitForConditionOrTimeOut(VerifyCallback); + } + + [UnityTest] + public IEnumerator TestINetworkSerializableStructCallsNetworkSerialize([Values(true, false)] bool useHost) { yield return InitializeServerAndClients(useHost); TestStruct.NetworkSerializeCalledOnWrite = false; @@ -773,6 +987,167 @@ public IEnumerator NetworkListIEnumerator([Values(true, false)] bool useHost) } } } + + [Test] + public void TestUnsupportedManagedTypesThrowExceptions() + { + var variable = new NetworkVariable(); + using var writer = new FastBufferWriter(1024, Allocator.Temp); + using var reader = new FastBufferReader(writer, Allocator.None); + // Just making sure these are null, just in case. + UserNetworkVariableSerialization.ReadValue = null; + UserNetworkVariableSerialization.WriteValue = null; + Assert.Throws(() => + { + variable.WriteField(writer); + }); + Assert.Throws(() => + { + variable.ReadField(reader); + }); + } + + [Test] + public void TestUnsupportedManagedTypesWithUserSerializationDoNotThrowExceptions() + { + var variable = new NetworkVariable(); + UserNetworkVariableSerialization.ReadValue = (FastBufferReader reader, out string value) => + { + reader.ReadValueSafe(out value); + }; + UserNetworkVariableSerialization.WriteValue = (FastBufferWriter writer, in string value) => + { + writer.WriteValueSafe(value); + }; + try + { + using var writer = new FastBufferWriter(1024, Allocator.Temp); + variable.Value = "012345"; + variable.WriteField(writer); + variable.Value = ""; + + using var reader = new FastBufferReader(writer, Allocator.None); + variable.ReadField(reader); + Assert.AreEqual("012345", variable.Value); + } + finally + { + UserNetworkVariableSerialization.ReadValue = null; + UserNetworkVariableSerialization.WriteValue = null; + } + } + + [Test] + public void TestUnsupportedUnmanagedTypesThrowExceptions() + { + var variable = new NetworkVariable(); + using var writer = new FastBufferWriter(1024, Allocator.Temp); + using var reader = new FastBufferReader(writer, Allocator.None); + // Just making sure these are null, just in case. + UserNetworkVariableSerialization.ReadValue = null; + UserNetworkVariableSerialization.WriteValue = null; + Assert.Throws(() => + { + variable.WriteField(writer); + }); + Assert.Throws(() => + { + variable.ReadField(reader); + }); + } + + [Test] + public void TestUnsupportedUnmanagedTypesWithUserSerializationDoNotThrowExceptions() + { + var variable = new NetworkVariable(); + UserNetworkVariableSerialization.ReadValue = (FastBufferReader reader, out Guid value) => + { + var tmpValue = new ForceNetworkSerializeByMemcpy(); + reader.ReadValueSafe(out tmpValue); + value = tmpValue.Value; + }; + UserNetworkVariableSerialization.WriteValue = (FastBufferWriter writer, in Guid value) => + { + var tmpValue = new ForceNetworkSerializeByMemcpy(value); + writer.WriteValueSafe(tmpValue); + }; + try + { + using var writer = new FastBufferWriter(1024, Allocator.Temp); + var guid = Guid.NewGuid(); + variable.Value = guid; + variable.WriteField(writer); + variable.Value = Guid.Empty; + + using var reader = new FastBufferReader(writer, Allocator.None); + variable.ReadField(reader); + Assert.AreEqual(guid, variable.Value); + } + finally + { + UserNetworkVariableSerialization.ReadValue = null; + UserNetworkVariableSerialization.WriteValue = null; + } + } + + [Test] + public void TestManagedINetworkSerializableNetworkVariablesDeserializeInPlace() + { + var variable = new NetworkVariable(); + variable.Value = new ManagedNetworkSerializableType + { + InMemoryValue = 1, + Ints = new[] { 2, 3, 4 }, + Str = "five" + }; + + using var writer = new FastBufferWriter(1024, Allocator.Temp); + variable.WriteField(writer); + Assert.AreEqual(1, variable.Value.InMemoryValue); + Assert.AreEqual(new[] { 2, 3, 4 }, variable.Value.Ints); + Assert.AreEqual("five", variable.Value.Str); + variable.Value = new ManagedNetworkSerializableType + { + InMemoryValue = 10, + Ints = new[] { 20, 30, 40, 50 }, + Str = "sixty" + }; + + using var reader = new FastBufferReader(writer, Allocator.None); + variable.ReadField(reader); + Assert.AreEqual(10, variable.Value.InMemoryValue, "In-memory value was not the same - in-place deserialization should not change this"); + Assert.AreEqual(new[] { 2, 3, 4 }, variable.Value.Ints, "Ints were not correctly deserialized"); + Assert.AreEqual("five", variable.Value.Str, "Str was not correctly deserialized"); + } + + [Test] + public void TestUnmnagedINetworkSerializableNetworkVariablesDeserializeInPlace() + { + var variable = new NetworkVariable(); + variable.Value = new UnmanagedNetworkSerializableType + { + InMemoryValue = 1, + Int = 2, + Str = "three" + }; + using var writer = new FastBufferWriter(1024, Allocator.Temp); + variable.WriteField(writer); + Assert.AreEqual(1, variable.Value.InMemoryValue); + Assert.AreEqual(2, variable.Value.Int); + Assert.AreEqual("three", variable.Value.Str); + variable.Value = new UnmanagedNetworkSerializableType + { + InMemoryValue = 10, + Int = 20, + Str = "thirty" + }; + + using var reader = new FastBufferReader(writer, Allocator.None); + variable.ReadField(reader); + Assert.AreEqual(10, variable.Value.InMemoryValue, "In-memory value was not the same - in-place deserialization should not change this"); + Assert.AreEqual(2, variable.Value.Int, "Int was not correctly deserialized"); + Assert.AreEqual("three", variable.Value.Str, "Str was not correctly deserialized"); + } #endregion private float m_OriginalTimeScale = 1.0f; diff --git a/testproject/Assets/Secure.meta b/testproject/Assets/Secure.meta new file mode 100644 index 0000000000..53071a065d --- /dev/null +++ b/testproject/Assets/Secure.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 65d7761c69e8549e0bf4cb843755bda9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Secure/generate.sh.meta b/testproject/Assets/Secure/generate.sh.meta new file mode 100644 index 0000000000..f4e864acf9 --- /dev/null +++ b/testproject/Assets/Secure/generate.sh.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d7e08ec7360ea437289050cd469913e2 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs index 419f74cb65..cff1784d83 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs @@ -83,11 +83,11 @@ private void GenerateParentIsNotCorrect(InSceneParentChildHandler handler, bool var serverOrClient = handler.NetworkManager.IsServer ? "Server" : "Client"; if (!shouldHaveParent) { - m_ErrorValidationLog.Append($"[{serverOrClient }-{handler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{handler.NetworkObjectId}'s still has the parent {handler.transform.parent.name} when it should be null!"); + m_ErrorValidationLog.Append($"[{serverOrClient}-{handler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{handler.NetworkObjectId}'s still has the parent {handler.transform.parent.name} when it should be null!"); } else { - m_ErrorValidationLog.Append($"[{serverOrClient }-{handler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{handler.NetworkObjectId}'s does not have a parent when it should!"); + m_ErrorValidationLog.Append($"[{serverOrClient}-{handler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{handler.NetworkObjectId}'s does not have a parent when it should!"); } } diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs index 87507cfdc9..c66d6d9036 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs @@ -86,7 +86,7 @@ public override void OnNetworkSpawn() } else { - throw new Exception($"Failed to assigned {gameObject.name} already assigned! {nameof(GameObject)} { ClientsRegistered[localClientId].Children[childIndex].Child.name} is already assigned to Client-{localClientId}'s child entry!"); + throw new Exception($"Failed to assigned {gameObject.name} already assigned! {nameof(GameObject)} {ClientsRegistered[localClientId].Children[childIndex].Child.name} is already assigned to Client-{localClientId}'s child entry!"); } } // We should never reach this point diff --git a/testproject/Packages/manifest.json b/testproject/Packages/manifest.json index ca669e2802..9e494b2400 100644 --- a/testproject/Packages/manifest.json +++ b/testproject/Packages/manifest.json @@ -1,15 +1,15 @@ { "dependencies": { - "com.unity.addressables": "1.16.19", - "com.unity.collab-proxy": "1.9.0", - "com.unity.ide.rider": "3.0.7", - "com.unity.ide.visualstudio": "2.0.11", - "com.unity.ide.vscode": "1.2.3", - "com.unity.mathematics": "1.2.1", + "com.unity.addressables": "1.18.19", + "com.unity.collab-proxy": "1.17.2", + "com.unity.ide.rider": "3.0.15", + "com.unity.ide.visualstudio": "2.0.16", + "com.unity.ide.vscode": "1.2.5", + "com.unity.mathematics": "1.2.6", "com.unity.netcode.gameobjects": "file:../../com.unity.netcode.gameobjects", "com.unity.package-validation-suite": "0.21.0-preview", - "com.unity.services.authentication": "1.0.0-pre.4", - "com.unity.services.core": "1.1.0-pre.8", + "com.unity.services.authentication": "2.2.0", + "com.unity.services.core": "1.4.3", "com.unity.services.relay": "1.0.1-pre.1", "com.unity.test-framework": "1.1.31", "com.unity.test-framework.performance": "2.8.0-preview", diff --git a/testproject/Packages/packages-lock.json b/testproject/Packages/packages-lock.json index 0ab03fe88f..5570ecb7d3 100644 --- a/testproject/Packages/packages-lock.json +++ b/testproject/Packages/packages-lock.json @@ -1,12 +1,14 @@ { "dependencies": { "com.unity.addressables": { - "version": "1.16.19", + "version": "1.18.19", "depth": 0, "source": "registry", "dependencies": { - "com.unity.scriptablebuildpipeline": "1.15.2", + "com.unity.scriptablebuildpipeline": "1.19.2", "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", "com.unity.modules.unitywebrequest": "1.0.0", "com.unity.modules.unitywebrequestassetbundle": "1.0.0" }, @@ -22,10 +24,12 @@ "url": "https://packages.unity.com" }, "com.unity.collab-proxy": { - "version": "1.9.0", + "version": "1.17.2", "depth": 0, "source": "registry", - "dependencies": {}, + "dependencies": { + "com.unity.services.core": "1.0.1" + }, "url": "https://packages.unity.com" }, "com.unity.collections": { @@ -46,7 +50,7 @@ "url": "https://packages.unity.com" }, "com.unity.ide.rider": { - "version": "3.0.7", + "version": "3.0.15", "depth": 0, "source": "registry", "dependencies": { @@ -55,7 +59,7 @@ "url": "https://packages.unity.com" }, "com.unity.ide.visualstudio": { - "version": "2.0.11", + "version": "2.0.16", "depth": 0, "source": "registry", "dependencies": { @@ -64,7 +68,7 @@ "url": "https://packages.unity.com" }, "com.unity.ide.vscode": { - "version": "1.2.3", + "version": "1.2.5", "depth": 0, "source": "registry", "dependencies": {}, @@ -72,7 +76,7 @@ }, "com.unity.mathematics": { "version": "1.2.6", - "depth": 2, + "depth": 0, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" @@ -94,7 +98,7 @@ "url": "https://packages.unity.com" }, "com.unity.nuget.newtonsoft-json": { - "version": "2.0.0", + "version": "3.0.2", "depth": 1, "source": "registry", "dependencies": {}, @@ -110,29 +114,32 @@ "url": "https://packages.unity.com" }, "com.unity.scriptablebuildpipeline": { - "version": "1.15.2", + "version": "1.19.2", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.services.authentication": { - "version": "1.0.0-pre.4", + "version": "2.2.0", "depth": 0, "source": "registry", "dependencies": { - "com.unity.nuget.newtonsoft-json": "2.0.0", - "com.unity.services.core": "1.1.0-pre.8", - "com.unity.modules.unitywebrequest": "1.0.0" + "com.unity.nuget.newtonsoft-json": "3.0.2", + "com.unity.services.core": "1.4.3", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.ugui": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.services.core": { - "version": "1.1.0-pre.8", + "version": "1.4.3", "depth": 0, "source": "registry", "dependencies": { - "com.unity.modules.unitywebrequest": "1.0.0" + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.nuget.newtonsoft-json": "3.0.2", + "com.unity.modules.androidjni": "1.0.0" }, "url": "https://packages.unity.com" }, diff --git a/testproject/ProjectSettings/ProjectVersion.txt b/testproject/ProjectSettings/ProjectVersion.txt index 79c71bce82..dd7e0b04f1 100644 --- a/testproject/ProjectSettings/ProjectVersion.txt +++ b/testproject/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 2020.3.12f1 -m_EditorVersionWithRevision: 2020.3.12f1 (b3b2c6512326) +m_EditorVersion: 2020.3.40f1 +m_EditorVersionWithRevision: 2020.3.40f1 (ba48d4efcef1) From 83aaca5ddc6edcb52733a2faec811a1711d5197c Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Thu, 6 Oct 2022 13:16:07 -0400 Subject: [PATCH 091/138] refactor: Use string-based encryption APIs in UnityTransport (#2238) * Add missing XML documentation * Use string-based secure APIs from UTP Co-authored-by: ashwini <36935028+ashwinimurt@users.noreply.github.com> --- .../Transports/UTP/SecretsLoaderHelper.cs | 24 ++++++- .../Runtime/Transports/UTP/UnityTransport.cs | 65 +++++++++---------- 2 files changed, 55 insertions(+), 34 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/SecretsLoaderHelper.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/SecretsLoaderHelper.cs index 82c4d37013..2d830ad686 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/SecretsLoaderHelper.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/SecretsLoaderHelper.cs @@ -83,6 +83,8 @@ private void Awake() [Tooltip("Hostname")] [SerializeField] private string m_ServerCommonName = "localhost"; + + /// Common name of the server (typically its hostname). public string ServerCommonName { get => m_ServerCommonName; @@ -92,6 +94,8 @@ public string ServerCommonName [Tooltip("Client CA filepath. Useful with self-signed certificates")] [SerializeField] private string m_ClientCAFilePath = "Assets/Secure/myGameClientCA.pem"; + + /// Client CA filepath. Useful with self-signed certificates public string ClientCAFilePath { get => m_ClientCAFilePath; @@ -101,6 +105,11 @@ public string ClientCAFilePath [Tooltip("Client CA Override. Only useful for development with self-signed certificates. Certificate content, for platforms that lack file access (WebGL)")] [SerializeField] private string m_ClientCAOverride = ""; + + /// + /// Client CA Override. Only useful for development with self-signed certificates. + /// Certificate content, for platforms that lack file access (WebGL) + /// public string ClientCAOverride { get => m_ClientCAOverride; @@ -110,14 +119,19 @@ public string ClientCAOverride [Tooltip("Server Certificate filepath")] [SerializeField] private string m_ServerCertificateFilePath = "Assets/Secure/myGameServerCertificate.pem"; + + /// Server Certificate filepath public string ServerCertificateFilePath { get => m_ServerCertificateFilePath; set => m_ServerCertificateFilePath = value; } - [Tooltip("Server Private Keyfilepath")] + + [Tooltip("Server Private Key filepath")] [SerializeField] private string m_ServerPrivateFilePath = "Assets/Secure/myGameServerPrivate.pem"; + + /// Server Private Key filepath public string ServerPrivateFilePath { get => m_ServerPrivateFilePath; @@ -125,6 +139,8 @@ public string ServerPrivateFilePath } private string m_ClientCA; + + /// CA certificate used by the client. public string ClientCA { get @@ -137,13 +153,19 @@ public string ClientCA } set => m_ClientCA = value; } + private string m_ServerCertificate; + + /// Certificate used by the server. public string ServerCertificate { get => ReadFile(m_ServerCertificateFilePath, "Server Certificate"); set => m_ServerCertificate = value; } + private string m_ServerPrivate; + + /// Private key used by the server. public string ServerPrivate { get => ReadFile(m_ServerPrivateFilePath, "Server Key"); diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index 2a4c2cce10..d134e8434b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -1367,34 +1367,33 @@ private void ConfigureSimulatorForUtp1() } #endif - private FixedString4096Bytes m_ServerPrivate; - private FixedString4096Bytes m_ServerCertificate; + private string m_ServerPrivateKey; + private string m_ServerCertificate; - private FixedString512Bytes m_ServerCommonName; - private FixedString4096Bytes m_ClientCertificate; + private string m_ServerCommonName; + private string m_ClientCaCertificate; + /// Set the server parameters for encryption. + /// Public certificate for the server (PEM format). + /// Private key for the server (PEM format). public void SetServerSecrets(string serverCertificate, string serverPrivateKey) { - if (serverPrivateKey.Length > m_ServerPrivate.Capacity || - serverCertificate.Length > m_ServerCertificate.Capacity) - { - throw new Exception("Secret lengths are above what Unity Transport allows."); - } - - m_ServerPrivate = serverPrivateKey; + m_ServerPrivateKey = serverPrivateKey; m_ServerCertificate = serverCertificate; } - public void SetClientSecrets(string serverCommonName, string clientCertificate = null) + /// Set the client parameters for encryption. + /// + /// If the CA certificate is not provided, validation will be done against the OS/browser + /// certificate store. This is what you'd want if using certificates from a known provider. + /// For self-signed certificates, the CA certificate needs to be provided. + /// + /// Common name of the server (typically hostname). + /// CA certificate used to validate the server's authenticity. + public void SetClientSecrets(string serverCommonName, string caCertificate = null) { - if (serverCommonName.Length > m_ServerCommonName.Capacity || - clientCertificate?.Length > m_ClientCertificate.Capacity) - { - throw new Exception("Secret lengths are above what Unity Transport allows."); - } - m_ServerCommonName = serverCommonName; - m_ClientCertificate = clientCertificate; + m_ClientCaCertificate = caCertificate; } /// @@ -1447,14 +1446,9 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, // log an error because we have mismatched configuration Debug.LogError("Mismatched security configuration, between Relay and local NetworkManager settings"); } - else - { - if (m_UseWebSockets) - { - // Todo: new code to support Relay+WSS - throw new NotImplementedException(); - } - } + + // No need to to anything else if using Relay because UTP will handle the + // configuration of the security parameters on its own. } else { @@ -1462,13 +1456,11 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, { if (NetworkManager.IsServer) { - if (m_ServerCertificate.Length == 0 || - m_ServerPrivate.Length == 0) + if (m_ServerCertificate.Length == 0 || m_ServerPrivateKey.Length == 0) { throw new Exception("In order to use encrypted communications, when hosting, you must set the server certificate and key."); } - m_NetworkSettings.WithSecureServerParameters(certificate: ref m_ServerCertificate, - privateKey: ref m_ServerPrivate); + m_NetworkSettings.WithSecureServerParameters(m_ServerCertificate, m_ServerPrivateKey); } else { @@ -1476,12 +1468,19 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, { throw new Exception("In order to use encrypted communications, clients must set the server common name."); } - m_NetworkSettings.WithSecureClientParameters(serverName: ref m_ServerCommonName, caCertificate: ref m_ClientCertificate); + else if (m_ClientCaCertificate == null) + { + m_NetworkSettings.WithSecureClientParameters(m_ServerCommonName); + } + else + { + m_NetworkSettings.WithSecureClientParameters(m_ClientCaCertificate, m_ServerCommonName)); + } } } catch(Exception e) { - Debug.LogException(e,this); + Debug.LogException(e, this); } } } From 322b81aa115490bb78847199642d85369325869c Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 7 Oct 2022 13:19:56 -0500 Subject: [PATCH 092/138] fix: Exclude test for rising animation state count during runtime builds (#2241) * test update: only include the animation state count check when running in the editor --- .../Tests/Runtime/Animation/AnimatorTestHelper.cs | 4 ++++ .../Tests/Runtime/Animation/NetworkAnimatorTests.cs | 12 +++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs b/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs index 05403caf81..4819142000 100644 --- a/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs +++ b/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs @@ -29,10 +29,14 @@ private void Awake() m_NetworkAnimator = GetComponent(); } + // Since the com.unity.netcode.components does not allow test project to access its internals + // during runtime, this is only used when running test runner from within the editor +#if UNITY_EDITOR internal int GetAnimatorStateCount() { return m_NetworkAnimator.GetAnimationMessage().AnimationStates.Count; } +#endif public override void OnNetworkSpawn() { diff --git a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs index ac74c1ec42..2412a11e93 100644 --- a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs +++ b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs @@ -256,9 +256,13 @@ public IEnumerator TriggerUpdateTests([Values] OwnerShipMode ownerShipMode, [Val AssertOnTimeout($"Timed out waiting for the client-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!"); var animatorTestHelper = ownerShipMode == OwnerShipMode.ClientOwner ? AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[0].LocalClientId] : AnimatorTestHelper.ServerSideInstance; var layerCount = animatorTestHelper.GetAnimator().layerCount; - var animationStateCount = animatorTestHelper.GetAnimatorStateCount(); + // Since the com.unity.netcode.components does not allow test project to access its internals + // during runtime, this is only used when running test runner from within the editor +#if UNITY_EDITOR + var animationStateCount = animatorTestHelper.GetAnimatorStateCount(); Assert.True(layerCount == animationStateCount, $"AnimationState count {animationStateCount} does not equal the layer count {layerCount}!"); +#endif if (authoritativeMode == AuthoritativeMode.ServerAuth) { animatorTestHelper = AnimatorTestHelper.ServerSideInstance; @@ -304,7 +308,9 @@ public IEnumerator TriggerUpdateTests([Values] OwnerShipMode ownerShipMode, [Val // Verify we only entered each state once yield return WaitForConditionOrTimeOut(() => CheckStateEnterCount.AllStatesEnteredMatch(clientIdList)); AssertOnTimeout($"Timed out waiting for all states entered to match!"); - + // Since the com.unity.netcode.components does not allow test project to access its internals + // during runtime, this is only used when running test runner from within the editor +#if UNITY_EDITOR // Now, update some states for several seconds to assure the AnimationState count does not grow var waitForSeconds = new WaitForSeconds(0.25f); bool rotateToggle = true; @@ -317,7 +323,7 @@ public IEnumerator TriggerUpdateTests([Values] OwnerShipMode ownerShipMode, [Val yield return waitForSeconds; rotateToggle = !rotateToggle; } - +#endif AnimatorTestHelper.IsTriggerTest = false; VerboseDebug($" ------------------ Trigger Test [{TriggerTest.Iteration}][{ownerShipMode}] Stopping ------------------ "); } From 840f12b07526eddfb35f0f5968e2848129da4c4f Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 7 Oct 2022 15:54:20 -0500 Subject: [PATCH 093/138] fix: in-scene placed NetworkObject will not spawn client-side when disabled upon being despawned [MTT-4832] (#2239) * fix This resolves the issue with in-scene placed NetworkObjects that are disabled when despawned not being able to re-spawn again and handles synchronizing despawned in-scene placed NetworkObjects during a scene switch (LoadSceneMode.Single). * test Added integration test that validates disabling NetworkObjects when despawned works with currently connected clients, late joining clients, and when scene switching (LoadSceneMode.Single) while also having the server despawn the in-scene placed NetworkObject upon its first spawn (i.e. so it starts off not visible/active to the clients when they finish the scene switch). --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../SceneManagement/NetworkSceneManager.cs | 3 + .../Runtime/SceneManagement/SceneEventData.cs | 160 +++++++++------- .../Runtime/Spawning/NetworkSpawnManager.cs | 7 + .../DespawnInSceneNetworkObject.cs | 71 +++---- .../SceneTransitioningBase1.unity | 6 +- .../InScenePlacedNetworkObjectTests.cs | 174 +++++++++++++++++- .../NetworkObjectTestComponent.cs | 35 +++- 8 files changed, 351 insertions(+), 106 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index d871452658..cbbdc26d95 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -28,6 +28,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where an in-scene placed NetworkObject would not invoke NetworkBehaviour.OnNetworkSpawn if the GameObject was disabled when it was despawned. (#2239) - Fixed issue where clients were not rebuilding the `NetworkConfig` hash value for each unique connection request. (#2226) - Fixed the issue where player objects were not taking the `DontDestroyWithOwner` property into consideration when a client disconnected. (#2225) - Fixed issue where `SceneEventProgress` would not complete if a client late joins while it is still in progress. (#2222) diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 531e582f55..c91cee28e9 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -1441,6 +1441,9 @@ private void OnServerLoadedScene(uint sceneEventId, Scene scene) } } + // Add any despawned when spawned in-scene placed NetworkObjects to the scene event data + sceneEventData.AddDespawnedInSceneNetworkObjects(); + // Set the server's scene's handle so the client can build a look up table sceneEventData.SceneHandle = scene.handle; diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs index ae8d5a31a9..222e437ca2 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs @@ -268,7 +268,8 @@ internal void AddSpawnedNetworkObjects() internal void AddDespawnedInSceneNetworkObjects() { m_DespawnedInSceneObjectsSync.Clear(); - var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType().Where((c) => c.NetworkManager == m_NetworkManager); + // Find all active and non-active in-scene placed NetworkObjects + var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType(includeInactive: true).Where((c) => c.NetworkManager == m_NetworkManager); foreach (var sobj in inSceneNetworkObjects) { if (sobj.IsSceneObject.HasValue && sobj.IsSceneObject.Value && !sobj.IsSpawned) @@ -461,7 +462,6 @@ internal void WriteSceneSynchronizationData(FastBufferWriter writer) for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count; ++i) { var noStart = writer.Position; - var sceneObject = m_DespawnedInSceneObjectsSync[i].GetMessageSceneObject(TargetClientId); BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle()); BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash); var noStop = writer.Position; @@ -507,6 +507,15 @@ internal void SerializeScenePlacedObjects(FastBufferWriter writer) } } + // Write the number of despawned in-scene placed NetworkObjects + writer.WriteValueSafe(m_DespawnedInSceneObjectsSync.Count); + // Write the scene handle and GlobalObjectIdHash value + for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count; ++i) + { + BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle()); + BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash); + } + var tailPosition = writer.Position; // Reposition to our count position to the head before we wrote our object count writer.Seek(headPosition); @@ -624,6 +633,8 @@ internal void DeserializeScenePlacedObjects() sceneObject.Deserialize(InternalBuffer); NetworkObject.AddSceneObject(sceneObject, InternalBuffer, m_NetworkManager); } + // Now deserialize the despawned in-scene placed NetworkObjects list (if any) + DeserializeDespawnedInScenePlacedNetworkObjects(); } finally { @@ -746,6 +757,84 @@ internal void WriteClientSynchronizationResults(FastBufferWriter writer) } } + /// + /// For synchronizing any despawned in-scene placed NetworkObjects that were + /// despawned by the server during synchronization or scene loading + /// + private void DeserializeDespawnedInScenePlacedNetworkObjects() + { + // Process all de-spawned in-scene NetworkObjects for this network session + m_DespawnedInSceneObjects.Clear(); + InternalBuffer.ReadValueSafe(out int despawnedObjectsCount); + var sceneCache = new Dictionary>(); + + for (int i = 0; i < despawnedObjectsCount; i++) + { + // We just need to get the scene + ByteUnpacker.ReadValuePacked(InternalBuffer, out int networkSceneHandle); + ByteUnpacker.ReadValuePacked(InternalBuffer, out uint globalObjectIdHash); + var sceneRelativeNetworkObjects = new Dictionary(); + if (!sceneCache.ContainsKey(networkSceneHandle)) + { + if (m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle.ContainsKey(networkSceneHandle)) + { + var localSceneHandle = m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle[networkSceneHandle]; + if (m_NetworkManager.SceneManager.ScenesLoaded.ContainsKey(localSceneHandle)) + { + var objectRelativeScene = m_NetworkManager.SceneManager.ScenesLoaded[localSceneHandle]; + + // Find all active and non-active in-scene placed NetworkObjects + var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType(includeInactive: true).Where((c) => + c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList(); + + foreach (var inSceneObject in inSceneNetworkObjects) + { + if (!sceneRelativeNetworkObjects.ContainsKey(inSceneObject.GlobalObjectIdHash)) + { + sceneRelativeNetworkObjects.Add(inSceneObject.GlobalObjectIdHash, inSceneObject); + } + } + // Add this to a cache so we don't have to run this potentially multiple times (nothing will spawn or despawn during this time + sceneCache.Add(networkSceneHandle, sceneRelativeNetworkObjects); + } + else + { + UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) cannot find its relative local scene handle {localSceneHandle}!"); + } + } + else + { + UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) cannot find its relative NetworkSceneHandle {networkSceneHandle}!"); + } + } + else // Use the cached NetworkObjects if they exist + { + sceneRelativeNetworkObjects = sceneCache[networkSceneHandle]; + } + + // Now find the in-scene NetworkObject with the current GlobalObjectIdHash we are looking for + if (sceneRelativeNetworkObjects.ContainsKey(globalObjectIdHash)) + { + // Since this is a NetworkObject that was never spawned, we just need to send a notification + // out that it was despawned so users can make adjustments + sceneRelativeNetworkObjects[globalObjectIdHash].InvokeBehaviourNetworkDespawn(); + if (!m_NetworkManager.SceneManager.ScenePlacedObjects.ContainsKey(globalObjectIdHash)) + { + m_NetworkManager.SceneManager.ScenePlacedObjects.Add(globalObjectIdHash, new Dictionary()); + } + + if (!m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].ContainsKey(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle())) + { + m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].Add(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle(), sceneRelativeNetworkObjects[globalObjectIdHash]); + } + } + else + { + UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) could not be found!"); + } + } + } + /// /// Client Side: /// During the processing of a server sent Event_Sync, this method will be called for each scene once @@ -779,72 +868,9 @@ internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager) } } - // Process all de-spawned in-scene NetworkObjects for this network session - m_DespawnedInSceneObjects.Clear(); - InternalBuffer.ReadValueSafe(out int despawnedObjectsCount); - var sceneCache = new Dictionary>(); - - for (int i = 0; i < despawnedObjectsCount; i++) - { - // We just need to get the scene - ByteUnpacker.ReadValuePacked(InternalBuffer, out int networkSceneHandle); - ByteUnpacker.ReadValuePacked(InternalBuffer, out uint globalObjectIdHash); - var sceneRelativeNetworkObjects = new Dictionary(); - if (!sceneCache.ContainsKey(networkSceneHandle)) - { - if (m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle.ContainsKey(networkSceneHandle)) - { - var localSceneHandle = m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle[networkSceneHandle]; - if (m_NetworkManager.SceneManager.ScenesLoaded.ContainsKey(localSceneHandle)) - { - var objectRelativeScene = m_NetworkManager.SceneManager.ScenesLoaded[localSceneHandle]; - var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType().Where((c) => - c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList(); + // Now deserialize the despawned in-scene placed NetworkObjects list (if any) + DeserializeDespawnedInScenePlacedNetworkObjects(); - foreach (var inSceneObject in inSceneNetworkObjects) - { - sceneRelativeNetworkObjects.Add(inSceneObject.GlobalObjectIdHash, inSceneObject); - } - // Add this to a cache so we don't have to run this potentially multiple times (nothing will spawn or despawn during this time - sceneCache.Add(networkSceneHandle, sceneRelativeNetworkObjects); - } - else - { - UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) cannot find its relative local scene handle {localSceneHandle}!"); - } - } - else - { - UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) cannot find its relative NetworkSceneHandle {networkSceneHandle}!"); - } - } - else // Use the cached NetworkObjects if they exist - { - sceneRelativeNetworkObjects = sceneCache[networkSceneHandle]; - } - - // Now find the in-scene NetworkObject with the current GlobalObjectIdHash we are looking for - if (sceneRelativeNetworkObjects.ContainsKey(globalObjectIdHash)) - { - // Since this is a NetworkObject that was never spawned, we just need to send a notification - // out that it was despawned so users can make adjustments - sceneRelativeNetworkObjects[globalObjectIdHash].InvokeBehaviourNetworkDespawn(); - if (!m_NetworkManager.SceneManager.ScenePlacedObjects.ContainsKey(globalObjectIdHash)) - { - m_NetworkManager.SceneManager.ScenePlacedObjects.Add(globalObjectIdHash, new Dictionary()); - } - - if (!m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].ContainsKey(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle())) - { - m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].Add(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle(), sceneRelativeNetworkObjects[globalObjectIdHash]); - } - - } - else - { - UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) could not be found!"); - } - } } finally { diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 175e21a451..5279496443 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -388,6 +388,13 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO NetworkLog.LogError($"{nameof(NetworkPrefab)} hash was not found! In-Scene placed {nameof(NetworkObject)} soft synchronization failure for Hash: {globalObjectIdHash}!"); } } + + // Since this NetworkObject is an in-scene placed NetworkObject, if it is disabled then enable it so + // NetworkBehaviours will have their OnNetworkSpawn method invoked + if (networkObject != null && !networkObject.gameObject.activeInHierarchy) + { + networkObject.gameObject.SetActive(true); + } } if (networkObject != null) diff --git a/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/DespawnInSceneNetworkObject.cs b/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/DespawnInSceneNetworkObject.cs index 825bfb86a3..e14af520f5 100644 --- a/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/DespawnInSceneNetworkObject.cs +++ b/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/DespawnInSceneNetworkObject.cs @@ -8,79 +8,82 @@ namespace TestProject.ManualTests /// /// Used for manually testing spawning and despawning in-scene /// placed NetworkObjects - /// - /// Note: We do not destroy in-scene placed NetworkObjects, but - /// users must handle visibility (rendering wise) when the in-scene - /// NetworkObject is spawned and despawned. This class just enables - /// or disabled the mesh renderer. /// public class DespawnInSceneNetworkObject : NetworkBehaviour { + [Tooltip("When set, the server will despawn the NetworkObject upon its first spawn.")] + public bool StartDespawned; + private Coroutine m_ScanInputHandle; - private MeshRenderer m_MeshRenderer; - private void Start() - { - if (!IsSpawned) - { - m_MeshRenderer = GetComponent(); - if (m_MeshRenderer != null) - { - m_MeshRenderer.enabled = false; - } - } - } + // Used to prevent the server from despawning + // the in-scene placed NetworkObject after the + // first spawn (only if StartDespawned is true) + private bool m_ServerDespawnedOnFirstSpawn; + + private NetworkManager m_CachedNetworkManager; public override void OnNetworkSpawn() { Debug.Log($"{name} spawned!"); - m_MeshRenderer = GetComponent(); - if (m_MeshRenderer != null) - { - m_MeshRenderer.enabled = true; - } + if (!IsServer) { return; } + + m_CachedNetworkManager = NetworkManager; + if (m_ScanInputHandle == null) { - m_ScanInputHandle = StartCoroutine(ScanInput()); + // Using the NetworkManager to create the coroutine so it is not deactivated + // when the GameObject this NetworkBehaviour is attached to is disabled. + m_ScanInputHandle = NetworkManager.StartCoroutine(ScanInput(NetworkObject)); + } + + // m_ServerDespawnedOnFirstSpawn prevents the server from always + // despawning on the server-side after the first spawn. + if (StartDespawned && !m_ServerDespawnedOnFirstSpawn) + { + m_ServerDespawnedOnFirstSpawn = true; + NetworkObject.Despawn(false); } } public override void OnNetworkDespawn() { - if (m_MeshRenderer != null) - { - m_MeshRenderer.enabled = false; - } + // It is OK to disable in-scene placed NetworkObjects upon + // despawning. When re-spawned the client-side will re-activate + // the GameObject, while the server-side must set the GameObject + // active itself. + gameObject.SetActive(false); + Debug.Log($"{name} despawned!"); base.OnNetworkDespawn(); } public override void OnDestroy() { - if (m_ScanInputHandle != null) + if (m_ScanInputHandle != null && m_CachedNetworkManager != null) { - StopCoroutine(m_ScanInputHandle); + m_CachedNetworkManager.StopCoroutine(m_ScanInputHandle); } m_ScanInputHandle = null; base.OnDestroy(); } - private IEnumerator ScanInput() + private IEnumerator ScanInput(NetworkObject networkObject) { while (true) { try { - if (IsSpawned) + if (networkObject.IsSpawned) { if (Input.GetKeyDown(KeyCode.Backspace)) { Debug.Log($"{name} should despawn."); - NetworkObject.Despawn(false); + networkObject.Despawn(false); } } else if (NetworkManager.Singleton && NetworkManager.Singleton.IsListening) @@ -88,7 +91,8 @@ private IEnumerator ScanInput() if (Input.GetKeyDown(KeyCode.Backspace)) { Debug.Log($"{name} should spawn."); - NetworkObject.Spawn(); + networkObject.gameObject.SetActive(true); + networkObject.Spawn(); } } } @@ -99,7 +103,6 @@ private IEnumerator ScanInput() yield return null; } - } } } diff --git a/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/SceneTransitioningBase1.unity b/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/SceneTransitioningBase1.unity index 3288c65aaf..de7c662a05 100644 --- a/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/SceneTransitioningBase1.unity +++ b/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/SceneTransitioningBase1.unity @@ -2250,6 +2250,11 @@ PrefabInstance: propertyPath: m_Name value: InSceneObjectToDespawn objectReference: {fileID: 0} + - target: {fileID: 4518755925279129999, guid: 3a854a190ab5b1b4fb00bec725fdda9e, + type: 3} + propertyPath: StartDespawned + value: 1 + objectReference: {fileID: 0} m_RemovedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 3a854a190ab5b1b4fb00bec725fdda9e, type: 3} --- !u!1 &1008611498 @@ -2466,7 +2471,6 @@ MonoBehaviour: m_ProtocolType: 0 m_MaxPacketQueueSize: 128 m_MaxPayloadSize: 512000 - m_MaxSendQueueSize: 4096000 m_HeartbeatTimeoutMS: 500 m_ConnectTimeoutMS: 1000 m_MaxConnectAttempts: 60 diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs index 3d02ff0689..2425576d7c 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Collections.Generic; using System.Linq; using UnityEngine; using NUnit.Framework; @@ -21,8 +22,7 @@ public class InScenePlacedNetworkObjectTests : NetcodeIntegrationTest protected override IEnumerator OnSetup() { - NetworkObjectTestComponent.ServerNetworkObjectInstance = null; - NetworkObjectTestComponent.SpawnedInstances.Clear(); + NetworkObjectTestComponent.Reset(); m_CanStartServerAndClients = false; return base.OnSetup(); } @@ -181,7 +181,6 @@ public IEnumerator ParentedInSceneObjectLateJoiningClient() AssertOnTimeout($"Timed out waiting for the client-side id ({m_ClientNetworkManagers[0].LocalClientId}) server player transform to be set on the client-side in-scene object!"); } - private void OnSceneEvent(SceneEvent sceneEvent) { if (sceneEvent.SceneEventType == SceneEventType.LoadComplete && sceneEvent.SceneName == k_SceneToLoad && sceneEvent.ClientId == m_ClientNetworkManagers[0].LocalClientId) @@ -228,6 +227,175 @@ private void Unload_OnSceneEvent(SceneEvent sceneEvent) } } + + private bool m_AllClientsLoadedScene; + private bool m_AllClientsUnloadedScene; + + private int m_NumberOfInstancesCheck; + + private Scene m_SceneLoaded; + + private bool HaveAllClientsDespawnedInSceneObject() + { + // Make sure we despawned all instances + if (NetworkObjectTestComponent.DespawnedInstances.Count < m_NumberOfInstancesCheck) + { + return false; + } + + foreach (var despawnedInstance in NetworkObjectTestComponent.DespawnedInstances) + { + if (despawnedInstance.gameObject.activeInHierarchy) + { + return false; + } + } + + return true; + } + + private bool HaveAllClientsSpawnedInSceneObject() + { + // Make sure we despawned all instances + if (NetworkObjectTestComponent.SpawnedInstances.Count < m_NumberOfInstancesCheck) + { + return false; + } + + foreach (var despawnedInstance in NetworkObjectTestComponent.SpawnedInstances) + { + if (!despawnedInstance.gameObject.activeInHierarchy) + { + return false; + } + } + + return true; + } + + /// + /// This validates that users can despawn in-scene placed NetworkObjects and disable the + /// associated GameObject when OnNetworkDespawn is invoked while still being able to + /// re-spawn the same in-scene placed NetworkObject. + /// This test validates this for: + /// - Currently connected clients + /// - Late joining client + /// - Scene switching and having the server despawn the NetworkObject the first time it is spawned. + /// + [UnityTest] + public IEnumerator EnableDisableInSceneObjectTests() + { + NetworkObjectTestComponent.ServerNetworkObjectInstance = null; + // Enabled disabling the NetworkObject when it is despawned + NetworkObjectTestComponent.DisableOnDespawn = true; + // Set the number of instances to expect + m_NumberOfInstancesCheck = NumberOfClients + (m_UseHost ? 1 : 0); + + // Start the host and clients and load the in-scene object scene additively + m_CanStartServerAndClients = true; + yield return StartServerAndClients(); + m_ServerNetworkManager.SceneManager.OnLoadEventCompleted += SceneManager_OnLoadEventCompleted; + m_ServerNetworkManager.SceneManager.LoadScene(k_SceneToLoad, LoadSceneMode.Additive); + yield return WaitForConditionOrTimeOut(() => m_AllClientsLoadedScene); + AssertOnTimeout($"Timed out waiting for {k_SceneToLoad} scene to be loaded on all clients!"); + m_ServerNetworkManager.SceneManager.OnLoadEventCompleted -= SceneManager_OnLoadEventCompleted; + + // Verify all connected clients spawned the in-scene placed NetworkObject + yield return WaitForConditionOrTimeOut(HaveAllClientsSpawnedInSceneObject); + AssertOnTimeout($"Timed out waiting for all instances to be spawned and enabled!"); + + var serverInSceneObjectInstance = NetworkObjectTestComponent.ServerNetworkObjectInstance; + Assert.IsNotNull(serverInSceneObjectInstance, $"Could not get the server-side registration of {nameof(NetworkObjectTestComponent)}!"); + + // Test #1: Despawn the in-scene placed NetworkObject and verify it is despawned and disabled on the clients + serverInSceneObjectInstance.Despawn(false); + + yield return WaitForConditionOrTimeOut(HaveAllClientsDespawnedInSceneObject); + AssertOnTimeout($"[Test #1] Timed out waiting for all instances to be despawned and disabled!"); + + // Test #2: Late-join a client and re-verify that all in-scene placed object instances are still disabled + yield return CreateAndStartNewClient(); + + var newlyJoinedClient = m_ClientNetworkManagers[NumberOfClients]; + + m_NumberOfInstancesCheck++; + yield return WaitForConditionOrTimeOut(HaveAllClientsDespawnedInSceneObject); + AssertOnTimeout($"[Test #2] Timed out waiting for all instances to be despawned and disabled!"); + + // Test #3: Now spawn the same in-scene placed NetworkObject + serverInSceneObjectInstance.gameObject.SetActive(true); + serverInSceneObjectInstance.Spawn(); + yield return WaitForConditionOrTimeOut(HaveAllClientsSpawnedInSceneObject); + AssertOnTimeout($"[Test #2] Timed out waiting for all instances to be enabled and spawned!"); + + // Test #4: Now unload the in-scene object's scene and scene switch to the same scene while + // also having the server-side disable the in-scene placed NetworkObject and verify all + // connected clients completed the scene switch and that all in-scene placed NetworkObjects + // are despawned and disabled. + m_AllClientsLoadedScene = false; + m_AllClientsUnloadedScene = false; + + NetworkObjectTestComponent.ServerNetworkObjectInstance = null; + NetworkObjectTestComponent.DisableOnSpawn = true; + m_ServerNetworkManager.SceneManager.OnUnloadEventCompleted += SceneManager_OnUnloadEventCompleted; + m_ServerNetworkManager.SceneManager.UnloadScene(m_SceneLoaded); + yield return WaitForConditionOrTimeOut(() => m_AllClientsUnloadedScene); + AssertOnTimeout($"Timed out waiting for {k_SceneToLoad} scene to be unloaded on all clients!"); + m_ServerNetworkManager.SceneManager.OnUnloadEventCompleted -= SceneManager_OnUnloadEventCompleted; + + // Verify the spawned instances list is empty + Assert.True(NetworkObjectTestComponent.SpawnedInstances.Count == 0, $"There are {NetworkObjectTestComponent.SpawnedInstances.Count} that did not despawn when the scene was unloaded!"); + + // Go ahead and clear out the despawned instances list + NetworkObjectTestComponent.DespawnedInstances.Clear(); + + // Now scene switch (LoadSceneMode.Single) into the scene with the in-scene placed NetworkObject we have been testing + m_ServerNetworkManager.SceneManager.OnLoadEventCompleted += SceneManager_OnLoadEventCompleted; + m_ServerNetworkManager.SceneManager.LoadScene(k_SceneToLoad, LoadSceneMode.Single); + yield return WaitForConditionOrTimeOut(() => m_AllClientsLoadedScene); + AssertOnTimeout($"Timed out waiting for {k_SceneToLoad} scene to be loaded on all clients!"); + m_ServerNetworkManager.SceneManager.OnLoadEventCompleted -= SceneManager_OnLoadEventCompleted; + + // Verify all client instances are disabled and despawned when done scene switching + yield return WaitForConditionOrTimeOut(HaveAllClientsDespawnedInSceneObject); + AssertOnTimeout($"[Test #4] Timed out waiting for all instances to be despawned and disabled!"); + + serverInSceneObjectInstance = NetworkObjectTestComponent.ServerNetworkObjectInstance; + Assert.IsNotNull(serverInSceneObjectInstance, $"[Test #4] Could not get the server-side registration of {nameof(NetworkObjectTestComponent)}!"); + + // Test #5: Now spawn the in-scene placed NetworkObject + serverInSceneObjectInstance.gameObject.SetActive(true); + serverInSceneObjectInstance.Spawn(); + + // Verify all clients spawned their in-scene NetworkObject relative instance + yield return WaitForConditionOrTimeOut(HaveAllClientsSpawnedInSceneObject); + AssertOnTimeout($"[Test #2] Timed out waiting for all instances to be enabled and spawned!"); + yield return StopOneClient(newlyJoinedClient, true); + + // Tests complete! + } + + private void SceneManager_OnUnloadEventCompleted(string sceneName, LoadSceneMode loadSceneMode, List clientsCompleted, List clientsTimedOut) + { + foreach (var clientId in clientsCompleted) + { + Assert.True(m_ServerNetworkManager.ConnectedClientsIds.Contains(clientId)); + } + m_AllClientsUnloadedScene = true; + } + + private void SceneManager_OnLoadEventCompleted(string sceneName, LoadSceneMode loadSceneMode, List clientsCompleted, List clientsTimedOut) + { + foreach (var clientId in clientsCompleted) + { + Assert.True(m_ServerNetworkManager.ConnectedClientsIds.Contains(clientId)); + } + m_AllClientsLoadedScene = true; + m_SceneLoaded = SceneManager.GetSceneByName(sceneName); + } + + + /// /// Very important to always have a backup "unloading" catch /// in the event your test fails it could not potentially unload diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectTestComponent.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectTestComponent.cs index 978e3093ed..89c6b3728b 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectTestComponent.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectTestComponent.cs @@ -12,16 +12,44 @@ namespace TestProject.RuntimeTests /// public class NetworkObjectTestComponent : NetworkBehaviour { + public static bool DisableOnDespawn; + public static bool DisableOnSpawn; public static NetworkObject ServerNetworkObjectInstance; public static List SpawnedInstances = new List(); + public static List DespawnedInstances = new List(); + public static void Reset() + { + DisableOnDespawn = false; + DisableOnSpawn = false; + ServerNetworkObjectInstance = null; + SpawnedInstances.Clear(); + DespawnedInstances.Clear(); + } + + // When disabling on spawning we only want this to happen on the initial spawn. + // This is used to track this so the server only does it once upon spawning. + public bool ObjectWasDisabledUponSpawn; public override void OnNetworkSpawn() { + SpawnedInstances.Add(this); + if (DisableOnDespawn) + { + if (DespawnedInstances.Contains(this)) + { + DespawnedInstances.Remove(this); + } + } + if (IsServer) { ServerNetworkObjectInstance = NetworkObject; + if (DisableOnSpawn && !ObjectWasDisabledUponSpawn) + { + NetworkObject.Despawn(false); + ObjectWasDisabledUponSpawn = true; + } } - SpawnedInstances.Add(this); base.OnNetworkSpawn(); } @@ -33,6 +61,11 @@ public override void OnNetworkDespawn() m_HasNotifiedSpawned = false; Debug.Log($"{NetworkManager.name} de-spawned {gameObject.name}."); SpawnedInstances.Remove(this); + if (DisableOnDespawn) + { + DespawnedInstances.Add(this); + gameObject.SetActive(false); + } base.OnNetworkDespawn(); } From 3caf64f52a7c5ab8d9404cb798255a8a8a3216ac Mon Sep 17 00:00:00 2001 From: Kitty Draper <284434+ShadauxCat@users.noreply.github.com> Date: Mon, 10 Oct 2022 16:51:09 -0500 Subject: [PATCH 094/138] fix: Ensure network variable ILPP code picks up types used in NetworkList, too (#2245) --- .../Editor/CodeGen/NetworkBehaviourILPP.cs | 2 +- .../Tests/Runtime/NetworkVariableTests.cs | 42 ++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index 09b0551832..24d378c6aa 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -755,7 +755,7 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass //var type = field.FieldType; if (type.IsGenericInstance) { - if (type.Resolve().Name == typeof(NetworkVariable<>).Name) + if (type.Resolve().Name == typeof(NetworkVariable<>).Name || type.Resolve().Name == typeof(NetworkList<>).Name) { var genericInstanceType = (GenericInstanceType)type; var wrappedType = genericInstanceType.GenericArguments[0]; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs index 300ec59e3b..d4f17b21fa 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs @@ -48,6 +48,26 @@ public class StructHavingNetworkBehaviour : TemplateNetworkBehaviourType, INetworkSerializeByMemcpy + { + public int Value; + + public bool Equals(StructUsedOnlyInNetworkList other) + { + return Value == other.Value; + } + + public override bool Equals(object obj) + { + return obj is StructUsedOnlyInNetworkList other && Equals(other); + } + + public override int GetHashCode() + { + return Value; + } + } + [TestFixtureSource(nameof(TestDataSource))] public class NetworkVariablePermissionTests : NetcodeIntegrationTest { @@ -433,6 +453,7 @@ public enum SomeEnum public readonly NetworkVariable TheScalar = new NetworkVariable(); public readonly NetworkVariable TheEnum = new NetworkVariable(); public readonly NetworkList TheList = new NetworkList(); + public readonly NetworkList TheStructList = new NetworkList(); public readonly NetworkList TheLargeList = new NetworkList(); public readonly NetworkVariable FixedString32 = new NetworkVariable(); @@ -449,7 +470,6 @@ public void Awake() public readonly NetworkVariable TheStruct = new NetworkVariable(); public readonly NetworkVariable TheClass = new NetworkVariable(); - public readonly NetworkList TheListOfStructs = new NetworkList(); public NetworkVariable> TheTemplateStruct = new NetworkVariable>(); public NetworkVariable> TheTemplateClass = new NetworkVariable>(); @@ -813,6 +833,26 @@ bool VerifyClass() yield return WaitForConditionOrTimeOut(VerifyClass); } + [UnityTest] + public IEnumerator TestNetworkListStruct([Values(true, false)] bool useHost) + { + yield return InitializeServerAndClients(useHost); + + bool VerifyList() + { + return m_Player1OnClient1.TheStructList.Count == m_Player1OnServer.TheStructList.Count && + m_Player1OnClient1.TheStructList[0].Value == m_Player1OnServer.TheStructList[0].Value && + m_Player1OnClient1.TheStructList[1].Value == m_Player1OnServer.TheStructList[1].Value; + } + + m_Player1OnServer.TheStructList.Add(new StructUsedOnlyInNetworkList { Value = 1 }); + m_Player1OnServer.TheStructList.Add(new StructUsedOnlyInNetworkList { Value = 2 }); + m_Player1OnServer.TheStructList.SetDirty(true); + + // Wait for the client-side to notify it is finished initializing and spawning. + yield return WaitForConditionOrTimeOut(VerifyList); + } + [UnityTest] public IEnumerator TestNetworkVariableStruct([Values(true, false)] bool useHost) { From a952af8763d1b4fca4ab01fe83ef0e8a862f69bd Mon Sep 17 00:00:00 2001 From: Kitty Draper <284434+ShadauxCat@users.noreply.github.com> Date: Tue, 11 Oct 2022 16:46:59 -0500 Subject: [PATCH 095/138] fix: Refactored DeferredMessagingTests to not use coroutines and increased the slush value for the two timing-based tests... (#2248) * fix: Refactored DeferredMessagingTests to not use coroutines and increased the slush value for the two timing-based tests... hopefully this will clear up the instability * standards * fixed failing tests * Removed some commented-out code and left-in debug code. --- .../TestHelpers/Runtime/MessageHooks.cs | 26 ++- .../Runtime/MessageHooksConditional.cs | 48 +++++- .../Runtime/NetcodeIntegrationTest.cs | 10 +- .../Tests/Runtime/DeferredMessagingTests.cs | 156 ++++++------------ 4 files changed, 117 insertions(+), 123 deletions(-) diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/MessageHooks.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/MessageHooks.cs index 1f56850558..23f5f6c89a 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/MessageHooks.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/MessageHooks.cs @@ -5,15 +5,21 @@ namespace Unity.Netcode.TestHelpers.Runtime internal class MessageHooks : INetworkHooks { public bool IsWaiting = true; - public delegate bool MessageReceiptCheck(object receivedMessage); + public delegate bool MessageReceiptCheck(Type receivedMessageType); public MessageReceiptCheck ReceiptCheck; + public delegate bool MessageHandleCheck(object receivedMessage); + public MessageHandleCheck HandleCheck; public static bool CurrentMessageHasTriggerdAHook = false; - public static bool CheckForMessageOfType(object receivedMessage) where T : INetworkMessage + public static bool CheckForMessageOfTypeHandled(object receivedMessage) where T : INetworkMessage { return receivedMessage is T; } + public static bool CheckForMessageOfTypeReceived(Type receivedMessageType) where T : INetworkMessage + { + return receivedMessageType == typeof(T); + } public void OnBeforeSendMessage(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage { @@ -25,10 +31,24 @@ public void OnAfterSendMessage(ulong clientId, ref T message, NetworkDelivery public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) { + // The way the system works, it goes through all hooks and calls OnBeforeHandleMessage, then handles the message, + // then goes thorugh all hooks and calls OnAfterHandleMessage. + // This ensures each message only manages to activate a single message hook - because we know that only + // one message will ever be handled between OnBeforeHandleMessage and OnAfterHandleMessage, + // we can reset the flag here, and then in OnAfterHandleMessage, the moment the message matches a hook, + // it'll flip this flag back on, and then other hooks will stop checking that message. + // Without this flag, waiting for 10 messages of the same type isn't possible - all 10 hooks would get + // tripped by the first message. + CurrentMessageHasTriggerdAHook = false; } public void OnAfterReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) { + if (!CurrentMessageHasTriggerdAHook && IsWaiting && (HandleCheck == null || HandleCheck.Invoke(messageType))) + { + IsWaiting = false; + CurrentMessageHasTriggerdAHook = true; + } } public void OnBeforeSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery) @@ -72,7 +92,7 @@ public void OnBeforeHandleMessage(ref T message, ref NetworkContext context) public void OnAfterHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage { - if (!CurrentMessageHasTriggerdAHook && IsWaiting && (ReceiptCheck == null || ReceiptCheck.Invoke(message))) + if (!CurrentMessageHasTriggerdAHook && IsWaiting && (HandleCheck == null || HandleCheck.Invoke(message))) { IsWaiting = false; CurrentMessageHasTriggerdAHook = true; diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/MessageHooksConditional.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/MessageHooksConditional.cs index 733cae8c90..f16978ef95 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/MessageHooksConditional.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/MessageHooksConditional.cs @@ -64,18 +64,34 @@ public MessageHooksConditional(List messageHookEntries) } } + public enum ReceiptType + { + Received, + Handled + } + public class MessageHookEntry { internal MessageHooks MessageHooks; protected NetworkManager m_NetworkManager; private MessageHooks.MessageReceiptCheck m_MessageReceiptCheck; + private MessageHooks.MessageHandleCheck m_MessageHandleCheck; internal string MessageType; + private ReceiptType m_ReceiptType; public void Initialize() { - Assert.IsNotNull(m_MessageReceiptCheck, $"{nameof(m_MessageReceiptCheck)} is null, did you forget to initialize?"); MessageHooks = new MessageHooks(); - MessageHooks.ReceiptCheck = m_MessageReceiptCheck; + if (m_ReceiptType == ReceiptType.Handled) + { + Assert.IsNotNull(m_MessageHandleCheck, $"{nameof(m_MessageHandleCheck)} is null, did you forget to initialize?"); + MessageHooks.HandleCheck = m_MessageHandleCheck; + } + else + { + Assert.IsNotNull(m_MessageReceiptCheck, $"{nameof(m_MessageReceiptCheck)} is null, did you forget to initialize?"); + MessageHooks.ReceiptCheck = m_MessageReceiptCheck; + } Assert.IsNotNull(m_NetworkManager.MessagingSystem, $"{nameof(NetworkManager.MessagingSystem)} is null! Did you forget to start first?"); m_NetworkManager.MessagingSystem.Hook(MessageHooks); } @@ -83,23 +99,41 @@ public void Initialize() internal void AssignMessageType() where T : INetworkMessage { MessageType = typeof(T).Name; - m_MessageReceiptCheck = MessageHooks.CheckForMessageOfType; + if (m_ReceiptType == ReceiptType.Handled) + { + m_MessageHandleCheck = MessageHooks.CheckForMessageOfTypeHandled; + } + else + { + m_MessageReceiptCheck = MessageHooks.CheckForMessageOfTypeReceived; + } Initialize(); } internal void AssignMessageType(Type type) { MessageType = type.Name; - m_MessageReceiptCheck = (message) => + if (m_ReceiptType == ReceiptType.Handled) { - return message.GetType() == type; - }; + m_MessageHandleCheck = (message) => + { + return message.GetType() == type; + }; + } + else + { + m_MessageReceiptCheck = (messageType) => + { + return messageType == type; + }; + } Initialize(); } - public MessageHookEntry(NetworkManager networkManager) + public MessageHookEntry(NetworkManager networkManager, ReceiptType type = ReceiptType.Handled) { m_NetworkManager = networkManager; + m_ReceiptType = type; } } } diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index a83d0bdb18..044a5358a4 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -762,22 +762,23 @@ protected IEnumerator WaitForClientsConnectedOrTimeOut() yield return WaitForClientsConnectedOrTimeOut(m_ClientNetworkManagers); } - internal IEnumerator WaitForMessageReceived(List wiatForReceivedBy) where T : INetworkMessage + internal IEnumerator WaitForMessageReceived(List wiatForReceivedBy, ReceiptType type = ReceiptType.Handled) where T : INetworkMessage { // Build our message hook entries tables so we can determine if all clients received spawn or ownership messages var messageHookEntriesForSpawn = new List(); foreach (var clientNetworkManager in wiatForReceivedBy) { - var messageHook = new MessageHookEntry(clientNetworkManager); + var messageHook = new MessageHookEntry(clientNetworkManager, type); messageHook.AssignMessageType(); messageHookEntriesForSpawn.Add(messageHook); } // Used to determine if all clients received the CreateObjectMessage var hooks = new MessageHooksConditional(messageHookEntriesForSpawn); yield return WaitForConditionOrTimeOut(hooks); + Assert.False(s_GlobalTimeoutHelper.TimedOut); } - internal IEnumerator WaitForMessagesReceived(List messagesInOrder, List wiatForReceivedBy) + internal IEnumerator WaitForMessagesReceived(List messagesInOrder, List wiatForReceivedBy, ReceiptType type = ReceiptType.Handled) { // Build our message hook entries tables so we can determine if all clients received spawn or ownership messages var messageHookEntriesForSpawn = new List(); @@ -785,7 +786,7 @@ internal IEnumerator WaitForMessagesReceived(List messagesInOrder, List messagesInOrder, List diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs index 198802d022..6c63358389 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs @@ -1,5 +1,7 @@ +using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; @@ -341,98 +343,48 @@ private void AssertSpawnTriggerCountForObject(TestDeferredMessageManager manager Assert.AreEqual(0, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnAddPrefab)); } - private static CoroutineRunner s_CoroutineRunner; - - private Coroutine Run(IEnumerator enumerator) - { - if (s_CoroutineRunner == null) - { - s_CoroutineRunner = new GameObject(nameof(CoroutineRunner)).AddComponent(); - } - - return s_CoroutineRunner.StartCoroutine(enumerator); - } - - private IEnumerator RunMultiple(List waitFor) + private IEnumerator WaitForAllClientsToReceive() where T : INetworkMessage { - yield return WaitMultiple(StartMultiple(waitFor)); - } - - private List StartMultiple(List waitFor) - { - var runningCoroutines = new List(); - foreach (var enumerator in waitFor) - { - runningCoroutines.Add(Run(enumerator)); - } - - return runningCoroutines; - } - - private IEnumerator WaitMultiple(List runningCoroutines) - { - foreach (var coroutine in runningCoroutines) - { - yield return coroutine; - } - } - - private List WaitForAllClientsToReceive() where T : INetworkMessage - { - var waiters = new List(); - foreach (var client in m_ClientNetworkManagers) - { - waiters.Add(NetcodeIntegrationTestHelpers.WaitForMessageOfTypeReceived(client)); - } - - return waiters; + yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList(), ReceiptType.Received); } - private List WaitForAllClientsToReceive() + private IEnumerator WaitForAllClientsToReceive() where TFirstMessage : INetworkMessage where TSecondMessage : INetworkMessage { - var waiters = new List(); - foreach (var client in m_ClientNetworkManagers) + yield return WaitForMessagesReceived(new List { - waiters.Add(NetcodeIntegrationTestHelpers.WaitForMessageOfTypeReceived(client)); - waiters.Add(NetcodeIntegrationTestHelpers.WaitForMessageOfTypeReceived(client)); - } - - return waiters; + typeof(TFirstMessage), + typeof(TSecondMessage) + }, m_ClientNetworkManagers.ToList(), ReceiptType.Received); } - private List WaitForAllClientsToReceive() + private IEnumerator WaitForAllClientsToReceive() where TFirstMessage : INetworkMessage where TSecondMessage : INetworkMessage where TThirdMessage : INetworkMessage { - var waiters = new List(); - foreach (var client in m_ClientNetworkManagers) + yield return WaitForMessagesReceived(new List { - waiters.Add(NetcodeIntegrationTestHelpers.WaitForMessageOfTypeReceived(client)); - waiters.Add(NetcodeIntegrationTestHelpers.WaitForMessageOfTypeReceived(client)); - waiters.Add(NetcodeIntegrationTestHelpers.WaitForMessageOfTypeReceived(client)); - } - return waiters; + typeof(TFirstMessage), + typeof(TSecondMessage), + typeof(TThirdMessage), + }, m_ClientNetworkManagers.ToList(), ReceiptType.Received); } - private List WaitForAllClientsToReceive() + private IEnumerator WaitForAllClientsToReceive() where TFirstMessage : INetworkMessage where TSecondMessage : INetworkMessage where TThirdMessage : INetworkMessage where TFourthMessage : INetworkMessage { - var waiters = new List(); - foreach (var client in m_ClientNetworkManagers) + yield return WaitForMessagesReceived(new List { - waiters.Add(NetcodeIntegrationTestHelpers.WaitForMessageOfTypeReceived(client)); - waiters.Add(NetcodeIntegrationTestHelpers.WaitForMessageOfTypeReceived(client)); - waiters.Add(NetcodeIntegrationTestHelpers.WaitForMessageOfTypeReceived(client)); - waiters.Add(NetcodeIntegrationTestHelpers.WaitForMessageOfTypeReceived(client)); - } - - return waiters; + typeof(TFirstMessage), + typeof(TSecondMessage), + typeof(TThirdMessage), + typeof(TFourthMessage), + }, m_ClientNetworkManagers.ToList(), ReceiptType.Received); } [UnityTest] @@ -447,7 +399,7 @@ public IEnumerator WhenAnRpcArrivesBeforeASpawnArrives_ItIsDeferred() serverObject.GetComponent().SendTestClientRpc(); - yield return RunMultiple(WaitForAllClientsToReceive()); + yield return WaitForAllClientsToReceive(); foreach (var client in m_ClientNetworkManagers) { @@ -470,7 +422,7 @@ public IEnumerator WhenADespawnArrivesBeforeASpawnArrives_ItIsDeferred() serverObject.GetComponent().Despawn(false); - yield return RunMultiple(WaitForAllClientsToReceive()); + yield return WaitForAllClientsToReceive(); foreach (var client in m_ClientNetworkManagers) { @@ -492,7 +444,7 @@ public IEnumerator WhenAChangeOwnershipMessageArrivesBeforeASpawnArrives_ItIsDef yield return WaitForClientsToCatchSpawns(); serverObject.GetComponent().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); - yield return RunMultiple(WaitForAllClientsToReceive()); + yield return WaitForAllClientsToReceive(); foreach (var client in m_ClientNetworkManagers) { var manager = (TestDeferredMessageManager)client.DeferredMessageManager; @@ -509,10 +461,6 @@ public IEnumerator WhenANetworkVariableDeltaMessageArrivesBeforeASpawnArrives_It yield return SpawnClients(); CatchSpawns(); - // Have to start these before spawning here because spawning sends a NetworkVariableDeltaMessage, too - // Depending on timing, if we start this after spawning, we may end up missing the first one. - var waiters = WaitForAllClientsToReceive(); - var coroutines = StartMultiple(waiters); var serverObject = Object.Instantiate(m_NetworkVariablePrefab); serverObject.GetComponent().NetworkManagerOwner = m_ServerNetworkManager; @@ -521,7 +469,7 @@ public IEnumerator WhenANetworkVariableDeltaMessageArrivesBeforeASpawnArrives_It serverObject.GetComponent().TestNetworkVariable.Value = 1; - yield return WaitMultiple(coroutines); + yield return WaitForAllClientsToReceive(); foreach (var client in m_ClientNetworkManagers) { @@ -544,7 +492,7 @@ public IEnumerator WhenASpawnMessageArrivesBeforeThePrefabIsAvailable_ItIsDeferr serverObject.GetComponent().NetworkManagerOwner = m_ServerNetworkManager; serverObject.GetComponent().Spawn(); - yield return RunMultiple(WaitForAllClientsToReceive()); + yield return WaitForAllClientsToReceive(); foreach (var client in m_ClientNetworkManagers) { @@ -698,10 +646,6 @@ public IEnumerator WhenMultipleSpawnTriggeredMessagesAreDeferred_TheyAreAllProce yield return SpawnClients(); CatchSpawns(); - // We wait for the RPC message, the NetworkVariableDeltaMessage, and the ChangeOwnershipMessage messages - var waiters = WaitForAllClientsToReceive(); - var coroutines = StartMultiple(waiters); - var serverObject = Object.Instantiate(m_RpcAndNetworkVariablePrefab); serverObject.GetComponent().NetworkManagerOwner = m_ServerNetworkManager; serverObject.GetComponent().Spawn(); @@ -709,13 +653,12 @@ public IEnumerator WhenMultipleSpawnTriggeredMessagesAreDeferred_TheyAreAllProce serverObject.GetComponent().SendTestClientRpc(); serverObject.GetComponent().TestNetworkVariable.Value = 1; - // TODO: Remove this if we figure out how to work around the NetworkVariableDeltaMessage.Serialized issue at line 59 - // Otherwise, we have to wait for at least 1 tick for the NetworkVariableDeltaMessage to be generated before changing ownership - yield return s_DefaultWaitForTick; + + yield return WaitForAllClientsToReceive(); + serverObject.GetComponent().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); - // Should be received in order so we'll wait for the last one. - yield return WaitMultiple(coroutines); + yield return WaitForAllClientsToReceive(); foreach (var client in m_ClientNetworkManagers) { @@ -774,7 +717,7 @@ public IEnumerator WhenMultipleAddPrefabTriggeredMessagesAreDeferred_TheyAreAllP serverObject2.GetComponent().NetworkManagerOwner = m_ServerNetworkManager; serverObject2.GetComponent().Spawn(); - yield return RunMultiple(WaitForAllClientsToReceive()); + yield return WaitForAllClientsToReceive(); foreach (var client in m_ClientNetworkManagers) { @@ -839,9 +782,6 @@ public IEnumerator WhenSpawnTriggeredMessagesAreDeferredBeforeThePrefabIsAdded_A m_SkipAddingPrefabsToClient = true; yield return SpawnClients(); - var waiters = WaitForAllClientsToReceive(); - var coroutines = StartMultiple(waiters); - var serverObject = Object.Instantiate(m_RpcAndNetworkVariablePrefab); serverObject.GetComponent().NetworkManagerOwner = m_ServerNetworkManager; serverObject.GetComponent().Spawn(); @@ -850,10 +790,11 @@ public IEnumerator WhenSpawnTriggeredMessagesAreDeferredBeforeThePrefabIsAdded_A serverObject.GetComponent().TestNetworkVariable.Value = 1; // TODO: Remove this if we figure out how to work around the NetworkVariableDeltaMessage.Serialized issue at line 59 // Otherwise, we have to wait for at least 1 tick for the NetworkVariableDeltaMessage to be generated before changing ownership - yield return s_DefaultWaitForTick; + yield return WaitForAllClientsToReceive(); + serverObject.GetComponent().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); - yield return WaitMultiple(coroutines); + yield return WaitForAllClientsToReceive(); // Validate messages are deferred and pending foreach (var client in m_ClientNetworkManagers) @@ -981,8 +922,6 @@ public IEnumerator WhenMultipleMessagesForTheSameObjectAreDeferredForMoreThanThe m_SkipAddingPrefabsToClient = true; yield return SpawnClients(); CatchSpawns(); - var waiters = WaitForAllClientsToReceive(); - var coroutines = StartMultiple(waiters); foreach (var client in m_ClientNetworkManagers) { @@ -1012,7 +951,9 @@ public IEnumerator WhenMultipleMessagesForTheSameObjectAreDeferredForMoreThanThe serverObject.GetComponent().TestNetworkVariable.Value = 1; serverObject.GetComponent().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); - yield return WaitMultiple(coroutines); + yield return WaitForMessagesReceived( + new List {typeof(ClientRpcMessage), typeof(NetworkVariableDeltaMessage), typeof(ChangeOwnershipMessage), + }, m_ClientNetworkManagers.ToList(), ReceiptType.Received); foreach (var unused in m_ClientNetworkManagers) { @@ -1026,7 +967,7 @@ public IEnumerator WhenMultipleMessagesForTheSameObjectAreDeferredForMoreThanThe { ++purgeCount; var elapsed = Time.realtimeSinceStartup - start; - Assert.GreaterOrEqual(elapsed, timeout - 0.05f); + Assert.GreaterOrEqual(elapsed, timeout - 0.25f); Assert.AreEqual(3, manager.DeferredMessageCountTotal()); Assert.AreEqual(3, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn)); Assert.AreEqual(3, manager.DeferredMessageCountForKey(IDeferredMessageManager.TriggerType.OnSpawn, key)); @@ -1060,11 +1001,6 @@ public IEnumerator WhenMultipleMessagesForDifferentObjectsAreDeferredForMoreThan yield return SpawnClients(); CatchSpawns(); - // Since there are two unique objects we need to look for two sets of messages - var waiters = WaitForAllClientsToReceive(); - waiters.AddRange(WaitForAllClientsToReceive()); - var coroutines = StartMultiple(waiters); - foreach (var client in m_ClientNetworkManagers) { client.NetworkConfig.SpawnTimeout = timeout; @@ -1102,7 +1038,9 @@ public IEnumerator WhenMultipleMessagesForDifferentObjectsAreDeferredForMoreThan serverObject2.GetComponent().TestNetworkVariable.Value = 1; serverObject2.GetComponent().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); - yield return WaitMultiple(coroutines); + yield return WaitForMessagesReceived( + new List {typeof(ClientRpcMessage), typeof(NetworkVariableDeltaMessage), typeof(ChangeOwnershipMessage),typeof(ClientRpcMessage), typeof(NetworkVariableDeltaMessage), typeof(ChangeOwnershipMessage), + }, m_ClientNetworkManagers.ToList(), ReceiptType.Received); foreach (var unused in m_ClientNetworkManagers) { @@ -1119,7 +1057,7 @@ public IEnumerator WhenMultipleMessagesForDifferentObjectsAreDeferredForMoreThan { ++purgeCount; var elapsed = Time.realtimeSinceStartup - start; - Assert.GreaterOrEqual(elapsed, timeout - 0.05f); + Assert.GreaterOrEqual(elapsed, timeout - 0.25f); Assert.AreEqual(remainingMessagesTotalThisClient, manager.DeferredMessageCountTotal()); Assert.AreEqual(remainingMessagesTotalThisClient, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn)); Assert.AreEqual(3, manager.DeferredMessageCountForKey(IDeferredMessageManager.TriggerType.OnSpawn, key)); @@ -1174,7 +1112,7 @@ public IEnumerator WhenADeferredMessageIsRemoved_OtherMessagesForSameObjectAreRe serverObject.GetComponent().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); - yield return RunMultiple(WaitForAllClientsToReceive()); + yield return WaitForAllClientsToReceive(); yield return new WaitForSeconds(timeout - 0.5f); @@ -1187,7 +1125,7 @@ public IEnumerator WhenADeferredMessageIsRemoved_OtherMessagesForSameObjectAreRe } serverObject.GetComponent().ChangeOwnership(m_ServerNetworkManager.LocalClientId); - yield return RunMultiple(WaitForAllClientsToReceive()); + yield return WaitForAllClientsToReceive(); foreach (var client in m_ClientNetworkManagers) { @@ -1269,7 +1207,7 @@ public IEnumerator WhenADeferredMessageIsRemoved_OtherMessagesForDifferentObject serverObject.GetComponent().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); - yield return RunMultiple(WaitForAllClientsToReceive()); + yield return WaitForAllClientsToReceive(); yield return new WaitForSeconds(timeout - 0.5f); @@ -1283,7 +1221,7 @@ public IEnumerator WhenADeferredMessageIsRemoved_OtherMessagesForDifferentObject } serverObject2.GetComponent().ChangeOwnership(m_ServerNetworkManager.LocalClientId); - yield return RunMultiple(WaitForAllClientsToReceive()); + yield return WaitForAllClientsToReceive(); foreach (var client in m_ClientNetworkManagers) { From df9a8562876e3ab494345b3f81d446d2735ac5bf Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Tue, 11 Oct 2022 22:15:30 -0400 Subject: [PATCH 096/138] fix: Allow empty paths in SecretsLoaderHelper (#2250) Co-authored-by: ashwini <36935028+ashwinimurt@users.noreply.github.com> --- .../Runtime/Transports/UTP/SecretsLoaderHelper.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/SecretsLoaderHelper.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/SecretsLoaderHelper.cs index 2d830ad686..40b8f2ba44 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/SecretsLoaderHelper.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/SecretsLoaderHelper.cs @@ -93,7 +93,7 @@ public string ServerCommonName [Tooltip("Client CA filepath. Useful with self-signed certificates")] [SerializeField] - private string m_ClientCAFilePath = "Assets/Secure/myGameClientCA.pem"; + private string m_ClientCAFilePath = ""; // "Assets/Secure/myGameClientCA.pem" /// Client CA filepath. Useful with self-signed certificates public string ClientCAFilePath @@ -118,7 +118,7 @@ public string ClientCAOverride [Tooltip("Server Certificate filepath")] [SerializeField] - private string m_ServerCertificateFilePath = "Assets/Secure/myGameServerCertificate.pem"; + private string m_ServerCertificateFilePath = ""; // "Assets/Secure/myGameServerCertificate.pem" /// Server Certificate filepath public string ServerCertificateFilePath @@ -129,7 +129,7 @@ public string ServerCertificateFilePath [Tooltip("Server Private Key filepath")] [SerializeField] - private string m_ServerPrivateFilePath = "Assets/Secure/myGameServerPrivate.pem"; + private string m_ServerPrivateFilePath = ""; // "Assets/Secure/myGameServerPrivate.pem" /// Server Private Key filepath public string ServerPrivateFilePath @@ -174,6 +174,11 @@ public string ServerPrivate private static string ReadFile(string path, string label) { + if (path == null || path == "") + { + return ""; + } + var reader = new StreamReader(path); string fileContent = reader.ReadToEnd(); Debug.Log((fileContent.Length > 1) ? ("Successfully loaded " + fileContent.Length + " byte(s) from " + label) : ("Could not read " + label + " file")); From f9e73d4143b8e23f6c36699a0718c2e7b8c74f04 Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Wed, 12 Oct 2022 16:47:57 -0400 Subject: [PATCH 097/138] fix: Not sending ChangeOwnership messages to clients that have an object hidden (#2251) * fix: Not sending ChangeOwnership messages to clients that have an object hidden --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Spawning/NetworkSpawnManager.cs | 7 ++-- .../Metrics/OwnershipChangeMetricsTests.cs | 7 ++++ .../Tests/Runtime/NetworkShowHideTests.cs | 32 +++++++++++++++++++ 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index cbbdc26d95..86b55d75a6 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -28,6 +28,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue caused when changing ownership of objects hidden to some clients (#2242) - Fixed issue where an in-scene placed NetworkObject would not invoke NetworkBehaviour.OnNetworkSpawn if the GameObject was disabled when it was despawned. (#2239) - Fixed issue where clients were not rebuilding the `NetworkConfig` hash value for each unique connection request. (#2226) - Fixed the issue where player objects were not taking the `DontDestroyWithOwner` property into consideration when a client disconnected. (#2225) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 5279496443..2c530d8046 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -278,11 +278,14 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId) NetworkObjectId = networkObject.NetworkObjectId, OwnerClientId = networkObject.OwnerClientId }; - var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds); foreach (var client in NetworkManager.ConnectedClients) { - NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size); + if (networkObject.IsNetworkVisibleTo(client.Value.ClientId)) + { + var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, client.Value.ClientId); + NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size); + } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs index cb4febf5e4..bfcfdc23eb 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs @@ -61,6 +61,13 @@ public IEnumerator TrackOwnershipChangeSentMetric() var ownershipChangeSent = metricValues.First(); Assert.AreEqual(networkObject.NetworkObjectId, ownershipChangeSent.NetworkId.NetworkId); Assert.AreEqual(Server.LocalClientId, ownershipChangeSent.Connection.Id); + Assert.AreEqual(0, ownershipChangeSent.BytesCount); + + // The first metric is to the server(self), so its size is now correctly reported as 0. + // Let's check the last one instead, to have a valid value + ownershipChangeSent = metricValues.Last(); + Assert.AreEqual(networkObject.NetworkObjectId, ownershipChangeSent.NetworkId.NetworkId); + Assert.AreEqual(Client.LocalClientId, ownershipChangeSent.Connection.Id); Assert.AreEqual(FastBufferWriter.GetWriteSize() + k_MessageHeaderSize, ownershipChangeSent.BytesCount); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs index 8707bd95eb..2f43484ef3 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs @@ -306,5 +306,37 @@ public IEnumerator NetworkHideDespawnTest() LogAssert.NoUnexpectedReceived(); } + + [UnityTest] + public IEnumerator NetworkHideChangeOwnership() + { + ShowHideObject.ClientTargetedNetworkObjects.Clear(); + ShowHideObject.ClientIdToTarget = m_ClientNetworkManagers[1].LocalClientId; + ShowHideObject.Silent = true; + + var spawnedObject1 = SpawnObject(m_PrefabToSpawn, m_ServerNetworkManager); + m_NetSpawnedObject1 = spawnedObject1.GetComponent(); + + m_NetSpawnedObject1.GetComponent().MyNetworkVariable.Value++; + // Hide an object to a client + m_NetSpawnedObject1.NetworkHide(m_ClientNetworkManagers[1].LocalClientId); + + yield return WaitForConditionOrTimeOut(() => ShowHideObject.ClientTargetedNetworkObjects.Count == 0); + + // Change ownership while the object is hidden to some + m_NetSpawnedObject1.ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); + + // The two-second wait is actually needed as there's a potential warning of unhandled message after 1 second + yield return new WaitForSeconds(1.25f); + + LogAssert.NoUnexpectedReceived(); + + // Show the object again to check nothing unexpected happens + m_NetSpawnedObject1.NetworkShow(m_ClientNetworkManagers[1].LocalClientId); + + yield return WaitForConditionOrTimeOut(() => ShowHideObject.ClientTargetedNetworkObjects.Count == 1); + + Assert.True(ShowHideObject.ClientTargetedNetworkObjects[0].OwnerClientId == m_ClientNetworkManagers[0].LocalClientId); + } } } From 5f39c1083219651cb123f3d93b806e76ee5723d9 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 13 Oct 2022 09:51:52 -0500 Subject: [PATCH 098/138] fix: NetworkSpawnManager.OnDespawnObject removing parent on client-side (#2252) * fix Fixes issue where despawning a parent NetworkObject would try to remove itself from the child on the client-side. * test Adding a test to verify this fix --- .../Runtime/Core/NetworkObject.cs | 8 ++ .../Runtime/Spawning/NetworkSpawnManager.cs | 15 +++- .../InSceneParentChildHandler.cs | 5 ++ .../ParentingInSceneObjectsTests.cs | 79 ++++++++++++++++++- 4 files changed, 103 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 6b21c5f89a..9d956f7d26 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -624,6 +624,14 @@ public bool TrySetParent(GameObject parent, bool worldPositionStays = true) return networkObject == null ? false : TrySetParent(networkObject, worldPositionStays); } + /// + /// Used when despawning the parent, we want to preserve the cached WorldPositionStays value + /// + internal bool TryRemoveParentCachedWorldPositionStays() + { + return TrySetParent((NetworkObject)null, m_CachedWorldPositionStays); + } + /// /// Removes the parent of the NetworkObject's transform /// diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 2c530d8046..45c190b801 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -778,7 +778,8 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec } // If we are shutting down the NetworkManager, then ignore resetting the parent - if (!NetworkManager.ShutdownInProgress) + // and only attempt to remove the child's parent on the server-side + if (!NetworkManager.ShutdownInProgress && NetworkManager.IsServer) { // Move child NetworkObjects to the root when parent NetworkObject is destroyed foreach (var spawnedNetObj in SpawnedObjectsList) @@ -786,7 +787,17 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec var latestParent = spawnedNetObj.GetNetworkParenting(); if (latestParent.HasValue && latestParent.Value == networkObject.NetworkObjectId) { - spawnedNetObj.gameObject.transform.parent = null; + // Try to remove the parent using the cached WorldPositioNStays value + // Note: WorldPositionStays will still default to true if this was an + // in-scene placed NetworkObject and parenting was predefined in the + // scene via the editor. + if (!spawnedNetObj.TryRemoveParentCachedWorldPositionStays()) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogError($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} could not be moved to the root when its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} was being destroyed"); + } + } if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { diff --git a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs index 80e9ac63e1..25277eb42d 100644 --- a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs +++ b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs @@ -48,6 +48,11 @@ public static void ResetInstancesTracking(bool enableVerboseDebug) ClientRelativeInstances.Clear(); } + public InSceneParentChildHandler GetChild() + { + return m_Child; + } + private Vector3 GenerateVector3(Vector3 min, Vector3 max) { var result = Vector3.zero; diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs index cff1784d83..f506d0f2b0 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs @@ -78,12 +78,13 @@ private void GenerateScaleDoesNotMatch(InSceneParentChildHandler serverHandler, m_ErrorValidationLog.Append($"[Client-{clientHandler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{clientHandler.NetworkObjectId}'s scale {clientHandler.transform.localScale} does not equal the server-side scale {serverHandler.transform.localScale}"); } - private void GenerateParentIsNotCorrect(InSceneParentChildHandler handler, bool shouldHaveParent) + private void GenerateParentIsNotCorrect(InSceneParentChildHandler handler, bool shouldHaveParent, bool isStillSpawnedCheck = false) { var serverOrClient = handler.NetworkManager.IsServer ? "Server" : "Client"; + var shouldNotBeSpawned = isStillSpawnedCheck ? " and is still spawned!" : string.Empty; if (!shouldHaveParent) { - m_ErrorValidationLog.Append($"[{serverOrClient}-{handler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{handler.NetworkObjectId}'s still has the parent {handler.transform.parent.name} when it should be null!"); + m_ErrorValidationLog.Append($"[{serverOrClient}-{handler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{handler.NetworkObjectId}'s still has the parent {handler.transform.parent.name} when it should be null{shouldNotBeSpawned}!"); } else { @@ -127,6 +128,7 @@ private bool ValidateClientAgainstServerTransformValues() private bool ValidateAllChildrenParentingStatus(bool checkForParent) { + m_ErrorValidationLog.Clear(); foreach (var instance in InSceneParentChildHandler.ServerRelativeInstances) { if (!instance.Value.IsRootParent) @@ -273,6 +275,79 @@ public IEnumerator InSceneParentingTest([Values] ParentingSpace parentingSpace) AssertOnTimeout($"[Final Pass - Last Test] Timed out waiting for all children to be removed from their parent!\n {m_ErrorValidationLog}"); } + /// + /// Validates the root parent is despawned and its child is moved to the root (null) + /// + private bool ValidateRootParentDespawnedAndChildAtRoot() + { + m_ErrorValidationLog.Clear(); + + var childOfRoot_ServerSide = InSceneParentChildHandler.ServerRootParent.GetChild(); + if (InSceneParentChildHandler.ServerRootParent.IsSpawned) + { + m_ErrorValidationLog.Append("Server-Side root parent is still spawned!"); + GenerateParentIsNotCorrect(childOfRoot_ServerSide, false, InSceneParentChildHandler.ServerRootParent.IsSpawned); + return false; + } + + if (childOfRoot_ServerSide.transform.parent != null) + { + m_ErrorValidationLog.Append("Server-Side root parent is not null!"); + return false; + } + + foreach (var clientInstances in InSceneParentChildHandler.ClientRelativeInstances) + { + foreach (var instance in clientInstances.Value) + { + if (instance.Value.IsRootParent) + { + var childHandler = instance.Value.GetChild(); + + if (instance.Value.IsSpawned) + { + m_ErrorValidationLog.Append("Client-Side is still spawned!"); + return false; + } + if (childHandler != null && childHandler.transform.parent != null) + { + m_ErrorValidationLog.Append("Client-Side still has parent!"); + return false; + } + } + } + } + return true; + } + + [UnityTest] + public IEnumerator DespawnParentTest([Values] ParentingSpace parentingSpace) + { + InSceneParentChildHandler.WorldPositionStays = parentingSpace == ParentingSpace.WorldPositionStays; + SceneManager.sceneLoaded += SceneManager_sceneLoaded; + SceneManager.LoadScene(k_BaseSceneToLoad, LoadSceneMode.Additive); + m_InitialClientsLoadedScene = false; + m_ServerNetworkManager.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; + + var sceneEventStartedStatus = m_ServerNetworkManager.SceneManager.LoadScene(k_TestSceneToLoad, LoadSceneMode.Additive); + Assert.True(sceneEventStartedStatus == SceneEventProgressStatus.Started, $"Failed to load scene {k_TestSceneToLoad} with a return status of {sceneEventStartedStatus}."); + yield return WaitForConditionOrTimeOut(() => m_InitialClientsLoadedScene); + AssertOnTimeout($"Timed out waiting for all clients to load scene {k_TestSceneToLoad}!"); + + // [Currently Connected Clients] + // remove the parents, change all transform values, and re-parent + InSceneParentChildHandler.ServerRootParent.DeparentSetValuesAndReparent(); + yield return WaitForConditionOrTimeOut(ValidateClientAgainstServerTransformValues); + AssertOnTimeout($"Timed out waiting for all clients transform values to match the server transform values!\n {m_ErrorValidationLog}"); + + // Now despawn the root parent + InSceneParentChildHandler.ServerRootParent.NetworkObject.Despawn(false); + + // Verify all clients despawned the parent object and the child of the parent has root as its parent + yield return WaitForConditionOrTimeOut(ValidateRootParentDespawnedAndChildAtRoot); + AssertOnTimeout($"{m_ErrorValidationLog}"); + } + private void SceneManager_OnSceneEvent(SceneEvent sceneEvent) { if (sceneEvent.SceneName != k_TestSceneToLoad) From 4cc5d3c9f7c95f4487927c3ab980fa2dd3231508 Mon Sep 17 00:00:00 2001 From: Frank Luong <100299641+fluong6@users.noreply.github.com> Date: Thu, 13 Oct 2022 15:25:16 -0400 Subject: [PATCH 099/138] fix: Removing extra bracket in UnityTransport (#2253) * removing extra bracket * adding tmp commit to pass the semantic pull request check * Revert "adding tmp commit to pass the semantic pull request check" This reverts commit d7f929688ce3c9836731038879d186034879a729. --- .../Runtime/Transports/UTP/UnityTransport.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index d134e8434b..f616b1d01f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -1474,7 +1474,7 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, } else { - m_NetworkSettings.WithSecureClientParameters(m_ClientCaCertificate, m_ServerCommonName)); + m_NetworkSettings.WithSecureClientParameters(m_ClientCaCertificate, m_ServerCommonName); } } } From 5e567bc94538484f3724eabeb16fd605b37d9e13 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 13 Oct 2022 16:10:41 -0500 Subject: [PATCH 100/138] fix: SceneEventProgress does not handle NetworkManager shut down during scene event (#2254) * fix This resolves the issue where SceneEventProgress was not checking if the NetworkManager was no longer listening or a shutdown was in progress. * update Added additional NetworkLog warning when a client times out during connection approval. * test Added a mock test to verify SceneEventProgress can handle the NetworkManager shutting down during a scene event. --- .../Runtime/Core/NetworkManager.cs | 9 +- .../SceneManagement/SceneEventProgress.cs | 36 +++++- .../SceneEventProgressTests.cs | 106 +++++++++++++++++- 3 files changed, 140 insertions(+), 11 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 0c1e0d9d59..badf7f2408 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1685,13 +1685,18 @@ private IEnumerator ApprovalTimeout(ulong clientId) else { float timeStarted = Time.realtimeSinceStartup; - - //We yield every frame incase a pending client disconnects and someone else gets its connection id + //We yield every frame in case a pending client disconnects and someone else gets its connection id while (IsListening && (Time.realtimeSinceStartup - timeStarted) < NetworkConfig.ClientConnectionBufferTimeout && !IsConnectedClient) { yield return null; } + if (!IsConnectedClient && NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + // TODO: Possibly add a callback for users to be notified of this condition here? + NetworkLog.LogWarning($"[ApprovalTimeout] Client timed out! You might need to increase the {nameof(NetworkConfig.ClientConnectionBufferTimeout)} duration. Approval Check Start: {timeStarted} | Approval Check Stopped: {Time.realtimeSinceStartup}"); + } + if (!IsListening) { yield break; diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs index 80997c80d9..bce5389b1c 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs @@ -201,6 +201,13 @@ internal void ClientFinishedSceneEvent(ulong clientId) /// private bool HasFinished() { + // If the network session is terminated/terminating then finish tracking + // this scene event + if (!IsNetworkSessionActive()) + { + return true; + } + // Clients skip over this foreach (var clientStatus in ClientsProcessingSceneEvent) { @@ -222,11 +229,25 @@ internal void SetAsyncOperation(AsyncOperation asyncOperation) m_AsyncOperation = asyncOperation; m_AsyncOperation.completed += new Action(asyncOp2 => { - OnSceneEventCompleted?.Invoke(SceneEventId); + // Don't invoke the callback if the network session is disconnected + // during a SceneEventProgress + if (IsNetworkSessionActive()) + { + OnSceneEventCompleted?.Invoke(SceneEventId); + } + + // Go ahead and try finishing even if the network session is terminated/terminating + // as we might need to stop the coroutine TryFinishingSceneEventProgress(); }); } + + internal bool IsNetworkSessionActive() + { + return m_NetworkManager != null && m_NetworkManager.IsListening && !m_NetworkManager.ShutdownInProgress; + } + /// /// Will try to finish the current scene event in progress as long as /// all conditions are met. @@ -235,10 +256,15 @@ internal void TryFinishingSceneEventProgress() { if (HasFinished() || HasTimedOut()) { - OnComplete?.Invoke(this); - m_NetworkManager.SceneManager.SceneEventProgressTracking.Remove(Guid); - m_NetworkManager.OnClientDisconnectCallback -= OnClientDisconnectCallback; - if (m_NetworkManager.IsServer) + // Don't attempt to finalize this scene event if we are no longer listening or a shutdown is in progress + if (IsNetworkSessionActive()) + { + OnComplete?.Invoke(this); + m_NetworkManager.SceneManager.SceneEventProgressTracking.Remove(Guid); + m_NetworkManager.OnClientDisconnectCallback -= OnClientDisconnectCallback; + } + + if (m_TimeOutCoroutine != null) { m_NetworkManager.StopCoroutine(m_TimeOutCoroutine); } diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventProgressTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventProgressTests.cs index 575a875763..5721a2dae6 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventProgressTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventProgressTests.cs @@ -13,6 +13,8 @@ namespace TestProject.RuntimeTests public class SceneEventProgressTests : NetcodeIntegrationTest { private const string k_SceneUsedToGetAsyncOperation = "EmptyScene"; + private const string k_SceneUsedToGetClientAsyncOperation = "UnitTestBaseScene"; + protected override int NumberOfClients => 4; private bool m_SceneEventProgressCompleted; @@ -51,6 +53,7 @@ private void StartNewSceneEventProgress() m_CurrentSceneEventProgress.SetAsyncOperation(SceneManager.LoadSceneAsync(k_SceneUsedToGetAsyncOperation, LoadSceneMode.Additive)); } + private void MockServerLoadedSene(Scene scene, LoadSceneMode loadSceneMode) { if (scene.name == k_SceneUsedToGetAsyncOperation) @@ -155,6 +158,31 @@ public IEnumerator ClientsDisconnectDuring() VerifyClientsThatCompleted(); } + [UnityTest] + public IEnumerator ClientsShutdownDuring() + { + StartNewSceneEventProgress(); + + for (int i = 0; i < NumberOfClients; i++) + { + var currentClientNetworkManager = m_ClientNetworkManagers[i]; + // Two clients will shutdown + var clientFinished = i % 2 == 0; + SetClientFinished(currentClientNetworkManager.LocalClientId, clientFinished); + + if (!clientFinished) + { + currentClientNetworkManager.Shutdown(); + } + // wait anywhere from 100-500ms until processing next client + var randomWaitPeriod = Random.Range(0.1f, 0.5f); + yield return new WaitForSeconds(randomWaitPeriod); + } + yield return WaitForConditionOrTimeOut(() => m_SceneEventProgressCompleted); + AssertOnTimeout($"Timed out waiting for SceneEventProgress to time out!"); + VerifyClientsThatCompleted(); + } + /// /// This verifies that SceneEventProgress will still complete /// even when clients late join. @@ -163,16 +191,13 @@ public IEnumerator ClientsDisconnectDuring() public IEnumerator ClientsLateJoinDuring() { StartNewSceneEventProgress(); - for (int i = 0; i < NumberOfClients; i++) { // Two clients will connect during a SceneEventProgress var shouldNewClientJoin = i % 2 == 0; var currentClientNetworkManager = m_ClientNetworkManagers[i]; - // All connected clients will finish their SceneEventProgress SetClientFinished(currentClientNetworkManager.LocalClientId, true); - if (shouldNewClientJoin) { yield return CreateAndStartNewClient(); @@ -181,11 +206,84 @@ public IEnumerator ClientsLateJoinDuring() var randomWaitPeriod = Random.Range(0.1f, 0.5f); yield return new WaitForSeconds(randomWaitPeriod); } - yield return WaitForConditionOrTimeOut(() => m_SceneEventProgressCompleted); AssertOnTimeout($"Timed out waiting for SceneEventProgress to finish!"); + VerifyClientsThatCompleted(); + } + + private List m_ClientsThatTimedOutAndFinished = new List(); + + private SceneEventProgress StartClientSceneEventProgress(NetworkManager networkManager) + { + var sceneEventProgress = new SceneEventProgress(networkManager, SceneEventProgressStatus.Started); + // Mock Scene Loading Event for mocking timed out client + m_CurrentSceneEventProgress.SceneEventId = (uint)networkManager.LocalClientId; + var asyncOperation = SceneManager.LoadSceneAsync(k_SceneUsedToGetClientAsyncOperation, LoadSceneMode.Additive); + asyncOperation.completed += new System.Action(asyncOp2 => + { + m_ClientsThatTimedOutAndFinished.Add(networkManager.LocalClientId); + }); + + m_CurrentSceneEventProgress.SetAsyncOperation(asyncOperation); + return sceneEventProgress; + } + + private Dictionary m_ClientsToFinishAfterDisconnecting = new Dictionary(); + private bool TimedOutClientsFinishedSceneEventProgress() + { + // Now, verify all "mock timed out" clients still finished their SceneEventProgress + foreach (var entry in m_ClientsToFinishAfterDisconnecting) + { + if (!m_ClientsThatTimedOutAndFinished.Contains(entry.Key.LocalClientId)) + { + return false; + } + } + return true; + } + + /// + /// This mocks a client timing out during a SceneEventProgress + /// + [UnityTest] + public IEnumerator ClientsMockTimeOutDuring() + { + m_ClientsThatTimedOutAndFinished.Clear(); + m_ClientsToFinishAfterDisconnecting.Clear(); + StartNewSceneEventProgress(); + + for (int i = 0; i < NumberOfClients; i++) + { + // Two clients will mock timing out during a SceneEventProgress + var shouldClientFinish = i % 2 == 0; + var currentClientNetworkManager = m_ClientNetworkManagers[i]; + + // Set whether the client should or should not have finished + SetClientFinished(currentClientNetworkManager.LocalClientId, shouldClientFinish); + if (!shouldClientFinish) + { + var sceneEventProgress = StartClientSceneEventProgress(currentClientNetworkManager); + m_ClientsToFinishAfterDisconnecting.Add(currentClientNetworkManager, sceneEventProgress); + currentClientNetworkManager.Shutdown(); + } + } + + yield return WaitForConditionOrTimeOut(() => m_SceneEventProgressCompleted); + AssertOnTimeout($"Timed out waiting for SceneEventProgress to finish!"); VerifyClientsThatCompleted(); + + yield return WaitForConditionOrTimeOut(TimedOutClientsFinishedSceneEventProgress); + if (s_GlobalTimeoutHelper.TimedOut) + { + foreach (var entry in m_ClientsToFinishAfterDisconnecting) + { + Assert.IsTrue(m_ClientsThatTimedOutAndFinished.Contains(entry.Key.LocalClientId), $"Client-{entry.Key.LocalClientId} did not complete its {nameof(SceneEventProgress)}!"); + // Now, as a final check we try to finish the "mock" timed out client's scene event progress + entry.Value.TryFinishingSceneEventProgress(); + } + } + m_ClientsToFinishAfterDisconnecting.Clear(); } } } From 7b9dcb6b60af177871856df491d33998c02bb940 Mon Sep 17 00:00:00 2001 From: Kitty Draper <284434+ShadauxCat@users.noreply.github.com> Date: Thu, 13 Oct 2022 18:41:21 -0500 Subject: [PATCH 101/138] fix: INetworkMessageILPP failing to output registration code for network messages (#2256) * fix Resolves the issue where NetworkMessageILPP was failing for users using different API Compatibility Settings. --- .../Editor/CodeGen/CodeGenHelpers.cs | 62 ++++++++++--------- .../Editor/CodeGen/INetworkMessageILPP.cs | 35 ++++------- .../Editor/CodeGen/NetworkBehaviourILPP.cs | 2 +- 3 files changed, 46 insertions(+), 53 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs index d76aef52db..7571857a54 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs @@ -398,20 +398,21 @@ public static AssemblyDefinition AssemblyDefinitionFor(ICompiledAssembly compile return assemblyDefinition; } - public static (ModuleDefinition DotnetModule, ModuleDefinition UnityModule, ModuleDefinition NetcodeModule) FindBaseModules(AssemblyDefinition assemblyDefinition, PostProcessorAssemblyResolver assemblyResolver) + private static void SearchForBaseModulesRecursive(AssemblyDefinition assemblyDefinition, PostProcessorAssemblyResolver assemblyResolver, ref ModuleDefinition unityModule, ref ModuleDefinition netcodeModule, HashSet visited) { - ModuleDefinition dotnetModule = null; - ModuleDefinition unityModule = null; - ModuleDefinition netcodeModule = null; foreach (var module in assemblyDefinition.Modules) { - if (dotnetModule == null && module.Name == DotnetModuleName) + if (module == null) { - dotnetModule = module; continue; } + if (unityModule != null && netcodeModule != null) + { + return; + } + if (unityModule == null && module.Name == UnityModuleName) { unityModule = module; @@ -424,41 +425,46 @@ public static (ModuleDefinition DotnetModule, ModuleDefinition UnityModule, Modu continue; } } - - if (dotnetModule != null && unityModule != null && netcodeModule != null) + if (unityModule != null && netcodeModule != null) { - return (dotnetModule, unityModule, netcodeModule); + return; } foreach (var assemblyNameReference in assemblyDefinition.MainModule.AssemblyReferences) { - foreach (var module in assemblyResolver.Resolve(assemblyNameReference).Modules) + if (assemblyNameReference == null) { - if (dotnetModule == null && module.Name == DotnetModuleName) - { - dotnetModule = module; - continue; - } - if (unityModule == null && module.Name == UnityModuleName) - { - unityModule = module; - continue; - } + continue; + } + if (visited.Contains(assemblyNameReference.Name)) + { + continue; + } - if (netcodeModule == null && module.Name == NetcodeModuleName) - { - netcodeModule = module; - continue; - } + visited.Add(assemblyNameReference.Name); + + var assembly = assemblyResolver.Resolve(assemblyNameReference); + if (assembly == null) + { + continue; } + SearchForBaseModulesRecursive(assembly, assemblyResolver, ref unityModule, ref netcodeModule, visited); - if (dotnetModule != null && unityModule != null && netcodeModule != null) + if (unityModule != null && netcodeModule != null) { - return (dotnetModule, unityModule, netcodeModule); + return; } } + } + + public static (ModuleDefinition UnityModule, ModuleDefinition NetcodeModule) FindBaseModules(AssemblyDefinition assemblyDefinition, PostProcessorAssemblyResolver assemblyResolver) + { + ModuleDefinition unityModule = null; + ModuleDefinition netcodeModule = null; + var visited = new HashSet(); + SearchForBaseModulesRecursive(assemblyDefinition, assemblyResolver, ref unityModule, ref netcodeModule, visited); - return (dotnetModule, unityModule, netcodeModule); + return (unityModule, netcodeModule); } } } diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs index d1d63b2e6f..30b6f0b05e 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs @@ -38,13 +38,7 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) } // modules - (m_DotnetModule, _, m_NetcodeModule) = CodeGenHelpers.FindBaseModules(assemblyDefinition, m_AssemblyResolver); - - if (m_DotnetModule == null) - { - m_Diagnostics.AddError($"Cannot find .NET module: {CodeGenHelpers.DotnetModuleName}"); - return null; - } + (_, m_NetcodeModule) = CodeGenHelpers.FindBaseModules(assemblyDefinition, m_AssemblyResolver); if (m_NetcodeModule == null) { @@ -104,7 +98,6 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics); } - private ModuleDefinition m_DotnetModule; private ModuleDefinition m_NetcodeModule; private PostProcessorAssemblyResolver m_AssemblyResolver; @@ -121,22 +114,16 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) private bool ImportReferences(ModuleDefinition moduleDefinition) { - TypeDefinition typeTypeDef = null; - TypeDefinition listTypeDef = null; - foreach (var dotnetTypeDef in m_DotnetModule.GetAllTypes()) - { - if (typeTypeDef == null && dotnetTypeDef.Name == typeof(Type).Name) - { - typeTypeDef = dotnetTypeDef; - continue; - } - - if (listTypeDef == null && dotnetTypeDef.Name == typeof(List<>).Name) - { - listTypeDef = dotnetTypeDef; - continue; - } - } + // Different environments seem to have different situations... + // Some have these definitions in netstandard.dll... + // some seem to have them elsewhere... + // Since they're standard .net classes they're not going to cause + // the same issues as referencing other assemblies, in theory, since + // the definitions should be standard and consistent across platforms + // (i.e., there's no #if UNITY_EDITOR in them that could create + // invalid IL code) + TypeDefinition typeTypeDef = moduleDefinition.ImportReference(typeof(Type)).Resolve(); + TypeDefinition listTypeDef = moduleDefinition.ImportReference(typeof(List<>)).Resolve(); TypeDefinition messageHandlerTypeDef = null; TypeDefinition messageWithHandlerTypeDef = null; diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index 24d378c6aa..ce7d3bb08f 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -44,7 +44,7 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) } // modules - (_, m_UnityModule, m_NetcodeModule) = CodeGenHelpers.FindBaseModules(assemblyDefinition, m_AssemblyResolver); + (m_UnityModule, m_NetcodeModule) = CodeGenHelpers.FindBaseModules(assemblyDefinition, m_AssemblyResolver); if (m_UnityModule == null) { From 05556132b0becf4986ec39ef65a69d22c54bfb74 Mon Sep 17 00:00:00 2001 From: ashwini <36935028+ashwinimurt@users.noreply.github.com> Date: Fri, 14 Oct 2022 10:45:33 -0700 Subject: [PATCH 102/138] chore: Update changelog (#2258) --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 86b55d75a6..cdc9fdb43c 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -7,7 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). -## [Unreleased] +## [1.1.0] - 2022-10-19 ### Added From 406e0c2260cd76b615177a116dd1ce2f74f38aec Mon Sep 17 00:00:00 2001 From: Kitty Draper <284434+ShadauxCat@users.noreply.github.com> Date: Fri, 14 Oct 2022 14:31:04 -0500 Subject: [PATCH 103/138] chore: Added XMLDoc for HiddenScriptEditor.OnInspectorGUI() (#2259) --- com.unity.netcode.gameobjects/Editor/HiddenScriptEditor.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/com.unity.netcode.gameobjects/Editor/HiddenScriptEditor.cs b/com.unity.netcode.gameobjects/Editor/HiddenScriptEditor.cs index 1236fb8332..592580ea33 100644 --- a/com.unity.netcode.gameobjects/Editor/HiddenScriptEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/HiddenScriptEditor.cs @@ -13,6 +13,10 @@ namespace Unity.Netcode.Editor public class HiddenScriptEditor : UnityEditor.Editor { private static readonly string[] k_HiddenFields = { "m_Script" }; + + /// + /// Draws inspector properties without the script field. + /// public override void OnInspectorGUI() { EditorGUI.BeginChangeCheck(); From 0d7a57ab0588d71b44cb5e1228d704eba7500662 Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Mon, 17 Oct 2022 11:14:27 -0400 Subject: [PATCH 104/138] fix: Letting NetworkVariables be serialized even if they're not going to be sent (#2263) fix: Letting NetworkVariables be serialized even if they're not going to be sent. This prevents NetworkList synchronization issues --- .../Runtime/Core/NetworkBehaviourUpdater.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs index daf3d95479..e20fe90d54 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs @@ -42,10 +42,6 @@ internal void NetworkBehaviourUpdate(NetworkManager networkManager) for (int i = 0; i < networkManager.ConnectedClientsList.Count; i++) { var client = networkManager.ConnectedClientsList[i]; - if (networkManager.IsHost && client.ClientId == networkManager.LocalClientId) - { - continue; - } if (dirtyObj.IsNetworkVisibleTo(client.ClientId)) { From 924d3da6c9d9d7aff03a25e77d17f8f0832a516f Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 17 Oct 2022 21:19:45 -0500 Subject: [PATCH 105/138] fix: NetworkManager ApprovalTimeout should not depend upon client synchronization (#2261) * fix This fix separates the IsConnectedClient from the approval process by adding an IsApproved flag. This also has a fix to prevent the domain backup error during serialization. * test Added the ability to bypass the entire NetcodeIntegrationTest connection approval process after the server and clients have been started. This allows us to now use NetcodeIntegrationTest for unique connection oriented tests without being bound to the asserts if not all clients connected properly. Refactored for ConnectionApprovalTimeoutTests to use the added m_BypassConnectionTimeout to bypass the waiting for clients to connect. It still uses the message hook catch technique to simulate the timeout scenarios where either a server detects a transport connection but never receives a connection request or a client sends the connection request but never receives approval for the connection. --- com.unity.netcode.gameobjects/CHANGELOG.md | 13 ++ .../Components/NetworkAnimator.cs | 4 + .../Runtime/Core/NetworkManager.cs | 106 ++++++++++------ .../Messages/ConnectionApprovedMessage.cs | 1 + .../Runtime/NetcodeIntegrationTest.cs | 51 +++++--- .../Runtime/ConnectionApprovalTimeoutTests.cs | 117 +++++++++--------- 6 files changed, 176 insertions(+), 116 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index cdc9fdb43c..1c7ad71184 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -7,6 +7,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). +## [Unreleased] + +### Added +- Added `NetworkManager.IsApproved` flag that is set to `true` a client has been approved.(#2261) + +### Changed + + +### Fixed + +- Fixed `NetworkManager.ApprovalTimeout` will not timeout due to slower client synchronization times as it now uses the added `NetworkManager.IsApproved` flag to determined if the client has been approved or not.(#2261) + + ## [1.1.0] - 2022-10-19 ### Added diff --git a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs index b8a21282c3..258b64ea0b 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs @@ -232,6 +232,10 @@ private void BuildDestinationToTransitionInfoTable() private void BuildTransitionStateInfoList() { #if UNITY_EDITOR + if (UnityEditor.EditorApplication.isUpdating) + { + return; + } TransitionStateInfoList = new List(); var animatorController = m_Animator.runtimeAnimatorController as AnimatorController; if (animatorController == null) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index badf7f2408..6941813373 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -366,10 +366,22 @@ public IReadOnlyList ConnectedClientsIds public bool IsListening { get; internal set; } /// - /// Gets if we are connected as a client + /// When true, the client is connected, approved, and synchronized with + /// the server. /// public bool IsConnectedClient { get; internal set; } + /// + /// Is true when the client has been approved. + /// + /// + /// This only reflects the client's approved status and does not mean the client + /// has finished the connection and synchronization process. The server-host will + /// always be approved upon being starting the + /// + /// + public bool IsApproved { get; internal set; } + /// /// Can be used to determine if the is currently shutting itself down /// @@ -882,6 +894,8 @@ private void Initialize(bool server) return; } + IsApproved = false; + ComponentFactory.SetDefaults(); if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) @@ -1025,7 +1039,6 @@ public bool StartServer() } Initialize(true); - IsServer = true; IsClient = false; IsListening = true; @@ -1038,6 +1051,7 @@ public bool StartServer() SpawnManager.ServerSpawnSceneObjectsOnStartSweep(); OnServerStarted?.Invoke(); + IsApproved = true; return true; } else @@ -1159,6 +1173,7 @@ public bool StartHost() } response.Approved = true; + IsApproved = true; HandleConnectionApproval(ServerClientId, response); } else @@ -1420,6 +1435,7 @@ internal void ShutdownInternal() } IsConnectedClient = false; + IsApproved = false; // We need to clean up NetworkObjects before we reset the IsServer // and IsClient properties. This provides consistency of these two @@ -1656,59 +1672,71 @@ private void SendConnectionRequest() private IEnumerator ApprovalTimeout(ulong clientId) { - if (IsServer) + var timeStarted = IsServer ? LocalTime.TimeAsFloat : Time.realtimeSinceStartup; + var timedOut = false; + var connectionApproved = false; + var connectionNotApproved = false; + var timeoutMarker = timeStarted + NetworkConfig.ClientConnectionBufferTimeout; + + while (IsListening && !ShutdownInProgress && !timedOut && !connectionApproved) { - NetworkTime timeStarted = LocalTime; + yield return null; + // Check if we timed out + timedOut = timeoutMarker < (IsServer ? LocalTime.TimeAsFloat : Time.realtimeSinceStartup); - //We yield every frame incase a pending client disconnects and someone else gets its connection id - while (IsListening && (LocalTime - timeStarted).Time < NetworkConfig.ClientConnectionBufferTimeout && PendingClients.ContainsKey(clientId)) + if (IsServer) { - yield return null; - } + // When the client is no longer in the pending clients list and is in the connected clients list + // it has been approved + connectionApproved = !PendingClients.ContainsKey(clientId) && ConnectedClients.ContainsKey(clientId); - if (!IsListening) - { - yield break; + // For the server side, if the client is in neither list then it was declined or the client disconnected + connectionNotApproved = !PendingClients.ContainsKey(clientId) && !ConnectedClients.ContainsKey(clientId); } - - if (PendingClients.ContainsKey(clientId) && !ConnectedClients.ContainsKey(clientId)) + else { - // Timeout - if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) - { - NetworkLog.LogInfo($"Client {clientId} Handshake Timed Out"); - } - - DisconnectClient(clientId); + connectionApproved = IsApproved; } } - else + + // Exit coroutine if we are no longer listening or a shutdown is in progress (client or server) + if (!IsListening || ShutdownInProgress) { - float timeStarted = Time.realtimeSinceStartup; - //We yield every frame in case a pending client disconnects and someone else gets its connection id - while (IsListening && (Time.realtimeSinceStartup - timeStarted) < NetworkConfig.ClientConnectionBufferTimeout && !IsConnectedClient) - { - yield return null; - } + yield break; + } - if (!IsConnectedClient && NetworkLog.CurrentLogLevel <= LogLevel.Normal) + // If the client timed out or was not approved + if (timedOut || connectionNotApproved) + { + // Timeout + if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) { - // TODO: Possibly add a callback for users to be notified of this condition here? - NetworkLog.LogWarning($"[ApprovalTimeout] Client timed out! You might need to increase the {nameof(NetworkConfig.ClientConnectionBufferTimeout)} duration. Approval Check Start: {timeStarted} | Approval Check Stopped: {Time.realtimeSinceStartup}"); + if (timedOut) + { + if (IsServer) + { + // Log a warning that the transport detected a connection but then did not receive a follow up connection request message. + // (hacking or something happened to the server's network connection) + NetworkLog.LogWarning($"Server detected a transport connection from Client-{clientId}, but timed out waiting for the connection request message."); + } + else + { + // We only provide informational logging for the client side + NetworkLog.LogInfo("Timed out waiting for the server to approve the connection request."); + } + } + else if (connectionNotApproved) + { + NetworkLog.LogInfo($"Client-{clientId} was either denied approval or disconnected while being approved."); + } } - if (!IsListening) + if (IsServer) { - yield break; + DisconnectClient(clientId); } - - if (!IsConnectedClient) + else { - // Timeout - if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) - { - NetworkLog.LogInfo("Server Handshake Timed Out"); - } Shutdown(true); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index 14c4ac3d97..0a685a731d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -79,6 +79,7 @@ public void Handle(ref NetworkContext context) networkManager.NetworkTickSystem.Reset(networkManager.NetworkTimeSystem.LocalTime, networkManager.NetworkTimeSystem.ServerTime); networkManager.LocalClient = new NetworkClient() { ClientId = networkManager.LocalClientId }; + networkManager.IsApproved = true; // Only if scene management is disabled do we handle NetworkObject synchronization at this point if (!networkManager.NetworkConfig.EnableSceneManagement) diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index 044a5358a4..c2f3dfe557 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -131,6 +131,18 @@ public enum HostOrServer protected bool m_EnableVerboseDebug { get; set; } + /// + /// When set to true, this will bypass the entire + /// wait for clients to connect process. + /// + /// + /// CAUTION: + /// Setting this to true will bypass other helper + /// identification related code, so this should only + /// be used for connection failure oriented testing + /// + protected bool m_BypassConnectionTimeout { get; set; } + /// /// Used to display the various integration test /// stages and can be used to log verbose information @@ -455,31 +467,36 @@ protected IEnumerator StartServerAndClients() // Notification that the server and clients have been started yield return OnStartedServerAndClients(); - // Wait for all clients to connect - yield return WaitForClientsConnectedOrTimeOut(); - AssertOnTimeout($"{nameof(StartServerAndClients)} timed out waiting for all clients to be connected!"); - - if (m_UseHost || m_ServerNetworkManager.IsHost) + // When true, we skip everything else (most likely a connection oriented test) + if (!m_BypassConnectionTimeout) { - // Add the server player instance to all m_ClientSidePlayerNetworkObjects entries - var serverPlayerClones = Object.FindObjectsOfType().Where((c) => c.IsPlayerObject && c.OwnerClientId == m_ServerNetworkManager.LocalClientId); - foreach (var playerNetworkObject in serverPlayerClones) + // Wait for all clients to connect + yield return WaitForClientsConnectedOrTimeOut(); + + AssertOnTimeout($"{nameof(StartServerAndClients)} timed out waiting for all clients to be connected!"); + + if (m_UseHost || m_ServerNetworkManager.IsHost) { - if (!m_PlayerNetworkObjects.ContainsKey(playerNetworkObject.NetworkManager.LocalClientId)) + // Add the server player instance to all m_ClientSidePlayerNetworkObjects entries + var serverPlayerClones = Object.FindObjectsOfType().Where((c) => c.IsPlayerObject && c.OwnerClientId == m_ServerNetworkManager.LocalClientId); + foreach (var playerNetworkObject in serverPlayerClones) { - m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary()); + if (!m_PlayerNetworkObjects.ContainsKey(playerNetworkObject.NetworkManager.LocalClientId)) + { + m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary()); + } + m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(m_ServerNetworkManager.LocalClientId, playerNetworkObject); } - m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(m_ServerNetworkManager.LocalClientId, playerNetworkObject); } - } - ClientNetworkManagerPostStartInit(); + ClientNetworkManagerPostStartInit(); - // Notification that at this time the server and client(s) are instantiated, - // started, and connected on both sides. - yield return OnServerAndClientsConnected(); + // Notification that at this time the server and client(s) are instantiated, + // started, and connected on both sides. + yield return OnServerAndClientsConnected(); - VerboseDebug($"Exiting {nameof(StartServerAndClients)}"); + VerboseDebug($"Exiting {nameof(StartServerAndClients)}"); + } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs index bca5bd8271..9f58c59597 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs @@ -1,5 +1,4 @@ using System.Collections; -using System.Linq; using System.Text.RegularExpressions; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; @@ -8,97 +7,95 @@ namespace Unity.Netcode.RuntimeTests { - [TestFixture(true)] - [TestFixture(false)] + [TestFixture(ApprovalTimedOutTypes.ServerDoesNotRespond)] + [TestFixture(ApprovalTimedOutTypes.ClientDoesNotRequest)] public class ConnectionApprovalTimeoutTests : NetcodeIntegrationTest { protected override int NumberOfClients => 1; - protected override bool CanStartServerAndClients() => false; + public enum ApprovalTimedOutTypes + { + ClientDoesNotRequest, + ServerDoesNotRespond + } - private bool m_UseSceneManagement; - public ConnectionApprovalTimeoutTests(bool useSceneManagement) + private ApprovalTimedOutTypes m_ApprovalFailureType; + + public ConnectionApprovalTimeoutTests(ApprovalTimedOutTypes approvalFailureType) { - m_UseSceneManagement = useSceneManagement; + m_ApprovalFailureType = approvalFailureType; } // Must be >= 2 since this is an int value and the test waits for timeout - 1 to try to verify it doesn't // time out early - private const int k_TestTimeoutPeriod = 2; + private const int k_TestTimeoutPeriod = 1; + + private Regex m_ExpectedLogMessage; + private LogType m_LogType; - private void Start() + + protected override IEnumerator OnSetup() { - m_ServerNetworkManager.NetworkConfig.EnableSceneManagement = m_UseSceneManagement; - m_ClientNetworkManagers[0].NetworkConfig.EnableSceneManagement = m_UseSceneManagement; - if (!NetcodeIntegrationTestHelpers.Start(false, m_ServerNetworkManager, m_ClientNetworkManagers)) - { - Debug.LogError("Failed to start instances"); - Assert.Fail("Failed to start instances"); - } + m_BypassConnectionTimeout = true; + return base.OnSetup(); } - [UnityTest] - public IEnumerator WhenClientDoesntRequestApproval_ServerTimesOut() + protected override IEnumerator OnTearDown() { - Start(); - var hook = new MessageCatcher(m_ServerNetworkManager); - m_ServerNetworkManager.MessagingSystem.Hook(hook); ; + m_BypassConnectionTimeout = false; + return base.OnTearDown(); + } + protected override void OnServerAndClientsCreated() + { m_ServerNetworkManager.NetworkConfig.ClientConnectionBufferTimeout = k_TestTimeoutPeriod; m_ServerNetworkManager.LogLevel = LogLevel.Developer; + m_ClientNetworkManagers[0].NetworkConfig.ClientConnectionBufferTimeout = k_TestTimeoutPeriod; m_ClientNetworkManagers[0].LogLevel = LogLevel.Developer; - - yield return new WaitForSeconds(m_ServerNetworkManager.NetworkConfig.ClientConnectionBufferTimeout - 1); - - Assert.AreEqual(0, m_ServerNetworkManager.ConnectedClients.Count); - Assert.AreEqual(1, m_ServerNetworkManager.PendingClients.Count); - - var expectedLogMessage = new Regex($"Client {m_ServerNetworkManager.PendingClients.FirstOrDefault().Key} Handshake Timed Out"); - - NetcodeLogAssert.LogWasNotReceived(LogType.Log, expectedLogMessage); - - yield return new WaitForSeconds(2); - - NetcodeLogAssert.LogWasReceived(LogType.Log, expectedLogMessage); - - Assert.AreEqual(0, m_ServerNetworkManager.ConnectedClients.Count); - Assert.AreEqual(0, m_ServerNetworkManager.PendingClients.Count); + base.OnServerAndClientsCreated(); } - [UnityTest] - public IEnumerator WhenServerDoesntRespondWithApproval_ClientTimesOut() + protected override IEnumerator OnStartedServerAndClients() { - Start(); - - if (m_UseSceneManagement) + if (m_ApprovalFailureType == ApprovalTimedOutTypes.ServerDoesNotRespond) { - var sceneEventHook = new MessageCatcher(m_ClientNetworkManagers[0]); - m_ClientNetworkManagers[0].MessagingSystem.Hook(sceneEventHook); + // We catch (don't process) the incoming approval message to simulate the server not sending the approved message in time + m_ClientNetworkManagers[0].MessagingSystem.Hook(new MessageCatcher(m_ClientNetworkManagers[0])); + m_ExpectedLogMessage = new Regex("Timed out waiting for the server to approve the connection request."); + m_LogType = LogType.Log; } else { - var approvalHook = new MessageCatcher(m_ClientNetworkManagers[0]); - m_ClientNetworkManagers[0].MessagingSystem.Hook(approvalHook); - } - - m_ClientNetworkManagers[0].NetworkConfig.ClientConnectionBufferTimeout = k_TestTimeoutPeriod; - m_ServerNetworkManager.LogLevel = LogLevel.Developer; - m_ClientNetworkManagers[0].LogLevel = LogLevel.Developer; + // We catch (don't process) the incoming connection request message to simulate a transport connection but the client never + // sends (or takes too long to send) the connection request. + m_ServerNetworkManager.MessagingSystem.Hook(new MessageCatcher(m_ServerNetworkManager)); - yield return new WaitForSeconds(m_ClientNetworkManagers[0].NetworkConfig.ClientConnectionBufferTimeout - 1); + // For this test, we know the timed out client will be Client-1 + m_ExpectedLogMessage = new Regex("Server detected a transport connection from Client-1, but timed out waiting for the connection request message."); + m_LogType = LogType.Warning; + } + yield return null; + } - Assert.IsFalse(m_ClientNetworkManagers[0].IsConnectedClient); - Assert.IsTrue(m_ClientNetworkManagers[0].IsListening); + [UnityTest] + public IEnumerator ValidateApprovalTimeout() + { + // Delay for half of the wait period + yield return new WaitForSeconds(k_TestTimeoutPeriod * 0.5f); - var expectedLogMessage = new Regex("Server Handshake Timed Out"); - NetcodeLogAssert.LogWasNotReceived(LogType.Log, expectedLogMessage); + // Verify we haven't received the time out message yet + NetcodeLogAssert.LogWasNotReceived(LogType.Log, m_ExpectedLogMessage); - yield return new WaitForSeconds(2); + // Wait for 3/4s of the time out period to pass (totaling 1.25x the wait period) + yield return new WaitForSeconds(k_TestTimeoutPeriod * 0.75f); - NetcodeLogAssert.LogWasReceived(LogType.Log, expectedLogMessage); + // We should have the test relative log message by this time. + NetcodeLogAssert.LogWasReceived(m_LogType, m_ExpectedLogMessage); - Assert.IsFalse(m_ClientNetworkManagers[0].IsConnectedClient); - Assert.IsFalse(m_ClientNetworkManagers[0].IsListening); + // It should only have the host client connected + Assert.AreEqual(1, m_ServerNetworkManager.ConnectedClients.Count, $"Expected only one client when there were {m_ServerNetworkManager.ConnectedClients.Count} clients connected!"); + Assert.AreEqual(0, m_ServerNetworkManager.PendingClients.Count, $"Expected no pending clients when there were {m_ServerNetworkManager.PendingClients.Count} pending clients!"); + Assert.True(!m_ClientNetworkManagers[0].IsApproved, $"Expected the client to not have been approved, but it was!"); } } } From b5dda2764470fb145fc2f6bc32bc385c25b60ecd Mon Sep 17 00:00:00 2001 From: ashwini <36935028+ashwinimurt@users.noreply.github.com> Date: Tue, 18 Oct 2022 11:36:21 -0700 Subject: [PATCH 106/138] chore: update changelog for 1.1 (#2264) --- com.unity.netcode.gameobjects/CHANGELOG.md | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 1c7ad71184..3acbe8c62c 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -7,23 +7,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). -## [Unreleased] - -### Added -- Added `NetworkManager.IsApproved` flag that is set to `true` a client has been approved.(#2261) - -### Changed - - -### Fixed - -- Fixed `NetworkManager.ApprovalTimeout` will not timeout due to slower client synchronization times as it now uses the added `NetworkManager.IsApproved` flag to determined if the client has been approved or not.(#2261) - - -## [1.1.0] - 2022-10-19 +## [1.1.0] - 2022-10-21 ### Added +- Added `NetworkManager.IsApproved` flag that is set to `true` a client has been approved.(#2261) - `UnityTransport` now provides a way to set the Relay server data directly from the `RelayServerData` structure (provided by the Unity Transport package) throuh its `SetRelayServerData` method. This allows making use of the new APIs in UTP 1.3 that simplify integration of the Relay SDK. (#2235) - IPv6 is now supported for direct connections when using `UnityTransport`. (#2232) - Added WebSocket support when using UTP 2.0 with `UseWebSockets` property in the `UnityTransport` component of the `NetworkManager` allowing to pick WebSockets for communication. When building for WebGL, this selection happens automatically. (#2201) @@ -41,6 +29,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed `NetworkManager.ApprovalTimeout` will not timeout due to slower client synchronization times as it now uses the added `NetworkManager.IsApproved` flag to determined if the client has been approved or not.(#2261) - Fixed issue caused when changing ownership of objects hidden to some clients (#2242) - Fixed issue where an in-scene placed NetworkObject would not invoke NetworkBehaviour.OnNetworkSpawn if the GameObject was disabled when it was despawned. (#2239) - Fixed issue where clients were not rebuilding the `NetworkConfig` hash value for each unique connection request. (#2226) From cb28716b7982ba7c28cf6099ccb5153fc16c831c Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Wed, 19 Oct 2022 14:54:00 -0400 Subject: [PATCH 107/138] fix: NetworkManager property more robust on NetworkBehaviour (#2257) fix: more robust checks around NetworkObjects being null --- .../Runtime/Core/NetworkBehaviour.cs | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 7f95289319..c3d7245540 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -286,7 +286,18 @@ internal string GenerateObserverErrorMessage(ClientRpcParams clientRpcParams, ul /// Gets the NetworkManager that owns this NetworkBehaviour instance /// See note around `NetworkObject` for how there is a chicken / egg problem when we are not initialized /// - public NetworkManager NetworkManager => NetworkObject.NetworkManager; + public NetworkManager NetworkManager + { + get + { + if (NetworkObject?.NetworkManager != null) + { + return NetworkObject?.NetworkManager; + } + + return NetworkManager.Singleton; + } + } /// /// If a NetworkObject is assigned, it will return whether or not this NetworkObject @@ -349,9 +360,16 @@ public NetworkObject NetworkObject { get { - if (m_NetworkObject == null) + try + { + if (m_NetworkObject == null) + { + m_NetworkObject = GetComponentInParent(); + } + } + catch (Exception) { - m_NetworkObject = GetComponentInParent(); + return null; } // ShutdownInProgress check: From 8027546e4150cf824338a2233590dec68bc782fc Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Wed, 19 Oct 2022 15:52:04 -0400 Subject: [PATCH 108/138] fix: Don't allocate extraneous memory in FastBufferReader (#2265) * fix: Don't allocate extraneous memory in FastBufferReader * Add PR number to CHANGELOG entry --- com.unity.netcode.gameobjects/CHANGELOG.md | 6 ++++++ .../Runtime/Serialization/FastBufferReader.cs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 3acbe8c62c..8174ad23b5 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -7,6 +7,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). +## [Unreleased] + +### Fixed + +- Creating a `FastBufferReader` with `Allocator.None` will not result in extra memory being allocated for the buffer (since it's owned externally in that scenario). (#2265) + ## [1.1.0] - 2022-10-21 ### Added diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs index e052c95e4f..180311c8d2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs @@ -65,7 +65,7 @@ internal unsafe void CommitBitwiseReads(int amount) ReaderHandle* readerHandle = null; if (copyAllocator == Allocator.None) { - readerHandle = (ReaderHandle*)UnsafeUtility.Malloc(sizeof(ReaderHandle) + length, UnsafeUtility.AlignOf(), internalAllocator); + readerHandle = (ReaderHandle*)UnsafeUtility.Malloc(sizeof(ReaderHandle), UnsafeUtility.AlignOf(), internalAllocator); readerHandle->BufferPointer = buffer; readerHandle->Position = offset; } From 5062fcc04404e8eb7dcb9dfeb276c6577a8dcf30 Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Wed, 19 Oct 2022 16:40:51 -0400 Subject: [PATCH 109/138] docs: removing internal comment from summary (#2267) --- .../Runtime/Core/NetworkBehaviour.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index c3d7245540..8289676383 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -346,15 +346,14 @@ internal bool IsBehaviourEditable() m_NetworkObject.NetworkManager.IsServer; } - /// - /// Gets the NetworkObject that owns this NetworkBehaviour instance /// TODO: this needs an overhaul. It's expensive, it's ja little naive in how it looks for networkObject in /// its parent and worst, it creates a puzzle if you are a NetworkBehaviour wanting to see if you're live or not /// (e.g. editor code). All you want to do is find out if NetworkManager is null, but to do that you /// need NetworkObject, but if you try and grab NetworkObject and NetworkManager isn't up you'll get /// the warning below. This is why IsBehaviourEditable had to be created. Matt was going to re-do /// how NetworkObject works but it was close to the release and too risky to change - /// + /// + /// Gets the NetworkObject that owns this NetworkBehaviour instance /// public NetworkObject NetworkObject { From aaf3e42e656d219413297213a6cca4e15abdbdfc Mon Sep 17 00:00:00 2001 From: ashwini <36935028+ashwinimurt@users.noreply.github.com> Date: Thu, 20 Oct 2022 11:51:13 -0700 Subject: [PATCH 110/138] chore: Add CI for WebGL builds [MTT-4656] (#2247) chore: Add CI for WebGL --- .yamato/_run-all.yml | 19 +++++++++++++++++++ .yamato/webgl-build.yml | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 .yamato/webgl-build.yml diff --git a/.yamato/_run-all.yml b/.yamato/_run-all.yml index dfc0667efb..8bf0c61968 100644 --- a/.yamato/_run-all.yml +++ b/.yamato/_run-all.yml @@ -8,6 +8,14 @@ run_all_tests: - .yamato/mobile-build-and-test.yml#run_{{ projects.first.name }}_tests_{{ mobile_validation_editor }}_iOS - .yamato/mobile-build-and-test.yml#run_{{ projects.first.name }}_tests_{{ mobile_validation_editor }}_android # - .yamato/_run-all.yml#all_project_tests_standalone +{% for project in projects -%} +{% if project.name == "testproject" -%} +{% for editor in project.test_editors -%} + - .yamato/webgl-build.yml#build_{{ project.name }}_tests_{{ editor }}_webgl +{% endfor -%} +{% endif -%} +{% endfor -%} + {% for platform in test_platforms -%} {% for project in projects -%} {% for editor in project.test_editors -%} @@ -140,3 +148,14 @@ all_project_tests_mobile: {% endfor -%} {% endif -%} {% endfor -%} + +all_project_tests_webgl: + name: Build All Project Tests - WebGL + dependencies: +{% for project in projects -%} +{% if project.name == "testproject" -%} +{% for editor in project.test_editors -%} + - .yamato/webgl-build.yml#build_{{ project.name }}_tests_{{ editor }}_webgl +{% endfor -%} +{% endif -%} +{% endfor -%} diff --git a/.yamato/webgl-build.yml b/.yamato/webgl-build.yml new file mode 100644 index 0000000000..2a01a934b7 --- /dev/null +++ b/.yamato/webgl-build.yml @@ -0,0 +1,39 @@ +{% metadata_file .yamato/project.metafile %} +--- + +{% for project in projects -%} +{% if project.name == "testproject" -%} +{% for editor in project.test_editors -%} +build_{{ project.name }}_tests_{{ editor }}_webgl: + name: Build {{ project.name }} Tests - {{ editor }} - WebGL + agent: + type: Unity::VM + image: dots-ci/windows10:v1.493-auto + flavor: b1.xlarge + commands: + - pip install unity-downloader-cli --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple + - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr.bat --output utr.bat + - python .yamato/disable-burst-if-requested.py --project-path testproject --platform WebGL + - unity-downloader-cli -u {{ editor }} -c editor -c webgl -c il2cpp -w --fast + - | + set UTR_VERSION=0.12.0 + utr.bat --artifacts_path=artifacts --timeout=1800 --testproject={{ project.name }} --editor-location=.Editor --suite=playmode --platform=WebGL --build-only --player-save-path=build/players --extra-editor-arg=-batchmode --extra-editor-arg=-nographics --scripting-backend=il2cpp --extra-editor-arg="-cloudEnvironment staging" + artifacts: + logs: + paths: + - '*.log' + - '*.xml' + - artifacts/**/* + - testproject/Logs/** + - testproject/Library/*.log + - testproject/*.log + - testproject/Builds/*.log + - build/test-results/** + - artifacts/** + - build/players/** + variables: + CI: true + ENABLE_BURST_COMPILATION: False +{% endfor -%} +{% endif -%} +{% endfor -%} \ No newline at end of file From a4d199281305f57f661af825f32879bacca08298 Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Tue, 25 Oct 2022 11:17:41 -0400 Subject: [PATCH 111/138] fix: flushing send and receive queues upon disconnection (#2274) --- com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 6941813373..0eec452d19 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1348,6 +1348,7 @@ private void OnDestroy() private void DisconnectRemoteClient(ulong clientId) { var transportId = ClientIdToTransportId(clientId); + MessagingSystem.ProcessSendQueues(); NetworkConfig.NetworkTransport.DisconnectRemoteClient(transportId); } @@ -1828,6 +1829,10 @@ private void HandleRawTransportPoll(NetworkEvent networkEvent, ulong clientId, A NetworkLog.LogInfo($"Disconnect Event From {clientId}"); } + // Process the incoming message queue so that we get everything from the server disconnecting us + // or, if we are the server, so we got everything from that client. + MessagingSystem.ProcessIncomingMessageQueue(); + OnClientDisconnectCallback?.Invoke(clientId); if (IsServer) From 0d3718fead775be69cf85d3f6f316a6307ea866f Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 26 Oct 2022 18:55:06 -0500 Subject: [PATCH 112/138] fix: Migrate OnClientConnectedCallback invocation into StartHost [MTT-4972] (#2277) * fix Migrate InvokeOnClientConnectedCallback into StartHost (for host only). * test Updating the NetworkManagerTests to validate this PR, it also validates that this update does not impact anything when scene management is disabled. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Core/NetworkManager.cs | 6 +- .../Tests/Runtime/NetworkManagerTests.cs | 84 ++++++++++++++++++- .../NetworkObjectTestComponent.cs | 17 ++++ 4 files changed, 104 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 8174ad23b5..9a23eb0b8c 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -11,6 +11,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed the issue where `NetworkManager.OnClientConnectedCallback` was being invoked before in-scene placed `NetworkObject`s had been spawned when starting `NetworkManager` as a host. (#2277) - Creating a `FastBufferReader` with `Allocator.None` will not result in extra memory being allocated for the buffer (since it's owned externally in that scenario). (#2265) ## [1.1.0] - 2022-10-21 diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 0eec452d19..912f26d17d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1188,6 +1188,11 @@ public bool StartHost() SpawnManager.ServerSpawnSceneObjectsOnStartSweep(); + // This assures that any in-scene placed NetworkObject is spawned and + // any associated NetworkBehaviours' netcode related properties are + // set prior to invoking OnClientConnected. + InvokeOnClientConnectedCallback(LocalClientId); + OnServerStarted?.Invoke(); return true; @@ -2226,7 +2231,6 @@ internal void HandleConnectionApproval(ulong ownerClientId, ConnectionApprovalRe { LocalClient = client; SpawnManager.UpdateObservedNetworkObjects(ownerClientId); - InvokeOnClientConnectedCallback(ownerClientId); } if (!response.CreatePlayerObject || (response.PlayerPrefabHash == null && NetworkConfig.PlayerPrefab == null)) diff --git a/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs b/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs index fcdea8c711..078d954464 100644 --- a/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs @@ -1,17 +1,95 @@ using NUnit.Framework; +using UnityEngine; +using UnityEngine.SceneManagement; +using Unity.Netcode; using Unity.Netcode.TestHelpers.Runtime; - +using System.Collections; namespace TestProject.RuntimeTests { + [TestFixture(UseSceneManagement.SceneManagementDisabled)] + [TestFixture(UseSceneManagement.SceneManagementEnabled)] public class NetworkManagerTests : NetcodeIntegrationTest { - protected override int NumberOfClients => 1; + private const string k_SceneToLoad = "InSceneNetworkObject"; + protected override int NumberOfClients => 0; + + public enum UseSceneManagement + { + SceneManagementEnabled, + SceneManagementDisabled + } + + private bool m_EnableSceneManagement; + private NetworkObject m_NetworkObject; + private bool m_NetworkObjectWasSpawned; + private bool m_NetworkBehaviourIsHostWasSet; + private bool m_NetworkBehaviourIsClientWasSet; + private bool m_NetworkBehaviourIsServerWasSet; + private int m_NumberOfTimesInvoked; + private AsyncOperation m_AsyncOperation; + private NetworkObjectTestComponent m_NetworkObjectTestComponent; + + private bool m_UseSceneManagement; + + public NetworkManagerTests(UseSceneManagement useSceneManagement) + { + m_UseSceneManagement = useSceneManagement == UseSceneManagement.SceneManagementEnabled; + } + + private void OnClientConnectedCallback(NetworkObject networkObject, int numberOfTimesInvoked, bool isHost, bool isClient, bool isServer) + { + m_NetworkObject = networkObject; + m_NetworkObjectWasSpawned = networkObject.IsSpawned; + m_NetworkBehaviourIsHostWasSet = isHost; + m_NetworkBehaviourIsClientWasSet = isClient; + m_NetworkBehaviourIsServerWasSet = isServer; + m_NumberOfTimesInvoked = numberOfTimesInvoked; + } + + private bool TestComponentFound() + { + if (!m_AsyncOperation.isDone) + { + return false; + } + + m_NetworkObjectTestComponent = Object.FindObjectOfType(); + if (m_NetworkObjectTestComponent == null) + { + return false; + } + return true; + } + + protected override IEnumerator OnSetup() + { + m_AsyncOperation = SceneManager.LoadSceneAsync(k_SceneToLoad, LoadSceneMode.Additive); + yield return WaitForConditionOrTimeOut(TestComponentFound); + AssertOnTimeout($"Failed to find {nameof(NetworkObjectTestComponent)} after loading test scene {k_SceneToLoad}"); + } + + protected override IEnumerator OnTearDown() + { + SceneManager.UnloadSceneAsync(SceneManager.GetSceneByName(k_SceneToLoad)); + yield return s_DefaultWaitForTick; + } + + protected override void OnServerAndClientsCreated() + { + m_ServerNetworkManager.NetworkConfig.EnableSceneManagement = m_EnableSceneManagement; + m_NetworkObjectTestComponent.ConfigureClientConnected(m_ServerNetworkManager, OnClientConnectedCallback); + } [Test] - public void ValidateHostLocalClient() + public void ValidateHostSettings() { Assert.IsTrue(m_ServerNetworkManager.LocalClient != null); + Assert.IsTrue(m_NetworkObjectWasSpawned, $"{m_NetworkObject.name} was not spawned when OnClientConnectedCallback was invoked!"); + Assert.IsTrue(m_NetworkBehaviourIsHostWasSet, $"IsHost was not true when OnClientConnectedCallback was invoked!"); + Assert.IsTrue(m_NetworkBehaviourIsClientWasSet, $"IsClient was not true when OnClientConnectedCallback was invoked!"); + Assert.IsTrue(m_NumberOfTimesInvoked == 1, $"OnClientConnectedCallback was invoked {m_NumberOfTimesInvoked} as opposed to just once!"); + Assert.IsTrue(m_NetworkBehaviourIsServerWasSet, $"IsServer was not true when OnClientConnectedCallback was invoked!"); } } } diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectTestComponent.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectTestComponent.cs index 89c6b3728b..bf3674a7dd 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectTestComponent.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectTestComponent.cs @@ -27,6 +27,23 @@ public static void Reset() DespawnedInstances.Clear(); } + private Action m_ActionClientConnected; + private int m_NumberOfTimesInvoked; + public void ConfigureClientConnected(NetworkManager networkManager, Action clientConnected) + { + networkManager.OnClientConnectedCallback += NetworkManager_OnClientConnectedCallback; + m_ActionClientConnected = clientConnected; + } + + private void NetworkManager_OnClientConnectedCallback(ulong obj) + { + m_NumberOfTimesInvoked++; + if (m_ActionClientConnected != null) + { + m_ActionClientConnected.Invoke(NetworkObject, m_NumberOfTimesInvoked, IsHost, IsClient, IsServer); + } + } + // When disabling on spawning we only want this to happen on the initial spawn. // This is used to track this so the server only does it once upon spawning. public bool ObjectWasDisabledUponSpawn; From e931f4b31e68427020bdc8f55ea8a65f496dc9d0 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 28 Oct 2022 17:48:58 -0500 Subject: [PATCH 113/138] fix: NetworkObjects do not honor when Auto Object Parent Sync is turned off (#2281) * fix This resolves the issue where NetworkObjects were not taking Auto Object Parent Sync into consideration. * test The test to validate that nested in-scene placed NetworkObjects that both have Auto Object Parent Synchronization enabled and disabled synchronize their transforms properly on the client side. The same test covers NetworkObjects nested under GameObjects with no NetworkObject components. All nested instances have varying position, rotation, and scale values. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Core/NetworkObject.cs | 41 +- .../Runtime/Spawning/NetworkSpawnManager.cs | 9 +- .../ParentingAutoSyncManager.cs | 64 ++ .../ParentingAutoSyncManager.cs.meta | 11 + .../ParentingInSceneObjects.unity | 588 +++++++++++++++++- .../ParentingInSceneObjectsTests.cs | 127 ++++ 7 files changed, 832 insertions(+), 9 deletions(-) create mode 100644 testproject/Assets/Tests/Runtime/ObjectParenting/ParentingAutoSyncManager.cs create mode 100644 testproject/Assets/Tests/Runtime/ObjectParenting/ParentingAutoSyncManager.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 9a23eb0b8c..8718b58d8a 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -11,6 +11,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where in-scene placed `NetworkObjects` were not honoring the `AutoObjectParentSync` property. (#2281) - Fixed the issue where `NetworkManager.OnClientConnectedCallback` was being invoked before in-scene placed `NetworkObject`s had been spawned when starting `NetworkManager` as a host. (#2277) - Creating a `FastBufferReader` with `Allocator.None` will not result in extra memory being allocated for the buffer (since it's owned externally in that scenario). (#2265) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 9d956f7d26..100ab8404f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -830,6 +830,9 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa // parent and then re-parents the child under a GameObject with a NetworkObject component attached. if (parentNetworkObject == null) { + // If we are parented under a GameObject, go ahead and mark the world position stays as false + // so clients synchronize their transform in local space. (only for in-scene placed NetworkObjects) + m_CachedWorldPositionStays = false; return true; } else // If the parent still isn't spawned add this to the orphaned children and return false @@ -841,8 +844,11 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa else { // If we made it this far, go ahead and set the network parenting values - // with the default WorldPoisitonSays value - SetNetworkParenting(parentNetworkObject.NetworkObjectId, true); + // with the WorldPoisitonSays value set to false + // Note: Since in-scene placed NetworkObjects are parented in the scene + // the default "assumption" is that children are parenting local space + // relative. + SetNetworkParenting(parentNetworkObject.NetworkObjectId, false); // Set the cached parent m_CachedParent = parentNetworkObject.transform; @@ -1235,6 +1241,13 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId) if (!AlwaysReplicateAsRoot && transform.parent != null) { parentNetworkObject = transform.parent.GetComponent(); + // In-scene placed NetworkObjects parented under GameObjects with no NetworkObject + // should set the has parent flag and preserve the world position stays value + if (parentNetworkObject == null && obj.Header.IsSceneObject) + { + obj.Header.HasParent = true; + obj.WorldPositionStays = m_CachedWorldPositionStays; + } } if (parentNetworkObject != null) @@ -1254,19 +1267,37 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId) if (IncludeTransformWhenSpawning == null || IncludeTransformWhenSpawning(OwnerClientId)) { obj.Header.HasTransform = true; + + // We start with the default AutoObjectParentSync values to determine which transform space we will + // be synchronizing clients with. + var syncRotationPositionLocalSpaceRelative = obj.Header.HasParent && !m_CachedWorldPositionStays; + var syncScaleLocalSpaceRelative = obj.Header.HasParent && !m_CachedWorldPositionStays; + + // If auto object synchronization is turned off + if (!AutoObjectParentSync) + { + // We always synchronize position and rotation world space relative + syncRotationPositionLocalSpaceRelative = false; + // Scale is special, it synchronizes local space relative if it has a + // parent since applying the world space scale under a parent with scale + // will result in the improper scale for the child + syncScaleLocalSpaceRelative = obj.Header.HasParent; + } + + obj.Transform = new SceneObject.TransformData { // If we are parented and we have the m_CachedWorldPositionStays disabled, then use local space // values as opposed world space values. - Position = parentNetworkObject && !m_CachedWorldPositionStays ? transform.localPosition : transform.position, - Rotation = parentNetworkObject && !m_CachedWorldPositionStays ? transform.localRotation : transform.rotation, + Position = syncRotationPositionLocalSpaceRelative ? transform.localPosition : transform.position, + Rotation = syncRotationPositionLocalSpaceRelative ? transform.localRotation : transform.rotation, // We only use the lossyScale if the NetworkObject has a parent. Multi-generation nested children scales can // impact the final scale of the child NetworkObject in question. The solution is to use the lossy scale // which can be thought of as "world space scale". // More information: // https://docs.unity3d.com/ScriptReference/Transform-lossyScale.html - Scale = parentNetworkObject && !m_CachedWorldPositionStays ? transform.localScale : transform.lossyScale, + Scale = syncScaleLocalSpaceRelative ? transform.localScale : transform.lossyScale, }; } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 45c190b801..8e21e532c0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -402,7 +402,7 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO if (networkObject != null) { - // SPECIAL CASE: + // SPECIAL CASE FOR IN-SCENE PLACED: (only when the parent has a NetworkObject) // This is a special case scenario where a late joining client has joined and loaded one or // more scenes that contain nested in-scene placed NetworkObject children yet the server's // synchronization information does not indicate the NetworkObject in question has a parent. @@ -423,7 +423,9 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO // but it is up to the user to set those values if (sceneObject.Header.HasTransform && !isSpawnedByPrefabHandler) { - if (worldPositionStays) + // If world position stays is true or we have auto object parent synchronization disabled + // then we want to apply the position and rotation values world space relative + if (worldPositionStays || !networkObject.AutoObjectParentSync) { networkObject.transform.position = position; networkObject.transform.rotation = rotation; @@ -443,6 +445,9 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO // that is the default value of Vector3. if (!sceneObject.Header.IsPlayerObject) { + // Since scale is always applied to local space scale, we do the transform + // space logic during serialization such that it works out whether AutoObjectParentSync + // is enabled or not (see NetworkObject.SceneObject) networkObject.transform.localScale = scale; } } diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingAutoSyncManager.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingAutoSyncManager.cs new file mode 100644 index 0000000000..d4ace7d837 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingAutoSyncManager.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using UnityEngine; +using Unity.Netcode; + + +namespace TestProject.RuntimeTests +{ + /// + /// Helper class that builds 4 transform lists based on parent-child hierarchy + /// for the + /// + public class ParentingAutoSyncManager : NetworkBehaviour + { + public static ParentingAutoSyncManager ServerInstance; + public static Dictionary ClientInstances = new Dictionary(); + + public GameObject WithNetworkObjectAutoSyncOn; + public GameObject WithNetworkObjectAutoSyncOff; + public GameObject GameObjectAutoSyncOn; + public GameObject GameObjectAutoSyncOff; + + public List NetworkObjectAutoSyncOnTransforms = new List(); + public List NetworkObjectAutoSyncOffTransforms = new List(); + public List GameObjectAutoSyncOnTransforms = new List(); + public List GameObjectAutoSyncOffTransforms = new List(); + + public static void Reset() + { + ServerInstance = null; + ClientInstances.Clear(); + } + + public override void OnNetworkSpawn() + { + if (IsServer) + { + ServerInstance = this; + } + else + { + ClientInstances.Add(NetworkManager.LocalClientId, this); + } + var currentRoot = WithNetworkObjectAutoSyncOn.transform; + NetworkObjectAutoSyncOnTransforms.Add(currentRoot); + NetworkObjectAutoSyncOnTransforms.Add(currentRoot.GetChild(0)); + NetworkObjectAutoSyncOnTransforms.Add(currentRoot.GetChild(0).GetChild(0)); + + currentRoot = WithNetworkObjectAutoSyncOff.transform; + NetworkObjectAutoSyncOffTransforms.Add(currentRoot); + NetworkObjectAutoSyncOffTransforms.Add(currentRoot.GetChild(0)); + NetworkObjectAutoSyncOffTransforms.Add(currentRoot.GetChild(0).GetChild(0)); + + currentRoot = GameObjectAutoSyncOn.transform; + GameObjectAutoSyncOnTransforms.Add(currentRoot); + GameObjectAutoSyncOnTransforms.Add(currentRoot.GetChild(0)); + GameObjectAutoSyncOnTransforms.Add(currentRoot.GetChild(0).GetChild(0)); + + currentRoot = GameObjectAutoSyncOff.transform; + GameObjectAutoSyncOffTransforms.Add(currentRoot); + GameObjectAutoSyncOffTransforms.Add(currentRoot.GetChild(0)); + GameObjectAutoSyncOffTransforms.Add(currentRoot.GetChild(0).GetChild(0)); + } + } +} diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingAutoSyncManager.cs.meta b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingAutoSyncManager.cs.meta new file mode 100644 index 0000000000..d147ac87ad --- /dev/null +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingAutoSyncManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3326399105afcf4459b51991aeeaacd2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjects.unity b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjects.unity index 355f3d788b..4b846072ec 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjects.unity +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjects.unity @@ -123,6 +123,55 @@ NavMeshSettings: debug: m_Flags: 0 m_NavMeshData: {fileID: 0} +--- !u!1 &69934937 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 69934939} + - component: {fileID: 69934938} + m_Layer: 0 + m_Name: ParentedAutoSyncOn + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &69934938 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 69934937} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 2988556371 + AlwaysReplicateAsRoot: 0 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 +--- !u!4 &69934939 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 69934937} + m_LocalRotation: {x: 0, y: 0.53729963, z: 0, w: 0.8433915} + m_LocalPosition: {x: 10, y: 10, z: 10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1353280373} + m_Father: {fileID: 0} + m_RootOrder: 4 + m_LocalEulerAnglesHint: {x: 0, y: 65, z: 0} --- !u!1 &118026864 GameObject: m_ObjectHideFlags: 0 @@ -157,7 +206,6 @@ MonoBehaviour: m_ProtocolType: 0 m_MaxPacketQueueSize: 128 m_MaxPayloadSize: 6144 - m_MaxSendQueueSize: 98304 m_HeartbeatTimeoutMS: 500 m_ConnectTimeoutMS: 1000 m_MaxConnectAttempts: 60 @@ -231,6 +279,38 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: cf54eec7d39507743a95b32caf5b7c2a, type: 3} m_Name: m_EditorClassIdentifier: +--- !u!1 &153482222 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 153482223} + m_Layer: 0 + m_Name: Child1 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &153482223 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 153482222} + m_LocalRotation: {x: 0.18730325, y: -0.036408048, z: 0.18730325, w: 0.963592} + m_LocalPosition: {x: 2, y: 0, z: 2} + m_LocalScale: {x: 0.1, y: 0.1, z: 0.1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1934730583} + m_Father: {fileID: 1351092234} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 22, y: 0, z: 22} --- !u!1 &295758851 GameObject: m_ObjectHideFlags: 0 @@ -408,6 +488,70 @@ Camera: m_OcclusionCulling: 1 m_StereoConvergence: 10 m_StereoSeparation: 0.022 +--- !u!1 &364512791 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 364512792} + m_Layer: 0 + m_Name: Child1 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &364512792 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 364512791} + m_LocalRotation: {x: 0.18730325, y: -0.036408048, z: 0.18730325, w: 0.963592} + m_LocalPosition: {x: 2, y: 0, z: 2} + m_LocalScale: {x: 0.1, y: 0.1, z: 0.1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 700085893} + m_Father: {fileID: 366400900} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 22, y: 0, z: 22} +--- !u!1 &366400899 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 366400900} + m_Layer: 0 + m_Name: GameObjectParentAutoSyncOn + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &366400900 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 366400899} + m_LocalRotation: {x: 0, y: 0.53729963, z: 0, w: 0.8433915} + m_LocalPosition: {x: 10, y: 10, z: 10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 364512792} + m_Father: {fileID: 0} + m_RootOrder: 7 + m_LocalEulerAnglesHint: {x: 0, y: 65, z: 0} --- !u!1 &505911173 GameObject: m_ObjectHideFlags: 0 @@ -532,6 +676,103 @@ MonoBehaviour: RotationMin: {x: 0.01, y: 0.01, z: 0.01} ScaleMax: 3 ScaleMin: 0.25 +--- !u!1 &518555745 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 518555746} + - component: {fileID: 518555747} + m_Layer: 0 + m_Name: Child2 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &518555746 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 518555745} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 10, y: 10, z: 10} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1353280373} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &518555747 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 518555745} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 4284572620 + AlwaysReplicateAsRoot: 0 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 +--- !u!1 &582431330 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 582431331} + - component: {fileID: 582431332} + m_Layer: 0 + m_Name: Child1 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &582431331 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 582431330} + m_LocalRotation: {x: 0.18730325, y: -0.036408048, z: 0.18730325, w: 0.963592} + m_LocalPosition: {x: 2, y: 0, z: 2} + m_LocalScale: {x: 0.1, y: 0.1, z: 0.1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1546803112} + m_Father: {fileID: 1282066021} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 22, y: 0, z: 22} +--- !u!114 &582431332 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 582431330} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 3027368638 + AlwaysReplicateAsRoot: 0 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 0 --- !u!1 &605493856 GameObject: m_ObjectHideFlags: 0 @@ -577,6 +818,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3} m_Name: m_EditorClassIdentifier: + m_SendPointerHoverToParent: 1 m_HorizontalAxis: Horizontal m_VerticalAxis: Vertical m_SubmitButton: Submit @@ -722,6 +964,123 @@ MonoBehaviour: RotationMin: {x: 0.01, y: 0.01, z: 0.01} ScaleMax: 3 ScaleMin: 0.25 +--- !u!1 &694186461 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 694186464} + - component: {fileID: 694186463} + - component: {fileID: 694186462} + m_Layer: 0 + m_Name: AutoSyncManager + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &694186462 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 694186461} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 2343724113 + AlwaysReplicateAsRoot: 0 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 +--- !u!114 &694186463 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 694186461} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3326399105afcf4459b51991aeeaacd2, type: 3} + m_Name: + m_EditorClassIdentifier: + WithNetworkObjectAutoSyncOn: {fileID: 69934937} + WithNetworkObjectAutoSyncOff: {fileID: 1282066019} + GameObjectAutoSyncOn: {fileID: 366400899} + GameObjectAutoSyncOff: {fileID: 1351092233} + NetworkObjectAutoSyncOnTransforms: [] + NetworkObjectAutoSyncOffTransforms: [] + GameObjectAutoSyncOnTransforms: [] + GameObjectAutoSyncOffTransforms: [] +--- !u!4 &694186464 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 694186461} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 561, y: 144.52359, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 8 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &700085892 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 700085893} + - component: {fileID: 700085894} + m_Layer: 0 + m_Name: Child2 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &700085893 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 700085892} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 10, y: 10, z: 10} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 364512792} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &700085894 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 700085892} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 1482043520 + AlwaysReplicateAsRoot: 0 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 0 --- !u!1 &733348965 GameObject: m_ObjectHideFlags: 0 @@ -1095,6 +1454,55 @@ MonoBehaviour: RotationMin: {x: 0.01, y: 0.01, z: 0.01} ScaleMax: 3 ScaleMin: 0.25 +--- !u!1 &1282066019 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1282066021} + - component: {fileID: 1282066020} + m_Layer: 0 + m_Name: ParentedAutoSyncOff + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1282066020 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1282066019} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 3624362024 + AlwaysReplicateAsRoot: 0 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 0 +--- !u!4 &1282066021 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1282066019} + m_LocalRotation: {x: 0, y: 0.53729963, z: 0, w: 0.8433915} + m_LocalPosition: {x: 10, y: 10, z: 10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 582431331} + m_Father: {fileID: 0} + m_RootOrder: 5 + m_LocalEulerAnglesHint: {x: 0, y: 65, z: 0} --- !u!1 &1343771061 GameObject: m_ObjectHideFlags: 0 @@ -1189,6 +1597,87 @@ Light: m_UseViewFrustumForShadowCasterCull: 1 m_ShadowRadius: 0 m_ShadowAngle: 0 +--- !u!1 &1351092233 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1351092234} + m_Layer: 0 + m_Name: GameObjectParentAutoSyncOff + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1351092234 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1351092233} + m_LocalRotation: {x: 0, y: 0.53729963, z: 0, w: 0.8433915} + m_LocalPosition: {x: 10, y: 10, z: 10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 153482223} + m_Father: {fileID: 0} + m_RootOrder: 6 + m_LocalEulerAnglesHint: {x: 0, y: 65, z: 0} +--- !u!1 &1353280372 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1353280373} + - component: {fileID: 1353280374} + m_Layer: 0 + m_Name: Child1 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1353280373 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1353280372} + m_LocalRotation: {x: 0.18730325, y: -0.036408048, z: 0.18730325, w: 0.963592} + m_LocalPosition: {x: 2, y: 0, z: 2} + m_LocalScale: {x: 0.1, y: 0.1, z: 0.1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 518555746} + m_Father: {fileID: 69934939} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 22, y: 0, z: 22} +--- !u!114 &1353280374 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1353280372} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 2685148660 + AlwaysReplicateAsRoot: 0 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 --- !u!1 &1392712513 GameObject: m_ObjectHideFlags: 0 @@ -1253,6 +1742,54 @@ MonoBehaviour: AlwaysReplicateAsRoot: 0 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 +--- !u!1 &1546803111 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1546803112} + - component: {fileID: 1546803113} + m_Layer: 0 + m_Name: Child2 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1546803112 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1546803111} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 10, y: 10, z: 10} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 582431331} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1546803113 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1546803111} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 136657534 + AlwaysReplicateAsRoot: 0 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 0 --- !u!1 &1601792188 GameObject: m_ObjectHideFlags: 0 @@ -1409,6 +1946,54 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1934730582 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1934730583} + - component: {fileID: 1934730584} + m_Layer: 0 + m_Name: Child2 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1934730583 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1934730582} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 10, y: 10, z: 10} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 153482223} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1934730584 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1934730582} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 638354969 + AlwaysReplicateAsRoot: 0 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 0 --- !u!1 &2062433906 GameObject: m_ObjectHideFlags: 0 @@ -1470,4 +2055,3 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 8384b01f654da304ebe165da1624cf56, type: 3} m_Name: m_EditorClassIdentifier: - Instances: [] diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs index f506d0f2b0..8accb2aba8 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs @@ -41,6 +41,7 @@ protected override IEnumerator OnSetup() { InSceneParentChildHandler.ResetInstancesTracking(m_EnableVerboseDebug); InSceneParentedUnderGameObjectHandler.Instances.Clear(); + ParentingAutoSyncManager.Reset(); return base.OnSetup(); } @@ -383,5 +384,131 @@ public IEnumerator InSceneNestedUnderGameObjectTest() Assert.False(instance.transform.parent == null, $"{instance.name}'s parent is null when it should not be!"); } } + + + /// + /// Validates that all client transforms match all server transforms for the InSceneNestedAutoSyncObjectTest + /// + private bool AllClientInstancesMatchServerInstance() + { + for (int i = 0; i < ParentingAutoSyncManager.ServerInstance.NetworkObjectAutoSyncOnTransforms.Count; i++) + { + var serverTransformToTest = ParentingAutoSyncManager.ServerInstance.NetworkObjectAutoSyncOnTransforms[i]; + for (int j = 0; j < m_ClientNetworkManagers.Length; j++) + { + var clientRelativeAutoSyncManager = ParentingAutoSyncManager.ClientInstances[m_ClientNetworkManagers[j].LocalClientId]; + var clientTransformToTest = clientRelativeAutoSyncManager.NetworkObjectAutoSyncOnTransforms[i]; + if (!Approximately(clientTransformToTest.position, serverTransformToTest.position)) + { + return false; + } + + if (!Approximately(clientTransformToTest.rotation, serverTransformToTest.rotation)) + { + return false; + } + + if (!Approximately(clientTransformToTest.localScale, serverTransformToTest.localScale)) + { + return false; + } + } + } + + for (int i = 0; i < ParentingAutoSyncManager.ServerInstance.NetworkObjectAutoSyncOffTransforms.Count; i++) + { + var serverTransformToTest = ParentingAutoSyncManager.ServerInstance.NetworkObjectAutoSyncOffTransforms[i]; + for (int j = 0; j < m_ClientNetworkManagers.Length; j++) + { + var clientRelativeAutoSyncManager = ParentingAutoSyncManager.ClientInstances[m_ClientNetworkManagers[j].LocalClientId]; + var clientTransformToTest = clientRelativeAutoSyncManager.NetworkObjectAutoSyncOffTransforms[i]; + if (!Approximately(clientTransformToTest.position, serverTransformToTest.position)) + { + return false; + } + + if (!Approximately(clientTransformToTest.rotation, serverTransformToTest.rotation)) + { + return false; + } + + if (!Approximately(clientTransformToTest.localScale, serverTransformToTest.localScale)) + { + return false; + } + } + } + + for (int i = 0; i < ParentingAutoSyncManager.ServerInstance.GameObjectAutoSyncOnTransforms.Count; i++) + { + var serverTransformToTest = ParentingAutoSyncManager.ServerInstance.GameObjectAutoSyncOnTransforms[i]; + for (int j = 0; j < m_ClientNetworkManagers.Length; j++) + { + var clientRelativeAutoSyncManager = ParentingAutoSyncManager.ClientInstances[m_ClientNetworkManagers[j].LocalClientId]; + var clientTransformToTest = clientRelativeAutoSyncManager.GameObjectAutoSyncOnTransforms[i]; + if (!Approximately(clientTransformToTest.position, serverTransformToTest.position)) + { + return false; + } + + if (!Approximately(clientTransformToTest.rotation, serverTransformToTest.rotation)) + { + return false; + } + + if (!Approximately(clientTransformToTest.localScale, serverTransformToTest.localScale)) + { + return false; + } + } + } + + for (int i = 0; i < ParentingAutoSyncManager.ServerInstance.GameObjectAutoSyncOffTransforms.Count; i++) + { + var serverTransformToTest = ParentingAutoSyncManager.ServerInstance.GameObjectAutoSyncOffTransforms[i]; + for (int j = 0; j < m_ClientNetworkManagers.Length; j++) + { + var clientRelativeAutoSyncManager = ParentingAutoSyncManager.ClientInstances[m_ClientNetworkManagers[j].LocalClientId]; + var clientTransformToTest = clientRelativeAutoSyncManager.GameObjectAutoSyncOffTransforms[i]; + if (!Approximately(clientTransformToTest.position, serverTransformToTest.position)) + { + return false; + } + + if (!Approximately(clientTransformToTest.rotation, serverTransformToTest.rotation)) + { + return false; + } + + if (!Approximately(clientTransformToTest.localScale, serverTransformToTest.localScale)) + { + return false; + } + } + } + return true; + } + + /// + /// Validates that both nested in-scene NetworkObjects and in-scene NetworkObjects + /// nested under GameObjects synchronize clients with the appropriate transform + /// space values (world vs local). + /// + [UnityTest] + public IEnumerator InSceneNestedAutoSyncObjectTest() + { + SceneManager.sceneLoaded += SceneManager_sceneLoaded; + SceneManager.LoadScene(k_BaseSceneToLoad, LoadSceneMode.Additive); + m_InitialClientsLoadedScene = false; + m_ServerNetworkManager.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; + + var sceneEventStartedStatus = m_ServerNetworkManager.SceneManager.LoadScene(k_TestSceneToLoad, LoadSceneMode.Additive); + Assert.True(sceneEventStartedStatus == SceneEventProgressStatus.Started, $"Failed to load scene {k_TestSceneToLoad} with a return status of {sceneEventStartedStatus}."); + yield return WaitForConditionOrTimeOut(() => m_InitialClientsLoadedScene); + AssertOnTimeout($"Timed out waiting for all clients to load scene {k_TestSceneToLoad}!"); + + yield return WaitForConditionOrTimeOut(AllClientInstancesMatchServerInstance); + AssertOnTimeout($"Timed out waiting for all client transforms to match the server-side values in test scene {k_TestSceneToLoad}!"); + } } } From 957c3c186d797f669c50d2e2826f7d8d9203f6c1 Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Tue, 1 Nov 2022 18:17:13 -0400 Subject: [PATCH 114/138] style: removing unused code (#2287) --- .../Tests/Runtime/NetworkShowHideTests.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs index 2f43484ef3..e8aebb5e98 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs @@ -8,11 +8,6 @@ namespace Unity.Netcode.RuntimeTests { - public class NetworkShowHideTestComponent : NetworkBehaviour - { - - } - public class ShowHideObject : NetworkBehaviour { public static List ClientTargetedNetworkObjects = new List(); @@ -94,11 +89,6 @@ public class NetworkShowHideTests : NetcodeIntegrationTest private NetworkObject m_Object2OnClient0; private NetworkObject m_Object3OnClient0; - protected override void OnCreatePlayerPrefab() - { - var networkTransform = m_PlayerPrefab.AddComponent(); - } - protected override void OnServerAndClientsCreated() { m_PrefabToSpawn = CreateNetworkObjectPrefab("ShowHideObject"); From becdcd5d53eeabacfa54456f9e4545216b5afacd Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 1 Nov 2022 19:28:21 -0500 Subject: [PATCH 115/138] refactor: Migrate menu settings to project settings [MTT-5001] (#2285) * update Migrated the multiplayer tools install reminder over to the new Netcode for GameObjects settings. Deleted the UITestHelpers class completely as it is no longer needed. --- com.unity.netcode.gameobjects/CHANGELOG.md | 7 ++ .../Editor/Configuration.meta | 8 ++ .../NetcodeForGameObjectsSettings.cs | 39 ++++++++ .../NetcodeForGameObjectsSettings.cs.meta | 11 +++ .../Configuration/NetcodeSettingsProvider.cs | 94 +++++++++++++++++++ .../NetcodeSettingsProvider.cs.meta | 11 +++ .../Editor/NetworkBehaviourEditor.cs | 22 +---- .../Editor/NetworkManagerEditor.cs | 6 +- .../Tests/Editor/UI.meta | 3 - .../Tests/Editor/UI/UITestHelpers.cs | 15 --- .../Tests/Editor/UI/UITestHelpers.cs.meta | 3 - 11 files changed, 176 insertions(+), 43 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Editor/Configuration.meta create mode 100644 com.unity.netcode.gameobjects/Editor/Configuration/NetcodeForGameObjectsSettings.cs create mode 100644 com.unity.netcode.gameobjects/Editor/Configuration/NetcodeForGameObjectsSettings.cs.meta create mode 100644 com.unity.netcode.gameobjects/Editor/Configuration/NetcodeSettingsProvider.cs create mode 100644 com.unity.netcode.gameobjects/Editor/Configuration/NetcodeSettingsProvider.cs.meta delete mode 100644 com.unity.netcode.gameobjects/Tests/Editor/UI.meta delete mode 100644 com.unity.netcode.gameobjects/Tests/Editor/UI/UITestHelpers.cs delete mode 100644 com.unity.netcode.gameobjects/Tests/Editor/UI/UITestHelpers.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 8718b58d8a..65a8fda1f8 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -9,12 +9,19 @@ Additional documentation and release notes are available at [Multiplayer Documen ## [Unreleased] +### Added +- Added `NetworkObject` auto-add helper and Multiplayer Tools install reminder settings to Project Settings. (#2285) + ### Fixed - Fixed issue where in-scene placed `NetworkObjects` were not honoring the `AutoObjectParentSync` property. (#2281) - Fixed the issue where `NetworkManager.OnClientConnectedCallback` was being invoked before in-scene placed `NetworkObject`s had been spawned when starting `NetworkManager` as a host. (#2277) - Creating a `FastBufferReader` with `Allocator.None` will not result in extra memory being allocated for the buffer (since it's owned externally in that scenario). (#2265) +### Removed +- Removed the `NetworkObject` auto-add and Multiplayer Tools install reminder settings from the Menu interface. (#2285) + + ## [1.1.0] - 2022-10-21 ### Added diff --git a/com.unity.netcode.gameobjects/Editor/Configuration.meta b/com.unity.netcode.gameobjects/Editor/Configuration.meta new file mode 100644 index 0000000000..1f445ad093 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/Configuration.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 52153943c346dd04e8712ab540ab9c22 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeForGameObjectsSettings.cs b/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeForGameObjectsSettings.cs new file mode 100644 index 0000000000..f62b287465 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeForGameObjectsSettings.cs @@ -0,0 +1,39 @@ +using UnityEditor; + + +namespace Unity.Netcode.Editor.Configuration +{ + internal class NetcodeForGameObjectsSettings + { + internal const string AutoAddNetworkObjectIfNoneExists = "AutoAdd-NetworkObject-When-None-Exist"; + internal const string InstallMultiplayerToolsTipDismissedPlayerPrefKey = "Netcode_Tip_InstallMPTools_Dismissed"; + + internal static int GetNetcodeInstallMultiplayerToolTips() + { + if (EditorPrefs.HasKey(InstallMultiplayerToolsTipDismissedPlayerPrefKey)) + { + return EditorPrefs.GetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey); + } + return 0; + } + + internal static void SetNetcodeInstallMultiplayerToolTips(int toolTipPrefSetting) + { + EditorPrefs.SetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey, toolTipPrefSetting); + } + + internal static bool GetAutoAddNetworkObjectSetting() + { + if (EditorPrefs.HasKey(AutoAddNetworkObjectIfNoneExists)) + { + return EditorPrefs.GetBool(AutoAddNetworkObjectIfNoneExists); + } + return false; + } + + internal static void SetAutoAddNetworkObjectSetting(bool autoAddSetting) + { + EditorPrefs.SetBool(AutoAddNetworkObjectIfNoneExists, autoAddSetting); + } + } +} diff --git a/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeForGameObjectsSettings.cs.meta b/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeForGameObjectsSettings.cs.meta new file mode 100644 index 0000000000..f1c3145417 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeForGameObjectsSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2f9c9b10bc41a0e46ab71324dd0ac6e1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeSettingsProvider.cs b/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeSettingsProvider.cs new file mode 100644 index 0000000000..c0f27adb52 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeSettingsProvider.cs @@ -0,0 +1,94 @@ +using UnityEditor; +using UnityEngine; + +namespace Unity.Netcode.Editor.Configuration +{ + internal static class NetcodeSettingsProvider + { + [SettingsProvider] + public static SettingsProvider CreateNetcodeSettingsProvider() + { + // First parameter is the path in the Settings window. + // Second parameter is the scope of this setting: it only appears in the Settings window for the Project scope. + var provider = new SettingsProvider("Project/NetcodeForGameObjects", SettingsScope.Project) + { + label = "Netcode for GameObjects", + keywords = new[] { "netcode", "editor" }, + guiHandler = OnGuiHandler, + }; + + return provider; + } + + internal static NetcodeSettingsLabel NetworkObjectsSectionLabel = new NetcodeSettingsLabel("NetworkObject Helper Settings", 20); + internal static NetcodeSettingsToggle AutoAddNetworkObjectToggle = new NetcodeSettingsToggle("Auto-Add NetworkObjects", "When enabled, NetworkObjects are automatically added to GameObjects when NetworkBehaviours are added first.", 20); + internal static NetcodeSettingsLabel MultiplayerToolsLabel = new NetcodeSettingsLabel("Multiplayer Tools", 20); + internal static NetcodeSettingsToggle MultiplayerToolTipStatusToggle = new NetcodeSettingsToggle("Multiplayer Tools Install Reminder", "When enabled, the NetworkManager will display " + + "the notification to install the multiplayer tools package.", 20); + + private static void OnGuiHandler(string obj) + { + var autoAddNetworkObjectSetting = NetcodeForGameObjectsSettings.GetAutoAddNetworkObjectSetting(); + var multiplayerToolsTipStatus = NetcodeForGameObjectsSettings.GetNetcodeInstallMultiplayerToolTips() == 0; + EditorGUI.BeginChangeCheck(); + NetworkObjectsSectionLabel.DrawLabel(); + autoAddNetworkObjectSetting = AutoAddNetworkObjectToggle.DrawToggle(autoAddNetworkObjectSetting); + MultiplayerToolsLabel.DrawLabel(); + multiplayerToolsTipStatus = MultiplayerToolTipStatusToggle.DrawToggle(multiplayerToolsTipStatus); + if (EditorGUI.EndChangeCheck()) + { + NetcodeForGameObjectsSettings.SetAutoAddNetworkObjectSetting(autoAddNetworkObjectSetting); + NetcodeForGameObjectsSettings.SetNetcodeInstallMultiplayerToolTips(multiplayerToolsTipStatus ? 0 : 1); + } + } + } + + internal class NetcodeSettingsLabel : NetcodeGUISettings + { + private string m_LabelContent; + + public void DrawLabel() + { + EditorGUIUtility.labelWidth = m_LabelSize; + GUILayout.Label(m_LabelContent, EditorStyles.boldLabel, m_LayoutWidth); + } + + public NetcodeSettingsLabel(string labelText, float layoutOffset = 0.0f) + { + m_LabelContent = labelText; + AdjustLableSize(labelText, layoutOffset); + } + } + + internal class NetcodeSettingsToggle : NetcodeGUISettings + { + private GUIContent m_ToggleContent; + + public bool DrawToggle(bool currentSetting) + { + EditorGUIUtility.labelWidth = m_LabelSize; + return EditorGUILayout.Toggle(m_ToggleContent, currentSetting, m_LayoutWidth); + } + + public NetcodeSettingsToggle(string labelText, string toolTip, float layoutOffset) + { + AdjustLableSize(labelText, layoutOffset); + m_ToggleContent = new GUIContent(labelText, toolTip); + } + } + + internal class NetcodeGUISettings + { + private const float k_MaxLabelWidth = 450f; + protected float m_LabelSize { get; private set; } + + protected GUILayoutOption m_LayoutWidth { get; private set; } + + protected void AdjustLableSize(string labelText, float offset = 0.0f) + { + m_LabelSize = Mathf.Min(k_MaxLabelWidth, EditorStyles.label.CalcSize(new GUIContent(labelText)).x); + m_LayoutWidth = GUILayout.Width(m_LabelSize + offset); + } + } + +} diff --git a/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeSettingsProvider.cs.meta b/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeSettingsProvider.cs.meta new file mode 100644 index 0000000000..c133da284d --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeSettingsProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6b373a89fcbd41444a97ebd1798b326f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs index 95e353a8ca..d5dfa85e43 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs @@ -3,6 +3,7 @@ using System.Reflection; using UnityEngine; using UnityEditor; +using Unity.Netcode.Editor.Configuration; namespace Unity.Netcode.Editor { @@ -230,8 +231,6 @@ private void OnEnable() CheckForNetworkObject((target as NetworkBehaviour).gameObject); } - internal const string AutoAddNetworkObjectIfNoneExists = "AutoAdd-NetworkObject-When-None-Exist"; - /// /// Recursively finds the root parent of a /// @@ -308,7 +307,7 @@ public static void CheckForNetworkObject(GameObject gameObject, bool networkObje // and the user has already turned "Auto-Add NetworkObject" on when first notified about the requirement // then just send a reminder to the user why the NetworkObject they just deleted seemingly "re-appeared" // again. - if (networkObjectRemoved && EditorPrefs.HasKey(AutoAddNetworkObjectIfNoneExists) && EditorPrefs.GetBool(AutoAddNetworkObjectIfNoneExists)) + if (networkObjectRemoved && NetcodeForGameObjectsSettings.GetAutoAddNetworkObjectSetting()) { Debug.LogWarning($"{gameObject.name} still has {nameof(NetworkBehaviour)}s and Auto-Add NetworkObjects is enabled. A NetworkObject is being added back to {gameObject.name}."); Debug.Log($"To reset Auto-Add NetworkObjects: Select the Netcode->General->Reset Auto-Add NetworkObject menu item."); @@ -317,7 +316,7 @@ public static void CheckForNetworkObject(GameObject gameObject, bool networkObje // Notify and provide the option to add it one time, always add a NetworkObject, or do nothing and let the user manually add it if (EditorUtility.DisplayDialog($"{nameof(NetworkBehaviour)}s require a {nameof(NetworkObject)}", $"{gameObject.name} does not have a {nameof(NetworkObject)} component. Would you like to add one now?", "Yes", "No (manually add it)", - DialogOptOutDecisionType.ForThisMachine, AutoAddNetworkObjectIfNoneExists)) + DialogOptOutDecisionType.ForThisMachine, NetcodeForGameObjectsSettings.AutoAddNetworkObjectIfNoneExists)) { gameObject.AddComponent(); var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene(); @@ -327,20 +326,5 @@ public static void CheckForNetworkObject(GameObject gameObject, bool networkObje } } } - - /// - /// This allows users to reset the Auto-Add NetworkObject preference - /// so the next time they add a NetworkBehaviour to a GameObject without - /// a NetworkObject it will display the dialog box again and not - /// automatically add a NetworkObject. - /// - [MenuItem("Netcode/General/Reset Auto-Add NetworkObject", false, 1)] - private static void ResetMultiplayerToolsTipStatus() - { - if (EditorPrefs.HasKey(AutoAddNetworkObjectIfNoneExists)) - { - EditorPrefs.SetBool(AutoAddNetworkObjectIfNoneExists, false); - } - } } } diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs index debaca2e45..d79cd9c2fe 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs @@ -3,6 +3,7 @@ using UnityEditor; using UnityEngine; using UnityEditorInternal; +using Unity.Netcode.Editor.Configuration; namespace Unity.Netcode.Editor { @@ -14,7 +15,6 @@ namespace Unity.Netcode.Editor [CanEditMultipleObjects] public class NetworkManagerEditor : UnityEditor.Editor { - internal const string InstallMultiplayerToolsTipDismissedPlayerPrefKey = "Netcode_Tip_InstallMPTools_Dismissed"; private static GUIStyle s_CenteredWordWrappedLabelStyle; private static GUIStyle s_HelpBoxStyle; @@ -359,7 +359,7 @@ private static void DrawInstallMultiplayerToolsTip() const string targetUrl = "https://docs-multiplayer.unity3d.com/netcode/current/tools/install-tools"; const string infoIconName = "console.infoicon"; - if (PlayerPrefs.GetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey, 0) != 0) + if (NetcodeForGameObjectsSettings.GetNetcodeInstallMultiplayerToolTips() != 0) { return; } @@ -405,7 +405,7 @@ private static void DrawInstallMultiplayerToolsTip() GUILayout.FlexibleSpace(); if (GUILayout.Button(dismissButtonText, dismissButtonStyle, GUILayout.ExpandWidth(false))) { - PlayerPrefs.SetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey, 1); + NetcodeForGameObjectsSettings.SetNetcodeInstallMultiplayerToolTips(1); } EditorGUIUtility.AddCursorRect(GUILayoutUtility.GetLastRect(), MouseCursor.Link); GUILayout.FlexibleSpace(); diff --git a/com.unity.netcode.gameobjects/Tests/Editor/UI.meta b/com.unity.netcode.gameobjects/Tests/Editor/UI.meta deleted file mode 100644 index f22cc756dc..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Editor/UI.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: f672293e0efc41a6a7e930fd7ff14436 -timeCreated: 1631650280 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Editor/UI/UITestHelpers.cs b/com.unity.netcode.gameobjects/Tests/Editor/UI/UITestHelpers.cs deleted file mode 100644 index 5eefc13da0..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Editor/UI/UITestHelpers.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Unity.Netcode.Editor; -using UnityEditor; -using UnityEngine; - -namespace Unity.Netcode.EditorTests -{ - internal static class UITestHelpers - { - [MenuItem("Netcode/UI/Reset Multiplayer Tools Tip Status")] - private static void ResetMultiplayerToolsTipStatus() - { - PlayerPrefs.DeleteKey(NetworkManagerEditor.InstallMultiplayerToolsTipDismissedPlayerPrefKey); - } - } -} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/UI/UITestHelpers.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/UI/UITestHelpers.cs.meta deleted file mode 100644 index c9addc4f10..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Editor/UI/UITestHelpers.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: bde5fc3349494f77bebd0be12a6957e1 -timeCreated: 1631650292 \ No newline at end of file From 39063465720c33d23188ce44bcb0755c9828890e Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Wed, 2 Nov 2022 13:36:30 -0400 Subject: [PATCH 116/138] fix: catching potential exceptions from user code during disconnect callback (#2288) * fix: catching potential exceptions from user code during disconnect callback --- .../Runtime/Core/NetworkManager.cs | 9 ++- .../Tests/Runtime/DisconnectReasonTests.cs | 60 +++++++++++++++++++ .../Runtime/DisconnectReasonTests.cs.meta | 11 ++++ 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DisconnectReasonTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DisconnectReasonTests.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 912f26d17d..bd3c9a0651 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1838,7 +1838,14 @@ private void HandleRawTransportPoll(NetworkEvent networkEvent, ulong clientId, A // or, if we are the server, so we got everything from that client. MessagingSystem.ProcessIncomingMessageQueue(); - OnClientDisconnectCallback?.Invoke(clientId); + try + { + OnClientDisconnectCallback?.Invoke(clientId); + } + catch (Exception exception) + { + Debug.LogException(exception); + } if (IsServer) { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectReasonTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectReasonTests.cs new file mode 100644 index 0000000000..30cb452366 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectReasonTests.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections; +using System.Text.RegularExpressions; +using UnityEngine; +using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; + +namespace Unity.Netcode.RuntimeTests +{ + public class DisconnectReasonObject : NetworkBehaviour + { + + } + + public class DisconnectReasonTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 2; + + private GameObject m_PrefabToSpawn; + + protected override void OnServerAndClientsCreated() + { + m_PrefabToSpawn = CreateNetworkObjectPrefab("DisconnectReasonObject"); + m_PrefabToSpawn.AddComponent(); + } + + private int m_DisconnectCount; + + public void OnClientDisconnectCallback(ulong clientId) + { + m_DisconnectCount++; + throw new SystemException("whatever"); + } + + [UnityTest] + public IEnumerator DisconnectExceptionTest() + { + float startTime = Time.realtimeSinceStartup; + + // Add a callback for first client, when they get disconnected + m_ClientNetworkManagers[0].OnClientDisconnectCallback += OnClientDisconnectCallback; + m_ClientNetworkManagers[1].OnClientDisconnectCallback += OnClientDisconnectCallback; + + // Disconnect first client, from the server + LogAssert.Expect(LogType.Exception, new Regex(".*whatever.*")); + m_ServerNetworkManager.DisconnectClient(m_ClientNetworkManagers[0].LocalClientId); + + // Disconnect second client, from the server + LogAssert.Expect(LogType.Exception, new Regex(".*whatever.*")); + m_ServerNetworkManager.DisconnectClient(m_ClientNetworkManagers[1].LocalClientId); + + while (m_DisconnectCount < 2 && Time.realtimeSinceStartup < startTime + 10.0f) + { + yield return null; + } + + Debug.Assert(m_DisconnectCount == 2); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectReasonTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectReasonTests.cs.meta new file mode 100644 index 0000000000..26b12ef0ea --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectReasonTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 93141fa15824f406b89dbc6f32c8910d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From acd0b6d2ae2e30f6dab215b01c013427b757560f Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 2 Nov 2022 20:26:21 -0500 Subject: [PATCH 117/138] fix: SceneEventProgress was triggering twice when running a host and no clients were connected (#2292) * fix Exclude the host-client from being processed as one of the clients to test for when the scene event has completed. GetClientsWithStatus needed to add the host-client to the list of clients that completed once the scene event was completed. Also fixed a bug in GetClientsWithStatus where, when checking for clients that did not complete, it would include the clients that completed and the clients that did not complete in the list of client identifiers it returned. Fixing issue where integration tests could lag by more than 1 network tick and the AsyncOperation could not yet be assigned. * test Added HostReceivesOneLoadEventCompletedNotification integration test to validate the fix for this PR. Updated NetcodeIntegrationTest virtual methods to be able to gain access to a newly created client (via NetcodeIntegrationTest.CreateAndStartNewClient) when it is first created, started, and when it has been connected. The updates to SceneEventProgress exposed an issue with ClientSynchronizationValidationTest. It was written before many NetcodeIntegrationTest helpers, so there are adjustments here to leverage from those which greatly simplifies the test. Switched the scene that was not validated by the client-side to one that did not contain an in-scene placed NetworkObject. Co-authored-by: Unity Netcode CI <74025435+netcode-ci-service@users.noreply.github.com> --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../SceneManagement/SceneEventProgress.cs | 44 +++++++++--- .../Runtime/NetcodeIntegrationTest.cs | 50 ++++++++++--- .../ClientSynchronizationValidationTest.cs | 70 ++++++------------- .../NetworkSceneManagerUsageTests.cs | 36 ++++++++++ 5 files changed, 134 insertions(+), 67 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 65a8fda1f8..5d9b4a9e6a 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -14,6 +14,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where the host would receive more than one event completed notification when loading or unloading a scene only when no clients were connected. (#2292) - Fixed issue where in-scene placed `NetworkObjects` were not honoring the `AutoObjectParentSync` property. (#2281) - Fixed the issue where `NetworkManager.OnClientConnectedCallback` was being invoked before in-scene placed `NetworkObject`s had been spawned when starting `NetworkManager` as a host. (#2277) - Creating a `FastBufferReader` with `Allocator.None` will not result in extra memory being allocated for the buffer (since it's owned externally in that scenario). (#2265) diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs index bce5389b1c..6ab998c0d7 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs @@ -108,19 +108,37 @@ internal bool HasTimedOut() internal List GetClientsWithStatus(bool completedSceneEvent) { var clients = new List(); - foreach (var clientStatus in ClientsProcessingSceneEvent) + if (completedSceneEvent) { - if (clientStatus.Value == completedSceneEvent) + // If we are the host, then add the host-client to the list + // of clients that completed if the AsyncOperation is done. + if (m_NetworkManager.IsHost && m_AsyncOperation.isDone) { - clients.Add(clientStatus.Key); + clients.Add(m_NetworkManager.LocalClientId); } - } - // If we are getting the list of clients that have not completed the - // scene event, then add any clients that disconnected during this - // scene event. - if (!completedSceneEvent) + // Add all clients that completed the scene event + foreach (var clientStatus in ClientsProcessingSceneEvent) + { + if (clientStatus.Value == completedSceneEvent) + { + clients.Add(clientStatus.Key); + } + } + } + else { + // If we are the host, then add the host-client to the list + // of clients that did not complete if the AsyncOperation is + // not done. + if (m_NetworkManager.IsHost && !m_AsyncOperation.isDone) + { + clients.Add(m_NetworkManager.LocalClientId); + } + + // If we are getting the list of clients that have not completed the + // scene event, then add any clients that disconnected during this + // scene event. clients.AddRange(ClientsThatDisconnected); } return clients; @@ -138,6 +156,11 @@ internal SceneEventProgress(NetworkManager networkManager, SceneEventProgressSta // Track the clients that were connected when we started this event foreach (var connectedClientId in networkManager.ConnectedClientsIds) { + // Ignore the host client + if (NetworkManager.ServerClientId == connectedClientId) + { + continue; + } ClientsProcessingSceneEvent.Add(connectedClientId, false); } @@ -218,7 +241,10 @@ private bool HasFinished() } // Return the local scene event's AsyncOperation status - return m_AsyncOperation.isDone; + // Note: Integration tests process scene loading through a queue + // and the AsyncOperation could not be assigned for several + // network tick periods. Return false if that is the case. + return m_AsyncOperation == null ? false : m_AsyncOperation.isDone; } /// diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index c2f3dfe557..aad8529813 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -274,16 +274,6 @@ protected void CreateServerAndClients() CreateServerAndClients(NumberOfClients); } - protected virtual void OnNewClientCreated(NetworkManager networkManager) - { - - } - - protected virtual void OnNewClientStartedAndConnected(NetworkManager networkManager) - { - - } - private void AddRemoveNetworkManager(NetworkManager networkManager, bool addNetworkManager) { var clientNetworkManagersList = new List(m_ClientNetworkManagers); @@ -299,6 +289,37 @@ private void AddRemoveNetworkManager(NetworkManager networkManager, bool addNetw m_NumberOfClients = clientNetworkManagersList.Count; } + /// + /// CreateAndStartNewClient Only + /// Invoked when the newly created client has been created + /// + protected virtual void OnNewClientCreated(NetworkManager networkManager) + { + + } + + /// + /// CreateAndStartNewClient Only + /// Invoked when the newly created client has been created and started + /// + protected virtual void OnNewClientStarted(NetworkManager networkManager) + { + } + + /// + /// CreateAndStartNewClient Only + /// Invoked when the newly created client has been created, started, and connected + /// to the server-host. + /// + protected virtual void OnNewClientStartedAndConnected(NetworkManager networkManager) + { + + } + + /// + /// This will create, start, and connect a new client while in the middle of an + /// integration test. + /// protected IEnumerator CreateAndStartNewClient() { var networkManager = NetcodeIntegrationTestHelpers.CreateNewClient(m_ClientNetworkManagers.Length); @@ -309,9 +330,15 @@ protected IEnumerator CreateAndStartNewClient() OnNewClientCreated(networkManager); NetcodeIntegrationTestHelpers.StartOneClient(networkManager); + AddRemoveNetworkManager(networkManager, true); + + OnNewClientStarted(networkManager); + // Wait for the new client to connect yield return WaitForClientsConnectedOrTimeOut(); + + OnNewClientStartedAndConnected(networkManager); if (s_GlobalTimeoutHelper.TimedOut) { AddRemoveNetworkManager(networkManager, false); @@ -322,6 +349,9 @@ protected IEnumerator CreateAndStartNewClient() VerboseDebug($"[{networkManager.name}] Created and connected!"); } + /// + /// This will stop a client while in the middle of an integration test + /// protected IEnumerator StopOneClient(NetworkManager networkManager, bool destroy = false) { NetcodeIntegrationTestHelpers.StopOneClient(networkManager, destroy); diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationValidationTest.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationValidationTest.cs index 91564ac13f..5e5eba1106 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationValidationTest.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationValidationTest.cs @@ -1,6 +1,5 @@ using System.Collections; using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using UnityEngine; using UnityEngine.SceneManagement; @@ -12,48 +11,23 @@ namespace TestProject.RuntimeTests { public class ClientSynchronizationValidationTest : NetcodeIntegrationTest { - protected override int NumberOfClients => 1; + protected override int NumberOfClients => 0; private const string k_FirstSceneToLoad = "UnitTestBaseScene"; - private const string k_SecondSceneToSkip = "InSceneNetworkObject"; - private const string k_ThirdSceneToLoad = "EmptyScene"; - private bool m_CanStartServerAndClients; - private List m_ClientSceneVerifiers = new List(); + private const string k_SecondSceneToLoad = "InSceneNetworkObject"; + private const string k_ThirdSceneToSkip = "EmptyScene"; - protected override bool CanStartServerAndClients() - { - return m_CanStartServerAndClients; - } + private List m_ClientSceneVerifiers = new List(); - protected override IEnumerator OnStartedServerAndClients() + protected override void OnNewClientStarted(NetworkManager networkManager) { - // Create ClientSceneVerificationHandlers for each client - foreach (var client in m_ClientNetworkManagers) - { - m_ClientSceneVerifiers.Add(new ClientSceneVerificationHandler(client)); - } - return base.OnStartedServerAndClients(); + m_ClientSceneVerifiers.Add(new ClientSceneVerificationHandler(networkManager)); + base.OnNewClientStarted(networkManager); } [UnityTest] public IEnumerator ClientVerifySceneBeforeLoading() { - // Because despawning a client will cause it to shutdown and clean everything in the - // scene hierarchy, we have to prevent one of the clients from spawning initially before - // we test synchronizing late joining clients. - // So, we prevent the automatic starting of the server and clients, remove the client we - // will be targeting to join late from the m_ClientNetworkManagers array, start the server - // and the remaining client, despawn the in-scene NetworkObject, and then start and synchronize - // the clientToTest. - var clientToTest = m_ClientNetworkManagers[0]; - var clients = m_ClientNetworkManagers.ToList(); - clients.Remove(clientToTest); - m_ClientNetworkManagers = clients.ToArray(); - m_CanStartServerAndClients = true; - yield return StartServerAndClients(); - clients.Add(clientToTest); - m_ClientNetworkManagers = clients.ToArray(); - - var scenesToLoad = new List() { k_FirstSceneToLoad, k_SecondSceneToSkip, k_ThirdSceneToLoad }; + var scenesToLoad = new List() { k_FirstSceneToLoad, k_SecondSceneToLoad, k_ThirdSceneToSkip }; m_ServerNetworkManager.SceneManager.OnLoadComplete += OnLoadComplete; foreach (var sceneToLoad in scenesToLoad) { @@ -65,15 +39,10 @@ public IEnumerator ClientVerifySceneBeforeLoading() AssertOnTimeout($"Timed out waiting for scene {m_SceneBeingLoaded} to finish loading!"); } - // Now late join a client to make sure the client synchronizes to 2 of the 3 scenes loaded - NetcodeIntegrationTestHelpers.StartOneClient(clientToTest); - yield return WaitForConditionOrTimeOut(() => (clientToTest.IsConnectedClient && clientToTest.IsListening)); - AssertOnTimeout($"Timed out waiting for {clientToTest.name} to reconnect!"); - - yield return s_DefaultWaitForTick; + yield return CreateAndStartNewClient(); - // Update the newly joined client information - ClientNetworkManagerPostStartInit(); + yield return WaitForConditionOrTimeOut(m_ClientSceneVerifiers[0].HasLoadedExpectedScenes); + AssertOnTimeout($"Timed out waiting for the client to have loaded the expected scenes"); // Check to make sure only the two scenes were loaded and one // completely skipped. @@ -110,15 +79,20 @@ public ClientSceneVerificationHandler(NetworkManager networkManager) m_NetworkManager.SceneManager.OnLoad += ClientSceneManager_OnLoad; m_NetworkManager.SceneManager.OnLoadComplete += ClientSceneManager_OnLoadComplete; m_ValidSceneEventCount.Add(k_FirstSceneToLoad, 0); - m_ValidSceneEventCount.Add(k_SecondSceneToSkip, 0); - m_ValidSceneEventCount.Add(k_ThirdSceneToLoad, 0); + m_ValidSceneEventCount.Add(k_SecondSceneToLoad, 0); + m_ValidSceneEventCount.Add(k_ThirdSceneToSkip, 0); + } + + public bool HasLoadedExpectedScenes() + { + return m_ValidSceneEventCount[k_FirstSceneToLoad] == 2 && m_ValidSceneEventCount[k_SecondSceneToLoad] == 2; } public void ValidateScenesLoaded() { - Assert.IsFalse(m_ValidSceneEventCount[k_SecondSceneToSkip] > 0, $"Client still loaded the invalidated scene {k_SecondSceneToSkip}!"); - Assert.IsTrue(m_ValidSceneEventCount[k_FirstSceneToLoad] == 1, $"Client did not load and process the validated scene {k_FirstSceneToLoad}! Expected (1) but was ({m_ValidSceneEventCount[k_FirstSceneToLoad]})"); - Assert.IsTrue(m_ValidSceneEventCount[k_ThirdSceneToLoad] == 1, $"Client did not load and process the validated scene {k_ThirdSceneToLoad}! Expected (1) but was ({m_ValidSceneEventCount[k_ThirdSceneToLoad]})"); + Assert.IsTrue(m_ValidSceneEventCount[k_ThirdSceneToSkip] == 0, $"Client still loaded the invalidated scene {k_ThirdSceneToSkip}!"); + Assert.IsTrue(m_ValidSceneEventCount[k_FirstSceneToLoad] == 2, $"Client did not load and process the validated scene {k_FirstSceneToLoad}! Expected (1) but was ({m_ValidSceneEventCount[k_FirstSceneToLoad]})"); + Assert.IsTrue(m_ValidSceneEventCount[k_SecondSceneToLoad] == 2, $"Client did not load and process the validated scene {k_SecondSceneToLoad}! Expected (1) but was ({m_ValidSceneEventCount[k_SecondSceneToLoad]})"); } private void ClientSceneManager_OnLoadComplete(ulong clientId, string sceneName, LoadSceneMode loadSceneMode) @@ -139,7 +113,7 @@ private void ClientSceneManager_OnLoad(ulong clientId, string sceneName, LoadSce private bool VerifySceneBeforeLoading(int sceneIndex, string sceneName, LoadSceneMode loadSceneMode) { - if (sceneName == k_SecondSceneToSkip) + if (sceneName == k_ThirdSceneToSkip) { return false; } diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerUsageTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerUsageTests.cs index 2077b573a8..693992ce17 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerUsageTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerUsageTests.cs @@ -134,5 +134,41 @@ private void ClientSceneManager_OnLoadComplete(ulong clientId, string sceneName, m_ClientLoadedScene = true; } } + + private int m_LoadEventCompletedInvocationCount; + + /// + /// This test validates that a host only receives one OnLoadEventCompleted callback per scene loading event when + /// no clients are connected. + /// Note: the fix was within SceneEventProgress but is associated with NetworkSceneManager + /// + [UnityTest] + public IEnumerator HostReceivesOneLoadEventCompletedNotification() + { + // Host only test + if (!m_UseHost) + { + yield break; + } + + yield return StopOneClient(m_ClientNetworkManagers[0]); + m_LoadEventCompletedInvocationCount = 0; + m_ServerNetworkManager.SceneManager.OnLoadEventCompleted += SceneManager_OnLoadEventCompleted; + + var retStatus = m_ServerNetworkManager.SceneManager.LoadScene(k_AdditiveScene1, LoadSceneMode.Additive); + Assert.AreEqual(retStatus, SceneEventProgressStatus.Started); + yield return WaitForConditionOrTimeOut(() => m_LoadEventCompletedInvocationCount > 0); + AssertOnTimeout($"Host timed out loading scene {k_AdditiveScene1} additively!"); + + // Wait one tick to make sure any other notifications are not triggered. + yield return s_DefaultWaitForTick; + + Assert.IsTrue(m_LoadEventCompletedInvocationCount == 1, $"Expected OnLoadEventCompleted to be triggered once but was triggered {m_LoadEventCompletedInvocationCount} times!"); + } + + private void SceneManager_OnLoadEventCompleted(string sceneName, LoadSceneMode loadSceneMode, System.Collections.Generic.List clientsCompleted, System.Collections.Generic.List clientsTimedOut) + { + m_LoadEventCompletedInvocationCount++; + } } } From bb135635cde92ad9fc97ba77a86be29dd345e571 Mon Sep 17 00:00:00 2001 From: Jesse Olmer Date: Thu, 3 Nov 2022 07:45:33 -0700 Subject: [PATCH 118/138] chore: Better NetworkBehaviour index out of range message (#2295) Changes the error message to be more easily actionable for users Co-authored-by: Noel Stephens --- .../Runtime/Core/NetworkObject.cs | 15 +++++++++++++- .../Tests/Editor/NetworkObjectTests.cs | 20 ++++++++----------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 100ab8404f..8b9d6b5f52 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1051,7 +1051,20 @@ internal NetworkBehaviour GetNetworkBehaviourAtOrderIndex(ushort index) { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { - NetworkLog.LogError($"Behaviour index was out of bounds. Did you mess up the order of your {nameof(NetworkBehaviour)}s?"); + NetworkLog.LogError($"{nameof(NetworkBehaviour)} index {index} was out of bounds for {name}. NetworkBehaviours must be the same, and in the same order, between server and client."); + } + + if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) + { + var currentKnownChildren = new System.Text.StringBuilder(); + currentKnownChildren.Append($"Known child {nameof(NetworkBehaviour)}s:"); + for (int i = 0; i < ChildNetworkBehaviours.Count; i++) + { + var childNetworkBehaviour = ChildNetworkBehaviours[i]; + currentKnownChildren.Append($" [{i}] {childNetworkBehaviour.__getTypeName()}"); + currentKnownChildren.Append(i < ChildNetworkBehaviours.Count - 1 ? "," : "."); + } + NetworkLog.LogInfo(currentKnownChildren.ToString()); } return null; diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkObjectTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/NetworkObjectTests.cs index d887dcf75b..cb14dbf79f 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/NetworkObjectTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/NetworkObjectTests.cs @@ -1,3 +1,4 @@ +using System.Text.RegularExpressions; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; @@ -31,19 +32,17 @@ public void NetworkManagerOverrideTest() } [Test] - public void GetBehaviourIndexNone() + [TestCase(0)] + [TestCase(1)] + [TestCase(2)] + public void GetBehaviourIndexNone(int index) { var gameObject = new GameObject(nameof(GetBehaviourIndexNone)); var networkObject = gameObject.AddComponent(); - // TODO: Maybe not hardcode message? - LogAssert.Expect(LogType.Error, $"[Netcode] Behaviour index was out of bounds. Did you mess up the order of your {nameof(NetworkBehaviour)}s?"); - LogAssert.Expect(LogType.Error, $"[Netcode] Behaviour index was out of bounds. Did you mess up the order of your {nameof(NetworkBehaviour)}s?"); - LogAssert.Expect(LogType.Error, $"[Netcode] Behaviour index was out of bounds. Did you mess up the order of your {nameof(NetworkBehaviour)}s?"); + LogAssert.Expect(LogType.Error, new Regex(".*out of bounds.*")); - Assert.That(networkObject.GetNetworkBehaviourAtOrderIndex(0), Is.Null); - Assert.That(networkObject.GetNetworkBehaviourAtOrderIndex(1), Is.Null); - Assert.That(networkObject.GetNetworkBehaviourAtOrderIndex(2), Is.Null); + Assert.That(networkObject.GetNetworkBehaviourAtOrderIndex((ushort)index), Is.Null); // Cleanup Object.DestroyImmediate(gameObject); @@ -56,13 +55,10 @@ public void GetBehaviourIndexOne() var networkObject = gameObject.AddComponent(); var networkBehaviour = gameObject.AddComponent(); - // TODO: Maybe not hardcode message? - LogAssert.Expect(LogType.Error, $"[Netcode] Behaviour index was out of bounds. Did you mess up the order of your {nameof(NetworkBehaviour)}s?"); - LogAssert.Expect(LogType.Error, $"[Netcode] Behaviour index was out of bounds. Did you mess up the order of your {nameof(NetworkBehaviour)}s?"); + LogAssert.Expect(LogType.Error, new Regex(".*out of bounds.*")); Assert.That(networkObject.GetNetworkBehaviourAtOrderIndex(0), Is.EqualTo(networkBehaviour)); Assert.That(networkObject.GetNetworkBehaviourAtOrderIndex(1), Is.Null); - Assert.That(networkObject.GetNetworkBehaviourAtOrderIndex(2), Is.Null); // Cleanup Object.DestroyImmediate(gameObject); From 1f5f97ccc614491874042569bd6f15cdc61c7bf1 Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Thu, 3 Nov 2022 20:08:04 -0400 Subject: [PATCH 119/138] fix: Error when using 'Use Encryption' with secure Relay (#2289) * fix: Error when using 'Use Encryption' with secure Relay * Add PR number to CHANGELOG entry --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Transports/UTP/UnityTransport.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 5d9b4a9e6a..f60dab9fa6 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -15,6 +15,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed - Fixed issue where the host would receive more than one event completed notification when loading or unloading a scene only when no clients were connected. (#2292) +- Fixed an issue in `UnityTransport` where an error would be logged if the 'Use Encryption' flag was enabled with a Relay configuration that used a secure protocol. (#2289) - Fixed issue where in-scene placed `NetworkObjects` were not honoring the `AutoObjectParentSync` property. (#2281) - Fixed the issue where `NetworkManager.OnClientConnectedCallback` was being invoked before in-scene placed `NetworkObject`s had been spawned when starting `NetworkManager` as a host. (#2277) - Creating a `FastBufferReader` with `Allocator.None` will not result in extra memory being allocated for the buffer (since it's owned externally in that scenario). (#2265) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index f616b1d01f..84519388c5 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -1441,7 +1441,7 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, { if (m_ProtocolType == ProtocolType.RelayUnityTransport) { - if (m_RelayServerData.IsSecure != 0) + if (m_RelayServerData.IsSecure == 0) { // log an error because we have mismatched configuration Debug.LogError("Mismatched security configuration, between Relay and local NetworkManager settings"); From 8fcad5b32290c2f47aee17068cc82a93f86c1de7 Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Mon, 7 Nov 2022 12:09:21 -0500 Subject: [PATCH 120/138] feat: disconnect reason (#2280) * disconnect reason --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Core/NetworkManager.cs | 40 +++++++++++++ .../Messaging/DisconnectReasonMessage.cs | 38 +++++++++++++ .../Messaging/DisconnectReasonMessage.cs.meta | 11 ++++ .../Tests/Editor/DisconnectMessageTests.cs | 56 +++++++++++++++++++ .../Editor/DisconnectMessageTests.cs.meta | 11 ++++ .../{ => Messaging}/DisconnectReasonTests.cs | 35 +++++++++++- .../DisconnectReasonTests.cs.meta | 0 .../Runtime/MultiClientConnectionApproval.cs | 13 +++++ 9 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/DisconnectReasonMessage.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/DisconnectReasonMessage.cs.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Editor/DisconnectMessageTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Editor/DisconnectMessageTests.cs.meta rename com.unity.netcode.gameobjects/Tests/Runtime/{ => Messaging}/DisconnectReasonTests.cs (57%) rename com.unity.netcode.gameobjects/Tests/Runtime/{ => Messaging}/DisconnectReasonTests.cs.meta (100%) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index f60dab9fa6..425d9dd1a1 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -11,6 +11,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added - Added `NetworkObject` auto-add helper and Multiplayer Tools install reminder settings to Project Settings. (#2285) +- Added `public string DisconnectReason` getter to `NetworkManager` and `string Reason` to `ConnectionApprovalResponse`. Allows connection approval to communicate back a reason. Also added `public void DisconnectClient(ulong clientId, string reason)` allowing setting a disconnection reason, when explicitly disconnecting a client. ### Fixed diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index bd3c9a0651..0e650bd0c4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -86,6 +86,12 @@ public NetworkPrefabHandler PrefabHandler private bool m_ShuttingDown; private bool m_StopProcessingMessages; + // + // When disconnected from the server, the server may send a reason. If a reason was sent, this property will + // tell client code what the reason was. It should be queried after the OnClientDisconnectCallback is called + // + public string DisconnectReason { get; internal set; } + private class NetworkManagerHooks : INetworkHooks { private NetworkManager m_NetworkManager; @@ -448,6 +454,11 @@ public class ConnectionApprovalResponse /// If the Approval decision cannot be made immediately, the client code can set Pending to true, keep a reference to the ConnectionApprovalResponse object and write to it later. Client code must exercise care to setting all the members to the value it wants before marking Pending to false, to indicate completion. If the field is set as Pending = true, we'll monitor the object until it gets set to not pending anymore and use the parameters then. /// public bool Pending; + + // + // Optional reason. If Approved is false, this reason will be sent to the client so they know why they + // were not approved. + public string Reason; } /// @@ -894,6 +905,7 @@ private void Initialize(bool server) return; } + DisconnectReason = string.Empty; IsApproved = false; ComponentFactory.SetDefaults(); @@ -2011,12 +2023,31 @@ internal void HandleIncomingData(ulong clientId, ArraySegment payload, flo /// /// The ClientId to disconnect public void DisconnectClient(ulong clientId) + { + DisconnectClient(clientId, null); + } + + /// + /// Disconnects the remote client. + /// + /// The ClientId to disconnect + /// Disconnection reason. If set, client will receive a DisconnectReasonMessage and have the + /// reason available in the NetworkManager.DisconnectReason property + public void DisconnectClient(ulong clientId, string reason) { if (!IsServer) { throw new NotServerException($"Only server can disconnect remote clients. Please use `{nameof(Shutdown)}()` instead."); } + if (!string.IsNullOrEmpty(reason)) + { + var disconnectReason = new DisconnectReasonMessage(); + disconnectReason.Reason = reason; + SendMessage(ref disconnectReason, NetworkDelivery.Reliable, clientId); + } + MessagingSystem.ProcessSendQueues(); + OnClientDisconnectFromServer(clientId); DisconnectRemoteClient(clientId); } @@ -2250,6 +2281,15 @@ internal void HandleConnectionApproval(ulong ownerClientId, ConnectionApprovalRe } else { + if (!string.IsNullOrEmpty(response.Reason)) + { + var disconnectReason = new DisconnectReasonMessage(); + disconnectReason.Reason = response.Reason; + SendMessage(ref disconnectReason, NetworkDelivery.Reliable, ownerClientId); + + MessagingSystem.ProcessSendQueues(); + } + PendingClients.Remove(ownerClientId); DisconnectRemoteClient(ownerClientId); } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/DisconnectReasonMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/DisconnectReasonMessage.cs new file mode 100644 index 0000000000..e3167a30fa --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/DisconnectReasonMessage.cs @@ -0,0 +1,38 @@ +namespace Unity.Netcode +{ + internal struct DisconnectReasonMessage : INetworkMessage + { + public string Reason; + + public void Serialize(FastBufferWriter writer) + { + string reasonSent = Reason; + if (reasonSent == null) + { + reasonSent = string.Empty; + } + + if (writer.TryBeginWrite(FastBufferWriter.GetWriteSize(reasonSent))) + { + writer.WriteValueSafe(reasonSent); + } + else + { + writer.WriteValueSafe(string.Empty); + NetworkLog.LogWarning( + "Disconnect reason didn't fit. Disconnected without sending a reason. Consider shortening the reason string."); + } + } + + public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + { + reader.ReadValueSafe(out Reason); + return true; + } + + public void Handle(ref NetworkContext context) + { + ((NetworkManager)context.SystemOwner).DisconnectReason = Reason; + } + }; +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/DisconnectReasonMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/DisconnectReasonMessage.cs.meta new file mode 100644 index 0000000000..87bae597bb --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/DisconnectReasonMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d7742516058394f96999464f3ea32c71 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Editor/DisconnectMessageTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/DisconnectMessageTests.cs new file mode 100644 index 0000000000..e217919eeb --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/DisconnectMessageTests.cs @@ -0,0 +1,56 @@ +using NUnit.Framework; +using Unity.Collections; + +namespace Unity.Netcode.EditorTests +{ + public class DisconnectMessageTests + { + [Test] + public void EmptyDisconnectReason() + { + var networkContext = new NetworkContext(); + var writer = new FastBufferWriter(20, Allocator.Temp, 20); + var msg = new DisconnectReasonMessage(); + msg.Reason = string.Empty; + msg.Serialize(writer); + + var fbr = new FastBufferReader(writer, Allocator.Temp); + var recvMsg = new DisconnectReasonMessage(); + recvMsg.Deserialize(fbr, ref networkContext); + + Assert.IsEmpty(recvMsg.Reason); + } + + [Test] + public void DisconnectReason() + { + var networkContext = new NetworkContext(); + var writer = new FastBufferWriter(20, Allocator.Temp, 20); + var msg = new DisconnectReasonMessage(); + msg.Reason = "Foo"; + msg.Serialize(writer); + + var fbr = new FastBufferReader(writer, Allocator.Temp); + var recvMsg = new DisconnectReasonMessage(); + recvMsg.Deserialize(fbr, ref networkContext); + + Assert.AreEqual("Foo", recvMsg.Reason); + } + + [Test] + public void DisconnectReasonTooLong() + { + var networkContext = new NetworkContext(); + var writer = new FastBufferWriter(20, Allocator.Temp, 20); + var msg = new DisconnectReasonMessage(); + msg.Reason = "ThisStringIsWayLongerThanTwentyBytes"; + msg.Serialize(writer); + + var fbr = new FastBufferReader(writer, Allocator.Temp); + var recvMsg = new DisconnectReasonMessage(); + recvMsg.Deserialize(fbr, ref networkContext); + + Assert.IsEmpty(recvMsg.Reason); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/DisconnectMessageTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/DisconnectMessageTests.cs.meta new file mode 100644 index 0000000000..8cfb8e24e2 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/DisconnectMessageTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 55a1355c62fe14a118253f8bbee7c3cf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectReasonTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/DisconnectReasonTests.cs similarity index 57% rename from com.unity.netcode.gameobjects/Tests/Runtime/DisconnectReasonTests.cs rename to com.unity.netcode.gameobjects/Tests/Runtime/Messaging/DisconnectReasonTests.cs index 30cb452366..cb19fd380c 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectReasonTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/DisconnectReasonTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Text.RegularExpressions; +using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; using Unity.Netcode.TestHelpers.Runtime; @@ -25,16 +26,48 @@ protected override void OnServerAndClientsCreated() } private int m_DisconnectCount; + private bool m_ThrowOnDisconnect = false; public void OnClientDisconnectCallback(ulong clientId) { m_DisconnectCount++; - throw new SystemException("whatever"); + if (m_ThrowOnDisconnect) + { + throw new SystemException("whatever"); + } + } + + [UnityTest] + public IEnumerator DisconnectReasonTest() + { + float startTime = Time.realtimeSinceStartup; + m_ThrowOnDisconnect = false; + m_DisconnectCount = 0; + + // Add a callback for both clients, when they get disconnected + m_ClientNetworkManagers[0].OnClientDisconnectCallback += OnClientDisconnectCallback; + m_ClientNetworkManagers[1].OnClientDisconnectCallback += OnClientDisconnectCallback; + + // Disconnect both clients, from the server + m_ServerNetworkManager.DisconnectClient(m_ClientNetworkManagers[0].LocalClientId, "Bogus reason 1"); + m_ServerNetworkManager.DisconnectClient(m_ClientNetworkManagers[1].LocalClientId, "Bogus reason 2"); + + while (m_DisconnectCount < 2 && Time.realtimeSinceStartup < startTime + 10.0f) + { + yield return null; + } + + Assert.AreEqual(m_ClientNetworkManagers[0].DisconnectReason, "Bogus reason 1"); + Assert.AreEqual(m_ClientNetworkManagers[1].DisconnectReason, "Bogus reason 2"); + + Debug.Assert(m_DisconnectCount == 2); } [UnityTest] public IEnumerator DisconnectExceptionTest() { + m_ThrowOnDisconnect = true; + m_DisconnectCount = 0; float startTime = Time.realtimeSinceStartup; // Add a callback for first client, when they get disconnected diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectReasonTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/DisconnectReasonTests.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/DisconnectReasonTests.cs.meta rename to com.unity.netcode.gameobjects/Tests/Runtime/Messaging/DisconnectReasonTests.cs.meta diff --git a/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs b/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs index 6cf6c456e4..b6904f4f2d 100644 --- a/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs +++ b/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs @@ -171,6 +171,11 @@ private IEnumerator ConnectionApprovalHandler(int numClients, int failureTestCou } } + foreach (var c in clientsToClean) + { + Assert.AreEqual(c.DisconnectReason, "Some valid reason"); + } + foreach (var client in clients) { // If a client failed, then it will already be shutdown @@ -228,6 +233,14 @@ private void ConnectionApprovalCallback(NetworkManager.ConnectionApprovalRequest response.Rotation = null; response.PlayerPrefabHash = m_PrefabOverrideGlobalObjectIdHash; } + if (!response.Approved) + { + response.Reason = "Some valid reason"; + } + else + { + response.Reason = string.Empty; + } } From e057143f8f7a634ad2a37d61add5e090b60dd10d Mon Sep 17 00:00:00 2001 From: Kitty Draper <284434+ShadauxCat@users.noreply.github.com> Date: Tue, 8 Nov 2022 10:57:42 -0600 Subject: [PATCH 121/138] feat: QoL: Byte Packing of integers [MTT-4924] (#2276) Adds byte packing for most integer fields in the SDK (a few, such as hashes, are left un-packed because they're likely to be large integers and not benefit - and may possibly actually be harmed - by variable-length encoding). Integers in `RPC`s and `NetworkVariable`s are also packed properly. No functionality has been added to allow a value to be sent unpacked in `RPC`s or `NetworkVariable`s in cases where packing may be detrimental, but this can be achieved as follows: ```csharp public struct UnpackedInt32 : INetworkSerializeByMemcpy { int Value; } public NetworkVariable MyVariable; ```` In addition to byte packing most integer fields, this commit updates `WriteValueBitPacked()` to be able to handle the full range of its type (now using an extra byte - and thus ending up making the representation larger instead of smaller - for values at the extreme ends of the size), at the cost of using one extra bit to encode the size of the type. --- com.unity.netcode.gameobjects/CHANGELOG.md | 4 + .../Editor/CodeGen/NetworkBehaviourILPP.cs | 197 ++++++- .../Runtime/Core/NetworkBehaviour.cs | 6 + .../Runtime/Core/NetworkManager.cs | 23 +- .../Runtime/Core/NetworkObject.cs | 168 +++--- .../Messages/ChangeOwnershipMessage.cs | 6 +- .../Messages/ConnectionApprovedMessage.cs | 21 +- .../Messaging/Messages/CreateObjectMessage.cs | 2 +- .../Messages/DestroyObjectMessage.cs | 6 +- .../Messages/NetworkVariableDeltaMessage.cs | 17 +- .../Messaging/Messages/ParentSyncMessage.cs | 36 +- .../Runtime/Messaging/Messages/RpcMessages.cs | 23 +- .../Messaging/Messages/TimeSyncMessage.cs | 4 +- .../Runtime/Messaging/MessagingSystem.cs | 2 +- .../NetworkVariableSerialization.cs | 112 ++++ .../Runtime/SceneManagement/SceneEventData.cs | 5 +- .../Runtime/Serialization/BytePacker.cs | 216 +++----- .../Runtime/Serialization/ByteUnpacker.cs | 219 ++++---- .../Runtime/Serialization/ByteUtility.cs | 58 +++ .../Runtime/Serialization/ByteUtility.cs.meta | 3 + .../Runtime/Spawning/NetworkSpawnManager.cs | 36 +- .../TestHelpers/Runtime/DebugNetworkHooks.cs | 63 +++ .../Runtime/DebugNetworkHooks.cs.meta | 3 + .../Runtime/NetcodeIntegrationTest.cs | 19 + .../Editor/Serialization/BytePackerTests.cs | 491 ++++-------------- .../Metrics/OwnershipChangeMetricsTests.cs | 21 +- .../Runtime/Metrics/ServerLogsMetricTests.cs | 28 +- .../NetworkTransform/NetworkTransformTests.cs | 3 +- 28 files changed, 968 insertions(+), 824 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Runtime/Serialization/ByteUtility.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Serialization/ByteUtility.cs.meta create mode 100644 com.unity.netcode.gameobjects/TestHelpers/Runtime/DebugNetworkHooks.cs create mode 100644 com.unity.netcode.gameobjects/TestHelpers/Runtime/DebugNetworkHooks.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 425d9dd1a1..5cd304969a 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -13,6 +13,10 @@ Additional documentation and release notes are available at [Multiplayer Documen - Added `NetworkObject` auto-add helper and Multiplayer Tools install reminder settings to Project Settings. (#2285) - Added `public string DisconnectReason` getter to `NetworkManager` and `string Reason` to `ConnectionApprovalResponse`. Allows connection approval to communicate back a reason. Also added `public void DisconnectClient(ulong clientId, string reason)` allowing setting a disconnection reason, when explicitly disconnecting a client. +### Changed + +- Optimized bandwidth usage by encoding most integer fields using variable-length encoding. (#2276) + ### Fixed - Fixed issue where the host would receive more than one event completed notification when loading or unloading a scene only when no clients were connected. (#2292) diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index ce7d3bb08f..4c1fe206fa 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -139,6 +139,19 @@ private bool IsMemcpyableType(TypeReference type) return false; } + private bool IsSpecialCaseType(TypeReference type) + { + foreach (var supportedType in SpecialCaseTypes) + { + if (type.FullName == supportedType.FullName) + { + return true; + } + } + + return false; + } + private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) { foreach (var typeDefinition in assembly.MainModule.Types) @@ -153,6 +166,11 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) foreach (var type in m_WrappedNetworkVariableTypes) { + if (IsSpecialCaseType(type)) + { + continue; + } + // If a serializable type isn't found, FallbackSerializer will be used automatically, which will // call into UserNetworkVariableSerialization, giving the user a chance to define their own serializaiton // for types that aren't in our official supported types list. @@ -257,6 +275,20 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef; private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef; + private MethodReference m_BytePacker_WriteValueBitPacked_Short_MethodRef; + private MethodReference m_BytePacker_WriteValueBitPacked_UShort_MethodRef; + private MethodReference m_BytePacker_WriteValueBitPacked_Int_MethodRef; + private MethodReference m_BytePacker_WriteValueBitPacked_UInt_MethodRef; + private MethodReference m_BytePacker_WriteValueBitPacked_Long_MethodRef; + private MethodReference m_BytePacker_WriteValueBitPacked_ULong_MethodRef; + + private MethodReference m_ByteUnpacker_ReadValueBitPacked_Short_MethodRef; + private MethodReference m_ByteUnpacker_ReadValueBitPacked_UShort_MethodRef; + private MethodReference m_ByteUnpacker_ReadValueBitPacked_Int_MethodRef; + private MethodReference m_ByteUnpacker_ReadValueBitPacked_UInt_MethodRef; + private MethodReference m_ByteUnpacker_ReadValueBitPacked_Long_MethodRef; + private MethodReference m_ByteUnpacker_ReadValueBitPacked_ULong_MethodRef; + private TypeReference m_FastBufferWriter_TypeRef; private readonly Dictionary m_FastBufferWriter_WriteValue_MethodRefs = new Dictionary(); private readonly List m_FastBufferWriter_ExtensionMethodRefs = new List(); @@ -276,12 +308,13 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) typeof(decimal), typeof(double), typeof(float), - typeof(int), + // the following types have special handling + /*typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(short), - typeof(ushort), + typeof(ushort),*/ typeof(Vector2), typeof(Vector3), typeof(Vector2Int), @@ -293,6 +326,16 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) typeof(Ray), typeof(Ray2D) }; + internal static readonly Type[] SpecialCaseTypes = new[] + { + // the following types have special handling + typeof(int), + typeof(uint), + typeof(long), + typeof(ulong), + typeof(short), + typeof(ushort), + }; private const string k_Debug_LogError = nameof(Debug.LogError); private const string k_NetworkManager_LocalClientId = nameof(NetworkManager.LocalClientId); @@ -343,6 +386,8 @@ private bool ImportReferences(ModuleDefinition moduleDefinition) TypeDefinition fastBufferWriterTypeDef = null; TypeDefinition fastBufferReaderTypeDef = null; TypeDefinition networkVariableSerializationTypesTypeDef = null; + TypeDefinition bytePackerTypeDef = null; + TypeDefinition byteUnpackerTypeDef = null; foreach (var netcodeTypeDef in m_NetcodeModule.GetAllTypes()) { if (networkManagerTypeDef == null && netcodeTypeDef.Name == nameof(NetworkManager)) @@ -398,6 +443,18 @@ private bool ImportReferences(ModuleDefinition moduleDefinition) networkVariableSerializationTypesTypeDef = netcodeTypeDef; continue; } + + if (bytePackerTypeDef == null && netcodeTypeDef.Name == nameof(BytePacker)) + { + bytePackerTypeDef = netcodeTypeDef; + continue; + } + + if (byteUnpackerTypeDef == null && netcodeTypeDef.Name == nameof(ByteUnpacker)) + { + byteUnpackerTypeDef = netcodeTypeDef; + continue; + } } foreach (var methodDef in debugTypeDef.Methods) @@ -652,6 +709,82 @@ private bool ImportReferences(ModuleDefinition moduleDefinition) } } + foreach (var method in bytePackerTypeDef.Methods) + { + if (!method.IsStatic) + { + continue; + } + + switch (method.Name) + { + case nameof(BytePacker.WriteValueBitPacked): + if (method.Parameters[1].ParameterType.FullName == typeof(short).FullName) + { + m_BytePacker_WriteValueBitPacked_Short_MethodRef = m_MainModule.ImportReference(method); + } + else if (method.Parameters[1].ParameterType.FullName == typeof(ushort).FullName) + { + m_BytePacker_WriteValueBitPacked_UShort_MethodRef = m_MainModule.ImportReference(method); + } + else if (method.Parameters[1].ParameterType.FullName == typeof(int).FullName) + { + m_BytePacker_WriteValueBitPacked_Int_MethodRef = m_MainModule.ImportReference(method); + } + else if (method.Parameters[1].ParameterType.FullName == typeof(uint).FullName) + { + m_BytePacker_WriteValueBitPacked_UInt_MethodRef = m_MainModule.ImportReference(method); + } + else if (method.Parameters[1].ParameterType.FullName == typeof(long).FullName) + { + m_BytePacker_WriteValueBitPacked_Long_MethodRef = m_MainModule.ImportReference(method); + } + else if (method.Parameters[1].ParameterType.FullName == typeof(ulong).FullName) + { + m_BytePacker_WriteValueBitPacked_ULong_MethodRef = m_MainModule.ImportReference(method); + } + break; + } + } + + foreach (var method in byteUnpackerTypeDef.Methods) + { + if (!method.IsStatic) + { + continue; + } + + switch (method.Name) + { + case nameof(ByteUnpacker.ReadValueBitPacked): + if (method.Parameters[1].ParameterType.FullName == typeof(short).MakeByRefType().FullName) + { + m_ByteUnpacker_ReadValueBitPacked_Short_MethodRef = m_MainModule.ImportReference(method); + } + else if (method.Parameters[1].ParameterType.FullName == typeof(ushort).MakeByRefType().FullName) + { + m_ByteUnpacker_ReadValueBitPacked_UShort_MethodRef = m_MainModule.ImportReference(method); + } + else if (method.Parameters[1].ParameterType.FullName == typeof(int).MakeByRefType().FullName) + { + m_ByteUnpacker_ReadValueBitPacked_Int_MethodRef = m_MainModule.ImportReference(method); + } + else if (method.Parameters[1].ParameterType.FullName == typeof(uint).MakeByRefType().FullName) + { + m_ByteUnpacker_ReadValueBitPacked_UInt_MethodRef = m_MainModule.ImportReference(method); + } + else if (method.Parameters[1].ParameterType.FullName == typeof(long).MakeByRefType().FullName) + { + m_ByteUnpacker_ReadValueBitPacked_Long_MethodRef = m_MainModule.ImportReference(method); + } + else if (method.Parameters[1].ParameterType.FullName == typeof(ulong).MakeByRefType().FullName) + { + m_ByteUnpacker_ReadValueBitPacked_ULong_MethodRef = m_MainModule.ImportReference(method); + } + break; + } + } + return true; } @@ -1008,6 +1141,36 @@ private MethodReference GetFastBufferWriterWriteMethod(string name, TypeReferenc private bool GetWriteMethodForParameter(TypeReference paramType, out MethodReference methodRef) { + if (paramType.FullName == typeof(short).FullName) + { + methodRef = m_BytePacker_WriteValueBitPacked_Short_MethodRef; + return true; + } + if (paramType.FullName == typeof(ushort).FullName) + { + methodRef = m_BytePacker_WriteValueBitPacked_UShort_MethodRef; + return true; + } + if (paramType.FullName == typeof(int).FullName) + { + methodRef = m_BytePacker_WriteValueBitPacked_Int_MethodRef; + return true; + } + if (paramType.FullName == typeof(uint).FullName) + { + methodRef = m_BytePacker_WriteValueBitPacked_UInt_MethodRef; + return true; + } + if (paramType.FullName == typeof(long).FullName) + { + methodRef = m_BytePacker_WriteValueBitPacked_Long_MethodRef; + return true; + } + if (paramType.FullName == typeof(ulong).FullName) + { + methodRef = m_BytePacker_WriteValueBitPacked_ULong_MethodRef; + return true; + } var assemblyQualifiedName = paramType.FullName + ", " + paramType.Resolve().Module.Assembly.FullName; var foundMethodRef = m_FastBufferWriter_WriteValue_MethodRefs.TryGetValue(assemblyQualifiedName, out methodRef); @@ -1154,6 +1317,36 @@ private MethodReference GetFastBufferReaderReadMethod(string name, TypeReference private bool GetReadMethodForParameter(TypeReference paramType, out MethodReference methodRef) { + if (paramType.FullName == typeof(short).FullName) + { + methodRef = m_ByteUnpacker_ReadValueBitPacked_Short_MethodRef; + return true; + } + if (paramType.FullName == typeof(ushort).FullName) + { + methodRef = m_ByteUnpacker_ReadValueBitPacked_UShort_MethodRef; + return true; + } + if (paramType.FullName == typeof(int).FullName) + { + methodRef = m_ByteUnpacker_ReadValueBitPacked_Int_MethodRef; + return true; + } + if (paramType.FullName == typeof(uint).FullName) + { + methodRef = m_ByteUnpacker_ReadValueBitPacked_UInt_MethodRef; + return true; + } + if (paramType.FullName == typeof(long).FullName) + { + methodRef = m_ByteUnpacker_ReadValueBitPacked_Long_MethodRef; + return true; + } + if (paramType.FullName == typeof(ulong).FullName) + { + methodRef = m_ByteUnpacker_ReadValueBitPacked_ULong_MethodRef; + return true; + } var assemblyQualifiedName = paramType.FullName + ", " + paramType.Resolve().Module.Assembly.FullName; var foundMethodRef = m_FastBufferReader_ReadValue_MethodRefs.TryGetValue(assemblyQualifiedName, out methodRef); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 8289676383..2aff48d93e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -21,6 +21,7 @@ internal enum __RpcExecStage Client = 2 } + // NetworkBehaviourILPP will override this in derived classes to return the name of the concrete type internal virtual string __getTypeName() => nameof(NetworkBehaviour); @@ -776,6 +777,11 @@ internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClie if (canClientRead) { var writePos = writer.Position; + // Note: This value can't be packed because we don't know how large it will be in advance + // we reserve space for it, then write the data, then come back and fill in the space + // to pack here, we'd have to write data to a temporary buffer and copy it in - which + // isn't worth possibly saving one byte if and only if the data is less than 63 bytes long... + // The way we do packing, any value > 63 in a ushort will use the full 2 bytes to represent. writer.WriteValueSafe((ushort)0); var startPos = writer.Position; NetworkVariableFields[j].WriteField(writer); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 0e650bd0c4..5fcf753531 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -2192,14 +2192,11 @@ internal void HandleConnectionApproval(ulong ownerClientId, ConnectionApprovalRe // Generate a SceneObject for the player object to spawn var sceneObject = new NetworkObject.SceneObject { - Header = new NetworkObject.SceneObject.HeaderData - { - IsPlayerObject = true, - OwnerClientId = ownerClientId, - IsSceneObject = false, - HasTransform = true, - Hash = playerPrefabHash, - }, + OwnerClientId = ownerClientId, + IsPlayerObject = true, + IsSceneObject = false, + HasTransform = true, + Hash = playerPrefabHash, TargetClientId = ownerClientId, Transform = new NetworkObject.SceneObject.TransformData { @@ -2316,11 +2313,11 @@ internal void ApprovedPlayerSpawn(ulong clientId, uint playerPrefabHash) { ObjectInfo = ConnectedClients[clientId].PlayerObject.GetMessageSceneObject(clientPair.Key) }; - message.ObjectInfo.Header.Hash = playerPrefabHash; - message.ObjectInfo.Header.IsSceneObject = false; - message.ObjectInfo.Header.HasParent = false; - message.ObjectInfo.Header.IsPlayerObject = true; - message.ObjectInfo.Header.OwnerClientId = clientId; + message.ObjectInfo.Hash = playerPrefabHash; + message.ObjectInfo.IsSceneObject = false; + message.ObjectInfo.HasParent = false; + message.ObjectInfo.IsPlayerObject = true; + message.ObjectInfo.OwnerClientId = clientId; var size = SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientPair.Key); NetworkMetrics.TrackObjectSpawnSent(clientPair.Key, ConnectedClients[clientId].PlayerObject, size); } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 8b9d6b5f52..5bd7411bd0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1075,19 +1075,43 @@ internal NetworkBehaviour GetNetworkBehaviourAtOrderIndex(ushort index) internal struct SceneObject { - public struct HeaderData : INetworkSerializeByMemcpy + private byte m_BitField; + public uint Hash; + public ulong NetworkObjectId; + public ulong OwnerClientId; + + public bool IsPlayerObject + { + get => ByteUtility.GetBit(m_BitField, 0); + set => ByteUtility.SetBit(ref m_BitField, 0, value); + } + public bool HasParent + { + get => ByteUtility.GetBit(m_BitField, 1); + set => ByteUtility.SetBit(ref m_BitField, 1, value); + } + public bool IsSceneObject { - public ulong NetworkObjectId; - public ulong OwnerClientId; - public uint Hash; + get => ByteUtility.GetBit(m_BitField, 2); + set => ByteUtility.SetBit(ref m_BitField, 2, value); + } + public bool HasTransform + { + get => ByteUtility.GetBit(m_BitField, 3); + set => ByteUtility.SetBit(ref m_BitField, 3, value); + } - public bool IsPlayerObject; - public bool HasParent; - public bool IsSceneObject; - public bool HasTransform; + public bool IsLatestParentSet + { + get => ByteUtility.GetBit(m_BitField, 4); + set => ByteUtility.SetBit(ref m_BitField, 4, value); } - public HeaderData Header; + public bool WorldPositionStays + { + get => ByteUtility.GetBit(m_BitField, 5); + set => ByteUtility.SetBit(ref m_BitField, 5, value); + } //If(Metadata.HasParent) public ulong ParentObjectId; @@ -1103,7 +1127,6 @@ public struct TransformData : INetworkSerializeByMemcpy public TransformData Transform; //If(Metadata.IsReparented) - public bool IsLatestParentSet; //If(IsLatestParentSet) public ulong? LatestParent; @@ -1113,40 +1136,33 @@ public struct TransformData : INetworkSerializeByMemcpy public int NetworkSceneHandle; - public bool WorldPositionStays; - public unsafe void Serialize(FastBufferWriter writer) + public void Serialize(FastBufferWriter writer) { - var writeSize = sizeof(HeaderData); - if (Header.HasParent) - { - writeSize += FastBufferWriter.GetWriteSize(ParentObjectId); - writeSize += FastBufferWriter.GetWriteSize(WorldPositionStays); - writeSize += FastBufferWriter.GetWriteSize(IsLatestParentSet); - writeSize += IsLatestParentSet ? FastBufferWriter.GetWriteSize() : 0; - } - writeSize += Header.HasTransform ? FastBufferWriter.GetWriteSize() : 0; - writeSize += Header.IsSceneObject ? FastBufferWriter.GetWriteSize() : 0; + writer.WriteValueSafe(m_BitField); + writer.WriteValueSafe(Hash); + BytePacker.WriteValueBitPacked(writer, NetworkObjectId); + BytePacker.WriteValueBitPacked(writer, OwnerClientId); - if (!writer.TryBeginWrite(writeSize)) + if (HasParent) { - throw new OverflowException("Could not serialize SceneObject: Out of buffer space."); + BytePacker.WriteValueBitPacked(writer, ParentObjectId); + if (IsLatestParentSet) + { + BytePacker.WriteValueBitPacked(writer, LatestParent.Value); + } } - writer.WriteValue(Header); + var writeSize = 0; + writeSize += HasTransform ? FastBufferWriter.GetWriteSize() : 0; + writeSize += IsSceneObject ? FastBufferWriter.GetWriteSize() : 0; - if (Header.HasParent) + if (!writer.TryBeginWrite(writeSize)) { - writer.WriteValue(ParentObjectId); - writer.WriteValue(WorldPositionStays); - writer.WriteValue(IsLatestParentSet); - if (IsLatestParentSet) - { - writer.WriteValue(LatestParent.Value); - } + throw new OverflowException("Could not serialize SceneObject: Out of buffer space."); } - if (Header.HasTransform) + if (HasTransform) { writer.WriteValue(Transform); } @@ -1156,7 +1172,7 @@ public unsafe void Serialize(FastBufferWriter writer) // handle, it provides us with a unique and persistent "scene prefab asset" instance. // This is only set on in-scene placed NetworkObjects to reduce the over-all packet // sizes for dynamically spawned NetworkObjects. - if (Header.IsSceneObject) + if (IsSceneObject) { writer.WriteValue(OwnerObject.GetSceneOriginHandle()); } @@ -1164,51 +1180,34 @@ public unsafe void Serialize(FastBufferWriter writer) OwnerObject.WriteNetworkVariableData(writer, TargetClientId); } - public unsafe void Deserialize(FastBufferReader reader) + public void Deserialize(FastBufferReader reader) { - if (!reader.TryBeginRead(sizeof(HeaderData))) - { - throw new OverflowException("Could not deserialize SceneObject: Out of buffer space."); - } - reader.ReadValue(out Header); - var readSize = 0; - if (Header.HasParent) + reader.ReadValueSafe(out m_BitField); + reader.ReadValueSafe(out Hash); + ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId); + ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId); + + if (HasParent) { - readSize += FastBufferWriter.GetWriteSize(ParentObjectId); - readSize += FastBufferWriter.GetWriteSize(WorldPositionStays); - readSize += FastBufferWriter.GetWriteSize(IsLatestParentSet); - // We need to read at this point in order to get the IsLatestParentSet value - if (!reader.TryBeginRead(readSize)) + ByteUnpacker.ReadValueBitPacked(reader, out ParentObjectId); + if (IsLatestParentSet) { - throw new OverflowException("Could not deserialize SceneObject: Out of buffer space."); + ByteUnpacker.ReadValueBitPacked(reader, out ulong latestParent); + LatestParent = latestParent; } - - // Read the initial parenting related properties - reader.ReadValue(out ParentObjectId); - reader.ReadValue(out WorldPositionStays); - reader.ReadValue(out IsLatestParentSet); - - // Now calculate the remaining bytes to read - readSize = 0; - readSize += IsLatestParentSet ? FastBufferWriter.GetWriteSize() : 0; } - readSize += Header.HasTransform ? FastBufferWriter.GetWriteSize() : 0; - readSize += Header.IsSceneObject ? FastBufferWriter.GetWriteSize() : 0; + var readSize = 0; + readSize += HasTransform ? FastBufferWriter.GetWriteSize() : 0; + readSize += IsSceneObject ? FastBufferWriter.GetWriteSize() : 0; // Try to begin reading the remaining bytes if (!reader.TryBeginRead(readSize)) { - throw new OverflowException("Could not deserialize SceneObject: Out of buffer space."); + throw new OverflowException("Could not deserialize SceneObject: Reading past the end of the buffer"); } - if (IsLatestParentSet) - { - reader.ReadValueSafe(out ulong latestParent); - LatestParent = latestParent; - } - - if (Header.HasTransform) + if (HasTransform) { reader.ReadValue(out Transform); } @@ -1218,9 +1217,9 @@ public unsafe void Deserialize(FastBufferReader reader) // handle, it provides us with a unique and persistent "scene prefab asset" instance. // Client-side NetworkSceneManagers use this to locate their local instance of the // NetworkObject instance. - if (Header.IsSceneObject) + if (IsSceneObject) { - reader.ReadValueSafe(out NetworkSceneHandle); + reader.ReadValue(out NetworkSceneHandle); } } } @@ -1237,14 +1236,11 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId) { var obj = new SceneObject { - Header = new SceneObject.HeaderData - { - IsPlayerObject = IsPlayerObject, - NetworkObjectId = NetworkObjectId, - OwnerClientId = OwnerClientId, - IsSceneObject = IsSceneObject ?? true, - Hash = HostCheckForGlobalObjectIdHashOverride(), - }, + NetworkObjectId = NetworkObjectId, + OwnerClientId = OwnerClientId, + IsPlayerObject = IsPlayerObject, + IsSceneObject = IsSceneObject ?? true, + Hash = HostCheckForGlobalObjectIdHashOverride(), OwnerObject = this, TargetClientId = targetClientId }; @@ -1256,16 +1252,16 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId) parentNetworkObject = transform.parent.GetComponent(); // In-scene placed NetworkObjects parented under GameObjects with no NetworkObject // should set the has parent flag and preserve the world position stays value - if (parentNetworkObject == null && obj.Header.IsSceneObject) + if (parentNetworkObject == null && obj.IsSceneObject) { - obj.Header.HasParent = true; + obj.HasParent = true; obj.WorldPositionStays = m_CachedWorldPositionStays; } } if (parentNetworkObject != null) { - obj.Header.HasParent = true; + obj.HasParent = true; obj.ParentObjectId = parentNetworkObject.NetworkObjectId; obj.WorldPositionStays = m_CachedWorldPositionStays; var latestParent = GetNetworkParenting(); @@ -1279,12 +1275,12 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId) if (IncludeTransformWhenSpawning == null || IncludeTransformWhenSpawning(OwnerClientId)) { - obj.Header.HasTransform = true; + obj.HasTransform = true; // We start with the default AutoObjectParentSync values to determine which transform space we will // be synchronizing clients with. - var syncRotationPositionLocalSpaceRelative = obj.Header.HasParent && !m_CachedWorldPositionStays; - var syncScaleLocalSpaceRelative = obj.Header.HasParent && !m_CachedWorldPositionStays; + var syncRotationPositionLocalSpaceRelative = obj.HasParent && !m_CachedWorldPositionStays; + var syncScaleLocalSpaceRelative = obj.HasParent && !m_CachedWorldPositionStays; // If auto object synchronization is turned off if (!AutoObjectParentSync) @@ -1294,7 +1290,7 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId) // Scale is special, it synchronizes local space relative if it has a // parent since applying the world space scale under a parent with scale // will result in the improper scale for the child - syncScaleLocalSpaceRelative = obj.Header.HasParent; + syncScaleLocalSpaceRelative = obj.HasParent; } @@ -1333,7 +1329,7 @@ internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBuf if (networkObject == null) { // Log the error that the NetworkObject failed to construct - Debug.LogError($"Failed to spawn {nameof(NetworkObject)} for Hash {sceneObject.Header.Hash}."); + Debug.LogError($"Failed to spawn {nameof(NetworkObject)} for Hash {sceneObject.Hash}."); // If we failed to load this NetworkObject, then skip past the network variable data variableData.ReadValueSafe(out ushort varSize); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs index 19f84bc000..6ab471904c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs @@ -7,7 +7,8 @@ internal struct ChangeOwnershipMessage : INetworkMessage, INetworkSerializeByMem public void Serialize(FastBufferWriter writer) { - writer.WriteValueSafe(this); + BytePacker.WriteValueBitPacked(writer, NetworkObjectId); + BytePacker.WriteValueBitPacked(writer, OwnerClientId); } public bool Deserialize(FastBufferReader reader, ref NetworkContext context) @@ -17,7 +18,8 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context) { return false; } - reader.ReadValueSafe(out this); + ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId); + ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId); if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId)) { networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index 0a685a731d..e721334ffa 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -19,8 +19,8 @@ public void Serialize(FastBufferWriter writer) { throw new OverflowException($"Not enough space in the write buffer to serialize {nameof(ConnectionApprovedMessage)}"); } - writer.WriteValue(OwnerClientId); - writer.WriteValue(NetworkTick); + BytePacker.WriteValueBitPacked(writer, OwnerClientId); + BytePacker.WriteValueBitPacked(writer, NetworkTick); uint sceneObjectCount = 0; if (SpawnedObjectsList != null) @@ -39,13 +39,15 @@ public void Serialize(FastBufferWriter writer) ++sceneObjectCount; } } + writer.Seek(pos); - writer.WriteValue(sceneObjectCount); + // Can't pack this value because its space is reserved, so it needs to always use all the reserved space. + writer.WriteValueSafe(sceneObjectCount); writer.Seek(writer.Length); } else { - writer.WriteValue(sceneObjectCount); + writer.WriteValueSafe(sceneObjectCount); } } @@ -57,13 +59,8 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context) return false; } - if (!reader.TryBeginRead(sizeof(ulong) + sizeof(int) + sizeof(int))) - { - throw new OverflowException($"Not enough space in the buffer to read {nameof(ConnectionApprovedMessage)}"); - } - - reader.ReadValue(out OwnerClientId); - reader.ReadValue(out NetworkTick); + ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId); + ByteUnpacker.ReadValueBitPacked(reader, out NetworkTick); m_ReceivedSceneObjectData = reader; return true; } @@ -85,7 +82,7 @@ public void Handle(ref NetworkContext context) if (!networkManager.NetworkConfig.EnableSceneManagement) { networkManager.SpawnManager.DestroySceneObjects(); - m_ReceivedSceneObjectData.ReadValue(out uint sceneObjectCount); + m_ReceivedSceneObjectData.ReadValueSafe(out uint sceneObjectCount); // Deserializing NetworkVariable data is deferred from Receive() to Handle to avoid needing // to create a list to hold the data. This is a breach of convention for performance reasons. diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs index 547197da99..f27227cba8 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs @@ -21,7 +21,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context) ObjectInfo.Deserialize(reader); if (!networkManager.NetworkConfig.ForceSamePrefabs && !networkManager.SpawnManager.HasPrefab(ObjectInfo)) { - networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Header.Hash, reader, ref context); + networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Hash, reader, ref context); return false; } m_ReceivedNetworkVariableData = reader; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs index 2730088944..7e13f9cc72 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs @@ -7,7 +7,8 @@ internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcp public void Serialize(FastBufferWriter writer) { - writer.WriteValueSafe(this); + BytePacker.WriteValueBitPacked(writer, NetworkObjectId); + writer.WriteValueSafe(DestroyGameObject); } public bool Deserialize(FastBufferReader reader, ref NetworkContext context) @@ -18,7 +19,8 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context) return false; } - reader.ReadValueSafe(out this); + ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId); + reader.ReadValueSafe(out DestroyGameObject); if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject)) { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs index 22588216bd..8cb44ad1a2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs @@ -28,8 +28,8 @@ public void Serialize(FastBufferWriter writer) throw new OverflowException($"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}"); } - writer.WriteValue(NetworkObjectId); - writer.WriteValue(NetworkBehaviourIndex); + BytePacker.WriteValueBitPacked(writer, NetworkObjectId); + BytePacker.WriteValueBitPacked(writer, NetworkBehaviourIndex); for (int i = 0; i < NetworkBehaviour.NetworkVariableFields.Count; i++) { @@ -38,7 +38,7 @@ public void Serialize(FastBufferWriter writer) // This var does not belong to the currently iterating delivery group. if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { - writer.WriteValueSafe((ushort)0); + BytePacker.WriteValueBitPacked(writer, (ushort)0); } else { @@ -66,7 +66,7 @@ public void Serialize(FastBufferWriter writer) { if (!shouldWrite) { - BytePacker.WriteValueBitPacked(writer, 0); + BytePacker.WriteValueBitPacked(writer, (ushort)0); } } else @@ -112,13 +112,8 @@ public void Serialize(FastBufferWriter writer) public bool Deserialize(FastBufferReader reader, ref NetworkContext context) { - if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(NetworkObjectId) + FastBufferWriter.GetWriteSize(NetworkBehaviourIndex))) - { - throw new OverflowException($"Not enough data in the buffer to read {nameof(NetworkVariableDeltaMessage)}"); - } - - reader.ReadValue(out NetworkObjectId); - reader.ReadValue(out NetworkBehaviourIndex); + ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId); + ByteUnpacker.ReadValueBitPacked(reader, out NetworkBehaviourIndex); m_ReceivedNetworkVariableData = reader; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs index 0023b1a66c..4a68d02e7a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs @@ -6,16 +6,30 @@ internal struct ParentSyncMessage : INetworkMessage { public ulong NetworkObjectId; - public bool WorldPositionStays; + private byte m_BitField; + + public bool WorldPositionStays + { + get => ByteUtility.GetBit(m_BitField, 0); + set => ByteUtility.SetBit(ref m_BitField, 0, value); + } //If(Metadata.IsReparented) - public bool IsLatestParentSet; + public bool IsLatestParentSet + { + get => ByteUtility.GetBit(m_BitField, 1); + set => ByteUtility.SetBit(ref m_BitField, 1, value); + } //If(IsLatestParentSet) public ulong? LatestParent; // Is set when the parent should be removed (similar to IsReparented functionality but only for removing the parent) - public bool RemoveParent; + public bool RemoveParent + { + get => ByteUtility.GetBit(m_BitField, 2); + set => ByteUtility.SetBit(ref m_BitField, 2, value); + } // These additional properties are used to synchronize clients with the current position, // rotation, and scale after parenting/de-parenting (world/local space relative). This @@ -27,16 +41,13 @@ internal struct ParentSyncMessage : INetworkMessage public void Serialize(FastBufferWriter writer) { - BytePacker.WriteValuePacked(writer, NetworkObjectId); - writer.WriteValueSafe(RemoveParent); - writer.WriteValueSafe(WorldPositionStays); + BytePacker.WriteValueBitPacked(writer, NetworkObjectId); + writer.WriteValueSafe(m_BitField); if (!RemoveParent) { - writer.WriteValueSafe(IsLatestParentSet); - if (IsLatestParentSet) { - BytePacker.WriteValueBitPacked(writer, (ulong)LatestParent); + BytePacker.WriteValueBitPacked(writer, LatestParent.Value); } } @@ -54,13 +65,10 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context) return false; } - ByteUnpacker.ReadValuePacked(reader, out NetworkObjectId); - reader.ReadValueSafe(out RemoveParent); - reader.ReadValueSafe(out WorldPositionStays); + ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId); + reader.ReadValueSafe(out m_BitField); if (!RemoveParent) { - reader.ReadValueSafe(out IsLatestParentSet); - if (IsLatestParentSet) { ByteUnpacker.ReadValueBitPacked(reader, out ulong latestParent); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs index 1ab0492a93..b13db21aa4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs @@ -8,24 +8,17 @@ internal static class RpcMessageHelpers { public static unsafe void Serialize(ref FastBufferWriter writer, ref RpcMetadata metadata, ref FastBufferWriter payload) { - if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize() + payload.Length)) - { - throw new OverflowException("Not enough space in the buffer to store RPC data."); - } - - writer.WriteValue(metadata); - writer.WriteBytes(payload.GetUnsafePtr(), payload.Length); + BytePacker.WriteValueBitPacked(writer, metadata.NetworkObjectId); + BytePacker.WriteValueBitPacked(writer, metadata.NetworkBehaviourId); + BytePacker.WriteValueBitPacked(writer, metadata.NetworkRpcMethodId); + writer.WriteBytesSafe(payload.GetUnsafePtr(), payload.Length); } public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkContext context, ref RpcMetadata metadata, ref FastBufferReader payload) { - int metadataSize = FastBufferWriter.GetWriteSize(); - if (!reader.TryBeginRead(metadataSize)) - { - throw new InvalidOperationException("Not enough data in the buffer to read RPC meta."); - } - - reader.ReadValue(out metadata); + ByteUnpacker.ReadValueBitPacked(reader, out metadata.NetworkObjectId); + ByteUnpacker.ReadValueBitPacked(reader, out metadata.NetworkBehaviourId); + ByteUnpacker.ReadValueBitPacked(reader, out metadata.NetworkRpcMethodId); var networkManager = (NetworkManager)context.SystemOwner; if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(metadata.NetworkObjectId)) @@ -46,7 +39,7 @@ public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkCo return false; } - payload = new FastBufferReader(reader.GetUnsafePtr() + metadataSize, Allocator.None, reader.Length - metadataSize); + payload = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.None, reader.Length - reader.Position); #if DEVELOPMENT_BUILD || UNITY_EDITOR if (NetworkManager.__rpc_name_table.TryGetValue(metadata.NetworkRpcMethodId, out var rpcMethodName)) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs index 613bb90f75..24fcbc0563 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs @@ -6,7 +6,7 @@ internal struct TimeSyncMessage : INetworkMessage, INetworkSerializeByMemcpy public void Serialize(FastBufferWriter writer) { - writer.WriteValueSafe(this); + BytePacker.WriteValueBitPacked(writer, Tick); } public bool Deserialize(FastBufferReader reader, ref NetworkContext context) @@ -16,7 +16,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context) { return false; } - reader.ReadValueSafe(out this); + ByteUnpacker.ReadValueBitPacked(reader, out Tick); return true; } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/MessagingSystem.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/MessagingSystem.cs index 82917a1fe9..f5d379dd02 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/MessagingSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/MessagingSystem.cs @@ -74,7 +74,7 @@ internal uint GetMessageType(Type t) } public const int NON_FRAGMENTED_MESSAGE_MAX_SIZE = 1300; - public const int FRAGMENTED_MESSAGE_MAX_SIZE = BytePacker.BitPackedIntMax; + public const int FRAGMENTED_MESSAGE_MAX_SIZE = int.MaxValue; internal struct MessageWithHandler { diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs index 83c4de2de7..463f50244d 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs @@ -1,6 +1,8 @@ using System; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; +using UnityEditor; +using UnityEngine; namespace Unity.Netcode { @@ -20,6 +22,96 @@ internal interface INetworkVariableSerializer public void Read(FastBufferReader reader, ref T value); } + /// + /// Packing serializer for shorts + /// + internal class ShortSerializer : INetworkVariableSerializer + { + public void Write(FastBufferWriter writer, ref short value) + { + BytePacker.WriteValueBitPacked(writer, value); + } + public void Read(FastBufferReader reader, ref short value) + { + ByteUnpacker.ReadValueBitPacked(reader, out value); + } + } + + /// + /// Packing serializer for shorts + /// + internal class UshortSerializer : INetworkVariableSerializer + { + public void Write(FastBufferWriter writer, ref ushort value) + { + BytePacker.WriteValueBitPacked(writer, value); + } + public void Read(FastBufferReader reader, ref ushort value) + { + ByteUnpacker.ReadValueBitPacked(reader, out value); + } + } + + /// + /// Packing serializer for ints + /// + internal class IntSerializer : INetworkVariableSerializer + { + public void Write(FastBufferWriter writer, ref int value) + { + BytePacker.WriteValueBitPacked(writer, value); + } + public void Read(FastBufferReader reader, ref int value) + { + ByteUnpacker.ReadValueBitPacked(reader, out value); + } + } + + /// + /// Packing serializer for ints + /// + internal class UintSerializer : INetworkVariableSerializer + { + public void Write(FastBufferWriter writer, ref uint value) + { + BytePacker.WriteValueBitPacked(writer, value); + } + public void Read(FastBufferReader reader, ref uint value) + { + ByteUnpacker.ReadValueBitPacked(reader, out value); + } + } + + /// + /// Packing serializer for longs + /// + internal class LongSerializer : INetworkVariableSerializer + { + public void Write(FastBufferWriter writer, ref long value) + { + BytePacker.WriteValueBitPacked(writer, value); + } + public void Read(FastBufferReader reader, ref long value) + { + ByteUnpacker.ReadValueBitPacked(reader, out value); + } + } + + /// + /// Packing serializer for longs + /// + internal class UlongSerializer : INetworkVariableSerializer + { + public void Write(FastBufferWriter writer, ref ulong value) + { + BytePacker.WriteValueBitPacked(writer, value); + } + public void Read(FastBufferReader reader, ref ulong value) + { + ByteUnpacker.ReadValueBitPacked(reader, out value); + } + } + /// /// Basic serializer for unmanaged types. /// This covers primitives, built-in unity types, and IForceSerializeByMemcpy @@ -188,6 +280,26 @@ public void Read(FastBufferReader reader, ref T value) /// public static class NetworkVariableSerializationTypes { + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)] +#if UNITY_EDITOR + [InitializeOnLoadMethod] +#endif + internal static void InitializeIntegerSerialization() + { + NetworkVariableSerialization.Serializer = new ShortSerializer(); + NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; + NetworkVariableSerialization.Serializer = new UshortSerializer(); + NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; + NetworkVariableSerialization.Serializer = new IntSerializer(); + NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; + NetworkVariableSerialization.Serializer = new UintSerializer(); + NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; + NetworkVariableSerialization.Serializer = new LongSerializer(); + NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; + NetworkVariableSerialization.Serializer = new UlongSerializer(); + NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; + } + /// /// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer /// diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs index 222e437ca2..0b8936d332 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs @@ -380,7 +380,7 @@ internal void Serialize(FastBufferWriter writer) writer.WriteValueSafe(SceneEventType); // Write the scene loading mode - writer.WriteValueSafe(LoadSceneMode); + writer.WriteValueSafe((byte)LoadSceneMode); // Write the scene event progress Guid if (SceneEventType != SceneEventType.Synchronize) @@ -533,7 +533,8 @@ internal void SerializeScenePlacedObjects(FastBufferWriter writer) internal void Deserialize(FastBufferReader reader) { reader.ReadValueSafe(out SceneEventType); - reader.ReadValueSafe(out LoadSceneMode); + reader.ReadValueSafe(out byte loadSceneMode); + LoadSceneMode = (LoadSceneMode)loadSceneMode; if (SceneEventType != SceneEventType.Synchronize) { diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BytePacker.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BytePacker.cs index 737faf7b49..58b7ea1e33 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/BytePacker.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BytePacker.cs @@ -50,7 +50,7 @@ public static unsafe void WriteValuePacked(FastBufferWriter writer, TEnum [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteValuePacked(FastBufferWriter writer, float value) { - WriteUInt32Packed(writer, ToUint(value)); + WriteValueBitPacked(writer, ToUint(value)); } /// @@ -61,7 +61,7 @@ public static void WriteValuePacked(FastBufferWriter writer, float value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteValuePacked(FastBufferWriter writer, double value) { - WriteUInt64Packed(writer, ToUlong(value)); + WriteValueBitPacked(writer, ToUlong(value)); } /// @@ -98,7 +98,7 @@ public static void WriteValuePacked(FastBufferWriter writer, double value) /// The writer to write to /// Value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteValuePacked(FastBufferWriter writer, short value) => WriteUInt32Packed(writer, (ushort)Arithmetic.ZigZagEncode(value)); + public static void WriteValuePacked(FastBufferWriter writer, short value) => WriteValueBitPacked(writer, value); /// /// Write an unsigned short (UInt16) as a varint to the buffer. @@ -109,7 +109,7 @@ public static void WriteValuePacked(FastBufferWriter writer, double value) /// The writer to write to /// Value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteValuePacked(FastBufferWriter writer, ushort value) => WriteUInt32Packed(writer, value); + public static void WriteValuePacked(FastBufferWriter writer, ushort value) => WriteValueBitPacked(writer, value); /// /// Write a two-byte character as a varint to the buffer. @@ -120,7 +120,7 @@ public static void WriteValuePacked(FastBufferWriter writer, double value) /// The writer to write to /// Value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteValuePacked(FastBufferWriter writer, char c) => WriteUInt32Packed(writer, c); + public static void WriteValuePacked(FastBufferWriter writer, char c) => WriteValueBitPacked(writer, c); /// /// Write a signed int (Int32) as a ZigZag encoded varint to the buffer. @@ -128,7 +128,7 @@ public static void WriteValuePacked(FastBufferWriter writer, double value) /// The writer to write to /// Value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteValuePacked(FastBufferWriter writer, int value) => WriteUInt32Packed(writer, (uint)Arithmetic.ZigZagEncode(value)); + public static void WriteValuePacked(FastBufferWriter writer, int value) => WriteValueBitPacked(writer, value); /// /// Write an unsigned int (UInt32) to the buffer. @@ -136,7 +136,7 @@ public static void WriteValuePacked(FastBufferWriter writer, double value) /// The writer to write to /// Value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteValuePacked(FastBufferWriter writer, uint value) => WriteUInt32Packed(writer, value); + public static void WriteValuePacked(FastBufferWriter writer, uint value) => WriteValueBitPacked(writer, value); /// /// Write an unsigned long (UInt64) to the buffer. @@ -144,7 +144,7 @@ public static void WriteValuePacked(FastBufferWriter writer, double value) /// The writer to write to /// Value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteValuePacked(FastBufferWriter writer, ulong value) => WriteUInt64Packed(writer, value); + public static void WriteValuePacked(FastBufferWriter writer, ulong value) => WriteValueBitPacked(writer, value); /// /// Write a signed long (Int64) as a ZigZag encoded varint to the buffer. @@ -152,7 +152,7 @@ public static void WriteValuePacked(FastBufferWriter writer, double value) /// The writer to write to /// Value to write [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteValuePacked(FastBufferWriter writer, long value) => WriteUInt64Packed(writer, Arithmetic.ZigZagEncode(value)); + public static void WriteValuePacked(FastBufferWriter writer, long value) => WriteValueBitPacked(writer, value); /// /// Convenience method that writes two packed Vector3 from the ray to the buffer @@ -282,231 +282,183 @@ public static void WriteValuePacked(FastBufferWriter writer, string s) [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueBitPacked(FastBufferWriter writer, T value) where T: unmanaged => writer.WriteValueSafe(value); #else - /// - /// Maximum serializable value for a BitPacked ushort (minimum for unsigned is 0) + /// Obsolete value that no longer carries meaning. Do not use. /// public const ushort BitPackedUshortMax = (1 << 15) - 1; /// - /// Maximum serializable value for a BitPacked short + /// Obsolete value that no longer carries meaning. Do not use. /// public const short BitPackedShortMax = (1 << 14) - 1; /// - /// Minimum serializable value size for a BitPacked ushort + /// Obsolete value that no longer carries meaning. Do not use. /// public const short BitPackedShortMin = -(1 << 14); /// - /// Maximum serializable value for a BitPacked uint (minimum for unsigned is 0) + /// Obsolete value that no longer carries meaning. Do not use. /// public const uint BitPackedUintMax = (1 << 30) - 1; /// - /// Maximum serializable value for a BitPacked int + /// Obsolete value that no longer carries meaning. Do not use. /// public const int BitPackedIntMax = (1 << 29) - 1; /// - /// Minimum serializable value size for a BitPacked int + /// Obsolete value that no longer carries meaning. Do not use. /// public const int BitPackedIntMin = -(1 << 29); /// - /// Maximum serializable value for a BitPacked ulong (minimum for unsigned is 0) + /// Obsolete value that no longer carries meaning. Do not use. /// public const ulong BitPackedULongMax = (1L << 61) - 1; /// - /// Maximum serializable value for a BitPacked long + /// Obsolete value that no longer carries meaning. Do not use. /// public const long BitPackedLongMax = (1L << 60) - 1; /// - /// Minimum serializable value size for a BitPacked long + /// Obsolete value that no longer carries meaning. Do not use. /// public const long BitPackedLongMin = -(1L << 60); /// - /// Writes a 14-bit signed short to the buffer in a bit-encoded packed format. - /// The first bit indicates whether the value is 1 byte or 2. - /// The sign bit takes up another bit. - /// That leaves 14 bits for the value. - /// A value greater than 2^14-1 or less than -2^14 will throw an exception in editor and development builds. - /// In release builds builds the exception is not thrown and the value is truncated by losing its two - /// most significant bits after zig-zag encoding. + /// Writes a 16-bit signed short to the buffer in a bit-encoded packed format. + /// Zig-zag encoding is used to move the sign bit to the least significant bit, so that negative values + /// are still able to be compressed. + /// The first two bits indicate whether the value is 1, 2, or 3 bytes. + /// If the value uses 14 bits or less, the remaining 14 bits contain the value. + /// For performance, reasons, if the value is 15 bits or more, there will be six 0 bits, followed + /// by the original unmodified 16-bit value in the next 2 bytes. /// /// The writer to write to /// The value to pack public static void WriteValueBitPacked(FastBufferWriter writer, short value) => WriteValueBitPacked(writer, (ushort)Arithmetic.ZigZagEncode(value)); /// - /// Writes a 15-bit unsigned short to the buffer in a bit-encoded packed format. - /// The first bit indicates whether the value is 1 byte or 2. - /// That leaves 15 bits for the value. - /// A value greater than 2^15-1 will throw an exception in editor and development builds. - /// In release builds builds the exception is not thrown and the value is truncated by losing its - /// most significant bit. + /// Writes a 16-bit unsigned short to the buffer in a bit-encoded packed format. + /// The first two bits indicate whether the value is 1, 2, or 3 bytes. + /// If the value uses 14 bits or less, the remaining 14 bits contain the value. + /// For performance, reasons, if the value is 15 bits or more, there will be six 0 bits, followed + /// by the original unmodified 16-bit value in the next 2 bytes. /// /// The writer to write to /// The value to pack public static void WriteValueBitPacked(FastBufferWriter writer, ushort value) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR - if (value >= BitPackedUshortMax) + if (value > (1 << 14) - 1) { - throw new ArgumentException("BitPacked ushorts must be <= 15 bits"); - } -#endif - - if (value <= 0b0111_1111) - { - if (!writer.TryBeginWriteInternal(1)) + if (!writer.TryBeginWriteInternal(3)) { throw new OverflowException("Writing past the end of the buffer"); } - writer.WriteByte((byte)(value << 1)); + writer.WriteByte(3); + writer.WriteValue(value); return; } - if (!writer.TryBeginWriteInternal(2)) + value <<= 2; + var numBytes = BitCounter.GetUsedByteCount(value); + if (!writer.TryBeginWriteInternal(numBytes)) { throw new OverflowException("Writing past the end of the buffer"); } - writer.WriteValue((ushort)((value << 1) | 0b1)); + writer.WritePartialValue(value | (ushort)(numBytes), numBytes); } /// - /// Writes a 29-bit signed int to the buffer in a bit-encoded packed format. - /// The first two bits indicate whether the value is 1, 2, 3, or 4 bytes. - /// The sign bit takes up another bit. - /// That leaves 29 bits for the value. - /// A value greater than 2^29-1 or less than -2^29 will throw an exception in editor and development builds. - /// In release builds builds the exception is not thrown and the value is truncated by losing its three - /// most significant bits after zig-zag encoding. + /// Writes a 32-bit signed int to the buffer in a bit-encoded packed format. + /// Zig-zag encoding is used to move the sign bit to the least significant bit, so that negative values + /// are still able to be compressed. + /// The first three bits indicate whether the value is 1, 2, 3, 4, or 5 bytes. + /// If the value uses 29 bits or less, the remaining 29 bits contain the value. + /// For performance, reasons, if the value is 30 bits or more, there will be five 0 bits, followed + /// by the original unmodified 32-bit value in the next 4 bytes. /// /// The writer to write to /// The value to pack public static void WriteValueBitPacked(FastBufferWriter writer, int value) => WriteValueBitPacked(writer, (uint)Arithmetic.ZigZagEncode(value)); /// - /// Writes a 30-bit unsigned int to the buffer in a bit-encoded packed format. - /// The first two bits indicate whether the value is 1, 2, 3, or 4 bytes. - /// That leaves 30 bits for the value. - /// A value greater than 2^30-1 will throw an exception in editor and development builds. - /// In release builds builds the exception is not thrown and the value is truncated by losing its two - /// most significant bits. + /// Writes a 32-bit unsigned int to the buffer in a bit-encoded packed format. + /// The first three bits indicate whether the value is 1, 2, 3, 4, or 5 bytes. + /// If the value uses 29 bits or less, the remaining 29 bits contain the value. + /// For performance, reasons, if the value is 30 bits or more, there will be five 0 bits, followed + /// by the original unmodified 32-bit value in the next 4 bytes. /// /// The writer to write to /// The value to pack public static void WriteValueBitPacked(FastBufferWriter writer, uint value) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR - if (value > BitPackedUintMax) + if (value > (1 << 29) - 1) { - throw new ArgumentException("BitPacked uints must be <= 30 bits"); + if (!writer.TryBeginWriteInternal(5)) + { + throw new OverflowException("Writing past the end of the buffer"); + } + writer.WriteByte(5); + writer.WriteValue(value); + return; } -#endif - value <<= 2; + + value <<= 3; var numBytes = BitCounter.GetUsedByteCount(value); if (!writer.TryBeginWriteInternal(numBytes)) { throw new OverflowException("Writing past the end of the buffer"); } - writer.WritePartialValue(value | (uint)(numBytes - 1), numBytes); + writer.WritePartialValue(value | (uint)(numBytes), numBytes); } /// - /// Writes a 60-bit signed long to the buffer in a bit-encoded packed format. - /// The first three bits indicate whether the value is 1, 2, 3, 4, 5, 6, 7, or 8 bytes. - /// The sign bit takes up another bit. - /// That leaves 60 bits for the value. - /// A value greater than 2^60-1 or less than -2^60 will throw an exception in editor and development builds. - /// In release builds builds the exception is not thrown and the value is truncated by losing its four - /// most significant bits after zig-zag encoding. + /// Writes a 64-bit signed long to the buffer in a bit-encoded packed format. + /// Zig-zag encoding is used to move the sign bit to the least significant bit, so that negative values + /// are still able to be compressed. + /// The first four bits indicate whether the value is 1, 2, 3, 4, 5, 6, 7, 8, or 9 bytes. + /// If the value uses 60 bits or less, the remaining 60 bits contain the value. + /// For performance, reasons, if the value is 61 bits or more, there will be four 0 bits, followed + /// by the original unmodified 64-bit value in the next 8 bytes. /// /// The writer to write to /// The value to pack public static void WriteValueBitPacked(FastBufferWriter writer, long value) => WriteValueBitPacked(writer, Arithmetic.ZigZagEncode(value)); /// - /// Writes a 61-bit unsigned long to the buffer in a bit-encoded packed format. - /// The first three bits indicate whether the value is 1, 2, 3, 4, 5, 6, 7, or 8 bytes. - /// That leaves 31 bits for the value. - /// A value greater than 2^61-1 will throw an exception in editor and development builds. - /// In release builds builds the exception is not thrown and the value is truncated by losing its three - /// most significant bits. + /// Writes a 64-bit unsigned long to the buffer in a bit-encoded packed format. + /// The first four bits indicate whether the value is 1, 2, 3, 4, 5, 6, 7, 8, or 9 bytes. + /// If the value uses 60 bits or less, the remaining 60 bits contain the value. + /// For performance, reasons, if the value is 61 bits or more, there will be four 0 bits, followed + /// by the original unmodified 64-bit value in the next 8 bytes. /// /// The writer to write to /// The value to pack public static void WriteValueBitPacked(FastBufferWriter writer, ulong value) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR - if (value > BitPackedULongMax) + if (value > (1L << 60) - 1) { - throw new ArgumentException("BitPacked ulongs must be <= 61 bits"); + if (!writer.TryBeginWriteInternal(9)) + { + throw new OverflowException("Writing past the end of the buffer"); + } + writer.WriteByte(9); + writer.WriteValue(value); + return; } -#endif - value <<= 3; + + value <<= 4; var numBytes = BitCounter.GetUsedByteCount(value); if (!writer.TryBeginWriteInternal(numBytes)) { throw new OverflowException("Writing past the end of the buffer"); } - writer.WritePartialValue(value | (uint)(numBytes - 1), numBytes); + writer.WritePartialValue(value | (uint)(numBytes), numBytes); } #endif - - private static void WriteUInt64Packed(FastBufferWriter writer, ulong value) - { - if (value <= 240) - { - writer.WriteByteSafe((byte)value); - return; - } - if (value <= 2287) - { - writer.WriteByteSafe((byte)(((value - 240) >> 8) + 241)); - writer.WriteByteSafe((byte)(value - 240)); - return; - } - var writeBytes = BitCounter.GetUsedByteCount(value); - - if (!writer.TryBeginWriteInternal(writeBytes + 1)) - { - throw new OverflowException("Writing past the end of the buffer"); - } - writer.WriteByte((byte)(247 + writeBytes)); - writer.WritePartialValue(value, writeBytes); - } - - // Looks like the same code as WriteUInt64Packed? - // It's actually different because it will call the more efficient 32-bit version - // of BytewiseUtility.GetUsedByteCount(). - private static void WriteUInt32Packed(FastBufferWriter writer, uint value) - { - if (value <= 240) - { - writer.WriteByteSafe((byte)value); - return; - } - if (value <= 2287) - { - writer.WriteByteSafe((byte)(((value - 240) >> 8) + 241)); - writer.WriteByteSafe((byte)(value - 240)); - return; - } - var writeBytes = BitCounter.GetUsedByteCount(value); - - if (!writer.TryBeginWriteInternal(writeBytes + 1)) - { - throw new OverflowException("Writing past the end of the buffer"); - } - writer.WriteByte((byte)(247 + writeBytes)); - writer.WritePartialValue(value, writeBytes); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static unsafe uint ToUint(T value) where T : unmanaged { diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUnpacker.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUnpacker.cs index a84a0d97bc..2d2cd88716 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUnpacker.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUnpacker.cs @@ -11,7 +11,6 @@ namespace Unity.Netcode /// public static class ByteUnpacker { - #if UNITY_NETCODE_DEBUG_NO_PACKING [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -58,7 +57,7 @@ public static unsafe void ReadValuePacked(FastBufferReader reader, out TE [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ReadValuePacked(FastBufferReader reader, out float value) { - ReadUInt32Packed(reader, out uint asUInt); + ReadValueBitPacked(reader, out uint asUInt); value = ToSingle(asUInt); } @@ -70,7 +69,7 @@ public static void ReadValuePacked(FastBufferReader reader, out float value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ReadValuePacked(FastBufferReader reader, out double value) { - ReadUInt64Packed(reader, out ulong asULong); + ReadValueBitPacked(reader, out ulong asULong); value = ToDouble(asULong); } @@ -109,11 +108,7 @@ public static void ReadValuePacked(FastBufferReader reader, out sbyte value) /// The reader to read from /// Value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReadValuePacked(FastBufferReader reader, out short value) - { - ReadUInt32Packed(reader, out uint readValue); - value = (short)Arithmetic.ZigZagDecode(readValue); - } + public static void ReadValuePacked(FastBufferReader reader, out short value) => ReadValueBitPacked(reader, out value); /// /// Read an unsigned short (UInt16) as a varint from the stream. @@ -121,11 +116,7 @@ public static void ReadValuePacked(FastBufferReader reader, out short value) /// The reader to read from /// Value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReadValuePacked(FastBufferReader reader, out ushort value) - { - ReadUInt32Packed(reader, out uint readValue); - value = (ushort)readValue; - } + public static void ReadValuePacked(FastBufferReader reader, out ushort value) => ReadValueBitPacked(reader, out value); /// /// Read a two-byte character as a varint from the stream. @@ -135,7 +126,7 @@ public static void ReadValuePacked(FastBufferReader reader, out ushort value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ReadValuePacked(FastBufferReader reader, out char c) { - ReadUInt32Packed(reader, out uint readValue); + ReadValueBitPacked(reader, out ushort readValue); c = (char)readValue; } @@ -145,11 +136,7 @@ public static void ReadValuePacked(FastBufferReader reader, out char c) /// The reader to read from /// Value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReadValuePacked(FastBufferReader reader, out int value) - { - ReadUInt32Packed(reader, out uint readValue); - value = (int)Arithmetic.ZigZagDecode(readValue); - } + public static void ReadValuePacked(FastBufferReader reader, out int value) => ReadValueBitPacked(reader, out value); /// /// Read an unsigned int (UInt32) from the stream. @@ -157,7 +144,7 @@ public static void ReadValuePacked(FastBufferReader reader, out int value) /// The reader to read from /// Value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReadValuePacked(FastBufferReader reader, out uint value) => ReadUInt32Packed(reader, out value); + public static void ReadValuePacked(FastBufferReader reader, out uint value) => ReadValueBitPacked(reader, out value); /// /// Read an unsigned long (UInt64) from the stream. @@ -165,7 +152,7 @@ public static void ReadValuePacked(FastBufferReader reader, out int value) /// The reader to read from /// Value to read [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReadValuePacked(FastBufferReader reader, out ulong value) => ReadUInt64Packed(reader, out value); + public static void ReadValuePacked(FastBufferReader reader, out ulong value) => ReadValueBitPacked(reader, out value); /// /// Read a signed long (Int64) as a ZigZag encoded varint from the stream. @@ -175,8 +162,7 @@ public static void ReadValuePacked(FastBufferReader reader, out int value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ReadValuePacked(FastBufferReader reader, out long value) { - ReadUInt64Packed(reader, out ulong readValue); - value = Arithmetic.ZigZagDecode(readValue); + ReadValueBitPacked(reader, out value); } /// @@ -341,7 +327,9 @@ public static unsafe void ReadValueBitPacked(FastBufferReader reader, out ushort ushort returnValue = 0; byte* ptr = ((byte*)&returnValue); byte* data = reader.GetUnsafePtrAtCurrentPosition(); - int numBytes = (data[0] & 0b1) + 1; + // Mask out the first two bits - they contain the total byte count + // (1, 2, or 3) + int numBytes = (data[0] & 0b11); if (!reader.TryBeginReadInternal(numBytes)) { throw new OverflowException("Reading past the end of the buffer"); @@ -350,17 +338,23 @@ public static unsafe void ReadValueBitPacked(FastBufferReader reader, out ushort switch (numBytes) { case 1: - *ptr = *data; + ptr[0] = data[0]; break; case 2: - *ptr = *data; - *(ptr + 1) = *(data + 1); + ptr[0] = data[0]; + ptr[1] = data[1]; break; + case 3: + // First byte contains no data, it's just a marker. The data is in the remaining two bytes. + ptr[0] = data[1]; + ptr[1] = data[2]; + value = returnValue; + return; default: throw new InvalidOperationException("Could not read bit-packed value: impossible byte count"); } - value = (ushort)(returnValue >> 1); + value = (ushort)(returnValue >> 2); } /// @@ -386,7 +380,8 @@ public static unsafe void ReadValueBitPacked(FastBufferReader reader, out uint v uint returnValue = 0; byte* ptr = ((byte*)&returnValue); byte* data = reader.GetUnsafePtrAtCurrentPosition(); - int numBytes = (data[0] & 0b11) + 1; + // Mask out the first three bits - they contain the total byte count (1-5) + int numBytes = (data[0] & 0b111); if (!reader.TryBeginReadInternal(numBytes)) { throw new OverflowException("Reading past the end of the buffer"); @@ -395,26 +390,34 @@ public static unsafe void ReadValueBitPacked(FastBufferReader reader, out uint v switch (numBytes) { case 1: - *ptr = *data; + ptr[0] = data[0]; break; case 2: - *ptr = *data; - *(ptr + 1) = *(data + 1); + ptr[0] = data[0]; + ptr[1] = data[1]; break; case 3: - *ptr = *data; - *(ptr + 1) = *(data + 1); - *(ptr + 2) = *(data + 2); + ptr[0] = data[0]; + ptr[1] = data[1]; + ptr[2] = data[2]; break; case 4: - *ptr = *data; - *(ptr + 1) = *(data + 1); - *(ptr + 2) = *(data + 2); - *(ptr + 3) = *(data + 3); + ptr[0] = data[0]; + ptr[1] = data[1]; + ptr[2] = data[2]; + ptr[3] = data[3]; break; + case 5: + // First byte contains no data, it's just a marker. The data is in the remaining two bytes. + ptr[0] = data[1]; + ptr[1] = data[2]; + ptr[2] = data[3]; + ptr[3] = data[4]; + value = returnValue; + return; } - value = returnValue >> 2; + value = returnValue >> 3; } /// @@ -440,7 +443,8 @@ public static unsafe void ReadValueBitPacked(FastBufferReader reader, out ulong ulong returnValue = 0; byte* ptr = ((byte*)&returnValue); byte* data = reader.GetUnsafePtrAtCurrentPosition(); - int numBytes = (data[0] & 0b111) + 1; + // Mask out the first four bits - they contain the total byte count (1-9) + int numBytes = (data[0] & 0b1111); if (!reader.TryBeginReadInternal(numBytes)) { throw new OverflowException("Reading past the end of the buffer"); @@ -449,109 +453,74 @@ public static unsafe void ReadValueBitPacked(FastBufferReader reader, out ulong switch (numBytes) { case 1: - *ptr = *data; + ptr[0] = data[0]; break; case 2: - *ptr = *data; - *(ptr + 1) = *(data + 1); + ptr[0] = data[0]; + ptr[1] = data[1]; break; case 3: - *ptr = *data; - *(ptr + 1) = *(data + 1); - *(ptr + 2) = *(data + 2); + ptr[0] = data[0]; + ptr[1] = data[1]; + ptr[2] = data[2]; break; case 4: - *ptr = *data; - *(ptr + 1) = *(data + 1); - *(ptr + 2) = *(data + 2); - *(ptr + 3) = *(data + 3); + ptr[0] = data[0]; + ptr[1] = data[1]; + ptr[2] = data[2]; + ptr[3] = data[3]; break; case 5: - *ptr = *data; - *(ptr + 1) = *(data + 1); - *(ptr + 2) = *(data + 2); - *(ptr + 3) = *(data + 3); - *(ptr + 4) = *(data + 4); + ptr[0] = data[0]; + ptr[1] = data[1]; + ptr[2] = data[2]; + ptr[3] = data[3]; + ptr[4] = data[4]; break; case 6: - *ptr = *data; - *(ptr + 1) = *(data + 1); - *(ptr + 2) = *(data + 2); - *(ptr + 3) = *(data + 3); - *(ptr + 4) = *(data + 4); - *(ptr + 5) = *(data + 5); + ptr[0] = data[0]; + ptr[1] = data[1]; + ptr[2] = data[2]; + ptr[3] = data[3]; + ptr[4] = data[4]; + ptr[5] = data[5]; break; case 7: - *ptr = *data; - *(ptr + 1) = *(data + 1); - *(ptr + 2) = *(data + 2); - *(ptr + 3) = *(data + 3); - *(ptr + 4) = *(data + 4); - *(ptr + 5) = *(data + 5); - *(ptr + 6) = *(data + 6); + ptr[0] = data[0]; + ptr[1] = data[1]; + ptr[2] = data[2]; + ptr[3] = data[3]; + ptr[4] = data[4]; + ptr[5] = data[5]; + ptr[6] = data[6]; break; case 8: - *ptr = *data; - *(ptr + 1) = *(data + 1); - *(ptr + 2) = *(data + 2); - *(ptr + 3) = *(data + 3); - *(ptr + 4) = *(data + 4); - *(ptr + 5) = *(data + 5); - *(ptr + 6) = *(data + 6); - *(ptr + 7) = *(data + 7); + ptr[0] = data[0]; + ptr[1] = data[1]; + ptr[2] = data[2]; + ptr[3] = data[3]; + ptr[4] = data[4]; + ptr[5] = data[5]; + ptr[6] = data[6]; + ptr[7] = data[7]; break; + case 9: + // First byte contains no data, it's just a marker. The data is in the remaining two bytes. + ptr[0] = data[1]; + ptr[1] = data[2]; + ptr[2] = data[3]; + ptr[3] = data[4]; + ptr[4] = data[5]; + ptr[5] = data[6]; + ptr[6] = data[7]; + ptr[7] = data[8]; + value = returnValue; + return; } - value = returnValue >> 3; + value = returnValue >> 4; } #endif - private static void ReadUInt64Packed(FastBufferReader reader, out ulong value) - { - reader.ReadByteSafe(out byte firstByte); - if (firstByte <= 240) - { - value = firstByte; - return; - } - - if (firstByte <= 248) - { - reader.ReadByteSafe(out byte secondByte); - value = 240UL + ((firstByte - 241UL) << 8) + secondByte; - return; - } - - var numBytes = firstByte - 247; - if (!reader.TryBeginReadInternal(numBytes)) - { - throw new OverflowException("Reading past the end of the buffer"); - } - reader.ReadPartialValue(out value, numBytes); - } - - private static void ReadUInt32Packed(FastBufferReader reader, out uint value) - { - reader.ReadByteSafe(out byte firstByte); - if (firstByte <= 240) - { - value = firstByte; - return; - } - - if (firstByte <= 248) - { - reader.ReadByteSafe(out byte secondByte); - value = 240U + ((firstByte - 241U) << 8) + secondByte; - return; - } - - var numBytes = firstByte - 247; - if (!reader.TryBeginReadInternal(numBytes)) - { - throw new OverflowException("Reading past the end of the buffer"); - } - reader.ReadPartialValue(out value, numBytes); - } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static unsafe float ToSingle(T value) where T : unmanaged diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUtility.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUtility.cs new file mode 100644 index 0000000000..45ad1c7e3d --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUtility.cs @@ -0,0 +1,58 @@ +using System.Runtime.CompilerServices; + +namespace Unity.Netcode +{ + internal class ByteUtility + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe byte ToByte(bool b) => *(byte*)&b; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool GetBit(byte bitField, ushort bitPosition) + { + return (bitField & (1 << bitPosition)) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void SetBit(ref byte bitField, ushort bitPosition, bool value) + { + bitField = (byte)((bitField & ~(1 << bitPosition)) | (ToByte(value) << bitPosition)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool GetBit(ushort bitField, ushort bitPosition) + { + return (bitField & (1 << bitPosition)) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void SetBit(ref ushort bitField, ushort bitPosition, bool value) + { + bitField = (ushort)((bitField & ~(1 << bitPosition)) | (ToByte(value) << bitPosition)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool GetBit(uint bitField, ushort bitPosition) + { + return (bitField & (1 << bitPosition)) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void SetBit(ref uint bitField, ushort bitPosition, bool value) + { + bitField = (uint)((bitField & ~(1 << bitPosition)) | ((uint)ToByte(value) << bitPosition)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool GetBit(ulong bitField, ushort bitPosition) + { + return (bitField & (ulong)(1 << bitPosition)) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void SetBit(ref ulong bitField, ushort bitPosition, bool value) + { + bitField = ((bitField & (ulong)~(1 << bitPosition)) | ((ulong)ToByte(value) << bitPosition)); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUtility.cs.meta b/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUtility.cs.meta new file mode 100644 index 0000000000..972bc25eb1 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUtility.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 25bb0dd7157c423b8cfe0ecf06e15ae5 +timeCreated: 1666711082 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 8e21e532c0..945d6247e1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -291,13 +291,13 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId) internal bool HasPrefab(NetworkObject.SceneObject sceneObject) { - if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.Header.IsSceneObject) + if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.IsSceneObject) { - if (NetworkManager.PrefabHandler.ContainsHandler(sceneObject.Header.Hash)) + if (NetworkManager.PrefabHandler.ContainsHandler(sceneObject.Hash)) { return true; } - if (NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks.TryGetValue(sceneObject.Header.Hash, out var networkPrefab)) + if (NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks.TryGetValue(sceneObject.Hash, out var networkPrefab)) { switch (networkPrefab.Override) { @@ -312,7 +312,7 @@ internal bool HasPrefab(NetworkObject.SceneObject sceneObject) return false; } - var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(sceneObject.Header.Hash, sceneObject.NetworkSceneHandle); + var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(sceneObject.Hash, sceneObject.NetworkSceneHandle); return networkObject != null; } @@ -326,22 +326,22 @@ internal bool HasPrefab(NetworkObject.SceneObject sceneObject) internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneObject) { NetworkObject networkObject = null; - var globalObjectIdHash = sceneObject.Header.Hash; - var position = sceneObject.Header.HasTransform ? sceneObject.Transform.Position : default; - var rotation = sceneObject.Header.HasTransform ? sceneObject.Transform.Rotation : default; - var scale = sceneObject.Header.HasTransform ? sceneObject.Transform.Scale : default; - var parentNetworkId = sceneObject.Header.HasParent ? sceneObject.ParentObjectId : default; - var worldPositionStays = sceneObject.Header.HasParent ? sceneObject.WorldPositionStays : true; + var globalObjectIdHash = sceneObject.Hash; + var position = sceneObject.HasTransform ? sceneObject.Transform.Position : default; + var rotation = sceneObject.HasTransform ? sceneObject.Transform.Rotation : default; + var scale = sceneObject.HasTransform ? sceneObject.Transform.Scale : default; + var parentNetworkId = sceneObject.HasParent ? sceneObject.ParentObjectId : default; + var worldPositionStays = (!sceneObject.HasParent) || sceneObject.WorldPositionStays; var isSpawnedByPrefabHandler = false; // If scene management is disabled or the NetworkObject was dynamically spawned - if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.Header.IsSceneObject) + if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.IsSceneObject) { // If the prefab hash has a registered INetworkPrefabInstanceHandler derived class if (NetworkManager.PrefabHandler.ContainsHandler(globalObjectIdHash)) { // Let the handler spawn the NetworkObject - networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, sceneObject.Header.OwnerClientId, position, rotation); + networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, sceneObject.OwnerClientId, position, rotation); networkObject.NetworkManagerOwner = NetworkManager; isSpawnedByPrefabHandler = true; } @@ -407,7 +407,7 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO // more scenes that contain nested in-scene placed NetworkObject children yet the server's // synchronization information does not indicate the NetworkObject in question has a parent. // Under this scenario, we want to remove the parent before spawning and setting the transform values. - if (sceneObject.Header.IsSceneObject && !sceneObject.Header.HasParent && networkObject.transform.parent != null) + if (sceneObject.IsSceneObject && !sceneObject.HasParent && networkObject.transform.parent != null) { // if the in-scene placed NetworkObject has a parent NetworkObject but the synchronization information does not // include parenting, then we need to force the removal of that parent @@ -421,7 +421,7 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO // Set the transform unless we were spawned by a prefab handler // Note: prefab handlers are provided the position and rotation // but it is up to the user to set those values - if (sceneObject.Header.HasTransform && !isSpawnedByPrefabHandler) + if (sceneObject.HasTransform && !isSpawnedByPrefabHandler) { // If world position stays is true or we have auto object parent synchronization disabled // then we want to apply the position and rotation values world space relative @@ -443,7 +443,7 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO // the network prefab used to represent the player. // Note: not doing this would set the player's scale to zero since // that is the default value of Vector3. - if (!sceneObject.Header.IsPlayerObject) + if (!sceneObject.IsPlayerObject) { // Since scale is always applied to local space scale, we do the transform // space logic during serialization such that it works out whether AutoObjectParentSync @@ -452,7 +452,7 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO } } - if (sceneObject.Header.HasParent) + if (sceneObject.HasParent) { // Go ahead and set network parenting properties networkObject.SetNetworkParenting(parentNetworkId, worldPositionStays); @@ -461,7 +461,7 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO // Dynamically spawned NetworkObjects that occur during a LoadSceneMode.Single load scene event are migrated into the DDOL // until the scene is loaded. They are then migrated back into the newly loaded and currently active scene. - if (!sceneObject.Header.IsSceneObject && NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad) + if (!sceneObject.IsSceneObject && NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad) { UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject); } @@ -510,7 +510,7 @@ internal void SpawnNetworkObjectLocally(NetworkObject networkObject, in NetworkO networkObject.SetNetworkVariableData(variableData); - SpawnNetworkObjectLocallyCommon(networkObject, sceneObject.Header.NetworkObjectId, sceneObject.Header.IsSceneObject, sceneObject.Header.IsPlayerObject, sceneObject.Header.OwnerClientId, destroyWithScene); + SpawnNetworkObjectLocallyCommon(networkObject, sceneObject.NetworkObjectId, sceneObject.IsSceneObject, sceneObject.IsPlayerObject, sceneObject.OwnerClientId, destroyWithScene); } private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong ownerClientId, bool destroyWithScene) diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/DebugNetworkHooks.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/DebugNetworkHooks.cs new file mode 100644 index 0000000000..480a1c081d --- /dev/null +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/DebugNetworkHooks.cs @@ -0,0 +1,63 @@ +using System; +using UnityEngine; + +namespace Unity.Netcode.TestHelpers.Runtime +{ + internal class DebugNetworkHooks : INetworkHooks + { + public void OnBeforeSendMessage(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage + { + } + + public void OnAfterSendMessage(ulong clientId, ref T message, NetworkDelivery delivery, int messageSizeBytes) where T : INetworkMessage + { + Debug.Log($"Sending message of type {typeof(T).Name} to {clientId} ({messageSizeBytes} bytes)"); + } + + public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) + { + Debug.Log($"Receiving message of type {messageType.Name} from {senderId}"); + } + + public void OnAfterReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) + { + } + + public void OnBeforeSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery) + { + Debug.Log($"==> Sending a batch of {messageCount} messages ({batchSizeInBytes} bytes) to {clientId} ({delivery})"); + } + + public void OnAfterSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery) + { + } + + public void OnBeforeReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes) + { + Debug.Log($"<== Receiving a batch of {messageCount} messages ({batchSizeInBytes} bytes) from {senderId}"); + } + + public void OnAfterReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes) + { + } + + public bool OnVerifyCanSend(ulong destinationId, Type messageType, NetworkDelivery delivery) + { + return true; + } + + public bool OnVerifyCanReceive(ulong senderId, Type messageType, FastBufferReader messageContent, ref NetworkContext context) + { + return true; + } + + public void OnBeforeHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage + { + Debug.Log($"Handling a message of type {typeof(T).Name}"); + } + + public void OnAfterHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage + { + } + } +} diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/DebugNetworkHooks.cs.meta b/com.unity.netcode.gameobjects/TestHelpers/Runtime/DebugNetworkHooks.cs.meta new file mode 100644 index 0000000000..ed3cc37016 --- /dev/null +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/DebugNetworkHooks.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b5f8098e713443eeba116a25de551cf8 +timeCreated: 1666626883 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index aad8529813..e91eb28a1e 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -474,6 +474,8 @@ protected void ClientNetworkManagerPostStartInit() } } + protected virtual bool LogAllMessages => false; + /// /// This starts the server and clients as long as /// returns true. @@ -492,6 +494,11 @@ protected IEnumerator StartServerAndClients() Assert.Fail("Failed to start instances"); } + if (LogAllMessages) + { + EnableMessageLogging(); + } + RegisterSceneManagerHandler(); // Notification that the server and clients have been started @@ -722,6 +729,18 @@ protected void DestroySceneNetworkObjects() } } + /// + /// For debugging purposes, this will turn on verbose logging of all messages and batches sent and received + /// + protected void EnableMessageLogging() + { + m_ServerNetworkManager.MessagingSystem.Hook(new DebugNetworkHooks()); + foreach (var client in m_ClientNetworkManagers) + { + client.MessagingSystem.Hook(new DebugNetworkHooks()); + } + } + /// /// Waits for the function condition to return true or it will time out. /// This will operate at the current m_ServerNetworkManager.NetworkConfig.TickRate diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs index c4e039bf2a..3c072b4bec 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs @@ -76,116 +76,6 @@ public enum WriteType #endregion - private void CheckUnsignedPackedSize64(FastBufferWriter writer, ulong value) - { - - if (value <= 240) - { - Assert.AreEqual(1, writer.Position); - } - else if (value <= 2287) - { - Assert.AreEqual(2, writer.Position); - } - else - { - Assert.AreEqual(BitCounter.GetUsedByteCount(value) + 1, writer.Position); - } - } - - private void CheckUnsignedPackedValue64(FastBufferWriter writer, ulong value) - { - var reader = new FastBufferReader(writer, Allocator.Temp); - using (reader) - { - ByteUnpacker.ReadValuePacked(reader, out ulong readValue); - Assert.AreEqual(readValue, value); - } - } - - private void CheckUnsignedPackedSize32(FastBufferWriter writer, uint value) - { - - if (value <= 240) - { - Assert.AreEqual(1, writer.Position); - } - else if (value <= 2287) - { - Assert.AreEqual(2, writer.Position); - } - else - { - Assert.AreEqual(BitCounter.GetUsedByteCount(value) + 1, writer.Position); - } - } - - private void CheckUnsignedPackedValue32(FastBufferWriter writer, uint value) - { - var reader = new FastBufferReader(writer, Allocator.Temp); - using (reader) - { - ByteUnpacker.ReadValuePacked(reader, out uint readValue); - Assert.AreEqual(readValue, value); - } - } - - private void CheckSignedPackedSize64(FastBufferWriter writer, long value) - { - ulong asUlong = Arithmetic.ZigZagEncode(value); - - if (asUlong <= 240) - { - Assert.AreEqual(1, writer.Position); - } - else if (asUlong <= 2287) - { - Assert.AreEqual(2, writer.Position); - } - else - { - Assert.AreEqual(BitCounter.GetUsedByteCount(asUlong) + 1, writer.Position); - } - } - - private void CheckSignedPackedValue64(FastBufferWriter writer, long value) - { - var reader = new FastBufferReader(writer, Allocator.Temp); - using (reader) - { - ByteUnpacker.ReadValuePacked(reader, out long readValue); - Assert.AreEqual(readValue, value); - } - } - - private void CheckSignedPackedSize32(FastBufferWriter writer, int value) - { - ulong asUlong = Arithmetic.ZigZagEncode(value); - - if (asUlong <= 240) - { - Assert.AreEqual(1, writer.Position); - } - else if (asUlong <= 2287) - { - Assert.AreEqual(2, writer.Position); - } - else - { - Assert.AreEqual(BitCounter.GetUsedByteCount(asUlong) + 1, writer.Position); - } - } - - private void CheckSignedPackedValue32(FastBufferWriter writer, int value) - { - var reader = new FastBufferReader(writer, Allocator.Temp); - using (reader) - { - ByteUnpacker.ReadValuePacked(reader, out int readValue); - Assert.AreEqual(readValue, value); - } - } - private unsafe void VerifyBytewiseEquality(T value, T otherValue) where T : unmanaged { byte* asBytePointer = (byte*)&value; @@ -229,237 +119,94 @@ private unsafe void RunTypeTest(T value) where T : unmanaged } } - - [Test] - public void TestPacking64BitsUnsigned() + private int GetByteCount64Bits(ulong value) { - var writer = new FastBufferWriter(9, Allocator.Temp); - using (writer) - { - writer.TryBeginWrite(9); - ulong value = 0; - BytePacker.WriteValuePacked(writer, value); - Assert.AreEqual(1, writer.Position); - - for (var i = 0; i < 64; ++i) - { - value = 1UL << i; - writer.Seek(0); - writer.Truncate(); - BytePacker.WriteValuePacked(writer, value); - CheckUnsignedPackedSize64(writer, value); - CheckUnsignedPackedValue64(writer, value); - for (var j = 0; j < 8; ++j) - { - value = (1UL << i) | (1UL << j); - writer.Seek(0); - writer.Truncate(); - BytePacker.WriteValuePacked(writer, value); - CheckUnsignedPackedSize64(writer, value); - CheckUnsignedPackedValue64(writer, value); - } - } - } - } - - [Test] - public void TestPacking32BitsUnsigned() - { - var writer = new FastBufferWriter(9, Allocator.Temp); - - using (writer) - { - writer.TryBeginWrite(9); - uint value = 0; - BytePacker.WriteValuePacked(writer, value); - Assert.AreEqual(1, writer.Position); - - for (var i = 0; i < 64; ++i) - { - value = 1U << i; - writer.Seek(0); - writer.Truncate(); - BytePacker.WriteValuePacked(writer, value); - CheckUnsignedPackedSize32(writer, value); - CheckUnsignedPackedValue32(writer, value); - for (var j = 0; j < 8; ++j) - { - value = (1U << i) | (1U << j); - writer.Seek(0); - writer.Truncate(); - BytePacker.WriteValuePacked(writer, value); - CheckUnsignedPackedSize32(writer, value); - CheckUnsignedPackedValue32(writer, value); - } - } - } - } - - [Test] - public void TestPacking64BitsSigned() - { - var writer = new FastBufferWriter(9, Allocator.Temp); - - using (writer) - { - writer.TryBeginWrite(9); - long value = 0; - BytePacker.WriteValuePacked(writer, value); - Assert.AreEqual(1, writer.Position); - - for (var i = 0; i < 64; ++i) - { - value = 1L << i; - writer.Seek(0); - writer.Truncate(); - BytePacker.WriteValuePacked(writer, value); - CheckSignedPackedSize64(writer, value); - CheckSignedPackedValue64(writer, value); - - writer.Seek(0); - writer.Truncate(); - BytePacker.WriteValuePacked(writer, -value); - CheckSignedPackedSize64(writer, -value); - CheckSignedPackedValue64(writer, -value); - for (var j = 0; j < 8; ++j) - { - value = (1L << i) | (1L << j); - writer.Seek(0); - writer.Truncate(); - BytePacker.WriteValuePacked(writer, value); - CheckSignedPackedSize64(writer, value); - CheckSignedPackedValue64(writer, value); - - writer.Seek(0); - writer.Truncate(); - BytePacker.WriteValuePacked(writer, -value); - CheckSignedPackedSize64(writer, -value); - CheckSignedPackedValue64(writer, -value); - } - } - } - } - - [Test] - public void TestPacking32BitsSigned() - { - var writer = new FastBufferWriter(9, Allocator.Temp); - - using (writer) - { - writer.TryBeginWrite(5); - int value = 0; - BytePacker.WriteValuePacked(writer, value); - Assert.AreEqual(1, writer.Position); - - for (var i = 0; i < 64; ++i) - { - value = 1 << i; - writer.Seek(0); - writer.Truncate(); - BytePacker.WriteValuePacked(writer, value); - CheckSignedPackedSize32(writer, value); - CheckSignedPackedValue32(writer, value); - - writer.Seek(0); - writer.Truncate(); - BytePacker.WriteValuePacked(writer, -value); - CheckSignedPackedSize32(writer, -value); - CheckSignedPackedValue32(writer, -value); - for (var j = 0; j < 8; ++j) - { - value = (1 << i) | (1 << j); - writer.Seek(0); - writer.Truncate(); - BytePacker.WriteValuePacked(writer, value); - CheckSignedPackedSize32(writer, value); - CheckSignedPackedValue32(writer, value); - - writer.Seek(0); - writer.Truncate(); - BytePacker.WriteValuePacked(writer, -value); - CheckSignedPackedSize32(writer, -value); - CheckSignedPackedValue32(writer, -value); - } - } - } - } - - private int GetByteCount61Bits(ulong value) - { - - if (value <= 0b0001_1111) + if (value <= 0b0000_1111) { return 1; } - if (value <= 0b0001_1111_1111_1111) + if (value <= 0b0000_1111_1111_1111) { return 2; } - if (value <= 0b0001_1111_1111_1111_1111_1111) + if (value <= 0b0000_1111_1111_1111_1111_1111) { return 3; } - if (value <= 0b0001_1111_1111_1111_1111_1111_1111_1111) + if (value <= 0b0000_1111_1111_1111_1111_1111_1111_1111) { return 4; } - if (value <= 0b0001_1111_1111_1111_1111_1111_1111_1111_1111_1111) + if (value <= 0b0000_1111_1111_1111_1111_1111_1111_1111_1111_1111) { return 5; } - if (value <= 0b0001_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111) + if (value <= 0b0000_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111) { return 6; } - if (value <= 0b0001_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111) + if (value <= 0b0000_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111) { return 7; } - return 8; + if (value <= 0b0000_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111) + { + return 8; + } + + return 9; } - private int GetByteCount30Bits(uint value) + private int GetByteCount32Bits(uint value) { - if (value <= 0b0011_1111) + if (value <= 0b0001_1111) { return 1; } - if (value <= 0b0011_1111_1111_1111) + if (value <= 0b0001_1111_1111_1111) { return 2; } - if (value <= 0b0011_1111_1111_1111_1111_1111) + if (value <= 0b0001_1111_1111_1111_1111_1111) { return 3; } - return 4; + if (value <= 0b0001_1111_1111_1111_1111_1111_1111_1111) + { + return 4; + } + + return 5; } - private int GetByteCount15Bits(ushort value) + private int GetByteCount16Bits(ushort value) { - if (value <= 0b0111_1111) + if (value <= 0b0011_1111) { return 1; } + if (value <= 0b0011_1111_1111_1111) + { + return 2; + } - return 2; + return 3; } - private ulong Get61BitEncodedValue(FastBufferWriter writer) + private ulong Get64BitEncodedValue(FastBufferWriter writer) { var reader = new FastBufferReader(writer, Allocator.Temp); using (reader) @@ -469,7 +216,7 @@ private ulong Get61BitEncodedValue(FastBufferWriter writer) } } - private long Get60BitSignedEncodedValue(FastBufferWriter writer) + private long Get64BitSignedEncodedValue(FastBufferWriter writer) { var reader = new FastBufferReader(writer, Allocator.Temp); using (reader) @@ -479,7 +226,7 @@ private long Get60BitSignedEncodedValue(FastBufferWriter writer) } } - private uint Get30BitEncodedValue(FastBufferWriter writer) + private uint Get32BitEncodedValue(FastBufferWriter writer) { var reader = new FastBufferReader(writer, Allocator.Temp); using (reader) @@ -489,7 +236,7 @@ private uint Get30BitEncodedValue(FastBufferWriter writer) } } - private int Get29BitSignedEncodedValue(FastBufferWriter writer) + private int Get32BitSignedEncodedValue(FastBufferWriter writer) { var reader = new FastBufferReader(writer, Allocator.Temp); using (reader) @@ -499,7 +246,7 @@ private int Get29BitSignedEncodedValue(FastBufferWriter writer) } } - private ushort Get15BitEncodedValue(FastBufferWriter writer) + private ushort Get16BitEncodedValue(FastBufferWriter writer) { var reader = new FastBufferReader(writer, Allocator.Temp); using (reader) @@ -509,7 +256,7 @@ private ushort Get15BitEncodedValue(FastBufferWriter writer) } } - private short Get14BitSignedEncodedValue(FastBufferWriter writer) + private short Get16BitSignedEncodedValue(FastBufferWriter writer) { var reader = new FastBufferReader(writer, Allocator.Temp); using (reader) @@ -520,7 +267,7 @@ private short Get14BitSignedEncodedValue(FastBufferWriter writer) } [Test] - public void TestBitPacking61BitsUnsigned() + public void TestBitPacking64BitsUnsigned() { var writer = new FastBufferWriter(9, Allocator.Temp); @@ -530,18 +277,18 @@ public void TestBitPacking61BitsUnsigned() ulong value = 0; BytePacker.WriteValueBitPacked(writer, value); Assert.AreEqual(1, writer.Position); - Assert.AreEqual(0, writer.ToArray()[0] & 0b111); - Assert.AreEqual(value, Get61BitEncodedValue(writer)); + Assert.AreEqual(1, writer.ToArray()[0] & 0b1111); + Assert.AreEqual(value, Get64BitEncodedValue(writer)); - for (var i = 0; i < 61; ++i) + for (var i = 0; i < 64; ++i) { value = 1UL << i; writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(writer, value); - Assert.AreEqual(GetByteCount61Bits(value), writer.Position, $"Failed on {value} ({i})"); - Assert.AreEqual(GetByteCount61Bits(value) - 1, writer.ToArray()[0] & 0b111, $"Failed on {value} ({i})"); - Assert.AreEqual(value, Get61BitEncodedValue(writer)); + Assert.AreEqual(GetByteCount64Bits(value), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount64Bits(value), writer.ToArray()[0] & 0b1111, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get64BitEncodedValue(writer)); for (var j = 0; j < 8; ++j) { @@ -549,18 +296,16 @@ public void TestBitPacking61BitsUnsigned() writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(writer, value); - Assert.AreEqual(GetByteCount61Bits(value), writer.Position, $"Failed on {value} ({i}, {j})"); - Assert.AreEqual(GetByteCount61Bits(value) - 1, writer.ToArray()[0] & 0b111, $"Failed on {value} ({i}, {j})"); - Assert.AreEqual(value, Get61BitEncodedValue(writer)); + Assert.AreEqual(GetByteCount64Bits(value), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount64Bits(value), writer.ToArray()[0] & 0b1111, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get64BitEncodedValue(writer)); } } - - Assert.Throws(() => { BytePacker.WriteValueBitPacked(writer, 1UL << 61); }); } } [Test] - public void TestBitPacking60BitsSigned() + public void TestBitPacking64BitsSigned() { var writer = new FastBufferWriter(9, Allocator.Temp); @@ -570,28 +315,28 @@ public void TestBitPacking60BitsSigned() long value = 0; BytePacker.WriteValueBitPacked(writer, value); Assert.AreEqual(1, writer.Position); - Assert.AreEqual(0, writer.ToArray()[0] & 0b111); - Assert.AreEqual(value, Get60BitSignedEncodedValue(writer)); + Assert.AreEqual(1, writer.ToArray()[0] & 0b1111); + Assert.AreEqual(value, Get64BitSignedEncodedValue(writer)); - for (var i = 0; i < 61; ++i) + for (var i = 0; i < 64; ++i) { value = 1U << i; ulong zzvalue = Arithmetic.ZigZagEncode(value); writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(writer, value); - Assert.AreEqual(GetByteCount61Bits(zzvalue), writer.Position, $"Failed on {value} ({i})"); - Assert.AreEqual(GetByteCount61Bits(zzvalue) - 1, writer.ToArray()[0] & 0b111, $"Failed on {value} ({i})"); - Assert.AreEqual(value, Get60BitSignedEncodedValue(writer)); + Assert.AreEqual(GetByteCount64Bits(zzvalue), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount64Bits(zzvalue), writer.ToArray()[0] & 0b1111, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get64BitSignedEncodedValue(writer)); value = -value; zzvalue = Arithmetic.ZigZagEncode(value); writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(writer, value); - Assert.AreEqual(GetByteCount61Bits(zzvalue), writer.Position, $"Failed on {value} ({i})"); - Assert.AreEqual(GetByteCount61Bits(zzvalue) - 1, writer.ToArray()[0] & 0b111, $"Failed on {value} ({i})"); - Assert.AreEqual(value, Get60BitSignedEncodedValue(writer)); + Assert.AreEqual(GetByteCount64Bits(zzvalue), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount64Bits(zzvalue), writer.ToArray()[0] & 0b1111, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get64BitSignedEncodedValue(writer)); for (var j = 0; j < 8; ++j) { @@ -600,27 +345,25 @@ public void TestBitPacking60BitsSigned() writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(writer, value); - Assert.AreEqual(GetByteCount61Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})"); - Assert.AreEqual(GetByteCount61Bits(zzvalue) - 1, writer.ToArray()[0] & 0b111, $"Failed on {value} ({i}, {j})"); - Assert.AreEqual(value, Get60BitSignedEncodedValue(writer)); + Assert.AreEqual(GetByteCount64Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount64Bits(zzvalue), writer.ToArray()[0] & 0b1111, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get64BitSignedEncodedValue(writer)); value = -value; zzvalue = Arithmetic.ZigZagEncode(value); writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(writer, value); - Assert.AreEqual(GetByteCount61Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})"); - Assert.AreEqual(GetByteCount61Bits(zzvalue) - 1, writer.ToArray()[0] & 0b111, $"Failed on {value} ({i}, {j})"); - Assert.AreEqual(value, Get60BitSignedEncodedValue(writer)); + Assert.AreEqual(GetByteCount64Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount64Bits(zzvalue), writer.ToArray()[0] & 0b1111, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get64BitSignedEncodedValue(writer)); } } - - Assert.Throws(() => { BytePacker.WriteValueBitPacked(writer, 1UL << 61); }); } } [Test] - public void TestBitPacking30BitsUnsigned() + public void TestBitPacking32BitsUnsigned() { var writer = new FastBufferWriter(9, Allocator.Temp); @@ -630,18 +373,18 @@ public void TestBitPacking30BitsUnsigned() uint value = 0; BytePacker.WriteValueBitPacked(writer, value); Assert.AreEqual(1, writer.Position); - Assert.AreEqual(0, writer.ToArray()[0] & 0b11); - Assert.AreEqual(value, Get30BitEncodedValue(writer)); + Assert.AreEqual(1, writer.ToArray()[0] & 0b111); + Assert.AreEqual(value, Get32BitEncodedValue(writer)); - for (var i = 0; i < 30; ++i) + for (var i = 0; i < 32; ++i) { value = 1U << i; writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(writer, value); - Assert.AreEqual(GetByteCount30Bits(value), writer.Position, $"Failed on {value} ({i})"); - Assert.AreEqual(GetByteCount30Bits(value) - 1, writer.ToArray()[0] & 0b11, $"Failed on {value} ({i})"); - Assert.AreEqual(value, Get30BitEncodedValue(writer)); + Assert.AreEqual(GetByteCount32Bits(value), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount32Bits(value), writer.ToArray()[0] & 0b111, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get32BitEncodedValue(writer)); for (var j = 0; j < 8; ++j) { @@ -649,18 +392,16 @@ public void TestBitPacking30BitsUnsigned() writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(writer, value); - Assert.AreEqual(GetByteCount30Bits(value), writer.Position, $"Failed on {value} ({i}, {j})"); - Assert.AreEqual(GetByteCount30Bits(value) - 1, writer.ToArray()[0] & 0b11, $"Failed on {value} ({i}, {j})"); - Assert.AreEqual(value, Get30BitEncodedValue(writer)); + Assert.AreEqual(GetByteCount32Bits(value), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount32Bits(value), writer.ToArray()[0] & 0b111, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get32BitEncodedValue(writer)); } } - - Assert.Throws(() => { BytePacker.WriteValueBitPacked(writer, 1U << 30); }); } } [Test] - public void TestBitPacking29BitsSigned() + public void TestBitPacking32BitsSigned() { var writer = new FastBufferWriter(9, Allocator.Temp); @@ -670,28 +411,28 @@ public void TestBitPacking29BitsSigned() int value = 0; BytePacker.WriteValueBitPacked(writer, value); Assert.AreEqual(1, writer.Position); - Assert.AreEqual(0, writer.ToArray()[0] & 0b11); - Assert.AreEqual(value, Get30BitEncodedValue(writer)); + Assert.AreEqual(1, writer.ToArray()[0] & 0b111); + Assert.AreEqual(value, Get32BitEncodedValue(writer)); - for (var i = 0; i < 29; ++i) + for (var i = 0; i < 32; ++i) { value = 1 << i; uint zzvalue = (uint)Arithmetic.ZigZagEncode(value); writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(writer, value); - Assert.AreEqual(GetByteCount30Bits(zzvalue), writer.Position, $"Failed on {value} ({i})"); - Assert.AreEqual(GetByteCount30Bits(zzvalue) - 1, writer.ToArray()[0] & 0b11, $"Failed on {value} ({i})"); - Assert.AreEqual(value, Get29BitSignedEncodedValue(writer)); + Assert.AreEqual(GetByteCount32Bits(zzvalue), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount32Bits(zzvalue), writer.ToArray()[0] & 0b111, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get32BitSignedEncodedValue(writer)); value = -value; zzvalue = (uint)Arithmetic.ZigZagEncode(value); writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(writer, value); - Assert.AreEqual(GetByteCount30Bits(zzvalue), writer.Position, $"Failed on {value} ({i})"); - Assert.AreEqual(GetByteCount30Bits(zzvalue) - 1, writer.ToArray()[0] & 0b11, $"Failed on {value} ({i})"); - Assert.AreEqual(value, Get29BitSignedEncodedValue(writer)); + Assert.AreEqual(GetByteCount32Bits(zzvalue), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount32Bits(zzvalue), writer.ToArray()[0] & 0b111, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get32BitSignedEncodedValue(writer)); for (var j = 0; j < 8; ++j) { @@ -700,25 +441,25 @@ public void TestBitPacking29BitsSigned() writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(writer, value); - Assert.AreEqual(GetByteCount30Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})"); - Assert.AreEqual(GetByteCount30Bits(zzvalue) - 1, writer.ToArray()[0] & 0b11, $"Failed on {value} ({i}, {j})"); - Assert.AreEqual(value, Get29BitSignedEncodedValue(writer)); + Assert.AreEqual(GetByteCount32Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount32Bits(zzvalue), writer.ToArray()[0] & 0b111, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get32BitSignedEncodedValue(writer)); value = -value; zzvalue = (uint)Arithmetic.ZigZagEncode(value); writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(writer, value); - Assert.AreEqual(GetByteCount30Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})"); - Assert.AreEqual(GetByteCount30Bits(zzvalue) - 1, writer.ToArray()[0] & 0b11, $"Failed on {value} ({i}, {j})"); - Assert.AreEqual(value, Get29BitSignedEncodedValue(writer)); + Assert.AreEqual(GetByteCount32Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount32Bits(zzvalue), writer.ToArray()[0] & 0b111, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get32BitSignedEncodedValue(writer)); } } } } [Test] - public void TestBitPacking15BitsUnsigned() + public void TestBitPacking16BitsUnsigned() { var writer = new FastBufferWriter(9, Allocator.Temp); @@ -728,18 +469,18 @@ public void TestBitPacking15BitsUnsigned() ushort value = 0; BytePacker.WriteValueBitPacked(writer, value); Assert.AreEqual(1, writer.Position); - Assert.AreEqual(0, writer.ToArray()[0] & 0b1); - Assert.AreEqual(value, Get15BitEncodedValue(writer)); + Assert.AreEqual(1, writer.ToArray()[0] & 0b11); + Assert.AreEqual(value, Get16BitEncodedValue(writer)); - for (var i = 0; i < 15; ++i) + for (var i = 0; i < 16; ++i) { value = (ushort)(1U << i); writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(writer, value); - Assert.AreEqual(GetByteCount15Bits(value), writer.Position, $"Failed on {value} ({i})"); - Assert.AreEqual(GetByteCount15Bits(value) - 1, writer.ToArray()[0] & 0b1, $"Failed on {value} ({i})"); - Assert.AreEqual(value, Get15BitEncodedValue(writer)); + Assert.AreEqual(GetByteCount16Bits(value), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount16Bits(value), writer.ToArray()[0] & 0b11, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get16BitEncodedValue(writer)); for (var j = 0; j < 8; ++j) { @@ -747,17 +488,15 @@ public void TestBitPacking15BitsUnsigned() writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(writer, value); - Assert.AreEqual(GetByteCount15Bits(value), writer.Position, $"Failed on {value} ({i}, {j})"); - Assert.AreEqual(GetByteCount15Bits(value) - 1, writer.ToArray()[0] & 0b1, $"Failed on {value} ({i}, {j})"); - Assert.AreEqual(value, Get15BitEncodedValue(writer)); + Assert.AreEqual(GetByteCount16Bits(value), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount16Bits(value), writer.ToArray()[0] & 0b11, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get16BitEncodedValue(writer)); } } - - Assert.Throws(() => { BytePacker.WriteValueBitPacked(writer, (ushort)(1U << 15)); }); } } [Test] - public void TestBitPacking14BitsSigned() + public void TestBitPacking16BitsSigned() { var writer = new FastBufferWriter(9, Allocator.Temp); @@ -767,28 +506,28 @@ public void TestBitPacking14BitsSigned() short value = 0; BytePacker.WriteValueBitPacked(writer, value); Assert.AreEqual(1, writer.Position); - Assert.AreEqual(0, writer.ToArray()[0] & 0b1); - Assert.AreEqual(value, Get15BitEncodedValue(writer)); + Assert.AreEqual(1, writer.ToArray()[0] & 0b11); + Assert.AreEqual(value, Get16BitEncodedValue(writer)); - for (var i = 0; i < 14; ++i) + for (var i = 0; i < 16; ++i) { value = (short)(1 << i); ushort zzvalue = (ushort)Arithmetic.ZigZagEncode(value); writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(writer, value); - Assert.AreEqual(GetByteCount15Bits(zzvalue), writer.Position, $"Failed on {value} ({i})"); - Assert.AreEqual(GetByteCount15Bits(zzvalue) - 1, writer.ToArray()[0] & 0b1, $"Failed on {value} ({i})"); - Assert.AreEqual(value, Get14BitSignedEncodedValue(writer)); + Assert.AreEqual(GetByteCount16Bits(zzvalue), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount16Bits(zzvalue), writer.ToArray()[0] & 0b11, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get16BitSignedEncodedValue(writer)); value = (short)-value; zzvalue = (ushort)Arithmetic.ZigZagEncode(value); writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(writer, value); - Assert.AreEqual(GetByteCount15Bits(zzvalue), writer.Position, $"Failed on {value} ({i})"); - Assert.AreEqual(GetByteCount15Bits(zzvalue) - 1, writer.ToArray()[0] & 0b1, $"Failed on {value} ({i})"); - Assert.AreEqual(value, Get14BitSignedEncodedValue(writer)); + Assert.AreEqual(GetByteCount16Bits(zzvalue), writer.Position, $"Failed on {value} ({i})"); + Assert.AreEqual(GetByteCount16Bits(zzvalue), writer.ToArray()[0] & 0b11, $"Failed on {value} ({i})"); + Assert.AreEqual(value, Get16BitSignedEncodedValue(writer)); for (var j = 0; j < 8; ++j) { @@ -797,18 +536,18 @@ public void TestBitPacking14BitsSigned() writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(writer, value); - Assert.AreEqual(GetByteCount15Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})"); - Assert.AreEqual(GetByteCount15Bits(zzvalue) - 1, writer.ToArray()[0] & 0b1, $"Failed on {value} ({i}, {j})"); - Assert.AreEqual(value, Get14BitSignedEncodedValue(writer)); + Assert.AreEqual(GetByteCount16Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount16Bits(zzvalue), writer.ToArray()[0] & 0b11, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get16BitSignedEncodedValue(writer)); value = (short)-value; zzvalue = (ushort)Arithmetic.ZigZagEncode(value); writer.Seek(0); writer.Truncate(); BytePacker.WriteValueBitPacked(writer, value); - Assert.AreEqual(GetByteCount15Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})"); - Assert.AreEqual(GetByteCount15Bits(zzvalue) - 1, writer.ToArray()[0] & 0b1, $"Failed on {value} ({i}, {j})"); - Assert.AreEqual(value, Get14BitSignedEncodedValue(writer)); + Assert.AreEqual(GetByteCount16Bits(zzvalue), writer.Position, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(GetByteCount16Bits(zzvalue), writer.ToArray()[0] & 0b11, $"Failed on {value} ({i}, {j})"); + Assert.AreEqual(value, Get16BitSignedEncodedValue(writer)); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs index bfcfdc23eb..c6d723a4df 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Linq; using NUnit.Framework; +using Unity.Collections; using Unity.Multiplayer.Tools.MetricTypes; using UnityEngine; using UnityEngine.TestTools; @@ -43,6 +44,18 @@ private NetworkObject SpawnNetworkObject() return networkObject; } + private int GetWriteSizeForOwnerChange(NetworkObject networkObject, ulong newOwner) + { + var message = new ChangeOwnershipMessage + { + NetworkObjectId = networkObject.NetworkObjectId, + OwnerClientId = newOwner + }; + using var writer = new FastBufferWriter(1024, Allocator.Temp); + message.Serialize(writer); + return writer.Length; + } + [UnityTest] public IEnumerator TrackOwnershipChangeSentMetric() { @@ -68,7 +81,9 @@ public IEnumerator TrackOwnershipChangeSentMetric() ownershipChangeSent = metricValues.Last(); Assert.AreEqual(networkObject.NetworkObjectId, ownershipChangeSent.NetworkId.NetworkId); Assert.AreEqual(Client.LocalClientId, ownershipChangeSent.Connection.Id); - Assert.AreEqual(FastBufferWriter.GetWriteSize() + k_MessageHeaderSize, ownershipChangeSent.BytesCount); + + var serializedLength = GetWriteSizeForOwnerChange(networkObject, 1); + Assert.AreEqual(serializedLength + k_MessageHeaderSize, ownershipChangeSent.BytesCount); } [UnityTest] @@ -89,7 +104,9 @@ public IEnumerator TrackOwnershipChangeReceivedMetric() var ownershipChangeReceived = metricValues.First(); Assert.AreEqual(networkObject.NetworkObjectId, ownershipChangeReceived.NetworkId.NetworkId); - Assert.AreEqual(FastBufferWriter.GetWriteSize(), ownershipChangeReceived.BytesCount); + + var serializedLength = GetWriteSizeForOwnerChange(networkObject, 1); + Assert.AreEqual(serializedLength, ownershipChangeReceived.BytesCount); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/ServerLogsMetricTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/ServerLogsMetricTests.cs index 78d70bc342..9c5dae1acf 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/ServerLogsMetricTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/ServerLogsMetricTests.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Linq; using NUnit.Framework; +using Unity.Collections; using Unity.Multiplayer.Tools.MetricTypes; using UnityEngine.TestTools; using Unity.Netcode.TestHelpers.Runtime.Metrics; @@ -11,10 +12,8 @@ namespace Unity.Netcode.RuntimeTests.Metrics { internal class ServerLogsMetricTests : SingleClientMetricTestBase { - // Header is dynamically sized due to packing, will be 2 bytes for all test messages. - private const int k_MessageHeaderSize = 2; - private static readonly int k_ServerLogSentMessageOverhead = 2 + k_MessageHeaderSize; - private static readonly int k_ServerLogReceivedMessageOverhead = 2; + // Header is dynamically sized due to packing, will be 3 bytes for all test messages. + private const int k_MessageHeaderSize = 3; protected override IEnumerator OnSetup() { @@ -22,6 +21,19 @@ protected override IEnumerator OnSetup() return base.OnSetup(); } + + private int GetWriteSizeForLog(NetworkLog.LogType logType, string logMessage) + { + var message = new ServerLogMessage + { + LogType = logType, + Message = logMessage + }; + using var writer = new FastBufferWriter(1024, Allocator.Temp); + message.Serialize(writer); + return writer.Length; + } + [UnityTest] public IEnumerator TrackServerLogSentMetric() { @@ -41,7 +53,9 @@ public IEnumerator TrackServerLogSentMetric() var sentMetric = sentMetrics.First(); Assert.AreEqual(Server.LocalClientId, sentMetric.Connection.Id); Assert.AreEqual((uint)NetworkLog.LogType.Warning, (uint)sentMetric.LogLevel); - Assert.AreEqual(message.Length + k_ServerLogSentMessageOverhead, sentMetric.BytesCount); + + var serializedLength = GetWriteSizeForLog(NetworkLog.LogType.Warning, message); + Assert.AreEqual(serializedLength + k_MessageHeaderSize, sentMetric.BytesCount); } [UnityTest] @@ -64,7 +78,9 @@ public IEnumerator TrackServerLogReceivedMetric() var receivedMetric = receivedMetrics.First(); Assert.AreEqual(Client.LocalClientId, receivedMetric.Connection.Id); Assert.AreEqual((uint)NetworkLog.LogType.Warning, (uint)receivedMetric.LogLevel); - Assert.AreEqual(message.Length + k_ServerLogReceivedMessageOverhead, receivedMetric.BytesCount); + + var serializedLength = GetWriteSizeForLog(NetworkLog.LogType.Warning, message); + Assert.AreEqual(serializedLength, receivedMetric.BytesCount); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index 9486bdf26d..f3af7bdfd1 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -734,7 +734,6 @@ public IEnumerator TeleportTest([Values] Interpolation interpolation) Assert.IsTrue(m_DetectedPotentialInterpolatedTeleport == 0.0f, $"Detected possible interpolation on non-authority side! NonAuthority distance: {m_DetectedPotentialInterpolatedTeleport} | Target distance: {targetDistance}"); } - /// /// This test validates the method /// usage for the non-authoritative side. It will either be the owner or the server making/requesting state changes. @@ -842,7 +841,7 @@ private bool RotationMatchesValue(Vector3 rotationEulerToMatch) } if (!nonauthorityIsEqual) { - VerboseDebug($"NonAuthority position {nonAuthorityRotationEuler} != rotation to match: {rotationEulerToMatch}!"); + VerboseDebug($"NonAuthority rotation {nonAuthorityRotationEuler} != rotation to match: {rotationEulerToMatch}!"); } return auhtorityIsEqual && nonauthorityIsEqual; } From 9fcc31ad038aa0b486e0a85c4fbaff101894060d Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Wed, 9 Nov 2022 11:44:54 -0500 Subject: [PATCH 122/138] docs: xmldoc for DisconnectReason (#2300) --- .../Editor/NetworkBehaviourEditor.cs | 3 +++ .../Runtime/Core/NetworkManager.cs | 15 ++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs index d5dfa85e43..40b528835a 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs @@ -7,6 +7,9 @@ namespace Unity.Netcode.Editor { + /// + /// The for + /// [CustomEditor(typeof(NetworkBehaviour), true)] [CanEditMultipleObjects] public class NetworkBehaviourEditor : UnityEditor.Editor diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 5fcf753531..c355d95096 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -86,10 +86,10 @@ public NetworkPrefabHandler PrefabHandler private bool m_ShuttingDown; private bool m_StopProcessingMessages; - // - // When disconnected from the server, the server may send a reason. If a reason was sent, this property will - // tell client code what the reason was. It should be queried after the OnClientDisconnectCallback is called - // + /// + /// When disconnected from the server, the server may send a reason. If a reason was sent, this property will + /// tell client code what the reason was. It should be queried after the OnClientDisconnectCallback is called + /// public string DisconnectReason { get; internal set; } private class NetworkManagerHooks : INetworkHooks @@ -455,9 +455,10 @@ public class ConnectionApprovalResponse /// public bool Pending; - // - // Optional reason. If Approved is false, this reason will be sent to the client so they know why they - // were not approved. + /// + /// Optional reason. If Approved is false, this reason will be sent to the client so they know why they + /// were not approved. + /// public string Reason; } From 674fd0ee96d3bc70cbb75fbc67f3e45600b107b1 Mon Sep 17 00:00:00 2001 From: Kitty Draper <284434+ShadauxCat@users.noreply.github.com> Date: Wed, 9 Nov 2022 11:56:03 -0600 Subject: [PATCH 123/138] fix: Enable custom messages to be sent to self when running as host. (#2296) --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 +- .../Runtime/Messaging/CustomMessageManager.cs | 60 +++++++++- .../Runtime/MessageHooksConditional.cs | 10 +- .../Runtime/Messaging/NamedMessageTests.cs | 105 ++++++++++++++---- .../Runtime/Messaging/UnnamedMessageTests.cs | 96 ++++++++++++---- 5 files changed, 225 insertions(+), 48 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 5cd304969a..72765d9252 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -19,12 +19,12 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Custom messages are now properly received by the local client when they're sent while running in host mode. (#2296) - Fixed issue where the host would receive more than one event completed notification when loading or unloading a scene only when no clients were connected. (#2292) - Fixed an issue in `UnityTransport` where an error would be logged if the 'Use Encryption' flag was enabled with a Relay configuration that used a secure protocol. (#2289) - Fixed issue where in-scene placed `NetworkObjects` were not honoring the `AutoObjectParentSync` property. (#2281) - Fixed the issue where `NetworkManager.OnClientConnectedCallback` was being invoked before in-scene placed `NetworkObject`s had been spawned when starting `NetworkManager` as a host. (#2277) - Creating a `FastBufferReader` with `Allocator.None` will not result in extra memory being allocated for the buffer (since it's owned externally in that scenario). (#2265) - ### Removed - Removed the `NetworkObject` auto-add and Multiplayer Tools install reminder settings from the Menu interface. (#2285) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs index aa69ba73fc..573d645b42 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Unity.Collections; namespace Unity.Netcode { @@ -68,9 +69,23 @@ public void SendUnnamedMessage(IReadOnlyList clientIds, FastBufferWriter if (clientIds == null) { - throw new ArgumentNullException("You must pass in a valid clientId List"); + throw new ArgumentNullException(nameof(clientIds), "You must pass in a valid clientId List"); } + if (m_NetworkManager.IsHost) + { + for (var i = 0; i < clientIds.Count; ++i) + { + if (clientIds[i] == m_NetworkManager.LocalClientId) + { + InvokeUnnamedMessage( + m_NetworkManager.LocalClientId, + new FastBufferReader(messageBuffer, Allocator.None), + 0 + ); + } + } + } var message = new UnnamedMessage { SendData = messageBuffer @@ -92,6 +107,18 @@ public void SendUnnamedMessage(IReadOnlyList clientIds, FastBufferWriter /// The delivery type (QoS) to send data with public void SendUnnamedMessage(ulong clientId, FastBufferWriter messageBuffer, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced) { + if (m_NetworkManager.IsHost) + { + if (clientId == m_NetworkManager.LocalClientId) + { + InvokeUnnamedMessage( + m_NetworkManager.LocalClientId, + new FastBufferReader(messageBuffer, Allocator.None), + 0 + ); + return; + } + } var message = new UnnamedMessage { SendData = messageBuffer @@ -220,6 +247,20 @@ public void SendNamedMessage(string messageName, ulong clientId, FastBufferWrite hash = XXHash.Hash64(messageName); break; } + if (m_NetworkManager.IsHost) + { + if (clientId == m_NetworkManager.LocalClientId) + { + InvokeNamedMessage( + hash, + m_NetworkManager.LocalClientId, + new FastBufferReader(messageStream, Allocator.None), + 0 + ); + + return; + } + } var message = new NamedMessage { @@ -251,7 +292,7 @@ public void SendNamedMessage(string messageName, IReadOnlyList clientIds, if (clientIds == null) { - throw new ArgumentNullException("You must pass in a valid clientId List"); + throw new ArgumentNullException(nameof(clientIds), "You must pass in a valid clientId List"); } ulong hash = 0; @@ -264,6 +305,21 @@ public void SendNamedMessage(string messageName, IReadOnlyList clientIds, hash = XXHash.Hash64(messageName); break; } + if (m_NetworkManager.IsHost) + { + for (var i = 0; i < clientIds.Count; ++i) + { + if (clientIds[i] == m_NetworkManager.LocalClientId) + { + InvokeNamedMessage( + hash, + m_NetworkManager.LocalClientId, + new FastBufferReader(messageStream, Allocator.None), + 0 + ); + } + } + } var message = new NamedMessage { Hash = hash, diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/MessageHooksConditional.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/MessageHooksConditional.cs index f16978ef95..80b2dd84a0 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/MessageHooksConditional.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/MessageHooksConditional.cs @@ -39,7 +39,10 @@ protected override bool OnHasConditionBeenReached() if (AllMessagesReceived) { - return AllMessagesReceived; + foreach (var entry in m_MessageHookEntries) + { + entry.RemoveHook(); + } } return AllMessagesReceived; @@ -110,6 +113,11 @@ internal void AssignMessageType() where T : INetworkMessage Initialize(); } + internal void RemoveHook() + { + m_NetworkManager.MessagingSystem.Unhook(MessageHooks); + } + internal void AssignMessageType(Type type) { MessageType = type.Name; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/NamedMessageTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/NamedMessageTests.cs index f2cde70626..b96c63d9fb 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/NamedMessageTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/NamedMessageTests.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using NUnit.Framework; using Unity.Collections; -using UnityEngine; using UnityEngine.TestTools; using Unity.Netcode.TestHelpers.Runtime; @@ -27,6 +26,18 @@ protected override NetworkManagerInstatiationMode OnSetIntegrationTestMode() public IEnumerator NamedMessageIsReceivedOnClientWithContent() { var messageName = Guid.NewGuid().ToString(); + + ulong receivedMessageSender = 0; + var receivedMessageContent = new ForceNetworkSerializeByMemcpy(new Guid()); + FirstClient.CustomMessagingManager.RegisterNamedMessageHandler( + messageName, + (ulong sender, FastBufferReader reader) => + { + receivedMessageSender = sender; + + reader.ReadValueSafe(out receivedMessageContent); + }); + var messageContent = new ForceNetworkSerializeByMemcpy(Guid.NewGuid()); var writer = new FastBufferWriter(1300, Allocator.Temp); using (writer) @@ -38,9 +49,20 @@ public IEnumerator NamedMessageIsReceivedOnClientWithContent() writer); } + yield return WaitForMessageReceived(new List { FirstClient }); + + Assert.AreEqual(messageContent.Value, receivedMessageContent.Value); + Assert.AreEqual(m_ServerNetworkManager.LocalClientId, receivedMessageSender); + } + + [Test] + public void NamedMessageIsReceivedOnHostWithContent() + { + var messageName = Guid.NewGuid().ToString(); + ulong receivedMessageSender = 0; var receivedMessageContent = new ForceNetworkSerializeByMemcpy(new Guid()); - FirstClient.CustomMessagingManager.RegisterNamedMessageHandler( + m_ServerNetworkManager.CustomMessagingManager.RegisterNamedMessageHandler( messageName, (ulong sender, FastBufferReader reader) => { @@ -49,16 +71,6 @@ public IEnumerator NamedMessageIsReceivedOnClientWithContent() reader.ReadValueSafe(out receivedMessageContent); }); - yield return new WaitForSeconds(0.2f); - - Assert.AreEqual(messageContent.Value, receivedMessageContent.Value); - Assert.AreEqual(m_ServerNetworkManager.LocalClientId, receivedMessageSender); - } - - [UnityTest] - public IEnumerator NamedMessageIsReceivedOnMultipleClientsWithContent() - { - var messageName = Guid.NewGuid().ToString(); var messageContent = new ForceNetworkSerializeByMemcpy(Guid.NewGuid()); var writer = new FastBufferWriter(1300, Allocator.Temp); using (writer) @@ -66,10 +78,19 @@ public IEnumerator NamedMessageIsReceivedOnMultipleClientsWithContent() writer.WriteValueSafe(messageContent); m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage( messageName, - new List { FirstClient.LocalClientId, SecondClient.LocalClientId }, + m_ServerNetworkManager.LocalClientId, writer); } + Assert.AreEqual(messageContent.Value, receivedMessageContent.Value); + Assert.AreEqual(m_ServerNetworkManager.LocalClientId, receivedMessageSender); + } + + [UnityTest] + public IEnumerator NamedMessageIsReceivedOnMultipleClientsWithContent() + { + var messageName = Guid.NewGuid().ToString(); + ulong firstReceivedMessageSender = 0; var firstReceivedMessageContent = new ForceNetworkSerializeByMemcpy(new Guid()); FirstClient.CustomMessagingManager.RegisterNamedMessageHandler( @@ -92,26 +113,44 @@ public IEnumerator NamedMessageIsReceivedOnMultipleClientsWithContent() reader.ReadValueSafe(out secondReceivedMessageContent); }); - yield return new WaitForSeconds(0.2f); + ulong thirdReceivedMessageSender = 0; + var thirdReceivedMessageContent = new ForceNetworkSerializeByMemcpy(new Guid()); + m_ServerNetworkManager.CustomMessagingManager.RegisterNamedMessageHandler( + messageName, + (ulong sender, FastBufferReader reader) => + { + thirdReceivedMessageSender = sender; + + reader.ReadValueSafe(out thirdReceivedMessageContent); + }); + + var messageContent = new ForceNetworkSerializeByMemcpy(Guid.NewGuid()); + var writer = new FastBufferWriter(1300, Allocator.Temp); + using (writer) + { + writer.WriteValueSafe(messageContent); + m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage( + messageName, + new List { m_ServerNetworkManager.LocalClientId, FirstClient.LocalClientId, SecondClient.LocalClientId }, + writer); + } + + yield return WaitForMessageReceived(new List { FirstClient, SecondClient }); Assert.AreEqual(messageContent.Value, firstReceivedMessageContent.Value); Assert.AreEqual(m_ServerNetworkManager.LocalClientId, firstReceivedMessageSender); Assert.AreEqual(messageContent.Value, secondReceivedMessageContent.Value); Assert.AreEqual(m_ServerNetworkManager.LocalClientId, secondReceivedMessageSender); + + Assert.AreEqual(messageContent.Value, thirdReceivedMessageContent.Value); + Assert.AreEqual(m_ServerNetworkManager.LocalClientId, thirdReceivedMessageSender); } [UnityTest] public IEnumerator WhenSendingNamedMessageToAll_AllClientsReceiveIt() { var messageName = Guid.NewGuid().ToString(); - var messageContent = new ForceNetworkSerializeByMemcpy(Guid.NewGuid()); - var writer = new FastBufferWriter(1300, Allocator.Temp); - using (writer) - { - writer.WriteValueSafe(messageContent); - m_ServerNetworkManager.CustomMessagingManager.SendNamedMessageToAll(messageName, writer); - } ulong firstReceivedMessageSender = 0; var firstReceivedMessageContent = new ForceNetworkSerializeByMemcpy(new Guid()); @@ -135,13 +174,35 @@ public IEnumerator WhenSendingNamedMessageToAll_AllClientsReceiveIt() reader.ReadValueSafe(out secondReceivedMessageContent); }); - yield return new WaitForSeconds(0.2f); + ulong thirdReceivedMessageSender = 0; + var thirdReceivedMessageContent = new ForceNetworkSerializeByMemcpy(new Guid()); + m_ServerNetworkManager.CustomMessagingManager.RegisterNamedMessageHandler( + messageName, + (ulong sender, FastBufferReader reader) => + { + thirdReceivedMessageSender = sender; + + reader.ReadValueSafe(out thirdReceivedMessageContent); + }); + + var messageContent = new ForceNetworkSerializeByMemcpy(Guid.NewGuid()); + var writer = new FastBufferWriter(1300, Allocator.Temp); + using (writer) + { + writer.WriteValueSafe(messageContent); + m_ServerNetworkManager.CustomMessagingManager.SendNamedMessageToAll(messageName, writer); + } + + yield return WaitForMessageReceived(new List { FirstClient, SecondClient }); Assert.AreEqual(messageContent.Value, firstReceivedMessageContent.Value); Assert.AreEqual(m_ServerNetworkManager.LocalClientId, firstReceivedMessageSender); Assert.AreEqual(messageContent.Value, secondReceivedMessageContent.Value); Assert.AreEqual(m_ServerNetworkManager.LocalClientId, secondReceivedMessageSender); + + Assert.AreEqual(messageContent.Value, thirdReceivedMessageContent.Value); + Assert.AreEqual(m_ServerNetworkManager.LocalClientId, thirdReceivedMessageSender); } [Test] diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/UnnamedMessageTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/UnnamedMessageTests.cs index 711e7cf137..5c88696c85 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/UnnamedMessageTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/UnnamedMessageTests.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using NUnit.Framework; using Unity.Collections; -using UnityEngine; using UnityEngine.TestTools; using Unity.Netcode.TestHelpers.Runtime; @@ -19,6 +18,16 @@ public class UnnamedMessageTests : NetcodeIntegrationTest [UnityTest] public IEnumerator UnnamedMessageIsReceivedOnClientWithContent() { + ulong receivedMessageSender = 0; + var receivedMessageContent = new ForceNetworkSerializeByMemcpy(new Guid()); + FirstClient.CustomMessagingManager.OnUnnamedMessage += + (ulong sender, FastBufferReader reader) => + { + receivedMessageSender = sender; + + reader.ReadValueSafe(out receivedMessageContent); + }; + var messageContent = new ForceNetworkSerializeByMemcpy(Guid.NewGuid()); var writer = new FastBufferWriter(1300, Allocator.Temp); using (writer) @@ -29,9 +38,18 @@ public IEnumerator UnnamedMessageIsReceivedOnClientWithContent() writer); } + yield return WaitForMessageReceived(new List { FirstClient }); + + Assert.AreEqual(messageContent.Value, receivedMessageContent.Value); + Assert.AreEqual(m_ServerNetworkManager.LocalClientId, receivedMessageSender); + } + + [Test] + public void UnnamedMessageIsReceivedOnHostWithContent() + { ulong receivedMessageSender = 0; var receivedMessageContent = new ForceNetworkSerializeByMemcpy(new Guid()); - FirstClient.CustomMessagingManager.OnUnnamedMessage += + m_ServerNetworkManager.CustomMessagingManager.OnUnnamedMessage += (ulong sender, FastBufferReader reader) => { receivedMessageSender = sender; @@ -39,25 +57,23 @@ public IEnumerator UnnamedMessageIsReceivedOnClientWithContent() reader.ReadValueSafe(out receivedMessageContent); }; - yield return new WaitForSeconds(0.2f); - - Assert.AreEqual(messageContent.Value, receivedMessageContent.Value); - Assert.AreEqual(m_ServerNetworkManager.LocalClientId, receivedMessageSender); - } - - [UnityTest] - public IEnumerator UnnamedMessageIsReceivedOnMultipleClientsWithContent() - { var messageContent = new ForceNetworkSerializeByMemcpy(Guid.NewGuid()); var writer = new FastBufferWriter(1300, Allocator.Temp); using (writer) { writer.WriteValueSafe(messageContent); m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessage( - new List { FirstClient.LocalClientId, SecondClient.LocalClientId }, + m_ServerNetworkManager.LocalClientId, writer); } + Assert.AreEqual(messageContent.Value, receivedMessageContent.Value); + Assert.AreEqual(m_ServerNetworkManager.LocalClientId, receivedMessageSender); + } + + [UnityTest] + public IEnumerator UnnamedMessageIsReceivedOnMultipleClientsWithContent() + { ulong firstReceivedMessageSender = 0; var firstReceivedMessageContent = new ForceNetworkSerializeByMemcpy(new Guid()); FirstClient.CustomMessagingManager.OnUnnamedMessage += @@ -78,26 +94,41 @@ public IEnumerator UnnamedMessageIsReceivedOnMultipleClientsWithContent() reader.ReadValueSafe(out secondReceivedMessageContent); }; - yield return new WaitForSeconds(0.2f); + ulong thirdReceivedMessageSender = 0; + var thirdReceivedMessageContent = new ForceNetworkSerializeByMemcpy(new Guid()); + m_ServerNetworkManager.CustomMessagingManager.OnUnnamedMessage += + (ulong sender, FastBufferReader reader) => + { + thirdReceivedMessageSender = sender; + + reader.ReadValueSafe(out thirdReceivedMessageContent); + }; + + var messageContent = new ForceNetworkSerializeByMemcpy(Guid.NewGuid()); + var writer = new FastBufferWriter(1300, Allocator.Temp); + using (writer) + { + writer.WriteValueSafe(messageContent); + m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessage( + new List { m_ServerNetworkManager.LocalClientId, FirstClient.LocalClientId, SecondClient.LocalClientId }, + writer); + } + + yield return WaitForMessageReceived(new List { FirstClient, SecondClient }); Assert.AreEqual(messageContent.Value, firstReceivedMessageContent.Value); Assert.AreEqual(m_ServerNetworkManager.LocalClientId, firstReceivedMessageSender); Assert.AreEqual(messageContent.Value, secondReceivedMessageContent.Value); Assert.AreEqual(m_ServerNetworkManager.LocalClientId, secondReceivedMessageSender); + + Assert.AreEqual(messageContent.Value, thirdReceivedMessageContent.Value); + Assert.AreEqual(m_ServerNetworkManager.LocalClientId, thirdReceivedMessageSender); } [UnityTest] public IEnumerator WhenSendingUnnamedMessageToAll_AllClientsReceiveIt() { - var messageContent = new ForceNetworkSerializeByMemcpy(Guid.NewGuid()); - var writer = new FastBufferWriter(1300, Allocator.Temp); - using (writer) - { - writer.WriteValueSafe(messageContent); - m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessageToAll(writer); - } - ulong firstReceivedMessageSender = 0; var firstReceivedMessageContent = new ForceNetworkSerializeByMemcpy(new Guid()); FirstClient.CustomMessagingManager.OnUnnamedMessage += @@ -118,13 +149,34 @@ public IEnumerator WhenSendingUnnamedMessageToAll_AllClientsReceiveIt() reader.ReadValueSafe(out secondReceivedMessageContent); }; - yield return new WaitForSeconds(0.2f); + ulong thirdReceivedMessageSender = 0; + var thirdReceivedMessageContent = new ForceNetworkSerializeByMemcpy(new Guid()); + m_ServerNetworkManager.CustomMessagingManager.OnUnnamedMessage += + (ulong sender, FastBufferReader reader) => + { + thirdReceivedMessageSender = sender; + + reader.ReadValueSafe(out thirdReceivedMessageContent); + }; + + var messageContent = new ForceNetworkSerializeByMemcpy(Guid.NewGuid()); + var writer = new FastBufferWriter(1300, Allocator.Temp); + using (writer) + { + writer.WriteValueSafe(messageContent); + m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessageToAll(writer); + } + + yield return WaitForMessageReceived(new List { FirstClient, SecondClient }); Assert.AreEqual(messageContent.Value, firstReceivedMessageContent.Value); Assert.AreEqual(m_ServerNetworkManager.LocalClientId, firstReceivedMessageSender); Assert.AreEqual(messageContent.Value, secondReceivedMessageContent.Value); Assert.AreEqual(m_ServerNetworkManager.LocalClientId, secondReceivedMessageSender); + + Assert.AreEqual(messageContent.Value, thirdReceivedMessageContent.Value); + Assert.AreEqual(m_ServerNetworkManager.LocalClientId, thirdReceivedMessageSender); } [Test] From 6d6388795178c895d86cf13a24768ae34e81e813 Mon Sep 17 00:00:00 2001 From: Frank Luong <100299641+fluong6@users.noreply.github.com> Date: Wed, 9 Nov 2022 17:03:41 -0500 Subject: [PATCH 124/138] chore: updating tools to point to release 1.1.0 (#2302) * updating tools to point to release 1.1.0 * Update manifest.json * Updating tools 1.1.0 --- testproject-tools-integration/Packages/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testproject-tools-integration/Packages/manifest.json b/testproject-tools-integration/Packages/manifest.json index 98dff627b8..d1845d58f5 100644 --- a/testproject-tools-integration/Packages/manifest.json +++ b/testproject-tools-integration/Packages/manifest.json @@ -2,7 +2,7 @@ "registry": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates", "dependencies": { "com.unity.ide.rider": "3.0.7", - "com.unity.multiplayer.tools": "https://github.com/Unity-Technologies/com.unity.multiplayer.tools.git#release/1.0.0", + "com.unity.multiplayer.tools": "https://github.com/Unity-Technologies/com.unity.multiplayer.tools.git#release/1.1.0", "com.unity.netcode.gameobjects": "file:../../com.unity.netcode.gameobjects", "com.unity.test-framework": "1.1.31", "com.unity.test-framework.performance": "2.8.0-preview", From 08031b88adb832428d516053be2bd9371782b72f Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Thu, 10 Nov 2022 11:19:07 -0500 Subject: [PATCH 125/138] perf: Jobify SendBatchedMessages in UnityTransport [MTT-4999] (#2304) * Make ErrorUtilities usable from Burst * Jobify SendBatchedMessages --- .../Runtime/Transports/UTP/UnityTransport.cs | 147 ++++++++++-------- 1 file changed, 83 insertions(+), 64 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index 84519388c5..720aa1bf0e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -10,8 +10,10 @@ using UnityEngine; using NetcodeNetworkEvent = Unity.Netcode.NetworkEvent; using TransportNetworkEvent = Unity.Networking.Transport.NetworkEvent; +using Unity.Burst; using Unity.Collections.LowLevel.Unsafe; using Unity.Collections; +using Unity.Jobs; using Unity.Networking.Transport; using Unity.Networking.Transport.Relay; using Unity.Networking.Transport.Utilities; @@ -51,50 +53,48 @@ void CreateDriver( /// public static class ErrorUtilities { - private const string k_NetworkSuccess = "Success"; - private const string k_NetworkIdMismatch = "NetworkId is invalid, likely caused by stale connection {0}."; - private const string k_NetworkVersionMismatch = "NetworkVersion is invalid, likely caused by stale connection {0}."; - private const string k_NetworkStateMismatch = "Sending data while connecting on connection {0} is not allowed."; - private const string k_NetworkPacketOverflow = "Unable to allocate packet due to buffer overflow."; - private const string k_NetworkSendQueueFull = "Currently unable to queue packet as there is too many in-flight packets. This could be because the send queue size ('Max Send Queue Size') is too small."; - private const string k_NetworkHeaderInvalid = "Invalid Unity Transport Protocol header."; - private const string k_NetworkDriverParallelForErr = "The parallel network driver needs to process a single unique connection per job, processing a single connection multiple times in a parallel for is not supported."; - private const string k_NetworkSendHandleInvalid = "Invalid NetworkInterface Send Handle. Likely caused by pipeline send data corruption."; - private const string k_NetworkArgumentMismatch = "Invalid NetworkEndpoint Arguments."; + private static readonly FixedString128Bytes k_NetworkSuccess = "Success"; + private static readonly FixedString128Bytes k_NetworkIdMismatch = "Invalid connection ID {0}."; + private static readonly FixedString128Bytes k_NetworkVersionMismatch = "Connection ID is invalid. Likely caused by sending on stale connection {0}."; + private static readonly FixedString128Bytes k_NetworkStateMismatch = "Connection state is invalid. Likely caused by sending on connection {0} which is stale or still connecting."; + private static readonly FixedString128Bytes k_NetworkPacketOverflow = "Packet is too large to be allocated by the transport."; + private static readonly FixedString128Bytes k_NetworkSendQueueFull = "Unable to queue packet in the transport. Likely caused by send queue size ('Max Send Queue Size') being too small."; /// - /// Convert error code to human readable error message. + /// Convert a UTP error code to human-readable error message. /// - /// Status code of the error - /// Subject connection ID of the error - /// Human readable error message. + /// UTP error code. + /// ID of the connection on which the error occurred. + /// Human-readable error message. public static string ErrorToString(Networking.Transport.Error.StatusCode error, ulong connectionId) { - switch (error) + return ErrorToString((int)error, connectionId); + } + + internal static string ErrorToString(int error, ulong connectionId) + { + return ErrorToFixedString(error, connectionId).ToString(); + } + + internal static FixedString128Bytes ErrorToFixedString(int error, ulong connectionId) + { + switch ((Networking.Transport.Error.StatusCode)error) { case Networking.Transport.Error.StatusCode.Success: return k_NetworkSuccess; case Networking.Transport.Error.StatusCode.NetworkIdMismatch: - return string.Format(k_NetworkIdMismatch, connectionId); + return FixedString.Format(k_NetworkIdMismatch, connectionId); case Networking.Transport.Error.StatusCode.NetworkVersionMismatch: - return string.Format(k_NetworkVersionMismatch, connectionId); + return FixedString.Format(k_NetworkVersionMismatch, connectionId); case Networking.Transport.Error.StatusCode.NetworkStateMismatch: - return string.Format(k_NetworkStateMismatch, connectionId); + return FixedString.Format(k_NetworkStateMismatch, connectionId); case Networking.Transport.Error.StatusCode.NetworkPacketOverflow: return k_NetworkPacketOverflow; case Networking.Transport.Error.StatusCode.NetworkSendQueueFull: return k_NetworkSendQueueFull; - case Networking.Transport.Error.StatusCode.NetworkHeaderInvalid: - return k_NetworkHeaderInvalid; - case Networking.Transport.Error.StatusCode.NetworkDriverParallelForErr: - return k_NetworkDriverParallelForErr; - case Networking.Transport.Error.StatusCode.NetworkSendHandleInvalid: - return k_NetworkSendHandleInvalid; - case Networking.Transport.Error.StatusCode.NetworkArgumentMismatch: - return k_NetworkArgumentMismatch; + default: + return FixedString.Format("Unknown error code {0}.", error); } - - return $"Unknown ErrorCode {Enum.GetName(typeof(Networking.Transport.Error.StatusCode), error)}"; } } @@ -676,55 +676,74 @@ private bool StartRelayServer() } } - // Send as many batched messages from the queue as possible. - private void SendBatchedMessages(SendTarget sendTarget, BatchedSendQueue queue) + [BurstCompile] + private struct SendBatchedMessagesJob : IJob { - var clientId = sendTarget.ClientId; - var connection = ParseClientId(clientId); - var pipeline = sendTarget.NetworkPipeline; + public NetworkDriver.Concurrent Driver; + public SendTarget Target; + public BatchedSendQueue Queue; + public NetworkPipeline ReliablePipeline; - while (!queue.IsEmpty) + public void Execute() { - var result = m_Driver.BeginSend(pipeline, connection, out var writer); - if (result != (int)Networking.Transport.Error.StatusCode.Success) + var clientId = Target.ClientId; + var connection = ParseClientId(clientId); + var pipeline = Target.NetworkPipeline; + + while (!Queue.IsEmpty) { - Debug.LogError("Error sending the message: " + - ErrorUtilities.ErrorToString((Networking.Transport.Error.StatusCode)result, clientId)); - return; - } + var result = Driver.BeginSend(pipeline, connection, out var writer); + if (result != (int)Networking.Transport.Error.StatusCode.Success) + { + Debug.LogError($"Error sending message: {ErrorUtilities.ErrorToFixedString(result, clientId)}"); + return; + } - // We don't attempt to send entire payloads over the reliable pipeline. Instead we - // fragment it manually. This is safe and easy to do since the reliable pipeline - // basically implements a stream, so as long as we separate the different messages - // in the stream (the send queue does that automatically) we are sure they'll be - // reassembled properly at the other end. This allows us to lift the limit of ~44KB - // on reliable payloads (because of the reliable window size). - var written = pipeline == m_ReliableSequencedPipeline ? queue.FillWriterWithBytes(ref writer) : queue.FillWriterWithMessages(ref writer); + // We don't attempt to send entire payloads over the reliable pipeline. Instead we + // fragment it manually. This is safe and easy to do since the reliable pipeline + // basically implements a stream, so as long as we separate the different messages + // in the stream (the send queue does that automatically) we are sure they'll be + // reassembled properly at the other end. This allows us to lift the limit of ~44KB + // on reliable payloads (because of the reliable window size). + var written = pipeline == ReliablePipeline ? Queue.FillWriterWithBytes(ref writer) : Queue.FillWriterWithMessages(ref writer); - result = m_Driver.EndSend(writer); - if (result == written) - { - // Batched message was sent successfully. Remove it from the queue. - queue.Consume(written); - } - else - { - // Some error occured. If it's just the UTP queue being full, then don't log - // anything since that's okay (the unsent message(s) are still in the queue - // and we'll retry sending the later). Otherwise log the error and remove the - // message from the queue (we don't want to resend it again since we'll likely - // just get the same error again). - if (result != (int)Networking.Transport.Error.StatusCode.NetworkSendQueueFull) + result = Driver.EndSend(writer); + if (result == written) { - Debug.LogError("Error sending the message: " + ErrorUtilities.ErrorToString((Networking.Transport.Error.StatusCode)result, clientId)); - queue.Consume(written); + // Batched message was sent successfully. Remove it from the queue. + Queue.Consume(written); } + else + { + // Some error occured. If it's just the UTP queue being full, then don't log + // anything since that's okay (the unsent message(s) are still in the queue + // and we'll retry sending them later). Otherwise log the error and remove the + // message from the queue (we don't want to resend it again since we'll likely + // just get the same error again). + if (result != (int)Networking.Transport.Error.StatusCode.NetworkSendQueueFull) + { + Debug.LogError($"Error sending the message: {ErrorUtilities.ErrorToFixedString(result, clientId)}"); + Queue.Consume(written); + } - return; + return; + } } } } + // Send as many batched messages from the queue as possible. + private void SendBatchedMessages(SendTarget sendTarget, BatchedSendQueue queue) + { + new SendBatchedMessagesJob + { + Driver = m_Driver.ToConcurrent(), + Target = sendTarget, + Queue = queue, + ReliablePipeline = m_ReliableSequencedPipeline + }.Run(); + } + private bool AcceptConnection() { var connection = m_Driver.Accept(); From 22f0e67e288dea57126993189cca7dbd8f7468a7 Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Thu, 10 Nov 2022 12:24:35 -0500 Subject: [PATCH 126/138] fix: not using NativeArrayExtensions.IndexOf as this API changed in 21.0-pre.2 (#2303) fix: not using NativeArrayExtensions.IndexOf as this API changed in 2.1.0-pre.2 Co-authored-by: ashwini <36935028+ashwinimurt@users.noreply.github.com> Co-authored-by: Fatih Mar --- .../Runtime/NetworkVariable/Collections/NetworkList.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index e893db75a5..aa2539053e 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -413,7 +413,7 @@ public void Clear() /// public bool Contains(T item) { - int index = NativeArrayExtensions.IndexOf(m_List, item); + int index = m_List.IndexOf(item); return index != -1; } @@ -426,7 +426,7 @@ public bool Remove(T item) throw new InvalidOperationException("Client is not allowed to write to this NetworkList"); } - int index = NativeArrayExtensions.IndexOf(m_List, item); + int index = m_List.IndexOf(item); if (index == -1) { return false; From ffee5f091404fbe3df64c1416c69a4d8e767a085 Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Mon, 14 Nov 2022 17:15:39 -0500 Subject: [PATCH 127/138] feat: Show public NetworkVariables in editor with a nice name (#2308) --- .../Editor/NetworkBehaviourEditor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs index 40b528835a..744805db7f 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs @@ -37,8 +37,8 @@ private void Init(MonoScript script) var ft = fields[i].FieldType; if (ft.IsGenericType && ft.GetGenericTypeDefinition() == typeof(NetworkVariable<>) && !fields[i].IsDefined(typeof(HideInInspector), true)) { - m_NetworkVariableNames.Add(fields[i].Name); - m_NetworkVariableFields.Add(fields[i].Name, fields[i]); + m_NetworkVariableNames.Add(ObjectNames.NicifyVariableName(fields[i].Name)); + m_NetworkVariableFields.Add(ObjectNames.NicifyVariableName(fields[i].Name), fields[i]); } } } From 6944f19fb85ab7e7aa1d9035e3c3d0805d02b093 Mon Sep 17 00:00:00 2001 From: Fatih Mar Date: Tue, 15 Nov 2022 19:22:09 +0000 Subject: [PATCH 128/138] refactor: replace 3rd-party xxhash impl with in-house reimpl (#2310) --- com.unity.netcode.gameobjects/CHANGELOG.md | 11 +- .../Runtime/Hashing/XXHash.cs | 248 ++++++++++++++ .../Hashing/{XXHash => }/XXHash.cs.meta | 2 +- .../Runtime/Hashing/XXHash.meta | 8 - .../Runtime/Hashing/XXHash/LICENSE | 21 -- .../Runtime/Hashing/XXHash/LICENSE.meta | 7 - .../Runtime/Hashing/XXHash/XXHash.cs | 318 ------------------ .../Tests/Editor/XXHashTests.cs | 31 ++ .../Tests/Editor/XXHashTests.cs.meta | 11 + .../Third Party Notices.md | 32 -- .../Third Party Notices.md.meta | 7 - 11 files changed, 300 insertions(+), 396 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Runtime/Hashing/XXHash.cs rename com.unity.netcode.gameobjects/Runtime/Hashing/{XXHash => }/XXHash.cs.meta (83%) delete mode 100644 com.unity.netcode.gameobjects/Runtime/Hashing/XXHash.meta delete mode 100644 com.unity.netcode.gameobjects/Runtime/Hashing/XXHash/LICENSE delete mode 100644 com.unity.netcode.gameobjects/Runtime/Hashing/XXHash/LICENSE.meta delete mode 100644 com.unity.netcode.gameobjects/Runtime/Hashing/XXHash/XXHash.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Editor/XXHashTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Editor/XXHashTests.cs.meta delete mode 100644 com.unity.netcode.gameobjects/Third Party Notices.md delete mode 100644 com.unity.netcode.gameobjects/Third Party Notices.md.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 72765d9252..4e244838fd 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -1,4 +1,3 @@ - # Changelog All notable changes to this project will be documented in this file. @@ -10,11 +9,13 @@ Additional documentation and release notes are available at [Multiplayer Documen ## [Unreleased] ### Added + - Added `NetworkObject` auto-add helper and Multiplayer Tools install reminder settings to Project Settings. (#2285) - Added `public string DisconnectReason` getter to `NetworkManager` and `string Reason` to `ConnectionApprovalResponse`. Allows connection approval to communicate back a reason. Also added `public void DisconnectClient(ulong clientId, string reason)` allowing setting a disconnection reason, when explicitly disconnecting a client. ### Changed +- Changed 3rd-party `XXHash` (32 & 64) implementation with an in-house reimplementation (#2310) - Optimized bandwidth usage by encoding most integer fields using variable-length encoding. (#2276) ### Fixed @@ -25,9 +26,10 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed issue where in-scene placed `NetworkObjects` were not honoring the `AutoObjectParentSync` property. (#2281) - Fixed the issue where `NetworkManager.OnClientConnectedCallback` was being invoked before in-scene placed `NetworkObject`s had been spawned when starting `NetworkManager` as a host. (#2277) - Creating a `FastBufferReader` with `Allocator.None` will not result in extra memory being allocated for the buffer (since it's owned externally in that scenario). (#2265) + ### Removed -- Removed the `NetworkObject` auto-add and Multiplayer Tools install reminder settings from the Menu interface. (#2285) +- Removed the `NetworkObject` auto-add and Multiplayer Tools install reminder settings from the Menu interface. (#2285) ## [1.1.0] - 2022-10-21 @@ -179,6 +181,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Removed `ClientNetworkTransform` from the package samples and moved to Boss Room's Utilities package which can be found [here](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/main/Packages/com.unity.multiplayer.samples.coop/Utilities/Net/ClientAuthority/ClientNetworkTransform.cs) (#1912) ### Fixed + - Fixed issue where `NetworkSceneManager` did not synchronize despawned in-scene placed NetworkObjects. (#1898) - Fixed `NetworkTransform` generating false positive rotation delta checks when rolling over between 0 and 360 degrees. (#1890) - Fixed client throwing an exception if it has messages in the outbound queue when processing the `NetworkEvent.Disconnect` event and is using UTP. (#1884) @@ -232,10 +235,12 @@ Additional documentation and release notes are available at [Multiplayer Documen ## [1.0.0-pre.6] - 2022-03-02 ### Added + - NetworkAnimator now properly synchrhonizes all animation layers as well as runtime-adjusted weighting between them (#1765) - Added first set of tests for NetworkAnimator - parameter syncing, trigger set / reset, override network animator (#1735) ### Fixed + - Fixed an issue where sometimes the first client to connect to the server could see messages from the server as coming from itself. (#1683) - Fixed an issue where clients seemed to be able to send messages to ClientId 1, but these messages would actually still go to the server (id 0) instead of that client. (#1683) - Improved clarity of error messaging when a client attempts to send a message to a destination other than the server, which isn't allowed. (#1683) @@ -287,6 +292,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Removed `FixedQueue`, `StreamExtensions`, `TypeExtensions` (#1398) ### Fixed + - Fixed in-scene NetworkObjects that are moved into the DDOL scene not getting restored to their original active state (enabled/disabled) after a full scene transition (#1354) - Fixed invalid IL code being generated when using `this` instead of `this ref` for the FastBufferReader/FastBufferWriter parameter of an extension method. (#1393) - Fixed an issue where if you are running as a server (not host) the LoadEventCompleted and UnloadEventCompleted events would fire early by the NetworkSceneManager (#1379) @@ -301,6 +307,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed network tick value sometimes being duplicated or skipped. (#1614) ### Changed + - The SDK no longer limits message size to 64k. (The transport may still impose its own limits, but the SDK no longer does.) (#1384) - Updated com.unity.collections to 1.1.0 (#1451) - NetworkManager's GameObject is no longer allowed to be nested under one or more GameObject(s).(#1484) diff --git a/com.unity.netcode.gameobjects/Runtime/Hashing/XXHash.cs b/com.unity.netcode.gameobjects/Runtime/Hashing/XXHash.cs new file mode 100644 index 0000000000..615736c3a1 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Hashing/XXHash.cs @@ -0,0 +1,248 @@ +using System; +using System.Text; +using System.Runtime.CompilerServices; + +namespace Unity.Netcode +{ + internal static class XXHash + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe uint Hash32(byte* input, int length, uint seed = 0) + { + unchecked + { + const uint prime1 = 2654435761u; + const uint prime2 = 2246822519u; + const uint prime3 = 3266489917u; + const uint prime4 = 0668265263u; + const uint prime5 = 0374761393u; + + uint hash = seed + prime5; + + if (length >= 16) + { + uint val0 = seed + prime1 + prime2; + uint val1 = seed + prime2; + uint val2 = seed + 0; + uint val3 = seed - prime1; + + int count = length >> 4; + for (int i = 0; i < count; i++) + { + var pos0 = *(uint*)(input + 0); + var pos1 = *(uint*)(input + 4); + var pos2 = *(uint*)(input + 8); + var pos3 = *(uint*)(input + 12); + + val0 += pos0 * prime2; + val0 = (val0 << 13) | (val0 >> (32 - 13)); + val0 *= prime1; + + val1 += pos1 * prime2; + val1 = (val1 << 13) | (val1 >> (32 - 13)); + val1 *= prime1; + + val2 += pos2 * prime2; + val2 = (val2 << 13) | (val2 >> (32 - 13)); + val2 *= prime1; + + val3 += pos3 * prime2; + val3 = (val3 << 13) | (val3 >> (32 - 13)); + val3 *= prime1; + + input += 16; + } + + hash = ((val0 << 01) | (val0 >> (32 - 01))) + + ((val1 << 07) | (val1 >> (32 - 07))) + + ((val2 << 12) | (val2 >> (32 - 12))) + + ((val3 << 18) | (val3 >> (32 - 18))); + } + + hash += (uint)length; + + length &= 15; + while (length >= 4) + { + hash += *(uint*)input * prime3; + hash = ((hash << 17) | (hash >> (32 - 17))) * prime4; + input += 4; + length -= 4; + } + while (length > 0) + { + hash += *input * prime5; + hash = ((hash << 11) | (hash >> (32 - 11))) * prime1; + ++input; + --length; + } + + hash ^= hash >> 15; + hash *= prime2; + hash ^= hash >> 13; + hash *= prime3; + hash ^= hash >> 16; + + return hash; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ulong Hash64(byte* input, int length, uint seed = 0) + { + unchecked + { + const ulong prime1 = 11400714785074694791ul; + const ulong prime2 = 14029467366897019727ul; + const ulong prime3 = 01609587929392839161ul; + const ulong prime4 = 09650029242287828579ul; + const ulong prime5 = 02870177450012600261ul; + + ulong hash = seed + prime5; + + if (length >= 32) + { + ulong val0 = seed + prime1 + prime2; + ulong val1 = seed + prime2; + ulong val2 = seed + 0; + ulong val3 = seed - prime1; + + int count = length >> 5; + for (int i = 0; i < count; i++) + { + var pos0 = *(ulong*)(input + 0); + var pos1 = *(ulong*)(input + 8); + var pos2 = *(ulong*)(input + 16); + var pos3 = *(ulong*)(input + 24); + + val0 += pos0 * prime2; + val0 = (val0 << 31) | (val0 >> (64 - 31)); + val0 *= prime1; + + val1 += pos1 * prime2; + val1 = (val1 << 31) | (val1 >> (64 - 31)); + val1 *= prime1; + + val2 += pos2 * prime2; + val2 = (val2 << 31) | (val2 >> (64 - 31)); + val2 *= prime1; + + val3 += pos3 * prime2; + val3 = (val3 << 31) | (val3 >> (64 - 31)); + val3 *= prime1; + + input += 32; + } + + hash = ((val0 << 01) | (val0 >> (64 - 01))) + + ((val1 << 07) | (val1 >> (64 - 07))) + + ((val2 << 12) | (val2 >> (64 - 12))) + + ((val3 << 18) | (val3 >> (64 - 18))); + + val0 *= prime2; + val0 = (val0 << 31) | (val0 >> (64 - 31)); + val0 *= prime1; + hash ^= val0; + hash = hash * prime1 + prime4; + + val1 *= prime2; + val1 = (val1 << 31) | (val1 >> (64 - 31)); + val1 *= prime1; + hash ^= val1; + hash = hash * prime1 + prime4; + + val2 *= prime2; + val2 = (val2 << 31) | (val2 >> (64 - 31)); + val2 *= prime1; + hash ^= val2; + hash = hash * prime1 + prime4; + + val3 *= prime2; + val3 = (val3 << 31) | (val3 >> (64 - 31)); + val3 *= prime1; + hash ^= val3; + hash = hash * prime1 + prime4; + } + + hash += (ulong)length; + + length &= 31; + while (length >= 8) + { + ulong lane = *(ulong*)input * prime2; + lane = ((lane << 31) | (lane >> (64 - 31))) * prime1; + hash ^= lane; + hash = ((hash << 27) | (hash >> (64 - 27))) * prime1 + prime4; + input += 8; + length -= 8; + } + if (length >= 4) + { + hash ^= *(uint*)input * prime1; + hash = ((hash << 23) | (hash >> (64 - 23))) * prime2 + prime3; + input += 4; + length -= 4; + } + while (length > 0) + { + hash ^= *input * prime5; + hash = ((hash << 11) | (hash >> (64 - 11))) * prime1; + ++input; + --length; + } + + hash ^= hash >> 33; + hash *= prime2; + hash ^= hash >> 29; + hash *= prime3; + hash ^= hash >> 32; + + return hash; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Hash32(this byte[] buffer) + { + int length = buffer.Length; + unsafe + { + fixed (byte* pointer = buffer) + { + return Hash32(pointer, length); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Hash32(this string text) => Hash32(Encoding.UTF8.GetBytes(text)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Hash32(this Type type) => Hash32(type.FullName); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Hash32() => Hash32(typeof(T)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Hash64(this byte[] buffer) + { + int length = buffer.Length; + unsafe + { + fixed (byte* pointer = buffer) + { + return Hash64(pointer, length); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Hash64(this string text) => Hash64(Encoding.UTF8.GetBytes(text)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Hash64(this Type type) => Hash64(type.FullName); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Hash64() => Hash64(typeof(T)); + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Hashing/XXHash/XXHash.cs.meta b/com.unity.netcode.gameobjects/Runtime/Hashing/XXHash.cs.meta similarity index 83% rename from com.unity.netcode.gameobjects/Runtime/Hashing/XXHash/XXHash.cs.meta rename to com.unity.netcode.gameobjects/Runtime/Hashing/XXHash.cs.meta index 5c090bbab7..97156256bf 100644 --- a/com.unity.netcode.gameobjects/Runtime/Hashing/XXHash/XXHash.cs.meta +++ b/com.unity.netcode.gameobjects/Runtime/Hashing/XXHash.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: b5aa7a49e9e694f148d810d34577546b +guid: c3077af091aa443acbdea9d3e97727b0 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/com.unity.netcode.gameobjects/Runtime/Hashing/XXHash.meta b/com.unity.netcode.gameobjects/Runtime/Hashing/XXHash.meta deleted file mode 100644 index f1fa98a705..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Hashing/XXHash.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 2c61e8fe9a68a486fbbc3128d233ded2 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Hashing/XXHash/LICENSE b/com.unity.netcode.gameobjects/Runtime/Hashing/XXHash/LICENSE deleted file mode 100644 index 6b55f78fe5..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Hashing/XXHash/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2015, 2016 Sedat Kapanoglu - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/com.unity.netcode.gameobjects/Runtime/Hashing/XXHash/LICENSE.meta b/com.unity.netcode.gameobjects/Runtime/Hashing/XXHash/LICENSE.meta deleted file mode 100644 index c6b28aa115..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Hashing/XXHash/LICENSE.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: cf89ecbf6f9954c8ea6d0848b1e79d87 -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Hashing/XXHash/XXHash.cs b/com.unity.netcode.gameobjects/Runtime/Hashing/XXHash/XXHash.cs deleted file mode 100644 index cfcb9d642d..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Hashing/XXHash/XXHash.cs +++ /dev/null @@ -1,318 +0,0 @@ -// -// Copyright (c) 2015-2019 Sedat Kapanoglu -// MIT License (see LICENSE file for details) -// - -// @mfatihmar (Unity): Modified for Unity support - -using System.Text; -using System.Runtime.CompilerServices; - -namespace Unity.Netcode -{ - /// - /// XXHash implementation. - /// - internal static class XXHash - { - private const ulong k_Prime64v1 = 11400714785074694791ul; - private const ulong k_Prime64v2 = 14029467366897019727ul; - private const ulong k_Prime64v3 = 1609587929392839161ul; - private const ulong k_Prime64v4 = 9650029242287828579ul; - private const ulong k_Prime64v5 = 2870177450012600261ul; - - private const uint k_Prime32v1 = 2654435761u; - private const uint k_Prime32v2 = 2246822519u; - private const uint k_Prime32v3 = 3266489917u; - private const uint k_Prime32v4 = 668265263u; - private const uint k_Prime32v5 = 374761393u; - - public static uint Hash32(string text) => Hash32(text, Encoding.UTF8); - public static uint Hash32(string text, Encoding encoding) => Hash32(encoding.GetBytes(text)); - public static uint Hash32(byte[] buffer) - { - unsafe - { - fixed (byte* ptr = buffer) - { - return Hash32(ptr, buffer.Length); - } - } - } - - /// - /// Generate a 32-bit xxHash value. - /// - /// Input buffer. - /// Input buffer length. - /// Optional seed. - /// 32-bit hash value. - public static unsafe uint Hash32(byte* buffer, int bufferLength, uint seed = 0) - { - const int stripeLength = 16; - - int len = bufferLength; - int remainingLen = len; - uint acc; - - byte* pInput = buffer; - if (len >= stripeLength) - { - uint acc1 = seed + k_Prime32v1 + k_Prime32v2; - uint acc2 = seed + k_Prime32v2; - uint acc3 = seed; - uint acc4 = seed - k_Prime32v1; - - do - { - acc = processStripe32(ref pInput, ref acc1, ref acc2, ref acc3, ref acc4); - remainingLen -= stripeLength; - } while (remainingLen >= stripeLength); - } - else - { - acc = seed + k_Prime32v5; - } - - acc += (uint)len; - acc = processRemaining32(pInput, acc, remainingLen); - - return avalanche32(acc); - } - - public static ulong Hash64(string text) => Hash64(text, Encoding.UTF8); - public static ulong Hash64(string text, Encoding encoding) => Hash64(encoding.GetBytes(text)); - public static ulong Hash64(byte[] buffer) - { - unsafe - { - fixed (byte* ptr = buffer) - { - return Hash64(ptr, buffer.Length); - } - } - } - - /// - /// Generate a 64-bit xxHash value. - /// - /// Input buffer. - /// Input buffer length. - /// Optional seed. - /// Computed 64-bit hash value. - public static unsafe ulong Hash64(byte* buffer, int bufferLength, ulong seed = 0) - { - const int stripeLength = 32; - - int len = bufferLength; - int remainingLen = len; - ulong acc; - - byte* pInput = buffer; - if (len >= stripeLength) - { - ulong acc1 = seed + k_Prime64v1 + k_Prime64v2; - ulong acc2 = seed + k_Prime64v2; - ulong acc3 = seed; - ulong acc4 = seed - k_Prime64v1; - - do - { - acc = processStripe64(ref pInput, ref acc1, ref acc2, ref acc3, ref acc4); - remainingLen -= stripeLength; - } while (remainingLen >= stripeLength); - } - else - { - acc = seed + k_Prime64v5; - } - - acc += (ulong)len; - acc = processRemaining64(pInput, acc, remainingLen); - - - return avalanche64(acc); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe ulong processStripe64( - ref byte* pInput, - ref ulong acc1, - ref ulong acc2, - ref ulong acc3, - ref ulong acc4) - { - processLane64(ref acc1, ref pInput); - processLane64(ref acc2, ref pInput); - processLane64(ref acc3, ref pInput); - processLane64(ref acc4, ref pInput); - - ulong acc = Bits.RotateLeft(acc1, 1) - + Bits.RotateLeft(acc2, 7) - + Bits.RotateLeft(acc3, 12) - + Bits.RotateLeft(acc4, 18); - - mergeAccumulator64(ref acc, acc1); - mergeAccumulator64(ref acc, acc2); - mergeAccumulator64(ref acc, acc3); - mergeAccumulator64(ref acc, acc4); - return acc; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe void processLane64(ref ulong accn, ref byte* pInput) - { - ulong lane = *(ulong*)pInput; - accn = round64(accn, lane); - pInput += 8; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe ulong processRemaining64( - byte* pInput, - ulong acc, - int remainingLen) - { - for (ulong lane; remainingLen >= 8; remainingLen -= 8, pInput += 8) - { - lane = *(ulong*)pInput; - - acc ^= round64(0, lane); - acc = Bits.RotateLeft(acc, 27) * k_Prime64v1; - acc += k_Prime64v4; - } - - for (uint lane32; remainingLen >= 4; remainingLen -= 4, pInput += 4) - { - lane32 = *(uint*)pInput; - - acc ^= lane32 * k_Prime64v1; - acc = Bits.RotateLeft(acc, 23) * k_Prime64v2; - acc += k_Prime64v3; - } - - for (byte lane8; remainingLen >= 1; remainingLen--, pInput++) - { - lane8 = *pInput; - acc ^= lane8 * k_Prime64v5; - acc = Bits.RotateLeft(acc, 11) * k_Prime64v1; - } - - return acc; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong avalanche64(ulong acc) - { - acc ^= acc >> 33; - acc *= k_Prime64v2; - acc ^= acc >> 29; - acc *= k_Prime64v3; - acc ^= acc >> 32; - return acc; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong round64(ulong accn, ulong lane) - { - accn += lane * k_Prime64v2; - return Bits.RotateLeft(accn, 31) * k_Prime64v1; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void mergeAccumulator64(ref ulong acc, ulong accn) - { - acc ^= round64(0, accn); - acc *= k_Prime64v1; - acc += k_Prime64v4; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe uint processStripe32( - ref byte* pInput, - ref uint acc1, - ref uint acc2, - ref uint acc3, - ref uint acc4) - { - processLane32(ref pInput, ref acc1); - processLane32(ref pInput, ref acc2); - processLane32(ref pInput, ref acc3); - processLane32(ref pInput, ref acc4); - - return Bits.RotateLeft(acc1, 1) - + Bits.RotateLeft(acc2, 7) - + Bits.RotateLeft(acc3, 12) - + Bits.RotateLeft(acc4, 18); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe void processLane32(ref byte* pInput, ref uint accn) - { - uint lane = *(uint*)pInput; - accn = round32(accn, lane); - pInput += 4; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe uint processRemaining32( - byte* pInput, - uint acc, - int remainingLen) - { - for (uint lane; remainingLen >= 4; remainingLen -= 4, pInput += 4) - { - lane = *(uint*)pInput; - acc += lane * k_Prime32v3; - acc = Bits.RotateLeft(acc, 17) * k_Prime32v4; - } - - for (byte lane; remainingLen >= 1; remainingLen--, pInput++) - { - lane = *pInput; - acc += lane * k_Prime32v5; - acc = Bits.RotateLeft(acc, 11) * k_Prime32v1; - } - - return acc; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint round32(uint accn, uint lane) - { - accn += lane * k_Prime32v2; - accn = Bits.RotateLeft(accn, 13); - accn *= k_Prime32v1; - return accn; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint avalanche32(uint acc) - { - acc ^= acc >> 15; - acc *= k_Prime32v2; - acc ^= acc >> 13; - acc *= k_Prime32v3; - acc ^= acc >> 16; - return acc; - } - - /// - /// Bit operations. - /// - private static class Bits - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static ulong RotateLeft(ulong value, int bits) - { - return (value << bits) | (value >> (64 - bits)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static uint RotateLeft(uint value, int bits) - { - return (value << bits) | (value >> (32 - bits)); - } - } - } -} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/XXHashTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/XXHashTests.cs new file mode 100644 index 0000000000..342a3739d0 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/XXHashTests.cs @@ -0,0 +1,31 @@ +using NUnit.Framework; + +namespace Unity.Netcode.EditorTests +{ + public class XXHashTests + { + [Test] + public void TestXXHash32Short() + { + Assert.That("TestStuff".Hash32(), Is.EqualTo(0x64e10c4c)); + } + + [Test] + public void TestXXHash32Long() + { + Assert.That("TestingHashingWithLongStringValues".Hash32(), Is.EqualTo(0xba3d1783)); + } + + [Test] + public void TestXXHas64Short() + { + Assert.That("TestStuff".Hash64(), Is.EqualTo(0x4c3be8d82d14a5a9)); + } + + [Test] + public void TestXXHash64Long() + { + Assert.That("TestingHashingWithLongStringValues".Hash64(), Is.EqualTo(0x5b374f98b10bf246)); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/XXHashTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/XXHashTests.cs.meta new file mode 100644 index 0000000000..a7886b3f44 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/XXHashTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ce9bdc7200b66410286810307554534b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Third Party Notices.md b/com.unity.netcode.gameobjects/Third Party Notices.md deleted file mode 100644 index 5d05eaa379..0000000000 --- a/com.unity.netcode.gameobjects/Third Party Notices.md +++ /dev/null @@ -1,32 +0,0 @@ -This package contains third-party software components governed by the license(s) indicated below: ---------- - -## Package: Editor/CodeGen/XXHash - ---------- - -Component Name: xxHash - -License Type: MIT - -Copyright (c) 2015, 2016 Sedat Kapanoglu - -http://www.xxhash.com/ - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/com.unity.netcode.gameobjects/Third Party Notices.md.meta b/com.unity.netcode.gameobjects/Third Party Notices.md.meta deleted file mode 100644 index 68346b1c42..0000000000 --- a/com.unity.netcode.gameobjects/Third Party Notices.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 43e3f545298d44899b42e94aa7d17f07 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: From 5ee385092767f29f5a91fae130dea0a3c9efbf1e Mon Sep 17 00:00:00 2001 From: Kitty Draper <284434+ShadauxCat@users.noreply.github.com> Date: Tue, 15 Nov 2022 18:17:10 -0600 Subject: [PATCH 129/138] feat: message versioning [MTT-3048] (#2290) Adds support for SDKs at different versions with different message formats to be able to talk to each other - the side with the higher version being responsible for both converting up when receiving and converting down when sending. --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 +- .../Editor/CodeGen/INetworkMessageILPP.cs | 35 +- .../Runtime/Core/NetworkBehaviour.cs | 2 +- .../Runtime/Core/NetworkManager.cs | 34 +- .../Messaging/DisconnectReasonMessage.cs | 18 +- .../Runtime/Messaging/INetworkMessage.cs | 5 +- .../Messages/ChangeOwnershipMessage.cs | 6 +- .../Messages/ConnectionApprovedMessage.cs | 52 +- .../Messages/ConnectionRequestMessage.cs | 48 +- .../Messaging/Messages/CreateObjectMessage.cs | 6 +- .../Messages/DestroyObjectMessage.cs | 6 +- .../Messaging/Messages/MessageMetadata.cs | 23 + .../Messages/MessageMetadata.cs.meta | 3 + .../Messaging/Messages/NamedMessage.cs | 6 +- .../Messages/NetworkVariableDeltaMessage.cs | 6 +- .../Messaging/Messages/OrderingMessage.cs | 50 -- .../Messages/OrderingMessage.cs.meta | 11 - .../Messaging/Messages/ParentSyncMessage.cs | 6 +- .../Runtime/Messaging/Messages/RpcMessages.cs | 12 +- .../Messaging/Messages/SceneEventMessage.cs | 6 +- .../Messaging/Messages/ServerLogMessage.cs | 6 +- .../Messaging/Messages/TimeSyncMessage.cs | 6 +- .../Messaging/Messages/UnnamedMessage.cs | 6 +- .../Runtime/Messaging/MessagingSystem.cs | 240 ++++++--- .../Tests/Editor/DisconnectMessageTests.cs | 12 +- .../Editor/Messaging/MessageReceivingTests.cs | 10 +- .../Messaging/MessageRegistrationTests.cs | 107 ++-- .../Editor/Messaging/MessageSendingTests.cs | 13 +- .../Messaging/MessageVersioningTests.cs | 503 ++++++++++++++++++ .../Messaging/MessageVersioningTests.cs.meta | 3 + .../Metrics/OwnershipChangeMetricsTests.cs | 2 +- .../Runtime/Metrics/ServerLogsMetricTests.cs | 2 +- 32 files changed, 990 insertions(+), 257 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/Messages/MessageMetadata.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/Messages/MessageMetadata.cs.meta delete mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/Messages/OrderingMessage.cs delete mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/Messages/OrderingMessage.cs.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageVersioningTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageVersioningTests.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 4e244838fd..dcfa2464e2 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -9,7 +9,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ## [Unreleased] ### Added - +- Added support for different versions of the SDK to talk to each other in circumstances where changes permit it. Starting with this version and into future versions, patch versions should be compatible as long as the minor version is the same. (#2290) - Added `NetworkObject` auto-add helper and Multiplayer Tools install reminder settings to Project Settings. (#2285) - Added `public string DisconnectReason` getter to `NetworkManager` and `string Reason` to `ConnectionApprovalResponse`. Allows connection approval to communicate back a reason. Also added `public void DisconnectClient(ulong clientId, string reason)` allowing setting a disconnection reason, when explicitly disconnecting a client. diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs index 30b6f0b05e..9a1e8ac297 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/INetworkMessageILPP.cs @@ -102,15 +102,19 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) private PostProcessorAssemblyResolver m_AssemblyResolver; private MethodReference m_MessagingSystem_ReceiveMessage_MethodRef; + private MethodReference m_MessagingSystem_CreateMessageAndGetVersion_MethodRef; private TypeReference m_MessagingSystem_MessageWithHandler_TypeRef; private MethodReference m_MessagingSystem_MessageHandler_Constructor_TypeRef; + private MethodReference m_MessagingSystem_VersionGetter_Constructor_TypeRef; private FieldReference m_ILPPMessageProvider___network_message_types_FieldRef; private FieldReference m_MessagingSystem_MessageWithHandler_MessageType_FieldRef; private FieldReference m_MessagingSystem_MessageWithHandler_Handler_FieldRef; + private FieldReference m_MessagingSystem_MessageWithHandler_GetVersion_FieldRef; private MethodReference m_Type_GetTypeFromHandle_MethodRef; private MethodReference m_List_Add_MethodRef; private const string k_ReceiveMessageName = nameof(MessagingSystem.ReceiveMessage); + private const string k_CreateMessageAndGetVersionName = nameof(MessagingSystem.CreateMessageAndGetVersion); private bool ImportReferences(ModuleDefinition moduleDefinition) { @@ -126,6 +130,7 @@ private bool ImportReferences(ModuleDefinition moduleDefinition) TypeDefinition listTypeDef = moduleDefinition.ImportReference(typeof(List<>)).Resolve(); TypeDefinition messageHandlerTypeDef = null; + TypeDefinition versionGetterTypeDef = null; TypeDefinition messageWithHandlerTypeDef = null; TypeDefinition ilppMessageProviderTypeDef = null; TypeDefinition messagingSystemTypeDef = null; @@ -137,6 +142,12 @@ private bool ImportReferences(ModuleDefinition moduleDefinition) continue; } + if (versionGetterTypeDef == null && netcodeTypeDef.Name == nameof(MessagingSystem.VersionGetter)) + { + versionGetterTypeDef = netcodeTypeDef; + continue; + } + if (messageWithHandlerTypeDef == null && netcodeTypeDef.Name == nameof(MessagingSystem.MessageWithHandler)) { messageWithHandlerTypeDef = netcodeTypeDef; @@ -157,6 +168,7 @@ private bool ImportReferences(ModuleDefinition moduleDefinition) } m_MessagingSystem_MessageHandler_Constructor_TypeRef = moduleDefinition.ImportReference(messageHandlerTypeDef.GetConstructors().First()); + m_MessagingSystem_VersionGetter_Constructor_TypeRef = moduleDefinition.ImportReference(versionGetterTypeDef.GetConstructors().First()); m_MessagingSystem_MessageWithHandler_TypeRef = moduleDefinition.ImportReference(messageWithHandlerTypeDef); foreach (var fieldDef in messageWithHandlerTypeDef.Fields) @@ -169,6 +181,9 @@ private bool ImportReferences(ModuleDefinition moduleDefinition) case nameof(MessagingSystem.MessageWithHandler.Handler): m_MessagingSystem_MessageWithHandler_Handler_FieldRef = moduleDefinition.ImportReference(fieldDef); break; + case nameof(MessagingSystem.MessageWithHandler.GetVersion): + m_MessagingSystem_MessageWithHandler_GetVersion_FieldRef = moduleDefinition.ImportReference(fieldDef); + break; } } @@ -211,6 +226,9 @@ private bool ImportReferences(ModuleDefinition moduleDefinition) case k_ReceiveMessageName: m_MessagingSystem_ReceiveMessage_MethodRef = moduleDefinition.ImportReference(methodDef); break; + case k_CreateMessageAndGetVersionName: + m_MessagingSystem_CreateMessageAndGetVersion_MethodRef = moduleDefinition.ImportReference(methodDef); + break; } } @@ -236,7 +254,7 @@ private MethodDefinition GetOrCreateStaticConstructor(TypeDefinition typeDefinit return staticCtorMethodDef; } - private void CreateInstructionsToRegisterType(ILProcessor processor, List instructions, TypeReference type, MethodReference receiveMethod) + private void CreateInstructionsToRegisterType(ILProcessor processor, List instructions, TypeReference type, MethodReference receiveMethod, MethodReference versionMethod) { // MessagingSystem.__network_message_types.Add(new MessagingSystem.MessageWithHandler{MessageType=typeof(type), Handler=type.Receive}); processor.Body.Variables.Add(new VariableDefinition(m_MessagingSystem_MessageWithHandler_TypeRef)); @@ -252,7 +270,7 @@ private void CreateInstructionsToRegisterType(ILProcessor processor, List instructions.Add(processor.Create(OpCodes.Ldloca, messageWithHandlerLocIdx)); instructions.Add(processor.Create(OpCodes.Ldnull)); @@ -260,6 +278,15 @@ private void CreateInstructionsToRegisterType(ILProcessor processor, List + instructions.Add(processor.Create(OpCodes.Ldloca, messageWithHandlerLocIdx)); + instructions.Add(processor.Create(OpCodes.Ldnull)); + + instructions.Add(processor.Create(OpCodes.Ldftn, versionMethod)); + instructions.Add(processor.Create(OpCodes.Newobj, m_MessagingSystem_VersionGetter_Constructor_TypeRef)); + instructions.Add(processor.Create(OpCodes.Stfld, m_MessagingSystem_MessageWithHandler_GetVersion_FieldRef)); + // ILPPMessageProvider.__network_message_types.Add(tmp); instructions.Add(processor.Create(OpCodes.Ldloc, messageWithHandlerLocIdx)); instructions.Add(processor.Create(OpCodes.Callvirt, m_List_Add_MethodRef)); @@ -285,7 +312,9 @@ private void CreateModuleInitializer(AssemblyDefinition assembly, List processor.Body.Instructions.Insert(processor.Body.Instructions.Count - 1, instruction)); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 2aff48d93e..daa8b30ad0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -730,7 +730,7 @@ private void NetworkVariableUpdate(ulong targetClientId, int behaviourIndex) var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, MessagingSystem.FRAGMENTED_MESSAGE_MAX_SIZE); using (tmpWriter) { - message.Serialize(tmpWriter); + message.Serialize(tmpWriter, message.Version); } } else diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index c355d95096..412fa2546d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1447,7 +1447,7 @@ internal void ShutdownInternal() } } - if (IsClient && IsConnectedClient) + if (IsClient && IsListening) { // Client only, send disconnect to server NetworkConfig.NetworkTransport.DisconnectLocalClient(); @@ -1605,6 +1605,7 @@ private void OnNetworkEarlyUpdate() } while (IsListening && networkEvent != NetworkEvent.Nothing); MessagingSystem.ProcessIncomingMessageQueue(); + MessagingSystem.CleanupDisconnectedClients(); #if DEVELOPMENT_BUILD || UNITY_EDITOR s_TransportPoll.End(); @@ -1686,7 +1687,23 @@ private void SendConnectionRequest() ShouldSendConnectionData = NetworkConfig.ConnectionApproval, ConnectionData = NetworkConfig.ConnectionData }; + + message.MessageVersions = new NativeArray(MessagingSystem.MessageHandlers.Length, Allocator.Temp); + for (int index = 0; index < MessagingSystem.MessageHandlers.Length; index++) + { + if (MessagingSystem.MessageTypes[index] != null) + { + var type = MessagingSystem.MessageTypes[index]; + message.MessageVersions[index] = new MessageVersionData + { + Hash = XXHash.Hash32(type.FullName), + Version = MessagingSystem.GetLocalVersion(type) + }; + } + } + SendMessage(ref message, NetworkDelivery.ReliableSequenced, ServerClientId); + message.MessageVersions.Dispose(); } private IEnumerator ApprovalTimeout(ulong clientId) @@ -2237,22 +2254,23 @@ internal void HandleConnectionApproval(ulong ownerClientId, ConnectionApprovalRe } } - SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId); - + message.MessageVersions = new NativeArray(MessagingSystem.MessageHandlers.Length, Allocator.Temp); for (int index = 0; index < MessagingSystem.MessageHandlers.Length; index++) { if (MessagingSystem.MessageTypes[index] != null) { - var orderingMessage = new OrderingMessage + var type = MessagingSystem.MessageTypes[index]; + message.MessageVersions[index] = new MessageVersionData { - Order = index, - Hash = XXHash.Hash32(MessagingSystem.MessageTypes[index].FullName) + Hash = XXHash.Hash32(type.FullName), + Version = MessagingSystem.GetLocalVersion(type) }; - - SendMessage(ref orderingMessage, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId); } } + SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId); + message.MessageVersions.Dispose(); + // If scene management is enabled, then let NetworkSceneManager handle the initial scene and NetworkObject synchronization if (!NetworkConfig.EnableSceneManagement) { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/DisconnectReasonMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/DisconnectReasonMessage.cs index e3167a30fa..eb5d39a37f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/DisconnectReasonMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/DisconnectReasonMessage.cs @@ -4,7 +4,9 @@ internal struct DisconnectReasonMessage : INetworkMessage { public string Reason; - public void Serialize(FastBufferWriter writer) + public int Version => 0; + + public void Serialize(FastBufferWriter writer, int targetVersion) { string reasonSent = Reason; if (reasonSent == null) @@ -12,9 +14,16 @@ public void Serialize(FastBufferWriter writer) reasonSent = string.Empty; } + // Since we don't send a ConnectionApprovedMessage, the version for this message is encded with the message + // itself. However, note that we HAVE received a ConnectionRequestMessage, so we DO have a valid targetVersion + // on this side of things - we just have to make sure the receiving side knows what version we sent it, + // since whoever has the higher version number is responsible for versioning and they may be the one + // with the higher version number. + BytePacker.WriteValueBitPacked(writer, Version); + if (writer.TryBeginWrite(FastBufferWriter.GetWriteSize(reasonSent))) { - writer.WriteValueSafe(reasonSent); + writer.WriteValue(reasonSent); } else { @@ -24,8 +33,11 @@ public void Serialize(FastBufferWriter writer) } } - public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { + // Since we don't get a ConnectionApprovedMessage, the version for this message is encded with the message + // itself. This will override what we got from MessagingSystem... which will always be 0 here. + ByteUnpacker.ReadValueBitPacked(reader, out receivedMessageVersion); reader.ReadValueSafe(out Reason); return true; } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/INetworkMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/INetworkMessage.cs index 1249081d0b..c05587a07e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/INetworkMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/INetworkMessage.cs @@ -40,8 +40,9 @@ namespace Unity.Netcode /// internal interface INetworkMessage { - void Serialize(FastBufferWriter writer); - bool Deserialize(FastBufferReader reader, ref NetworkContext context); + void Serialize(FastBufferWriter writer, int targetVersion); + bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion); void Handle(ref NetworkContext context); + int Version { get; } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs index 6ab471904c..417347b5a5 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs @@ -2,16 +2,18 @@ namespace Unity.Netcode { internal struct ChangeOwnershipMessage : INetworkMessage, INetworkSerializeByMemcpy { + public int Version => 0; + public ulong NetworkObjectId; public ulong OwnerClientId; - public void Serialize(FastBufferWriter writer) + public void Serialize(FastBufferWriter writer, int targetVersion) { BytePacker.WriteValueBitPacked(writer, NetworkObjectId); BytePacker.WriteValueBitPacked(writer, OwnerClientId); } - public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { var networkManager = (NetworkManager)context.SystemOwner; if (!networkManager.IsClient) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index e721334ffa..e0b3769128 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -1,10 +1,12 @@ -using System; using System.Collections.Generic; +using Unity.Collections; namespace Unity.Netcode { internal struct ConnectionApprovedMessage : INetworkMessage { + public int Version => 0; + public ulong OwnerClientId; public int NetworkTick; @@ -13,12 +15,24 @@ internal struct ConnectionApprovedMessage : INetworkMessage private FastBufferReader m_ReceivedSceneObjectData; - public void Serialize(FastBufferWriter writer) + public NativeArray MessageVersions; + + public void Serialize(FastBufferWriter writer, int targetVersion) { - if (!writer.TryBeginWrite(sizeof(ulong) + sizeof(int) + sizeof(int))) + // ============================================================ + // BEGIN FORBIDDEN SEGMENT + // DO NOT CHANGE THIS HEADER. Everything added to this message + // must go AFTER the message version header. + // ============================================================ + BytePacker.WriteValueBitPacked(writer, MessageVersions.Length); + foreach (var messageVersion in MessageVersions) { - throw new OverflowException($"Not enough space in the write buffer to serialize {nameof(ConnectionApprovedMessage)}"); + messageVersion.Serialize(writer); } + // ============================================================ + // END FORBIDDEN SEGMENT + // ============================================================ + BytePacker.WriteValueBitPacked(writer, OwnerClientId); BytePacker.WriteValueBitPacked(writer, NetworkTick); @@ -51,7 +65,7 @@ public void Serialize(FastBufferWriter writer) } } - public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { var networkManager = (NetworkManager)context.SystemOwner; if (!networkManager.IsClient) @@ -59,6 +73,34 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context) return false; } + // ============================================================ + // BEGIN FORBIDDEN SEGMENT + // DO NOT CHANGE THIS HEADER. Everything added to this message + // must go AFTER the message version header. + // ============================================================ + ByteUnpacker.ReadValueBitPacked(reader, out int length); + var messageHashesInOrder = new NativeArray(length, Allocator.Temp); + for (var i = 0; i < length; ++i) + { + var messageVersion = new MessageVersionData(); + messageVersion.Deserialize(reader); + networkManager.MessagingSystem.SetVersion(context.SenderId, messageVersion.Hash, messageVersion.Version); + messageHashesInOrder[i] = messageVersion.Hash; + + // Update the received version since this message will always be passed version 0, due to the map not + // being initialized until just now. + var messageType = networkManager.MessagingSystem.GetMessageForHash(messageVersion.Hash); + if (messageType == typeof(ConnectionApprovedMessage)) + { + receivedMessageVersion = messageVersion.Version; + } + } + networkManager.MessagingSystem.SetServerMessageOrder(messageHashesInOrder); + messageHashesInOrder.Dispose(); + // ============================================================ + // END FORBIDDEN SEGMENT + // ============================================================ + ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId); ByteUnpacker.ReadValueBitPacked(reader, out NetworkTick); m_ReceivedSceneObjectData = reader; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs index ee7965f807..73e8b1fe00 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs @@ -1,15 +1,35 @@ +using Unity.Collections; + namespace Unity.Netcode { internal struct ConnectionRequestMessage : INetworkMessage { + public int Version => 0; + public ulong ConfigHash; public byte[] ConnectionData; public bool ShouldSendConnectionData; - public void Serialize(FastBufferWriter writer) + public NativeArray MessageVersions; + + public void Serialize(FastBufferWriter writer, int targetVersion) { + // ============================================================ + // BEGIN FORBIDDEN SEGMENT + // DO NOT CHANGE THIS HEADER. Everything added to this message + // must go AFTER the message version header. + // ============================================================ + BytePacker.WriteValueBitPacked(writer, MessageVersions.Length); + foreach (var messageVersion in MessageVersions) + { + messageVersion.Serialize(writer); + } + // ============================================================ + // END FORBIDDEN SEGMENT + // ============================================================ + if (ShouldSendConnectionData) { writer.WriteValueSafe(ConfigHash); @@ -21,7 +41,7 @@ public void Serialize(FastBufferWriter writer) } } - public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { var networkManager = (NetworkManager)context.SystemOwner; if (!networkManager.IsServer) @@ -29,6 +49,30 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context) return false; } + // ============================================================ + // BEGIN FORBIDDEN SEGMENT + // DO NOT CHANGE THIS HEADER. Everything added to this message + // must go AFTER the message version header. + // ============================================================ + ByteUnpacker.ReadValueBitPacked(reader, out int length); + for (var i = 0; i < length; ++i) + { + var messageVersion = new MessageVersionData(); + messageVersion.Deserialize(reader); + networkManager.MessagingSystem.SetVersion(context.SenderId, messageVersion.Hash, messageVersion.Version); + + // Update the received version since this message will always be passed version 0, due to the map not + // being initialized until just now. + var messageType = networkManager.MessagingSystem.GetMessageForHash(messageVersion.Hash); + if (messageType == typeof(ConnectionRequestMessage)) + { + receivedMessageVersion = messageVersion.Version; + } + } + // ============================================================ + // END FORBIDDEN SEGMENT + // ============================================================ + if (networkManager.NetworkConfig.ConnectionApproval) { if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(ConfigHash) + FastBufferWriter.GetWriteSize())) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs index f27227cba8..defddd5f69 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs @@ -2,15 +2,17 @@ namespace Unity.Netcode { internal struct CreateObjectMessage : INetworkMessage { + public int Version => 0; + public NetworkObject.SceneObject ObjectInfo; private FastBufferReader m_ReceivedNetworkVariableData; - public void Serialize(FastBufferWriter writer) + public void Serialize(FastBufferWriter writer, int targetVersion) { ObjectInfo.Serialize(writer); } - public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { var networkManager = (NetworkManager)context.SystemOwner; if (!networkManager.IsClient) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs index 7e13f9cc72..425cbe9f0a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs @@ -2,16 +2,18 @@ namespace Unity.Netcode { internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcpy { + public int Version => 0; + public ulong NetworkObjectId; public bool DestroyGameObject; - public void Serialize(FastBufferWriter writer) + public void Serialize(FastBufferWriter writer, int targetVersion) { BytePacker.WriteValueBitPacked(writer, NetworkObjectId); writer.WriteValueSafe(DestroyGameObject); } - public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { var networkManager = (NetworkManager)context.SystemOwner; if (!networkManager.IsClient) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/MessageMetadata.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/MessageMetadata.cs new file mode 100644 index 0000000000..964428030b --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/MessageMetadata.cs @@ -0,0 +1,23 @@ +namespace Unity.Netcode +{ + /// + /// Conveys a version number on a remote node for the given message (identified by its hash) + /// + internal struct MessageVersionData + { + public uint Hash; + public int Version; + + public void Serialize(FastBufferWriter writer) + { + writer.WriteValueSafe(Hash); + BytePacker.WriteValueBitPacked(writer, Version); + } + + public void Deserialize(FastBufferReader reader) + { + reader.ReadValueSafe(out Hash); + ByteUnpacker.ReadValueBitPacked(reader, out Version); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/MessageMetadata.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/MessageMetadata.cs.meta new file mode 100644 index 0000000000..d6dc086a4f --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/MessageMetadata.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 754d727b316b4263a2fa0d4c54fdad52 +timeCreated: 1666895514 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NamedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NamedMessage.cs index 246f5aa029..87ac914c99 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NamedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NamedMessage.cs @@ -2,18 +2,20 @@ namespace Unity.Netcode { internal struct NamedMessage : INetworkMessage { + public int Version => 0; + public ulong Hash; public FastBufferWriter SendData; private FastBufferReader m_ReceiveData; - public unsafe void Serialize(FastBufferWriter writer) + public unsafe void Serialize(FastBufferWriter writer, int targetVersion) { writer.WriteValueSafe(Hash); writer.WriteBytesSafe(SendData.GetUnsafePtr(), SendData.Length); } - public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { reader.ReadValueSafe(out Hash); m_ReceiveData = reader; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs index 8cb44ad1a2..ba3289ad59 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs @@ -12,6 +12,8 @@ namespace Unity.Netcode /// internal struct NetworkVariableDeltaMessage : INetworkMessage { + public int Version => 0; + public ulong NetworkObjectId; public ushort NetworkBehaviourIndex; @@ -21,7 +23,7 @@ internal struct NetworkVariableDeltaMessage : INetworkMessage private FastBufferReader m_ReceivedNetworkVariableData; - public void Serialize(FastBufferWriter writer) + public void Serialize(FastBufferWriter writer, int targetVersion) { if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(NetworkObjectId) + FastBufferWriter.GetWriteSize(NetworkBehaviourIndex))) { @@ -110,7 +112,7 @@ public void Serialize(FastBufferWriter writer) } } - public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId); ByteUnpacker.ReadValueBitPacked(reader, out NetworkBehaviourIndex); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/OrderingMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/OrderingMessage.cs deleted file mode 100644 index 6651e0e533..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/OrderingMessage.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; - -namespace Unity.Netcode -{ - /// - /// Upon connecting, the host sends a series of OrderingMessage to the client so that it can make sure both sides - /// have the same message types in the same positions in - /// - MessagingSystem.m_MessageHandlers - /// - MessagingSystem.m_ReverseTypeMap - /// even if one side has extra messages (compilation, version, patch, or platform differences, etc...) - /// - /// The ConnectionRequestedMessage, ConnectionApprovedMessage and OrderingMessage are prioritized at the beginning - /// of the mapping, to guarantee they can be exchanged before the two sides share their ordering - /// The sorting used in also stable so that even if MessageType names share hashes, it will work most of the time - /// - internal struct OrderingMessage : INetworkMessage - { - public int Order; - public uint Hash; - - public void Serialize(FastBufferWriter writer) - { - if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(Order) + FastBufferWriter.GetWriteSize(Hash))) - { - throw new OverflowException($"Not enough space in the buffer to write {nameof(OrderingMessage)}"); - } - - writer.WriteValue(Order); - writer.WriteValue(Hash); - } - - public bool Deserialize(FastBufferReader reader, ref NetworkContext context) - { - if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(Order) + FastBufferWriter.GetWriteSize(Hash))) - { - throw new OverflowException($"Not enough data in the buffer to read {nameof(OrderingMessage)}"); - } - - reader.ReadValue(out Order); - reader.ReadValue(out Hash); - - return true; - } - - public void Handle(ref NetworkContext context) - { - ((NetworkManager)context.SystemOwner).MessagingSystem.ReorderMessage(Order, Hash); - } - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/OrderingMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/OrderingMessage.cs.meta deleted file mode 100644 index 3a8bc039c5..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/OrderingMessage.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 3ada9e8fd5bf94b1f9a6a21531c8a3ee -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs index 4a68d02e7a..c65c87b8f2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs @@ -4,6 +4,8 @@ namespace Unity.Netcode { internal struct ParentSyncMessage : INetworkMessage { + public int Version => 0; + public ulong NetworkObjectId; private byte m_BitField; @@ -39,7 +41,7 @@ public bool RemoveParent public Quaternion Rotation; public Vector3 Scale; - public void Serialize(FastBufferWriter writer) + public void Serialize(FastBufferWriter writer, int targetVersion) { BytePacker.WriteValueBitPacked(writer, NetworkObjectId); writer.WriteValueSafe(m_BitField); @@ -57,7 +59,7 @@ public void Serialize(FastBufferWriter writer) writer.WriteValueSafe(Scale); } - public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { var networkManager = (NetworkManager)context.SystemOwner; if (!networkManager.IsClient) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs index b13db21aa4..21889c0f3e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs @@ -85,17 +85,19 @@ internal struct RpcMetadata : INetworkSerializeByMemcpy internal struct ServerRpcMessage : INetworkMessage { + public int Version => 0; + public RpcMetadata Metadata; public FastBufferWriter WriteBuffer; public FastBufferReader ReadBuffer; - public unsafe void Serialize(FastBufferWriter writer) + public unsafe void Serialize(FastBufferWriter writer, int targetVersion) { RpcMessageHelpers.Serialize(ref writer, ref Metadata, ref WriteBuffer); } - public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context) + public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer); } @@ -118,17 +120,19 @@ public void Handle(ref NetworkContext context) internal struct ClientRpcMessage : INetworkMessage { + public int Version => 0; + public RpcMetadata Metadata; public FastBufferWriter WriteBuffer; public FastBufferReader ReadBuffer; - public void Serialize(FastBufferWriter writer) + public void Serialize(FastBufferWriter writer, int targetVersion) { RpcMessageHelpers.Serialize(ref writer, ref Metadata, ref WriteBuffer); } - public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer); } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs index 48be77d070..44175690ea 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs @@ -4,16 +4,18 @@ namespace Unity.Netcode // like most of the other messages when we have some more time and can come back and refactor this. internal struct SceneEventMessage : INetworkMessage { + public int Version => 0; + public SceneEventData EventData; private FastBufferReader m_ReceivedData; - public void Serialize(FastBufferWriter writer) + public void Serialize(FastBufferWriter writer, int targetVersion) { EventData.Serialize(writer); } - public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { m_ReceivedData = reader; return true; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs index 8f0cde84f2..b12c0080d7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs @@ -2,6 +2,8 @@ namespace Unity.Netcode { internal struct ServerLogMessage : INetworkMessage { + public int Version => 0; + public NetworkLog.LogType LogType; // It'd be lovely to be able to replace this with FixedString or NativeArray... // But it's not really practical. On the sending side, the user is likely to want @@ -11,13 +13,13 @@ internal struct ServerLogMessage : INetworkMessage public string Message; - public void Serialize(FastBufferWriter writer) + public void Serialize(FastBufferWriter writer, int targetVersion) { writer.WriteValueSafe(LogType); BytePacker.WriteValuePacked(writer, Message); } - public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { var networkManager = (NetworkManager)context.SystemOwner; if (networkManager.IsServer && networkManager.NetworkConfig.EnableNetworkLogs) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs index 24fcbc0563..97b0fcc5e7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs @@ -2,14 +2,16 @@ namespace Unity.Netcode { internal struct TimeSyncMessage : INetworkMessage, INetworkSerializeByMemcpy { + public int Version => 0; + public int Tick; - public void Serialize(FastBufferWriter writer) + public void Serialize(FastBufferWriter writer, int targetVersion) { BytePacker.WriteValueBitPacked(writer, Tick); } - public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { var networkManager = (NetworkManager)context.SystemOwner; if (!networkManager.IsClient) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/UnnamedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/UnnamedMessage.cs index b34d200d6e..ccce67c3f5 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/UnnamedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/UnnamedMessage.cs @@ -2,15 +2,17 @@ namespace Unity.Netcode { internal struct UnnamedMessage : INetworkMessage { + public int Version => 0; + public FastBufferWriter SendData; private FastBufferReader m_ReceivedData; - public unsafe void Serialize(FastBufferWriter writer) + public unsafe void Serialize(FastBufferWriter writer, int targetVersion) { writer.WriteBytesSafe(SendData.GetUnsafePtr(), SendData.Length); } - public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { m_ReceivedData = reader; return true; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/MessagingSystem.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/MessagingSystem.cs index f5d379dd02..6f5fca50fb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/MessagingSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/MessagingSystem.cs @@ -46,6 +46,7 @@ public SendQueueItem(NetworkDelivery delivery, int writerSize, Allocator writerA } internal delegate void MessageHandler(FastBufferReader reader, ref NetworkContext context, MessagingSystem system); + internal delegate int VersionGetter(); private NativeList m_IncomingMessageQueue = new NativeList(16, Allocator.Persistent); @@ -56,6 +57,11 @@ public SendQueueItem(NetworkDelivery delivery, int writerSize, Allocator writerA private Dictionary m_MessageTypes = new Dictionary(); private Dictionary> m_SendQueues = new Dictionary>(); + // This is m_PerClientMessageVersion[clientId][messageType] = version + private Dictionary> m_PerClientMessageVersions = new Dictionary>(); + private Dictionary m_MessagesByHash = new Dictionary(); + private Dictionary m_LocalVersions = new Dictionary(); + private List m_Hooks = new List(); private uint m_HighMessageType; @@ -80,6 +86,7 @@ internal struct MessageWithHandler { public Type MessageType; public MessageHandler Handler; + public VersionGetter GetVersion; } internal List PrioritizeMessageOrder(List allowedTypes) @@ -90,9 +97,8 @@ internal List PrioritizeMessageOrder(List PrioritizeMessageOrder(List data, float receiveTime) @@ -270,68 +282,53 @@ private bool CanReceive(ulong clientId, Type messageType, FastBufferReader messa return true; } - // Moves the handler for the type having hash `targetHash` to the `desiredOrder` position, in the handler list - // This allows the server to tell the client which id it is using for which message and make sure the right - // message is used when deserializing. - internal void ReorderMessage(int desiredOrder, uint targetHash) + internal Type GetMessageForHash(uint messageHash) { - if (desiredOrder < 0) + if (!m_MessagesByHash.ContainsKey(messageHash)) { - throw new ArgumentException("ReorderMessage desiredOrder must be positive"); + return null; } + return m_MessagesByHash[messageHash]; + } - if (desiredOrder < m_ReverseTypeMap.Length && - XXHash.Hash32(m_ReverseTypeMap[desiredOrder].FullName) == targetHash) + internal void SetVersion(ulong clientId, uint messageHash, int version) + { + if (!m_MessagesByHash.ContainsKey(messageHash)) { - // matching positions and hashes. All good. return; } + var messageType = m_MessagesByHash[messageHash]; - Debug.Log($"Unexpected hash for {desiredOrder}"); - - // Since the message at `desiredOrder` is not the expected one, - // insert an empty placeholder and move the messages down - var typesAsList = new List(m_ReverseTypeMap); + if (!m_PerClientMessageVersions.ContainsKey(clientId)) + { + m_PerClientMessageVersions[clientId] = new Dictionary(); + } - typesAsList.Insert(desiredOrder, null); - var handlersAsList = new List(m_MessageHandlers); - handlersAsList.Insert(desiredOrder, null); + m_PerClientMessageVersions[clientId][messageType] = version; + } - // we added a dummy message, bump the end up - m_HighMessageType++; + internal void SetServerMessageOrder(NativeArray messagesInIdOrder) + { + var oldHandlers = m_MessageHandlers; + var oldTypes = m_MessageTypes; + m_ReverseTypeMap = new Type[messagesInIdOrder.Length]; + m_MessageHandlers = new MessageHandler[messagesInIdOrder.Length]; + m_MessageTypes = new Dictionary(); - // Here, we rely on the server telling us about all messages, in order. - // So, we know the handlers before desiredOrder are correct. - // We start at desiredOrder to not shift them when we insert. - int position = desiredOrder; - bool found = false; - while (position < typesAsList.Count) + for (var i = 0; i < messagesInIdOrder.Length; ++i) { - if (typesAsList[position] != null && - XXHash.Hash32(typesAsList[position].FullName) == targetHash) + if (!m_MessagesByHash.ContainsKey(messagesInIdOrder[i])) { - found = true; - break; + continue; } - - position++; - } - - if (found) - { - // Copy the handler and type to the right index - - typesAsList[desiredOrder] = typesAsList[position]; - handlersAsList[desiredOrder] = handlersAsList[position]; - typesAsList.RemoveAt(position); - handlersAsList.RemoveAt(position); - - // we removed a copy after moving a message, reduce the high message index - m_HighMessageType--; + var messageType = m_MessagesByHash[messagesInIdOrder[i]]; + var oldId = oldTypes[messageType]; + var handler = oldHandlers[oldId]; + var newId = (uint)i; + m_MessageTypes[messageType] = newId; + m_MessageHandlers[newId] = handler; + m_ReverseTypeMap[newId] = messageType; } - - m_ReverseTypeMap = typesAsList.ToArray(); - m_MessageHandlers = handlersAsList.ToArray(); } public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulong senderId, float timestamp, int serializedHeaderSize) @@ -433,7 +430,7 @@ internal void ClientDisconnected(ulong clientId) m_SendQueues.Remove(clientId); } - private unsafe void CleanupDisconnectedClient(ulong clientId) + private void CleanupDisconnectedClient(ulong clientId) { var queue = m_SendQueues[clientId]; for (var i = 0; i < queue.Length; ++i) @@ -444,10 +441,67 @@ private unsafe void CleanupDisconnectedClient(ulong clientId) queue.Dispose(); } + internal void CleanupDisconnectedClients() + { + var removeList = new NativeList(Allocator.Temp); + foreach (var clientId in m_PerClientMessageVersions.Keys) + { + if (!m_SendQueues.ContainsKey(clientId)) + { + removeList.Add(clientId); + } + } + + foreach (var clientId in removeList) + { + m_PerClientMessageVersions.Remove(clientId); + } + } + + public static int CreateMessageAndGetVersion() where T : INetworkMessage, new() + { + return new T().Version; + } + + internal int GetMessageVersion(Type type, ulong clientId, bool forReceive = false) + { + if (!m_PerClientMessageVersions.TryGetValue(clientId, out var versionMap)) + { + if (forReceive) + { + Debug.LogWarning($"Trying to receive {type.Name} from client {clientId} which is not in a connected state."); + + } + else + { + Debug.LogWarning($"Trying to send {type.Name} to client {clientId} which is not in a connected state."); + } + return -1; + } + if (!versionMap.TryGetValue(type, out var messageVersion)) + { + return -1; + } + + return messageVersion; + } + public static void ReceiveMessage(FastBufferReader reader, ref NetworkContext context, MessagingSystem system) where T : INetworkMessage, new() { var message = new T(); - if (message.Deserialize(reader, ref context)) + var messageVersion = 0; + // Special cases because these are the messages that carry the version info - thus the version info isn't + // populated yet when we get these. The first part of these messages always has to be the version data + // and can't change. + if (typeof(T) != typeof(ConnectionRequestMessage) && typeof(T) != typeof(ConnectionApprovedMessage) && typeof(T) != typeof(DisconnectReasonMessage)) + { + messageVersion = system.GetMessageVersion(typeof(T), context.SenderId, true); + if (messageVersion < 0) + { + return; + } + } + if (message.Deserialize(reader, ref context, messageVersion)) { for (var hookIdx = 0; hookIdx < system.m_Hooks.Count; ++hookIdx) { @@ -485,16 +539,47 @@ internal int SendMessage(ref TMessageType messa return 0; } - var maxSize = delivery == NetworkDelivery.ReliableFragmentedSequenced ? FRAGMENTED_MESSAGE_MAX_SIZE : NON_FRAGMENTED_MESSAGE_MAX_SIZE; + var largestSerializedSize = 0; + var sentMessageVersions = new NativeHashSet(clientIds.Count, Allocator.Temp); + for (var i = 0; i < clientIds.Count; ++i) + { + var messageVersion = 0; + // Special case because this is the message that carries the version info - thus the version info isn't + // populated yet when we get this. The first part of this message always has to be the version data + // and can't change. + if (typeof(TMessageType) != typeof(ConnectionRequestMessage)) + { + messageVersion = GetMessageVersion(typeof(TMessageType), clientIds[i]); + if (messageVersion < 0) + { + // Client doesn't know this message exists, don't send it at all. + continue; + } + } + + if (sentMessageVersions.Contains(messageVersion)) + { + continue; + } + + sentMessageVersions.Add(messageVersion); + + var maxSize = delivery == NetworkDelivery.ReliableFragmentedSequenced ? FRAGMENTED_MESSAGE_MAX_SIZE : NON_FRAGMENTED_MESSAGE_MAX_SIZE; + + using var tmpSerializer = new FastBufferWriter(NON_FRAGMENTED_MESSAGE_MAX_SIZE - FastBufferWriter.GetWriteSize(), Allocator.Temp, maxSize - FastBufferWriter.GetWriteSize()); + + message.Serialize(tmpSerializer, messageVersion); - using var tmpSerializer = new FastBufferWriter(NON_FRAGMENTED_MESSAGE_MAX_SIZE - FastBufferWriter.GetWriteSize(), Allocator.Temp, maxSize - FastBufferWriter.GetWriteSize()); + var size = SendPreSerializedMessage(tmpSerializer, maxSize, ref message, delivery, clientIds, messageVersion); + largestSerializedSize = size > largestSerializedSize ? size : largestSerializedSize; + } - message.Serialize(tmpSerializer); + sentMessageVersions.Dispose(); - return SendPreSerializedMessage(tmpSerializer, maxSize, ref message, delivery, clientIds); + return largestSerializedSize; } - internal unsafe int SendPreSerializedMessage(in FastBufferWriter tmpSerializer, int maxSize, ref TMessageType message, NetworkDelivery delivery, in IReadOnlyList clientIds) + internal unsafe int SendPreSerializedMessage(in FastBufferWriter tmpSerializer, int maxSize, ref TMessageType message, NetworkDelivery delivery, in IReadOnlyList clientIds, int messageVersionFilter) where TMessageType : INetworkMessage { using var headerSerializer = new FastBufferWriter(FastBufferWriter.GetWriteSize(), Allocator.Temp); @@ -509,6 +594,25 @@ internal unsafe int SendPreSerializedMessage(in FastBufferWriter t for (var i = 0; i < clientIds.Count; ++i) { + var messageVersion = 0; + // Special case because this is the message that carries the version info - thus the version info isn't + // populated yet when we get this. The first part of this message always has to be the version data + // and can't change. + if (typeof(TMessageType) != typeof(ConnectionRequestMessage)) + { + messageVersion = GetMessageVersion(typeof(TMessageType), clientIds[i]); + if (messageVersion < 0) + { + // Client doesn't know this message exists, don't send it at all. + continue; + } + + if (messageVersion != messageVersionFilter) + { + continue; + } + } + var clientId = clientIds[i]; if (!CanSend(clientId, typeof(TMessageType), delivery)) @@ -559,8 +663,22 @@ internal unsafe int SendPreSerializedMessage(in FastBufferWriter t internal unsafe int SendPreSerializedMessage(in FastBufferWriter tmpSerializer, int maxSize, ref TMessageType message, NetworkDelivery delivery, ulong clientId) where TMessageType : INetworkMessage { + var messageVersion = 0; + // Special case because this is the message that carries the version info - thus the version info isn't + // populated yet when we get this. The first part of this message always has to be the version data + // and can't change. + if (typeof(TMessageType) != typeof(ConnectionRequestMessage)) + { + messageVersion = GetMessageVersion(typeof(TMessageType), clientId); + if (messageVersion < 0) + { + // Client doesn't know this message exists, don't send it at all. + return 0; + } + } + ulong* clientIds = stackalloc ulong[] { clientId }; - return SendPreSerializedMessage(tmpSerializer, maxSize, ref message, delivery, new PointerListWrapper(clientIds, 1)); + return SendPreSerializedMessage(tmpSerializer, maxSize, ref message, delivery, new PointerListWrapper(clientIds, 1), messageVersion); } private struct PointerListWrapper : IReadOnlyList diff --git a/com.unity.netcode.gameobjects/Tests/Editor/DisconnectMessageTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/DisconnectMessageTests.cs index e217919eeb..58f55020a8 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/DisconnectMessageTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/DisconnectMessageTests.cs @@ -12,11 +12,11 @@ public void EmptyDisconnectReason() var writer = new FastBufferWriter(20, Allocator.Temp, 20); var msg = new DisconnectReasonMessage(); msg.Reason = string.Empty; - msg.Serialize(writer); + msg.Serialize(writer, msg.Version); var fbr = new FastBufferReader(writer, Allocator.Temp); var recvMsg = new DisconnectReasonMessage(); - recvMsg.Deserialize(fbr, ref networkContext); + recvMsg.Deserialize(fbr, ref networkContext, msg.Version); Assert.IsEmpty(recvMsg.Reason); } @@ -28,11 +28,11 @@ public void DisconnectReason() var writer = new FastBufferWriter(20, Allocator.Temp, 20); var msg = new DisconnectReasonMessage(); msg.Reason = "Foo"; - msg.Serialize(writer); + msg.Serialize(writer, msg.Version); var fbr = new FastBufferReader(writer, Allocator.Temp); var recvMsg = new DisconnectReasonMessage(); - recvMsg.Deserialize(fbr, ref networkContext); + recvMsg.Deserialize(fbr, ref networkContext, msg.Version); Assert.AreEqual("Foo", recvMsg.Reason); } @@ -44,11 +44,11 @@ public void DisconnectReasonTooLong() var writer = new FastBufferWriter(20, Allocator.Temp, 20); var msg = new DisconnectReasonMessage(); msg.Reason = "ThisStringIsWayLongerThanTwentyBytes"; - msg.Serialize(writer); + msg.Serialize(writer, msg.Version); var fbr = new FastBufferReader(writer, Allocator.Temp); var recvMsg = new DisconnectReasonMessage(); - recvMsg.Deserialize(fbr, ref networkContext); + recvMsg.Deserialize(fbr, ref networkContext, msg.Version); Assert.IsEmpty(recvMsg.Reason); } diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageReceivingTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageReceivingTests.cs index d8b6f374f3..11c65e7740 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageReceivingTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageReceivingTests.cs @@ -18,12 +18,12 @@ private struct TestMessage : INetworkMessage, INetworkSerializeByMemcpy public static bool Handled; public static List DeserializedValues = new List(); - public void Serialize(FastBufferWriter writer) + public void Serialize(FastBufferWriter writer, int targetVersion) { writer.WriteValueSafe(this); } - public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { Deserialized = true; reader.ReadValueSafe(out this); @@ -35,6 +35,8 @@ public void Handle(ref NetworkContext context) Handled = true; DeserializedValues.Add(this); } + + public int Version => 0; } private class TestMessageProvider : IMessageProvider @@ -46,7 +48,8 @@ private class TestMessageProvider : IMessageProvider new MessagingSystem.MessageWithHandler { MessageType = typeof(TestMessage), - Handler = MessagingSystem.ReceiveMessage + Handler = MessagingSystem.ReceiveMessage, + GetVersion = MessagingSystem.CreateMessageAndGetVersion } }; } @@ -62,6 +65,7 @@ public void SetUp() TestMessage.DeserializedValues.Clear(); m_MessagingSystem = new MessagingSystem(new NopMessageSender(), this, new TestMessageProvider()); + m_MessagingSystem.SetVersion(0, XXHash.Hash32(typeof(TestMessage).FullName), 0); } [TearDown] diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs index 38ca9b0bf1..2f9d58cd3d 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using NUnit.Framework; @@ -11,12 +10,12 @@ private struct TestMessageOne : INetworkMessage, INetworkSerializeByMemcpy public int A; public int B; public int C; - public void Serialize(FastBufferWriter writer) + public void Serialize(FastBufferWriter writer, int targetVersion) { writer.WriteValue(this); } - public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { return true; } @@ -24,6 +23,8 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context) public void Handle(ref NetworkContext context) { } + + public int Version => 0; } private struct TestMessageTwo : INetworkMessage, INetworkSerializeByMemcpy @@ -31,12 +32,12 @@ private struct TestMessageTwo : INetworkMessage, INetworkSerializeByMemcpy public int A; public int B; public int C; - public void Serialize(FastBufferWriter writer) + public void Serialize(FastBufferWriter writer, int targetVersion) { writer.WriteValue(this); } - public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { return true; } @@ -44,6 +45,8 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context) public void Handle(ref NetworkContext context) { } + + public int Version => 0; } private class TestMessageProviderOne : IMessageProvider { @@ -54,12 +57,14 @@ private class TestMessageProviderOne : IMessageProvider new MessagingSystem.MessageWithHandler { MessageType = typeof(TestMessageOne), - Handler = MessagingSystem.ReceiveMessage + Handler = MessagingSystem.ReceiveMessage, + GetVersion = MessagingSystem.CreateMessageAndGetVersion }, new MessagingSystem.MessageWithHandler { MessageType = typeof(TestMessageTwo), - Handler = MessagingSystem.ReceiveMessage + Handler = MessagingSystem.ReceiveMessage, + GetVersion = MessagingSystem.CreateMessageAndGetVersion } }; } @@ -70,12 +75,12 @@ private struct TestMessageThree : INetworkMessage, INetworkSerializeByMemcpy public int A; public int B; public int C; - public void Serialize(FastBufferWriter writer) + public void Serialize(FastBufferWriter writer, int targetVersion) { writer.WriteValue(this); } - public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { return true; } @@ -83,6 +88,8 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context) public void Handle(ref NetworkContext context) { } + + public int Version => 0; } private class TestMessageProviderTwo : IMessageProvider { @@ -93,7 +100,8 @@ private class TestMessageProviderTwo : IMessageProvider new MessagingSystem.MessageWithHandler { MessageType = typeof(TestMessageThree), - Handler = MessagingSystem.ReceiveMessage + Handler = MessagingSystem.ReceiveMessage, + GetVersion = MessagingSystem.CreateMessageAndGetVersion } }; } @@ -103,12 +111,12 @@ private struct TestMessageFour : INetworkMessage, INetworkSerializeByMemcpy public int A; public int B; public int C; - public void Serialize(FastBufferWriter writer) + public void Serialize(FastBufferWriter writer, int targetVersion) { writer.WriteValue(this); } - public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { return true; } @@ -116,6 +124,8 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context) public void Handle(ref NetworkContext context) { } + + public int Version => 0; } private class TestMessageProviderThree : IMessageProvider { @@ -126,7 +136,8 @@ private class TestMessageProviderThree : IMessageProvider new MessagingSystem.MessageWithHandler { MessageType = typeof(TestMessageFour), - Handler = MessagingSystem.ReceiveMessage + Handler = MessagingSystem.ReceiveMessage, + GetVersion = MessagingSystem.CreateMessageAndGetVersion } }; } @@ -183,11 +194,11 @@ public void WhenCreatingMessageSystem_BoundTypeMessageHandlersAreRegistered() internal class AAAEarlyLexicographicNetworkMessage : INetworkMessage { - public void Serialize(FastBufferWriter writer) + public void Serialize(FastBufferWriter writer, int targetVersion) { } - public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { return true; } @@ -195,6 +206,8 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context) public void Handle(ref NetworkContext context) { } + + public int Version => 0; } #pragma warning disable IDE1006 @@ -212,18 +225,19 @@ internal class OrderingMessageProvider : IMessageProvider var messageWithHandler = new MessagingSystem.MessageWithHandler(); messageWithHandler.MessageType = typeof(zzzLateLexicographicNetworkMessage); + messageWithHandler.GetVersion = MessagingSystem.CreateMessageAndGetVersion; listMessages.Add(messageWithHandler); messageWithHandler.MessageType = typeof(ConnectionRequestMessage); + messageWithHandler.GetVersion = MessagingSystem.CreateMessageAndGetVersion; listMessages.Add(messageWithHandler); messageWithHandler.MessageType = typeof(ConnectionApprovedMessage); - listMessages.Add(messageWithHandler); - - messageWithHandler.MessageType = typeof(OrderingMessage); + messageWithHandler.GetVersion = MessagingSystem.CreateMessageAndGetVersion; listMessages.Add(messageWithHandler); messageWithHandler.MessageType = typeof(AAAEarlyLexicographicNetworkMessage); + messageWithHandler.GetVersion = MessagingSystem.CreateMessageAndGetVersion; listMessages.Add(messageWithHandler); return listMessages; @@ -237,65 +251,16 @@ public void MessagesGetPrioritizedCorrectly() var provider = new OrderingMessageProvider(); using var messagingSystem = new MessagingSystem(sender, null, provider); - // the 3 priority messages should appear first, in lexicographic order + // the 2 priority messages should appear first, in lexicographic order Assert.AreEqual(messagingSystem.MessageTypes[0], typeof(ConnectionApprovedMessage)); Assert.AreEqual(messagingSystem.MessageTypes[1], typeof(ConnectionRequestMessage)); - Assert.AreEqual(messagingSystem.MessageTypes[2], typeof(OrderingMessage)); // the other should follow after - Assert.AreEqual(messagingSystem.MessageTypes[3], typeof(AAAEarlyLexicographicNetworkMessage)); - Assert.AreEqual(messagingSystem.MessageTypes[4], typeof(zzzLateLexicographicNetworkMessage)); - - // there should not be any extras - Assert.AreEqual(messagingSystem.MessageHandlerCount, 5); - - // reorder the zzz one to position 3 - messagingSystem.ReorderMessage(3, XXHash.Hash32(typeof(zzzLateLexicographicNetworkMessage).FullName)); - - // the 3 priority messages should still appear first, in lexicographic order - Assert.AreEqual(messagingSystem.MessageTypes[0], typeof(ConnectionApprovedMessage)); - Assert.AreEqual(messagingSystem.MessageTypes[1], typeof(ConnectionRequestMessage)); - Assert.AreEqual(messagingSystem.MessageTypes[2], typeof(OrderingMessage)); - - // the other should follow after, but reordered + Assert.AreEqual(messagingSystem.MessageTypes[2], typeof(AAAEarlyLexicographicNetworkMessage)); Assert.AreEqual(messagingSystem.MessageTypes[3], typeof(zzzLateLexicographicNetworkMessage)); - Assert.AreEqual(messagingSystem.MessageTypes[4], typeof(AAAEarlyLexicographicNetworkMessage)); - // there should still not be any extras - Assert.AreEqual(messagingSystem.MessageHandlerCount, 5); - - // verify we get an exception when asking for an invalid position - try - { - messagingSystem.ReorderMessage(-1, XXHash.Hash32(typeof(zzzLateLexicographicNetworkMessage).FullName)); - Assert.Fail(); - } - catch (ArgumentException) - { - } - - // reorder the zzz one to position 3, again, to check nothing bad happens - messagingSystem.ReorderMessage(3, XXHash.Hash32(typeof(zzzLateLexicographicNetworkMessage).FullName)); - - // the two non-priority should not have moved - Assert.AreEqual(messagingSystem.MessageTypes[3], typeof(zzzLateLexicographicNetworkMessage)); - Assert.AreEqual(messagingSystem.MessageTypes[4], typeof(AAAEarlyLexicographicNetworkMessage)); - - // there should still not be any extras - Assert.AreEqual(messagingSystem.MessageHandlerCount, 5); - - // 4242 is a random hash that should not match anything - messagingSystem.ReorderMessage(3, 4242); - - // that should result in an extra entry - Assert.AreEqual(messagingSystem.MessageHandlerCount, 6); - - // with a null handler - Assert.AreEqual(messagingSystem.MessageHandlers[3], null); - - // and it should have bumped the previous messages down - Assert.AreEqual(messagingSystem.MessageTypes[4], typeof(zzzLateLexicographicNetworkMessage)); - Assert.AreEqual(messagingSystem.MessageTypes[5], typeof(AAAEarlyLexicographicNetworkMessage)); + // there should not be any extras + Assert.AreEqual(messagingSystem.MessageHandlerCount, 4); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs index 5fce803b07..66f398aa96 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs @@ -18,13 +18,13 @@ private struct TestMessage : INetworkMessage, INetworkSerializeByMemcpy public int B; public int C; public static bool Serialized; - public void Serialize(FastBufferWriter writer) + public void Serialize(FastBufferWriter writer, int targetVersion) { Serialized = true; writer.WriteValueSafe(this); } - public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { return true; } @@ -32,6 +32,8 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context) public void Handle(ref NetworkContext context) { } + + public int Version => 0; } private class TestMessageSender : IMessageSender @@ -66,7 +68,8 @@ public void Dispose() new MessagingSystem.MessageWithHandler { MessageType = typeof(TestMessage), - Handler = MessagingSystem.ReceiveMessage + Handler = MessagingSystem.ReceiveMessage, + GetVersion = MessagingSystem.CreateMessageAndGetVersion } }; // Track messages sent @@ -88,6 +91,7 @@ public void SetUp() m_TestMessageProvider = new TestMessageProvider(); m_MessagingSystem = new MessagingSystem(m_MessageSender, this, m_TestMessageProvider); m_MessagingSystem.ClientConnected(0); + m_MessagingSystem.SetVersion(0, XXHash.Hash32(typeof(TestMessage).FullName), 0); } [TearDown] @@ -256,7 +260,8 @@ private class TestNoHandlerMessageProvider : IMessageProvider new MessagingSystem.MessageWithHandler { MessageType = typeof(TestMessage), - Handler = null + Handler = null, + GetVersion = MessagingSystem.CreateMessageAndGetVersion } }; } diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageVersioningTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageVersioningTests.cs new file mode 100644 index 0000000000..cbc7109d8c --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageVersioningTests.cs @@ -0,0 +1,503 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using NUnit.Framework.Internal; + +namespace Unity.Netcode.EditorTests +{ + public class MessageVersioningTests + { + public static int SentVersion; + public static int ReceivedVersion; + + private const int k_DefaultB = 5; + private const int k_DefaultC = 10; + private const int k_DefaultD = 15; + private const long k_DefaultE = 20; + + private struct VersionedTestMessage_v0 : INetworkMessage, INetworkSerializeByMemcpy + { + public int A; + public int B; + public int C; + public static bool Serialized; + public static bool Deserialized; + public static bool Handled; + public static List DeserializedValues = new List(); + + public void Serialize(FastBufferWriter writer, int targetVersion) + { + SentVersion = Version; + Serialized = true; + writer.WriteValueSafe(A); + writer.WriteValueSafe(B); + writer.WriteValueSafe(C); + } + + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) + { + ReceivedVersion = Version; + Deserialized = true; + reader.ReadValueSafe(out A); + reader.ReadValueSafe(out B); + reader.ReadValueSafe(out C); + return true; + } + + public void Handle(ref NetworkContext context) + { + Handled = true; + DeserializedValues.Add(this); + } + + public int Version => 0; + } + + private struct VersionedTestMessage_v1 : INetworkMessage, INetworkSerializeByMemcpy + { + public int A; + public int B; + public int C; + public int D; + public static bool Serialized; + public static bool Deserialized; + public static bool Downgraded; + public static bool Upgraded; + public static bool Handled; + public static List DeserializedValues = new List(); + + public void Serialize(FastBufferWriter writer, int targetVersion) + { + if (targetVersion < Version) + { + Downgraded = true; + var v0 = new VersionedTestMessage_v0 { A = A, B = B, C = C }; + v0.Serialize(writer, targetVersion); + return; + } + SentVersion = Version; + Serialized = true; + writer.WriteValueSafe(C); + writer.WriteValueSafe(D); + writer.WriteValueSafe(A); + writer.WriteValueSafe(B); + } + + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) + { + if (receivedMessageVersion < Version) + { + var v0 = new VersionedTestMessage_v0(); + v0.Deserialize(reader, ref context, receivedMessageVersion); + A = v0.A; + B = v0.B; + C = v0.C; + D = k_DefaultD; + Upgraded = true; + return true; + } + ReceivedVersion = Version; + Deserialized = true; + reader.ReadValueSafe(out C); + reader.ReadValueSafe(out D); + reader.ReadValueSafe(out A); + reader.ReadValueSafe(out B); + return true; + } + + public void Handle(ref NetworkContext context) + { + Handled = true; + DeserializedValues.Add(this); + } + + public int Version => 1; + } + + private struct VersionedTestMessage : INetworkMessage, INetworkSerializeByMemcpy + { + public int A; + public float D; + public long E; + public static bool Serialized; + public static bool Deserialized; + public static bool Downgraded; + public static bool Upgraded; + public static bool Handled; + public static List DeserializedValues = new List(); + + public void Serialize(FastBufferWriter writer, int targetVersion) + { + if (targetVersion < Version) + { + Downgraded = true; + var v1 = new VersionedTestMessage_v1 { A = A, B = k_DefaultB, C = k_DefaultC, D = (int)D }; + v1.Serialize(writer, targetVersion); + return; + } + SentVersion = Version; + Serialized = true; + writer.WriteValueSafe(D); + writer.WriteValueSafe(A); + writer.WriteValueSafe(E); + } + + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) + { + if (receivedMessageVersion < Version) + { + var v1 = new VersionedTestMessage_v1(); + v1.Deserialize(reader, ref context, receivedMessageVersion); + A = v1.A; + D = (float)v1.D; + E = k_DefaultE; + Upgraded = true; + return true; + } + ReceivedVersion = Version; + Deserialized = true; + reader.ReadValueSafe(out D); + reader.ReadValueSafe(out A); + reader.ReadValueSafe(out E); + return true; + } + + public void Handle(ref NetworkContext context) + { + Handled = true; + DeserializedValues.Add(this); + } + + public int Version => 2; + } + + private class TestMessageProvider_v0 : IMessageProvider + { + public List GetMessages() + { + return new List + { + new MessagingSystem.MessageWithHandler + { + MessageType = typeof(VersionedTestMessage_v0), + Handler = MessagingSystem.ReceiveMessage, + GetVersion = MessagingSystem.CreateMessageAndGetVersion + } + }; + } + } + + private class TestMessageProvider_v1 : IMessageProvider + { + public List GetMessages() + { + return new List + { + new MessagingSystem.MessageWithHandler + { + MessageType = typeof(VersionedTestMessage_v1), + Handler = MessagingSystem.ReceiveMessage, + GetVersion = MessagingSystem.CreateMessageAndGetVersion + } + }; + } + } + + private class TestMessageProvider_v2 : IMessageProvider + { + public List GetMessages() + { + return new List + { + new MessagingSystem.MessageWithHandler + { + MessageType = typeof(VersionedTestMessage), + Handler = MessagingSystem.ReceiveMessage, + GetVersion = MessagingSystem.CreateMessageAndGetVersion + } + }; + } + } + + private class TestMessageSender : IMessageSender + { + public List MessageQueue = new List(); + + public void Send(ulong clientId, NetworkDelivery delivery, FastBufferWriter batchData) + { + MessageQueue.Add(batchData.ToArray()); + } + } + + private MessagingSystem m_MessagingSystem_v0; + private MessagingSystem m_MessagingSystem_v1; + private MessagingSystem m_MessagingSystem_v2; + private TestMessageSender m_MessageSender; + + private void CreateFakeClients(MessagingSystem system, uint hash) + { + // Create three fake clients for each messaging system + // client 0 has version 0, client 1 has version 1, and client 2 has version 2 + system.ClientConnected(0); + system.ClientConnected(1); + system.ClientConnected(2); + system.SetVersion(0, hash, 0); + system.SetVersion(1, hash, 1); + system.SetVersion(2, hash, 2); + } + + [SetUp] + public void SetUp() + { + VersionedTestMessage_v0.Serialized = false; + VersionedTestMessage_v0.Deserialized = false; + VersionedTestMessage_v0.Handled = false; + VersionedTestMessage_v0.DeserializedValues.Clear(); + VersionedTestMessage_v1.Serialized = false; + VersionedTestMessage_v1.Deserialized = false; + VersionedTestMessage_v1.Downgraded = false; + VersionedTestMessage_v1.Upgraded = false; + VersionedTestMessage_v1.Handled = false; + VersionedTestMessage_v1.DeserializedValues.Clear(); + VersionedTestMessage.Serialized = false; + VersionedTestMessage.Deserialized = false; + VersionedTestMessage.Downgraded = false; + VersionedTestMessage.Upgraded = false; + VersionedTestMessage.Handled = false; + VersionedTestMessage.DeserializedValues.Clear(); + m_MessageSender = new TestMessageSender(); + + m_MessagingSystem_v0 = new MessagingSystem(m_MessageSender, this, new TestMessageProvider_v0()); + m_MessagingSystem_v1 = new MessagingSystem(m_MessageSender, this, new TestMessageProvider_v1()); + m_MessagingSystem_v2 = new MessagingSystem(m_MessageSender, this, new TestMessageProvider_v2()); + + CreateFakeClients(m_MessagingSystem_v0, XXHash.Hash32(typeof(VersionedTestMessage_v0).FullName)); + CreateFakeClients(m_MessagingSystem_v1, XXHash.Hash32(typeof(VersionedTestMessage_v1).FullName)); + CreateFakeClients(m_MessagingSystem_v2, XXHash.Hash32(typeof(VersionedTestMessage).FullName)); + + // Make sure that all three messages got the same IDs... + Assert.AreEqual( + m_MessagingSystem_v0.GetMessageType(typeof(VersionedTestMessage_v0)), + m_MessagingSystem_v1.GetMessageType(typeof(VersionedTestMessage_v1))); + Assert.AreEqual( + m_MessagingSystem_v0.GetMessageType(typeof(VersionedTestMessage_v0)), + m_MessagingSystem_v2.GetMessageType(typeof(VersionedTestMessage))); + } + + [TearDown] + public void TearDown() + { + m_MessagingSystem_v0.Dispose(); + m_MessagingSystem_v1.Dispose(); + m_MessagingSystem_v2.Dispose(); + } + + private VersionedTestMessage_v0 GetMessage_v0() + { + var random = new Random(); + return new VersionedTestMessage_v0 + { + A = random.Next(), + B = random.Next(), + C = random.Next(), + }; + } + + private VersionedTestMessage_v1 GetMessage_v1() + { + var random = new Random(); + return new VersionedTestMessage_v1 + { + A = random.Next(), + B = random.Next(), + C = random.Next(), + D = random.Next(), + }; + } + + private VersionedTestMessage GetMessage_v2() + { + var random = new Random(); + return new VersionedTestMessage + { + A = random.Next(), + D = (float)(random.NextDouble() * 10000), + E = ((long)random.Next() << 32) + random.Next() + }; + } + + public void CheckPostSendExpectations(int sourceLocalVersion, int remoteVersion) + { + Assert.AreEqual(Math.Min(sourceLocalVersion, remoteVersion) == 0, VersionedTestMessage_v0.Serialized); + Assert.AreEqual(Math.Min(sourceLocalVersion, remoteVersion) == 1, VersionedTestMessage_v1.Serialized); + Assert.AreEqual(Math.Min(sourceLocalVersion, remoteVersion) == 2, VersionedTestMessage.Serialized); + Assert.AreEqual(sourceLocalVersion >= 1 && remoteVersion < 1, VersionedTestMessage_v1.Downgraded); + Assert.AreEqual(sourceLocalVersion >= 2 && remoteVersion < 2, VersionedTestMessage.Downgraded); + + Assert.AreEqual(1, m_MessageSender.MessageQueue.Count); + Assert.AreEqual(Math.Min(sourceLocalVersion, remoteVersion), SentVersion); + } + + public void CheckPostReceiveExpectations(int sourceLocalVersion, int remoteVersion) + { + Assert.AreEqual(SentVersion == 0, VersionedTestMessage_v0.Deserialized); + Assert.AreEqual(SentVersion == 1, VersionedTestMessage_v1.Deserialized); + Assert.AreEqual(SentVersion == 2, VersionedTestMessage.Deserialized); + Assert.AreEqual(remoteVersion >= 1 && sourceLocalVersion < 1, VersionedTestMessage_v1.Upgraded); + Assert.AreEqual(remoteVersion >= 2 && sourceLocalVersion < 2, VersionedTestMessage.Upgraded); + + Assert.AreEqual((remoteVersion == 0 ? 1 : 0), VersionedTestMessage_v0.DeserializedValues.Count); + Assert.AreEqual((remoteVersion == 1 ? 1 : 0), VersionedTestMessage_v1.DeserializedValues.Count); + Assert.AreEqual((remoteVersion == 2 ? 1 : 0), VersionedTestMessage.DeserializedValues.Count); + + Assert.AreEqual(SentVersion, ReceivedVersion); + } + + private void SendMessageWithVersions(T message, int fromVersion, int toVersion) where T : unmanaged, INetworkMessage + { + MessagingSystem sendSystem; + switch (fromVersion) + { + case 0: sendSystem = m_MessagingSystem_v0; break; + case 1: sendSystem = m_MessagingSystem_v1; break; + default: sendSystem = m_MessagingSystem_v2; break; + } + sendSystem.SendMessage(ref message, NetworkDelivery.Reliable, (ulong)toVersion); + sendSystem.ProcessSendQueues(); + CheckPostSendExpectations(fromVersion, toVersion); + + MessagingSystem receiveSystem; + switch (toVersion) + { + case 0: receiveSystem = m_MessagingSystem_v0; break; + case 1: receiveSystem = m_MessagingSystem_v1; break; + default: receiveSystem = m_MessagingSystem_v2; break; + } + receiveSystem.HandleIncomingData((ulong)fromVersion, new ArraySegment(m_MessageSender.MessageQueue[0]), 0.0f); + receiveSystem.ProcessIncomingMessageQueue(); + CheckPostReceiveExpectations(fromVersion, toVersion); + + m_MessageSender.MessageQueue.Clear(); + } + + [Test] + public void WhenSendingV0ToV0_DataIsReceivedCorrectly() + { + var message = GetMessage_v0(); + + SendMessageWithVersions(message, 0, 0); + + var receivedMessage = VersionedTestMessage_v0.DeserializedValues[0]; + Assert.AreEqual(message.A, receivedMessage.A); + Assert.AreEqual(message.B, receivedMessage.B); + Assert.AreEqual(message.C, receivedMessage.C); + } + + [Test] + public void WhenSendingV0ToV1_DataIsReceivedCorrectly() + { + var message = GetMessage_v0(); + + SendMessageWithVersions(message, 0, 1); + + var receivedMessage = VersionedTestMessage_v1.DeserializedValues[0]; + Assert.AreEqual(message.A, receivedMessage.A); + Assert.AreEqual(message.B, receivedMessage.B); + Assert.AreEqual(message.C, receivedMessage.C); + Assert.AreEqual(k_DefaultD, receivedMessage.D); + } + + [Test] + public void WhenSendingV0ToV2_DataIsReceivedCorrectly() + { + var message = GetMessage_v0(); + + SendMessageWithVersions(message, 0, 2); + + var receivedMessage = VersionedTestMessage.DeserializedValues[0]; + Assert.AreEqual(message.A, receivedMessage.A); + Assert.AreEqual((float)k_DefaultD, receivedMessage.D); + Assert.AreEqual(k_DefaultE, receivedMessage.E); + } + + [Test] + public void WhenSendingV1ToV0_DataIsReceivedCorrectly() + { + var message = GetMessage_v1(); + + SendMessageWithVersions(message, 1, 0); + + var receivedMessage = VersionedTestMessage_v0.DeserializedValues[0]; + Assert.AreEqual(message.A, receivedMessage.A); + Assert.AreEqual(message.B, receivedMessage.B); + Assert.AreEqual(message.C, receivedMessage.C); + } + + [Test] + public void WhenSendingV1ToV1_DataIsReceivedCorrectly() + { + var message = GetMessage_v1(); + + SendMessageWithVersions(message, 1, 1); + + var receivedMessage = VersionedTestMessage_v1.DeserializedValues[0]; + Assert.AreEqual(message.A, receivedMessage.A); + Assert.AreEqual(message.B, receivedMessage.B); + Assert.AreEqual(message.C, receivedMessage.C); + Assert.AreEqual(message.D, receivedMessage.D); + } + + [Test] + public void WhenSendingV1ToV2_DataIsReceivedCorrectly() + { + var message = GetMessage_v1(); + + SendMessageWithVersions(message, 1, 2); + + var receivedMessage = VersionedTestMessage.DeserializedValues[0]; + Assert.AreEqual(message.A, receivedMessage.A); + Assert.AreEqual((float)message.D, receivedMessage.D); + Assert.AreEqual(k_DefaultE, receivedMessage.E); + } + + [Test] + public void WhenSendingV2ToV0_DataIsReceivedCorrectly() + { + var message = GetMessage_v2(); + + SendMessageWithVersions(message, 2, 0); + + var receivedMessage = VersionedTestMessage_v0.DeserializedValues[0]; + Assert.AreEqual(message.A, receivedMessage.A); + Assert.AreEqual(k_DefaultB, receivedMessage.B); + Assert.AreEqual(k_DefaultC, receivedMessage.C); + } + + [Test] + public void WhenSendingV2ToV1_DataIsReceivedCorrectly() + { + var message = GetMessage_v2(); + + SendMessageWithVersions(message, 2, 1); + + var receivedMessage = VersionedTestMessage_v1.DeserializedValues[0]; + Assert.AreEqual(message.A, receivedMessage.A); + Assert.AreEqual(k_DefaultB, receivedMessage.B); + Assert.AreEqual(k_DefaultC, receivedMessage.C); + Assert.AreEqual((int)message.D, receivedMessage.D); + } + + [Test] + public void WhenSendingV2ToV2_DataIsReceivedCorrectly() + { + var message = GetMessage_v2(); + + SendMessageWithVersions(message, 2, 2); + + var receivedMessage = VersionedTestMessage.DeserializedValues[0]; + Assert.AreEqual(message.A, receivedMessage.A); + Assert.AreEqual(message.D, receivedMessage.D); + Assert.AreEqual(message.E, receivedMessage.E); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageVersioningTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageVersioningTests.cs.meta new file mode 100644 index 0000000000..11b7f62fe4 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageVersioningTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: eac9a654aacb4faf91128c9ab6024543 +timeCreated: 1667326658 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs index c6d723a4df..a13adac97c 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs @@ -52,7 +52,7 @@ private int GetWriteSizeForOwnerChange(NetworkObject networkObject, ulong newOwn OwnerClientId = newOwner }; using var writer = new FastBufferWriter(1024, Allocator.Temp); - message.Serialize(writer); + message.Serialize(writer, message.Version); return writer.Length; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/ServerLogsMetricTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/ServerLogsMetricTests.cs index 9c5dae1acf..17cfcb83fb 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/ServerLogsMetricTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/ServerLogsMetricTests.cs @@ -30,7 +30,7 @@ private int GetWriteSizeForLog(NetworkLog.LogType logType, string logMessage) Message = logMessage }; using var writer = new FastBufferWriter(1024, Allocator.Temp); - message.Serialize(writer); + message.Serialize(writer, message.Version); return writer.Length; } From 4ce1a40ab303e09c4cd59b9470c47149b92d4722 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 15 Nov 2022 22:48:32 -0600 Subject: [PATCH 130/138] feat: Nested NetworkBehaviour Synchronization [MTT-5024] (#2298) * feat This includes the changes to provide users with a way to synchronize NetworkBehaviours with custom data prior to their associated NetworkObject is spawned. * fix and refactor This updates NetworkTransform so that it will properly synchronize when placed on nested NetworkBehaviours (i.e. their GameObject has no NetworkObject component). This also includes some fixes that allows for NetworkObjects to fail, NetworkVariables to fail, and NetworkBehaviour synchronization to fail without impacting the rest of the synchronization process. Minor fix for in-scene placed parenting under a non-NetworkObject to prevent from being added to the orphaned child list. * test Renamed NetworkObjectSceneSerializationTests to NetworkObjectSynchronizationTests. Added a more robust/wider range of tests, also added running host or server as well as added a basic OnSynchronize test. Added integration test to validate nested network transforms synchronize properly with nested GameObjects' transforms. Fixed an issue with late joined clients not registering all players in NetcodeIntegrationTest. * test manual This include a nested NetworkTransform manual test to visually validate the nested NetworkTransform update. --- com.unity.netcode.gameobjects/CHANGELOG.md | 8 +- .../Components/NetworkTransform.cs | 27 + .../Runtime/Core/NetworkBehaviour.cs | 205 +++- .../Runtime/Core/NetworkObject.cs | 130 +- .../Runtime/SceneManagement/SceneEventData.cs | 54 +- .../Runtime/Spawning/NetworkSpawnManager.cs | 15 +- .../Runtime/NetcodeIntegrationTest.cs | 18 +- .../NetworkObjectSceneSerializationTests.cs | 233 ---- .../NetworkObjectSynchronizationTests.cs | 624 ++++++++++ ...NetworkObjectSynchronizationTests.cs.meta} | 0 .../Assets/Scripts/ServerHostClientText.cs | 7 + .../Assets/Tests/Manual/ManualTestsMenu.unity | 2 + .../Tests/Manual/NestedNetworkTransforms.meta | 8 + .../NestedNetworkTransforms/ChildMover.cs | 31 + .../ChildMover.cs.meta | 11 + .../ChildMoverManager.cs | 107 ++ .../ChildMoverManager.cs.meta | 11 + .../NestedNetworkTransforms.unity | 1031 ++++++++++++++++ .../NestedNetworkTransforms.unity.meta | 7 + .../NestedNetworkTransformsReference.asset | 19 + ...estedNetworkTransformsReference.asset.meta | 8 + .../PlayerCube Variant.prefab | 474 ++++++++ .../PlayerCube Variant.prefab.meta | 7 + .../ObjectParenting/AutomatedPlayerMover.cs | 93 ++ .../AutomatedPlayerMover.cs.meta | 11 + .../ObjectParenting/Navigationpoints.cs | 20 + .../ObjectParenting/Navigationpoints.cs.meta | 11 + .../NestedNetworkTransformTestScene.unity | 1077 +++++++++++++++++ ...NestedNetworkTransformTestScene.unity.meta | 7 + .../NestedNetworkTransformTests.cs | 183 +++ .../NestedNetworkTransformTests.cs.meta | 11 + .../ParentingInSceneObjectsTests.cs | 1 - .../Runtime/ObjectParenting/Resources.meta | 8 + .../Resources/PlayerNestedTransforms.prefab | 515 ++++++++ .../PlayerNestedTransforms.prefab.meta | 7 + .../ProjectSettings/EditorBuildSettings.asset | 6 + 36 files changed, 4681 insertions(+), 306 deletions(-) delete mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSceneSerializationTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs rename com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/{NetworkObjectSceneSerializationTests.cs.meta => NetworkObjectSynchronizationTests.cs.meta} (100%) create mode 100644 testproject/Assets/Tests/Manual/NestedNetworkTransforms.meta create mode 100644 testproject/Assets/Tests/Manual/NestedNetworkTransforms/ChildMover.cs create mode 100644 testproject/Assets/Tests/Manual/NestedNetworkTransforms/ChildMover.cs.meta create mode 100644 testproject/Assets/Tests/Manual/NestedNetworkTransforms/ChildMoverManager.cs create mode 100644 testproject/Assets/Tests/Manual/NestedNetworkTransforms/ChildMoverManager.cs.meta create mode 100644 testproject/Assets/Tests/Manual/NestedNetworkTransforms/NestedNetworkTransforms.unity create mode 100644 testproject/Assets/Tests/Manual/NestedNetworkTransforms/NestedNetworkTransforms.unity.meta create mode 100644 testproject/Assets/Tests/Manual/NestedNetworkTransforms/NestedNetworkTransformsReference.asset create mode 100644 testproject/Assets/Tests/Manual/NestedNetworkTransforms/NestedNetworkTransformsReference.asset.meta create mode 100644 testproject/Assets/Tests/Manual/NestedNetworkTransforms/PlayerCube Variant.prefab create mode 100644 testproject/Assets/Tests/Manual/NestedNetworkTransforms/PlayerCube Variant.prefab.meta create mode 100644 testproject/Assets/Tests/Runtime/ObjectParenting/AutomatedPlayerMover.cs create mode 100644 testproject/Assets/Tests/Runtime/ObjectParenting/AutomatedPlayerMover.cs.meta create mode 100644 testproject/Assets/Tests/Runtime/ObjectParenting/Navigationpoints.cs create mode 100644 testproject/Assets/Tests/Runtime/ObjectParenting/Navigationpoints.cs.meta create mode 100644 testproject/Assets/Tests/Runtime/ObjectParenting/NestedNetworkTransformTestScene.unity create mode 100644 testproject/Assets/Tests/Runtime/ObjectParenting/NestedNetworkTransformTestScene.unity.meta create mode 100644 testproject/Assets/Tests/Runtime/ObjectParenting/NestedNetworkTransformTests.cs create mode 100644 testproject/Assets/Tests/Runtime/ObjectParenting/NestedNetworkTransformTests.cs.meta create mode 100644 testproject/Assets/Tests/Runtime/ObjectParenting/Resources.meta create mode 100644 testproject/Assets/Tests/Runtime/ObjectParenting/Resources/PlayerNestedTransforms.prefab create mode 100644 testproject/Assets/Tests/Runtime/ObjectParenting/Resources/PlayerNestedTransforms.prefab.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index dcfa2464e2..ea2c6243b9 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -9,17 +9,23 @@ Additional documentation and release notes are available at [Multiplayer Documen ## [Unreleased] ### Added + +- Added protected method `NetworkBehaviour.OnSynchronize` which is invoked during the initial `NetworkObject` synchronization process. This provides users the ability to include custom serialization information that will be applied to the `NetworkBehaviour` prior to the `NetworkObject` being spawned. (#2298) - Added support for different versions of the SDK to talk to each other in circumstances where changes permit it. Starting with this version and into future versions, patch versions should be compatible as long as the minor version is the same. (#2290) - Added `NetworkObject` auto-add helper and Multiplayer Tools install reminder settings to Project Settings. (#2285) -- Added `public string DisconnectReason` getter to `NetworkManager` and `string Reason` to `ConnectionApprovalResponse`. Allows connection approval to communicate back a reason. Also added `public void DisconnectClient(ulong clientId, string reason)` allowing setting a disconnection reason, when explicitly disconnecting a client. +- Added `public string DisconnectReason` getter to `NetworkManager` and `string Reason` to `ConnectionApprovalResponse`. Allows connection approval to communicate back a reason. Also added `public void DisconnectClient(ulong clientId, string reason)` allowing setting a disconnection reason, when explicitly disconnecting a client. (#2280) ### Changed - Changed 3rd-party `XXHash` (32 & 64) implementation with an in-house reimplementation (#2310) +- When `NetworkConfig.EnsureNetworkVariableLengthSafety` is disabled `NetworkVariable` fields do not write the additional `ushort` size value (_which helps to reduce the total synchronization message size_), but when enabled it still writes the additional `ushort` value. (#2298) - Optimized bandwidth usage by encoding most integer fields using variable-length encoding. (#2276) ### Fixed +- Fixed issue where `NetworkTransform` components nested under a parent with a `NetworkObject` component (i.e. network prefab) would not have their associated `GameObject`'s transform synchronized. (#2298) +- Fixed issue where `NetworkObject`s that failed to instantiate could cause the entire synchronization pipeline to be disrupted/halted for a connecting client. (#2298) +- Fixed issue where in-scene placed `NetworkObject`s nested under a `GameObject` would be added to the orphaned children list causing continual console warning log messages. (#2298) - Custom messages are now properly received by the local client when they're sent while running in host mode. (#2296) - Fixed issue where the host would receive more than one event completed notification when loading or unloading a scene only when no clients were connected. (#2292) - Fixed an issue in `UnityTransport` where an error would be logged if the 'Use Encryption' flag was enabled with a Relay configuration that used a secure protocol. (#2289) diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index f8d4dbd6a7..d9e8393f34 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -451,6 +451,33 @@ internal NetworkTransformState GetLastSentState() return m_LastSentState; } + /// + /// This is invoked when a new client joins (server and client sides) + /// Server Side: Serializes as if we were teleporting (everything is sent via NetworkTransformState) + /// Client Side: Adds the interpolated state which applies the NetworkTransformState as well + /// + protected override void OnSynchronize(ref BufferSerializer serializer) + { + // We don't need to synchronize NetworkTransforms that are on the same + // GameObject as the NetworkObject. + if (NetworkObject.gameObject == gameObject) + { + return; + } + var synchronizationState = new NetworkTransformState(); + if (serializer.IsWriter) + { + synchronizationState.IsTeleportingNextFrame = true; + ApplyTransformToNetworkStateWithInfo(ref synchronizationState, m_CachedNetworkManager.LocalTime.Time, transform); + synchronizationState.NetworkSerialize(serializer); + } + else + { + synchronizationState.NetworkSerialize(serializer); + AddInterpolatedState(synchronizationState); + } + } + /// /// This will try to send/commit the current transform delta states (if any) /// diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index daa8b30ad0..1a43947852 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -763,6 +763,14 @@ internal void MarkVariablesDirty(bool dirty) } } + /// + /// Synchronizes by setting only the NetworkVariable field values that the client has permission to read. + /// Note: This is only invoked when first synchronizing a NetworkBehaviour (i.e. late join or spawned NetworkObject) + /// + /// + /// When NetworkConfig.EnsureNetworkVariableLengthSafety is enabled each NetworkVariable field will be preceded + /// by the number of bytes written for that specific field. + /// internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClientId) { if (NetworkVariableFields.Count == 0) @@ -772,32 +780,47 @@ internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClie for (int j = 0; j < NetworkVariableFields.Count; j++) { - bool canClientRead = NetworkVariableFields[j].CanClientRead(targetClientId); - if (canClientRead) + if (NetworkVariableFields[j].CanClientRead(targetClientId)) { - var writePos = writer.Position; - // Note: This value can't be packed because we don't know how large it will be in advance - // we reserve space for it, then write the data, then come back and fill in the space - // to pack here, we'd have to write data to a temporary buffer and copy it in - which - // isn't worth possibly saving one byte if and only if the data is less than 63 bytes long... - // The way we do packing, any value > 63 in a ushort will use the full 2 bytes to represent. - writer.WriteValueSafe((ushort)0); - var startPos = writer.Position; - NetworkVariableFields[j].WriteField(writer); - var size = writer.Position - startPos; - writer.Seek(writePos); - writer.WriteValueSafe((ushort)size); - writer.Seek(startPos + size); + if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) + { + var writePos = writer.Position; + // Note: This value can't be packed because we don't know how large it will be in advance + // we reserve space for it, then write the data, then come back and fill in the space + // to pack here, we'd have to write data to a temporary buffer and copy it in - which + // isn't worth possibly saving one byte if and only if the data is less than 63 bytes long... + // The way we do packing, any value > 63 in a ushort will use the full 2 bytes to represent. + writer.WriteValueSafe((ushort)0); + var startPos = writer.Position; + NetworkVariableFields[j].WriteField(writer); + var size = writer.Position - startPos; + writer.Seek(writePos); + writer.WriteValueSafe((ushort)size); + writer.Seek(startPos + size); + } + else + { + NetworkVariableFields[j].WriteField(writer); + } } - else + else // Only if EnsureNetworkVariableLengthSafety, otherwise just skip + if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { writer.WriteValueSafe((ushort)0); } } } - internal void SetNetworkVariableData(FastBufferReader reader) + /// + /// Synchronizes by setting only the NetworkVariable field values that the client has permission to read. + /// Note: This is only invoked when first synchronizing a NetworkBehaviour (i.e. late join or spawned NetworkObject) + /// + /// + /// When NetworkConfig.EnsureNetworkVariableLengthSafety is enabled each NetworkVariable field will be preceded + /// by the number of bytes written for that specific field. + /// + internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId) { if (NetworkVariableFields.Count == 0) { @@ -806,13 +829,23 @@ internal void SetNetworkVariableData(FastBufferReader reader) for (int j = 0; j < NetworkVariableFields.Count; j++) { - reader.ReadValueSafe(out ushort varSize); - if (varSize == 0) + var varSize = (ushort)0; + var readStartPos = 0; + if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) + { + reader.ReadValueSafe(out varSize); + if (varSize == 0) + { + continue; + } + readStartPos = reader.Position; + } + else // If the client cannot read this field, then skip it + if (!NetworkVariableFields[j].CanClientRead(clientId)) { continue; } - var readStartPos = reader.Position; NetworkVariableFields[j].ReadField(reader); if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) @@ -849,6 +882,138 @@ protected NetworkObject GetNetworkObject(ulong networkId) return NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(networkId, out NetworkObject networkObject) ? networkObject : null; } + /// + /// Override this method if your derived NetworkBehaviour requires custom synchronization data. + /// Note: Use of this method is only for the initial client synchronization of NetworkBehaviours + /// and will increase the payload size for client synchronization and dynamically spawned + /// s. + /// + /// + /// When serializing (writing) this will be invoked during the client synchronization period and + /// when spawning new NetworkObjects. + /// When deserializing (reading), this will be invoked prior to the NetworkBehaviour's associated + /// NetworkObject being spawned. + /// + /// The serializer to use to read and write the data. + /// + /// Either BufferSerializerReader or BufferSerializerWriter, depending whether the serializer + /// is in read mode or write mode. + /// + protected virtual void OnSynchronize(ref BufferSerializer serializer) where T : IReaderWriter + { + + } + + /// + /// Internal method that determines if a NetworkBehaviour has additional synchronization data to + /// be synchronized when first instantiated prior to its associated NetworkObject being spawned. + /// + /// + /// This includes try-catch blocks to recover from exceptions that might occur and continue to + /// synchronize any remaining NetworkBehaviours. + /// + /// true if it wrote synchronization data and false if it did not + internal bool Synchronize(ref BufferSerializer serializer) where T : IReaderWriter + { + if (serializer.IsWriter) + { + // Get the writer to handle seeking and determining how many bytes were written + var writer = serializer.GetFastBufferWriter(); + // Save our position before we attempt to write anything so we can seek back to it (i.e. error occurs) + var positionBeforeWrite = writer.Position; + writer.WriteValueSafe(NetworkBehaviourId); + + // Save our position where we will write the final size being written so we can skip over it in the + // event an exception occurs when deserializing. + var sizePosition = writer.Position; + writer.WriteValueSafe((ushort)0); + + // Save our position before synchronizing to determine how much was written + var positionBeforeSynchronize = writer.Position; + var threwException = false; + try + { + OnSynchronize(ref serializer); + } + catch (Exception ex) + { + threwException = true; + if (NetworkManager.LogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"{name} threw an exception during synchronization serialization, this {nameof(NetworkBehaviour)} is being skipped and will not be synchronized!"); + if (NetworkManager.LogLevel == LogLevel.Developer) + { + NetworkLog.LogError($"{ex.Message}\n {ex.StackTrace}"); + } + } + } + var finalPosition = writer.Position; + + // If we wrote nothing then skip writing anything for this NetworkBehaviour + if (finalPosition == positionBeforeSynchronize || threwException) + { + writer.Seek(positionBeforeWrite); + return false; + } + else + { + // Write the number of bytes serialized to handle exceptions on the deserialization side + var bytesWritten = finalPosition - positionBeforeSynchronize; + writer.Seek(sizePosition); + writer.WriteValueSafe((ushort)bytesWritten); + writer.Seek(finalPosition); + } + return true; + } + else + { + var reader = serializer.GetFastBufferReader(); + // We will always read the expected byte count + reader.ReadValueSafe(out ushort expectedBytesToRead); + + // Save our position before we begin synchronization deserialization + var positionBeforeSynchronize = reader.Position; + var synchronizationError = false; + try + { + // Invoke synchronization + OnSynchronize(ref serializer); + } + catch (Exception ex) + { + if (NetworkManager.LogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"{name} threw an exception during synchronization deserialization, this {nameof(NetworkBehaviour)} is being skipped and will not be synchronized!"); + if (NetworkManager.LogLevel == LogLevel.Developer) + { + NetworkLog.LogError($"{ex.Message}\n {ex.StackTrace}"); + } + } + synchronizationError = true; + } + + var totalBytesRead = reader.Position - positionBeforeSynchronize; + if (totalBytesRead != expectedBytesToRead) + { + if (NetworkManager.LogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"{name} read {totalBytesRead} bytes but was expected to read {expectedBytesToRead} bytes during synchronization deserialization! This {nameof(NetworkBehaviour)} is being skipped and will not be synchronized!"); + } + synchronizationError = true; + } + + // Skip over the entry if deserialization fails + if (synchronizationError) + { + var skipToPosition = positionBeforeSynchronize + expectedBytesToRead; + reader.Seek(skipToPosition); + return false; + } + return true; + } + } + + /// /// Invoked when the the is attached to. /// NOTE: If you override this, you will want to always invoke this base class version of this diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 5bd7411bd0..122b6f1ee4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1008,13 +1008,17 @@ internal static void VerifyParentingStatus() } } } - internal void SetNetworkVariableData(FastBufferReader reader) + + /// + /// Only invoked during first synchronization of a NetworkObject (late join or newly spawned) + /// + internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId) { for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { var behaviour = ChildNetworkBehaviours[i]; behaviour.InitializeVariables(); - behaviour.SetNetworkVariableData(reader); + behaviour.SetNetworkVariableData(reader, clientId); } } @@ -1168,16 +1172,17 @@ public void Serialize(FastBufferWriter writer) } // In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their - // NetworkSceneHandle and GlobalObjectIdHash. Since each loaded scene has a unique - // handle, it provides us with a unique and persistent "scene prefab asset" instance. - // This is only set on in-scene placed NetworkObjects to reduce the over-all packet - // sizes for dynamically spawned NetworkObjects. + // NetworkSceneHandle and GlobalObjectIdHash. Client-side NetworkSceneManagers use + // this to locate their local instance of the in-scene placed NetworkObject instance. + // Only written for in-scene placed NetworkObjects. if (IsSceneObject) { writer.WriteValue(OwnerObject.GetSceneOriginHandle()); } - OwnerObject.WriteNetworkVariableData(writer, TargetClientId); + // Synchronize NetworkVariables and NetworkBehaviours + var bufferSerializer = new BufferSerializer(new BufferSerializerWriter(writer)); + OwnerObject.SynchronizeNetworkBehaviours(ref bufferSerializer, TargetClientId); } public void Deserialize(FastBufferReader reader) @@ -1213,10 +1218,9 @@ public void Deserialize(FastBufferReader reader) } // In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their - // NetworkSceneHandle and GlobalObjectIdHash. Since each loaded scene has a unique - // handle, it provides us with a unique and persistent "scene prefab asset" instance. - // Client-side NetworkSceneManagers use this to locate their local instance of the - // NetworkObject instance. + // NetworkSceneHandle and GlobalObjectIdHash. Client-side NetworkSceneManagers use + // this to locate their local instance of the in-scene placed NetworkObject instance. + // Only read for in-scene placed NetworkObjects if (IsSceneObject) { reader.ReadValue(out NetworkSceneHandle); @@ -1232,6 +1236,78 @@ internal void PostNetworkVariableWrite() } } + /// + /// Handles synchronizing NetworkVariables and custom synchronization data for NetworkBehaviours. + /// + /// + /// This is where we determine how much data is written after the associated NetworkObject in order to recover + /// from a failed instantiated NetworkObject without completely disrupting client synchronization. + /// + internal void SynchronizeNetworkBehaviours(ref BufferSerializer serializer, ulong targetClientId = 0) where T : IReaderWriter + { + if (serializer.IsWriter) + { + var writer = serializer.GetFastBufferWriter(); + var positionBeforeSynchronizing = writer.Position; + writer.WriteValueSafe((ushort)0); + var sizeToSkipCalculationPosition = writer.Position; + + // Synchronize NetworkVariables + WriteNetworkVariableData(writer, targetClientId); + // Reserve the NetworkBehaviour synchronization count position + var networkBehaviourCountPosition = writer.Position; + writer.WriteValueSafe((byte)0); + + // Parse through all NetworkBehaviours and any that return true + // had additional synchronization data written. + // (See notes for reading/deserialization below) + var synchronizationCount = (byte)0; + foreach (var childBehaviour in ChildNetworkBehaviours) + { + if (childBehaviour.Synchronize(ref serializer)) + { + synchronizationCount++; + } + } + + var currentPosition = writer.Position; + // Write the total number of bytes written for NetworkVariable and NetworkBehaviour + // synchronization. + writer.Seek(positionBeforeSynchronizing); + // We want the size of everything after our size to skip calculation position + var size = (ushort)(currentPosition - sizeToSkipCalculationPosition); + writer.WriteValueSafe(size); + // Write the number of NetworkBehaviours synchronized + writer.Seek(networkBehaviourCountPosition); + writer.WriteValueSafe(synchronizationCount); + // seek back to the position after writing NetworkVariable and NetworkBehaviour + // synchronization data. + writer.Seek(currentPosition); + } + else + { + var reader = serializer.GetFastBufferReader(); + + reader.ReadValueSafe(out ushort sizeOfSynchronizationData); + var seekToEndOfSynchData = reader.Position + sizeOfSynchronizationData; + // Apply the network variable synchronization data + SetNetworkVariableData(reader, targetClientId); + // Read the number of NetworkBehaviours to synchronize + reader.ReadValueSafe(out byte numberSynchronized); + var networkBehaviourId = (ushort)0; + + // If a NetworkBehaviour writes synchronization data, it will first + // write its NetworkBehaviourId so when deserializing the client-side + // can find the right NetworkBehaviour to deserialize the synchronization data. + for (int i = 0; i < numberSynchronized; i++) + { + serializer.SerializeValue(ref networkBehaviourId); + var networkBehaviour = GetNetworkBehaviourAtOrderIndex(networkBehaviourId); + networkBehaviour.Synchronize(ref serializer); + } + } + } + internal SceneObject GetMessageSceneObject(ulong targetClientId) { var obj = new SceneObject @@ -1318,10 +1394,10 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId) /// when the client is approved or during a scene transition /// /// Deserialized scene object data - /// reader for the NetworkVariable data + /// FastBufferReader for the NetworkVariable data /// NetworkManager instance /// optional to use NetworkObject deserialized - internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBufferReader variableData, NetworkManager networkManager) + internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBufferReader reader, NetworkManager networkManager) { //Attempt to create a local NetworkObject var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject(sceneObject); @@ -1329,18 +1405,36 @@ internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBuf if (networkObject == null) { // Log the error that the NetworkObject failed to construct - Debug.LogError($"Failed to spawn {nameof(NetworkObject)} for Hash {sceneObject.Hash}."); + if (networkManager.LogLevel <= LogLevel.Normal) + { + NetworkLog.LogError($"Failed to spawn {nameof(NetworkObject)} for Hash {sceneObject.Hash}."); + } - // If we failed to load this NetworkObject, then skip past the network variable data - variableData.ReadValueSafe(out ushort varSize); - variableData.Seek(variableData.Position + varSize); + try + { + // If we failed to load this NetworkObject, then skip past the Network Variable and (if any) synchronization data + reader.ReadValueSafe(out ushort networkBehaviourSynchronizationDataLength); + reader.Seek(reader.Position + networkBehaviourSynchronizationDataLength); + } + catch (Exception ex) + { + Debug.LogException(ex); + } // We have nothing left to do here. return null; } + // This will get set again when the NetworkObject is spawned locally, but we set it here ahead of spawning + // in order to be able to determine which NetworkVariables the client will be allowed to read. + networkObject.OwnerClientId = sceneObject.OwnerClientId; + + // Synchronize NetworkBehaviours + var bufferSerializer = new BufferSerializer(new BufferSerializerReader(reader)); + networkObject.SynchronizeNetworkBehaviours(ref bufferSerializer, networkManager.LocalClientId); + // Spawn the NetworkObject - networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, sceneObject, variableData, false); + networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, sceneObject, false); return networkObject; } diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs index 0b8936d332..8ccb84f725 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs @@ -444,13 +444,13 @@ internal void WriteSceneSynchronizationData(FastBufferWriter writer) int totalBytes = 0; // Write the number of NetworkObjects we are serializing - BytePacker.WriteValuePacked(writer, m_NetworkObjectsSync.Count); + writer.WriteValueSafe(m_NetworkObjectsSync.Count); + // Serialize all NetworkObjects that are spawned for (var i = 0; i < m_NetworkObjectsSync.Count; ++i) { var noStart = writer.Position; var sceneObject = m_NetworkObjectsSync[i].GetMessageSceneObject(TargetClientId); - BytePacker.WriteValuePacked(writer, m_NetworkObjectsSync[i].GetSceneOriginHandle()); sceneObject.Serialize(writer); var noStop = writer.Position; totalBytes += (int)(noStop - noStart); @@ -462,8 +462,8 @@ internal void WriteSceneSynchronizationData(FastBufferWriter writer) for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count; ++i) { var noStart = writer.Position; - BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle()); - BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash); + writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle()); + writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash); var noStop = writer.Position; totalBytes += (int)(noStop - noStart); } @@ -497,8 +497,6 @@ internal void SerializeScenePlacedObjects(FastBufferWriter writer) { if (keyValuePairBySceneHandle.Value.Observers.Contains(TargetClientId)) { - // Write our server relative scene handle for the NetworkObject being serialized - writer.WriteValueSafe(keyValuePairBySceneHandle.Key); // Serialize the NetworkObject var sceneObject = keyValuePairBySceneHandle.Value.GetMessageSceneObject(TargetClientId); sceneObject.Serialize(writer); @@ -512,8 +510,8 @@ internal void SerializeScenePlacedObjects(FastBufferWriter writer) // Write the scene handle and GlobalObjectIdHash value for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count; ++i) { - BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle()); - BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash); + writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle()); + writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash); } var tailPosition = writer.Position; @@ -625,13 +623,15 @@ internal void DeserializeScenePlacedObjects() for (ushort i = 0; i < newObjectsCount; i++) { - InternalBuffer.ReadValueSafe(out int sceneHandle); - // Set our relative scene to the NetworkObject - m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(sceneHandle); - - // Deserialize the NetworkObject var sceneObject = new NetworkObject.SceneObject(); sceneObject.Deserialize(InternalBuffer); + + if (sceneObject.IsSceneObject) + { + // Set our relative scene to the NetworkObject + m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(sceneObject.NetworkSceneHandle); + } + NetworkObject.AddSceneObject(sceneObject, InternalBuffer, m_NetworkManager); } // Now deserialize the despawned in-scene placed NetworkObjects list (if any) @@ -772,8 +772,8 @@ private void DeserializeDespawnedInScenePlacedNetworkObjects() for (int i = 0; i < despawnedObjectsCount; i++) { // We just need to get the scene - ByteUnpacker.ReadValuePacked(InternalBuffer, out int networkSceneHandle); - ByteUnpacker.ReadValuePacked(InternalBuffer, out uint globalObjectIdHash); + InternalBuffer.ReadValueSafe(out int networkSceneHandle); + InternalBuffer.ReadValueSafe(out uint globalObjectIdHash); var sceneRelativeNetworkObjects = new Dictionary(); if (!sceneCache.ContainsKey(networkSceneHandle)) { @@ -848,24 +848,26 @@ internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager) try { // Process all spawned NetworkObjects for this network session - ByteUnpacker.ReadValuePacked(InternalBuffer, out int newObjectsCount); - - + InternalBuffer.ReadValueSafe(out int newObjectsCount); for (int i = 0; i < newObjectsCount; i++) { - // We want to make sure for each NetworkObject we have the appropriate scene selected as the scene that is - // currently being synchronized. This assures in-scene placed NetworkObjects will use the right NetworkObject - // from the list of populated - ByteUnpacker.ReadValuePacked(InternalBuffer, out int handle); - m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(handle); - var sceneObject = new NetworkObject.SceneObject(); sceneObject.Deserialize(InternalBuffer); + // If the sceneObject is in-scene placed, then set the scene being synchronized + if (sceneObject.IsSceneObject) + { + m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(sceneObject.NetworkSceneHandle); + } var spawnedNetworkObject = NetworkObject.AddSceneObject(sceneObject, InternalBuffer, networkManager); - if (!m_NetworkObjectsSync.Contains(spawnedNetworkObject)) + + // If we failed to deserialize the NetowrkObject then don't add null to the list + if (spawnedNetworkObject != null) { - m_NetworkObjectsSync.Add(spawnedNetworkObject); + if (!m_NetworkObjectsSync.Contains(spawnedNetworkObject)) + { + m_NetworkObjectsSync.Add(spawnedNetworkObject); + } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 945d6247e1..470317ee60 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -454,8 +454,14 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO if (sceneObject.HasParent) { - // Go ahead and set network parenting properties - networkObject.SetNetworkParenting(parentNetworkId, worldPositionStays); + // Go ahead and set network parenting properties, if the latest parent is not set then pass in null + // (we always want to set worldPositionStays) + ulong? parentId = null; + if (sceneObject.IsLatestParentSet) + { + parentId = parentNetworkId; + } + networkObject.SetNetworkParenting(parentId, worldPositionStays); } @@ -495,8 +501,7 @@ internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong netwo } // Ran on both server and client - internal void SpawnNetworkObjectLocally(NetworkObject networkObject, in NetworkObject.SceneObject sceneObject, - FastBufferReader variableData, bool destroyWithScene) + internal void SpawnNetworkObjectLocally(NetworkObject networkObject, in NetworkObject.SceneObject sceneObject, bool destroyWithScene) { if (networkObject == null) { @@ -508,8 +513,6 @@ internal void SpawnNetworkObjectLocally(NetworkObject networkObject, in NetworkO throw new SpawnStateException("Object is already spawned"); } - networkObject.SetNetworkVariableData(variableData); - SpawnNetworkObjectLocallyCommon(networkObject, sceneObject.NetworkObjectId, sceneObject.IsSceneObject, sceneObject.IsPlayerObject, sceneObject.OwnerClientId, destroyWithScene); } diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index e91eb28a1e..c924678fc4 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -431,8 +431,14 @@ private void ClientNetworkManagerPostStart(NetworkManager networkManager) networkManager.name = $"NetworkManager - Client - {networkManager.LocalClientId}"; Assert.NotNull(networkManager.LocalClient.PlayerObject, $"{nameof(StartServerAndClients)} detected that client {networkManager.LocalClientId} does not have an assigned player NetworkObject!"); + // Go ahead and create an entry for this new client + if (!m_PlayerNetworkObjects.ContainsKey(networkManager.LocalClientId)) + { + m_PlayerNetworkObjects.Add(networkManager.LocalClientId, new Dictionary()); + } + // Get all player instances for the current client NetworkManager instance - var clientPlayerClones = Object.FindObjectsOfType().Where((c) => c.IsPlayerObject && c.OwnerClientId == networkManager.LocalClientId); + var clientPlayerClones = Object.FindObjectsOfType().Where((c) => c.IsPlayerObject && c.OwnerClientId == networkManager.LocalClientId).ToList(); // Add this player instance to each client player entry foreach (var playerNetworkObject in clientPlayerClones) { @@ -446,6 +452,16 @@ private void ClientNetworkManagerPostStart(NetworkManager networkManager) m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(networkManager.LocalClientId, playerNetworkObject); } } + + // For late joining clients, add the remaining (if any) cloned versions of each client's player + clientPlayerClones = Object.FindObjectsOfType().Where((c) => c.IsPlayerObject && c.NetworkManager == networkManager).ToList(); + foreach (var playerNetworkObject in clientPlayerClones) + { + if (!m_PlayerNetworkObjects[networkManager.LocalClientId].ContainsKey(playerNetworkObject.OwnerClientId)) + { + m_PlayerNetworkObjects[networkManager.LocalClientId].Add(playerNetworkObject.OwnerClientId, playerNetworkObject); + } + } } protected void ClientNetworkManagerPostStartInit() diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSceneSerializationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSceneSerializationTests.cs deleted file mode 100644 index 8781faf2bd..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSceneSerializationTests.cs +++ /dev/null @@ -1,233 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; -using UnityEngine.SceneManagement; -using NUnit.Framework; -using Unity.Collections; -using Unity.Netcode.TestHelpers.Runtime; - -namespace Unity.Netcode.RuntimeTests -{ - public class NetworkObjectSceneSerializationTests - { - - /// - /// The purpose behind this test is to assure that in-scene NetworkObjects - /// that are serialized into a single stream (approval or switch scene this happens) - /// will continue to be processed even if one of the NetworkObjects is invalid. - /// - [Test] - public void NetworkObjectSceneSerializationFailure() - { - var networkObjectsToTest = new List(); - - var writer = new FastBufferWriter(1300, Allocator.Temp, 4096000); - var invalidNetworkObjectOffsets = new List(); - var invalidNetworkObjectIdCount = new List(); - var invalidNetworkObjects = new List(); - var invalidNetworkObjectFrequency = 3; - using (writer) - { - // Construct 50 NetworkObjects - for (int i = 0; i < 50; i++) - { - // Inject an invalid NetworkObject every [invalidNetworkObjectFrequency] entry - if ((i % invalidNetworkObjectFrequency) == 0) - { - // Create the invalid NetworkObject - var gameObject = new GameObject($"InvalidTestObject{i}"); - - Assert.IsNotNull(gameObject); - - var networkObject = gameObject.AddComponent(); - - Assert.IsNotNull(networkObject); - - var networkVariableComponent = gameObject.AddComponent(); - Assert.IsNotNull(networkVariableComponent); - - // Add invalid NetworkObject's starting position before serialization to handle trapping for the Debug.LogError message - // that we know will be thrown - invalidNetworkObjectOffsets.Add(writer.Position); - - networkObject.GlobalObjectIdHash = (uint)(i); - invalidNetworkObjectIdCount.Add(i); - - invalidNetworkObjects.Add(gameObject); - - writer.WriteValueSafe((int)networkObject.GetSceneOriginHandle()); - // Serialize the invalid NetworkObject - var sceneObject = networkObject.GetMessageSceneObject(0); - var prePosition = writer.Position; - sceneObject.Serialize(writer); - - Debug.Log( - $"Invalid {nameof(NetworkObject)} Size {writer.Position - prePosition}"); - - // Now adjust how frequent we will inject invalid NetworkObjects - invalidNetworkObjectFrequency = Random.Range(2, 5); - - } - else - { - // Create a valid NetworkObject - var gameObject = new GameObject($"TestObject{i}"); - - Assert.IsNotNull(gameObject); - - var networkObject = gameObject.AddComponent(); - - var networkVariableComponent = gameObject.AddComponent(); - Assert.IsNotNull(networkVariableComponent); - - Assert.IsNotNull(networkObject); - - networkObject.GlobalObjectIdHash = (uint)(i + 4096); - - networkObjectsToTest.Add(gameObject); - - writer.WriteValueSafe(networkObject.GetSceneOriginHandle()); - - // Handle populating the scenes loaded list - var scene = networkObject.gameObject.scene; - - if (!NetworkManagerHelper.NetworkManagerObject.SceneManager.ScenesLoaded.ContainsKey( - scene.handle)) - { - NetworkManagerHelper.NetworkManagerObject.SceneManager.ScenesLoaded - .Add(scene.handle, scene); - } - var handle = networkObject.GetSceneOriginHandle(); - // Since this is a unit test, we will fake the server to client handle lookup by just adding the same handle key and value - if (!NetworkManagerHelper.NetworkManagerObject.SceneManager.ServerSceneHandleToClientSceneHandle - .ContainsKey(handle)) - { - NetworkManagerHelper.NetworkManagerObject.SceneManager.ServerSceneHandleToClientSceneHandle - .Add(handle, handle); - } - - // Serialize the valid NetworkObject - var sceneObject = networkObject.GetMessageSceneObject(0); - sceneObject.Serialize(writer); - - if (!NetworkManagerHelper.NetworkManagerObject.SceneManager.ScenePlacedObjects.ContainsKey( - networkObject.GlobalObjectIdHash)) - { - NetworkManagerHelper.NetworkManagerObject.SceneManager.ScenePlacedObjects.Add( - networkObject.GlobalObjectIdHash, new Dictionary()); - } - - // Add this valid NetworkObject into the ScenePlacedObjects list - NetworkManagerHelper.NetworkManagerObject.SceneManager - .ScenePlacedObjects[networkObject.GlobalObjectIdHash] - .Add(SceneManager.GetActiveScene().handle, networkObject); - } - } - - var totalBufferSize = writer.Position; - - var reader = new FastBufferReader(writer, Allocator.Temp); - using (reader) - { - - var networkObjectsDeSerialized = new List(); - var currentLogLevel = NetworkManager.Singleton.LogLevel; - var invalidNetworkObjectCount = 0; - while (reader.Position != totalBufferSize) - { - // If we reach the point where we expect it to fail, then make sure we let TestRunner know it should expect this log error message - if (invalidNetworkObjectOffsets.Count > 0 && - reader.Position == invalidNetworkObjectOffsets[0]) - { - invalidNetworkObjectOffsets.RemoveAt(0); - - // Turn off Network Logging to avoid other errors that we know will happen after the below LogAssert.Expect message occurs. - NetworkManager.Singleton.LogLevel = LogLevel.Nothing; - - // Trap for this specific error message so we don't make Test Runner think we failed (it will fail on Debug.LogError) - UnityEngine.TestTools.LogAssert.Expect(LogType.Error, - $"Failed to spawn {nameof(NetworkObject)} for Hash {invalidNetworkObjectIdCount[invalidNetworkObjectCount]}."); - - invalidNetworkObjectCount++; - } - - - reader.ReadValueSafe(out int handle); - NetworkManagerHelper.NetworkManagerObject.SceneManager.SetTheSceneBeingSynchronized(handle); - var sceneObject = new NetworkObject.SceneObject(); - sceneObject.Deserialize(reader); - - var deserializedNetworkObject = NetworkObject.AddSceneObject(sceneObject, reader, - NetworkManagerHelper.NetworkManagerObject); - if (deserializedNetworkObject != null) - { - networkObjectsDeSerialized.Add(deserializedNetworkObject); - } - else - { - // Under this condition, we are expecting null (i.e. no NetworkObject instantiated) - // and will set our log level back to the original value to assure the valid NetworkObjects - // aren't causing any log Errors to occur - NetworkManager.Singleton.LogLevel = currentLogLevel; - } - } - - // Now validate all NetworkObjects returned against the original NetworkObjects we created - // after they validate, destroy the objects - foreach (var entry in networkObjectsToTest) - { - var entryNetworkObject = entry.GetComponent(); - Assert.IsTrue(networkObjectsDeSerialized.Contains(entryNetworkObject)); - Object.Destroy(entry); - } - } - } - - // Destroy the invalid network objects - foreach (var entry in invalidNetworkObjects) - { - Object.Destroy(entry); - } - } - - [SetUp] - public void Setup() - { - // Create, instantiate, and host - NetworkManagerHelper.StartNetworkManager(out NetworkManager networkManager, NetworkManagerHelper.NetworkManagerOperatingMode.None); - networkManager.NetworkConfig.EnableSceneManagement = true; - networkManager.StartHost(); - - } - - [TearDown] - public void TearDown() - { - // Stop, shutdown, and destroy - NetworkManagerHelper.ShutdownNetworkManager(); - } - } - - /// - /// A simple test class that will provide varying NetworkBuffer stream sizes - /// when the NetworkVariable is serialized - /// - public class NetworkBehaviourWithNetworkVariables : NetworkBehaviour - { - private const uint k_MinDataBlocks = 1; - private const uint k_MaxDataBlocks = 64; - - public NetworkList NetworkVariableData; - - private void Awake() - { - var dataBlocksAssigned = new List(); - var numberDataBlocks = Random.Range(k_MinDataBlocks, k_MaxDataBlocks); - for (var i = 0; i < numberDataBlocks; i++) - { - dataBlocksAssigned.Add((ulong)Random.Range(0.0f, float.MaxValue)); - } - - NetworkVariableData = new NetworkList(dataBlocksAssigned); - } - } -} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs new file mode 100644 index 0000000000..7f58283c27 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs @@ -0,0 +1,624 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.TestTools; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using Random = UnityEngine.Random; + +namespace Unity.Netcode.RuntimeTests +{ + [TestFixture(VariableLengthSafety.DisableNetVarSafety, HostOrServer.Host)] + [TestFixture(VariableLengthSafety.EnabledNetVarSafety, HostOrServer.Host)] + [TestFixture(VariableLengthSafety.DisableNetVarSafety, HostOrServer.Server)] + [TestFixture(VariableLengthSafety.EnabledNetVarSafety, HostOrServer.Server)] + public class NetworkObjectSynchronizationTests : NetcodeIntegrationTest + { + private const int k_NumberToSpawn = 30; + protected override int NumberOfClients => 0; + + private GameObject m_NetworkPrefab; + private GameObject m_InValidNetworkPrefab; + private GameObject m_SynchronizationPrefab; + private GameObject m_OnSynchronizePrefab; + private VariableLengthSafety m_VariableLengthSafety; + + private LogLevel m_CurrentLogLevel; + + public enum VariableLengthSafety + { + DisableNetVarSafety, + EnabledNetVarSafety, + } + + public NetworkObjectSynchronizationTests(VariableLengthSafety variableLengthSafety, HostOrServer hostOrServer) + { + m_VariableLengthSafety = variableLengthSafety; + m_UseHost = hostOrServer == HostOrServer.Host; + } + + protected override void OnCreatePlayerPrefab() + { + m_PlayerPrefab.AddComponent(); + base.OnCreatePlayerPrefab(); + } + + protected override void OnServerAndClientsCreated() + { + + // Set the NetworkVariable Safety Check setting + m_ServerNetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety = m_VariableLengthSafety == VariableLengthSafety.EnabledNetVarSafety; + + // Ignore the errors generated during this test (they are expected) + m_ServerNetworkManager.LogLevel = LogLevel.Nothing; + + // Disable forcing the same prefabs to avoid failed connections + m_ServerNetworkManager.NetworkConfig.ForceSamePrefabs = false; + + // Create the valid network prefab + m_NetworkPrefab = CreateNetworkObjectPrefab("ValidObject"); + m_NetworkPrefab.AddComponent(); + + // Create the invalid network prefab (that will fail on client side) + m_InValidNetworkPrefab = CreateNetworkObjectPrefab("InvalidObject"); + m_InValidNetworkPrefab.AddComponent(); + + // Create the synchronization network prefab (some pass and some fail) + m_SynchronizationPrefab = CreateNetworkObjectPrefab("SyncObject"); + m_SynchronizationPrefab.AddComponent(); + m_SynchronizationPrefab.AddComponent(); + + m_OnSynchronizePrefab = CreateNetworkObjectPrefab("OnSyncObject"); + m_OnSynchronizePrefab.AddComponent(); + + base.OnServerAndClientsCreated(); + } + + protected override void OnNewClientCreated(NetworkManager networkManager) + { + networkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab; + networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety = m_VariableLengthSafety == VariableLengthSafety.EnabledNetVarSafety; + foreach (var networkPrefab in m_ServerNetworkManager.NetworkConfig.NetworkPrefabs) + { + // To simulate a failure, we exclude the m_InValidNetworkPrefab from the connecting + // client's side. + if (networkPrefab.Prefab.name != m_InValidNetworkPrefab.name) + { + networkManager.NetworkConfig.NetworkPrefabs.Add(networkPrefab); + } + } + // Disable forcing the same prefabs to avoid failed connections + networkManager.NetworkConfig.ForceSamePrefabs = false; + networkManager.LogLevel = m_CurrentLogLevel; + base.OnNewClientCreated(networkManager); + } + + [UnityTest] + public IEnumerator NetworkObjectDeserializationFailure() + { + m_CurrentLogLevel = LogLevel.Nothing; + var validSpawnedNetworkObjects = new List(); + NetworkBehaviourWithNetworkVariables.ResetSpawnCount(); + + // Spawn NetworkObjects on the server side with half of them being the + // invalid network prefabs to simulate NetworkObject synchronization failure + for (int i = 0; i < k_NumberToSpawn; i++) + { + if (i % 2 == 0) + { + SpawnObject(m_InValidNetworkPrefab, m_ServerNetworkManager); + } + else + { + // Keep track of the prefabs that should successfully spawn on the client side + validSpawnedNetworkObjects.Add(SpawnObject(m_NetworkPrefab, m_ServerNetworkManager)); + } + } + + // Assure the server-side spawned all NetworkObjects + yield return WaitForConditionOrTimeOut(() => NetworkBehaviourWithNetworkVariables.ServerSpawnCount == k_NumberToSpawn); + + // Now spawn and connect a client that will fail to spawn half of the NetworkObjects spawned + yield return CreateAndStartNewClient(); + + if (m_UseHost) + { + var serverSideClientPlayerComponent = m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][m_ClientNetworkManagers[0].LocalClientId].GetComponent(); + var serverSideHostPlayerComponent = m_ServerNetworkManager.LocalClient.PlayerObject.GetComponent(); + var clientSidePlayerComponent = m_ClientNetworkManagers[0].LocalClient.PlayerObject.GetComponent(); + var clientSideHostPlayerComponent = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][m_ServerNetworkManager.LocalClientId].GetComponent(); + + // Validate that the client side player values match the server side value of the client's player + Assert.IsTrue(serverSideClientPlayerComponent.NetworkVariableData1.Value == clientSidePlayerComponent.NetworkVariableData1.Value, + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData1)}][Client Player] Client side value ({serverSideClientPlayerComponent.NetworkVariableData1.Value})" + + $" does not equal the server side value ({serverSideClientPlayerComponent.NetworkVariableData1.Value})!"); + Assert.IsTrue(serverSideClientPlayerComponent.NetworkVariableData2.Value == clientSidePlayerComponent.NetworkVariableData2.Value, + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData2)}][Client Player] Client side value ({serverSideClientPlayerComponent.NetworkVariableData2.Value})" + + $" does not equal the server side value ({serverSideClientPlayerComponent.NetworkVariableData2.Value})!"); + Assert.IsTrue(serverSideClientPlayerComponent.NetworkVariableData3.Value == clientSidePlayerComponent.NetworkVariableData3.Value, + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData3)}][Client Player] Client side value ({serverSideClientPlayerComponent.NetworkVariableData3.Value})" + + $" does not equal the server side value ({serverSideClientPlayerComponent.NetworkVariableData3.Value})!"); + Assert.IsTrue(serverSideClientPlayerComponent.NetworkVariableData4.Value == clientSidePlayerComponent.NetworkVariableData4.Value, + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData4)}][Client Player] Client side value ({serverSideClientPlayerComponent.NetworkVariableData4.Value})" + + $" does not equal the server side value ({serverSideClientPlayerComponent.NetworkVariableData4.Value})!"); + + + // Validate that only the 2nd and 4th NetworkVariable on the client side instance of the host's player is the same and the other two do not match + // (i.e. NetworkVariables owned by the server should not get synchronized on client) + Assert.IsTrue(serverSideHostPlayerComponent.NetworkVariableData1.Value != clientSideHostPlayerComponent.NetworkVariableData1.Value, + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData1)}][Host Player] Client side value ({serverSideHostPlayerComponent.NetworkVariableData1.Value})" + + $" should not be equal to the server side value ({clientSideHostPlayerComponent.NetworkVariableData1.Value})!"); + Assert.IsTrue(serverSideHostPlayerComponent.NetworkVariableData2.Value == clientSideHostPlayerComponent.NetworkVariableData2.Value, + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData2)}][Host Player] Client side value ({serverSideHostPlayerComponent.NetworkVariableData2.Value})" + + $" does not equal the server side value ({clientSideHostPlayerComponent.NetworkVariableData2.Value})!"); + Assert.IsTrue(serverSideHostPlayerComponent.NetworkVariableData3.Value != clientSideHostPlayerComponent.NetworkVariableData3.Value, + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData3)}][Host Player] Client side value ({serverSideHostPlayerComponent.NetworkVariableData3.Value})" + + $" should not be equal to the server side value ({clientSideHostPlayerComponent.NetworkVariableData3.Value})!"); + Assert.IsTrue(serverSideHostPlayerComponent.NetworkVariableData4.Value == clientSideHostPlayerComponent.NetworkVariableData4.Value, + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData4)}][Host Player] Client side value ({serverSideHostPlayerComponent.NetworkVariableData4.Value})" + + $" does not equal the server side value ({clientSideHostPlayerComponent.NetworkVariableData4.Value})!"); + } + else + { + // Spawn and connect another client when running as a server + yield return CreateAndStartNewClient(); + yield return WaitForConditionOrTimeOut(() => m_PlayerNetworkObjects[2].Count > 1); + AssertOnTimeout($"Timed out waiting for second client to have access to the first client's cloned player object!"); + + var clientSide1PlayerComponent = m_ClientNetworkManagers[0].LocalClient.PlayerObject.GetComponent(); + var clientSide2Player1Clone = m_PlayerNetworkObjects[2][clientSide1PlayerComponent.OwnerClientId].GetComponent(); + var clientOneId = clientSide1PlayerComponent.OwnerClientId; + + var clientSide2PlayerComponent = m_ClientNetworkManagers[1].LocalClient.PlayerObject.GetComponent(); + var clientSide1Player2Clone = m_PlayerNetworkObjects[1][clientSide2PlayerComponent.OwnerClientId].GetComponent(); + var clientTwoId = clientSide2PlayerComponent.OwnerClientId; + + // Validate that client one's 2nd and 4th NetworkVariables for the local and clone instances match and the other two do not + Assert.IsTrue(clientSide1PlayerComponent.NetworkVariableData1.Value != clientSide2Player1Clone.NetworkVariableData1.Value, + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData1)}][Player-{clientOneId}] Client-{clientOneId} value ({clientSide1PlayerComponent.NetworkVariableData1.Value})" + + $" should not be equal to Client-{clientTwoId}'s clone side value ({clientSide2Player1Clone.NetworkVariableData1.Value})!"); + + Assert.IsTrue(clientSide1PlayerComponent.NetworkVariableData2.Value == clientSide2Player1Clone.NetworkVariableData2.Value, + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData2)}][Player-{clientOneId}] Client-{clientOneId} value ({clientSide1PlayerComponent.NetworkVariableData2.Value})" + + $" does not equal Client-{clientTwoId}'s clone side value ({clientSide2Player1Clone.NetworkVariableData2.Value})!"); + + Assert.IsTrue(clientSide1PlayerComponent.NetworkVariableData3.Value != clientSide2Player1Clone.NetworkVariableData3.Value, + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData3)}][Player-{clientOneId}] Client-{clientOneId} value ({clientSide1PlayerComponent.NetworkVariableData3.Value})" + + $" should not be equal to Client-{clientTwoId}'s clone side value ({clientSide2Player1Clone.NetworkVariableData3.Value})!"); + + Assert.IsTrue(clientSide1PlayerComponent.NetworkVariableData4.Value == clientSide2Player1Clone.NetworkVariableData4.Value, + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData4)}][Player-{clientOneId}] Client-{clientOneId} value ({clientSide1PlayerComponent.NetworkVariableData4.Value})" + + $" does not equal Client-{clientTwoId}'s clone side value ({clientSide2Player1Clone.NetworkVariableData4.Value})!"); + + + // Validate that client two's 2nd and 4th NetworkVariables for the local and clone instances match and the other two do not + Assert.IsTrue(clientSide2PlayerComponent.NetworkVariableData1.Value != clientSide1Player2Clone.NetworkVariableData1.Value, + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData1)}][Player-{clientTwoId}] Client-{clientTwoId} value ({clientSide2PlayerComponent.NetworkVariableData1.Value})" + + $" should not be equal to Client-{clientOneId}'s clone side value ({clientSide1Player2Clone.NetworkVariableData1.Value})!"); + + Assert.IsTrue(clientSide2PlayerComponent.NetworkVariableData2.Value == clientSide1Player2Clone.NetworkVariableData2.Value, + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData2)}][Player-{clientTwoId}] Client-{clientTwoId} value ({clientSide2PlayerComponent.NetworkVariableData2.Value})" + + $" does not equal Client-{clientOneId}'s clone side value ({clientSide1Player2Clone.NetworkVariableData2.Value})!"); + + Assert.IsTrue(clientSide2PlayerComponent.NetworkVariableData3.Value != clientSide1Player2Clone.NetworkVariableData3.Value, + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData3)}][Player-{clientTwoId}] Client-{clientTwoId} value ({clientSide2PlayerComponent.NetworkVariableData3.Value})" + + $" should not be equal to Client-{clientOneId}'s clone side value ({clientSide1Player2Clone.NetworkVariableData3.Value})!"); + + Assert.IsTrue(clientSide2PlayerComponent.NetworkVariableData4.Value == clientSide1Player2Clone.NetworkVariableData4.Value, + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData4)}][Player-{clientTwoId}] Client-{clientTwoId} value ({clientSide2PlayerComponent.NetworkVariableData4.Value})" + + $" does not equal Client-{clientOneId}'s clone side value ({clientSide1Player2Clone.NetworkVariableData4.Value})!"); + } + + // Now validate all of the NetworkVariable values match to assure everything synchronized properly + foreach (var spawnedObject in validSpawnedNetworkObjects) + { + foreach (var clientNetworkManager in m_ClientNetworkManagers) + { + //Validate that the connected client has spawned all of the instances that shouldn't have failed. + var clientSideNetworkObjects = s_GlobalNetworkObjects[clientNetworkManager.LocalClientId]; + + Assert.IsTrue(NetworkBehaviourWithNetworkVariables.ClientSpawnCount[clientNetworkManager.LocalClientId] == validSpawnedNetworkObjects.Count, $"Client-{clientNetworkManager.LocalClientId} spawned " + + $"({NetworkBehaviourWithNetworkVariables.ClientSpawnCount}) {nameof(NetworkObject)}s but the expected number of {nameof(NetworkObject)}s should have been ({validSpawnedNetworkObjects.Count})!"); + + var spawnedNetworkObject = spawnedObject.GetComponent(); + Assert.IsTrue(clientSideNetworkObjects.ContainsKey(spawnedNetworkObject.NetworkObjectId), $"Failed to find valid spawned {nameof(NetworkObject)} on the client-side with a " + + $"{nameof(NetworkObject.NetworkObjectId)} of {spawnedNetworkObject.NetworkObjectId}"); + + var clientSideObject = clientSideNetworkObjects[spawnedNetworkObject.NetworkObjectId]; + Assert.IsTrue(clientSideObject.NetworkManager == clientNetworkManager, $"Client-side object {clientSideObject}'s {nameof(NetworkManager)} is not valid!"); + + ValidateNetworkBehaviourWithNetworkVariables(spawnedNetworkObject, clientSideObject); + } + } + } + + private void ValidateNetworkBehaviourWithNetworkVariables(NetworkObject serverSideNetworkObject, NetworkObject clientSideNetworkObject) + { + var serverSideComponent = serverSideNetworkObject.GetComponent(); + var clientSideComponent = clientSideNetworkObject.GetComponent(); + + string netVarName1 = nameof(NetworkBehaviourWithNetworkVariables.NetworkVariableData1); + string netVarName2 = nameof(NetworkBehaviourWithNetworkVariables.NetworkVariableData1); + string netVarName3 = nameof(NetworkBehaviourWithNetworkVariables.NetworkVariableData1); + string netVarName4 = nameof(NetworkBehaviourWithNetworkVariables.NetworkVariableData1); + + Assert.IsTrue(serverSideComponent.NetworkVariableData1.Count == clientSideComponent.NetworkVariableData1.Count, $"[{serverSideComponent.name}:{netVarName1}] Server side {nameof(NetworkList)} " + + $"count ({serverSideComponent.NetworkVariableData1.Count}) does not match the client side {nameof(NetworkList)} count ({clientSideComponent.NetworkVariableData1.Count})!"); + + for (int i = 0; i < serverSideComponent.NetworkVariableData1.Count; i++) + { + Assert.IsTrue(serverSideComponent.NetworkVariableData1[i] == clientSideComponent.NetworkVariableData1[i], $"[{serverSideComponent.name}:{netVarName1}][Index:{i}] Server side instance value " + + $"({serverSideComponent.NetworkVariableData1[i]}) does not match the client side instance value ({clientSideComponent.NetworkVariableData1[i]})!"); + } + + Assert.IsTrue(serverSideComponent.NetworkVariableData2.Value == clientSideComponent.NetworkVariableData2.Value, $"[{serverSideComponent.name}:{netVarName2}] Server side instance value ({serverSideComponent.NetworkVariableData2.Value}) " + + $"does not match the client side instance value ({clientSideComponent.NetworkVariableData2.Value})!"); + Assert.IsTrue(serverSideComponent.NetworkVariableData3.Value == clientSideComponent.NetworkVariableData3.Value, $"[{serverSideComponent.name}:{netVarName3}] Server side instance value ({serverSideComponent.NetworkVariableData3.Value}) " + + $"does not match the client side instance value ({clientSideComponent.NetworkVariableData3.Value})!"); + Assert.IsTrue(serverSideComponent.NetworkVariableData4.Value == clientSideComponent.NetworkVariableData4.Value, $"[{serverSideComponent.name}:{netVarName4}] Server side instance value ({serverSideComponent.NetworkVariableData4.Value}) " + + $"does not match the client side instance value ({clientSideComponent.NetworkVariableData4.Value})!"); + } + + /// + /// This validates that when a NetworkBehaviour fails serialization or deserialization during synchronizations that other NetworkBehaviours + /// will still be initialized properly + /// + [UnityTest] + public IEnumerator NetworkBehaviourSynchronization() + { + m_ServerNetworkManager.LogLevel = LogLevel.Normal; + m_CurrentLogLevel = LogLevel.Normal; + NetworkBehaviourSynchronizeFailureComponent.ResetBehaviour(); + + var spawnedObjectList = new List(); + var numberOfObjectsToSpawn = NetworkBehaviourSynchronizeFailureComponent.NumberOfFailureTypes * 4; + // Spawn 11 more NetworkObjects where there should be 4 of each failure type + for (int i = 0; i < numberOfObjectsToSpawn; i++) + { + var synchronizationObject = SpawnObject(m_SynchronizationPrefab, m_ServerNetworkManager); + var synchronizationBehaviour = synchronizationObject.GetComponent(); + synchronizationBehaviour.AssignNextFailureType(); + spawnedObjectList.Add(synchronizationObject); + } + + // Now spawn and connect a client that will fail to spawn half of the NetworkObjects spawned + yield return CreateAndStartNewClient(); + + // Validate that when a NetworkBehaviour fails to synchronize and is skipped over it does not + // impact the rest of the NetworkBehaviours. + var clientSideNetworkObjects = s_GlobalNetworkObjects[m_ClientNetworkManagers[0].LocalClientId]; + foreach (var spawnedObject in spawnedObjectList) + { + var serverSideSpawnedNetworkObject = spawnedObject.GetComponent(); + var clientSideObject = clientSideNetworkObjects[serverSideSpawnedNetworkObject.NetworkObjectId]; + var clientSideSpawnedNetworkObject = clientSideObject.GetComponent(); + + ValidateNetworkBehaviourWithNetworkVariables(serverSideSpawnedNetworkObject, clientSideSpawnedNetworkObject); + } + } + + /// + /// A basic validation for the NetworkBehaviour.OnSynchronize method + /// + [UnityTest] + public IEnumerator NetworkBehaviourOnSynchronize() + { + var serverSideInstance = SpawnObject(m_OnSynchronizePrefab, m_ServerNetworkManager).GetComponent(); + + // Now spawn and connect a client that will have custom serialized data applied during the client synchronization process. + yield return CreateAndStartNewClient(); + + var clientSideNetworkObjects = s_GlobalNetworkObjects[m_ClientNetworkManagers[0].LocalClientId]; + var clientSideInstance = clientSideNetworkObjects[serverSideInstance.NetworkObjectId].GetComponent(); + + // Validate the values match + Assert.IsTrue(serverSideInstance.CustomSerializationData.Value1 == clientSideInstance.CustomSerializationData.Value1, $"Client-side instance Value1 ({serverSideInstance.CustomSerializationData.Value1}) does not equal server-side instance Value1 ({clientSideInstance.CustomSerializationData.Value1})"); + Assert.IsTrue(serverSideInstance.CustomSerializationData.Value2 == clientSideInstance.CustomSerializationData.Value2, $"Client-side instance Value1 ({serverSideInstance.CustomSerializationData.Value2}) does not equal server-side instance Value1 ({clientSideInstance.CustomSerializationData.Value2})"); + Assert.IsTrue(serverSideInstance.CustomSerializationData.Value3 == clientSideInstance.CustomSerializationData.Value3, $"Client-side instance Value1 ({serverSideInstance.CustomSerializationData.Value3}) does not equal server-side instance Value1 ({clientSideInstance.CustomSerializationData.Value3})"); + Assert.IsTrue(serverSideInstance.CustomSerializationData.Value4 == clientSideInstance.CustomSerializationData.Value4, $"Client-side instance Value1 ({serverSideInstance.CustomSerializationData.Value4}) does not equal server-side instance Value1 ({clientSideInstance.CustomSerializationData.Value4})"); + } + } + + /// + /// A test NetworkBeahviour that provides a varying NetworkList size as well as + /// additional NetworkVariables to assure if a NetworkObject fails to be created + /// the synchronization process will continue (i.e. it will skip over that block + /// of the reader buffer). + /// + public class NetworkBehaviourWithNetworkVariables : NetworkBehaviour + { + public static int ServerSpawnCount { get; internal set; } + public static readonly Dictionary ClientSpawnCount = new Dictionary(); + + public static void ResetSpawnCount() + { + ServerSpawnCount = 0; + ClientSpawnCount.Clear(); + } + + private const uint k_MinDataBlocks = 1; + private const uint k_MaxDataBlocks = 64; + + // Add various types of NetworkVariables + public NetworkList NetworkVariableData1; + public NetworkVariable NetworkVariableData2; + public NetworkVariable NetworkVariableData3; + public NetworkVariable NetworkVariableData4; + + private void Awake() + { + var dataBlocksAssigned = new List(); + var numberDataBlocks = Random.Range(k_MinDataBlocks, k_MaxDataBlocks); + for (var i = 0; i < numberDataBlocks; i++) + { + dataBlocksAssigned.Add((ulong)Random.Range(0.0f, float.MaxValue)); + } + + NetworkVariableData1 = new NetworkList(dataBlocksAssigned); + NetworkVariableData2 = new NetworkVariable(Random.Range(1, 1000)); + NetworkVariableData3 = new NetworkVariable(Random.Range(1, 1000)); + NetworkVariableData4 = new NetworkVariable((byte)Random.Range(1, 255)); + + } + + public override void OnNetworkSpawn() + { + if (IsServer) + { + ServerSpawnCount++; + } + else + { + if (!ClientSpawnCount.ContainsKey(NetworkManager.LocalClientId)) + { + ClientSpawnCount.Add(NetworkManager.LocalClientId, 0); + } + ClientSpawnCount[NetworkManager.LocalClientId]++; + } + + base.OnNetworkSpawn(); + } + } + + /// + /// A test NetworkBeahviour that has varying permissions in order to validate that + /// when variable length safety checks are off NetworkVariables still are updated + /// properly. + /// + public class NetworkBehaviourWithOwnerNetworkVariables : NetworkBehaviour + { + + // Should not synchronize on non-owners + public NetworkVariable NetworkVariableData1 = new NetworkVariable(default, NetworkVariableReadPermission.Owner, NetworkVariableWritePermission.Server); + // Should synchronize with everyone + public NetworkVariable NetworkVariableData2 = new NetworkVariable(); + // Should not synchronize on non-owners + public NetworkVariable NetworkVariableData3 = new NetworkVariable(default, NetworkVariableReadPermission.Owner, NetworkVariableWritePermission.Server); + // Should synchronize with everyone + public NetworkVariable NetworkVariableData4 = new NetworkVariable(); + + public override void OnNetworkSpawn() + { + if (IsServer) + { + NetworkVariableData1.Value = Random.Range(1, 1000); + NetworkVariableData2.Value = Random.Range(1, 1000); + NetworkVariableData3.Value = (byte)Random.Range(1, 255); + NetworkVariableData4.Value = (ushort)Random.Range(1, ushort.MaxValue); + } + } + } + + /// + /// A test NetworkBeahviour that simulates various types of synchronization failures + /// and provides a synchronization success version to validate that synchronization + /// will continue if user synchronization code fails. + /// + public class NetworkBehaviourSynchronizeFailureComponent : NetworkBehaviour + { + public static int NumberOfFailureTypes { get; internal set; } + public static int ServerSpawnCount { get; internal set; } + public static int ClientSpawnCount { get; internal set; } + + private static FailureTypes s_FailureType = FailureTypes.None; + + public enum FailureTypes + { + None, + DuringWriting, + DuringReading, + DontReadAnything, + ThrowWriteSideException, + ThrowReadSideException + } + + public static void ResetBehaviour() + { + ServerSpawnCount = 0; + ClientSpawnCount = 0; + s_FailureType = FailureTypes.None; + NumberOfFailureTypes = System.Enum.GetValues(typeof(FailureTypes)).Length; + } + + private MyCustomData m_MyCustomData; + + private struct MyCustomData : INetworkSerializable + { + public FailureTypes FailureType; + private ushort m_DataSize; + private byte[] m_DataBlock; + + public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter + { + if (serializer.IsWriter) + { + var writer = serializer.GetFastBufferWriter(); + switch (FailureType) + { + case FailureTypes.None: + // We want to write something for these two cases + case FailureTypes.DuringReading: + case FailureTypes.DontReadAnything: + { + writer.WriteValueSafe(m_DataSize); + for (int i = 0; i < m_DataSize; i++) + { + writer.WriteValueSafe(m_DataBlock[i]); + } + break; + } + case FailureTypes.DuringWriting: + { + writer.WriteValueSafe(m_DataSize); + // Try to write past the allocated size to generate an exception + // while also filling the buffer to verify that the buffer will be + // reset back to the original position. + for (int i = 0; i <= m_DataSize; i++) + { + writer.WriteValueSafe(m_DataBlock[i]); + } + break; + } + case FailureTypes.ThrowWriteSideException: + { + throw new System.Exception("Write side exception!"); + } + } + } + else + { + var reader = serializer.GetFastBufferReader(); + switch (FailureType) + { + case FailureTypes.None: + { + reader.ReadValueSafe(out m_DataSize); + m_DataBlock = new byte[m_DataSize]; + for (int i = 0; i < m_DataSize; i++) + { + reader.ReadValueSafe(out m_DataBlock[i]); + } + break; + } + case FailureTypes.DuringReading: + { + reader.ReadValueSafe(out m_DataSize); + // Allocate more space than needed + m_DataBlock = new byte[(int)(m_DataSize * 1.5f)]; + // Now read past the size of this message to verify + // that the reader will get rest back to the appropriate + // position and an error will be generated for this + for (int i = 0; i < m_DataBlock.Length; i++) + { + reader.ReadValueSafe(out m_DataBlock[i]); + } + break; + } + case FailureTypes.DontReadAnything: + { + // Don't read anything + break; + } + case FailureTypes.ThrowReadSideException: + { + throw new System.Exception("Read side exception!"); + } + + } + } + } + + public void GenerateData(ushort size) + { + m_DataSize = size; + m_DataBlock = new byte[size]; + for (int i = 0; i < m_DataSize; i++) + { + m_DataBlock[i] = (byte)Random.Range(0, 512); + } + } + } + + // This NetworkVariable is synchronized before OnSynchronize is invoked + // which enables us to perform the tests. + // Users could follow the same pattern for game assets and synchronize + // clients based on NetworkVariable settings. (i.e. a specific NPC type or the like) + private NetworkVariable m_FailureType; + + public void AssignNextFailureType() + { + var currentPosition = (int)s_FailureType; + currentPosition = (++currentPosition) % NumberOfFailureTypes; + s_FailureType = (FailureTypes)currentPosition; + m_FailureType.Value = s_FailureType; + } + + + private void Awake() + { + m_FailureType = new NetworkVariable(); + m_MyCustomData = new MyCustomData(); + } + + public override void OnNetworkSpawn() + { + if (IsServer) + { + ServerSpawnCount++; + m_MyCustomData.GenerateData((ushort)Random.Range(1, 512)); + } + else + { + ClientSpawnCount++; + } + + base.OnNetworkSpawn(); + } + + protected override void OnSynchronize(ref BufferSerializer serializer) + { + // Assign the failure type first + m_MyCustomData.FailureType = m_FailureType.Value; + // Now handle the serialization for this failure type + m_MyCustomData.NetworkSerialize(serializer); + } + } + + public class NetworkBehaviourOnSynchronizeComponent : NetworkBehaviour + { + public SomeCustomSerializationData CustomSerializationData = new SomeCustomSerializationData(); + + public struct SomeCustomSerializationData : INetworkSerializable + { + public uint Value1; + public bool Value2; + public long Value3; + public float Value4; + public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter + { + serializer.SerializeValue(ref Value1); + serializer.SerializeValue(ref Value2); + serializer.SerializeValue(ref Value3); + serializer.SerializeValue(ref Value4); + } + } + + public override void OnNetworkSpawn() + { + if (IsServer) + { + CustomSerializationData.Value1 = (uint)Random.Range(0, 10000); + CustomSerializationData.Value2 = true; + CustomSerializationData.Value3 = Random.Range(0, 10000); + CustomSerializationData.Value4 = Random.Range(-1000.0f, 1000.0f); + } + base.OnNetworkSpawn(); + } + + protected override void OnSynchronize(ref BufferSerializer serializer) + { + serializer.SerializeNetworkSerializable(ref CustomSerializationData); + base.OnSynchronize(ref serializer); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSceneSerializationTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSceneSerializationTests.cs.meta rename to com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs.meta diff --git a/testproject/Assets/Scripts/ServerHostClientText.cs b/testproject/Assets/Scripts/ServerHostClientText.cs index a394fbf03e..4c49ca8ca1 100644 --- a/testproject/Assets/Scripts/ServerHostClientText.cs +++ b/testproject/Assets/Scripts/ServerHostClientText.cs @@ -7,6 +7,12 @@ public class ServerHostClientText : NetworkBehaviour [SerializeField] private Text m_DisplayText; private Color m_Color; + private Vector3 m_LocalPosition; + + private void Awake() + { + m_LocalPosition = transform.localPosition; + } private void Start() { @@ -30,6 +36,7 @@ public override void OnNetworkSpawn() m_DisplayText.text = "Client"; } } + transform.localPosition = m_LocalPosition; } private void OnGUI() diff --git a/testproject/Assets/Tests/Manual/ManualTestsMenu.unity b/testproject/Assets/Tests/Manual/ManualTestsMenu.unity index 0781d337fa..8ca598ab66 100644 --- a/testproject/Assets/Tests/Manual/ManualTestsMenu.unity +++ b/testproject/Assets/Tests/Manual/ManualTestsMenu.unity @@ -698,6 +698,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3} m_Name: m_EditorClassIdentifier: + m_SendPointerHoverToParent: 1 m_HorizontalAxis: Horizontal m_VerticalAxis: Vertical m_SubmitButton: Submit @@ -923,6 +924,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_SceneMenus: + - {fileID: 11400000, guid: 2c0ff1138526d4041a875c84f7114513, type: 2} - {fileID: 11400000, guid: 83bc07221c884b24e968f464eaccce26, type: 2} - {fileID: 11400000, guid: 644bd35b81cd40c4d88002a24e223462, type: 2} - {fileID: 11400000, guid: 33e25ac2a2f551c4db75a9e960a6bc2f, type: 2} diff --git a/testproject/Assets/Tests/Manual/NestedNetworkTransforms.meta b/testproject/Assets/Tests/Manual/NestedNetworkTransforms.meta new file mode 100644 index 0000000000..9f0f5283cd --- /dev/null +++ b/testproject/Assets/Tests/Manual/NestedNetworkTransforms.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8a96c655551fade40adc673dd12bf67a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Manual/NestedNetworkTransforms/ChildMover.cs b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/ChildMover.cs new file mode 100644 index 0000000000..38ad1881ee --- /dev/null +++ b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/ChildMover.cs @@ -0,0 +1,31 @@ +using UnityEngine; +using Unity.Netcode; + +namespace TestProject.ManualTests +{ + public class ChildMover : NetworkBehaviour + { + public static bool RandomizeScale; + + [Range(0.1f, 30.0f)] + public float RotationSpeed = 5.0f; + + public void PlayerIsMoving(float movementDirection) + { + if (IsSpawned && IsOwner) + { + var rotateDirection = movementDirection * RotationSpeed; + transform.RotateAround(transform.parent.position, Vector3.up, rotateDirection); + } + } + + public override void OnNetworkSpawn() + { + if (IsOwner && RandomizeScale) + { + transform.localScale = transform.localScale * Random.Range(0.5f, 2.0f); + } + base.OnNetworkSpawn(); + } + } +} diff --git a/testproject/Assets/Tests/Manual/NestedNetworkTransforms/ChildMover.cs.meta b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/ChildMover.cs.meta new file mode 100644 index 0000000000..8d284ce0c3 --- /dev/null +++ b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/ChildMover.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0d8ad30fca3f9a240bdce16f0166033b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Manual/NestedNetworkTransforms/ChildMoverManager.cs b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/ChildMoverManager.cs new file mode 100644 index 0000000000..6552943606 --- /dev/null +++ b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/ChildMoverManager.cs @@ -0,0 +1,107 @@ +using System.Collections.Generic; +using UnityEngine; +using Unity.Netcode; + +namespace TestProject.ManualTests +{ + public class ChildMoverManager : NetworkBehaviour + { + public List ChildMovers; + + [Range(0.001f, 5.0f)] + public float TriggerDistanceToMove = 0.2f; + + public Camera PlayerCamera; + private Vector3 m_LastPosition; + private Vector3 m_LastForward; + private Camera m_MainCamera; + + private void Awake() + { + if (PlayerCamera != null) + { + PlayerCamera.enabled = false; + } + } + + public override void OnNetworkSpawn() + { + if (IsOwner) + { + m_LastPosition = transform.position; + m_LastForward = transform.forward; + + for (int i = 0; i < Camera.allCamerasCount; i++) + { + var camera = Camera.allCameras[i]; + if (camera.name == "Main Camera") + { + m_MainCamera = Camera.allCameras[i]; + } + } + } + base.OnNetworkSpawn(); + } + + private float m_LastRotDirection = 1.0f; + private float m_LastMovementDirection = 1.0f; + private void Update() + { + if (IsOwner && IsSpawned) + { + var deltaPosition = (transform.position - m_LastPosition); + if (deltaPosition.sqrMagnitude >= (TriggerDistanceToMove * TriggerDistanceToMove)) + { + // Get our movement direction + var movementDirection = Vector3.Dot(deltaPosition.normalized, transform.forward); + if (movementDirection == 0) + { + movementDirection = m_LastMovementDirection; + } + else + { + m_LastMovementDirection = movementDirection; + } + var rotationDirection = Vector3.zero; + if (movementDirection > 0) + { + rotationDirection = Vector3.Cross(m_LastForward, transform.forward); + } + else if (movementDirection < 0) + { + rotationDirection = Vector3.Cross(transform.forward, m_LastForward); + } + else + { + rotationDirection.y = m_LastRotDirection; + } + + m_LastRotDirection = rotationDirection.y; + movementDirection *= rotationDirection.y < 0 ? -1 : 1; + + m_LastPosition = transform.position; + m_LastForward = transform.forward; + foreach (var childMover in ChildMovers) + { + childMover.PlayerIsMoving(Mathf.Sign(movementDirection)); + } + } + + if (Input.GetKeyDown(KeyCode.C) && PlayerCamera != null && m_MainCamera != null) + { + if (m_MainCamera.isActiveAndEnabled) + { + PlayerCamera.enabled = true; + m_MainCamera.enabled = false; + } + else + { + m_MainCamera.enabled = true; + PlayerCamera.enabled = false; + } + } + } + } + + } +} diff --git a/testproject/Assets/Tests/Manual/NestedNetworkTransforms/ChildMoverManager.cs.meta b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/ChildMoverManager.cs.meta new file mode 100644 index 0000000000..b17549dd15 --- /dev/null +++ b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/ChildMoverManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 482bb1f796fe43348bcbfd8161ed3825 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Manual/NestedNetworkTransforms/NestedNetworkTransforms.unity b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/NestedNetworkTransforms.unity new file mode 100644 index 0000000000..342f1a7b70 --- /dev/null +++ b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/NestedNetworkTransforms.unity @@ -0,0 +1,1031 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &635877837 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 635877840} + - component: {fileID: 635877839} + - component: {fileID: 635877838} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &635877838 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 635877837} + m_Enabled: 1 +--- !u!20 &635877839 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 635877837} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_FocalLength: 50 + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &635877840 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 635877837} + m_LocalRotation: {x: 0.60876137, y: 0, z: 0, w: 0.7933534} + m_LocalPosition: {x: 0, y: 60, z: -11} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 75, y: 0, z: 0} +--- !u!1 &691147452 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 691147454} + - component: {fileID: 691147453} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &691147453 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 691147452} + m_Enabled: 1 + serializedVersion: 10 + m_Type: 1 + m_Shape: 0 + m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.80208 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!4 &691147454 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 691147452} + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!1001 &759287026 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + m_TransformParent: {fileID: 1107190083} + m_Modifications: + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_Pivot.x + value: 0.5 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_Pivot.y + value: 0.5 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_RootOrder + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_AnchorMax.x + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_AnchorMax.y + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_AnchorMin.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_AnchorMin.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_SizeDelta.x + value: -952 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_SizeDelta.y + value: -344 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_AnchoredPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_AnchoredPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6963777608485144162, guid: d725b5588e1b956458798319e6541d84, + type: 3} + propertyPath: m_Name + value: ConnectionModeButtons + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: d725b5588e1b956458798319e6541d84, type: 3} +--- !u!224 &759287027 stripped +RectTransform: + m_CorrespondingSourceObject: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, + type: 3} + m_PrefabInstance: {fileID: 759287026} + m_PrefabAsset: {fileID: 0} +--- !u!1 &1107190079 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1107190083} + - component: {fileID: 1107190082} + - component: {fileID: 1107190081} + - component: {fileID: 1107190080} + m_Layer: 5 + m_Name: Canvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1107190080 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1107190079} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &1107190081 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1107190079} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 0 +--- !u!223 &1107190082 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1107190079} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 0 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_AdditionalShaderChannelsFlag: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &1107190083 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1107190079} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1136626022} + - {fileID: 1200545880} + - {fileID: 759287027} + - {fileID: 1622867933} + m_Father: {fileID: 0} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!1 &1136626021 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1136626022} + - component: {fileID: 1136626026} + - component: {fileID: 1136626025} + - component: {fileID: 1136626024} + - component: {fileID: 1136626023} + m_Layer: 5 + m_Name: ServerHostClientDisplay + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1136626022 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1136626021} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1107190083} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0} + m_AnchorMax: {x: 0.5, y: 0} + m_AnchoredPosition: {x: 0, y: 31} + m_SizeDelta: {x: 200, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1136626023 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1136626021} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 1789729126 + AlwaysReplicateAsRoot: 0 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 0 +--- !u!114 &1136626024 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1136626021} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 7ea6e834b18e2c840a76ce574eb4b144, type: 3} + m_Name: + m_EditorClassIdentifier: + m_DisplayText: {fileID: 1136626025} +--- !u!114 &1136626025 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1136626021} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 0.5058824, b: 0.003921569, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 24 + m_FontStyle: 1 + m_BestFit: 0 + m_MinSize: 1 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 0 + m_Text: Server +--- !u!222 &1136626026 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1136626021} + m_CullTransparentMesh: 1 +--- !u!1001 &1200545879 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + m_TransformParent: {fileID: 1107190083} + m_Modifications: + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_Pivot.x + value: 0.5 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_Pivot.y + value: 0.5 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_RootOrder + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_AnchorMax.x + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_AnchorMax.y + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_AnchorMin.x + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_AnchorMin.y + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_SizeDelta.x + value: 20 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_SizeDelta.y + value: 25 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalScale.x + value: 0.9806 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalScale.y + value: 0.9806 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalScale.z + value: 0.9806 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalRotation.x + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalRotation.y + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalRotation.z + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_AnchoredPosition.x + value: -22.946533 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_AnchoredPosition.y + value: -23.338623 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 2848221156307247795, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_Name + value: ExitButton + objectReference: {fileID: 0} + - target: {fileID: 5266522511616468950, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + propertyPath: m_SceneMenuToLoad + value: + objectReference: {fileID: 11400000, guid: 4a3cdce12e998384f8aca207b5a2c700, + type: 2} + m_RemovedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 3200770c16e3b2b4ebe7f604154faac7, type: 3} +--- !u!224 &1200545880 stripped +RectTransform: + m_CorrespondingSourceObject: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, + type: 3} + m_PrefabInstance: {fileID: 1200545879} + m_PrefabAsset: {fileID: 0} +--- !u!1001 &1525117742 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_RootOrder + value: 4 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_LocalScale.x + value: 1.5 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_LocalScale.z + value: 1.5 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511854, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_Name + value: SceneLevelGeometry + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: fe5fd652408224242a6fea8a6f8e6d05, type: 3} +--- !u!1 &1622867932 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1622867933} + - component: {fileID: 1622867935} + - component: {fileID: 1622867934} + m_Layer: 5 + m_Name: EventSystem + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1622867933 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1622867932} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1107190083} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1622867934 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1622867932} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3} + m_Name: + m_EditorClassIdentifier: + m_SendPointerHoverToParent: 1 + m_HorizontalAxis: Horizontal + m_VerticalAxis: Vertical + m_SubmitButton: Submit + m_CancelButton: Cancel + m_InputActionsPerSecond: 10 + m_RepeatDelay: 0.5 + m_ForceModuleActive: 0 +--- !u!114 &1622867935 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1622867932} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_FirstSelected: {fileID: 0} + m_sendNavigationEvents: 1 + m_DragThreshold: 10 +--- !u!1 &1909864228 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1909864230} + - component: {fileID: 1909864229} + - component: {fileID: 1909864231} + m_Layer: 0 + m_Name: NetworkManager + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1909864229 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1909864228} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 593a2fe42fa9d37498c96f9a383b6521, type: 3} + m_Name: + m_EditorClassIdentifier: + RunInBackground: 1 + LogLevel: 1 + NetworkConfig: + ProtocolVersion: 0 + NetworkTransport: {fileID: 1909864231} + PlayerPrefab: {fileID: 296612175404815451, guid: 61e89146f864ef2478af3da9d7dda1e2, + type: 3} + NetworkPrefabs: [] + TickRate: 30 + ClientConnectionBufferTimeout: 10 + ConnectionApproval: 0 + ConnectionData: + EnableTimeResync: 0 + TimeResyncInterval: 30 + EnsureNetworkVariableLengthSafety: 0 + EnableSceneManagement: 1 + ForceSamePrefabs: 1 + RecycleNetworkIds: 1 + NetworkIdRecycleDelay: 120 + RpcHashSize: 0 + LoadSceneTimeOut: 120 + SpawnTimeout: 1 + EnableNetworkLogs: 1 +--- !u!4 &1909864230 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1909864228} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1909864231 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1909864228} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 6960e84d07fb87f47956e7a81d71c4e6, type: 3} + m_Name: + m_EditorClassIdentifier: + m_ProtocolType: 0 + m_MaxPacketQueueSize: 128 + m_MaxPayloadSize: 6144 + m_HeartbeatTimeoutMS: 500 + m_ConnectTimeoutMS: 1000 + m_MaxConnectAttempts: 60 + m_DisconnectTimeoutMS: 30000 + ConnectionData: + Address: 127.0.0.1 + Port: 7777 + ServerListenAddress: + DebugSimulator: + PacketDelayMS: 0 + PacketJitterMS: 0 + PacketDropRate: 0 diff --git a/testproject/Assets/Tests/Manual/NestedNetworkTransforms/NestedNetworkTransforms.unity.meta b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/NestedNetworkTransforms.unity.meta new file mode 100644 index 0000000000..1fe34e9afa --- /dev/null +++ b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/NestedNetworkTransforms.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 92b8cccf28cbaba40854a025b66e2ac3 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Manual/NestedNetworkTransforms/NestedNetworkTransformsReference.asset b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/NestedNetworkTransformsReference.asset new file mode 100644 index 0000000000..3414672af6 --- /dev/null +++ b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/NestedNetworkTransformsReference.asset @@ -0,0 +1,19 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 39a16938ffb5cd846a9f6df7a686a9c4, type: 3} + m_Name: NestedNetworkTransformsReference + m_EditorClassIdentifier: + SceneToReference: {fileID: 102900000, guid: 92b8cccf28cbaba40854a025b66e2ac3, type: 3} + m_IncludedScenes: [] + m_DisplayName: Nested NetworkTransforms + m_ReferencedScenes: + - NestedNetworkTransforms diff --git a/testproject/Assets/Tests/Manual/NestedNetworkTransforms/NestedNetworkTransformsReference.asset.meta b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/NestedNetworkTransformsReference.asset.meta new file mode 100644 index 0000000000..d242a32947 --- /dev/null +++ b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/NestedNetworkTransformsReference.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2c0ff1138526d4041a875c84f7114513 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Manual/NestedNetworkTransforms/PlayerCube Variant.prefab b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/PlayerCube Variant.prefab new file mode 100644 index 0000000000..7dd9279aa0 --- /dev/null +++ b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/PlayerCube Variant.prefab @@ -0,0 +1,474 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &772585991204072682 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1522619104359096714} + - component: {fileID: 6327137497379236391} + - component: {fileID: 2204728973112518521} + - component: {fileID: 1866518356433686547} + - component: {fileID: 2645854474244200924} + m_Layer: 0 + m_Name: ChildOne + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1522619104359096714 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 772585991204072682} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 2, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 296612175404815447} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &6327137497379236391 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 772585991204072682} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 62ff4693d71b55440a645bbccd83ac8a, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &2204728973112518521 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 772585991204072682} + m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} +--- !u!114 &1866518356433686547 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 772585991204072682} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0d8ad30fca3f9a240bdce16f0166033b, type: 3} + m_Name: + m_EditorClassIdentifier: + RotationSpeed: 5 +--- !u!114 &2645854474244200924 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 772585991204072682} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: cf01cca54b77c0241ad6d9da5ef6a709, type: 3} + m_Name: + m_EditorClassIdentifier: + SyncPositionX: 1 + SyncPositionY: 1 + SyncPositionZ: 1 + SyncRotAngleX: 1 + SyncRotAngleY: 1 + SyncRotAngleZ: 1 + SyncScaleX: 1 + SyncScaleY: 1 + SyncScaleZ: 1 + PositionThreshold: 0.001 + RotAngleThreshold: 0.01 + ScaleThreshold: 0.01 + InLocalSpace: 0 + Interpolate: 1 +--- !u!1 &2771624607751045562 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5485086383386216104} + m_Layer: 0 + m_Name: FirstLevel + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &5485086383386216104 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2771624607751045562} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 4974009855568796650} + m_Father: {fileID: 296612175404815447} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &4147667212972069939 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7199215624742357828} + - component: {fileID: 8372809022110481992} + m_Layer: 0 + m_Name: PlayerView + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &7199215624742357828 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4147667212972069939} + m_LocalRotation: {x: 0.17364816, y: -0, z: -0, w: 0.9848078} + m_LocalPosition: {x: 0, y: 8, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 296612175404815447} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 20, y: 0, z: 0} +--- !u!20 &8372809022110481992 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4147667212972069939} + m_Enabled: 0 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_FocalLength: 50 + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!1 &6292214655028195304 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4974009855568796650} + - component: {fileID: 3062926429781172158} + - component: {fileID: 1595868777393624541} + - component: {fileID: 2914594030718603294} + - component: {fileID: 2888046383252644210} + m_Layer: 0 + m_Name: ChildTwo + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4974009855568796650 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6292214655028195304} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: -2, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 5485086383386216104} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &3062926429781172158 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6292214655028195304} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 62ff4693d71b55440a645bbccd83ac8a, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &1595868777393624541 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6292214655028195304} + m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} +--- !u!114 &2914594030718603294 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6292214655028195304} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0d8ad30fca3f9a240bdce16f0166033b, type: 3} + m_Name: + m_EditorClassIdentifier: + RotationSpeed: 5 +--- !u!114 &2888046383252644210 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6292214655028195304} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: cf01cca54b77c0241ad6d9da5ef6a709, type: 3} + m_Name: + m_EditorClassIdentifier: + SyncPositionX: 1 + SyncPositionY: 1 + SyncPositionZ: 1 + SyncRotAngleX: 1 + SyncRotAngleY: 1 + SyncRotAngleZ: 1 + SyncScaleX: 1 + SyncScaleY: 1 + SyncScaleZ: 1 + PositionThreshold: 0.001 + RotAngleThreshold: 0.01 + ScaleThreshold: 0.01 + InLocalSpace: 0 + Interpolate: 1 +--- !u!1001 &8977898853425847701 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 947981134, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, type: 3} + propertyPath: GlobalObjectIdHash + value: 951099334 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_RootOrder + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalScale.x + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalScale.y + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalScale.z + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767886, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_Name + value: PlayerCube Variant + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, type: 3} +--- !u!4 &296612175404815447 stripped +Transform: + m_CorrespondingSourceObject: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + m_PrefabInstance: {fileID: 8977898853425847701} + m_PrefabAsset: {fileID: 0} +--- !u!1 &296612175404815451 stripped +GameObject: + m_CorrespondingSourceObject: {fileID: 8685790303553767886, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + m_PrefabInstance: {fileID: 8977898853425847701} + m_PrefabAsset: {fileID: 0} +--- !u!114 &4389916208190318681 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 296612175404815451} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 482bb1f796fe43348bcbfd8161ed3825, type: 3} + m_Name: + m_EditorClassIdentifier: + ChildMovers: + - {fileID: 1866518356433686547} + - {fileID: 2914594030718603294} + TriggerDistanceToMove: 0.25 + PlayerCamera: {fileID: 8372809022110481992} diff --git a/testproject/Assets/Tests/Manual/NestedNetworkTransforms/PlayerCube Variant.prefab.meta b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/PlayerCube Variant.prefab.meta new file mode 100644 index 0000000000..c6d325e64c --- /dev/null +++ b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/PlayerCube Variant.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 61e89146f864ef2478af3da9d7dda1e2 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/AutomatedPlayerMover.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/AutomatedPlayerMover.cs new file mode 100644 index 0000000000..bd97836562 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/AutomatedPlayerMover.cs @@ -0,0 +1,93 @@ +using UnityEngine; +using Unity.Netcode.Components; + +namespace TestProject.RuntimeTests +{ + public class AutomatedPlayerMover : NetworkTransform + { + public static bool StopMovement; + private float m_Speed = 15.0f; + private float m_RotSpeed = 15.0f; + + private GameObject m_Destination; + private Vector3 m_TargetPosition; + + + + /// + /// Make this PlayerMovement-NetworkTransform component + /// Owner Authoritative + /// + protected override bool OnIsServerAuthoritative() + { + return false; + } + + private void UpdateDestination() + { + if (Navigationpoints.Instance != null) + { + var targetNavPointIndex = Random.Range(0, Navigationpoints.Instance.NavPoints.Count - 1); + m_Destination = Navigationpoints.Instance.NavPoints[targetNavPointIndex]; + + m_TargetPosition = m_Destination.transform.position; + m_TargetPosition.y = transform.position.y; + } + } + + public override void OnNetworkSpawn() + { + if (IsOwner) + { + UpdateDestination(); + var temp = transform.position; + temp.y = 0.5f; + transform.position = temp; + } + base.OnNetworkSpawn(); + } + + public override void OnNetworkDespawn() + { + base.OnNetworkDespawn(); + } + + private void LateUpdate() + { + if (!IsSpawned || !IsOwner || m_Destination == null || StopMovement) + { + return; + } + m_TargetPosition.y = transform.position.y; + var distance = Vector3.Distance(transform.position, m_TargetPosition); + if (distance < 0.25f) + { + var currentDestination = m_Destination; + while (m_Destination == currentDestination) + { + UpdateDestination(); + } + } + } + + private void FixedUpdate() + { + if (!IsSpawned || !IsOwner || m_Destination == null || StopMovement) + { + return; + } + else + { + transform.position = Vector3.MoveTowards(transform.position, m_Destination.transform.position, m_Speed * Time.fixedDeltaTime); + var normalizedDirection = (m_TargetPosition - transform.position).normalized; + if (normalizedDirection.magnitude != 0.0f) + { + var lookRotation = Quaternion.LookRotation(normalizedDirection, transform.up).eulerAngles; + var currentEuler = transform.eulerAngles; + currentEuler.y = Mathf.LerpAngle(currentEuler.y, lookRotation.y, Time.deltaTime * m_RotSpeed); + transform.eulerAngles = currentEuler; + } + } + } + } +} diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/AutomatedPlayerMover.cs.meta b/testproject/Assets/Tests/Runtime/ObjectParenting/AutomatedPlayerMover.cs.meta new file mode 100644 index 0000000000..3ccd51fd66 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/AutomatedPlayerMover.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dfb1af1a9249278438d2daa2877ee2ad +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/Navigationpoints.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/Navigationpoints.cs new file mode 100644 index 0000000000..e2a6aecfcf --- /dev/null +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/Navigationpoints.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace TestProject.RuntimeTests +{ + public class Navigationpoints : MonoBehaviour + { + public static Navigationpoints Instance; + + public List NavPoints; + + private void Awake() + { + if (Instance == null) + { + Instance = this; + } + } + } +} diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/Navigationpoints.cs.meta b/testproject/Assets/Tests/Runtime/ObjectParenting/Navigationpoints.cs.meta new file mode 100644 index 0000000000..82f15cd59e --- /dev/null +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/Navigationpoints.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fb5950beeb672f54fa8b2666aaefa223 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/NestedNetworkTransformTestScene.unity b/testproject/Assets/Tests/Runtime/ObjectParenting/NestedNetworkTransformTestScene.unity new file mode 100644 index 0000000000..e7971f34dd --- /dev/null +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/NestedNetworkTransformTestScene.unity @@ -0,0 +1,1077 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &75767844 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 75767845} + m_Layer: 0 + m_Name: NavigationPoint1 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &75767845 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 75767844} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0.5, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 582924497} + m_Father: {fileID: 0} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &147173471 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 147173472} + - component: {fileID: 147173476} + - component: {fileID: 147173475} + m_Layer: 9 + m_Name: ObjectLabel + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &147173472 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 147173471} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 165918550} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!102 &147173475 +TextMesh: + serializedVersion: 3 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 147173471} + m_Text: 3 + m_OffsetZ: 0 + m_CharacterSize: 1 + m_LineSpacing: 1 + m_Anchor: 7 + m_Alignment: 0 + m_TabSize: 4 + m_FontSize: 32 + m_FontStyle: 0 + m_RichText: 1 + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_Color: + serializedVersion: 2 + rgba: 4294967295 +--- !u!23 &147173476 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 147173471} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 10100, guid: 0000000000000000e000000000000000, type: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!1 &165918549 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 165918550} + m_Layer: 0 + m_Name: NavigationPoint3 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &165918550 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 165918549} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 50, y: 0.5, z: 50} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 147173472} + m_Father: {fileID: 0} + m_RootOrder: 5 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &170238949 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 170238951} + - component: {fileID: 170238950} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &170238950 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 170238949} + m_Enabled: 1 + serializedVersion: 10 + m_Type: 1 + m_Shape: 0 + m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.80208 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!4 &170238951 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 170238949} + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!1 &582924496 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 582924497} + - component: {fileID: 582924501} + - component: {fileID: 582924500} + m_Layer: 9 + m_Name: ObjectLabel + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &582924497 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 582924496} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 75767845} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!102 &582924500 +TextMesh: + serializedVersion: 3 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 582924496} + m_Text: 1 + m_OffsetZ: 0 + m_CharacterSize: 1 + m_LineSpacing: 1 + m_Anchor: 7 + m_Alignment: 0 + m_TabSize: 4 + m_FontSize: 32 + m_FontStyle: 0 + m_RichText: 1 + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_Color: + serializedVersion: 2 + rgba: 4294967295 +--- !u!23 &582924501 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 582924496} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 10100, guid: 0000000000000000e000000000000000, type: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!1 &1218177454 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1218177455} + - component: {fileID: 1218177459} + - component: {fileID: 1218177458} + m_Layer: 9 + m_Name: ObjectLabel + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1218177455 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1218177454} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1476695697} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!102 &1218177458 +TextMesh: + serializedVersion: 3 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1218177454} + m_Text: "5\t" + m_OffsetZ: 0 + m_CharacterSize: 1 + m_LineSpacing: 1 + m_Anchor: 7 + m_Alignment: 0 + m_TabSize: 4 + m_FontSize: 32 + m_FontStyle: 0 + m_RichText: 1 + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_Color: + serializedVersion: 2 + rgba: 4294967295 +--- !u!23 &1218177459 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1218177454} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 10100, guid: 0000000000000000e000000000000000, type: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!1 &1222925673 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1222925674} + - component: {fileID: 1222925678} + - component: {fileID: 1222925677} + m_Layer: 9 + m_Name: ObjectLabel + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1222925674 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1222925673} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 2026412476} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!102 &1222925677 +TextMesh: + serializedVersion: 3 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1222925673} + m_Text: "2\t" + m_OffsetZ: 0 + m_CharacterSize: 1 + m_LineSpacing: 1 + m_Anchor: 7 + m_Alignment: 0 + m_TabSize: 4 + m_FontSize: 32 + m_FontStyle: 0 + m_RichText: 1 + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_Color: + serializedVersion: 2 + rgba: 4294967295 +--- !u!23 &1222925678 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1222925673} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 10100, guid: 0000000000000000e000000000000000, type: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!1 &1323194500 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1323194502} + - component: {fileID: 1323194501} + m_Layer: 0 + m_Name: NavigationPoints + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1323194501 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1323194500} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fb5950beeb672f54fa8b2666aaefa223, type: 3} + m_Name: + m_EditorClassIdentifier: + NavPoints: + - {fileID: 75767844} + - {fileID: 2026412475} + - {fileID: 165918549} + - {fileID: 2050720992} + - {fileID: 1476695696} +--- !u!4 &1323194502 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1323194500} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 53.61696, z: -48.528473} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 8 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1001 &1337449911 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_RootOrder + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_LocalScale.x + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_LocalScale.z + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_LocalPosition.y + value: 0.000000059604645 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511854, guid: fe5fd652408224242a6fea8a6f8e6d05, + type: 3} + propertyPath: m_Name + value: SceneLevelGeometry + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: fe5fd652408224242a6fea8a6f8e6d05, type: 3} +--- !u!1 &1476695696 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1476695697} + m_Layer: 0 + m_Name: NavigationPoint5 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1476695697 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1476695696} + m_LocalRotation: {x: 0.2164396, y: 0, z: 0, w: 0.97629607} + m_LocalPosition: {x: 50, y: 0.5, z: -50} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1218177455} + m_Father: {fileID: 0} + m_RootOrder: 7 + m_LocalEulerAnglesHint: {x: 25, y: 0, z: 0} +--- !u!1 &1561339197 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1561339198} + - component: {fileID: 1561339202} + - component: {fileID: 1561339201} + m_Layer: 9 + m_Name: ObjectLabel + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1561339198 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1561339197} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 2050720993} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!102 &1561339201 +TextMesh: + serializedVersion: 3 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1561339197} + m_Text: "4\t" + m_OffsetZ: 0 + m_CharacterSize: 1 + m_LineSpacing: 1 + m_Anchor: 7 + m_Alignment: 0 + m_TabSize: 4 + m_FontSize: 32 + m_FontStyle: 0 + m_RichText: 1 + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_Color: + serializedVersion: 2 + rgba: 4294967295 +--- !u!23 &1561339202 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1561339197} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 10100, guid: 0000000000000000e000000000000000, type: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!1 &1922374989 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1922374992} + - component: {fileID: 1922374991} + - component: {fileID: 1922374990} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &1922374990 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1922374989} + m_Enabled: 1 +--- !u!20 &1922374991 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1922374989} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_FocalLength: 50 + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &1922374992 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1922374989} + m_LocalRotation: {x: 0.46174863, y: 0, z: 0, w: 0.8870109} + m_LocalPosition: {x: 0, y: 70, z: -60} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 55, y: 0, z: 0} +--- !u!1 &2026412475 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2026412476} + m_Layer: 0 + m_Name: NavigationPoint2 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2026412476 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2026412475} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -50, y: 0.5, z: 50} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1222925674} + m_Father: {fileID: 0} + m_RootOrder: 4 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &2050720992 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2050720993} + m_Layer: 0 + m_Name: NavigationPoint4 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2050720993 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2050720992} + m_LocalRotation: {x: 0.2164396, y: 0, z: 0, w: 0.97629607} + m_LocalPosition: {x: -50, y: 0.5, z: -50} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1561339198} + m_Father: {fileID: 0} + m_RootOrder: 6 + m_LocalEulerAnglesHint: {x: 25, y: 0, z: 0} diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/NestedNetworkTransformTestScene.unity.meta b/testproject/Assets/Tests/Runtime/ObjectParenting/NestedNetworkTransformTestScene.unity.meta new file mode 100644 index 0000000000..3623bc6e70 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/NestedNetworkTransformTestScene.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 14235ed18eff0964cbb3ff7ae2ed1933 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/NestedNetworkTransformTests.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/NestedNetworkTransformTests.cs new file mode 100644 index 0000000000..d1f145e8f1 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/NestedNetworkTransformTests.cs @@ -0,0 +1,183 @@ +using System.Text; +using System.Collections; +using Unity.Netcode.Components; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.SceneManagement; +using UnityEngine.TestTools; +using TestProject.ManualTests; + +namespace TestProject.RuntimeTests +{ + public class NestedNetworkTransformTests : IntegrationTestWithApproximation + { + private const string k_TestScene = "NestedNetworkTransformTestScene"; + private const string k_PlayerToLoad = "PlayerNestedTransforms"; + + protected override int NumberOfClients => 0; + + private Scene m_BaseSceneLoaded; + private Scene m_OriginalActiveScene; + + private Object m_PlayerPrefabResource; + + protected override void OnOneTimeSetup() + { + ChildMover.RandomizeScale = true; + // Preserve the test runner scene that is currently the active scene + m_OriginalActiveScene = SceneManager.GetActiveScene(); + // Load our test's scene used by all client players (it gets set as the currently active scene when loaded) + m_PlayerPrefabResource = Resources.Load(k_PlayerToLoad); + Assert.NotNull(m_PlayerPrefabResource, $"Failed to load resource {k_PlayerToLoad}"); + + // Migrate the resource into the DDOL to prevent the server from thinking it is in-scene placed. + Object.DontDestroyOnLoad(m_PlayerPrefabResource); + SceneManager.sceneLoaded += SceneManager_sceneLoaded; + SceneManager.LoadScene(k_TestScene, LoadSceneMode.Additive); + base.OnOneTimeSetup(); + } + + protected override void OnOneTimeTearDown() + { + ChildMover.RandomizeScale = false; + // Set test runner's scene back to the currently active scene + if (m_OriginalActiveScene.IsValid() && m_OriginalActiveScene.isLoaded) + { + SceneManager.SetActiveScene(m_OriginalActiveScene); + } + // Unload our base scene if it is still loaded + if (m_BaseSceneLoaded.IsValid() && m_BaseSceneLoaded.isLoaded) + { + SceneManager.UnloadSceneAsync(m_BaseSceneLoaded); + } + base.OnOneTimeTearDown(); + } + + protected override IEnumerator OnSetup() + { + yield return WaitForConditionOrTimeOut(() => m_BaseSceneLoaded.IsValid() && m_BaseSceneLoaded.isLoaded); + AssertOnTimeout($"Timed out waiting for scene {k_TestScene} to load!"); + yield return base.OnSetup(); + } + + private void SceneManager_sceneLoaded(Scene sceneLoaded, LoadSceneMode loadSceneMode) + { + if (loadSceneMode == LoadSceneMode.Additive && sceneLoaded.name == k_TestScene) + { + m_BaseSceneLoaded = sceneLoaded; + SceneManager.sceneLoaded -= SceneManager_sceneLoaded; + SceneManager.SetActiveScene(sceneLoaded); + } + } + + protected override IEnumerator OnTearDown() + { + // This prevents us from trying to destroy the resource loaded + m_PlayerPrefab = null; + return base.OnTearDown(); + } + + + protected override void OnCreatePlayerPrefab() + { + // Destroy the default player prefab + Object.DestroyImmediate(m_PlayerPrefab); + // Assign the network prefab resource loaded + m_PlayerPrefab = m_PlayerPrefabResource as GameObject; + base.OnCreatePlayerPrefab(); + } + + /// + /// Prevent the server from telling the clients to load our test scene + /// + private bool VerifySceneServer(int sceneIndex, string sceneName, LoadSceneMode loadSceneMode) + { + if (sceneName == k_TestScene) + { + return false; + } + return true; + } + + /// + /// Increase the threshold as we are just testing that the + /// NetworkTransform is synchronizing properly and there are + /// known issues with Euler rotation that will be fixed when + /// we start synchronizing Quaternions. + /// + protected override float GetDeltaVarianceThreshold() + { + return 0.1f; + } + + private StringBuilder m_ValidationErrors; + /// + /// Validates that all player transforms are approximately the + /// same when a new client joins. + /// + /// + private bool ValidateNetworkTransforms() + { + m_ValidationErrors.Clear(); + foreach (var connectedClient in m_ServerNetworkManager.ConnectedClientsIds) + { + var playerToValidate = m_PlayerNetworkObjects[connectedClient][connectedClient]; + var playerNetworkTransforms = playerToValidate.GetComponentsInChildren(); + foreach (var playerRelative in m_PlayerNetworkObjects) + { + if (playerRelative.Key == connectedClient) + { + continue; + } + var relativeClonedTransforms = playerRelative.Value[connectedClient].GetComponentsInChildren(); + for (int i = 0; i < playerNetworkTransforms.Length; i++) + { + if (!Approximately(playerNetworkTransforms[i].transform.position, relativeClonedTransforms[i].transform.position)) + { + m_ValidationErrors.Append($"[Position][Client-{connectedClient} {playerNetworkTransforms[i].transform.position}][Failing on Client-{playerRelative.Key} for Clone-{relativeClonedTransforms[i].OwnerClientId} {relativeClonedTransforms[i].transform.position}]"); + } + if (!Approximately(playerNetworkTransforms[i].transform.eulerAngles, relativeClonedTransforms[i].transform.eulerAngles)) + { + m_ValidationErrors.Append($"[Rotation][Client-{connectedClient} {playerNetworkTransforms[i].transform.eulerAngles}][Failing on Client-{playerRelative.Key} for Clone-{relativeClonedTransforms[i].OwnerClientId} {relativeClonedTransforms[i].transform.eulerAngles}]"); + } + if (!Approximately(playerNetworkTransforms[i].transform.localScale, relativeClonedTransforms[i].transform.localScale)) + { + m_ValidationErrors.Append($"[Scale][Client-{connectedClient} {playerNetworkTransforms[i].transform.localScale}][Failing on Client-{playerRelative.Key} for Clone-{relativeClonedTransforms[i].OwnerClientId} {relativeClonedTransforms[i].transform.localScale}]"); + } + } + } + } + return m_ValidationErrors.Length == 0; + } + + [UnityTest] + public IEnumerator NestedNetworkTransformSynchronization() + { + m_ValidationErrors = new StringBuilder(); + var waitPeriod = new WaitForSeconds(1.0f); + m_ServerNetworkManager.SceneManager.VerifySceneBeforeLoading = VerifySceneServer; + yield return waitPeriod; + + // Spawn 5 more clients over time + for (int i = 0; i < 5; i++) + { + yield return CreateAndStartNewClient(); + // Stop all movement for all players + AutomatedPlayerMover.StopMovement = true; + // Validate that the transforms are approximately the same + yield return WaitForConditionOrTimeOut(ValidateNetworkTransforms); + AssertOnTimeout($"Timed out waiting for all nested NetworkTransform cloned instances to match:\n {m_ValidationErrors}"); + // Continue player movement + AutomatedPlayerMover.StopMovement = false; + // Allow the players to move a bit. + yield return waitPeriod; + } + + // Just a final sanity check to make sure position and rotation match + AutomatedPlayerMover.StopMovement = true; + yield return WaitForConditionOrTimeOut(ValidateNetworkTransforms); + } + + } +} + diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/NestedNetworkTransformTests.cs.meta b/testproject/Assets/Tests/Runtime/ObjectParenting/NestedNetworkTransformTests.cs.meta new file mode 100644 index 0000000000..cebb30746c --- /dev/null +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/NestedNetworkTransformTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1623c8082eac2494badd6912c5ca2f7f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs index 8accb2aba8..c1853c0d0b 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs @@ -12,7 +12,6 @@ public class ParentingInSceneObjectsTests : IntegrationTestWithApproximation { private const string k_BaseSceneToLoad = "UnitTestBaseScene"; private const string k_TestSceneToLoad = "ParentingInSceneObjects"; - private const string k_NestedUndeGameObjectName = "RootParent_GameObject"; private const int k_NumIterationsDeparentReparent = 100; private const float k_AproximateThresholdValue = 0.001f; diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/Resources.meta b/testproject/Assets/Tests/Runtime/ObjectParenting/Resources.meta new file mode 100644 index 0000000000..dbf62d950b --- /dev/null +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/Resources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6d5e70d978cb18b4cbca95b8aa1aff40 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/Resources/PlayerNestedTransforms.prefab b/testproject/Assets/Tests/Runtime/ObjectParenting/Resources/PlayerNestedTransforms.prefab new file mode 100644 index 0000000000..9eb666bcdf --- /dev/null +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/Resources/PlayerNestedTransforms.prefab @@ -0,0 +1,515 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &772585991204072682 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1522619104359096714} + - component: {fileID: 6327137497379236391} + - component: {fileID: 2204728973112518521} + - component: {fileID: 1866518356433686547} + - component: {fileID: 2645854474244200924} + m_Layer: 0 + m_Name: ChildOne + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1522619104359096714 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 772585991204072682} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 2, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 296612175404815447} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &6327137497379236391 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 772585991204072682} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 62ff4693d71b55440a645bbccd83ac8a, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &2204728973112518521 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 772585991204072682} + m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} +--- !u!114 &1866518356433686547 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 772585991204072682} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0d8ad30fca3f9a240bdce16f0166033b, type: 3} + m_Name: + m_EditorClassIdentifier: + RotationSpeed: 5 +--- !u!114 &2645854474244200924 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 772585991204072682} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: cf01cca54b77c0241ad6d9da5ef6a709, type: 3} + m_Name: + m_EditorClassIdentifier: + SyncPositionX: 1 + SyncPositionY: 1 + SyncPositionZ: 1 + SyncRotAngleX: 1 + SyncRotAngleY: 1 + SyncRotAngleZ: 1 + SyncScaleX: 1 + SyncScaleY: 1 + SyncScaleZ: 1 + PositionThreshold: 0.001 + RotAngleThreshold: 0.01 + ScaleThreshold: 0.01 + InLocalSpace: 0 + Interpolate: 1 +--- !u!1 &2771624607751045562 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5485086383386216104} + m_Layer: 0 + m_Name: FirstLevel + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &5485086383386216104 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2771624607751045562} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 4974009855568796650} + m_Father: {fileID: 296612175404815447} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &4147667212972069939 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7199215624742357828} + - component: {fileID: 8372809022110481992} + m_Layer: 0 + m_Name: PlayerView + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &7199215624742357828 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4147667212972069939} + m_LocalRotation: {x: 0.17364816, y: -0, z: -0, w: 0.9848078} + m_LocalPosition: {x: 0, y: 8, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 296612175404815447} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 20, y: 0, z: 0} +--- !u!20 &8372809022110481992 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4147667212972069939} + m_Enabled: 0 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_FocalLength: 50 + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!1 &6292214655028195304 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4974009855568796650} + - component: {fileID: 3062926429781172158} + - component: {fileID: 1595868777393624541} + - component: {fileID: 2914594030718603294} + - component: {fileID: 2888046383252644210} + m_Layer: 0 + m_Name: ChildTwo + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4974009855568796650 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6292214655028195304} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: -2, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 5485086383386216104} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &3062926429781172158 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6292214655028195304} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 62ff4693d71b55440a645bbccd83ac8a, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &1595868777393624541 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6292214655028195304} + m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} +--- !u!114 &2914594030718603294 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6292214655028195304} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0d8ad30fca3f9a240bdce16f0166033b, type: 3} + m_Name: + m_EditorClassIdentifier: + RotationSpeed: 5 +--- !u!114 &2888046383252644210 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6292214655028195304} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: cf01cca54b77c0241ad6d9da5ef6a709, type: 3} + m_Name: + m_EditorClassIdentifier: + SyncPositionX: 1 + SyncPositionY: 1 + SyncPositionZ: 1 + SyncRotAngleX: 1 + SyncRotAngleY: 1 + SyncRotAngleZ: 1 + SyncScaleX: 1 + SyncScaleY: 1 + SyncScaleZ: 1 + PositionThreshold: 0.001 + RotAngleThreshold: 0.01 + ScaleThreshold: 0.01 + InLocalSpace: 0 + Interpolate: 1 +--- !u!1001 &8977898853425847701 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 947981134, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, type: 3} + propertyPath: GlobalObjectIdHash + value: 951099334 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_RootOrder + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalScale.x + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalScale.y + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalScale.z + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767877, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_Interpolate + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767877, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_IsKinematic + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8685790303553767886, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + propertyPath: m_Name + value: PlayerNestedTransforms + objectReference: {fileID: 0} + m_RemovedComponents: + - {fileID: 8685790303553767876, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, type: 3} + - {fileID: 7138389085065872747, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, type: 3} + - {fileID: 2744080254494315543, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, type: 3} + - {fileID: 8685790303553767873, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, type: 3} + - {fileID: 8685790303553767877, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, type: 3} + m_SourcePrefab: {fileID: 100100000, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, type: 3} +--- !u!4 &296612175404815447 stripped +Transform: + m_CorrespondingSourceObject: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + m_PrefabInstance: {fileID: 8977898853425847701} + m_PrefabAsset: {fileID: 0} +--- !u!1 &296612175404815451 stripped +GameObject: + m_CorrespondingSourceObject: {fileID: 8685790303553767886, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + m_PrefabInstance: {fileID: 8977898853425847701} + m_PrefabAsset: {fileID: 0} +--- !u!114 &4389916208190318681 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 296612175404815451} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 482bb1f796fe43348bcbfd8161ed3825, type: 3} + m_Name: + m_EditorClassIdentifier: + ChildMovers: + - {fileID: 1866518356433686547} + - {fileID: 2914594030718603294} + TriggerDistanceToMove: 0.25 + PlayerCamera: {fileID: 8372809022110481992} +--- !u!114 &5376990732947334198 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 296612175404815451} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dfb1af1a9249278438d2daa2877ee2ad, type: 3} + m_Name: + m_EditorClassIdentifier: + SyncPositionX: 1 + SyncPositionY: 1 + SyncPositionZ: 1 + SyncRotAngleX: 0 + SyncRotAngleY: 1 + SyncRotAngleZ: 0 + SyncScaleX: 1 + SyncScaleY: 1 + SyncScaleZ: 1 + PositionThreshold: 0.001 + RotAngleThreshold: 0.01 + ScaleThreshold: 0.01 + InLocalSpace: 0 + Interpolate: 1 diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/Resources/PlayerNestedTransforms.prefab.meta b/testproject/Assets/Tests/Runtime/ObjectParenting/Resources/PlayerNestedTransforms.prefab.meta new file mode 100644 index 0000000000..39a6843e92 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/Resources/PlayerNestedTransforms.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 0a46b240b37c5b74786b1938e8fd2eae +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/ProjectSettings/EditorBuildSettings.asset b/testproject/ProjectSettings/EditorBuildSettings.asset index a1496f313c..4b0fb15a49 100644 --- a/testproject/ProjectSettings/EditorBuildSettings.asset +++ b/testproject/ProjectSettings/EditorBuildSettings.asset @@ -113,6 +113,12 @@ EditorBuildSettings: - enabled: 1 path: Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjects.unity guid: 49fd14bff1eceda4f9299721a9029750 + - enabled: 1 + path: Assets/Tests/Manual/NestedNetworkTransforms/NestedNetworkTransforms.unity + guid: 92b8cccf28cbaba40854a025b66e2ac3 + - enabled: 1 + path: Assets/Tests/Runtime/ObjectParenting/NestedNetworkTransformTestScene.unity + guid: 14235ed18eff0964cbb3ff7ae2ed1933 m_configObjects: com.unity.addressableassets: {fileID: 11400000, guid: 5a3d5c53c25349c48912726ae850f3b0, type: 2} From 756b93200427385a93d5aa0ca3f152f5e64a2d86 Mon Sep 17 00:00:00 2001 From: Sam Bellomo <71790295+SamuelBellomo@users.noreply.github.com> Date: Wed, 16 Nov 2022 12:39:47 -0500 Subject: [PATCH 131/138] fix: some small fixes while testing webgl (#2291) * better error handling. Adding IsNullOrEmpty checks Removing catching of the exception. With previous version, NGO still starts and IsClient is still set. With exception, StartHost fails now correctly when certificates are not set. * adding const strings for relay connection types (instead of having to guess) * adding test for null check * Address review comments Co-authored-by: Simon Lemay --- .../Runtime/Transports/UTP/UnityTransport.cs | 40 ++++++++----------- .../Editor/Transports/UnityTransportTests.cs | 35 ++++++++++++++++ .../com.unity.netcode.editortests.asmdef | 7 +++- 3 files changed, 58 insertions(+), 24 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index 720aa1bf0e..84eeed7d0b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -1471,36 +1471,30 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, } else { - try + if (NetworkManager.IsServer) { - if (NetworkManager.IsServer) + if (String.IsNullOrEmpty(m_ServerCertificate) || String.IsNullOrEmpty(m_ServerPrivateKey)) { - if (m_ServerCertificate.Length == 0 || m_ServerPrivateKey.Length == 0) - { - throw new Exception("In order to use encrypted communications, when hosting, you must set the server certificate and key."); - } - m_NetworkSettings.WithSecureServerParameters(m_ServerCertificate, m_ServerPrivateKey); + throw new Exception("In order to use encrypted communications, when hosting, you must set the server certificate and key."); + } + + m_NetworkSettings.WithSecureServerParameters(m_ServerCertificate, m_ServerPrivateKey); + } + else + { + if (String.IsNullOrEmpty(m_ServerCommonName)) + { + throw new Exception("In order to use encrypted communications, clients must set the server common name."); + } + else if (String.IsNullOrEmpty(m_ClientCaCertificate)) + { + m_NetworkSettings.WithSecureClientParameters(m_ServerCommonName); } else { - if (m_ServerCommonName.Length == 0) - { - throw new Exception("In order to use encrypted communications, clients must set the server common name."); - } - else if (m_ClientCaCertificate == null) - { - m_NetworkSettings.WithSecureClientParameters(m_ServerCommonName); - } - else - { - m_NetworkSettings.WithSecureClientParameters(m_ClientCaCertificate, m_ServerCommonName); - } + m_NetworkSettings.WithSecureClientParameters(m_ClientCaCertificate, m_ServerCommonName); } } - catch(Exception e) - { - Debug.LogException(e, this); - } } } #endif diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs index fa53588973..5303974c4e 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs @@ -126,5 +126,40 @@ public void UnityTransport_RestartSucceedsAfterFailure() transport.Shutdown(); } + +#if UTP_TRANSPORT_2_0_ABOVE + [Test] + public void UnityTransport_EmptySecurityStringsShouldThrow([Values("", null)] string cert, [Values("", null)] string secret) + { + var supportingGO = new GameObject(); + try + { + var networkManager = supportingGO.AddComponent(); // NM is required for UTP to work with certificates. + networkManager.NetworkConfig = new NetworkConfig(); + UnityTransport transport = supportingGO.AddComponent(); + networkManager.NetworkConfig.NetworkTransport = transport; + transport.Initialize(); + transport.SetServerSecrets(serverCertificate: cert, serverPrivateKey: secret); + + // Use encryption, but don't set certificate and check for exception + transport.UseEncryption = true; + Assert.Throws(() => + { + networkManager.StartServer(); + }); + // Make sure StartServer failed + Assert.False(transport.NetworkDriver.IsCreated); + Assert.False(networkManager.IsServer); + Assert.False(networkManager.IsListening); + } + finally + { + if (supportingGO != null) + { + Object.DestroyImmediate(supportingGO); + } + } + } +#endif } } diff --git a/com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef b/com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef index 8bfcba5bca..7d2b61320e 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef +++ b/com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef @@ -32,6 +32,11 @@ "name": "Unity", "expression": "(0,2022.2.0a5)", "define": "UNITY_UNET_PRESENT" + }, + { + "name": "com.unity.transport", + "expression": "2.0.0-exp", + "define": "UTP_TRANSPORT_2_0_ABOVE" } ] -} \ No newline at end of file +} From b58e39535c1d0e1ec7e2d092aebe20187c43d979 Mon Sep 17 00:00:00 2001 From: Jeffrey Rainy Date: Thu, 17 Nov 2022 08:34:19 -0500 Subject: [PATCH 132/138] fix: change ownership of owner-read network variables mtt-5009 #2286 (#2301) --- .../Runtime/Spawning/NetworkSpawnManager.cs | 3 + .../Tests/Runtime/DeferredMessagingTests.cs | 14 ++-- .../Tests/Runtime/NetworkShowHideTests.cs | 66 +++++++++++++++++++ 3 files changed, 79 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 470317ee60..9175b295a9 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -270,6 +270,9 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId) networkObject.OwnerClientId = clientId; + networkObject.MarkVariablesDirty(true); + NetworkManager.BehaviourUpdater.AddForUpdate(networkObject); + // Server adds entries for all client ownership UpdateOwnershipTable(networkObject, networkObject.OwnerClientId); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs index 6c63358389..a5192c0c4d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs @@ -794,7 +794,11 @@ public IEnumerator WhenSpawnTriggeredMessagesAreDeferredBeforeThePrefabIsAdded_A serverObject.GetComponent().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); - yield return WaitForAllClientsToReceive(); + yield return WaitForAllClientsToReceive(); + + // wait three ticks + yield return NetcodeIntegrationTestHelpers.WaitForTicks(m_ServerNetworkManager, 3); + yield return NetcodeIntegrationTestHelpers.WaitForTicks(m_ClientNetworkManagers[0], 3); // Validate messages are deferred and pending foreach (var client in m_ClientNetworkManagers) @@ -802,9 +806,11 @@ public IEnumerator WhenSpawnTriggeredMessagesAreDeferredBeforeThePrefabIsAdded_A var manager = (TestDeferredMessageManager)client.DeferredMessageManager; Assert.IsTrue(manager.DeferMessageCalled); Assert.IsFalse(manager.ProcessTriggersCalled); - Assert.AreEqual(4, manager.DeferredMessageCountTotal()); - Assert.AreEqual(3, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn)); - Assert.AreEqual(3, manager.DeferredMessageCountForKey(IDeferredMessageManager.TriggerType.OnSpawn, serverObject.GetComponent().NetworkObjectId)); + + Assert.AreEqual(5, manager.DeferredMessageCountTotal()); + + Assert.AreEqual(4, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn)); + Assert.AreEqual(4, manager.DeferredMessageCountForKey(IDeferredMessageManager.TriggerType.OnSpawn, serverObject.GetComponent().NetworkObjectId)); Assert.AreEqual(1, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnAddPrefab)); Assert.AreEqual(1, manager.DeferredMessageCountForKey(IDeferredMessageManager.TriggerType.OnAddPrefab, serverObject.GetComponent().GlobalObjectIdHash)); AddPrefabsToClient(client); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs index e8aebb5e98..fe6d763440 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs @@ -13,6 +13,7 @@ public class ShowHideObject : NetworkBehaviour public static List ClientTargetedNetworkObjects = new List(); public static ulong ClientIdToTarget; public static bool Silent; + public static int ValueAfterOwnershipChange = 0; public static NetworkObject GetNetworkObjectById(ulong networkObjectId) { @@ -57,13 +58,35 @@ public override void OnNetworkDespawn() public NetworkVariable MyNetworkVariable; public NetworkList MyListSetOnSpawn; + public NetworkVariable MyOwnerReadNetworkVariable; + static public NetworkManager NetworkManagerOfInterest; + + internal static int GainOwnershipCount = 0; private void Awake() { + // Debug.Log($"Awake {NetworkManager.LocalClientId}"); MyNetworkVariable = new NetworkVariable(); MyNetworkVariable.OnValueChanged += Changed; MyListSetOnSpawn = new NetworkList(); + + MyOwnerReadNetworkVariable = new NetworkVariable(readPerm: NetworkVariableReadPermission.Owner); + MyOwnerReadNetworkVariable.OnValueChanged += OwnerReadChanged; + } + + public override void OnGainedOwnership() + { + GainOwnershipCount++; + base.OnGainedOwnership(); + } + + public void OwnerReadChanged(int before, int after) + { + if (NetworkManager == NetworkManagerOfInterest) + { + ValueAfterOwnershipChange = after; + } } public void Changed(int before, int after) @@ -328,5 +351,48 @@ public IEnumerator NetworkHideChangeOwnership() Assert.True(ShowHideObject.ClientTargetedNetworkObjects[0].OwnerClientId == m_ClientNetworkManagers[0].LocalClientId); } + + [UnityTest] + public IEnumerator NetworkHideChangeOwnershipNotHidden() + { + ShowHideObject.ClientTargetedNetworkObjects.Clear(); + ShowHideObject.ClientIdToTarget = m_ClientNetworkManagers[1].LocalClientId; + ShowHideObject.Silent = true; + + var spawnedObject1 = SpawnObject(m_PrefabToSpawn, m_ServerNetworkManager); + m_NetSpawnedObject1 = spawnedObject1.GetComponent(); + + // wait for host to have spawned and gained ownership + while (ShowHideObject.GainOwnershipCount == 0) + { + yield return new WaitForSeconds(0.0f); + } + + // change the value + m_NetSpawnedObject1.GetComponent().MyOwnerReadNetworkVariable.Value++; + + // wait for three ticks + yield return NetcodeIntegrationTestHelpers.WaitForTicks(m_ServerNetworkManager, 3); + + // check we'll actually be changing owners + Assert.False(ShowHideObject.ClientTargetedNetworkObjects[0].OwnerClientId == m_ClientNetworkManagers[0].LocalClientId); + + // only check for value change on one specific client + ShowHideObject.NetworkManagerOfInterest = m_ClientNetworkManagers[0]; + + // change ownership + m_NetSpawnedObject1.ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); + + // wait three ticks + yield return NetcodeIntegrationTestHelpers.WaitForTicks(m_ServerNetworkManager, 3); + yield return NetcodeIntegrationTestHelpers.WaitForTicks(m_ClientNetworkManagers[0], 3); + + // verify ownership changed + Assert.True(ShowHideObject.ClientTargetedNetworkObjects[0].OwnerClientId == m_ClientNetworkManagers[0].LocalClientId); + + // verify the expected client got the OnValueChanged. (Only client 1 sets this value) + Assert.True(ShowHideObject.ValueAfterOwnershipChange == 1); + } + } } From 9bb5a663d1e10e389261ff3b5897bb0e59abc360 Mon Sep 17 00:00:00 2001 From: Gregory Kim <114025876+kiyoji23@users.noreply.github.com> Date: Thu, 17 Nov 2022 15:53:31 -0500 Subject: [PATCH 133/138] chore: updating metafile transport to 2.0.0-pre.2 (#2311) updating metafile transport to 2.0.0-pre.2 --- .yamato/project.metafile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.yamato/project.metafile b/.yamato/project.metafile index ef1ad1c4e6..1d4186e706 100644 --- a/.yamato/project.metafile +++ b/.yamato/project.metafile @@ -69,7 +69,7 @@ projects: # Package dependencies dependencies: - name: com.unity.transport - version: 2.0.0-exp.7 + version: 2.0.0-pre.2 test_editors: - 2022.2 - trunk From 87e9026d888b9c5a5b96c66db31a717ab4a08f7d Mon Sep 17 00:00:00 2001 From: ashwinimurt Date: Fri, 18 Nov 2022 11:04:28 -0800 Subject: [PATCH 134/138] chore: Bump BGO version to 1.2.0 --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 +- com.unity.netcode.gameobjects/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index ea2c6243b9..0682f5943a 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). -## [Unreleased] +## [1.2.0] - 2022-11-21 ### Added diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index c1d7cb20a3..e4b384df75 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -2,7 +2,7 @@ "name": "com.unity.netcode.gameobjects", "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", - "version": "1.1.0", + "version": "1.2.0", "unity": "2020.3", "dependencies": { "com.unity.nuget.mono-cecil": "1.10.1", From 1e3a796147b0b46b34f6d13eb450858c2bcc2dae Mon Sep 17 00:00:00 2001 From: Kitty Draper <284434+ShadauxCat@users.noreply.github.com> Date: Mon, 21 Nov 2022 16:22:44 -0600 Subject: [PATCH 135/138] fix: Fixes for Deferred Messaging Tests [MTT-5102] (#2319) --- .../TestHelpers/Runtime/MessageHooks.cs | 4 ++-- .../TestHelpers/Runtime/NetcodeIntegrationTest.cs | 5 +++++ .../Tests/Runtime/DeferredMessagingTests.cs | 14 ++++++-------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/MessageHooks.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/MessageHooks.cs index 23f5f6c89a..9be56d943a 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/MessageHooks.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/MessageHooks.cs @@ -44,7 +44,7 @@ public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int message public void OnAfterReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) { - if (!CurrentMessageHasTriggerdAHook && IsWaiting && (HandleCheck == null || HandleCheck.Invoke(messageType))) + if (!CurrentMessageHasTriggerdAHook && IsWaiting && ReceiptCheck != null && ReceiptCheck.Invoke(messageType)) { IsWaiting = false; CurrentMessageHasTriggerdAHook = true; @@ -92,7 +92,7 @@ public void OnBeforeHandleMessage(ref T message, ref NetworkContext context) public void OnAfterHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage { - if (!CurrentMessageHasTriggerdAHook && IsWaiting && (HandleCheck == null || HandleCheck.Invoke(message))) + if (!CurrentMessageHasTriggerdAHook && IsWaiting && HandleCheck != null && HandleCheck.Invoke(message)) { IsWaiting = false; CurrentMessageHasTriggerdAHook = true; diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index c924678fc4..959f6c9719 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -331,6 +331,11 @@ protected IEnumerator CreateAndStartNewClient() NetcodeIntegrationTestHelpers.StartOneClient(networkManager); + if (LogAllMessages) + { + networkManager.MessagingSystem.Hook(new DebugNetworkHooks()); + } + AddRemoveNetworkManager(networkManager, true); OnNewClientStarted(networkManager); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs index a5192c0c4d..33ae50021a 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs @@ -639,6 +639,8 @@ bool HaveAllClientsSpawned() } } + protected override bool LogAllMessages => true; + [UnityTest] public IEnumerator WhenMultipleSpawnTriggeredMessagesAreDeferred_TheyAreAllProcessedOnSpawn() { @@ -658,7 +660,7 @@ public IEnumerator WhenMultipleSpawnTriggeredMessagesAreDeferred_TheyAreAllProce serverObject.GetComponent().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); - yield return WaitForAllClientsToReceive(); + yield return WaitForAllClientsToReceive(); foreach (var client in m_ClientNetworkManagers) { @@ -666,9 +668,9 @@ public IEnumerator WhenMultipleSpawnTriggeredMessagesAreDeferred_TheyAreAllProce Assert.IsTrue(manager.DeferMessageCalled); Assert.IsFalse(manager.ProcessTriggersCalled); - Assert.AreEqual(3, manager.DeferredMessageCountTotal()); - Assert.AreEqual(3, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn)); - Assert.AreEqual(3, manager.DeferredMessageCountForKey(IDeferredMessageManager.TriggerType.OnSpawn, serverObject.GetComponent().NetworkObjectId)); + Assert.AreEqual(4, manager.DeferredMessageCountTotal()); + Assert.AreEqual(4, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn)); + Assert.AreEqual(4, manager.DeferredMessageCountForKey(IDeferredMessageManager.TriggerType.OnSpawn, serverObject.GetComponent().NetworkObjectId)); Assert.AreEqual(0, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnAddPrefab)); AddPrefabsToClient(client); } @@ -796,10 +798,6 @@ public IEnumerator WhenSpawnTriggeredMessagesAreDeferredBeforeThePrefabIsAdded_A yield return WaitForAllClientsToReceive(); - // wait three ticks - yield return NetcodeIntegrationTestHelpers.WaitForTicks(m_ServerNetworkManager, 3); - yield return NetcodeIntegrationTestHelpers.WaitForTicks(m_ClientNetworkManagers[0], 3); - // Validate messages are deferred and pending foreach (var client in m_ClientNetworkManagers) { From 3eec1025bf7fdc3dcfc0bbcce6d45d11639cb47e Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 1 Dec 2022 11:50:18 -0600 Subject: [PATCH 136/138] chore: migrating PR-2320 (FindObjectsByType) into v1.2.0 release branch (#2325) fix: This fixes the issue with recent 2023 update where FindObjectsOfType was renamed to FindObjectsByType (#2320) * update Adding the 2023.1.a21 FindObjectsByType fix for NGO using the UNITY_2023_1_OR_NEWER define. Modified a few of the FindObjectsSortMode for areas that looked like they might require their results ordered by InstanceID. --- .../Editor/NetworkManagerHelper.cs | 4 ++++ .../SceneManagement/NetworkSceneManager.cs | 4 ++++ .../Runtime/SceneManagement/SceneEventData.cs | 15 ++++++++++++ .../Runtime/Spawning/NetworkSpawnManager.cs | 16 +++++++++++++ .../Runtime/IntegrationTestSceneHandler.cs | 5 ++++ .../Runtime/NetcodeIntegrationTest.cs | 24 ++++++++++++++++++- .../Tests/Runtime/AddNetworkPrefabTests.cs | 8 +++++-- .../Tests/Runtime/DeferredMessagingTests.cs | 17 ++++++++++--- .../Tests/Runtime/IntegrationTestExamples.cs | 13 +++++++++- ...orkObjectNetworkClientOwnedObjectsTests.cs | 5 ++++ .../Runtime/NetworkPrefabHandlerTests.cs | 5 ++++ .../Tests/Runtime/NetworkVisibilityTests.cs | 5 ++++ .../NetworkManagerMonitor.cs | 4 ++++ .../Assets/Tests/Runtime/AddressablesTests.cs | 13 ++++++++++ .../Tests/Runtime/DontDestroyOnLoadTests.cs | 5 ++++ .../NetworkVariablePerformanceTests.cs | 6 +++++ .../Tests/Runtime/NetworkManagerTests.cs | 5 +++- ...ariableInitializationOnNetworkSpawnTest.cs | 13 +++++++++- .../SceneObjectsNotDestroyedOnShutdownTest.cs | 8 +++++++ 19 files changed, 166 insertions(+), 9 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs index 4c4aabdd5d..c7d13b0e9c 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs @@ -65,7 +65,11 @@ private static void ScenesInBuildActiveSceneCheck() var scenesList = EditorBuildSettings.scenes.ToList(); var activeScene = SceneManager.GetActiveScene(); var isSceneInBuildSettings = scenesList.Count((c) => c.path == activeScene.path) == 1; +#if UNITY_2023_1_OR_NEWER + var networkManager = Object.FindFirstObjectByType(); +#else var networkManager = Object.FindObjectOfType(); +#endif if (!isSceneInBuildSettings && networkManager != null) { if (networkManager.NetworkConfig != null && networkManager.NetworkConfig.EnableSceneManagement) diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index c91cee28e9..781326f962 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -2018,7 +2018,11 @@ internal void PopulateScenePlacedObjects(Scene sceneToFilterBy, bool clearSceneP ScenePlacedObjects.Clear(); } +#if UNITY_2023_1_OR_NEWER + var networkObjects = UnityEngine.Object.FindObjectsByType(FindObjectsSortMode.InstanceID); +#else var networkObjects = UnityEngine.Object.FindObjectsOfType(); +#endif // Just add every NetworkObject found that isn't already in the list // With additive scenes, we can have multiple in-scene placed NetworkObjects with the same GlobalObjectIdHash value diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs index 8ccb84f725..f4c9927180 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs @@ -269,7 +269,12 @@ internal void AddDespawnedInSceneNetworkObjects() { m_DespawnedInSceneObjectsSync.Clear(); // Find all active and non-active in-scene placed NetworkObjects +#if UNITY_2023_1_OR_NEWER + var inSceneNetworkObjects = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.InstanceID).Where((c) => c.NetworkManager == m_NetworkManager); +#else var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType(includeInactive: true).Where((c) => c.NetworkManager == m_NetworkManager); + +#endif foreach (var sobj in inSceneNetworkObjects) { if (sobj.IsSceneObject.HasValue && sobj.IsSceneObject.Value && !sobj.IsSpawned) @@ -657,7 +662,11 @@ internal void ReadClientReSynchronizationData(FastBufferReader reader) if (networkObjectsToRemove.Length > 0) { +#if UNITY_2023_1_OR_NEWER + var networkObjects = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsSortMode.InstanceID); +#else var networkObjects = UnityEngine.Object.FindObjectsOfType(); +#endif var networkObjectIdToNetworkObject = new Dictionary(); foreach (var networkObject in networkObjects) { @@ -785,8 +794,14 @@ private void DeserializeDespawnedInScenePlacedNetworkObjects() var objectRelativeScene = m_NetworkManager.SceneManager.ScenesLoaded[localSceneHandle]; // Find all active and non-active in-scene placed NetworkObjects +#if UNITY_2023_1_OR_NEWER + var inSceneNetworkObjects = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.InstanceID).Where((c) => + c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList(); +#else var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType(includeInactive: true).Where((c) => c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList(); +#endif + foreach (var inSceneObject in inSceneNetworkObjects) { diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 9175b295a9..b354ffe831 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -658,7 +658,11 @@ internal void DespawnObject(NetworkObject networkObject, bool destroyObject = fa // Makes scene objects ready to be reused internal void ServerResetShudownStateForSceneObjects() { +#if UNITY_2023_1_OR_NEWER + var networkObjects = UnityEngine.Object.FindObjectsByType(FindObjectsSortMode.InstanceID).Where((c) => c.IsSceneObject != null && c.IsSceneObject == true); +#else var networkObjects = UnityEngine.Object.FindObjectsOfType().Where((c) => c.IsSceneObject != null && c.IsSceneObject == true); +#endif foreach (var sobj in networkObjects) { sobj.IsSpawned = false; @@ -689,7 +693,11 @@ internal void ServerDestroySpawnedSceneObjects() internal void DespawnAndDestroyNetworkObjects() { +#if UNITY_2023_1_OR_NEWER + var networkObjects = UnityEngine.Object.FindObjectsByType(FindObjectsSortMode.InstanceID); +#else var networkObjects = UnityEngine.Object.FindObjectsOfType(); +#endif for (int i = 0; i < networkObjects.Length; i++) { @@ -719,7 +727,11 @@ internal void DespawnAndDestroyNetworkObjects() internal void DestroySceneObjects() { +#if UNITY_2023_1_OR_NEWER + var networkObjects = UnityEngine.Object.FindObjectsByType(FindObjectsSortMode.InstanceID); +#else var networkObjects = UnityEngine.Object.FindObjectsOfType(); +#endif for (int i = 0; i < networkObjects.Length; i++) { @@ -746,7 +758,11 @@ internal void DestroySceneObjects() internal void ServerSpawnSceneObjectsOnStartSweep() { +#if UNITY_2023_1_OR_NEWER + var networkObjects = UnityEngine.Object.FindObjectsByType(FindObjectsSortMode.InstanceID); +#else var networkObjects = UnityEngine.Object.FindObjectsOfType(); +#endif var networkObjectsToSpawn = new List(); for (int i = 0; i < networkObjects.Length; i++) diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs index 15b6bc13f9..80e09c88db 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs @@ -147,7 +147,12 @@ private static void SceneManager_sceneLoaded(Scene scene, LoadSceneMode loadScen private static void ProcessInSceneObjects(Scene scene, NetworkManager networkManager) { // Get all in-scene placed NeworkObjects that were instantiated when this scene loaded +#if UNITY_2023_1_OR_NEWER + var inSceneNetworkObjects = Object.FindObjectsByType(FindObjectsSortMode.InstanceID).Where((c) => c.IsSceneObject != false && c.GetSceneOriginHandle() == scene.handle); +#else var inSceneNetworkObjects = Object.FindObjectsOfType().Where((c) => c.IsSceneObject != false && c.GetSceneOriginHandle() == scene.handle); +#endif + foreach (var sobj in inSceneNetworkObjects) { if (sobj.NetworkManagerOwner != networkManager) diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index 959f6c9719..9103f33528 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -442,8 +442,13 @@ private void ClientNetworkManagerPostStart(NetworkManager networkManager) m_PlayerNetworkObjects.Add(networkManager.LocalClientId, new Dictionary()); } +#if UNITY_2023_1_OR_NEWER + // Get all player instances for the current client NetworkManager instance + var clientPlayerClones = Object.FindObjectsByType(FindObjectsSortMode.None).Where((c) => c.IsPlayerObject && c.OwnerClientId == networkManager.LocalClientId).ToList(); +#else // Get all player instances for the current client NetworkManager instance var clientPlayerClones = Object.FindObjectsOfType().Where((c) => c.IsPlayerObject && c.OwnerClientId == networkManager.LocalClientId).ToList(); +#endif // Add this player instance to each client player entry foreach (var playerNetworkObject in clientPlayerClones) { @@ -457,9 +462,13 @@ private void ClientNetworkManagerPostStart(NetworkManager networkManager) m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(networkManager.LocalClientId, playerNetworkObject); } } - +#if UNITY_2023_1_OR_NEWER + // For late joining clients, add the remaining (if any) cloned versions of each client's player + clientPlayerClones = Object.FindObjectsByType(FindObjectsSortMode.None).Where((c) => c.IsPlayerObject && c.NetworkManager == networkManager).ToList(); +#else // For late joining clients, add the remaining (if any) cloned versions of each client's player clientPlayerClones = Object.FindObjectsOfType().Where((c) => c.IsPlayerObject && c.NetworkManager == networkManager).ToList(); +#endif foreach (var playerNetworkObject in clientPlayerClones) { if (!m_PlayerNetworkObjects[networkManager.LocalClientId].ContainsKey(playerNetworkObject.OwnerClientId)) @@ -479,7 +488,11 @@ protected void ClientNetworkManagerPostStartInit() } if (m_UseHost) { +#if UNITY_2023_1_OR_NEWER + var clientSideServerPlayerClones = Object.FindObjectsByType(FindObjectsSortMode.None).Where((c) => c.IsPlayerObject && c.OwnerClientId == m_ServerNetworkManager.LocalClientId); +#else var clientSideServerPlayerClones = Object.FindObjectsOfType().Where((c) => c.IsPlayerObject && c.OwnerClientId == m_ServerNetworkManager.LocalClientId); +#endif foreach (var playerNetworkObject in clientSideServerPlayerClones) { // When the server is not the host this needs to be done @@ -535,8 +548,13 @@ protected IEnumerator StartServerAndClients() if (m_UseHost || m_ServerNetworkManager.IsHost) { +#if UNITY_2023_1_OR_NEWER + // Add the server player instance to all m_ClientSidePlayerNetworkObjects entries + var serverPlayerClones = Object.FindObjectsByType(FindObjectsSortMode.None).Where((c) => c.IsPlayerObject && c.OwnerClientId == m_ServerNetworkManager.LocalClientId); +#else // Add the server player instance to all m_ClientSidePlayerNetworkObjects entries var serverPlayerClones = Object.FindObjectsOfType().Where((c) => c.IsPlayerObject && c.OwnerClientId == m_ServerNetworkManager.LocalClientId); +#endif foreach (var playerNetworkObject in serverPlayerClones) { if (!m_PlayerNetworkObjects.ContainsKey(playerNetworkObject.NetworkManager.LocalClientId)) @@ -726,7 +744,11 @@ protected virtual bool CanDestroyNetworkObject(NetworkObject networkObject) /// protected void DestroySceneNetworkObjects() { +#if UNITY_2023_1_OR_NEWER + var networkObjects = Object.FindObjectsByType(FindObjectsSortMode.InstanceID); +#else var networkObjects = Object.FindObjectsOfType(); +#endif foreach (var networkObject in networkObjects) { // This can sometimes be null depending upon order of operations diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/AddNetworkPrefabTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/AddNetworkPrefabTests.cs index 5ed703be26..57f179127d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/AddNetworkPrefabTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/AddNetworkPrefabTests.cs @@ -45,14 +45,18 @@ protected override void OnServerAndClientsCreated() private EmptyComponent GetObjectForClient(ulong clientId) { - foreach (var component in Object.FindObjectsOfType()) +#if UNITY_2023_1_OR_NEWER + var emptyComponents = Object.FindObjectsByType(FindObjectsSortMode.InstanceID); +#else + var emptyComponents = Object.FindObjectsOfType(); +#endif + foreach (var component in emptyComponents) { if (component.IsSpawned && component.NetworkManager.LocalClientId == clientId) { return component; } } - return null; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs index 33ae50021a..919195f3e6 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs @@ -271,14 +271,19 @@ private IEnumerator SpawnClients(bool clearTestDeferredMessageManagerCallFlags = private T GetComponentForClient(ulong clientId) where T : NetworkBehaviour { - foreach (var component in Object.FindObjectsOfType()) +#if UNITY_2023_1_OR_NEWER + var componentsToFind = Object.FindObjectsByType(FindObjectsSortMode.InstanceID); +#else + var componentsToFind = Object.FindObjectsOfType(); +#endif + + foreach (var component in componentsToFind) { if (component.IsSpawned && component.NetworkManager.LocalClientId == clientId) { return component; } } - return null; } @@ -753,7 +758,13 @@ bool HaveAllClientsSpawned() { var found1 = false; var found2 = false; - foreach (var component in Object.FindObjectsOfType()) +#if UNITY_2023_1_OR_NEWER + var deferredMessageTestRpcComponents = Object.FindObjectsByType(FindObjectsSortMode.None); +#else + var deferredMessageTestRpcComponents = Object.FindObjectsOfType(); +#endif + + foreach (var component in deferredMessageTestRpcComponents) { if (component.IsSpawned && component.NetworkManager.LocalClientId == client.LocalClientId) { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/IntegrationTestExamples.cs b/com.unity.netcode.gameobjects/Tests/Runtime/IntegrationTestExamples.cs index d67030bf9f..0556a1c71b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/IntegrationTestExamples.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/IntegrationTestExamples.cs @@ -29,10 +29,15 @@ public IEnumerator MyFirstIntegationTest() { // Check the condition for this test and automatically handle varying processing // environments and conditions +#if UNITY_2023_1_OR_NEWER + yield return WaitForConditionOrTimeOut(() => + Object.FindObjectsByType(FindObjectsSortMode.None).Where( + (c) => c.IsSpawned).Count() == 2); +#else yield return WaitForConditionOrTimeOut(() => Object.FindObjectsOfType().Where( (c) => c.IsSpawned).Count() == 2); - +#endif Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for instances " + "to be detected!"); } @@ -64,9 +69,15 @@ public IEnumerator MyFirstIntegationTest() { // Check the condition for this test and automatically handle varying processing // environments and conditions +#if UNITY_2023_1_OR_NEWER + yield return WaitForConditionOrTimeOut(() => + Object.FindObjectsByType(FindObjectsSortMode.None).Where( + (c) => c.IsSpawned).Count() == 2); +#else yield return WaitForConditionOrTimeOut(() => Object.FindObjectsOfType().Where( (c) => c.IsSpawned).Count() == 2); +#endif Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for instances " + "to be detected!"); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectNetworkClientOwnedObjectsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectNetworkClientOwnedObjectsTests.cs index bcdd613102..bdd1623cf8 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectNetworkClientOwnedObjectsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectNetworkClientOwnedObjectsTests.cs @@ -89,7 +89,12 @@ public IEnumerator WhenOwnershipIsChanged_OwnershipValuesUpdateCorrectly() Assert.IsFalse(serverBehaviour.IsOwnedByServer); Assert.AreEqual(m_ClientNetworkManagers[0].LocalClientId, serverBehaviour.OwnerClientId); +#if UNITY_2023_1_OR_NEWER + var clientObject = Object.FindObjectsByType(FindObjectsSortMode.InstanceID).Where((obj) => obj.NetworkManagerOwner == m_ClientNetworkManagers[0]).FirstOrDefault(); +#else var clientObject = Object.FindObjectsOfType().Where((obj) => obj.NetworkManagerOwner == m_ClientNetworkManagers[0]).FirstOrDefault(); +#endif + Assert.IsNotNull(clientObject); Assert.IsTrue(clientObject.IsOwner); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkPrefabHandlerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkPrefabHandlerTests.cs index 600d9b12e0..091cddca94 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkPrefabHandlerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkPrefabHandlerTests.cs @@ -186,7 +186,12 @@ public void TearDown() //Stop, shutdown, and destroy NetworkManagerHelper.ShutdownNetworkManager(); +#if UNITY_2023_1_OR_NEWER + var networkObjects = UnityEngine.Object.FindObjectsByType(FindObjectsSortMode.InstanceID).ToList(); +#else var networkObjects = UnityEngine.Object.FindObjectsOfType().ToList(); +#endif + var networkObjectsList = networkObjects.Where(c => c.name.Contains(k_PrefabObjectName)); foreach (var networkObject in networkObjectsList) { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVisibilityTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVisibilityTests.cs index e9d65b5258..0f95a100ae 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVisibilityTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVisibilityTests.cs @@ -48,7 +48,12 @@ protected override IEnumerator OnServerAndClientsConnected() [UnityTest] public IEnumerator HiddenObjectsTest() { +#if UNITY_2023_1_OR_NEWER + yield return WaitForConditionOrTimeOut(() => Object.FindObjectsByType(FindObjectsSortMode.None).Where((c) => c.IsSpawned).Count() == 2); +#else yield return WaitForConditionOrTimeOut(() => Object.FindObjectsOfType().Where((c) => c.IsSpawned).Count() == 2); +#endif + Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for the visible object count to equal 2!"); } } diff --git a/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/NetworkManagerMonitor.cs b/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/NetworkManagerMonitor.cs index 42242c3278..807e591f4b 100644 --- a/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/NetworkManagerMonitor.cs +++ b/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/NetworkManagerMonitor.cs @@ -10,7 +10,11 @@ public class NetworkManagerMonitor : MonoBehaviour // Start is called before the first frame update private void Start() { +#if UNITY_2023_1_OR_NEWER + var networkManagerInstances = FindObjectsByType(FindObjectsSortMode.InstanceID); +#else var networkManagerInstances = FindObjectsOfType(); +#endif foreach (var instance in networkManagerInstances) { if (instance.IsListening) diff --git a/testproject/Assets/Tests/Runtime/AddressablesTests.cs b/testproject/Assets/Tests/Runtime/AddressablesTests.cs index 43df5588a3..6e45dfc7d4 100644 --- a/testproject/Assets/Tests/Runtime/AddressablesTests.cs +++ b/testproject/Assets/Tests/Runtime/AddressablesTests.cs @@ -67,7 +67,12 @@ private IEnumerator SpawnAndValidate(GameObject prefab, bool waitAndAddOnClient serverObj.GetComponent().NetworkManagerOwner = m_ServerNetworkManager; serverObj.GetComponent().Spawn(); +#if UNITY_2023_1_OR_NEWER + var objs = Object.FindObjectsByType(FindObjectsSortMode.InstanceID); +#else var objs = Object.FindObjectsOfType(); +#endif + // Prefabs loaded by addressables actually don't show up in this search. // Unlike other tests that make prefabs programmatically, those aren't added to the scene until they're instantiated Assert.AreEqual(1, objs.Length); @@ -80,7 +85,11 @@ private IEnumerator SpawnAndValidate(GameObject prefab, bool waitAndAddOnClient { // Since it's not added, after the CreateObjectMessage is received, it's not spawned yet // Verify that to be the case as a precondition. +#if UNITY_2023_1_OR_NEWER + objs = Object.FindObjectsByType(FindObjectsSortMode.InstanceID); +#else objs = Object.FindObjectsOfType(); +#endif Assert.AreEqual(1, objs.Length); yield return new WaitUntil(() => Time.realtimeSinceStartup - startTime >= m_ClientNetworkManagers[0].NetworkConfig.SpawnTimeout - 0.25); foreach (var client in m_ClientNetworkManagers) @@ -89,7 +98,11 @@ private IEnumerator SpawnAndValidate(GameObject prefab, bool waitAndAddOnClient } } +#if UNITY_2023_1_OR_NEWER + objs = Object.FindObjectsByType(FindObjectsSortMode.InstanceID); +#else objs = Object.FindObjectsOfType(); +#endif Assert.AreEqual(NumberOfClients + 1, objs.Length); foreach (var obj in objs) { diff --git a/testproject/Assets/Tests/Runtime/DontDestroyOnLoadTests.cs b/testproject/Assets/Tests/Runtime/DontDestroyOnLoadTests.cs index 5741f13f75..7b7e9f42f0 100644 --- a/testproject/Assets/Tests/Runtime/DontDestroyOnLoadTests.cs +++ b/testproject/Assets/Tests/Runtime/DontDestroyOnLoadTests.cs @@ -79,7 +79,12 @@ public IEnumerator Teardown() m_ServerNetworkManager = null; m_ClientNetworkManagers = null; +#if UNITY_2023_1_OR_NEWER + var networkObjects = Object.FindObjectsByType(FindObjectsSortMode.InstanceID); +#else var networkObjects = Object.FindObjectsOfType(); +#endif + foreach (var netObject in networkObjects) { Object.DestroyImmediate(netObject); diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs index ad03efff90..218c80d61a 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs @@ -182,7 +182,13 @@ void UpdateFunc(float deltaTime) yield return new ExecuteStepInContext(StepExecutionContext.Clients, nbObjectsBytes => { var nbObjectsParam = BitConverter.ToInt32(nbObjectsBytes, 0); +#if UNITY_2023_1_OR_NEWER + Assert.That(Object.FindObjectsByType(FindObjectsSortMode.None).Length, Is.EqualTo(nbObjectsParam + 1), "Wrong number of spawned objects client side"); // +1 for the prefab to spawn +#else Assert.That(Object.FindObjectsOfType(typeof(OneNetVar)).Length, Is.EqualTo(nbObjectsParam + 1), "Wrong number of spawned objects client side"); // +1 for the prefab to spawn +#endif + + }, paramToPass: BitConverter.GetBytes(nbObjects)); yield return new ExecuteStepInContext(StepExecutionContext.Server, bytes => { diff --git a/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs b/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs index 078d954464..4858e37936 100644 --- a/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs @@ -53,8 +53,11 @@ private bool TestComponentFound() { return false; } - +#if UNITY_2023_1_OR_NEWER + m_NetworkObjectTestComponent = Object.FindFirstObjectByType(); +#else m_NetworkObjectTestComponent = Object.FindObjectOfType(); +#endif if (m_NetworkObjectTestComponent == null) { return false; diff --git a/testproject/Assets/Tests/Runtime/NetworkVariableInitializationOnNetworkSpawnTest.cs b/testproject/Assets/Tests/Runtime/NetworkVariableInitializationOnNetworkSpawnTest.cs index fc15af0d6d..9260e2f3e0 100644 --- a/testproject/Assets/Tests/Runtime/NetworkVariableInitializationOnNetworkSpawnTest.cs +++ b/testproject/Assets/Tests/Runtime/NetworkVariableInitializationOnNetworkSpawnTest.cs @@ -88,7 +88,13 @@ private IEnumerator RunTest() const int expectedNetworkObjects = numClients + 2; // +2 = one for prefab, one for server. const int maxFrames = 240; var doubleCheckTime = Time.realtimeSinceStartup + 5.0f; - while (Object.FindObjectsOfType().Length != expectedNetworkObjects) +#if UNITY_2023_1_OR_NEWER + var networkObjects = Object.FindObjectsByType(FindObjectsSortMode.InstanceID); +#else + var networkObjects = Object.FindObjectsOfType(); +#endif + + while (networkObjects.Length != expectedNetworkObjects) { if (Time.frameCount > maxFrames) { @@ -102,6 +108,11 @@ private IEnumerator RunTest() } var nextFrameNumber = Time.frameCount + 1; yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber); +#if UNITY_2023_1_OR_NEWER + networkObjects = Object.FindObjectsByType(FindObjectsSortMode.InstanceID); +#else + networkObjects = Object.FindObjectsOfType(); +#endif } serverObject.GetComponent().Variable.Value = NetworkVariableInitOnNetworkSpawn.ExpectedSpawnValueOnClient; diff --git a/testproject/Assets/Tests/Runtime/SceneObjectsNotDestroyedOnShutdownTest.cs b/testproject/Assets/Tests/Runtime/SceneObjectsNotDestroyedOnShutdownTest.cs index 3efaa5b93f..58ac16cf64 100644 --- a/testproject/Assets/Tests/Runtime/SceneObjectsNotDestroyedOnShutdownTest.cs +++ b/testproject/Assets/Tests/Runtime/SceneObjectsNotDestroyedOnShutdownTest.cs @@ -49,7 +49,11 @@ public IEnumerator SceneObjectsNotDestroyedOnShutdown() yield return NetcodeIntegrationTest.WaitForConditionOrTimeOut(() => m_TestScene.IsValid() && m_TestScene.isLoaded, timeoutHelper); Assert.False(timeoutHelper.TimedOut, "Timed out waiting for scene to load!"); +#if UNITY_2023_1_OR_NEWER + var loadedInSceneObject = Object.FindObjectsByType(FindObjectsSortMode.InstanceID).Where((c) => c.name == k_SceneObjectName).FirstOrDefault(); +#else var loadedInSceneObject = Object.FindObjectsOfType().Where((c) => c.name == k_SceneObjectName).FirstOrDefault(); +#endif Assert.IsNotNull(loadedInSceneObject, $"Failed to find {k_SceneObjectName} before starting client!"); @@ -66,7 +70,11 @@ public IEnumerator SceneObjectsNotDestroyedOnShutdown() yield return m_DefaultWaitForTick; // Find the same object +#if UNITY_2023_1_OR_NEWER + loadedInSceneObject = Object.FindObjectsByType(FindObjectsSortMode.InstanceID).Where((c) => c.name == k_SceneObjectName).FirstOrDefault(); +#else loadedInSceneObject = Object.FindObjectsOfType().Where((c) => c.name == k_SceneObjectName).FirstOrDefault(); +#endif // Verify it still exists Assert.IsNotNull(loadedInSceneObject, $"Failed to find {k_SceneObjectName} after starting client!"); From 96c4d4048f30fca7353d0ab35b03ad114f176787 Mon Sep 17 00:00:00 2001 From: ashwini <36935028+ashwinimurt@users.noreply.github.com> Date: Wed, 7 Dec 2022 11:11:27 -0800 Subject: [PATCH 137/138] chore: Ignore BasicBuildTest on release/1.2.0 (#2339) --- com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs index c8cde89618..de63ce1df6 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs @@ -12,6 +12,7 @@ public class BuildTests public const string DefaultBuildScenePath = "Tests/Editor/Build/BuildTestScene.unity"; [Test] + [Ignore("Disabling this test on release/1.2.0 branch due to Burst failures caused when running with upm ci")] public void BasicBuildTest() { var execAssembly = Assembly.GetExecutingAssembly(); From 4634aa114649d6b7539fc127bd400b511afb4c38 Mon Sep 17 00:00:00 2001 From: Jakob Ruckel Date: Mon, 11 Jul 2022 09:54:34 +0200 Subject: [PATCH 138/138] Some Debug output to investigate a crash --- .../Components/NetworkTransform.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index d9e8393f34..bb389b4aeb 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -544,9 +544,14 @@ private void ResetInterpolatedStateToCurrentAuthoritativeState() { var serverTime = NetworkManager.ServerTime.Time; var position = InLocalSpace ? transform.localPosition : transform.position; - m_PositionXInterpolator.ResetTo(position.x, serverTime); - m_PositionYInterpolator.ResetTo(position.y, serverTime); - m_PositionZInterpolator.ResetTo(position.z, serverTime); + try { + m_PositionXInterpolator.ResetTo(position.x, serverTime); + m_PositionYInterpolator.ResetTo(position.y, serverTime); + m_PositionZInterpolator.ResetTo(position.z, serverTime); + } catch(Exception e) { + Debug.LogError(gameObject.name + " will crash :("); + throw e; + } var rotation = InLocalSpace ? transform.localRotation : transform.rotation; m_RotationInterpolator.ResetTo(rotation, serverTime);