From 912922a0b52d01593055b360c3c1cba4fe8aff6c Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 27 Oct 2025 09:29:03 -0500 Subject: [PATCH 01/10] Merge branch 'release/2.7.0' of https://github.com/Unity-Technologies/com.unity.netcode.gameobjects into release/2.7.0 --- .yamato/wrench/preview-a-p-v.yml | 163 ++++++++++++++++++++++++ .yamato/wrench/validation-jobs.yml | 198 +++++++++++++++++++++++++++++ 2 files changed, 361 insertions(+) diff --git a/.yamato/wrench/preview-a-p-v.yml b/.yamato/wrench/preview-a-p-v.yml index be962fed7c..7b8d9efbe4 100644 --- a/.yamato/wrench/preview-a-p-v.yml +++ b/.yamato/wrench/preview-a-p-v.yml @@ -839,3 +839,166 @@ preview_apv_-_6000_5_-_windows: Job Maintainers: '#rm-packageworks' Wrench: 1.3.0.0 +# Functional tests for dependents found in the latest 6000.5 manifest (MacOS). +preview_apv_-_6000_5_-_macos: + name: Preview APV - 6000.5 - macos + agent: + image: package-ci/macos-13:default + type: Unity::VM::osx + flavor: b1.xlarge + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-0_3978eda62a03e3dbc469ab77590d20f8832032d8e0b586550597b7f590baefec.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm + timeout: 20 + retries: 10 + - command: unity-downloader-cli -u trunk -c editor --path .Editor --fast + timeout: 10 + retries: 3 + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.5 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: echo 'Skipping Editor Manifest Validator as it is only supported on Windows' + after: + - command: bash .yamato/generated-scripts/infrastructure-instability-detection-mac.sh + artifacts: + Crash Dumps: + paths: + - CrashDumps/** + logs: + paths: + - '*.log' + - '*.xml' + - upm-ci~/test-results/**/* + - upm-ci~/temp/*/Logs/** + - upm-ci~/temp/*/Library/*.log + - upm-ci~/temp/*/*.log + - upm-ci~/temp/Builds/*.log + packages: + paths: + - upm-ci~/packages/**/* + PreviewAPVResults: + paths: + - PreviewApvArtifacts~/** + - APVTest/**/manifest.json + pvp-results: + paths: + - upm-ci~/pvp/**/* + browsable: onDemand + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + variables: + UPMPVP_CONTEXT_WRENCH: 1.2.0.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 1.2.0.0 + +# Functional tests for dependents found in the latest 6000.5 manifest (Ubuntu). +preview_apv_-_6000_5_-_ubuntu: + name: Preview APV - 6000.5 - ubuntu + agent: + image: package-ci/ubuntu-22.04:v4 + type: Unity::VM + flavor: b1.large + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-0_3978eda62a03e3dbc469ab77590d20f8832032d8e0b586550597b7f590baefec.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm + timeout: 20 + retries: 10 + - command: unity-downloader-cli -u trunk -c editor --path .Editor --fast + timeout: 10 + retries: 3 + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.5 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: echo 'Skipping Editor Manifest Validator as it is only supported on Windows' + after: + - command: bash .yamato/generated-scripts/infrastructure-instability-detection-linux.sh + artifacts: + Crash Dumps: + paths: + - CrashDumps/** + logs: + paths: + - '*.log' + - '*.xml' + - upm-ci~/test-results/**/* + - upm-ci~/temp/*/Logs/** + - upm-ci~/temp/*/Library/*.log + - upm-ci~/temp/*/*.log + - upm-ci~/temp/Builds/*.log + packages: + paths: + - upm-ci~/packages/**/* + PreviewAPVResults: + paths: + - PreviewApvArtifacts~/** + - APVTest/**/manifest.json + pvp-results: + paths: + - upm-ci~/pvp/**/* + browsable: onDemand + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + variables: + UPMPVP_CONTEXT_WRENCH: 1.2.0.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 1.2.0.0 + +# Functional tests for dependents found in the latest 6000.5 manifest (Windows). +preview_apv_-_6000_5_-_windows: + name: Preview APV - 6000.5 - windows + agent: + image: package-ci/win10:default + type: Unity::VM + flavor: b1.large + commands: + - command: gsudo reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-0_3978eda62a03e3dbc469ab77590d20f8832032d8e0b586550597b7f590baefec.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm + timeout: 20 + retries: 10 + - command: unity-downloader-cli -u trunk -c editor --path .Editor --fast + timeout: 10 + retries: 3 + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.5 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: python PythonScripts/editor_manifest_validator.py --version=6000.5 --wrench-config=.yamato/wrench/wrench_config.json + after: + - command: .yamato\generated-scripts\infrastructure-instability-detection-win.cmd + artifacts: + Crash Dumps: + paths: + - CrashDumps/** + logs: + paths: + - '*.log' + - '*.xml' + - upm-ci~/test-results/**/* + - upm-ci~/temp/*/Logs/** + - upm-ci~/temp/*/Library/*.log + - upm-ci~/temp/*/*.log + - upm-ci~/temp/Builds/*.log + packages: + paths: + - upm-ci~/packages/**/* + PreviewAPVResults: + paths: + - PreviewApvArtifacts~/** + - APVTest/**/manifest.json + pvp-results: + paths: + - upm-ci~/pvp/**/* + browsable: onDemand + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + variables: + UPMPVP_CONTEXT_WRENCH: 1.2.0.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 1.2.0.0 + diff --git a/.yamato/wrench/validation-jobs.yml b/.yamato/wrench/validation-jobs.yml index 92cbd5d8cc..cb82fda930 100644 --- a/.yamato/wrench/validation-jobs.yml +++ b/.yamato/wrench/validation-jobs.yml @@ -742,6 +742,204 @@ validate_-_netcode_gameobjects_-_6000_4_-_windows: - command: unity-downloader-cli -u 6000.4/staging -c editor --path .Editor --fast timeout: 10 retries: 3 + - command: upm-pvp create-test-project testproject --packages "upm-ci~/packages/*.tgz" --filter "com.unity.netcode.gameobjects" --unity .Editor + timeout: 10 + retries: 1 + - command: echo No internal packages to add. + - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp + timeout: 40 + retries: 0 + - command: upm-pvp require "pkgprom-promote -PVP-29-2 rme" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json + timeout: 5 + retries: 0 + - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json + timeout: 10 + retries: 0 + - command: 'UnifiedTestRunner.exe --testproject=testproject --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" ' + timeout: 40 + retries: 1 + after: + - command: .yamato\generated-scripts\infrastructure-instability-detection-win.cmd + artifacts: + Crash Dumps: + paths: + - CrashDumps/** + packages: + paths: + - upm-ci~/packages/**/* + pvp-results: + paths: + - upm-ci~/pvp/**/* + browsable: onDemand + UTR: + paths: + - '*.log' + - '*.xml' + - artifacts/**/* + - testproject/Logs/** + - testproject/Library/*.log + - testproject/*.log + - testproject/Builds/*.log + - build/test-results/** + browsable: onDemand + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + variables: + UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 + UPMPVP_CONTEXT_WRENCH: 1.2.0.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 1.2.0.0 + labels: + - Packages:netcode.gameobjects + +# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.5 - macos (6000.5 - MacOS). +validate_-_netcode_gameobjects_-_6000_5_-_macos: + name: Validate - netcode.gameobjects - 6000.5 - macos + agent: + image: package-ci/macos-13:default + type: Unity::VM::osx + flavor: b1.xlarge + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-0_3978eda62a03e3dbc469ab77590d20f8832032d8e0b586550597b7f590baefec.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: unity-downloader-cli -u trunk -c editor --path .Editor --fast + timeout: 10 + retries: 3 + - command: upm-pvp create-test-project testproject --packages "upm-ci~/packages/*.tgz" --filter "com.unity.netcode.gameobjects" --unity .Editor + timeout: 10 + retries: 1 + - command: echo No internal packages to add. + - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp + timeout: 40 + retries: 0 + - command: upm-pvp require "pkgprom-promote -PVP-29-2 rme" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json + timeout: 5 + retries: 0 + - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json + timeout: 10 + retries: 0 + - command: 'UnifiedTestRunner --testproject=testproject --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" ' + timeout: 40 + retries: 1 + after: + - command: bash .yamato/generated-scripts/infrastructure-instability-detection-mac.sh + artifacts: + Crash Dumps: + paths: + - CrashDumps/** + packages: + paths: + - upm-ci~/packages/**/* + pvp-results: + paths: + - upm-ci~/pvp/**/* + browsable: onDemand + UTR: + paths: + - '*.log' + - '*.xml' + - artifacts/**/* + - testproject/Logs/** + - testproject/Library/*.log + - testproject/*.log + - testproject/Builds/*.log + - build/test-results/** + browsable: onDemand + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + variables: + UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 + UPMPVP_CONTEXT_WRENCH: 1.2.0.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 1.2.0.0 + labels: + - Packages:netcode.gameobjects + +# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.5 - ubuntu (6000.5 - Ubuntu). +validate_-_netcode_gameobjects_-_6000_5_-_ubuntu: + name: Validate - netcode.gameobjects - 6000.5 - ubuntu + agent: + image: package-ci/ubuntu-22.04:v4 + type: Unity::VM + flavor: b1.large + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-0_3978eda62a03e3dbc469ab77590d20f8832032d8e0b586550597b7f590baefec.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: unity-downloader-cli -u trunk -c editor --path .Editor --fast + timeout: 10 + retries: 3 + - command: upm-pvp create-test-project testproject --packages "upm-ci~/packages/*.tgz" --filter "com.unity.netcode.gameobjects" --unity .Editor + timeout: 10 + retries: 1 + - command: echo No internal packages to add. + - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp + timeout: 40 + retries: 0 + - command: upm-pvp require "pkgprom-promote -PVP-29-2 rme" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json + timeout: 5 + retries: 0 + - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json + timeout: 10 + retries: 0 + - command: 'UnifiedTestRunner --testproject=testproject --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" ' + timeout: 40 + retries: 1 + after: + - command: bash .yamato/generated-scripts/infrastructure-instability-detection-linux.sh + artifacts: + Crash Dumps: + paths: + - CrashDumps/** + packages: + paths: + - upm-ci~/packages/**/* + pvp-results: + paths: + - upm-ci~/pvp/**/* + browsable: onDemand + UTR: + paths: + - '*.log' + - '*.xml' + - artifacts/**/* + - testproject/Logs/** + - testproject/Library/*.log + - testproject/*.log + - testproject/Builds/*.log + - build/test-results/** + browsable: onDemand + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + variables: + UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 + UPMPVP_CONTEXT_WRENCH: 1.2.0.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 1.2.0.0 + labels: + - Packages:netcode.gameobjects + +# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.5 - windows (6000.5 - Windows). +validate_-_netcode_gameobjects_-_6000_5_-_windows: + name: Validate - netcode.gameobjects - 6000.5 - windows + agent: + image: package-ci/win10:default + type: Unity::VM + flavor: b1.large + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-0_3978eda62a03e3dbc469ab77590d20f8832032d8e0b586550597b7f590baefec.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: unity-downloader-cli -u trunk -c editor --path .Editor --fast + timeout: 10 + retries: 3 - command: upm-pvp create-test-project testproject --packages "upm-ci~/packages/*.tgz" --filter "com.unity.netcode.gameobjects com.unity.netcode.gameobjects.tests" --unity .Editor timeout: 10 retries: 1 From 7ab790a27031792d2fbc9c0b45f90d34da664501 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 27 Oct 2025 09:43:31 -0500 Subject: [PATCH 02/10] fix - revert Reverting changes made in 3664 with the time used when interpolating to local time. Added additional comments to prevent future fiddling with that (whether to use local or server time). --- .../Runtime/Components/NetworkTransform.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 1c2ed5dadb..6162ad91e2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -4230,8 +4230,14 @@ internal BufferedLinearInterpolatorQuaternion GetRotationInterpolator() // Non-Authority private void UpdateInterpolation() { - // Select the time system relative to the type of NetworkManager instance. - var timeSystem = m_CachedNetworkManager.IsServer ? m_CachedNetworkManager.LocalTime : m_CachedNetworkManager.ServerTime; + // Use the local time because: + // Client-Server: + // Local time is server time on a host or server. + // Local time on clients takes latency into consideration. + // Distributed authority: + // Local time is used by the authority. + // Local time on non-authority takes latency into consid]eration. + var timeSystem = m_CachedNetworkManager.LocalTime; var currentTime = timeSystem.Time; #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D var cachedDeltaTime = m_UseRigidbodyForMotion ? m_CachedNetworkManager.RealTimeProvider.FixedDeltaTime : m_CachedNetworkManager.RealTimeProvider.DeltaTime; From fc5e8f1adac16fb5dac57b09f767cc66adad9935 Mon Sep 17 00:00:00 2001 From: Emma Date: Tue, 28 Oct 2025 05:01:58 -0400 Subject: [PATCH 03/10] fix: Compilation error when using an RpcAttribute with RequireOwnership (#3750) --- .../Editor/CodeGen/NetworkBehaviourILPP.cs | 19 +++++-------------- .../Runtime/Core/NetworkBehaviour.cs | 6 +++++- .../Tests/Runtime/Rpc/RpcInvocationTests.cs | 13 +++++++++++++ 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index e6e70d69f6..4749012fd3 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -1654,18 +1654,14 @@ private CustomAttribute CheckAndGetRpcAttribute(MethodDefinition methodDefinitio return null; } - var typeSystem = methodDefinition.Module.TypeSystem; - var hasInvokePermission = false; + bool hasInvokePermission = false, hasRequireOwnership = false; - CustomAttributeNamedArgument? invokePermissionAttribute = null; foreach (var argument in rpcAttribute.Fields) { switch (argument.Name) { case k_ServerRpcAttribute_RequireOwnership: - var requireOwnership = argument.Argument.Type == typeSystem.Boolean && (bool)argument.Argument.Value; - var invokePermissionArg = new CustomAttributeArgument(m_RpcInvokePermissions_TypeRef, requireOwnership ? RpcInvokePermission.Owner : RpcInvokePermission.Everyone); - invokePermissionAttribute = new CustomAttributeNamedArgument(k_RpcAttribute_InvokePermission, invokePermissionArg); + hasRequireOwnership = true; break; case k_RpcAttribute_InvokePermission: hasInvokePermission = true; @@ -1673,15 +1669,10 @@ private CustomAttribute CheckAndGetRpcAttribute(MethodDefinition methodDefinitio } } - if (invokePermissionAttribute != null) + if (hasInvokePermission && hasRequireOwnership) { - if (hasInvokePermission) - { - m_Diagnostics.AddError($"{methodDefinition.Name} cannot declare both RequireOwnership and InvokePermission!"); - return null; - } - - rpcAttribute.Fields.Add(invokePermissionAttribute.Value); + m_Diagnostics.AddError($"{methodDefinition.Name} cannot declare both RequireOwnership and InvokePermission!"); + return null; } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 92b4679fdf..7f930b27dc 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -335,7 +335,11 @@ internal FastBufferWriter __beginSendRpc(uint rpcMethodId, RpcParams rpcParams, throw new RpcException("This RPC can only be sent by the server."); } - if (attributeParams.InvokePermission == RpcInvokePermission.Owner && !IsOwner) +#pragma warning disable CS0618 // Type or member is obsolete + var requireOwnership = attributeParams.RequireOwnership; +#pragma warning restore CS0618 // Type or member is obsolete + + if ((requireOwnership || attributeParams.InvokePermission == RpcInvokePermission.Owner) && !IsOwner) { throw new RpcException("This RPC can only be sent by its owner."); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcInvocationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcInvocationTests.cs index ba9708da9b..db6be63980 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcInvocationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcInvocationTests.cs @@ -98,11 +98,13 @@ public IEnumerator RpcInvokePermissionSendingTests() foreach (var (manager, instance) in m_InvokeInstances) { instance.ExpectedCallCounts[nameof(InvokePermissionBehaviour.OwnerInvokePermissionRpc)] = 1; + instance.ExpectedCallCounts[nameof(InvokePermissionBehaviour.OwnerRequireOwnershipRpc)] = 1; var threwException = false; try { instance.OwnerInvokePermissionRpc(); + instance.OwnerRequireOwnershipRpc(); } catch (RpcException) { @@ -167,8 +169,10 @@ public IEnumerator RpcInvokePermissionReceivingTests() foreach (var (manager, instance) in m_InvokeInstances) { instance.ExpectedCallCounts[nameof(InvokePermissionBehaviour.OwnerInvokePermissionRpc)] = 1; + instance.ExpectedCallCounts[nameof(InvokePermissionBehaviour.OwnerRequireOwnershipRpc)] = 1; SendUncheckedMessage(manager, instance, nameof(InvokePermissionBehaviour.OwnerInvokePermissionRpc)); + SendUncheckedMessage(manager, instance, nameof(InvokePermissionBehaviour.OwnerRequireOwnershipRpc)); } yield return WaitForConditionOrTimeOut(AllExpectedCallsReceived); @@ -445,6 +449,15 @@ public void ServerInvokePermissionRpc() TrackRpcCalled(GetCaller()); } + +#pragma warning disable CS0618 // Type or member is obsolete + [Rpc(SendTo.Everyone, RequireOwnership = true)] +#pragma warning restore CS0618 // Type or member is obsolete + public void OwnerRequireOwnershipRpc() + { + TrackRpcCalled(GetCaller()); + } + [Rpc(SendTo.Everyone, InvokePermission = RpcInvokePermission.Owner)] public void OwnerInvokePermissionRpc() { From a365b21bb808790464822610300824a49a1b0a84 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 28 Oct 2025 04:02:25 -0500 Subject: [PATCH 04/10] fix: clear DisconnectReason on server-side disconnect client (#3751) * fix Reset the disconnect reason when the server-side is disconnecting a client. * test Updating the disconnect test to validate that the disconnect reason is reset when a server force disconnects a client. --- .../Connection/NetworkConnectionManager.cs | 1 + .../Tests/Runtime/DisconnectTests.cs | 45 ++++++++++++++++--- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index dc21b8b711..7c39673e3a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -1450,6 +1450,7 @@ internal void DisconnectClient(ulong clientId, string reason = null) var transportId = ClientIdToTransportId(clientId); if (transportId.Item2) { + DisconnectReason = string.Empty; GenerateDisconnectInformation(clientId, transportId.Item1, reason); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs index e7ad3595e2..82669d7187 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs @@ -35,7 +35,7 @@ public enum ClientDisconnectType ClientDisconnectsFromServer } - protected override int NumberOfClients => 1; + protected override int NumberOfClients => 2; private OwnerPersistence m_OwnerPersistence; private ClientDisconnectType m_ClientDisconnectType; @@ -99,8 +99,14 @@ private void OnConnectionEvent(NetworkManager networkManager, ConnectionEventDat { return; } - - m_DisconnectedEvent.Add(networkManager, connectionEventData); + if (!m_DisconnectedEvent.ContainsKey(networkManager)) + { + m_DisconnectedEvent.Add(networkManager, connectionEventData); + } + else + { + m_DisconnectedEvent[networkManager] = connectionEventData; + } } /// @@ -144,10 +150,29 @@ private bool DoesServerStillHaveSpawnedPlayerObject() return !m_ServerNetworkManager.SpawnManager.SpawnedObjects.Any(x => x.Value.IsPlayerObject && x.Value.OwnerClientId == m_ClientId); } + /// + /// Used to compare against when the client-side disconnects + /// + private int m_ExpectedConnectedClientCount; + [UnityTest] public IEnumerator ClientPlayerDisconnected([Values] ClientDisconnectType clientDisconnectType) { - var clientNetworkManager = m_ClientNetworkManagers[0]; + // Cycling through 2 (or more) clients disconnecting + for (int i = m_ClientNetworkManagers.Length - 1; i >= 0; i--) + { + var client = m_ClientNetworkManagers[i]; + if (client.LocalClientId == m_ServerNetworkManager.LocalClientId) + { + continue; + } + m_ExpectedConnectedClientCount = m_ServerNetworkManager.ConnectedClients.Count; + yield return DisconnectClient(m_ClientNetworkManagers[i], clientDisconnectType); + } + } + + private IEnumerator DisconnectClient(NetworkManager clientNetworkManager, ClientDisconnectType clientDisconnectType) + { m_ClientId = clientNetworkManager.LocalClientId; m_ClientDisconnectType = clientDisconnectType; @@ -163,6 +188,9 @@ public IEnumerator ClientPlayerDisconnected([Values] ClientDisconnectType client clientNetworkManager.OnConnectionEvent += OnConnectionEvent; m_ServerNetworkManager.OnConnectionEvent += OnConnectionEvent; m_ServerNetworkManager.DisconnectClient(m_ClientId); + Assert.True(!string.IsNullOrEmpty(m_ServerNetworkManager.DisconnectReason), "Server-side disconnect notification should have been generated but was not!"); + var splitByDisconnectEvent = m_ServerNetworkManager.DisconnectReason.Split("[Disconnect Event]"); + Assert.IsTrue(splitByDisconnectEvent.Length <= 2, $"Multiple disconnect events found in the server-side disconnect reason:\n {m_ServerNetworkManager.DisconnectReason}"); } else { @@ -187,13 +215,14 @@ public IEnumerator ClientPlayerDisconnected([Values] ClientDisconnectType client } else { + m_ExpectedConnectedClientCount -= 1; Assert.IsTrue(m_DisconnectedEvent.ContainsKey(m_ServerNetworkManager), $"Could not find the server {nameof(NetworkManager)} disconnect event entry!"); Assert.IsTrue(m_DisconnectedEvent[m_ServerNetworkManager].ClientId == m_ClientId, $"Expected ClientID {m_ClientId} but found ClientID {m_DisconnectedEvent[m_ServerNetworkManager].ClientId} for the server {nameof(NetworkManager)} disconnect event entry!"); Assert.IsTrue(m_DisconnectedEvent.ContainsKey(clientNetworkManager), $"Could not find the client {nameof(NetworkManager)} disconnect event entry!"); Assert.IsTrue(m_DisconnectedEvent[clientNetworkManager].ClientId == m_ClientId, $"Expected ClientID {m_ClientId} but found ClientID {m_DisconnectedEvent[m_ServerNetworkManager].ClientId} for the client {nameof(NetworkManager)} disconnect event entry!"); - Assert.IsTrue(m_ServerNetworkManager.ConnectedClientsIds.Count == 1, $"Expected connected client identifiers count to be 1 but it was {m_ServerNetworkManager.ConnectedClientsIds.Count}!"); - Assert.IsTrue(m_ServerNetworkManager.ConnectedClients.Count == 1, $"Expected connected client identifiers count to be 1 but it was {m_ServerNetworkManager.ConnectedClients.Count}!"); - Assert.IsTrue(m_ServerNetworkManager.ConnectedClientsList.Count == 1, $"Expected connected client identifiers count to be 1 but it was {m_ServerNetworkManager.ConnectedClientsList.Count}!"); + Assert.IsTrue(m_ServerNetworkManager.ConnectedClientsIds.Count == m_ExpectedConnectedClientCount, $"Expected connected client identifiers count to be {m_ExpectedConnectedClientCount} but it was {m_ServerNetworkManager.ConnectedClientsIds.Count}!"); + Assert.IsTrue(m_ServerNetworkManager.ConnectedClients.Count == m_ExpectedConnectedClientCount, $"Expected connected client identifiers count to be {m_ExpectedConnectedClientCount} but it was {m_ServerNetworkManager.ConnectedClients.Count}!"); + Assert.IsTrue(m_ServerNetworkManager.ConnectedClientsList.Count == m_ExpectedConnectedClientCount, $"Expected connected client identifiers count to be {m_ExpectedConnectedClientCount} but it was {m_ServerNetworkManager.ConnectedClientsList.Count}!"); } if (m_OwnerPersistence == OwnerPersistence.DestroyWithOwner) @@ -244,6 +273,8 @@ public IEnumerator ClientPlayerDisconnected([Values] ClientDisconnectType client Assert.IsNull(m_ServerNetworkManager.ConnectionManager.LocalClient.PlayerObject, $"{m_ServerNetworkManager.name} still has Player assigned!"); } } + m_DisconnectedEvent.Clear(); + m_ClientDisconnected = false; } } } From 557018ba2f4b1e260183e9072a2668318c565ef6 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 28 Oct 2025 16:18:49 -0500 Subject: [PATCH 05/10] fix: Disabled Networkbehaviours are not excluded from NetworkObject.ChildNetworkBehaviours (#3756) * fix This resolves the issue where a disabled NetworkBehaviour was being included in the ChildNetworkBehaviours list which would cause an exception upon a client attempting to synchronize. * update Adding a warning generation method to make testing easier. * test Modifying the original test for this to assure it checks for the correct error message. * test - refactor Adding a bit more coverage to the `ValidatedDisableddNetworkBehaviourWarning` test so it validates that when either a child GameObject is set inactive or the NetworkBehaviiour itself is disabled the warning message is generated and the network prefab still spawns and synchronizes. --- .../Runtime/Core/NetworkObject.cs | 10 +++ .../Runtime/NetworkBehaviourGenericTests.cs | 81 ++++++++++++++----- 2 files changed, 73 insertions(+), 18 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 74d1a412f0..0498d9fae7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -2619,6 +2619,11 @@ internal void InvokeBehaviourNetworkDespawn() private List m_ChildNetworkBehaviours; + internal string GenerateDisabledNetworkBehaviourWarning(NetworkBehaviour networkBehaviour) + { + return $"[{name}][{networkBehaviour.GetType().Name}][{nameof(isActiveAndEnabled)}: {networkBehaviour.isActiveAndEnabled}] Disabled {nameof(NetworkBehaviour)}s are not supported and will be excluded from spawning and synchronization!"; + } + internal List ChildNetworkBehaviours { get @@ -2639,6 +2644,11 @@ internal List ChildNetworkBehaviours { continue; } + else if (!networkBehaviours[i].isActiveAndEnabled) + { + Debug.LogWarning(GenerateDisabledNetworkBehaviourWarning(networkBehaviours[i])); + continue; + } // Set ourselves as the NetworkObject that this behaviour belongs to and add it to the child list var nextIndex = (ushort)m_ChildNetworkBehaviours.Count; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs index 91e3124932..f180212418 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs @@ -16,6 +16,8 @@ internal class NetworkBehaviourGenericTests : NetcodeIntegrationTest private bool m_AllowServerToStart; + private GameObject m_PrefabToSpawn; + protected override bool CanStartServerAndClients() { return m_AllowServerToStart; @@ -32,45 +34,88 @@ public override void OnNetworkDespawn() } } + protected override void OnServerAndClientsCreated() + { + m_PrefabToSpawn = CreateNetworkObjectPrefab("TestPrefab"); + + var childObject = new GameObject + { + name = "ChildObject" + }; + childObject.transform.parent = m_PrefabToSpawn.transform; + childObject.AddComponent(); + base.OnServerAndClientsCreated(); + } + protected override IEnumerator OnSetup() { m_AllowServerToStart = false; return base.OnSetup(); } + + protected override void OnNewClientCreated(NetworkManager networkManager) + { + networkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab() + { + Prefab = m_PrefabToSpawn, + }); + base.OnNewClientCreated(networkManager); + } + + /// - /// This validates the fix for when a child GameObject with a NetworkBehaviour + /// 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. + /// - That when a child GameObject is deleted/disabled or a NetworkBehaviour is disabled + /// a message is logged and the NetworkObject still can be spawned and synchronized. /// [UnityTest] - public IEnumerator ValidatedDisableddNetworkBehaviourWarning() + public IEnumerator ValidatedDisableddNetworkBehaviourWarning([Values] bool disableGameObject) { m_AllowServerToStart = true; - yield return s_DefaultWaitForTick; - // Now just start the Host yield return StartServerAndClients(); - var parentObject = new GameObject(); - var childObject = new GameObject - { - 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); + // Now join a new client to make sure a connected client spawns the instance. + yield return CreateAndStartNewClient(); - 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!"); + // Adjust the prefab to either have the child GameObject completely disabled or the NetworkBehaviour + // disabled. + var childBehaviour = m_PrefabToSpawn.GetComponentInChildren(true); + if (disableGameObject) + { + childBehaviour.enabled = true; + childBehaviour.gameObject.SetActive(false); + } + else + { + childBehaviour.enabled = false; + childBehaviour.gameObject.SetActive(true); + } + // Now create an instance of the prefab + var instance = Object.Instantiate(m_PrefabToSpawn); + var instanceNetworkObject = instance.GetComponent(); + // Generate the expected warning message + var expectedWarning = instanceNetworkObject.GenerateDisabledNetworkBehaviourWarning(instanceNetworkObject.GetComponentInChildren(true)); + // Spawn the instance + SpawnObjectInstance(instanceNetworkObject, m_ServerNetworkManager); + LogAssert.Expect(LogType.Warning, $"{expectedWarning}"); + // Asure the connected client spawned the object first + yield return WaitForSpawnedOnAllOrTimeOut(instanceNetworkObject); + AssertOnTimeout($"Not all clients spawned {instanceNetworkObject.name}!"); + + // Now join a new client to make sure the client synchronizes with the disabled GameObject or NetworkBehaviour component. + yield return CreateAndStartNewClient(); - parentNetworkObject.Spawn(); - yield return s_DefaultWaitForTick; + // Asure the newly connected client synchronizes the spawned object correctly + yield return WaitForSpawnedOnAllOrTimeOut(instanceNetworkObject); + AssertOnTimeout($"Not all clients spawned {instanceNetworkObject.name}!"); } /// From b7541482512cc20e3ec95d39a8469de94b199929 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 29 Oct 2025 04:56:31 -0500 Subject: [PATCH 06/10] fix: disconnect reason should not duplicate any information (#3757) * fix Fixing issue with duplicated disconnect reasons for all cases. * test - update Making adjustments to the validation logic checking to determine if a server's disconnect reason is included in the NetworkManager.DisconnectReaon property. * style Removing a trailing whitespace * test - update One more area that needs to check for the server reason but not be a 1:1 due to the additional disconnect information before it. --- .../Connection/NetworkConnectionManager.cs | 55 ++++++++++++++++--- .../Messaging/DisconnectReasonMessage.cs | 5 +- .../Tests/Runtime/ConnectionApproval.cs | 2 +- .../Messaging/DisconnectReasonTests.cs | 5 +- 4 files changed, 54 insertions(+), 13 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index 7c39673e3a..1c60bfa1c9 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -103,11 +103,52 @@ public sealed class NetworkConnectionManager private static ProfilerMarker s_TransportDisconnect = new ProfilerMarker($"{nameof(NetworkManager)}.TransportDisconnect"); #endif + private string m_DisconnectReason; /// /// 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 + /// provide disconnect information that will be followed by the server's disconnect reason. /// - public string DisconnectReason { get; internal set; } + /// + /// On a server or host, this value could no longer exist after all subscribed callbacks are invoked for the + /// client that disconnected. It is recommended to copy the message to some other property or field when + /// is invoked. + /// + public string DisconnectReason + { + get + { + // For in-frequent event driven invocations, a method within a getter + // is "generally ok". + return GetDisconnectReason(); + } + internal set + { + m_DisconnectReason = value; + } + } + + /// + /// Returns the conbined result of the locally applied and the + /// server applied . + /// - If both values are empty or null, then it returns . + /// - If either value is valid, then it returns that value. + /// - If both values are valid, then it returns followed by a + /// new line and then . + /// + /// A disconnect reason, if any. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal string GetDisconnectReason(string header = null) + { + var disconnectReason = string.IsNullOrEmpty(m_DisconnectReason) ? string.Empty : m_DisconnectReason; + var serverDisconnectReason = string.IsNullOrEmpty(ServerDisconnectReason) ? string.Empty : $"\n{ServerDisconnectReason}"; + var headerInfo = string.IsNullOrEmpty(header) ? string.Empty : header; + return $"{headerInfo}{disconnectReason}{serverDisconnectReason}"; + } + + /// + /// Updated by . + /// + internal string ServerDisconnectReason; /// /// The callback to invoke once a client connects. This callback is only ran on the server and on the local client that connects. @@ -537,17 +578,15 @@ internal void DataEventHandler(ulong transportClientId, ref ArraySegment p private void GenerateDisconnectInformation(ulong clientId, ulong transportClientId, string reason = null) { var header = $"[Disconnect Event][Client-{clientId}][TransportClientId-{transportClientId}]"; - var existingDisconnectReason = DisconnectReason; - var defaultMessage = Transport.DisconnectEventMessage; if (reason != null) { defaultMessage = $"{reason} {defaultMessage}"; } + // Just go ahead and set this whether client or server so any subscriptions to a disconnect event can check the DisconnectReason // to determine why the client disconnected - DisconnectReason = $"{header}[{Transport.DisconnectEvent}] {defaultMessage}"; - DisconnectReason = $"{DisconnectReason}\n{existingDisconnectReason}"; + m_DisconnectReason = $"{header}[{Transport.DisconnectEvent}] {defaultMessage}"; if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) { @@ -1450,7 +1489,6 @@ internal void DisconnectClient(ulong clientId, string reason = null) var transportId = ClientIdToTransportId(clientId); if (transportId.Item2) { - DisconnectReason = string.Empty; GenerateDisconnectInformation(clientId, transportId.Item1, reason); } @@ -1476,7 +1514,8 @@ internal void Initialize(NetworkManager networkManager) TransportIdToClientIdMap.Clear(); ClientsToApprove.Clear(); NetworkObject.OrphanChildren.Clear(); - DisconnectReason = string.Empty; + m_DisconnectReason = string.Empty; + ServerDisconnectReason = string.Empty; NetworkManager = networkManager; MessageManager = networkManager.MessageManager; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/DisconnectReasonMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/DisconnectReasonMessage.cs index b26982ea1f..1e698df9a4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/DisconnectReasonMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/DisconnectReasonMessage.cs @@ -37,7 +37,10 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int public void Handle(ref NetworkContext context) { - ((NetworkManager)context.SystemOwner).ConnectionManager.DisconnectReason = Reason; + // Always apply the server-side generated disconnect reason to the server specific disconnect reason. + // This is combined with the additional disconnect information when getting NetworkManager.DisconnectReason + // (NetworkConnectionManager.DisconnectReason). + ((NetworkManager)context.SystemOwner).ConnectionManager.ServerDisconnectReason = Reason; } }; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs b/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs index fdf8cc3836..b630136c61 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs @@ -92,7 +92,7 @@ protected override void OnServerAndClientsCreated() private void Client_OnClientDisconnectCallback(ulong clientId) { m_ClientNetworkManagers[0].OnClientDisconnectCallback -= Client_OnClientDisconnectCallback; - m_ClientDisconnectReasonValidated = m_ClientNetworkManagers[0].LocalClientId == clientId && m_ClientNetworkManagers[0].DisconnectReason == k_InvalidToken; + m_ClientDisconnectReasonValidated = m_ClientNetworkManagers[0].LocalClientId == clientId && m_ClientNetworkManagers[0].DisconnectReason.Contains(k_InvalidToken); } private bool ClientAndHostValidated() diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/DisconnectReasonTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/DisconnectReasonTests.cs index e293be3e64..3d65334c86 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/DisconnectReasonTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/DisconnectReasonTests.cs @@ -57,9 +57,8 @@ public IEnumerator DisconnectReasonTest() yield return null; } - Assert.AreEqual(m_ClientNetworkManagers[0].DisconnectReason, "Bogus reason 1"); - Assert.AreEqual(m_ClientNetworkManagers[1].DisconnectReason, "Bogus reason 2"); - + Assert.True(m_ClientNetworkManagers[0].DisconnectReason.Contains("Bogus reason 1"), $"[Client-{m_ClientNetworkManagers[0].LocalClientId}] Disconnect reason should contain \"Bogus reason 1\" but is: {m_ClientNetworkManagers[0].DisconnectReason}"); + Assert.True(m_ClientNetworkManagers[1].DisconnectReason.Contains("Bogus reason 2"), $"[Client-{m_ClientNetworkManagers[0].LocalClientId}] Disconnect reason should contain \"Bogus reason 2\" but is: {m_ClientNetworkManagers[1].DisconnectReason}"); Debug.Assert(m_DisconnectCount == 2); } From 3997244bfe6eb1e9a7e3771a48677199ab091830 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 30 Oct 2025 09:28:35 -0500 Subject: [PATCH 07/10] fix: disabled NetworkBehaviours vs disabled GameObjects and tests excluded by lack of MP tools (#3761) * fix Use the GameObject.activeInHierarchy fix. Allow disabled NetworkBehaviour with a disabled GameObject to internally start pre-spawn but exit before invoking protected method. * test - update Re-factoring this test to align with the previously committed fix. * tests - enable Now that we have included the Mutliplayer Tools package in the test project, we needed to remove the !MULTIPLAYER_TOOLS check to assure these tests are actually being run. --- .../Runtime/Core/NetworkBehaviour.cs | 7 ++++++ .../Runtime/Core/NetworkObject.cs | 22 ++++++++----------- .../Tests/Editor/Build/BuildTests.cs | 2 -- .../Runtime/NetworkBehaviourGenericTests.cs | 19 +++++++++++----- .../NetworkTransform/NetworkTransformBase.cs | 2 -- .../NetworkTransformStateTests.cs | 2 -- .../NetworkTransform/NetworkTransformTests.cs | 16 -------------- .../NetworkVariable/NetworkVariableTests.cs | 4 ---- .../Tests/Runtime/Rpc/UniversalRpcTests.cs | 4 ++-- .../Runtime/Timing/TimeIntegrationTest.cs | 2 -- .../Runtime/TransformInterpolationTests.cs | 2 -- 11 files changed, 32 insertions(+), 50 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 7f930b27dc..ae232df523 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -761,6 +761,13 @@ internal void NetworkPreSpawn(ref NetworkManager networkManager, NetworkObject n UpdateNetworkProperties(); + // Exit early for disabled NetworkBehaviours. + // We still want the above values to be set. + if (!gameObject.activeInHierarchy) + { + return; + } + try { OnNetworkPreSpawn(ref networkManager); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 0498d9fae7..e29097426d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -2082,6 +2082,12 @@ internal void InvokeBehaviourOnNetworkObjectParentChanged(NetworkObject parentNe { for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { + // Any NetworkBehaviour that is not spawned and the associated GameObject is disabled should be + // skipped over (i.e. not supported). + if (!ChildNetworkBehaviours[i].IsSpawned && !ChildNetworkBehaviours[i].gameObject.activeInHierarchy) + { + continue; + } // Invoke internal notification ChildNetworkBehaviours[i].InternalOnNetworkObjectParentChanged(parentNetworkObject); // Invoke public notification @@ -2217,7 +2223,6 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) // DANGO-TODO: Do we want to worry about ownership permissions here? // It wouldn't make sense to not allow parenting, but keeping this note here as a reminder. var isAuthority = HasAuthority || (AllowOwnerToParent && IsOwner); - Debug.Log($"something is broken! isAuthority={isAuthority} | HasAuthority={HasAuthority} | (AllowOwnerToParent && IsOwner)={(AllowOwnerToParent && IsOwner)}"); // If we don't have authority and we are not shutting down, then don't allow any parenting. // If we are shutting down and don't have authority then allow it. @@ -2543,10 +2548,7 @@ internal void InvokeBehaviourNetworkPreSpawn() var networkManager = NetworkManager; for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { - if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy) - { - ChildNetworkBehaviours[i].NetworkPreSpawn(ref networkManager, this); - } + ChildNetworkBehaviours[i].NetworkPreSpawn(ref networkManager, this); } } @@ -2558,10 +2560,9 @@ internal void InvokeBehaviourNetworkSpawn() { if (!childBehaviour.gameObject.activeInHierarchy) { - Debug.LogWarning($"{childBehaviour.gameObject.name} is disabled! Netcode for GameObjects does not support spawning disabled NetworkBehaviours! The {childBehaviour.GetType().Name} component was skipped during spawn!"); + Debug.LogWarning($"{GenerateDisabledNetworkBehaviourWarning(childBehaviour)}"); continue; } - childBehaviour.InternalOnNetworkSpawn(); } } @@ -2621,7 +2622,7 @@ internal void InvokeBehaviourNetworkDespawn() internal string GenerateDisabledNetworkBehaviourWarning(NetworkBehaviour networkBehaviour) { - return $"[{name}][{networkBehaviour.GetType().Name}][{nameof(isActiveAndEnabled)}: {networkBehaviour.isActiveAndEnabled}] Disabled {nameof(NetworkBehaviour)}s are not supported and will be excluded from spawning and synchronization!"; + return $"[{name}][{networkBehaviour.GetType().Name}][{nameof(isActiveAndEnabled)}: {networkBehaviour.isActiveAndEnabled}] Disabled {nameof(NetworkBehaviour)}s will be excluded from spawning and synchronization!"; } internal List ChildNetworkBehaviours @@ -2644,11 +2645,6 @@ internal List ChildNetworkBehaviours { continue; } - else if (!networkBehaviours[i].isActiveAndEnabled) - { - Debug.LogWarning(GenerateDisabledNetworkBehaviourWarning(networkBehaviours[i])); - continue; - } // Set ourselves as the NetworkObject that this behaviour belongs to and add it to the child list var nextIndex = (ushort)m_ChildNetworkBehaviours.Count; diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs index 8bf2663d66..7ee23412bf 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs @@ -1,4 +1,3 @@ -#if !MULTIPLAYER_TOOLS using System.IO; using System.Reflection; using NUnit.Framework; @@ -41,4 +40,3 @@ public void BasicBuildTest() } } } -#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs index f180212418..5569278aa0 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs @@ -1,4 +1,6 @@ using System.Collections; +using System.Linq; +using System.Text.RegularExpressions; using NUnit.Framework; using Unity.Netcode.Components; using Unity.Netcode.TestHelpers.Runtime; @@ -71,8 +73,8 @@ protected override void OnNewClientCreated(NetworkManager networkManager) /// is not deleted until a later time would cause an exception due to the /// NetworkBehaviour not being removed from the NetworkObject.ChildNetworkBehaviours /// list. - /// - That when a child GameObject is deleted/disabled or a NetworkBehaviour is disabled - /// a message is logged and the NetworkObject still can be spawned and synchronized. + /// - When a NetworkBehaviour is disabled but the associated GameObject is enabled, + /// the object spawns without any issues. /// [UnityTest] public IEnumerator ValidatedDisableddNetworkBehaviourWarning([Values] bool disableGameObject) @@ -101,11 +103,18 @@ public IEnumerator ValidatedDisableddNetworkBehaviourWarning([Values] bool disab // Now create an instance of the prefab var instance = Object.Instantiate(m_PrefabToSpawn); var instanceNetworkObject = instance.GetComponent(); - // Generate the expected warning message - var expectedWarning = instanceNetworkObject.GenerateDisabledNetworkBehaviourWarning(instanceNetworkObject.GetComponentInChildren(true)); + // When the GameObject is disabled, check for the warning. + if (disableGameObject) + { + // Generate the expected warning message + var expectedWarning = instanceNetworkObject.GenerateDisabledNetworkBehaviourWarning(instanceNetworkObject.GetComponentInChildren(true)); + var expectedSplit = expectedWarning.Split(']'); + var expectedWarningBody = expectedSplit.Last(); + LogAssert.Expect(LogType.Warning, new Regex($".*{expectedWarningBody}*.")); + } + // Spawn the instance SpawnObjectInstance(instanceNetworkObject, m_ServerNetworkManager); - LogAssert.Expect(LogType.Warning, $"{expectedWarning}"); // Asure the connected client spawned the object first yield return WaitForSpawnedOnAllOrTimeOut(instanceNetworkObject); AssertOnTimeout($"Not all clients spawned {instanceNetworkObject.name}!"); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs index 7d9635dd76..cc7491eeec 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs @@ -781,7 +781,6 @@ internal class NetworkTransformTestComponent : NetworkTransform protected override void OnAuthorityPushTransformState(ref NetworkTransformState networkTransformState) { - Debug.Log($"[Auth]{name} State Pushed."); StatePushed = true; AuthorityLastSentState = networkTransformState; AuthorityPushedTransformState?.Invoke(ref networkTransformState); @@ -792,7 +791,6 @@ protected override void OnAuthorityPushTransformState(ref NetworkTransformState public bool StateUpdated { get; internal set; } protected override void OnNetworkTransformStateUpdated(ref NetworkTransformState oldState, ref NetworkTransformState newState) { - Debug.Log($"[Non-Auth]{name} State Updated."); StateUpdated = true; base.OnNetworkTransformStateUpdated(ref oldState, ref newState); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs index 6ab913866b..67e66b61b3 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs @@ -1,4 +1,3 @@ -#if !MULTIPLAYER_TOOLS using NUnit.Framework; using Unity.Netcode.Components; using Unity.Netcode.TestHelpers.Runtime; @@ -916,4 +915,3 @@ public void TestThresholds( } } } -#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index 43e03491a2..e76729f9c5 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -13,70 +13,57 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] -#if !MULTIPLAYER_TOOLS [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] - -#endif [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] -#if !MULTIPLAYER_TOOLS [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] - [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] - [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] -#endif [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] -#if !MULTIPLAYER_TOOLS [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] -#endif internal class NetworkTransformTests : NetworkTransformBase { protected const int k_TickRate = 60; @@ -162,8 +149,6 @@ private void AllChildrenLocalTransformValuesMatch(bool useSubChild, ChildrenTran } } -#if !MULTIPLAYER_TOOLS - private void UpdateTransformLocal(NetworkTransform networkTransformTestComponent) { networkTransformTestComponent.transform.localPosition += GetRandomVector3(0.5f, 2.0f); @@ -698,7 +683,6 @@ public void LateJoiningPlayerInitialScaleValues([Values] TransformSpace testLoca } } } -#endif /// /// Tests changing all axial values one at a time. diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs index 06ef4969d0..bde5422222 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs @@ -216,9 +216,7 @@ internal enum Serialization Default, } -#if !MULTIPLAYER_TOOLS [TestFixture(Serialization.EnsureLengthSafety)] -#endif [TestFixture(Serialization.Default)] internal class NetworkVariableTests : NetcodeIntegrationTest { @@ -339,7 +337,6 @@ private void InitializeServerAndClients(HostOrServer useHost) TimeTravelToNextTick(); } -#if !MULTIPLAYER_TOOLS /// /// Runs generalized tests on all predefined NetworkVariable types /// @@ -4884,4 +4881,3 @@ protected override IEnumerator OnTearDown() } } } -#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/UniversalRpcTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/UniversalRpcTests.cs index be1a15de20..e7073a8f80 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/UniversalRpcTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/UniversalRpcTests.cs @@ -1,4 +1,4 @@ -#if !MULTIPLAYER_TOOLS && !NGO_MINIMALPROJECT +#if !NGO_MINIMALPROJECT using System; using System.Collections; using System.Collections.Generic; @@ -1231,7 +1231,7 @@ private void RunTestTypeA(TestTypes testType) { foreach (var sendTo in sendToValues) { - UnityEngine.Debug.Log($"[{testType}][{sendTo}]"); + VerboseDebug($"[{testType}][{sendTo}]"); for (ulong objectOwner = 0; objectOwner <= numberOfClientsULong; objectOwner++) { for (ulong sender = 0; sender <= numberOfClientsULong; sender++) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeIntegrationTest.cs index f6f8147dc4..d5e404a16e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeIntegrationTest.cs @@ -1,4 +1,3 @@ -#if !MULTIPLAYER_TOOLS using System; using System.Collections; using NUnit.Framework; @@ -200,4 +199,3 @@ public override int GetHashCode() } } } -#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs index 840204d4a5..4e29237a82 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs @@ -1,4 +1,3 @@ -#if !MULTIPLAYER_TOOLS using System.Collections; using NUnit.Framework; using Unity.Netcode.Components; @@ -233,4 +232,3 @@ public IEnumerator TransformInterpolationTest() } } } -#endif From 0188d9529d970368bc429e85222412fd829d21af Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 30 Oct 2025 14:58:45 -0400 Subject: [PATCH 08/10] fix: Ensure DisconnectReason remains parsable (#3766) --- .../Connection/NetworkConnectionManager.cs | 38 ++++++------------- .../{ => Messages}/DisconnectReasonMessage.cs | 2 +- .../DisconnectReasonMessage.cs.meta | 0 3 files changed, 13 insertions(+), 27 deletions(-) rename com.unity.netcode.gameobjects/Runtime/Messaging/{ => Messages}/DisconnectReasonMessage.cs (97%) rename com.unity.netcode.gameobjects/Runtime/Messaging/{ => Messages}/DisconnectReasonMessage.cs.meta (100%) diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index 1c60bfa1c9..51f5dc2aeb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -113,36 +113,21 @@ public sealed class NetworkConnectionManager /// client that disconnected. It is recommended to copy the message to some other property or field when /// is invoked. /// - public string DisconnectReason - { - get - { - // For in-frequent event driven invocations, a method within a getter - // is "generally ok". - return GetDisconnectReason(); - } - internal set - { - m_DisconnectReason = value; - } - } + public string DisconnectReason => GetDisconnectReason(); // fine as function because this call is infrequent /// - /// Returns the conbined result of the locally applied and the - /// server applied . - /// - If both values are empty or null, then it returns . - /// - If either value is valid, then it returns that value. - /// - If both values are valid, then it returns followed by a - /// new line and then . + /// Gets the reason for why this client was disconnected if exists. /// - /// A disconnect reason, if any. + /// disconnect reason if it exists, otherwise . [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal string GetDisconnectReason(string header = null) + internal string GetDisconnectReason() { - var disconnectReason = string.IsNullOrEmpty(m_DisconnectReason) ? string.Empty : m_DisconnectReason; - var serverDisconnectReason = string.IsNullOrEmpty(ServerDisconnectReason) ? string.Empty : $"\n{ServerDisconnectReason}"; - var headerInfo = string.IsNullOrEmpty(header) ? string.Empty : header; - return $"{headerInfo}{disconnectReason}{serverDisconnectReason}"; + // TODO: fix this properly + if (!string.IsNullOrEmpty(ServerDisconnectReason)) + { + return ServerDisconnectReason; + } + return m_DisconnectReason; } /// @@ -590,7 +575,8 @@ private void GenerateDisconnectInformation(ulong clientId, ulong transportClient if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) { - NetworkLog.LogInfo($"{DisconnectReason}"); + var serverDisconnectReason = string.IsNullOrEmpty(ServerDisconnectReason) ? string.Empty : $"\n{ServerDisconnectReason}"; + NetworkLog.LogInfo($"{m_DisconnectReason}{serverDisconnectReason}"); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/DisconnectReasonMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DisconnectReasonMessage.cs similarity index 97% rename from com.unity.netcode.gameobjects/Runtime/Messaging/DisconnectReasonMessage.cs rename to com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DisconnectReasonMessage.cs index 1e698df9a4..1a8bf88dcc 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/DisconnectReasonMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DisconnectReasonMessage.cs @@ -10,7 +10,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) { string reasonSent = Reason ?? string.Empty; - // Since we don't send a ConnectionApprovedMessage, the version for this message is encded with the message itself. + // Since we don't send a ConnectionApprovedMessage, the version for this message is encoded 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); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/DisconnectReasonMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DisconnectReasonMessage.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Runtime/Messaging/DisconnectReasonMessage.cs.meta rename to com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DisconnectReasonMessage.cs.meta From f622cb165d256096de43a43326252d736af157e4 Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 30 Oct 2025 16:10:45 -0400 Subject: [PATCH 09/10] fix: NetworkAnimator initialization sequencing (#3767) * fix: NetworkAnimator initialization sequencing * Update changelog --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Components/NetworkAnimator.cs | 9 ++++++--- .../Runtime/Core/NetworkBehaviour.cs | 4 ++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 6585a7dc93..2c5d220a40 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -60,6 +60,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Initialization errors with NetworkAnimator. (#3767) - Multiple disconnect events from the same transport will no longer disconnect the host. (#3707) - Fixed NetworkTransform state synchronization issue when `NetworkTransform.SwitchTransformSpaceWhenParented` is enabled and the associated NetworkObject is parented multiple times in a single frame or within a couple of frames. (#3664) - Fixed issue when spawning, parenting, and immediately re-parenting when `NetworkTransform.SwitchTransformSpaceWhenParented` is enabled. (#3664) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs index b4ca574471..e5f8d7f9f1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs @@ -720,13 +720,16 @@ internal AnimationMessage GetAnimationMessage() return m_AnimationMessage; } - /// - public override void OnNetworkSpawn() + internal override void InternalOnNetworkPreSpawn(ref NetworkManager networkManager) { // Save internal state references - m_LocalNetworkManager = NetworkManager; + m_LocalNetworkManager = networkManager; DistributedAuthorityMode = m_LocalNetworkManager.DistributedAuthorityMode; + } + /// + public override void OnNetworkSpawn() + { // If there is no assigned Animator then generate a server network warning (logged locally and if applicable on the server-host side as well). if (m_Animator == null) { diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index ae232df523..a76066c237 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -753,6 +753,8 @@ public virtual void OnNetworkDespawn() { } /// public virtual void OnNetworkPreDespawn() { } + internal virtual void InternalOnNetworkPreSpawn(ref NetworkManager networkManager) { } + internal void NetworkPreSpawn(ref NetworkManager networkManager, NetworkObject networkObject) { m_NetworkObject = networkObject; @@ -761,6 +763,8 @@ internal void NetworkPreSpawn(ref NetworkManager networkManager, NetworkObject n UpdateNetworkProperties(); + InternalOnNetworkPreSpawn(ref networkManager); + // Exit early for disabled NetworkBehaviours. // We still want the above values to be set. if (!gameObject.activeInHierarchy) From fb943eb1099cf3ffc8e7328d3466b3e8a60353a7 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 31 Oct 2025 05:24:35 -0500 Subject: [PATCH 10/10] fix: 2.7.0 test instabilities (#3768) * test - fix Resolving the PeerDisconnectCallbackTests instability where sometimes the disconnect notification is processed prior to the targeted client's NetworkManager having completely disconnected and shutdown (this would only have a chance of happening if server disconnects the client). Hoping to resolve the instability with the ParentingInSceneObjectsTests resulting in a false failure. * test fix Fixing potential cause for instabilities on this set of tests due to the initial network prefab transform settings not being applied to the prefab but applied to the spawned instances which could be causing issues since they were being applied to the non-authority instances. This fix moves the applied test NetworkTrransform settings to the player prefab prior to spawning any instances. --- .../NetworkTransform/NetworkTransformBase.cs | 18 +++++------------ .../Runtime/PeerDisconnectCallbackTests.cs | 20 +++++++++++++++++-- .../ParentingInSceneObjectsTests.cs | 8 ++++---- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs index cc7491eeec..ba03c78fb4 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs @@ -230,6 +230,11 @@ protected override void OnCreatePlayerPrefab() { var networkTransformTestComponent = m_PlayerPrefab.AddComponent(); networkTransformTestComponent.ServerAuthority = m_Authority == Authority.ServerAuthority; + // Handle setting up additional transform settings for the current test here. + networkTransformTestComponent.UseUnreliableDeltas = UseUnreliableDeltas(); + networkTransformTestComponent.UseHalfFloatPrecision = m_Precision == Precision.Half; + networkTransformTestComponent.UseQuaternionSynchronization = m_Rotation == Rotation.Quaternion; + networkTransformTestComponent.UseQuaternionCompression = m_RotationCompression == RotationCompression.QuaternionCompress; } protected override void OnServerAndClientsCreated() @@ -291,19 +296,6 @@ protected virtual void OnClientsAndServerConnectedSetup() // Get the NetworkTransformTestComponent to make sure the client side is ready before starting test m_AuthoritativeTransform = m_AuthoritativePlayer.GetComponent(); m_NonAuthoritativeTransform = m_NonAuthoritativePlayer.GetComponent(); - - // Setup whether we are or are not using unreliable deltas - m_AuthoritativeTransform.UseUnreliableDeltas = UseUnreliableDeltas(); - m_NonAuthoritativeTransform.UseUnreliableDeltas = UseUnreliableDeltas(); - - m_AuthoritativeTransform.UseHalfFloatPrecision = m_Precision == Precision.Half; - m_AuthoritativeTransform.UseQuaternionSynchronization = m_Rotation == Rotation.Quaternion; - m_AuthoritativeTransform.UseQuaternionCompression = m_RotationCompression == RotationCompression.QuaternionCompress; - m_NonAuthoritativeTransform.UseHalfFloatPrecision = m_Precision == Precision.Half; - m_NonAuthoritativeTransform.UseQuaternionSynchronization = m_Rotation == Rotation.Quaternion; - m_NonAuthoritativeTransform.UseQuaternionCompression = m_RotationCompression == RotationCompression.QuaternionCompress; - - m_OwnerTransform = m_AuthoritativeTransform.IsOwner ? m_AuthoritativeTransform : m_NonAuthoritativeTransform; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs index 896aa90bf5..b7512753a4 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs @@ -83,9 +83,21 @@ private void OnConnectionEventCallback(NetworkManager networkManager, Connection } } + private bool m_TargetClientShutdown; + private NetworkManager m_TargetClient; + + private void ClientToDisconnect_OnClientStopped(bool wasHost) + { + m_TargetClient.OnClientStopped -= ClientToDisconnect_OnClientStopped; + m_TargetClientShutdown = true; + } + [UnityTest] public IEnumerator TestPeerDisconnectCallback([Values] ClientDisconnectType clientDisconnectType, [Values(1ul, 2ul, 3ul)] ulong disconnectedClient) { + m_TargetClientShutdown = false; + m_TargetClient = m_ClientNetworkManagers[disconnectedClient - 1]; + m_TargetClient.OnClientStopped += ClientToDisconnect_OnClientStopped; foreach (var client in m_ClientNetworkManagers) { client.OnConnectionEvent += OnConnectionEventCallback; @@ -129,12 +141,15 @@ public IEnumerator TestPeerDisconnectCallback([Values] ClientDisconnectType clie } else { - yield return StopOneClient(m_ClientNetworkManagers[disconnectedClient - 1]); + yield return StopOneClient(m_TargetClient); } yield return WaitForConditionOrTimeOut(hooks); + AssertOnTimeout($"Timed out waiting for all clients to receive the {nameof(ClientDisconnectedMessage)}!"); - Assert.False(s_GlobalTimeoutHelper.TimedOut); + // Make sure the target client is shutdown before performing validation + yield return WaitForConditionOrTimeOut(() => m_TargetClientShutdown); + AssertOnTimeout($"Timed out waiting for {m_TargetClient.name} to shutdown!"); foreach (var client in m_ClientNetworkManagers) { @@ -182,5 +197,6 @@ public IEnumerator TestPeerDisconnectCallback([Values] ClientDisconnectType clie // Host receives peer disconnect, dedicated server does not Assert.AreEqual(m_UseHost ? 3 : 2, m_PeerDisconnectCount); } + } } diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs index b227eb2fb8..ac5a7b47d8 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs @@ -332,21 +332,21 @@ public IEnumerator InSceneParentingTest([Values] ParentingSpace parentingSpace) { InSceneParentChildHandler.AuthorityRootParent.DeparentSetValuesAndReparent(); - yield return WaitForConditionOrTimeOut(ValidateClientsAgainstAuthorityTransformValues); + yield return WaitForConditionOrTimeOut(() => ValidateAllChildrenParentingStatus(true)); if (s_GlobalTimeoutHelper.TimedOut) { InSceneParentChildHandler.AuthorityRootParent.CheckChildren(); yield return debugWait; } - AssertOnTimeout($"[Final Pass][Deparent-Reparent-{i}] Timed out waiting for all clients transform values to match the server transform values!\n {m_ErrorValidationLog}"); + AssertOnTimeout($"[Final Pass][Deparent-Reparent-{i}] Timed out waiting for all children to be removed from their parent!\n {m_ErrorValidationLog}"); - yield return WaitForConditionOrTimeOut(() => ValidateAllChildrenParentingStatus(true)); + yield return WaitForConditionOrTimeOut(ValidateClientsAgainstAuthorityTransformValues); if (s_GlobalTimeoutHelper.TimedOut) { InSceneParentChildHandler.AuthorityRootParent.CheckChildren(); yield return debugWait; } - AssertOnTimeout($"[Final Pass][Deparent-Reparent-{i}] Timed out waiting for all children to be removed from their parent!\n {m_ErrorValidationLog}"); + AssertOnTimeout($"[Final Pass][Deparent-Reparent-{i}] Timed out waiting for all clients transform values to match the server transform values!\n {m_ErrorValidationLog}"); } // In the final pass, we remove the second generation nested child